mirror of
https://github.com/chatopera/cosin.git
synced 2025-06-16 18:30:03 +08:00
Merge branch 'develop' into feature/964
This commit is contained in:
commit
acca35dd51
30
.gitee/ISSUE_TEMPLATE/01_help.yml
Normal file
30
.gitee/ISSUE_TEMPLATE/01_help.yml
Normal file
@ -0,0 +1,30 @@
|
||||
name: 求助
|
||||
description: 开发环境搭建、功能咨询和使用问题等
|
||||
labels: ["help-wanted"]
|
||||
assignees:
|
||||
- zhangchanglong2021
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## 描述"
|
||||
- type: textarea
|
||||
id: content1
|
||||
attributes:
|
||||
label: "详细描述问题后优先处理解决! 截图、错误日志等"
|
||||
value: |
|
||||
1. 针对某功能,需要提供详细描述文档
|
||||
2. xxx
|
||||
3. xxx
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: content2
|
||||
attributes:
|
||||
label: "代码版本"
|
||||
value: |
|
||||
1. Git commit hash (`git rev-parse HEAD`),进入代码库并执行
|
||||
或
|
||||
2. v7, v8, v9 ...
|
||||
validations:
|
||||
required: true
|
30
.gitee/ISSUE_TEMPLATE/02_bug.yml
Normal file
30
.gitee/ISSUE_TEMPLATE/02_bug.yml
Normal file
@ -0,0 +1,30 @@
|
||||
name: 软件缺陷
|
||||
description: 报告软件缺陷
|
||||
labels: ["bug"]
|
||||
assignees:
|
||||
- kaifuny
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## 描述"
|
||||
- type: textarea
|
||||
id: content1
|
||||
attributes:
|
||||
label: "详细描述问题后优先处理解决! 截图、错误日志等"
|
||||
value: |
|
||||
1. 如何重现
|
||||
2. xxx
|
||||
3. xxx
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: content2
|
||||
attributes:
|
||||
label: "代码版本"
|
||||
value: |
|
||||
1. Git commit hash (`git rev-parse HEAD`),进入代码库并执行
|
||||
或
|
||||
2. v7, v8, v9 ...
|
||||
validations:
|
||||
required: true
|
30
.gitee/ISSUE_TEMPLATE/03_requirement.yml
Normal file
30
.gitee/ISSUE_TEMPLATE/03_requirement.yml
Normal file
@ -0,0 +1,30 @@
|
||||
name: 需求
|
||||
description: 增加新需求、反馈建议
|
||||
labels: ["requirement"]
|
||||
assignees:
|
||||
- kaifuny
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## 描述"
|
||||
- type: textarea
|
||||
id: content1
|
||||
attributes:
|
||||
label: "详细描述需求"
|
||||
value: |
|
||||
1. xxx 模块需要支持 xxx 功能
|
||||
2. xxx
|
||||
3. xxx
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: content2
|
||||
attributes:
|
||||
label: "代码版本"
|
||||
value: |
|
||||
1. Git commit hash (`git rev-parse HEAD`),进入代码库并执行
|
||||
或
|
||||
2. v7, v8, v9 ...
|
||||
validations:
|
||||
required: true
|
30
.gitee/ISSUE_TEMPLATE/04_profiling.yml
Normal file
30
.gitee/ISSUE_TEMPLATE/04_profiling.yml
Normal file
@ -0,0 +1,30 @@
|
||||
name: 性能优化
|
||||
description: 瓶颈分析、性能优化建议和安全漏洞等
|
||||
labels: ["profiling"]
|
||||
assignees:
|
||||
- lecjy
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## 描述"
|
||||
- type: textarea
|
||||
id: content1
|
||||
attributes:
|
||||
label: "详细描述需求"
|
||||
value: |
|
||||
1. xxx 模块需要支持 xxx 功能
|
||||
2. xxx
|
||||
3. xxx
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: content2
|
||||
attributes:
|
||||
label: "代码版本"
|
||||
value: |
|
||||
1. Git commit hash (`git rev-parse HEAD`),进入代码库并执行
|
||||
或
|
||||
2. v7, v8, v9 ...
|
||||
validations:
|
||||
required: true
|
35
.gitee/ISSUE_TEMPLATE/05_userstory.yml
Normal file
35
.gitee/ISSUE_TEMPLATE/05_userstory.yml
Normal file
@ -0,0 +1,35 @@
|
||||
name: 用户故事
|
||||
description: 用户故事
|
||||
title: "[us] "
|
||||
labels: ["userstory"]
|
||||
assignees:
|
||||
- kaifuny
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## 描述"
|
||||
- type: textarea
|
||||
id: content1
|
||||
attributes:
|
||||
label: "详细描述需求"
|
||||
value: |
|
||||
## 用户主体
|
||||
|
||||
## 用户故事
|
||||
|
||||
## 交互流程
|
||||
|
||||
## 交互细节
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: content2
|
||||
attributes:
|
||||
label: "代码版本"
|
||||
value: |
|
||||
1. Git commit hash (`git rev-parse HEAD`),进入代码库并执行
|
||||
或
|
||||
2. v7, v8, v9 ...
|
||||
validations:
|
||||
required: true
|
11
.gitee/ISSUE_TEMPLATE/config.yaml
Normal file
11
.gitee/ISSUE_TEMPLATE/config.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 文档中心
|
||||
url: https://docs.cskefu.com/
|
||||
about: 提供春松客服使用指南、教程、基本功能使用、介绍和常见问题解答
|
||||
- name: 春松客服大讲堂
|
||||
url: https://gitee.com/cskefu/cskefu-djt/blob/main/README.md
|
||||
about: 提供春松客服定制化开发技能课程
|
||||
- name: 商务洽谈
|
||||
url: https://www.chatopera.com/price.html
|
||||
about: 提供春松客服定制化开发、机器人客服平台等
|
@ -1,13 +1,21 @@
|
||||
---
|
||||
reviewers : cskefu/reviewers
|
||||
---
|
||||
|
||||
<!--- 在标题中简略说明问题 -->
|
||||
|
||||
## 描述
|
||||
<!--- 详细的描述变更 -->
|
||||
|
||||
### 关联 Issue #
|
||||
|
||||
## 解决的问题
|
||||
|
||||
<!--- 为什么变更是必要的? -->
|
||||
<!--- 如果这个PR解决了其他Issue,添加链接 -->
|
||||
|
||||
## 测试情况
|
||||
|
||||
<!--- 详细介绍怎么测试变更了 -->
|
||||
<!--- 介绍测试环境 -->
|
||||
<!--- 变更对其他代码的影响 -->
|
||||
@ -15,13 +23,17 @@
|
||||
## 截屏
|
||||
|
||||
## 变更的类型
|
||||
|
||||
<!--- 变更有哪些特点,添加 `x` 到下面的对应项目中: -->
|
||||
- [ ] 解决Bug
|
||||
|
||||
- [ ] 解决 Bug
|
||||
- [ ] 新功能(不影响其他功能)
|
||||
- [ ] 对其他功能有影响
|
||||
|
||||
## 检查:
|
||||
## 检查
|
||||
|
||||
<!--- 检查下面,各项,添加 `x` 到下面的对应项目中: -->
|
||||
|
||||
- [ ] 我的变更和代码规范一致
|
||||
- [ ] 我的变更需要更新文档
|
||||
- [ ] 我已经更新了对应的文档
|
9
.gitee/PULL_REQUEST_TEMPLATE/documentation.md
Normal file
9
.gitee/PULL_REQUEST_TEMPLATE/documentation.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
reviewers : cskefu/reviewers
|
||||
---
|
||||
|
||||
### Requirements for Contributing Documentation
|
||||
|
||||
## 变更说明
|
||||
|
||||
### 关联 Issue #
|
9
.gitee/PULL_REQUEST_TEMPLATE/performance_improvement.md
Normal file
9
.gitee/PULL_REQUEST_TEMPLATE/performance_improvement.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
reviewers : cskefu/reviewers
|
||||
---
|
||||
|
||||
### Requirements for Contributing a Performance Improvement
|
||||
|
||||
## 性能提升
|
||||
|
||||
### 关联 Issue #
|
12
.github/ISSUE_TEMPLATE.md
vendored
12
.github/ISSUE_TEMPLATE.md
vendored
@ -1,12 +0,0 @@
|
||||
# 描述
|
||||
|
||||
## 现在行为
|
||||
|
||||
## 预期行为
|
||||
|
||||
# 解决方案
|
||||
|
||||
# 环境
|
||||
|
||||
* 代码版本:
|
||||
Git commit hash (`git rev-parse HEAD`)
|
19
.github/workflows/gitee.yml
vendored
19
.github/workflows/gitee.yml
vendored
@ -1,19 +0,0 @@
|
||||
name: gitee
|
||||
|
||||
on:
|
||||
- push
|
||||
- delete
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
name: Git Repo Sync
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: wangchucheng/git-repo-sync@v0.1.0
|
||||
with:
|
||||
target-url: https://gitee.com/cskefu/cskefu.git
|
||||
target-username: ${{ secrets.USERNAME }}
|
||||
target-token: ${{ secrets.ACCESS_TOKEN }}
|
@ -1,10 +1,8 @@
|
||||
# Contributing
|
||||
|
||||
成为春松客服贡献者
|
||||
|
||||
## 提交反馈
|
||||
|
||||
在[春松客服 GitHub Issues](https://github.com/cskefu/cskefu/issues)中,先搜索是否有重复的。然后进行补充或新建 Issue。
|
||||
在[春松客服 Issues](https://gitee.com/cskefu/cskefu/issues)中,先搜索是否有重复的。然后进行补充或新建 Issue。
|
||||
|
||||
## 提交代码
|
||||
|
||||
@ -15,25 +13,14 @@
|
||||
|
||||
春松客服文档中心的项目,也是开源的,地址在:
|
||||
|
||||
[https://github.com/cskefu/cskefu-docs](https://github.com/cskefu/cskefu-docs)
|
||||
[https://gitee.com/cskefu/docs](https://gitee.com/cskefu/docs)
|
||||
|
||||
春松客服文档 Markdown 文件路径:
|
||||
|
||||
[https://github.com/cskefu/cskefu-docs/tree/main/docs](https://github.com/cskefu/cskefu-docs/tree/main/docs)
|
||||
[https://gitee.com/cskefu/docs/tree/main/docs](https://gitee.com/cskefu/docs/tree/main/docs)
|
||||
|
||||
更新文档:
|
||||
|
||||
1)提交 PR 到[春松客服文档中心 GitHub 仓库](https://github.com/cskefu/cskefu-docs/tree/main/docs),一步到位。
|
||||
1)提交 PR 到[春松客服文档中心 Git 仓库](https://gitee.com/cskefu/docs/tree/main),一步到位。
|
||||
|
||||
2)提交 Issue 到[春松客服 Issues](https://github.com/cskefu/cskefu/issues/new), 并撰写文档内容,使用该方案则需要后续其他协作者提交到 [春松客服文档中心 GitHub 仓库](https://github.com/cskefu/cskefu-docs) 中。
|
||||
|
||||
## 期待您成为春松客服贡献者
|
||||
|
||||
不管是何种贡献,只有大小之分,而无本质区别。
|
||||
|
||||
一起贡献,一起好!
|
||||
没有贡献,都不好!
|
||||
|
||||
团结是共赢,分裂是全输。
|
||||
|
||||

|
||||
2)提交 Issue 到[春松客服 Issues](https://gitee.com/cskefu/cskefu/issues/new), 并撰写文档内容,使用该方案则需要后续其他协作者提交到 [春松客服文档中心 Git 仓库](https://gitee.com/cskefu/docs) 中。
|
||||
|
58
README.md
58
README.md
@ -31,18 +31,6 @@
|
||||
|
||||
新版本介绍:[观看春松客服 v8 新版本发布会 @ 2023-07-01](https://www.cskefu.com/2023/07/03/community-conf/)
|
||||
|
||||
## 媒体报道
|
||||
|
||||
<img src="./public/assets/cskefu-gpv-2.png" height = "220" div align=right />
|
||||
|
||||
- [春松客服:通过开源加云原生模式,大规模交付智能客服系统](https://www.cskefu.com/2022/04/11/cskefu-opensource-plus-cloud-model/)
|
||||
|
||||
- [春松客服荣获 GVP 企业级开源项目认证](http://www.ctiforum.com/news/guonei/578988.html)
|
||||
|
||||
- [Chatopera 王海良:做好开源客服系统 | OpenTEKr 专访](https://www.bilibili.com/video/BV1qF411p7hW)
|
||||
|
||||
---
|
||||
|
||||
## 开发者列表 ✨
|
||||
|
||||
:evergreen_tree: 春松客服是开源的智能客服系统,于 2018 年 9 月由 [Chatopera](https://www.chatopera.com) 发布,在开源社区协作中优化和完善,春松客服属于[春松客服开源社区](https://github.com/cskefu/cskefu#%E6%98%A5%E6%9D%BE%E5%AE%A2%E6%9C%8D%E5%BC%80%E6%BA%90%E7%A4%BE%E5%8C%BA)。
|
||||
@ -134,27 +122,35 @@
|
||||
|
||||
<p align="center">
|
||||
<b>欢迎页</b><br>
|
||||
<img src="https://static-public.chatopera.com/assets/images/cskefu/cskefu-screen-1.jpg" width="900">
|
||||
<img src="./public/assets/cskefu-screen-1.jpg" width="900">
|
||||
</p>
|
||||
|
||||
### 坐席工作台
|
||||
<details>
|
||||
<summary>展开查看更多产品截图</summary>
|
||||
<p>
|
||||
|
||||
登录演示环境,查看更多产品能力:[https://demo.cskefu.com/](https://demo.cskefu.com/)
|
||||
<p align="center">
|
||||
<b>坐席工作台</b><br>
|
||||
<img src="./public/assets/44915582-eb8d2c80-ad65-11e8-8876-86c8b5bb5cc7.png" width="900">
|
||||
</p>
|
||||
|
||||
| **登录账号** | **密码** | **角色** |
|
||||
| ------------ | --------- | -------------- |
|
||||
| admin | admin1234 | 系统超级管理员 |
|
||||
| zhangsan | agent1234 | 客服坐席人员 |
|
||||
<p align="center">
|
||||
<b>坐席监控</b><br>
|
||||
<img src="./public/assets/44915711-432b9800-ad66-11e8-899b-1ea02244925d.png" width="900">
|
||||
</p>
|
||||
|
||||
### 网页端访客示例
|
||||
<p align="center">
|
||||
<b>集成客服机器人</b><br>
|
||||
<img src="./public/assets/51080565-4b82df00-1719-11e9-8cc4-dbbec0459224.png" width="900">
|
||||
</p>
|
||||
|
||||
[https://demo.cskefu.com/testclient.html](http://demo.cskefu.com/testclient.html)
|
||||
<p align="center">
|
||||
<b>客服机器人应答</b><br>
|
||||
<img src="./public/assets/51080567-50479300-1719-11e9-85d8-d209370c9d10.png" width="900">
|
||||
</p>
|
||||
|
||||
- 登录张三后可接待访客,否则显示没有客服人员在线
|
||||
|
||||
### 机器人客服示例
|
||||
|
||||
[https://oh-my.cskefu.com/im/text/0nhckh.html](https://oh-my.cskefu.com/im/text/0nhckh.html)
|
||||
</p>
|
||||
</details>
|
||||
|
||||
## 快速开始
|
||||
|
||||
@ -210,13 +206,11 @@
|
||||
在春松客服开源社区,我们建立关系、发现认同、合作共赢!
|
||||
|
||||
- 了解春松客服采用的开源许可协议,参考[文档](https://www.cskefu.com/2023/06/25/chunsong-public-license-1-0/)
|
||||
- 了解春松客服的开发计划,参考[文档](https://chatopera.github.io/cskefu.roadmap/)
|
||||
- 加入开源社区运营,成为社区合伙人,参考[文档](https://mp.weixin.qq.com/s/TLE87YX4k097iOXnV4WVSw)
|
||||
- 加入春松客服开源社区,参考[文档](https://www.cskefu.com/join-us/)
|
||||
- 了解春松客服的开发计划,参考[文档](https://github.com/cskefu/cskefu/issues)
|
||||
- 如何发布春松客服人物志,向社区介绍自己,参考[文档](https://www.cskefu.com/join-us/)
|
||||
- 如何提交反馈、文档,参考[文档](./CONTRIBUTING.md)
|
||||
- 如何成为春松客服开发者,参考[文档](https://docs.cskefu.com/docs/osc/devonboard/)
|
||||
- 如何提交代码,参考[文档](https://docs.cskefu.com/docs/osc/contribution)
|
||||
- 如何最新的春松客服开发进展:订阅[春松客服邮件列表](https://lists.cskefu.com/cgi-bin/mailman/listinfo/dev)
|
||||
- 如何获得春松客服商业插件和服务,参考[文档](https://www.chatopera.com/price.html)
|
||||
|
||||
春松客服之所以开源,是基于这样一种信念:爱人也是爱己,利他也是利己。
|
||||
因春松客服受益,而不回报开源社区的用户,我们不欢迎使用春松客服:我们开源并不是为了你们,你们是不被祝福的。
|
||||
@ -241,7 +235,7 @@
|
||||
- [IDE 配置和使用之 VSCode](https://docs.cskefu.com/docs/osc/ide_vscode)
|
||||
- 定制开发技能
|
||||
- [系统集成之 RestAPIs](https://docs.cskefu.com/docs/osc/restapi)
|
||||
- [从零开始学习定制春松客服技能:春松客服大讲堂 PPT 课件及视频](https://github.com/cskefu/cskefu.djt)
|
||||
- [从零开始学习定制春松客服技能:春松客服大讲堂 PPT 课件及视频](https://store.chatopera.com/product/cskfdjt19)
|
||||
- [掌握春松客服前端框架 Pugjs,介绍及使用注意事项](https://blog.csdn.net/samurais/article/details/114576611)
|
||||
- [提交代码](https://docs.cskefu.com/docs/osc/contribution)
|
||||
|
||||
|
@ -88,6 +88,7 @@
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>3.1.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
|
@ -27,8 +27,10 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 坐席自动分配策略集
|
||||
@ -58,6 +60,8 @@ public class ACDPolicyService {
|
||||
@Autowired
|
||||
private OrganProxy organProxy;
|
||||
|
||||
@Autowired
|
||||
private OrganRepository organRepository;
|
||||
/**
|
||||
* 载入坐席 ACD策略配置
|
||||
*
|
||||
@ -85,12 +89,26 @@ public class ACDPolicyService {
|
||||
if ((sessionConfig = cache.findOneSessionConfig(organid)) == null) {
|
||||
sessionConfig = sessionConfigRes.findBySkill(organid);
|
||||
if (sessionConfig == null) {
|
||||
sessionConfig = new SessionConfig();
|
||||
List<Organ> list = organRepository.findAll();
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return new SessionConfig();
|
||||
} else {
|
||||
Map<String, String> map = list.stream().collect(Collectors.toMap(item -> item.getId(), item -> item.getParent()));
|
||||
List<SessionConfig> configList = sessionConfigRes.findAll();
|
||||
if (CollectionUtils.isEmpty(configList)) {
|
||||
return new SessionConfig();
|
||||
} else {
|
||||
Map<String, SessionConfig> skillMap = configList.stream().collect(Collectors.toMap(item -> item.getSkill(), item -> item));
|
||||
if (map.get(organid) == null || skillMap.get(map.get(organid)) == null) {
|
||||
return new SessionConfig();
|
||||
}
|
||||
return skillMap.get(map.get(organid));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cache.putSessionConfig(sessionConfig, organid);
|
||||
}
|
||||
}
|
||||
|
||||
return sessionConfig;
|
||||
}
|
||||
|
||||
@ -394,4 +412,4 @@ public class ACDPolicyService {
|
||||
return agentUserRes.countByAgentnoAndStatusAndSkill(agentStatus.getAgentno(), MainContext.AgentUserStatusEnum.INSERVICE.toString(), skill);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
/*
|
||||
* 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,
|
||||
* 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
|
||||
*/
|
||||
|
||||
@ -22,12 +22,14 @@ 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.model.ExecuteResult;
|
||||
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 com.cskefu.cc.proxy.LicenseProxy;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -62,6 +64,9 @@ public class ACDVisBodyParserMw implements Middleware<ACDComposeContext> {
|
||||
@Autowired
|
||||
private ACDMessageHelper acdMessageHelper;
|
||||
|
||||
@Autowired
|
||||
private LicenseProxy licenseProxy;
|
||||
|
||||
/**
|
||||
* 设置AgentUser基本信息
|
||||
*
|
||||
@ -87,6 +92,16 @@ public class ACDVisBodyParserMw implements Middleware<ACDComposeContext> {
|
||||
ctx.getOnlineUserId(),
|
||||
ctx.getOnlineUserNickname(),
|
||||
ctx.getAppid());
|
||||
|
||||
// 执行计费逻辑
|
||||
ExecuteResult writeDownResult = licenseProxy.writeDownAgentUserUsageInStore(p);
|
||||
|
||||
if (writeDownResult.getRc() != ExecuteResult.RC_SUCC) {
|
||||
// 配额操作失败,提示座席
|
||||
p.setLicenseVerifiedPass(false);
|
||||
p.setLicenseBillingMsg(writeDownResult.getMsg());
|
||||
}
|
||||
|
||||
logger.info("[apply] create new agent user id {}", p.getId());
|
||||
return p;
|
||||
});
|
||||
|
@ -1,14 +1,14 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
/*
|
||||
* 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,
|
||||
* 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
|
||||
*/
|
||||
|
||||
@ -18,14 +18,17 @@ 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.exception.BillingResourceException;
|
||||
import com.cskefu.cc.model.AgentUser;
|
||||
import com.cskefu.cc.proxy.AgentAuditProxy;
|
||||
import com.cskefu.cc.proxy.LicenseProxy;
|
||||
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.aspectj.lang.annotation.Before;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -51,6 +54,31 @@ public class AgentUserAspect {
|
||||
@Autowired
|
||||
private AgentAuditProxy agentAuditProxy;
|
||||
|
||||
@Autowired
|
||||
private LicenseProxy licenseProxy;
|
||||
|
||||
@Before("execution(* com.cskefu.cc.persistence.repository.AgentUserRepository.save(..))")
|
||||
public void beforeSave(final JoinPoint joinPoint) {
|
||||
final AgentUser agentUser = (AgentUser) joinPoint.getArgs()[0];
|
||||
|
||||
if (StringUtils.isBlank(agentUser.getId())) {
|
||||
logger.info("[beforeSave] agentUser id is blank");
|
||||
|
||||
if (StringUtils.isNotBlank(agentUser.getOpttype()) && StringUtils.equals(MainContext.OptType.CHATBOT.toString(), agentUser.getOpttype())) {
|
||||
// 机器人座席支持的对话,跳过计数
|
||||
agentUser.setLicenseVerifiedPass(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 计数加一
|
||||
try {
|
||||
licenseProxy.increResourceUsageInMetaKv(MainContext.BillingResource.AGENGUSER, 1);
|
||||
} catch (BillingResourceException e) {
|
||||
logger.error("[beforeSave] error", e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@After("execution(* com.cskefu.cc.persistence.repository.AgentUserRepository.save(..))")
|
||||
public void save(final JoinPoint joinPoint) {
|
||||
final AgentUser agentUser = (AgentUser) joinPoint.getArgs()[0];
|
||||
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.aspect;
|
||||
|
||||
import com.cskefu.cc.basic.MainContext;
|
||||
import com.cskefu.cc.exception.BillingQuotaException;
|
||||
import com.cskefu.cc.exception.BillingResourceException;
|
||||
import com.cskefu.cc.model.Channel;
|
||||
import com.cskefu.cc.model.User;
|
||||
import com.cskefu.cc.proxy.LicenseProxy;
|
||||
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 ChannelAspect {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ChannelAspect.class);
|
||||
|
||||
@Autowired
|
||||
private LicenseProxy licenseProxy;
|
||||
|
||||
@Before("execution(* com.cskefu.cc.persistence.repository.ChannelRepository.save(..))")
|
||||
public void beforeSave(final JoinPoint joinPoint) throws BillingResourceException, BillingQuotaException {
|
||||
final Channel channel = (Channel) joinPoint.getArgs()[0];
|
||||
logger.info("[beforeSave] before channel id {}, type {}", channel.getId(), channel.getType());
|
||||
if (StringUtils.isBlank(channel.getId())) {
|
||||
// create new Channel
|
||||
if (StringUtils.equals(channel.getType(), MainContext.ChannelType.WEBIM.toString())) {
|
||||
// create new WEBIM channel
|
||||
licenseProxy.writeDownResourceUsageInStore(MainContext.BillingResource.CHANNELWEBIM, 1);
|
||||
}
|
||||
} else {
|
||||
// update existed Channel
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.aspect;
|
||||
|
||||
import com.cskefu.cc.basic.MainContext;
|
||||
import com.cskefu.cc.basic.MainUtils;
|
||||
import com.cskefu.cc.exception.BillingQuotaException;
|
||||
import com.cskefu.cc.exception.BillingResourceException;
|
||||
import com.cskefu.cc.model.Contacts;
|
||||
import com.cskefu.cc.model.User;
|
||||
import com.cskefu.cc.proxy.LicenseProxy;
|
||||
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 ContactsAspect {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ContactsAspect.class);
|
||||
|
||||
@Autowired
|
||||
private LicenseProxy licenseProxy;
|
||||
|
||||
@Before("execution(* com.cskefu.cc.persistence.repository.ContactsRepository.save(..))")
|
||||
public void beforeSave(final JoinPoint joinPoint) throws BillingResourceException, BillingQuotaException {
|
||||
final Contacts contacts = (Contacts) joinPoint.getArgs()[0];
|
||||
logger.info("[save] before contacts id {}", contacts.getId());
|
||||
if (StringUtils.isBlank(contacts.getId())) {
|
||||
// 执行配额扣除
|
||||
licenseProxy.writeDownResourceUsageInStore(MainContext.BillingResource.CONTACT, 1);
|
||||
contacts.setId(MainUtils.getUUID());
|
||||
} else {
|
||||
// update existed user
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.aspect;
|
||||
|
||||
import com.cskefu.cc.basic.MainContext;
|
||||
import com.cskefu.cc.exception.BillingQuotaException;
|
||||
import com.cskefu.cc.exception.BillingResourceException;
|
||||
import com.cskefu.cc.model.Channel;
|
||||
import com.cskefu.cc.model.Organ;
|
||||
import com.cskefu.cc.proxy.LicenseProxy;
|
||||
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 OrganAspect {
|
||||
private final static Logger logger = LoggerFactory.getLogger(OrganAspect.class);
|
||||
|
||||
@Autowired
|
||||
private LicenseProxy licenseProxy;
|
||||
|
||||
@Before("execution(* com.cskefu.cc.persistence.repository.OrganRepository.save(..))")
|
||||
public void beforeSave(final JoinPoint joinPoint) throws BillingResourceException, BillingQuotaException {
|
||||
final Organ organ = (Organ) joinPoint.getArgs()[0];
|
||||
logger.info("[beforeSave] before organ id {}", organ.getId());
|
||||
if (StringUtils.isBlank(organ.getId())) {
|
||||
// create new organ
|
||||
licenseProxy.writeDownResourceUsageInStore(MainContext.BillingResource.ORGAN, 1);
|
||||
} else {
|
||||
// update existed Channel
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.aspect;
|
||||
|
||||
import com.cskefu.cc.basic.MainContext;
|
||||
import com.cskefu.cc.exception.BillingQuotaException;
|
||||
import com.cskefu.cc.exception.BillingResourceException;
|
||||
import com.cskefu.cc.model.User;
|
||||
import com.cskefu.cc.proxy.LicenseProxy;
|
||||
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 UserAspect {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(UserAspect.class);
|
||||
|
||||
@Autowired
|
||||
private LicenseProxy licenseProxy;
|
||||
|
||||
@Before("execution(* com.cskefu.cc.persistence.repository.UserRepository.save(..))")
|
||||
public void beforeSave(final JoinPoint joinPoint) throws BillingResourceException, BillingQuotaException {
|
||||
final User user = (User) joinPoint.getArgs()[0];
|
||||
logger.info("[save] before user id {}", user.getId());
|
||||
if (StringUtils.isBlank(user.getId())) {
|
||||
// 执行配额扣除
|
||||
licenseProxy.writeDownResourceUsageInStore(MainContext.BillingResource.USER, 1);
|
||||
} else {
|
||||
// update existed user
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
/*
|
||||
* 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,
|
||||
* 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;
|
||||
@ -217,4 +217,26 @@ public class Constants {
|
||||
public static final String AUTH_TOKEN_TYPE_BEARER = "Bearer";
|
||||
public static final String AUTH_TOKEN_TYPE_BASIC = "Basic";
|
||||
|
||||
/**
|
||||
* License
|
||||
*/
|
||||
public static final String PRODUCT_ID_CSKEFU001 = "cskefu001";
|
||||
public static final String LICENSE_SERVER_INST_ID = "SERVERINSTID";
|
||||
public static final String LICENSE_SERVICE_NAME = "SERVICENAME";
|
||||
public static final String LICENSE_SERVICE_NAME_PREFIX = "春松客服";
|
||||
public static final String LICENSEIDS = "LICENSEIDS";
|
||||
public static final String METAKV_DATATYPE_STRING = "string";
|
||||
public static final String METAKV_DATATYPE_INT = "int";
|
||||
public static final String SHORTID = "shortId";
|
||||
public static final String LICENSES = "licenses";
|
||||
public static final String ADDDATE = "addDate";
|
||||
public static final String LICENSE = "license";
|
||||
public static final String UPDATETIME = "updateTime";
|
||||
public static final String STATUS = "status";
|
||||
public static final String PRODUCT = "product";
|
||||
public static final String LICENSESTOREPROVIDER = "licenseStoreProvider";
|
||||
public static final String USER = "user";
|
||||
public static final String RESOURCES_USAGE_KEY_PREFIX = "RESOURCES_USAGE";
|
||||
public static final String NEW_USER_SUCCESS = "new_user_success";
|
||||
public static final String PRODUCT_ID = "productId";
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
* 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,
|
||||
* 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,
|
||||
* Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0,
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
@ -929,6 +929,31 @@ public class MainContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计费资源
|
||||
*/
|
||||
public enum BillingResource {
|
||||
USER, // 系统用户
|
||||
AGENGUSER, // 访客会话
|
||||
CONTACT, // 联系人
|
||||
ORGAN, // 组织机构
|
||||
CHANNELWEBIM; // 网页渠道
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase();
|
||||
}
|
||||
|
||||
public static BillingResource toValue(final String str) {
|
||||
for (final BillingResource item : values()) {
|
||||
if (StringUtils.equalsIgnoreCase(item.toString(), str)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setApplicationContext(ApplicationContext context) {
|
||||
applicationContext = context;
|
||||
context.getBean(TerminateBean.class);
|
||||
|
@ -24,6 +24,7 @@ import com.cskefu.cc.model.BlackEntity;
|
||||
import com.cskefu.cc.model.SysDic;
|
||||
import com.cskefu.cc.model.SystemConfig;
|
||||
import com.cskefu.cc.persistence.repository.*;
|
||||
import com.cskefu.cc.proxy.LicenseProxy;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -36,7 +37,6 @@ public class AppCtxRefreshEventListener implements ApplicationListener<ContextRe
|
||||
|
||||
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)) {
|
||||
|
||||
@ -138,6 +138,9 @@ public class AppCtxRefreshEventListener implements ApplicationListener<ContextRe
|
||||
logger.info("[Plugins] registered plugin id {}, class {}", p.getPluginId(), p.getClass().getName());
|
||||
}
|
||||
|
||||
// 初始化 ServerInstId
|
||||
LicenseProxy licenseProxy = event.getApplicationContext().getBean(LicenseProxy.class);
|
||||
licenseProxy.checkOnStartup();
|
||||
} else {
|
||||
logger.info("[onApplicationEvent] bypass, initialization has been done already.");
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.chatopera.store.sdk.QuotaWdClient;
|
||||
import com.chatopera.store.sdk.exceptions.InvalidProviderException;
|
||||
import com.cskefu.cc.basic.MainContext;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class QuotaWdClientConfig {
|
||||
|
||||
@Value("${license.store.provider}")
|
||||
private String licenseStoreProvider;
|
||||
|
||||
/**
|
||||
* 证书商店服务客户端
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public QuotaWdClient quotaWdClient() throws InvalidProviderException {
|
||||
if (StringUtils.isBlank(licenseStoreProvider)) {
|
||||
System.out.println("[license] invalid license provider info, service is terminated.");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
QuotaWdClient quotaWdClient = new QuotaWdClient();
|
||||
return quotaWdClient;
|
||||
}
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
/*
|
||||
* 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.controller.admin;
|
||||
|
||||
import com.chatopera.store.sdk.exceptions.InvalidRequestException;
|
||||
import com.chatopera.store.sdk.exceptions.InvalidResponseException;
|
||||
import com.cskefu.cc.basic.Constants;
|
||||
import com.cskefu.cc.controller.Handler;
|
||||
import com.cskefu.cc.exception.LicenseNotFoundException;
|
||||
import com.cskefu.cc.exception.MetaKvInvalidKeyException;
|
||||
import com.cskefu.cc.model.User;
|
||||
import com.cskefu.cc.proxy.LicenseProxy;
|
||||
import com.cskefu.cc.util.Menu;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
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.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/admin/license")
|
||||
public class LicenseController extends Handler {
|
||||
private final static Logger logger = LoggerFactory.getLogger(LicenseController.class);
|
||||
|
||||
@Autowired
|
||||
private LicenseProxy licenseProxy;
|
||||
|
||||
@RequestMapping("/index")
|
||||
@Menu(type = "admin", subtype = "licenseList")
|
||||
public ModelAndView index(ModelMap map, HttpServletRequest request) {
|
||||
User user = super.getUser(request);
|
||||
if (user.isSuperadmin()) {
|
||||
try {
|
||||
List<JSONObject> licenses = licenseProxy.getLicensesInStore();
|
||||
map.addAttribute(Constants.UPDATETIME, new Date());
|
||||
map.addAttribute(Constants.LICENSES, licenses);
|
||||
map.addAttribute(Constants.LICENSESTOREPROVIDER, licenseProxy.getLicenseStoreProvider());
|
||||
|
||||
} catch (InvalidResponseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return request(super.createView("/admin/license/index"));
|
||||
} else {
|
||||
return request(super.createView("/public/error"));
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/add")
|
||||
@Menu(type = "admin", subtype = "licenseList")
|
||||
public ModelAndView add(ModelMap map, HttpServletRequest request) {
|
||||
User user = super.getUser(request);
|
||||
if (user.isSuperadmin()) {
|
||||
return request(super.createView("/admin/license/add"));
|
||||
} else {
|
||||
return request(super.createView("/public/error"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存新的证书
|
||||
*
|
||||
* @param map
|
||||
* @param request
|
||||
* @param licenseShortId
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping("/save")
|
||||
@Menu(type = "admin", subtype = "licenseList")
|
||||
public ModelAndView save(ModelMap map,
|
||||
HttpServletRequest request,
|
||||
@Valid String licenseShortId) throws MetaKvInvalidKeyException, InvalidRequestException {
|
||||
User user = super.getUser(request);
|
||||
logger.info("[save] licenseShortId {}", licenseShortId);
|
||||
String msg = "";
|
||||
|
||||
if (user.isSuperadmin()) {
|
||||
try {
|
||||
/**
|
||||
* 验证证书可以添加
|
||||
*/
|
||||
// 验证该证书不在当前证书列表中
|
||||
JSONArray currents = licenseProxy.getLicensesInMetakv();
|
||||
boolean isAddedBefore = false;
|
||||
|
||||
for (int i = 0; i < currents.length(); i++) {
|
||||
JSONObject item = (JSONObject) currents.get(i);
|
||||
if (StringUtils.equals(item.getString(Constants.SHORTID), licenseShortId)) {
|
||||
isAddedBefore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isAddedBefore) {
|
||||
msg = "already_added";
|
||||
return request(super.createView(
|
||||
"redirect:/admin/license/index.html?msg=" + msg));
|
||||
}
|
||||
|
||||
// 验证该证书存在
|
||||
licenseProxy.existLicenseInStore(licenseShortId);
|
||||
|
||||
// 验证该证书的所属产品没有现在没有其它证书:同一个产品最多只有一个证书
|
||||
JSONObject licBasic = licenseProxy.getLicenseBasicsInStore(licenseShortId);
|
||||
final String productId = licBasic.getJSONObject(Constants.PRODUCT).getString(Constants.SHORTID);
|
||||
|
||||
boolean isProductAdded = false;
|
||||
JSONArray addedLicenseBasicsFromStore = licenseProxy.getAddedLicenseBasicsInStore();
|
||||
|
||||
for (int i = 0; i < addedLicenseBasicsFromStore.length(); i++) {
|
||||
JSONObject item = (JSONObject) addedLicenseBasicsFromStore.get(i);
|
||||
if (StringUtils.equals(item.getJSONObject(Constants.PRODUCT).getString(Constants.SHORTID), productId)) {
|
||||
isProductAdded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isProductAdded) {
|
||||
msg = "product_added_already";
|
||||
return request(super.createView(
|
||||
"redirect:/admin/license/index.html?msg=" + msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加该证书
|
||||
*/
|
||||
JSONObject licenseKvData = new JSONObject();
|
||||
licenseKvData.put(Constants.SHORTID, licenseShortId);
|
||||
licenseKvData.put(Constants.ADDDATE, new Date());
|
||||
licenseKvData.put(Constants.PRODUCT_ID, productId);
|
||||
currents.put(0, licenseKvData);
|
||||
licenseProxy.createOrUpdateMetaKv(Constants.LICENSEIDS, currents.toString(), Constants.METAKV_DATATYPE_STRING);
|
||||
|
||||
// 跳转回到证书列表
|
||||
List<JSONObject> licenses = licenseProxy.getLicensesInStore();
|
||||
map.addAttribute(Constants.LICENSES, licenses);
|
||||
map.addAttribute(Constants.UPDATETIME, new Date());
|
||||
map.addAttribute(Constants.LICENSESTOREPROVIDER, licenseProxy.getLicenseStoreProvider());
|
||||
|
||||
return request(super.createView("/admin/license/index"));
|
||||
} catch (InvalidResponseException e) {
|
||||
logger.warn("[save] error in getLicenseFromStore", e);
|
||||
msg = "invalid_id";
|
||||
return request(super.createView(
|
||||
"redirect:/admin/license/index.html?msg=" + msg));
|
||||
} catch (LicenseNotFoundException e) {
|
||||
logger.warn("[save] error in getLicenseFromStore", e);
|
||||
msg = "notfound_id";
|
||||
return request(super.createView(
|
||||
"redirect:/admin/license/index.html?msg=" + msg));
|
||||
}
|
||||
} else {
|
||||
return request(super.createView("/public/error"));
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/delete/{licenseShortId}")
|
||||
@Menu(type = "admin", subtype = "licenseList")
|
||||
public ModelAndView delete(ModelMap map,
|
||||
HttpServletRequest request,
|
||||
@PathVariable String licenseShortId) throws MetaKvInvalidKeyException {
|
||||
User user = super.getUser(request);
|
||||
logger.info("[delete] licenseShortId {}", licenseShortId);
|
||||
String msg = "";
|
||||
|
||||
if (user.isSuperadmin()) {
|
||||
try {
|
||||
JSONArray currents = licenseProxy.getLicensesInMetakv();
|
||||
JSONArray post = new JSONArray();
|
||||
for (int i = 0; i < currents.length(); i++) {
|
||||
JSONObject item = (JSONObject) currents.get(i);
|
||||
if (!StringUtils.equals(item.getString(Constants.SHORTID), licenseShortId)) {
|
||||
post.put(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加该证书
|
||||
*/
|
||||
licenseProxy.createOrUpdateMetaKv(Constants.LICENSEIDS, post.toString(), Constants.METAKV_DATATYPE_STRING);
|
||||
|
||||
// 跳转回到证书列表
|
||||
List<JSONObject> licenses = licenseProxy.getLicensesInStore();
|
||||
map.addAttribute(Constants.LICENSES, licenses);
|
||||
map.addAttribute("updateTime", new Date());
|
||||
map.addAttribute(Constants.LICENSESTOREPROVIDER, licenseProxy.getLicenseStoreProvider());
|
||||
|
||||
return request(super.createView("/admin/license/index"));
|
||||
} catch (InvalidResponseException e) {
|
||||
logger.warn("[save] error in getLicenseFromStore", e);
|
||||
msg = "invalid_id";
|
||||
return request(super.createView(
|
||||
"redirect:/admin/license/index.html?msg=" + msg));
|
||||
}
|
||||
} else {
|
||||
return request(super.createView("/public/error"));
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/instance")
|
||||
@Menu(type = "admin", subtype = "licenseInst")
|
||||
public ModelAndView getInstanceInfo(ModelMap map, HttpServletRequest request) {
|
||||
User user = super.getUser(request);
|
||||
if (user.isSuperadmin()) {
|
||||
map.addAttribute(Constants.LICENSE_SERVICE_NAME, licenseProxy.resolveServicename());
|
||||
map.addAttribute(Constants.LICENSE_SERVER_INST_ID, licenseProxy.resolveServerinstId());
|
||||
map.addAttribute(Constants.LICENSESTOREPROVIDER, licenseProxy.getLicenseStoreProvider());
|
||||
return request(super.createView("/admin/license/instance"));
|
||||
} else {
|
||||
return request(super.createView("/public/error"));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
* 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,
|
||||
* 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,
|
||||
* 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;
|
||||
@ -17,6 +17,7 @@ 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.exception.BillingQuotaException;
|
||||
import com.cskefu.cc.model.*;
|
||||
import com.cskefu.cc.persistence.repository.*;
|
||||
import com.cskefu.cc.proxy.OrganProxy;
|
||||
@ -34,6 +35,8 @@ import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@ -157,16 +160,26 @@ public class OrganController extends Handler {
|
||||
public ModelAndView save(HttpServletRequest request, @Valid Organ organ) {
|
||||
Organ tempOrgan = organRepository.findByName(organ.getName());
|
||||
String msg = "admin_organ_new_success";
|
||||
String firstId = null;
|
||||
String createdId = null;
|
||||
if (tempOrgan != null) {
|
||||
msg = "admin_organ_update_name_not"; // 分类名字重复
|
||||
} else {
|
||||
firstId = organ.getId();
|
||||
|
||||
organRepository.save(organ);
|
||||
try {
|
||||
organRepository.save(organ);
|
||||
createdId = organ.getId();
|
||||
} catch (Exception e) {
|
||||
if (e instanceof UndeclaredThrowableException) {
|
||||
logger.error("[save] BillingQuotaException", e);
|
||||
if (StringUtils.startsWith(e.getCause().getMessage(), BillingQuotaException.SUFFIX)) {
|
||||
msg = e.getCause().getMessage();
|
||||
}
|
||||
} else {
|
||||
logger.error("[save] err", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return request(super.createView(
|
||||
"redirect:/admin/organ/index.html?msg=" + msg + "&organ=" + firstId));
|
||||
"redirect:/admin/organ/index.html?msg=" + msg + "&organ=" + createdId));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
* 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,
|
||||
* 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,
|
||||
* 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;
|
||||
@ -18,6 +18,7 @@ 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.BillingQuotaException;
|
||||
import com.cskefu.cc.model.*;
|
||||
import com.cskefu.cc.persistence.repository.ConsultInviteRepository;
|
||||
import com.cskefu.cc.persistence.repository.OrganRepository;
|
||||
@ -27,6 +28,8 @@ 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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@ -37,6 +40,8 @@ import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@ -48,6 +53,7 @@ import java.util.Map;
|
||||
@Controller
|
||||
@RequestMapping("/admin/im")
|
||||
public class ChannelController extends Handler {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ChannelController.class);
|
||||
|
||||
@Autowired
|
||||
private ChannelRepository snsAccountRes;
|
||||
@ -93,40 +99,59 @@ public class ChannelController extends Handler {
|
||||
return request(super.createView("/admin/channel/im/add"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的网站渠道
|
||||
*
|
||||
* @param request
|
||||
* @param channel
|
||||
* @return
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
@RequestMapping("/save")
|
||||
@Menu(type = "admin", subtype = "weixin")
|
||||
@Menu(type = "admin", subtype = "im")
|
||||
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());
|
||||
try {
|
||||
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);
|
||||
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);
|
||||
/**
|
||||
* 同时创建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);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof UndeclaredThrowableException) {
|
||||
logger.error("[save] BillingQuotaException", e);
|
||||
if (StringUtils.startsWith(e.getCause().getMessage(), BillingQuotaException.SUFFIX)) {
|
||||
status = e.getCause().getMessage();
|
||||
}
|
||||
} else {
|
||||
logger.error("[save] err", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ 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;
|
||||
|
@ -46,6 +46,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@ -84,7 +86,7 @@ public class ApiContactsController extends Handler {
|
||||
|
||||
if (!StringUtils.isBlank(creater)) {
|
||||
User user = super.getUser(request);
|
||||
contactsList = contactsRepository.findByCreaterAndSharesAndDatastatus(user.getId(), "all", false,
|
||||
contactsList = contactsRepository.findByCreaterAndSharesInAndDatastatus(user.getId(), Arrays.asList(user.getId(),"all"), false,
|
||||
PageRequest.of(
|
||||
super.getP(request),
|
||||
super.getPs(request)));
|
||||
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
* 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,
|
||||
* 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,
|
||||
* 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;
|
||||
@ -183,7 +183,9 @@ public class ApiUserController extends Handler {
|
||||
User user = userProxy.parseUserFromJson(payload);
|
||||
JsonObject resp = userProxy.createNewUser(user, parentOrgan);
|
||||
|
||||
if (StringUtils.isNotEmpty(roleId)) {
|
||||
if (StringUtils.isNotEmpty(roleId) &&
|
||||
StringUtils.equals(resp.get(RestUtils.RESP_KEY_DATA).getAsString(),
|
||||
Constants.NEW_USER_SUCCESS)) {
|
||||
Role role = roleRes.findById(roleId).orElse(null);
|
||||
UserRole userRole = new UserRole();
|
||||
userRole.setUser(user);
|
||||
|
@ -71,9 +71,6 @@ public class AgentAuditController extends Handler {
|
||||
@Autowired
|
||||
private UserRepository userRes;
|
||||
|
||||
@Autowired
|
||||
private AgentUserRepository agentUserRepository;
|
||||
|
||||
@Autowired
|
||||
private ChatMessageRepository chatMessageRepository;
|
||||
|
||||
@ -245,7 +242,7 @@ public class AgentAuditController extends Handler {
|
||||
view.addObject(
|
||||
"agentUserList", agentUserRes.findByStatusAndAgentnoIsNot(
|
||||
MainContext.AgentUserStatusEnum.INSERVICE.toString(), logined.getId(), defaultSort));
|
||||
List<AgentUser> agentUserList = agentUserRepository.findByUserid(userid);
|
||||
List<AgentUser> agentUserList = agentUserRes.findByUserid(userid);
|
||||
view.addObject(
|
||||
"curagentuser", agentUserList != null && agentUserList.size() > 0 ? agentUserList.get(0) : null);
|
||||
|
||||
@ -266,7 +263,7 @@ public class AgentAuditController extends Handler {
|
||||
}
|
||||
ModelAndView view = request(super.createView(mainagentuser));
|
||||
final User logined = super.getUser(request);
|
||||
AgentUser agentUser = agentUserRepository.findById(id).orElse(null);
|
||||
AgentUser agentUser = agentUserRes.findById(id).orElse(null);
|
||||
|
||||
if (agentUser != null) {
|
||||
view.addObject("curagentuser", agentUser);
|
||||
|
@ -1252,4 +1252,4 @@
|
||||
|
||||
return request(super.createView("redirect:/agent/index.html"));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
* 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,
|
||||
* 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,
|
||||
* 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.BillingQuotaException;
|
||||
import com.cskefu.cc.exception.CSKefuException;
|
||||
import com.cskefu.cc.model.*;
|
||||
import com.cskefu.cc.persistence.repository.*;
|
||||
@ -47,8 +48,10 @@ 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.lang.reflect.UndeclaredThrowableException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
@ -143,8 +146,8 @@ public class ContactsController extends Handler {
|
||||
map.put("ckind", ckind);
|
||||
}
|
||||
|
||||
Page<Contacts> contacts = contactsRes.findByCreaterAndSharesAndDatastatus(logined.getId(),
|
||||
logined.getId(),
|
||||
Page<Contacts> contacts = contactsRes.findByCreaterAndSharesInAndDatastatus(logined.getId(),
|
||||
Arrays.asList(logined.getId(),"all"),
|
||||
false,
|
||||
PageRequest.of(
|
||||
super.getP(request),
|
||||
@ -177,8 +180,8 @@ public class ContactsController extends Handler {
|
||||
map.put("ckind", ckind);
|
||||
}
|
||||
|
||||
Page<Contacts> contacts = contactsRes.findByCreaterAndSharesAndDatastatus(logined.getId(),
|
||||
logined.getId(),
|
||||
Page<Contacts> contacts = contactsRes.findByCreaterAndSharesInAndDatastatus(logined.getId(),
|
||||
Arrays.asList(logined.getId(),"all"),
|
||||
false,
|
||||
PageRequest.of(
|
||||
super.getP(request),
|
||||
@ -211,8 +214,8 @@ public class ContactsController extends Handler {
|
||||
map.put("ckind", ckind);
|
||||
}
|
||||
|
||||
Page<Contacts> contacts = contactsRes.findByCreaterAndSharesAndDatastatus(logined.getId(),
|
||||
logined.getId(),
|
||||
Page<Contacts> contacts = contactsRes.findByCreaterAndSharesInAndDatastatus(logined.getId(),
|
||||
Arrays.asList(logined.getId(),"all"),
|
||||
false,
|
||||
PageRequest.of(
|
||||
super.getP(request),
|
||||
@ -255,13 +258,11 @@ public class ContactsController extends Handler {
|
||||
@RequestParam(name = "idselflocation", required = false) String selflocation) {
|
||||
final User logined = super.getUser(request);
|
||||
Organ currentOrgan = super.getOrgan(request);
|
||||
String skypeIDReplace = contactsProxy.sanitizeSkypeId(contacts.getSkypeid());
|
||||
String msg = "";
|
||||
Contacts contact = contactsRes.findByskypeidAndDatastatus(skypeIDReplace, false);
|
||||
|
||||
// 添加数据
|
||||
if (contacts.getSkypeid() != null && contact == null) {
|
||||
logger.info("[save] 数据库没有相同skypeid");
|
||||
try {
|
||||
contacts.setId(null);
|
||||
contacts.setCreater(logined.getId());
|
||||
|
||||
if (currentOrgan != null && StringUtils.isBlank(contacts.getOrgan())) {
|
||||
@ -274,11 +275,16 @@ public class ContactsController extends Handler {
|
||||
}
|
||||
contactsRes.save(contacts);
|
||||
msg = "new_contacts_success";
|
||||
|
||||
return request(super.createView(
|
||||
"redirect:/apps/contacts/index.html?ckind=" + contacts.getCkind() + "&msg=" + msg));
|
||||
} catch (Exception e) {
|
||||
if (e instanceof UndeclaredThrowableException) {
|
||||
logger.error("[save] BillingQuotaException", e);
|
||||
if (StringUtils.startsWith(e.getCause().getMessage(), BillingQuotaException.SUFFIX)) {
|
||||
msg = e.getCause().getMessage();
|
||||
}
|
||||
} else {
|
||||
logger.error("[save] err", e);
|
||||
}
|
||||
}
|
||||
msg = "new_contacts_fail";
|
||||
return request(super.createView(
|
||||
"redirect:/apps/contacts/index.html?ckind=" + contacts.getCkind() + "&msg=" + msg));
|
||||
}
|
||||
@ -478,8 +484,8 @@ public class ContactsController extends Handler {
|
||||
map.put("ckind", ckind);
|
||||
}
|
||||
|
||||
Iterable<Contacts> contactsList = contactsRes.findByCreaterAndSharesAndDatastatus(
|
||||
logined.getId(), logined.getId(), false, PageRequest.of(super.getP(request), super.getPs(request)));
|
||||
Iterable<Contacts> contactsList = contactsRes.findByCreaterAndSharesInAndDatastatus(
|
||||
logined.getId(), Arrays.asList(logined.getId(),"all"),false, PageRequest.of(super.getP(request), super.getPs(request)));
|
||||
|
||||
MetadataTable table = metadataRes.findByTablename("uk_contacts");
|
||||
List<Map<String, Object>> values = new ArrayList<>();
|
||||
@ -512,8 +518,8 @@ public class ContactsController extends Handler {
|
||||
map.put("ckind", ckind);
|
||||
}
|
||||
|
||||
Iterable<Contacts> contactsList = contactsRes.findByCreaterAndSharesAndDatastatus(
|
||||
logined.getId(), logined.getId(), false, PageRequest.of(super.getP(request), super.getPs(request)));
|
||||
Iterable<Contacts> contactsList = contactsRes.findByCreaterAndSharesInAndDatastatus(
|
||||
logined.getId(), Arrays.asList(logined.getId(),"all"), false, PageRequest.of(super.getP(request), super.getPs(request)));
|
||||
MetadataTable table = metadataRes.findByTablename("uk_contacts");
|
||||
List<Map<String, Object>> values = new ArrayList<>();
|
||||
for (Contacts contacts : contactsList) {
|
||||
@ -552,8 +558,8 @@ public class ContactsController extends Handler {
|
||||
if (StringUtils.isNotBlank(agentserviceid)) {
|
||||
AgentService service = agentServiceRes.findById(agentserviceid).orElse(null);
|
||||
}
|
||||
Page<Contacts> contactsList = contactsRes.findByCreaterAndSharesAndDatastatus(
|
||||
logined.getId(), logined.getId(), false,
|
||||
Page<Contacts> contactsList = contactsRes.findByCreaterAndSharesInAndDatastatus(
|
||||
logined.getId(), Arrays.asList(logined.getId(),"all"), false,
|
||||
PageRequest.of(super.getP(request), super.getPs(request)));
|
||||
|
||||
map.addAttribute("contactsList", contactsList);
|
||||
|
@ -117,7 +117,7 @@ public class IMController extends Handler {
|
||||
private LeaveMsgRepository leaveMsgRes;
|
||||
|
||||
@Autowired
|
||||
private AgentUserRepository agentUserRepository;
|
||||
private AgentUserRepository agentUserRes;
|
||||
|
||||
@Autowired
|
||||
private AttachmentRepository attachementRes;
|
||||
@ -822,7 +822,7 @@ public class IMController extends Handler {
|
||||
Contacts contacts1 = contactsRes.findOneByWluidAndWlsidAndWlcidAndDatastatus(
|
||||
uid, sid, cid, false);
|
||||
if (contacts1 != null) {
|
||||
agentUserRepository.findOneByUserid(userid).ifPresent(p -> {
|
||||
agentUserRes.findOneByUserid(userid).ifPresent(p -> {
|
||||
// 关联AgentService的联系人
|
||||
if (StringUtils.isNotBlank(p.getAgentserviceid())) {
|
||||
AgentService agentService = agentServiceRepository.findById(p.getAgentserviceid()).orElse(null);
|
||||
|
@ -31,7 +31,6 @@ 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.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
@ -77,9 +77,6 @@ public class ChatServiceController extends Handler {
|
||||
@Autowired
|
||||
private AgentStatusRepository agentStatusRepository;
|
||||
|
||||
@Autowired
|
||||
private AgentUserRepository agentUserRepository;
|
||||
|
||||
@Autowired
|
||||
private LeaveMsgRepository leaveMsgRes;
|
||||
|
||||
@ -233,7 +230,7 @@ public class ChatServiceController extends Handler {
|
||||
if (agentUser != null) {
|
||||
agentUser.setAgentno(agentno);
|
||||
agentUser.setAgentname(targetAgent.getUname());
|
||||
agentUserRepository.save(agentUser);
|
||||
agentUserRes.save(agentUser);
|
||||
if (MainContext.AgentUserStatusEnum.INSERVICE.toString().equals(
|
||||
agentUser.getStatus())) {
|
||||
// 转接 , 发送消息给 目标坐席
|
||||
@ -288,11 +285,11 @@ public class ChatServiceController extends Handler {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
agentUser = agentUserRepository.findById(agentService.getAgentuserid()).orElse(null);
|
||||
agentUser = agentUserRes.findById(agentService.getAgentuserid()).orElse(null);
|
||||
if (agentUser != null) {
|
||||
agentUser.setAgentno(agentno);
|
||||
agentUser.setAgentname(targetAgent.getUname());
|
||||
agentUserRepository.save(agentUser);
|
||||
agentUserRes.save(agentUser);
|
||||
}
|
||||
}
|
||||
|
||||
@ -317,7 +314,7 @@ public class ChatServiceController extends Handler {
|
||||
AgentService agentService = agentServiceRes.findById(id).orElse(null);
|
||||
if (agentService != null) {
|
||||
User user = super.getUser(request);
|
||||
AgentUser agentUser = agentUserRepository.findById(agentService.getAgentuserid()).orElse(null);
|
||||
AgentUser agentUser = agentUserRes.findById(agentService.getAgentuserid()).orElse(null);
|
||||
if (agentUser != null) {
|
||||
acdAgentService.finishAgentUser(agentUser);
|
||||
}
|
||||
|
@ -130,9 +130,10 @@ public class OnlineUserController extends Handler {
|
||||
|
||||
}
|
||||
|
||||
agentUserContactsRes.findOneByUserid(
|
||||
userid).ifPresent(p -> {
|
||||
map.put("contacts", contactsRes.findById(p.getContactsid()).orElse(null));
|
||||
agentUserContactsRes.findOneByUserid(userid).ifPresent(p -> {
|
||||
if (p.getContactsid() != null) {
|
||||
map.put("contacts", contactsRes.findById(p.getContactsid()).orElse(null));
|
||||
}
|
||||
});
|
||||
AgentService service = agentServiceRes.findById(agentservice).orElse(null);
|
||||
if (service != null) {
|
||||
|
@ -31,7 +31,6 @@ 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.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
@ -31,6 +31,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Controller
|
||||
public class ContactsResourceController extends Handler {
|
||||
|
||||
@ -44,7 +46,7 @@ public class ContactsResourceController extends Handler {
|
||||
if (q == null) {
|
||||
q = "";
|
||||
}
|
||||
Page<Contacts> contactsList = contactsRes.findByCreaterAndSharesAndDatastatus(super.getUser(request).getId(), super.getUser(request).getId(), false, PageRequest.of(0, 10));
|
||||
Page<Contacts> contactsList = contactsRes.findByCreaterAndSharesInAndDatastatus(super.getUser(request).getId(), Arrays.asList(super.getUser(request).getId(),"all"),false, PageRequest.of(0, 10));
|
||||
|
||||
JSONArray result = new JSONArray();
|
||||
for (Contacts contact : contactsList.getContent()) {
|
||||
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class BillingQuotaException extends Exception {
|
||||
|
||||
public final static String SUFFIX = "billingquotaexception.";
|
||||
|
||||
// metakv 中没有找到可以支持配额的证书类型
|
||||
public final static String NO_LICENSE_FOUND = SUFFIX + "no_license_found";
|
||||
// 返回值异常,可能是网络连接不上,稍后再试
|
||||
public static final String RESPONSE_UNEXPECTED = SUFFIX + "response_unexpected";
|
||||
// 请求参数不合法
|
||||
public static final String INVALID_REQUEST_BODY = SUFFIX + "invalid_request_body";
|
||||
// 证书在证书商店不存在
|
||||
public static final String LICENSE_INVALID = SUFFIX + "license_invalid";
|
||||
// 证书关联的产品信息不合法
|
||||
public static final String PRODUCT_INVALID = SUFFIX + "product_invalid";
|
||||
// 证书失效或耗尽
|
||||
public static final String LICENSE_EXPIRED_OR_EXHAUSTED = SUFFIX + "license_expired_or_exhausted";
|
||||
// 证书关闭了对该 serverinst 的支持
|
||||
public static final String LICENSE_DISABLED_SERVERINST = SUFFIX + "license_disabled_serverinst";
|
||||
// 证书不支持配额回退
|
||||
public static final String LICENSE_UNSUPPORT_REFUND = SUFFIX + "license_unsupport_refund";
|
||||
// 证书配额余量不足,不能完成本次请求
|
||||
public static final String LICENSE_QUOTA_INADEQUATE = SUFFIX + "license_quota_inadequate";
|
||||
// 内部错误,不应该发生
|
||||
public static final String INTERNAL_ERROR = SUFFIX + "internal_error";
|
||||
|
||||
public BillingQuotaException(final String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class BillingResourceException extends Exception{
|
||||
public BillingResourceException(final String s){
|
||||
super(s);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class LicenseNotFoundException extends Exception{
|
||||
public LicenseNotFoundException(final String s){
|
||||
super(s);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class MetaKvInvalidKeyException extends Exception{
|
||||
public MetaKvInvalidKeyException(final String s){
|
||||
super(s);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class MetaKvNotExistException extends Exception{
|
||||
public MetaKvNotExistException(final String s){
|
||||
super(s);
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
* 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,
|
||||
* 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,
|
||||
* Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0,
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package com.cskefu.cc.model;
|
||||
@ -19,6 +19,7 @@ import org.hibernate.annotations.GenericGenerator;
|
||||
import org.hibernate.annotations.Proxy;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@ -105,6 +106,7 @@ public class AgentUser implements Serializable, Comparable<AgentUser> {
|
||||
|
||||
@Transient
|
||||
private boolean tip = false;
|
||||
|
||||
@Transient
|
||||
private boolean agentTip = false;
|
||||
|
||||
@ -119,11 +121,25 @@ public class AgentUser implements Serializable, Comparable<AgentUser> {
|
||||
|
||||
@Transient
|
||||
private boolean fromhis = false;
|
||||
|
||||
@Transient
|
||||
private boolean online = false;
|
||||
|
||||
@Transient
|
||||
private boolean disconnect = false;
|
||||
|
||||
/**
|
||||
* 证书验证通过
|
||||
*/
|
||||
@Transient
|
||||
private boolean licenseVerifiedPass = true;
|
||||
|
||||
/**
|
||||
* 证书验证提示信息
|
||||
*/
|
||||
@Transient
|
||||
private String licenseBillingMsg;
|
||||
|
||||
|
||||
public AgentUser() {
|
||||
}
|
||||
@ -617,4 +633,22 @@ public class AgentUser implements Serializable, Comparable<AgentUser> {
|
||||
public void setAgentname(String agentname) {
|
||||
this.agentname = agentname;
|
||||
}
|
||||
|
||||
@Transient
|
||||
public boolean isLicenseVerifiedPass() {
|
||||
return licenseVerifiedPass;
|
||||
}
|
||||
|
||||
public void setLicenseVerifiedPass(boolean licenseVerifiedPass) {
|
||||
this.licenseVerifiedPass = licenseVerifiedPass;
|
||||
}
|
||||
|
||||
@Transient
|
||||
public String getLicenseBillingMsg() {
|
||||
return licenseBillingMsg;
|
||||
}
|
||||
|
||||
public void setLicenseBillingMsg(String licenseBillingMsg) {
|
||||
this.licenseBillingMsg = licenseBillingMsg;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.model;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ExecuteResult implements Serializable {
|
||||
public final static int RC_SUCC = 0;
|
||||
public final static int RC_ERR1 = 1;
|
||||
public final static int RC_ERR2 = 2;
|
||||
public final static int RC_ERR3 = 3;
|
||||
public final static int RC_ERR4 = 4;
|
||||
public final static int RC_ERR5 = 5;
|
||||
public final static int RC_ERR6 = 6;
|
||||
public final static int RC_ERR7 = 7;
|
||||
public final static int RC_ERR8 = 8;
|
||||
public final static int RC_ERR9 = 9;
|
||||
|
||||
private int rc; // 0 for success, errors other
|
||||
private String error;
|
||||
private String msg;
|
||||
private JSONObject data;
|
||||
|
||||
|
||||
public ExecuteResult() {
|
||||
|
||||
}
|
||||
|
||||
public ExecuteResult(final int rc, final String msg) {
|
||||
this.rc = rc;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public ExecuteResult(final int rc, final String msg, final String error) {
|
||||
this.rc = rc;
|
||||
this.msg = msg;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public ExecuteResult(final int rc,
|
||||
final String msg,
|
||||
final String error,
|
||||
final JSONObject data) {
|
||||
this.rc = rc;
|
||||
this.msg = msg;
|
||||
this.error = error;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int getRc() {
|
||||
return rc;
|
||||
}
|
||||
|
||||
public void setRc(int rc) {
|
||||
this.rc = rc;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public JSONObject getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(JSONObject data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.cskefu.cc.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 存储元数据
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "cs_metakv")
|
||||
@org.hibernate.annotations.Proxy(lazy = false)
|
||||
public class MetaKv implements java.io.Serializable {
|
||||
|
||||
@Id
|
||||
private String metakey;
|
||||
|
||||
private String metavalue;
|
||||
|
||||
private String datatype;
|
||||
|
||||
private String comment;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date updatetime;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date createtime;
|
||||
|
||||
public String getMetakey() {
|
||||
return metakey;
|
||||
}
|
||||
|
||||
public void setMetakey(String metakey) {
|
||||
this.metakey = metakey;
|
||||
}
|
||||
|
||||
public String getMetavalue() {
|
||||
return metavalue;
|
||||
}
|
||||
|
||||
public void setMetavalue(String metavalue) {
|
||||
this.metavalue = metavalue;
|
||||
}
|
||||
|
||||
public String getDatatype() {
|
||||
return datatype;
|
||||
}
|
||||
|
||||
public void setDatatype(String datatype) {
|
||||
this.datatype = datatype;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public Date getUpdatetime() {
|
||||
return updatetime;
|
||||
}
|
||||
|
||||
public void setUpdatetime(Date updatetime) {
|
||||
this.updatetime = updatetime;
|
||||
}
|
||||
|
||||
public Date getCreatetime() {
|
||||
return createtime;
|
||||
}
|
||||
|
||||
public void setCreatetime(Date createtime) {
|
||||
this.createtime = createtime;
|
||||
}
|
||||
}
|
@ -69,26 +69,26 @@ public interface AgentUserRepository extends JpaRepository<AgentUser, String> {
|
||||
|
||||
AgentUser findOneByAgentnoAndStatus(String id, String status);
|
||||
|
||||
@Query(nativeQuery = true, value = "SELECT * FROM uk_agentuser AS u " +
|
||||
@Query(nativeQuery = true, value = "SELECT u.* FROM uk_agentuser AS u " +
|
||||
"LEFT JOIN uk_agentuser_contacts AS c " +
|
||||
"ON u.userid = c.userid WHERE c.id = ?1 AND NOT u.status = ?2 LIMIT 1")
|
||||
AgentUser findOneByContactIdAndStatusNot(final String contactid, final String status);
|
||||
|
||||
@Query(nativeQuery = true, value = "SELECT * FROM uk_agentuser AS u " +
|
||||
@Query(nativeQuery = true, value = "SELECT u.* FROM uk_agentuser AS u " +
|
||||
"LEFT JOIN uk_agentuser_contacts AS c " +
|
||||
"ON u.userid = c.userid WHERE c.contactsid = ?1 " +
|
||||
"AND c.channeltype = ?3 AND NOT u.status = ?2 " +
|
||||
"ORDER BY u.createtime DESC LIMIT 1")
|
||||
Optional<AgentUser> findOneByContactIdAndStatusNotAndChanneltype(final String contactid, final String status, final String channeltype);
|
||||
|
||||
@Query(nativeQuery = true, value = "SELECT * FROM uk_agentuser AS u " +
|
||||
@Query(nativeQuery = true, value = "SELECT u.* FROM uk_agentuser AS u " +
|
||||
"LEFT JOIN uk_agentuser_contacts AS c " +
|
||||
"ON u.userid = c.userid WHERE c.contactsid = ?1 " +
|
||||
"AND c.channeltype = ?2 " +
|
||||
"ORDER BY u.createtime DESC LIMIT 1")
|
||||
Optional<AgentUser> findOneByContactIdAndChanneltype(final String contactid, final String channeltype);
|
||||
|
||||
@Query(nativeQuery = true, value = "SELECT * FROM uk_agentuser AS u " +
|
||||
@Query(nativeQuery = true, value = "SELECT u.* FROM uk_agentuser AS u " +
|
||||
"WHERE u.userid = ?1 " +
|
||||
"AND u.channeltype = ?3 AND NOT u.status = ?2 " +
|
||||
"ORDER BY u.createtime DESC LIMIT 1")
|
||||
|
@ -40,7 +40,7 @@ public interface ContactsRepository extends JpaRepository<Contacts, String> {
|
||||
@Query(nativeQuery = true, value = "SELECT * FROM uk_contacts WHERE id = ?1")
|
||||
Optional<Contacts> findOneById(final String id);
|
||||
|
||||
Page<Contacts> findByCreaterAndSharesAndDatastatus(String id, String shares, boolean datastatus, Pageable pageRequest);
|
||||
Page<Contacts> findByCreaterAndSharesInAndDatastatus(String id, Collection<String> shares, boolean datastatus, Pageable pageRequest);
|
||||
|
||||
/**
|
||||
* 根据条件返回联系人,符合一下条件之一:
|
||||
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.persistence.repository;
|
||||
|
||||
import com.cskefu.cc.model.MetaKv;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface MetaKvRepository extends JpaRepository<MetaKv, String> {
|
||||
|
||||
Optional<MetaKv> findFirstByMetakey(final String p1);
|
||||
|
||||
}
|
@ -1,33 +1,33 @@
|
||||
/*
|
||||
* 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.persistence.repository;
|
||||
|
||||
import com.cskefu.cc.model.MetadataTable;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MetadataRepository extends JpaRepository<MetadataTable, String>{
|
||||
|
||||
MetadataTable findByTablename(String tablename);
|
||||
|
||||
Page<MetadataTable> findAll(Pageable paramPageable);
|
||||
|
||||
int countByTablename(String tableName) ;
|
||||
|
||||
List<MetadataTable> findAll();
|
||||
}
|
||||
/*
|
||||
* 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.persistence.repository;
|
||||
|
||||
import com.cskefu.cc.model.MetadataTable;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MetadataRepository extends JpaRepository<MetadataTable, String>{
|
||||
|
||||
MetadataTable findByTablename(String tablename);
|
||||
|
||||
Page<MetadataTable> findAll(Pageable paramPageable);
|
||||
|
||||
int countByTablename(String tableName) ;
|
||||
|
||||
List<MetadataTable> findAll();
|
||||
}
|
||||
|
@ -1,371 +1,371 @@
|
||||
/*
|
||||
* 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.plugins.chatbot;
|
||||
|
||||
import com.chatopera.bot.sdk.Response;
|
||||
import com.corundumstudio.socketio.AckRequest;
|
||||
import com.corundumstudio.socketio.SocketIOClient;
|
||||
import com.corundumstudio.socketio.SocketIOServer;
|
||||
import com.corundumstudio.socketio.annotation.OnConnect;
|
||||
import com.corundumstudio.socketio.annotation.OnDisconnect;
|
||||
import com.corundumstudio.socketio.annotation.OnEvent;
|
||||
import com.cskefu.cc.acd.ACDServiceRouter;
|
||||
import com.cskefu.cc.basic.Constants;
|
||||
import com.cskefu.cc.basic.MainContext;
|
||||
import com.cskefu.cc.basic.MainUtils;
|
||||
import com.cskefu.cc.model.*;
|
||||
import com.cskefu.cc.persistence.repository.AgentUserRepository;
|
||||
import com.cskefu.cc.persistence.repository.ChatbotRepository;
|
||||
import com.cskefu.cc.persistence.repository.PassportWebIMUserRepository;
|
||||
import com.cskefu.cc.proxy.OnlineUserProxy;
|
||||
import com.cskefu.cc.socketio.client.NettyClients;
|
||||
import com.cskefu.cc.socketio.message.AgentStatusMessage;
|
||||
import com.cskefu.cc.socketio.message.ChatMessage;
|
||||
import com.cskefu.cc.socketio.message.Message;
|
||||
import com.cskefu.cc.socketio.util.IMServiceUtils;
|
||||
import com.cskefu.cc.util.IP;
|
||||
import com.cskefu.cc.util.IPTools;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Date;
|
||||
|
||||
public class ChatbotEventHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ChatbotEventHandler.class);
|
||||
|
||||
protected final SocketIOServer server;
|
||||
|
||||
private static AgentUserRepository agentUserRes;
|
||||
private static PassportWebIMUserRepository onlineUserRes;
|
||||
private static ChatbotRepository chatbotRes;
|
||||
private static ChatbotProxy chatbotProxy;
|
||||
|
||||
@Autowired
|
||||
public ChatbotEventHandler(SocketIOServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@OnConnect
|
||||
public void onConnect(SocketIOClient client) {
|
||||
try {
|
||||
String user = client.getHandshakeData().getSingleUrlParam("userid");
|
||||
String nickname = client.getHandshakeData().getSingleUrlParam("nickname");
|
||||
String session = MainUtils.getContextID(client.getHandshakeData().getSingleUrlParam("session"));
|
||||
String appid = client.getHandshakeData().getSingleUrlParam("appid");
|
||||
String aiid = client.getHandshakeData().getSingleUrlParam("aiid");
|
||||
logger.info(
|
||||
"[onConnect] userid {}, nickname {}, session {}, appid {}, aiid {}", user, nickname, session, appid,
|
||||
aiid);
|
||||
|
||||
client.set("aiid", aiid);
|
||||
client.set("session", session);
|
||||
client.set("userid", user);
|
||||
client.set("appid", appid);
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
if (StringUtils.isNotBlank(user)) {
|
||||
/**
|
||||
* 加入到 缓存列表
|
||||
*/
|
||||
NettyClients.getInstance().putChatbotEventClient(user, client);
|
||||
CousultInvite invite = OnlineUserProxy.consult(appid);
|
||||
|
||||
/**
|
||||
* 更新坐席服务类型
|
||||
*/
|
||||
IMServiceUtils.shiftOpsType(user, MainContext.OptType.CHATBOT);
|
||||
|
||||
// send out tip
|
||||
Message tip = new Message();
|
||||
tip.setMessage("您正在使用机器人客服!");
|
||||
tip.setMessageType(MainContext.MessageType.MESSAGE.toString());
|
||||
tip.setCalltype(MainContext.CallType.IN.toString());
|
||||
tip.setCreatetime(MainUtils.dateFormate.format(now));
|
||||
|
||||
client.sendEvent(MainContext.MessageType.STATUS.toString(), tip);
|
||||
|
||||
// send out welcome message
|
||||
if (invite != null) {
|
||||
Chatbot chatbot = getChatbotRes().findById(invite.getAiid()).orElse(null);
|
||||
com.chatopera.bot.sdk.Chatbot bot = new com.chatopera.bot.sdk.Chatbot(
|
||||
chatbot.getClientId(), chatbot.getSecret(), chatbot.getBaseUrl());
|
||||
Response result = bot.command("GET", "/");
|
||||
|
||||
// 发送欢迎语
|
||||
if (result.getRc() == 0) {
|
||||
JSONObject details = (JSONObject) result.getData();
|
||||
ChatMessage welcome = new ChatMessage();
|
||||
String welcomeTextMessage = details.getString("welcome");
|
||||
if (StringUtils.isNotBlank(welcomeTextMessage)) {
|
||||
welcome.setCalltype(MainContext.CallType.OUT.toString());
|
||||
welcome.setAppid(appid);
|
||||
welcome.setAiid(aiid);
|
||||
welcome.setMessage(welcomeTextMessage);
|
||||
welcome.setTouser(user);
|
||||
welcome.setMsgtype(MainContext.MessageType.MESSAGE.toString());
|
||||
welcome.setUserid(user);
|
||||
welcome.setUsername(invite.getAiname());
|
||||
welcome.setUpdatetime(System.currentTimeMillis());
|
||||
client.sendEvent(MainContext.MessageType.MESSAGE.toString(), welcome);
|
||||
}
|
||||
|
||||
// 发送常见问题列表
|
||||
JSONObject faqhotresp = bot.conversation(user, "__faq_hot_list");
|
||||
logger.info("faqhot {}", faqhotresp.toString());
|
||||
if (faqhotresp.getInt("rc") == 0) {
|
||||
JSONObject faqhotdata = faqhotresp.getJSONObject("data");
|
||||
if ((!faqhotdata.getBoolean("logic_is_fallback")) &&
|
||||
faqhotdata.has("string") &&
|
||||
faqhotdata.has("params")) {
|
||||
ChatMessage faqhotmsg = new ChatMessage();
|
||||
faqhotmsg.setCalltype(MainContext.CallType.OUT.toString());
|
||||
faqhotmsg.setAppid(appid);
|
||||
faqhotmsg.setAiid(aiid);
|
||||
faqhotmsg.setMessage(faqhotdata.getString("string"));
|
||||
faqhotmsg.setExpmsg(faqhotdata.getJSONArray("params").toString());
|
||||
faqhotmsg.setTouser(user);
|
||||
faqhotmsg.setMsgtype(MainContext.MessageType.MESSAGE.toString());
|
||||
faqhotmsg.setUserid(user);
|
||||
faqhotmsg.setUsername(invite.getAiname());
|
||||
faqhotmsg.setUpdatetime(System.currentTimeMillis());
|
||||
client.sendEvent(MainContext.MessageType.MESSAGE.toString(), faqhotmsg);
|
||||
}
|
||||
}
|
||||
} else if (result.getRc() == 999 || result.getRc() == 998) {
|
||||
logger.error("[chat] chatbot agent response rc {}, error {}", result.getRc(), result.getError());
|
||||
} else {
|
||||
logger.error("[chat] chatbot agent response rc {}, error {}", result.getRc(), result.getError());
|
||||
}
|
||||
}
|
||||
|
||||
InetSocketAddress address = (InetSocketAddress) client.getRemoteAddress();
|
||||
String ip = MainUtils.getIpAddr(client.getHandshakeData().getHttpHeaders(), address.getHostString());
|
||||
PassportWebIMUser passportWebIMUser = getOnlineUserRes().findById(user).orElse(null);
|
||||
|
||||
if (passportWebIMUser == null) {
|
||||
passportWebIMUser = new PassportWebIMUser();
|
||||
passportWebIMUser.setAppid(appid);
|
||||
if (StringUtils.isNotBlank(nickname)) {
|
||||
passportWebIMUser.setUsername(nickname);
|
||||
} else {
|
||||
passportWebIMUser.setUsername(Constants.GUEST_USER + "_" + MainUtils.genIDByKey(user));
|
||||
}
|
||||
|
||||
passportWebIMUser.setSessionid(session);
|
||||
passportWebIMUser.setOptype(MainContext.OptType.CHATBOT.toString());
|
||||
passportWebIMUser.setUserid(user);
|
||||
passportWebIMUser.setId(user);
|
||||
passportWebIMUser.setChannel(MainContext.ChannelType.WEBIM.toString());
|
||||
passportWebIMUser.setIp(ip);
|
||||
passportWebIMUser.setUpdatetime(now);
|
||||
passportWebIMUser.setLogintime(now);
|
||||
passportWebIMUser.setCreatetime(now);
|
||||
IP ipdata = IPTools.getInstance().findGeography(ip);
|
||||
passportWebIMUser.setCity(ipdata.getCity());
|
||||
passportWebIMUser.setCountry(ipdata.getCountry());
|
||||
passportWebIMUser.setProvince(ipdata.getProvince());
|
||||
passportWebIMUser.setIsp(ipdata.getIsp());
|
||||
passportWebIMUser.setRegion(ipdata.getRegion());
|
||||
passportWebIMUser.setStatus(MainContext.OnlineUserStatusEnum.ONLINE.toString());
|
||||
}
|
||||
|
||||
// 在线客服访客咨询记录
|
||||
AgentUser agentUser = new AgentUser(
|
||||
passportWebIMUser.getId(),
|
||||
MainContext.ChannelType.WEBIM.toString(), // callout
|
||||
passportWebIMUser.getId(),
|
||||
passportWebIMUser.getUsername(),
|
||||
appid);
|
||||
|
||||
agentUser.setServicetime(now);
|
||||
agentUser.setCreatetime(now);
|
||||
agentUser.setUpdatetime(now);
|
||||
agentUser.setSessionid(session);
|
||||
agentUser.setRegion(passportWebIMUser.getRegion());
|
||||
|
||||
// 聊天机器人处理的请求
|
||||
agentUser.setOpttype(MainContext.OptType.CHATBOT.toString());
|
||||
agentUser.setAgentno(aiid); // 聊天机器人ID
|
||||
agentUser.setAgentname(invite != null ? invite.getAiname() : "机器人客服");
|
||||
agentUser.setCity(passportWebIMUser.getCity());
|
||||
agentUser.setProvince(passportWebIMUser.getProvince());
|
||||
agentUser.setCountry(passportWebIMUser.getCountry());
|
||||
AgentService agentService = ACDServiceRouter.getAcdChatbotService().processChatbotService(
|
||||
invite != null ? invite.getAiname() : "机器人客服", agentUser);
|
||||
agentUser.setAgentserviceid(agentService.getId());
|
||||
|
||||
// 标记为机器人坐席
|
||||
agentUser.setChatbotops(true);
|
||||
|
||||
// 保存到MySQL
|
||||
getAgentUserRes().save(agentUser);
|
||||
getOnlineUserRes().save(passportWebIMUser);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.info("[onConnect] error", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 @OnDisconnect 事件,客户端断开连接时调用,刷新客户端信息
|
||||
@OnDisconnect
|
||||
public void onDisconnect(SocketIOClient client) {
|
||||
String user = client.getHandshakeData().getSingleUrlParam("userid");
|
||||
if (StringUtils.isNotBlank(user)) {
|
||||
NettyClients.getInstance().removeChatbotEventClient(
|
||||
user, MainUtils.getContextID(client.getSessionId().toString()));
|
||||
PassportWebIMUser passportWebIMUser = MainContext.getCache().findOneOnlineUserByUserId(user);
|
||||
|
||||
MainContext.getCache().findOneAgentUserByUserId(user).ifPresent(p -> {
|
||||
ACDServiceRouter.getAcdChatbotService().processChatbotService(null, p);
|
||||
|
||||
MainContext.getCache().deleteAgentUserByUserId(p);
|
||||
MainContext.getCache().deleteOnlineUserById(user);
|
||||
|
||||
p.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
passportWebIMUser.setStatus(MainContext.OnlineUserStatusEnum.OFFLINE.toString());
|
||||
|
||||
getAgentUserRes().save(p);
|
||||
getOnlineUserRes().save(passportWebIMUser);
|
||||
});
|
||||
}
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
// 消息接收入口,网站有新用户接入对话
|
||||
@OnEvent(value = "new")
|
||||
public void onEvent(SocketIOClient client, AckRequest request, Message data) {
|
||||
|
||||
}
|
||||
|
||||
// 消息接收入口,坐席状态更新
|
||||
@OnEvent(value = "agentstatus")
|
||||
public void onEvent(SocketIOClient client, AckRequest request, AgentStatusMessage data) {
|
||||
logger.info("[onEvent] agentstatus: ", data.getMessage());
|
||||
}
|
||||
|
||||
// 消息接收入口,收发消息,用户向机器人发送消息
|
||||
@OnEvent(value = "message")
|
||||
public void onEvent(SocketIOClient client, AckRequest request, ChatMessage data) {
|
||||
String aiid = client.get("aiid");
|
||||
String user = client.get("userid");
|
||||
String sessionid = client.get("session");
|
||||
String appid = client.get("appid");
|
||||
logger.info(
|
||||
"[onEvent] message: session {}, aiid {}, userid {}, dataType {}, appid {}", sessionid, aiid,
|
||||
user, data.getType(), appid);
|
||||
|
||||
// ignore event if dataType is not message.
|
||||
if (!StringUtils.equals(data.getType(), Constants.IM_MESSAGE_TYPE_MESSAGE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MainContext.getCache().findOneAgentUserByUserId(user).ifPresent(p -> {
|
||||
/**
|
||||
* 以下代码主要用于检查 访客端的字数限制
|
||||
*/
|
||||
CousultInvite invite = OnlineUserProxy.consult(data.getAppid());
|
||||
// ignore event if no invite found.
|
||||
if (invite == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore if Chatbot is turnoff.
|
||||
if (!invite.isAi()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
if (invite.getMaxwordsnum() > 0) {
|
||||
if (StringUtils.isNotBlank(data.getMessage()) && data.getMessage().length() > invite.getMaxwordsnum()) {
|
||||
data.setMessage(data.getMessage().substring(0, invite.getMaxwordsnum()));
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(data.getMessage()) && data.getMessage().length() > 300) {
|
||||
data.setMessage(data.getMessage().substring(0, 300));
|
||||
}
|
||||
|
||||
data.setUsession(user); // 绑定唯一用户
|
||||
data.setSessionid(sessionid);
|
||||
data.setMessage(MainUtils.processEmoti(data.getMessage())); // 处理表情
|
||||
data.setTouser(aiid);
|
||||
data.setUsername(p.getUsername());
|
||||
data.setAiid(aiid);
|
||||
data.setAgentserviceid(p.getAgentserviceid());
|
||||
data.setChannel(p.getChanneltype());
|
||||
data.setContextid(p.getAgentserviceid()); // 一定要设置 ContextID
|
||||
data.setCalltype(MainContext.CallType.IN.toString());
|
||||
|
||||
// 保存并发送消息给访客
|
||||
getChatbotProxy().createTextMessage(
|
||||
data,
|
||||
MainContext.CallType.IN.toString());
|
||||
|
||||
// 更新访客咨询记录
|
||||
p.setUpdatetime(now);
|
||||
p.setLastmessage(now);
|
||||
p.setLastmsg(data.getMessage());
|
||||
getAgentUserRes().save(p);
|
||||
|
||||
// 发送消息给Bot
|
||||
getChatbotProxy().publishMessage(data, Constants.CHATBOT_EVENT_TYPE_CHAT);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private AgentUserRepository getAgentUserRes() {
|
||||
if (agentUserRes == null) {
|
||||
agentUserRes = MainContext.getContext().getBean(AgentUserRepository.class);
|
||||
}
|
||||
|
||||
return agentUserRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private ChatbotProxy getChatbotProxy() {
|
||||
if (chatbotProxy == null) {
|
||||
chatbotProxy = MainContext.getContext().getBean(ChatbotProxy.class);
|
||||
}
|
||||
return chatbotProxy;
|
||||
}
|
||||
|
||||
private PassportWebIMUserRepository getOnlineUserRes() {
|
||||
if (onlineUserRes == null) {
|
||||
onlineUserRes = MainContext.getContext().getBean(PassportWebIMUserRepository.class);
|
||||
}
|
||||
|
||||
return onlineUserRes;
|
||||
}
|
||||
|
||||
private ChatbotRepository getChatbotRes() {
|
||||
if (chatbotRes == null) {
|
||||
chatbotRes = MainContext.getContext().getBean(ChatbotRepository.class);
|
||||
}
|
||||
|
||||
return chatbotRes;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <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.plugins.chatbot;
|
||||
|
||||
import com.chatopera.bot.sdk.Response;
|
||||
import com.corundumstudio.socketio.AckRequest;
|
||||
import com.corundumstudio.socketio.SocketIOClient;
|
||||
import com.corundumstudio.socketio.SocketIOServer;
|
||||
import com.corundumstudio.socketio.annotation.OnConnect;
|
||||
import com.corundumstudio.socketio.annotation.OnDisconnect;
|
||||
import com.corundumstudio.socketio.annotation.OnEvent;
|
||||
import com.cskefu.cc.acd.ACDServiceRouter;
|
||||
import com.cskefu.cc.basic.Constants;
|
||||
import com.cskefu.cc.basic.MainContext;
|
||||
import com.cskefu.cc.basic.MainUtils;
|
||||
import com.cskefu.cc.model.*;
|
||||
import com.cskefu.cc.persistence.repository.AgentUserRepository;
|
||||
import com.cskefu.cc.persistence.repository.ChatbotRepository;
|
||||
import com.cskefu.cc.persistence.repository.PassportWebIMUserRepository;
|
||||
import com.cskefu.cc.proxy.OnlineUserProxy;
|
||||
import com.cskefu.cc.socketio.client.NettyClients;
|
||||
import com.cskefu.cc.socketio.message.AgentStatusMessage;
|
||||
import com.cskefu.cc.socketio.message.ChatMessage;
|
||||
import com.cskefu.cc.socketio.message.Message;
|
||||
import com.cskefu.cc.socketio.util.IMServiceUtils;
|
||||
import com.cskefu.cc.util.IP;
|
||||
import com.cskefu.cc.util.IPTools;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Date;
|
||||
|
||||
public class ChatbotEventHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ChatbotEventHandler.class);
|
||||
|
||||
protected final SocketIOServer server;
|
||||
|
||||
private static AgentUserRepository agentUserRes;
|
||||
private static PassportWebIMUserRepository onlineUserRes;
|
||||
private static ChatbotRepository chatbotRes;
|
||||
private static ChatbotProxy chatbotProxy;
|
||||
|
||||
@Autowired
|
||||
public ChatbotEventHandler(SocketIOServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@OnConnect
|
||||
public void onConnect(SocketIOClient client) {
|
||||
try {
|
||||
String user = client.getHandshakeData().getSingleUrlParam("userid");
|
||||
String nickname = client.getHandshakeData().getSingleUrlParam("nickname");
|
||||
String session = MainUtils.getContextID(client.getHandshakeData().getSingleUrlParam("session"));
|
||||
String appid = client.getHandshakeData().getSingleUrlParam("appid");
|
||||
String aiid = client.getHandshakeData().getSingleUrlParam("aiid");
|
||||
logger.info(
|
||||
"[onConnect] userid {}, nickname {}, session {}, appid {}, aiid {}", user, nickname, session, appid,
|
||||
aiid);
|
||||
|
||||
client.set("aiid", aiid);
|
||||
client.set("session", session);
|
||||
client.set("userid", user);
|
||||
client.set("appid", appid);
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
if (StringUtils.isNotBlank(user)) {
|
||||
/**
|
||||
* 加入到 缓存列表
|
||||
*/
|
||||
NettyClients.getInstance().putChatbotEventClient(user, client);
|
||||
CousultInvite invite = OnlineUserProxy.consult(appid);
|
||||
|
||||
/**
|
||||
* 更新坐席服务类型
|
||||
*/
|
||||
IMServiceUtils.shiftOpsType(user, MainContext.OptType.CHATBOT);
|
||||
|
||||
// send out tip
|
||||
Message tip = new Message();
|
||||
tip.setMessage("您正在使用机器人客服!");
|
||||
tip.setMessageType(MainContext.MessageType.MESSAGE.toString());
|
||||
tip.setCalltype(MainContext.CallType.IN.toString());
|
||||
tip.setCreatetime(MainUtils.dateFormate.format(now));
|
||||
|
||||
client.sendEvent(MainContext.MessageType.STATUS.toString(), tip);
|
||||
|
||||
// send out welcome message
|
||||
if (invite != null) {
|
||||
Chatbot chatbot = getChatbotRes().findById(invite.getAiid()).orElse(null);
|
||||
com.chatopera.bot.sdk.Chatbot bot = new com.chatopera.bot.sdk.Chatbot(
|
||||
chatbot.getClientId(), chatbot.getSecret(), chatbot.getBaseUrl());
|
||||
Response result = bot.command("GET", "/");
|
||||
|
||||
// 发送欢迎语
|
||||
if (result.getRc() == 0) {
|
||||
JSONObject details = (JSONObject) result.getData();
|
||||
ChatMessage welcome = new ChatMessage();
|
||||
String welcomeTextMessage = details.getString("welcome");
|
||||
if (StringUtils.isNotBlank(welcomeTextMessage)) {
|
||||
welcome.setCalltype(MainContext.CallType.OUT.toString());
|
||||
welcome.setAppid(appid);
|
||||
welcome.setAiid(aiid);
|
||||
welcome.setMessage(welcomeTextMessage);
|
||||
welcome.setTouser(user);
|
||||
welcome.setMsgtype(MainContext.MessageType.MESSAGE.toString());
|
||||
welcome.setUserid(user);
|
||||
welcome.setUsername(invite.getAiname());
|
||||
welcome.setUpdatetime(System.currentTimeMillis());
|
||||
client.sendEvent(MainContext.MessageType.MESSAGE.toString(), welcome);
|
||||
}
|
||||
|
||||
// 发送常见问题列表
|
||||
JSONObject faqhotresp = bot.conversation(user, "__faq_hot_list");
|
||||
logger.info("faqhot {}", faqhotresp.toString());
|
||||
if (faqhotresp.getInt("rc") == 0) {
|
||||
JSONObject faqhotdata = faqhotresp.getJSONObject("data");
|
||||
if ((!faqhotdata.getBoolean("logic_is_fallback")) &&
|
||||
faqhotdata.has("string") &&
|
||||
faqhotdata.has("params")) {
|
||||
ChatMessage faqhotmsg = new ChatMessage();
|
||||
faqhotmsg.setCalltype(MainContext.CallType.OUT.toString());
|
||||
faqhotmsg.setAppid(appid);
|
||||
faqhotmsg.setAiid(aiid);
|
||||
faqhotmsg.setMessage(faqhotdata.getString("string"));
|
||||
faqhotmsg.setExpmsg(faqhotdata.getJSONArray("params").toString());
|
||||
faqhotmsg.setTouser(user);
|
||||
faqhotmsg.setMsgtype(MainContext.MessageType.MESSAGE.toString());
|
||||
faqhotmsg.setUserid(user);
|
||||
faqhotmsg.setUsername(invite.getAiname());
|
||||
faqhotmsg.setUpdatetime(System.currentTimeMillis());
|
||||
client.sendEvent(MainContext.MessageType.MESSAGE.toString(), faqhotmsg);
|
||||
}
|
||||
}
|
||||
} else if (result.getRc() == 999 || result.getRc() == 998) {
|
||||
logger.error("[chat] chatbot agent response rc {}, error {}", result.getRc(), result.getError());
|
||||
} else {
|
||||
logger.error("[chat] chatbot agent response rc {}, error {}", result.getRc(), result.getError());
|
||||
}
|
||||
}
|
||||
|
||||
InetSocketAddress address = (InetSocketAddress) client.getRemoteAddress();
|
||||
String ip = MainUtils.getIpAddr(client.getHandshakeData().getHttpHeaders(), address.getHostString());
|
||||
PassportWebIMUser passportWebIMUser = getOnlineUserRes().findById(user).orElse(null);
|
||||
|
||||
if (passportWebIMUser == null) {
|
||||
passportWebIMUser = new PassportWebIMUser();
|
||||
passportWebIMUser.setAppid(appid);
|
||||
if (StringUtils.isNotBlank(nickname)) {
|
||||
passportWebIMUser.setUsername(nickname);
|
||||
} else {
|
||||
passportWebIMUser.setUsername(Constants.GUEST_USER + "_" + MainUtils.genIDByKey(user));
|
||||
}
|
||||
|
||||
passportWebIMUser.setSessionid(session);
|
||||
passportWebIMUser.setOptype(MainContext.OptType.CHATBOT.toString());
|
||||
passportWebIMUser.setUserid(user);
|
||||
passportWebIMUser.setId(user);
|
||||
passportWebIMUser.setChannel(MainContext.ChannelType.WEBIM.toString());
|
||||
passportWebIMUser.setIp(ip);
|
||||
passportWebIMUser.setUpdatetime(now);
|
||||
passportWebIMUser.setLogintime(now);
|
||||
passportWebIMUser.setCreatetime(now);
|
||||
IP ipdata = IPTools.getInstance().findGeography(ip);
|
||||
passportWebIMUser.setCity(ipdata.getCity());
|
||||
passportWebIMUser.setCountry(ipdata.getCountry());
|
||||
passportWebIMUser.setProvince(ipdata.getProvince());
|
||||
passportWebIMUser.setIsp(ipdata.getIsp());
|
||||
passportWebIMUser.setRegion(ipdata.getRegion());
|
||||
passportWebIMUser.setStatus(MainContext.OnlineUserStatusEnum.ONLINE.toString());
|
||||
}
|
||||
|
||||
// 在线客服访客咨询记录
|
||||
AgentUser agentUser = new AgentUser(
|
||||
passportWebIMUser.getId(),
|
||||
MainContext.ChannelType.WEBIM.toString(), // callout
|
||||
passportWebIMUser.getId(),
|
||||
passportWebIMUser.getUsername(),
|
||||
appid);
|
||||
|
||||
agentUser.setServicetime(now);
|
||||
agentUser.setCreatetime(now);
|
||||
agentUser.setUpdatetime(now);
|
||||
agentUser.setSessionid(session);
|
||||
agentUser.setRegion(passportWebIMUser.getRegion());
|
||||
|
||||
// 聊天机器人处理的请求
|
||||
agentUser.setOpttype(MainContext.OptType.CHATBOT.toString());
|
||||
agentUser.setAgentno(aiid); // 聊天机器人ID
|
||||
agentUser.setAgentname(invite != null ? invite.getAiname() : "机器人客服");
|
||||
agentUser.setCity(passportWebIMUser.getCity());
|
||||
agentUser.setProvince(passportWebIMUser.getProvince());
|
||||
agentUser.setCountry(passportWebIMUser.getCountry());
|
||||
AgentService agentService = ACDServiceRouter.getAcdChatbotService().processChatbotService(
|
||||
invite != null ? invite.getAiname() : "机器人客服", agentUser);
|
||||
agentUser.setAgentserviceid(agentService.getId());
|
||||
|
||||
// 标记为机器人坐席
|
||||
agentUser.setChatbotops(true);
|
||||
|
||||
// 保存到MySQL
|
||||
getAgentUserRes().save(agentUser);
|
||||
getOnlineUserRes().save(passportWebIMUser);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.info("[onConnect] error", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 @OnDisconnect 事件,客户端断开连接时调用,刷新客户端信息
|
||||
@OnDisconnect
|
||||
public void onDisconnect(SocketIOClient client) {
|
||||
String user = client.getHandshakeData().getSingleUrlParam("userid");
|
||||
if (StringUtils.isNotBlank(user)) {
|
||||
NettyClients.getInstance().removeChatbotEventClient(
|
||||
user, MainUtils.getContextID(client.getSessionId().toString()));
|
||||
PassportWebIMUser passportWebIMUser = MainContext.getCache().findOneOnlineUserByUserId(user);
|
||||
|
||||
MainContext.getCache().findOneAgentUserByUserId(user).ifPresent(p -> {
|
||||
ACDServiceRouter.getAcdChatbotService().processChatbotService(null, p);
|
||||
|
||||
MainContext.getCache().deleteAgentUserByUserId(p);
|
||||
MainContext.getCache().deleteOnlineUserById(user);
|
||||
|
||||
p.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
passportWebIMUser.setStatus(MainContext.OnlineUserStatusEnum.OFFLINE.toString());
|
||||
|
||||
getAgentUserRes().save(p);
|
||||
getOnlineUserRes().save(passportWebIMUser);
|
||||
});
|
||||
}
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
// 消息接收入口,网站有新用户接入对话
|
||||
@OnEvent(value = "new")
|
||||
public void onEvent(SocketIOClient client, AckRequest request, Message data) {
|
||||
|
||||
}
|
||||
|
||||
// 消息接收入口,坐席状态更新
|
||||
@OnEvent(value = "agentstatus")
|
||||
public void onEvent(SocketIOClient client, AckRequest request, AgentStatusMessage data) {
|
||||
logger.info("[onEvent] agentstatus: ", data.getMessage());
|
||||
}
|
||||
|
||||
// 消息接收入口,收发消息,用户向机器人发送消息
|
||||
@OnEvent(value = "message")
|
||||
public void onEvent(SocketIOClient client, AckRequest request, ChatMessage data) {
|
||||
String aiid = client.get("aiid");
|
||||
String user = client.get("userid");
|
||||
String sessionid = client.get("session");
|
||||
String appid = client.get("appid");
|
||||
logger.info(
|
||||
"[onEvent] message: session {}, aiid {}, userid {}, dataType {}, appid {}", sessionid, aiid,
|
||||
user, data.getType(), appid);
|
||||
|
||||
// ignore event if dataType is not message.
|
||||
if (!StringUtils.equals(data.getType(), Constants.IM_MESSAGE_TYPE_MESSAGE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MainContext.getCache().findOneAgentUserByUserId(user).ifPresent(p -> {
|
||||
/**
|
||||
* 以下代码主要用于检查 访客端的字数限制
|
||||
*/
|
||||
CousultInvite invite = OnlineUserProxy.consult(data.getAppid());
|
||||
// ignore event if no invite found.
|
||||
if (invite == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore if Chatbot is turnoff.
|
||||
if (!invite.isAi()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
if (invite.getMaxwordsnum() > 0) {
|
||||
if (StringUtils.isNotBlank(data.getMessage()) && data.getMessage().length() > invite.getMaxwordsnum()) {
|
||||
data.setMessage(data.getMessage().substring(0, invite.getMaxwordsnum()));
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(data.getMessage()) && data.getMessage().length() > 300) {
|
||||
data.setMessage(data.getMessage().substring(0, 300));
|
||||
}
|
||||
|
||||
data.setUsession(user); // 绑定唯一用户
|
||||
data.setSessionid(sessionid);
|
||||
data.setMessage(MainUtils.processEmoti(data.getMessage())); // 处理表情
|
||||
data.setTouser(aiid);
|
||||
data.setUsername(p.getUsername());
|
||||
data.setAiid(aiid);
|
||||
data.setAgentserviceid(p.getAgentserviceid());
|
||||
data.setChannel(p.getChanneltype());
|
||||
data.setContextid(p.getAgentserviceid()); // 一定要设置 ContextID
|
||||
data.setCalltype(MainContext.CallType.IN.toString());
|
||||
|
||||
// 保存并发送消息给访客
|
||||
getChatbotProxy().createTextMessage(
|
||||
data,
|
||||
MainContext.CallType.IN.toString());
|
||||
|
||||
// 更新访客咨询记录
|
||||
p.setUpdatetime(now);
|
||||
p.setLastmessage(now);
|
||||
p.setLastmsg(data.getMessage());
|
||||
getAgentUserRes().save(p);
|
||||
|
||||
// 发送消息给Bot
|
||||
getChatbotProxy().publishMessage(data, Constants.CHATBOT_EVENT_TYPE_CHAT);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private AgentUserRepository getAgentUserRes() {
|
||||
if (agentUserRes == null) {
|
||||
agentUserRes = MainContext.getContext().getBean(AgentUserRepository.class);
|
||||
}
|
||||
|
||||
return agentUserRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private ChatbotProxy getChatbotProxy() {
|
||||
if (chatbotProxy == null) {
|
||||
chatbotProxy = MainContext.getContext().getBean(ChatbotProxy.class);
|
||||
}
|
||||
return chatbotProxy;
|
||||
}
|
||||
|
||||
private PassportWebIMUserRepository getOnlineUserRes() {
|
||||
if (onlineUserRes == null) {
|
||||
onlineUserRes = MainContext.getContext().getBean(PassportWebIMUserRepository.class);
|
||||
}
|
||||
|
||||
return onlineUserRes;
|
||||
}
|
||||
|
||||
private ChatbotRepository getChatbotRes() {
|
||||
if (chatbotRes == null) {
|
||||
chatbotRes = MainContext.getContext().getBean(ChatbotRepository.class);
|
||||
}
|
||||
|
||||
return chatbotRes;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
/*
|
||||
* 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,
|
||||
* 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.proxy;
|
||||
@ -34,6 +34,7 @@ import org.springframework.web.servlet.ModelAndView;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
@ -96,6 +97,8 @@ public class AgentUserProxy {
|
||||
@Lazy
|
||||
private PeerSyncIM peerSyncIM;
|
||||
|
||||
@Autowired
|
||||
private LicenseProxy licenseProxy;
|
||||
|
||||
/**
|
||||
* 与联系人主动聊天前查找获取AgentUser
|
||||
|
@ -0,0 +1,565 @@
|
||||
/*
|
||||
* 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.proxy;
|
||||
|
||||
import com.chatopera.store.enums.LICSTATUS;
|
||||
import com.chatopera.store.sdk.QuotaWdClient;
|
||||
import com.chatopera.store.sdk.Response;
|
||||
import com.chatopera.store.sdk.exceptions.InvalidRequestException;
|
||||
import com.chatopera.store.sdk.exceptions.InvalidResponseException;
|
||||
import com.cskefu.cc.basic.Constants;
|
||||
import com.cskefu.cc.basic.MainContext;
|
||||
import com.cskefu.cc.basic.MainUtils;
|
||||
import com.cskefu.cc.exception.*;
|
||||
import com.cskefu.cc.model.AgentUser;
|
||||
import com.cskefu.cc.model.ExecuteResult;
|
||||
import com.cskefu.cc.model.MetaKv;
|
||||
import com.cskefu.cc.persistence.repository.MetaKvRepository;
|
||||
import com.cskefu.cc.util.Base62;
|
||||
import com.cskefu.cc.util.DateConverter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 证书服务
|
||||
*/
|
||||
@Service
|
||||
public class LicenseProxy {
|
||||
private final static Logger logger = LoggerFactory.getLogger(LicenseProxy.class);
|
||||
|
||||
@Autowired
|
||||
private MetaKvRepository metaKvRes;
|
||||
|
||||
@Autowired
|
||||
private QuotaWdClient quotaWdClient;
|
||||
|
||||
private static final Map<MainContext.BillingResource, Integer> BILLING_RES_QUOTA_MAPPINGS = new HashMap<>();
|
||||
|
||||
static {
|
||||
BILLING_RES_QUOTA_MAPPINGS.put(MainContext.BillingResource.USER, 100);
|
||||
BILLING_RES_QUOTA_MAPPINGS.put(MainContext.BillingResource.AGENGUSER, 1);
|
||||
BILLING_RES_QUOTA_MAPPINGS.put(MainContext.BillingResource.CHANNELWEBIM, 100);
|
||||
BILLING_RES_QUOTA_MAPPINGS.put(MainContext.BillingResource.CONTACT, 1);
|
||||
BILLING_RES_QUOTA_MAPPINGS.put(MainContext.BillingResource.ORGAN, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 serverinstId
|
||||
* serverinstId 作为服务唯一的实例ID
|
||||
*/
|
||||
public void checkOnStartup() {
|
||||
/**
|
||||
* Check service connection
|
||||
*/
|
||||
System.out.println("[license] license service URL " + quotaWdClient.getBaseUrl());
|
||||
try {
|
||||
Response resp = quotaWdClient.ping();
|
||||
System.out.println("[license] license service ping successfully.");
|
||||
|
||||
if (resp.getRc() != 0) {
|
||||
throw new InvalidResponseException("Unexpected response from license service " + resp.toString());
|
||||
}
|
||||
} catch (InvalidResponseException e) {
|
||||
logger.error("[license] make sure this host machine could connect to " + quotaWdClient.getBaseUrl() + " during running.");
|
||||
logger.error("[license] checkOnStartup could not connect to license service, CSKeFu instance is terminated.", e);
|
||||
// Very serious event happens, just shutdown the instance
|
||||
SpringApplication.exit(MainContext.getContext(), () -> 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init local data for License
|
||||
*/
|
||||
resolveServerinstId();
|
||||
resolveServicename();
|
||||
resolveLicenseIds();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取或初始化 serverinstId
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String resolveServerinstId() {
|
||||
Optional<MetaKv> metaServerinstIdOpt = metaKvRes.findFirstByMetakey(Constants.LICENSE_SERVER_INST_ID);
|
||||
if (metaServerinstIdOpt.isEmpty()) {
|
||||
// 没有 serverinstId 信息,初始化
|
||||
final String serverinstId = Base62.generateShortId();
|
||||
createMetaKv(Constants.LICENSE_SERVER_INST_ID, serverinstId, Constants.METAKV_DATATYPE_STRING);
|
||||
return serverinstId;
|
||||
}
|
||||
return metaServerinstIdOpt.get().getMetavalue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取或初始化 licenseIds
|
||||
*/
|
||||
private void resolveLicenseIds() {
|
||||
Optional<MetaKv> metaLicensesOpt = metaKvRes.findFirstByMetakey(Constants.LICENSEIDS);
|
||||
if (metaLicensesOpt.isEmpty()) {
|
||||
// 没有 license 信息,初始化
|
||||
createMetaKv(Constants.LICENSEIDS, (new JSONArray()).toString(), Constants.METAKV_DATATYPE_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取或初始化 serviceName
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String resolveServicename() {
|
||||
Optional<MetaKv> metaServicenameOpt = metaKvRes.findFirstByMetakey(Constants.LICENSE_SERVICE_NAME);
|
||||
if (metaServicenameOpt.isEmpty()) {
|
||||
// 没有 Service Name 信息,初始化
|
||||
final String serviceName = generateLicenseServiceName();
|
||||
createMetaKv(Constants.LICENSE_SERVICE_NAME, serviceName, Constants.METAKV_DATATYPE_STRING);
|
||||
return serviceName;
|
||||
}
|
||||
return metaServicenameOpt.get().getMetavalue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 MetaKv 表中取得数据 MetaKv
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
* @throws MetaKvNotExistException
|
||||
*/
|
||||
public MetaKv retrieveMetaKv(final String key) throws MetaKvNotExistException, MetaKvInvalidKeyException {
|
||||
|
||||
if (StringUtils.isBlank(key)) {
|
||||
throw new MetaKvInvalidKeyException("Key must not be empy");
|
||||
}
|
||||
|
||||
Optional<MetaKv> kvOpt = metaKvRes.findFirstByMetakey(key);
|
||||
if (kvOpt.isEmpty()) {
|
||||
throw new MetaKvNotExistException(key + " not exist");
|
||||
} else {
|
||||
return kvOpt.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或更新 MetaKv
|
||||
* UpdateOnExist
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @param datatype
|
||||
*/
|
||||
public MetaKv createOrUpdateMetaKv(final String key, final String value, final String datatype) throws MetaKvInvalidKeyException {
|
||||
try {
|
||||
MetaKv kv = retrieveMetaKv(key);
|
||||
kv.setMetavalue(value);
|
||||
kv.setUpdatetime(new Date());
|
||||
metaKvRes.save(kv);
|
||||
return kv;
|
||||
} catch (MetaKvNotExistException e) {
|
||||
return createMetaKv(key, value, datatype);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 建立 Metakv 数据
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @param datatype
|
||||
*/
|
||||
public MetaKv createMetaKv(final String key, final String value, final String datatype) {
|
||||
Date now = new Date();
|
||||
MetaKv metakv = new MetaKv();
|
||||
metakv.setCreatetime(now);
|
||||
metakv.setUpdatetime(now);
|
||||
metakv.setMetakey(key);
|
||||
metakv.setMetavalue(value);
|
||||
metakv.setDatatype(datatype);
|
||||
|
||||
metaKvRes.save(metakv);
|
||||
return metakv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加 MetaKv 中的 Key 的值,作为 Integer 做增量,不存在则初始化其值为 0,然后增量操作
|
||||
*
|
||||
* @param key
|
||||
* @param incrValue
|
||||
*/
|
||||
public MetaKv increValueInMetaKv(final String key, final int incrValue) {
|
||||
try {
|
||||
MetaKv kv = retrieveMetaKv(key);
|
||||
int pre = Integer.parseInt(kv.getMetavalue());
|
||||
kv.setMetavalue(Integer.toString(pre + incrValue));
|
||||
kv.setUpdatetime(new Date());
|
||||
metaKvRes.save(kv);
|
||||
return kv;
|
||||
} catch (MetaKvNotExistException e) {
|
||||
return createMetaKv(key, Integer.toString(incrValue), Constants.METAKV_DATATYPE_INT);
|
||||
} catch (MetaKvInvalidKeyException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成随机字符串,作为服务名称
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String generateLicenseServiceName() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
sb.append(Constants.LICENSE_SERVICE_NAME_PREFIX);
|
||||
sb.append(Base62.generatingRandomAlphanumericString(5));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库及证书商店获得证书列表信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<JSONObject> getLicensesInStore() throws InvalidResponseException {
|
||||
List<JSONObject> result = new ArrayList<>();
|
||||
|
||||
try {
|
||||
JSONArray ja = new JSONArray((retrieveMetaKv(Constants.LICENSEIDS).getMetavalue()));
|
||||
HashMap<String, String> addDates = new HashMap<>();
|
||||
List<String> licenseIds = new ArrayList<>();
|
||||
for (int i = 0; i < ja.length(); i++) {
|
||||
JSONObject obj = ((JSONObject) ja.get(i));
|
||||
licenseIds.add(obj.getString(Constants.SHORTID));
|
||||
addDates.put(obj.getString(Constants.SHORTID), obj.getString(Constants.ADDDATE));
|
||||
}
|
||||
|
||||
Response resp = null;
|
||||
try {
|
||||
resp = quotaWdClient.getLicenseBasics(licenseIds);
|
||||
} catch (InvalidRequestException e) {
|
||||
return result;
|
||||
}
|
||||
JSONArray data = (JSONArray) resp.getData();
|
||||
|
||||
for (int i = 0; i < data.length(); i++) {
|
||||
JSONObject lic = (JSONObject) data.get(i);
|
||||
if(StringUtils.equals(lic.getJSONObject(Constants.LICENSE).getString(Constants.STATUS), "notfound")){
|
||||
// fill in placeholders for notfound license
|
||||
final JSONObject licenseJsonTmp = lic.getJSONObject(Constants.LICENSE);
|
||||
licenseJsonTmp.put("effectivedateend", "N/A");
|
||||
licenseJsonTmp.put("quotaeffectiveremaining", "N/A");
|
||||
|
||||
JSONObject productJsonTmp = new JSONObject();
|
||||
productJsonTmp.put("shortId", "N/A");
|
||||
productJsonTmp.put("name", "N/A");
|
||||
lic.put("product", productJsonTmp);
|
||||
|
||||
JSONObject userJsonTmp = new JSONObject();
|
||||
userJsonTmp.put("nickname", "N/A");
|
||||
lic.put("user", userJsonTmp);
|
||||
|
||||
// lic.put(Constants.ADDDATE, null);
|
||||
|
||||
result.add(lic);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
Date addDate = DateConverter.parseCSTAsChinaTimezone(addDates.get(lic.getJSONObject(Constants.LICENSE).getString(Constants.SHORTID)));
|
||||
lic.put(Constants.ADDDATE, addDate);
|
||||
} catch (ParseException e) {
|
||||
logger.info("[getLicensesFromStore] can not resolve add date");
|
||||
}
|
||||
result.add(lic);
|
||||
}
|
||||
|
||||
} catch (MetaKvNotExistException e) {
|
||||
logger.info("[getLicenses] no LICENSEIDS data in MySQL DB");
|
||||
} catch (MetaKvInvalidKeyException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得在 MetaKV 表中的 license 信息
|
||||
*
|
||||
* @return JSONArray
|
||||
*/
|
||||
public JSONArray getLicensesInMetakv() {
|
||||
try {
|
||||
String value = retrieveMetaKv(Constants.LICENSEIDS).getMetavalue();
|
||||
return new JSONArray(value);
|
||||
} catch (MetaKvNotExistException e) {
|
||||
return new JSONArray();
|
||||
} catch (MetaKvInvalidKeyException e) {
|
||||
return new JSONArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param licenseShortId
|
||||
* @return
|
||||
* @throws InvalidResponseException
|
||||
*/
|
||||
public JSONObject getLicenseBasicsInStore(final String licenseShortId) throws InvalidResponseException, InvalidRequestException {
|
||||
Response resp = quotaWdClient.getLicenseBasics(licenseShortId);
|
||||
if (resp.getRc() == 0) {
|
||||
JSONArray data = (JSONArray) resp.getData();
|
||||
if (data.length() != 1)
|
||||
throw new InvalidResponseException("Unexpected data in Response.");
|
||||
|
||||
return (JSONObject) (data).get(0);
|
||||
} else {
|
||||
throw new InvalidResponseException("Unexpected Response.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得已经添加的证书在 Store 中的基本信息
|
||||
*
|
||||
* @return
|
||||
* @throws InvalidResponseException
|
||||
*/
|
||||
public JSONArray getAddedLicenseBasicsInStore() throws InvalidResponseException {
|
||||
JSONArray arr = getLicensesInMetakv();
|
||||
List<String> ids = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
ids.add(((JSONObject) arr.get(i)).getString(Constants.SHORTID));
|
||||
}
|
||||
|
||||
if (ids.size() > 0) {
|
||||
Response resp = null;
|
||||
try {
|
||||
resp = quotaWdClient.getLicenseBasics(StringUtils.join(ids, ","));
|
||||
} catch (InvalidRequestException e) {
|
||||
logger.error("[getAddedLicenseBasicsFromStore] InvalidRequestException", e);
|
||||
}
|
||||
if (resp.getRc() != 0) {
|
||||
throw new InvalidResponseException("Invalid response, rc " + Integer.toString(resp.getRc()));
|
||||
}
|
||||
|
||||
return (JSONArray) resp.getData();
|
||||
} else {
|
||||
logger.error("[license] getAddedLicenseBasicsFromStore - No license ids in metaKv");
|
||||
return new JSONArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证证书存在
|
||||
*
|
||||
* @param licenseShortId
|
||||
* @return
|
||||
*/
|
||||
public LICSTATUS existLicenseInStore(final String licenseShortId) throws InvalidResponseException, LicenseNotFoundException, InvalidRequestException {
|
||||
Map<String, LICSTATUS> statuses = quotaWdClient.getLicenseStatus(licenseShortId);
|
||||
|
||||
if (statuses.size() == 1) {
|
||||
for (final Map.Entry<String, LICSTATUS> entry : statuses.entrySet()) {
|
||||
final LICSTATUS status = entry.getValue();
|
||||
|
||||
if (status == LICSTATUS.NOTFOUND)
|
||||
throw new LicenseNotFoundException("LicenseId not found [" + licenseShortId + "]");
|
||||
|
||||
return status;
|
||||
}
|
||||
throw new InvalidResponseException("Unexpected response, internal error.");
|
||||
} else {
|
||||
throw new InvalidResponseException("Unexpected response, should contain one record.");
|
||||
}
|
||||
}
|
||||
|
||||
public String getLicenseStoreProvider() {
|
||||
return quotaWdClient.getBaseUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得资源用量存储
|
||||
*
|
||||
* @param resourceKey
|
||||
* @return
|
||||
*/
|
||||
public String getResourceUsageKey(final String resourceKey) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(Constants.RESOURCES_USAGE_KEY_PREFIX);
|
||||
sb.append("_");
|
||||
sb.append(StringUtils.toRootUpperCase(resourceKey));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 增加计费资源用量
|
||||
*
|
||||
* @param billingResource
|
||||
* @param consume
|
||||
*/
|
||||
public void increResourceUsageInMetaKv(final MainContext.BillingResource billingResource, int consume) throws BillingResourceException {
|
||||
switch (billingResource) {
|
||||
case USER:
|
||||
case CONTACT:
|
||||
case ORGAN:
|
||||
case AGENGUSER:
|
||||
case CHANNELWEBIM:
|
||||
increValueInMetaKv(getResourceUsageKey(billingResource.toString()), consume);
|
||||
break;
|
||||
default:
|
||||
throw new BillingResourceException("invalid_billing_resource_type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在 MetaKv 中资源的已经使用的计数
|
||||
*
|
||||
* @param billingResource
|
||||
* @return
|
||||
*/
|
||||
private int getResourceUsageInMetaKv(final MainContext.BillingResource billingResource) {
|
||||
final String key = getResourceUsageKey(billingResource.toString());
|
||||
try {
|
||||
MetaKv kv = retrieveMetaKv(key);
|
||||
int pre = Integer.parseInt(kv.getMetavalue());
|
||||
return pre;
|
||||
} catch (MetaKvNotExistException e) {
|
||||
createMetaKv(key, Integer.toString(0), Constants.METAKV_DATATYPE_INT);
|
||||
return 0;
|
||||
} catch (MetaKvInvalidKeyException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得春松客服 cskefu001 产品的证书标识 ID
|
||||
* 春松客服证书基本类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getLicenseIdAsCskefu001InMetaKv() throws BillingQuotaException {
|
||||
JSONArray curr = getLicensesInMetakv();
|
||||
for (int i = 0; i < curr.length(); i++) {
|
||||
JSONObject jo = (JSONObject) curr.get(i);
|
||||
if (jo.has(Constants.PRODUCT_ID) &&
|
||||
StringUtils.equals(jo.getString(Constants.PRODUCT_ID), Constants.PRODUCT_ID_CSKEFU001)) {
|
||||
return jo.getString(Constants.SHORTID);
|
||||
}
|
||||
}
|
||||
throw new BillingQuotaException(BillingQuotaException.NO_LICENSE_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行配额变更操作
|
||||
*
|
||||
* @param billingResource
|
||||
* @param unitNum
|
||||
* @return
|
||||
*/
|
||||
public void writeDownResourceUsageInStore(final MainContext.BillingResource billingResource,
|
||||
int unitNum) throws BillingQuotaException, BillingResourceException {
|
||||
|
||||
// 检查是否还在体验阶段
|
||||
if (billingResource == MainContext.BillingResource.CONTACT) {
|
||||
int alreadyUsed = getResourceUsageInMetaKv(billingResource);
|
||||
if (alreadyUsed <= 1) {
|
||||
// 可以免费创建 1 个联系人
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 请求操作配额
|
||||
String licenseId = getLicenseIdAsCskefu001InMetaKv();
|
||||
String serverinstId = resolveServerinstId();
|
||||
String servicename = resolveServicename();
|
||||
|
||||
try {
|
||||
Response resp = quotaWdClient.write(licenseId,
|
||||
serverinstId, servicename, unitNum * BILLING_RES_QUOTA_MAPPINGS.get(billingResource));
|
||||
// 识别操作是否完成,并处理
|
||||
if (resp.getRc() == 0) {
|
||||
final JSONObject data = (JSONObject) resp.getData();
|
||||
|
||||
// 配额操作成功,执行计数
|
||||
increResourceUsageInMetaKv(billingResource, unitNum);
|
||||
} else if (resp.getRc() == 1 || resp.getRc() == 2) {
|
||||
throw new BillingQuotaException(BillingQuotaException.INVALID_REQUEST_BODY);
|
||||
} else if (resp.getRc() == 3) {
|
||||
// 证书商店中不存在
|
||||
throw new BillingQuotaException(BillingQuotaException.LICENSE_INVALID);
|
||||
} else if (resp.getRc() == 4) {
|
||||
// 证书商店中不存在该产品或产品类型无效
|
||||
throw new BillingQuotaException(BillingQuotaException.PRODUCT_INVALID);
|
||||
} else if (resp.getRc() == 5) {
|
||||
// 该证书不支持资源回退
|
||||
throw new BillingQuotaException(BillingQuotaException.LICENSE_UNSUPPORT_REFUND);
|
||||
} else if (resp.getRc() == 6) {
|
||||
// 证书失效或耗尽,不支持继续扣除配额
|
||||
throw new BillingQuotaException(BillingQuotaException.LICENSE_EXPIRED_OR_EXHAUSTED);
|
||||
} else if (resp.getRc() == 7) {
|
||||
// 该证书禁用了该 serverinstId
|
||||
throw new BillingQuotaException(BillingQuotaException.LICENSE_DISABLED_SERVERINST);
|
||||
} else if (resp.getRc() == 8) {
|
||||
// 配额扣除额度超过该证书目前的剩余量
|
||||
throw new BillingQuotaException(BillingQuotaException.LICENSE_QUOTA_INADEQUATE);
|
||||
} else {
|
||||
// 未知情况
|
||||
logger.error("[writeDownResourceUsageInStore] resp data {}", resp.toString());
|
||||
throw new BillingQuotaException(BillingQuotaException.INTERNAL_ERROR);
|
||||
}
|
||||
} catch (InvalidResponseException e) {
|
||||
// TODO 处理异常信息
|
||||
logger.error("[writeDownResourceUsageInStore] error ", e);
|
||||
throw new BillingQuotaException(BillingQuotaException.RESPONSE_UNEXPECTED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 访客会话执行计费
|
||||
*
|
||||
* @param agentUser
|
||||
* @return
|
||||
*/
|
||||
public ExecuteResult writeDownAgentUserUsageInStore(final AgentUser agentUser) {
|
||||
// 检查是否还在体验阶段
|
||||
ExecuteResult er = new ExecuteResult();
|
||||
int alreadyUsed = getResourceUsageInMetaKv(MainContext.BillingResource.AGENGUSER);
|
||||
if (alreadyUsed <= 100) {
|
||||
// 可以免费创建 100 个访客会话
|
||||
er.setRc(ExecuteResult.RC_SUCC);
|
||||
return er;
|
||||
}
|
||||
|
||||
try {
|
||||
writeDownResourceUsageInStore(MainContext.BillingResource.AGENGUSER, 1);
|
||||
er.setRc(ExecuteResult.RC_SUCC);
|
||||
} catch (BillingQuotaException e) {
|
||||
er.setRc(ExecuteResult.RC_ERR1);
|
||||
er.setMsg(e.getMessage());
|
||||
} catch (BillingResourceException e) {
|
||||
er.setRc(ExecuteResult.RC_ERR2);
|
||||
er.setMsg(e.getMessage());
|
||||
}
|
||||
|
||||
return er;
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
/*
|
||||
* 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.
|
||||
* Copyright (C) 2019-2022 Chatopera Inc, All rights reserved.
|
||||
* <https://www.chatopera.com>
|
||||
*/
|
||||
|
||||
@ -16,6 +16,7 @@ package com.cskefu.cc.proxy;
|
||||
import com.cskefu.cc.basic.Constants;
|
||||
import com.cskefu.cc.basic.MainContext;
|
||||
import com.cskefu.cc.basic.MainUtils;
|
||||
import com.cskefu.cc.exception.BillingQuotaException;
|
||||
import com.cskefu.cc.model.*;
|
||||
import com.cskefu.cc.persistence.repository.*;
|
||||
import com.cskefu.cc.util.restapi.RestUtils;
|
||||
@ -29,6 +30,8 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -79,22 +82,34 @@ public class UserProxy {
|
||||
public JsonObject createNewUser(final User user, Organ organ) {
|
||||
JsonObject result = new JsonObject();
|
||||
String msg = validUser(user);
|
||||
if (StringUtils.equalsIgnoreCase(msg, "new_user_success")) {
|
||||
|
||||
if (StringUtils.equalsIgnoreCase(msg, Constants.NEW_USER_SUCCESS)) {
|
||||
// 此时 msg 是 new_user_success
|
||||
user.setSuperadmin(false); // 不支持创建第二个系统管理员
|
||||
try {
|
||||
if (StringUtils.isNotBlank(user.getPassword())) {
|
||||
user.setPassword(MainUtils.md5(user.getPassword()));
|
||||
}
|
||||
userRes.save(user);
|
||||
|
||||
if (StringUtils.isNotBlank(user.getPassword())) {
|
||||
user.setPassword(MainUtils.md5(user.getPassword()));
|
||||
}
|
||||
userRes.save(user);
|
||||
|
||||
if (organ != null) {
|
||||
OrganUser ou = new OrganUser();
|
||||
ou.setUserid(user.getId());
|
||||
ou.setOrgan(organ.getId());
|
||||
organUserRes.save(ou);
|
||||
if (organ != null) {
|
||||
OrganUser ou = new OrganUser();
|
||||
ou.setUserid(user.getId());
|
||||
ou.setOrgan(organ.getId());
|
||||
organUserRes.save(ou);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof UndeclaredThrowableException) {
|
||||
logger.error("[createNewUser] BillingQuotaException", e);
|
||||
if (StringUtils.startsWith(e.getCause().getMessage(), BillingQuotaException.SUFFIX)) {
|
||||
msg = e.getCause().getMessage();
|
||||
}
|
||||
} else {
|
||||
logger.error("[createNewUser] err", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 新账号未通过验证,返回创建失败信息msg
|
||||
result.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
|
||||
result.addProperty(RestUtils.RESP_KEY_DATA, msg);
|
||||
|
@ -1,63 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
* 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,
|
||||
* 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,
|
||||
* Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0,
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package com.cskefu.cc.util;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
public class Base62 {
|
||||
private static final int BINARY = 0x2;
|
||||
private static final int BINARY = 0x2;
|
||||
|
||||
private static final int NUMBER_61 = 0x0000003d;
|
||||
|
||||
static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
||||
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
|
||||
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
|
||||
'X', 'Y', 'Z' };
|
||||
private static final int NUMBER_61 = 0x0000003d;
|
||||
|
||||
static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
||||
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
|
||||
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
|
||||
'X', 'Y', 'Z'};
|
||||
|
||||
|
||||
public static String encode(long value){
|
||||
return encode(String.valueOf(value)).toLowerCase() ;
|
||||
}
|
||||
|
||||
public static String encode(String str){
|
||||
String md5Hex = DigestUtils.md5Hex(str);
|
||||
// 6 digit binary can indicate 62 letter & number from 0-9a-zA-Z
|
||||
int binaryLength = 6 * 6;
|
||||
long binaryLengthFixer = Long.valueOf(StringUtils.repeat("1", binaryLength), BINARY);
|
||||
for (int i = 0; i < 4;) {
|
||||
String subString = StringUtils.substring(md5Hex, i * 8, (i + 1) * 8);
|
||||
subString = Long.toBinaryString(Long.valueOf(subString, 16) & binaryLengthFixer);
|
||||
subString = StringUtils.leftPad(subString, binaryLength, "0");
|
||||
StringBuilder sbBuilder = new StringBuilder();
|
||||
for (int j = 0; j < 6; j++) {
|
||||
String subString2 = StringUtils.substring(subString, j * 6, (j + 1) * 6);
|
||||
int charIndex = Integer.valueOf(subString2, BINARY) & NUMBER_61;
|
||||
sbBuilder.append(DIGITS[charIndex]);
|
||||
}
|
||||
String shortUrl = sbBuilder.toString();
|
||||
if(shortUrl!=null){
|
||||
return shortUrl;
|
||||
}
|
||||
}
|
||||
// if all 4 possibilities are already exists
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String encode(long value) {
|
||||
return encode(String.valueOf(value)).toLowerCase();
|
||||
}
|
||||
|
||||
public static String encode(String str) {
|
||||
String md5Hex = DigestUtils.md5Hex(str);
|
||||
// 6 digit binary can indicate 62 letter & number from 0-9a-zA-Z
|
||||
int binaryLength = 6 * 6;
|
||||
long binaryLengthFixer = Long.valueOf(StringUtils.repeat("1", binaryLength), BINARY);
|
||||
for (int i = 0; i < 4; ) {
|
||||
String subString = StringUtils.substring(md5Hex, i * 8, (i + 1) * 8);
|
||||
subString = Long.toBinaryString(Long.valueOf(subString, 16) & binaryLengthFixer);
|
||||
subString = StringUtils.leftPad(subString, binaryLength, "0");
|
||||
StringBuilder sbBuilder = new StringBuilder();
|
||||
for (int j = 0; j < 6; j++) {
|
||||
String subString2 = StringUtils.substring(subString, j * 6, (j + 1) * 6);
|
||||
int charIndex = Integer.valueOf(subString2, BINARY) & NUMBER_61;
|
||||
sbBuilder.append(DIGITS[charIndex]);
|
||||
}
|
||||
String shortUrl = sbBuilder.toString();
|
||||
if (shortUrl != null) {
|
||||
return shortUrl;
|
||||
}
|
||||
}
|
||||
// if all 4 possibilities are already exists
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void print(Object messagr){
|
||||
System.out.println(messagr);
|
||||
}
|
||||
private static void print(Object messagr) {
|
||||
System.out.println(messagr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成 16 位随机字符串作为 ID
|
||||
* https://stackoverflow.com/questions/4267475/generating-8-character-only-uuids
|
||||
*/
|
||||
public static String generateShortId() {
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyMMdd");
|
||||
String randomStr = RandomStringUtils.randomAlphanumeric(10);
|
||||
return df.format(new Date()) + randomStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
*
|
||||
* @param targetStringLength
|
||||
* @return
|
||||
*/
|
||||
public static String generatingRandomAlphanumericString(final int targetStringLength) {
|
||||
int leftLimit = 48; // numeral '0'
|
||||
int rightLimit = 122; // letter 'z'
|
||||
Random random = new Random();
|
||||
|
||||
String generatedString = random.ints(leftLimit, rightLimit + 1)
|
||||
.filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97))
|
||||
.limit(targetStringLength)
|
||||
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
|
||||
.toString();
|
||||
|
||||
return generatedString;
|
||||
}
|
||||
}
|
@ -1,54 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
|
||||
* <https://www.chatopera.com>, Licensed under the Chunsong Public
|
||||
* 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,
|
||||
* 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,
|
||||
* Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0,
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package com.cskefu.cc.util;
|
||||
|
||||
import org.apache.commons.beanutils.converters.DateTimeConverter;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class DateConverter extends DateTimeConverter {
|
||||
|
||||
public DateConverter() {
|
||||
}
|
||||
|
||||
public DateConverter(Object defaultValue) {
|
||||
super(defaultValue);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.commons.beanutils.converters.AbstractConverter#getDefaultType()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected Class getDefaultType() {
|
||||
return Date.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.apache.commons.beanutils.converters.DateTimeConverter#convertToType(java.lang.Class, java.lang.Object)
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected Object convertToType(Class arg0, Object arg1) throws Exception {
|
||||
if (arg1 == null) {
|
||||
return null;
|
||||
}
|
||||
String value = arg1.toString().trim();
|
||||
if (value.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
return super.convertToType(arg0, arg1);
|
||||
}
|
||||
import java.util.Locale;
|
||||
|
||||
public class DateConverter extends DateTimeConverter {
|
||||
|
||||
final public static String ZONE_ID_DEFAULT = "Asia/Shanghai";
|
||||
// format date string like `Wed Aug 30 16:30:23 CST 2023` to Date
|
||||
public static SimpleDateFormat TIMEZONE_CHINA_FORMAT_DEFAULT = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US);
|
||||
|
||||
public DateConverter() {
|
||||
}
|
||||
|
||||
public DateConverter(Object defaultValue) {
|
||||
super(defaultValue);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.commons.beanutils.converters.AbstractConverter#getDefaultType()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected Class getDefaultType() {
|
||||
return Date.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.apache.commons.beanutils.converters.DateTimeConverter#convertToType(java.lang.Class, java.lang.Object)
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected Object convertToType(Class arg0, Object arg1) throws Exception {
|
||||
if (arg1 == null) {
|
||||
return null;
|
||||
}
|
||||
String value = arg1.toString().trim();
|
||||
if (value.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
return super.convertToType(arg0, arg1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Java将CST的时间字符串转换成需要的日期格式字符串
|
||||
* https://blog.csdn.net/qq_44868502/article/details/103511505
|
||||
* (new Date()).toString() 与 String to Date 的转化
|
||||
*
|
||||
* @param dstr
|
||||
* @return
|
||||
* @throws ParseException
|
||||
*/
|
||||
static public Date parseCSTAsChinaTimezone(final String dstr) throws ParseException {
|
||||
return (Date) TIMEZONE_CHINA_FORMAT_DEFAULT.parse(dstr);
|
||||
}
|
||||
}
|
@ -12,18 +12,34 @@ package com.cskefu.cc.util;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.chatopera.store.enums.LICSTATUS;
|
||||
import com.chatopera.store.exceptions.EnumValueException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
public class PugHelper {
|
||||
public String formatDate(String pattern, Date value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
public final static String NA = "N/A";
|
||||
|
||||
SimpleDateFormat format = new SimpleDateFormat(pattern);
|
||||
return format.format(value);
|
||||
|
||||
public String formatDate(String pattern, Date value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
return NA;
|
||||
}
|
||||
|
||||
SimpleDateFormat format = new SimpleDateFormat(pattern);
|
||||
String result = format.format(value);
|
||||
|
||||
if (StringUtils.isBlank(result)) {
|
||||
return NA;
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return NA;
|
||||
}
|
||||
|
||||
public String padRight(Object src, String ch) {
|
||||
@ -42,8 +58,39 @@ public class PugHelper {
|
||||
return new String(charr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在字符串中替换一些字符为 *, 起到混淆、加密、遮盖的敏感信息的目的
|
||||
*
|
||||
* @param prev
|
||||
* @return
|
||||
*/
|
||||
public String messupStringWithStars(final String prev) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
if (prev.length() >= 6) {
|
||||
sb.append("***");
|
||||
int initial = prev.length() - 4;
|
||||
for (int i = initial; i < prev.length(); i++) {
|
||||
sb.append(prev.charAt(i));
|
||||
}
|
||||
} else { // < 6
|
||||
if (prev.length() <= 2 && prev.length() > 0) {
|
||||
return "***";
|
||||
} else { // 2 < length < 6
|
||||
sb.append("***");
|
||||
int initial = prev.length() - 2;
|
||||
for (int i = initial; i < prev.length(); i++) {
|
||||
sb.append(prev.charAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 String 转化为 JSONArray
|
||||
*
|
||||
* @param str
|
||||
* @return
|
||||
*/
|
||||
@ -72,4 +119,59 @@ public class PugHelper {
|
||||
Collections.reverse(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得证书状态的中文
|
||||
*
|
||||
* @param status
|
||||
* @return
|
||||
*/
|
||||
public String getLicstatusInChinese(final String status) {
|
||||
try {
|
||||
LICSTATUS licstatus = LICSTATUS.toValue(status);
|
||||
switch (licstatus) {
|
||||
case NOTFOUND -> {
|
||||
return "未找到";
|
||||
}
|
||||
case EXHAUSTED -> {
|
||||
return "配额耗尽";
|
||||
}
|
||||
case INUSE -> {
|
||||
return "使用中";
|
||||
}
|
||||
case EXPIRED -> {
|
||||
return "已过期";
|
||||
}
|
||||
default -> {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
} catch (EnumValueException e) {
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取字符串,首先根据分隔符分隔,然后选取前 N 个,使用连接符连接返回
|
||||
*
|
||||
* @param orignal
|
||||
* @param splitBy
|
||||
* @param firstN
|
||||
* @param joinWith
|
||||
* @return
|
||||
*/
|
||||
public String splitStringAndJoinWith(final String orignal, final String splitBy, final int firstN, final String joinWith) {
|
||||
String[] splits = StringUtils.split(orignal, splitBy);
|
||||
int n = Math.min(splits.length, firstN);
|
||||
List<String> joined = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
joined.add(splits[i]);
|
||||
}
|
||||
|
||||
if (joined.size() > 0) {
|
||||
return StringUtils.join(joined, joinWith);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ spring.datasource.password=123456
|
||||
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
|
||||
spring.jpa.show-sql=false
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
spring.jpa.properties.hibernate.allow_update_outside_transaction=true
|
||||
|
||||
##############################################
|
||||
# Cache
|
||||
@ -152,13 +153,19 @@ cskefu.modules.cca=true
|
||||
cskefu.modules.entim=false
|
||||
cskefu.modules.report=true
|
||||
|
||||
##############################################
|
||||
# Channels
|
||||
##############################################
|
||||
channel.skype.crm=
|
||||
# https://gitlab.chatopera.com/chatopera/cosinee/issues/838
|
||||
cskefu.settings.webim.visitor-separate=false
|
||||
|
||||
##############################################
|
||||
# Skype Channel
|
||||
# License
|
||||
##############################################
|
||||
channel.skype.crm=
|
||||
# License Service Provider URL
|
||||
license.store.provider=https://store.chatopera.com
|
||||
|
||||
|
||||
##############################################
|
||||
# Telemetry
|
||||
@ -171,14 +178,4 @@ telemetry.channel.webim.visitor=on
|
||||
extras.login.banner=off
|
||||
extras.login.chatbox=off
|
||||
extras.auth.super-admin.pass=
|
||||
extras.log.request=off
|
||||
|
||||
spring.jpa.properties.hibernate.allow_update_outside_transaction=true
|
||||
|
||||
##############################################
|
||||
# ssl
|
||||
##############################################
|
||||
# server.ssl.key-store=classpath:cskefu.jks
|
||||
# server.ssl.key-alias=cskefu
|
||||
# server.ssl.key-store-password=123456
|
||||
# server.http2.enabled=true
|
||||
extras.log.request=off
|
@ -11,50 +11,145 @@
|
||||
* Licensed under the Apache License, Version 2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
function processUserAddOrUpdateResult(responsecode, cb){
|
||||
/**
|
||||
* 处理系统用户的创建的返回值
|
||||
* @param responsecode
|
||||
* @param cb
|
||||
*/
|
||||
function processUserAddOrUpdateResult(responsecode, cb) {
|
||||
switch (responsecode) {
|
||||
case 'username_exist':
|
||||
layer.msg('用户名存在,请重新填写',{icon: 2, time: 3000});
|
||||
layer.msg('用户名存在,请重新填写', {icon: 2, time: 3000});
|
||||
// 清空用户名
|
||||
$('input[name="username"]').val("");
|
||||
break;
|
||||
case 'email_exist':
|
||||
layer.msg('邮件存在,请重新填写',{icon: 2, time: 3000});
|
||||
layer.msg('邮件存在,请重新填写', {icon: 2, time: 3000});
|
||||
// 清空邮件
|
||||
$('input[name="email"]').val("");
|
||||
break;
|
||||
case 'mobile_exist':
|
||||
layer.msg('手机存在,请重新填写',{icon: 2, time: 3000});
|
||||
layer.msg('手机存在,请重新填写', {icon: 2, time: 3000});
|
||||
// 清空手机号
|
||||
$('input[name="mobile"]').val("");
|
||||
break;
|
||||
case 'sip_account_exist':
|
||||
layer.msg('SIP地址已经存在,请重新填写',{icon: 2, time: 3000});
|
||||
layer.msg('SIP地址已经存在,请重新填写', {icon: 2, time: 3000});
|
||||
// 清空SIP
|
||||
$('input[name="sipaccount"]').val("");
|
||||
break;
|
||||
case 'extension_binded':
|
||||
layer.msg('分机号已经被其他用户绑定',{icon: 2, time: 3000});
|
||||
layer.msg('分机号已经被其他用户绑定', {icon: 2, time: 3000});
|
||||
$('input[name="extensionid"]').val("");
|
||||
break;
|
||||
case 'extension_not_exist':
|
||||
layer.msg('绑定分机不存在',{icon: 2, time: 3000});
|
||||
layer.msg('绑定分机不存在', {icon: 2, time: 3000});
|
||||
$('input[name="extensionid"]').val("");
|
||||
break;
|
||||
case 'pbxhost_not_exist':
|
||||
layer.msg('指定的呼叫中心语音平台不存在',{icon: 2, time: 3000});
|
||||
layer.msg('指定的呼叫中心语音平台不存在', {icon: 2, time: 3000});
|
||||
$('input[name="pbxhostid"]').val("");
|
||||
break;
|
||||
case 't1':
|
||||
layer.msg('当前用户坐席就绪或对话未结束,不能切换为非坐席',{icon: 2, time: 3000});
|
||||
layer.msg('当前用户坐席就绪或对话未结束,不能切换为非坐席', {icon: 2, time: 3000});
|
||||
break;
|
||||
case 'new_user_success':
|
||||
layer.msg('新用户创建成功',{icon: 1, time: 1000});
|
||||
layer.msg('新用户创建成功', {icon: 1, time: 1000});
|
||||
cb();
|
||||
break;
|
||||
case 'edit_user_success':
|
||||
layer.msg('用户编辑成功',{icon: 1, time: 1000});
|
||||
layer.msg('用户编辑成功', {icon: 1, time: 1000});
|
||||
cb();
|
||||
break;
|
||||
default:
|
||||
handleGeneralCodeInQueryPathOrApiResp(responsecode, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理在 RedirectURL, API 中返回的 code 信息: status, msg, etc.
|
||||
* code 为约定的返回值,通过下面的函数进行展示
|
||||
* @param code
|
||||
* @param cb
|
||||
*/
|
||||
function handleGeneralCodeInQueryPathOrApiResp(code, cb) {
|
||||
switch (code) {
|
||||
case 'billingquotaexception.no_license_found':
|
||||
layer.confirm('证书不存在,联系系统超级管理员导入。', {
|
||||
title: '使用授权证书', icon: 2, btn: [
|
||||
'查看说明'
|
||||
],
|
||||
btn1: function (index, layero, that) {
|
||||
// 查看说明
|
||||
window.open("https://docs.cskefu.com/docs/licenses", "_blank");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb()
|
||||
}
|
||||
break;
|
||||
case 'billingquotaexception.response_unexpected':
|
||||
layer.msg('【使用授权证书】证书商店返回异常,稍后再试。', {icon: 2, time: 5000});
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb()
|
||||
}
|
||||
break;
|
||||
case 'billingquotaexception.invalid_request_body':
|
||||
layer.msg('【使用授权证书】请求证书商店参数不合法,请获取最新软件代码。', {icon: 2, time: 5000});
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb()
|
||||
}
|
||||
break;
|
||||
case 'billingquotaexception.license_invalid':
|
||||
layer.msg('【使用授权证书】证书商店中不存在该证书,请联系系统超级管理员导入。', {icon: 2, time: 5000});
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb()
|
||||
}
|
||||
break;
|
||||
case 'billingquotaexception.product_invalid':
|
||||
layer.msg('【使用授权证书】产品或产品款式不存在,请联系系统超级管理员导入新证书。', {icon: 2, time: 5000});
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb()
|
||||
}
|
||||
break;
|
||||
case 'billingquotaexception.license_expired_or_exhausted':
|
||||
layer.msg('【使用授权证书】证书过期或耗尽,请升级证书或绑定新证书。', {icon: 2, time: 5000});
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb()
|
||||
}
|
||||
break;
|
||||
case 'billingquotaexception.license_disabled_serverinst':
|
||||
layer.msg('【使用授权证书】证书商店禁用了本服务实例。', {icon: 2, time: 5000});
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb()
|
||||
}
|
||||
break;
|
||||
case 'billingquotaexception.license_unsupport_refund':
|
||||
layer.msg('【使用授权证书】目前使用的证书不支持回退配额。', {icon: 2, time: 5000});
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb()
|
||||
}
|
||||
break;
|
||||
case 'billingquotaexception.license_quota_inadequate':
|
||||
layer.msg('【使用授权证书】本次操作需要使用的配额资源超过证书中剩余配额,请联系系统超级管理员升级证书。', {
|
||||
icon: 2,
|
||||
time: 5000
|
||||
});
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb()
|
||||
}
|
||||
break;
|
||||
case 'billingquotaexception.internal_error':
|
||||
layer.msg('【使用授权证书】系统错误,稍后再试。', {icon: 2, time: 5000});
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb()
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log("[handleGeneralCodeInQueryPathOrApiResp] none code matched", code);
|
||||
if (cb && (typeof (x) === 'function')) {
|
||||
cb("no_code_matched");
|
||||
}
|
||||
}
|
||||
}
|
@ -11,196 +11,203 @@
|
||||
* Licensed under the Apache License, Version 2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
var title = "春松客服-开源客服系统" ;
|
||||
var socket , newuser = [] , newmessage = [] , ring = [];
|
||||
newuser['mp3'] = '/images/new.mp3';
|
||||
var title = "春松客服-开源客服系统";
|
||||
var socket, newuser = [], newmessage = [], ring = [];
|
||||
newuser['mp3'] = '/images/new.mp3';
|
||||
newmessage['mp3'] = '/images/message.mp3';
|
||||
ring['mp3'] = '/images/ring.mp3';
|
||||
$(document).ready(function(){
|
||||
var protocol = window.location.protocol.replace(/:/g,'');
|
||||
socket = io(protocol+'://'+hostname+':'+port+"/im/agent?userid="+userid+"&session="+session+"&admin="+adminuser , {transports: ['websocket'], upgrade: false});
|
||||
socket.on('connect',function() {
|
||||
console.log("[IM] 连接初始化成功");
|
||||
//请求服务端记录 当前用户在线事件
|
||||
}).on('disconnect',function() {
|
||||
console.log("[IM] 连接已断开");
|
||||
//请求服务端记录,当前用户离线
|
||||
$(document).ready(function () {
|
||||
var protocol = window.location.protocol.replace(/:/g, '');
|
||||
socket = io(protocol + '://' + hostname + ':' + port + "/im/agent?userid=" + userid + "&session=" + session + "&admin=" + adminuser, {
|
||||
transports: ['websocket'],
|
||||
upgrade: false
|
||||
});
|
||||
socket.on('connect', function () {
|
||||
console.log("[IM] 连接初始化成功");
|
||||
//请求服务端记录 当前用户在线事件
|
||||
}).on('disconnect', function () {
|
||||
console.log("[IM] 连接已断开");
|
||||
//请求服务端记录,当前用户离线
|
||||
});
|
||||
|
||||
socket.on('chatevent', function(data) {
|
||||
// console.log(data.messageType + " ..... message:"+data.message);
|
||||
}).on('task', function(data) {
|
||||
|
||||
}).on('new', function(data) {
|
||||
if($('#customerChatAudit').length > 0){
|
||||
if(customerChatAudit.$('#agentuser_' + data.userid).length > 0 && customerChatAudit.$("#chat_users li").length>1){
|
||||
customerChatAudit.$('#agentuser_' + data.userid).remove();
|
||||
customerChatAudit.$("#chat_users li:first-child a").click();
|
||||
}else{
|
||||
customerChatAudit.$('#ccaIndex').html("<div class=\"layui-layout layui-layout-content\" style=\"height: 100%;\">\n" +
|
||||
" <div class=\"box default-box\" style=\"height: 100%;\">\n" +
|
||||
" <div class=\"box-body ukefu-im-theme\">\n" +
|
||||
" <div class=\"ukefu-empty\" style=\"background: none\">\n" +
|
||||
" <i class=\"layui-icon\"></i>\n" +
|
||||
" <div style=\"\">还没有任何对话</div>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>");
|
||||
}
|
||||
}
|
||||
if($('#multiMediaDialogWin').length > 0 && multiMediaDialogWin != null && multiMediaDialogWin.$ &&multiMediaDialogWin.$('#agentusers').length > 0){
|
||||
multiMediaDialogWin.Proxy.newAgentUserService(data,"agent");
|
||||
}else{
|
||||
//来电弹屏
|
||||
$('#agentdesktop').attr('data-href' , '/agent/index.html?userid='+data.userid).click();
|
||||
WebIM.audioplayer('audioplane', newuser, false); // 播放
|
||||
}
|
||||
}).on('status', function(data) {
|
||||
$.post('/lazyAgentStatus').success(function(html){
|
||||
$('#agents_status').html(html);
|
||||
});
|
||||
// $('#agents_status').html("服务中的人数:"+data.users+"人,当前排队人数:"+data.inquene+"人,在线坐席数:"+data.agents+"人,坐席忙:"+data.busy+"人");
|
||||
}).on('message', function(data) {
|
||||
if($('#multiMediaDialogWin').length > 0 && multiMediaDialogWin != null && multiMediaDialogWin.$ && multiMediaDialogWin.$('#agentusers').length > 0){
|
||||
multiMediaDialogWin.Proxy.newAgentUserMessage(data,"agent");
|
||||
if(data.type == 'message'){
|
||||
WebIM.audioplayer('audioplane', newmessage, false); // 播放
|
||||
if(multiMediaDialogWin.isAisuggest && multiMediaDialogWin.isAisuggest == "true"){
|
||||
multiMediaDialogWin.Proxy.quickReply(data,"agent");
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//来电弹屏
|
||||
$('#agentdesktop').attr('data-href' , '/agent/index.html?userid='+data.userid).click();
|
||||
}
|
||||
}).on('workorder', function(data) {
|
||||
|
||||
}).on('transout', function(data){
|
||||
// TODO 坐席会话被转接出去
|
||||
if($('#multiMediaDialogWin').length > 0){
|
||||
if(multiMediaDialogWin.document.getElementById('agentusers') != null){
|
||||
multiMediaDialogWin.Proxy.transoutAgentUserService(data);
|
||||
}
|
||||
}
|
||||
layer.msg("您与"+data.username+"的会话已被转接给"+data.agentname,{time:1500})
|
||||
socket.on('chatevent', function (data) {
|
||||
// console.log(data.messageType + " ..... message:"+data.message);
|
||||
}).on('task', function (data) {
|
||||
}).on('new', function (data) {
|
||||
console.log("new data ...", data);
|
||||
if ($('#customerChatAudit').length > 0) {
|
||||
if (customerChatAudit.$('#agentuser_' + data.userid).length > 0 && customerChatAudit.$("#chat_users li").length > 1) {
|
||||
customerChatAudit.$('#agentuser_' + data.userid).remove();
|
||||
customerChatAudit.$("#chat_users li:first-child a").click();
|
||||
} else {
|
||||
customerChatAudit.$('#ccaIndex').html("<div class=\"layui-layout layui-layout-content\" style=\"height: 100%;\">\n" +
|
||||
" <div class=\"box default-box\" style=\"height: 100%;\">\n" +
|
||||
" <div class=\"box-body ukefu-im-theme\">\n" +
|
||||
" <div class=\"ukefu-empty\" style=\"background: none\">\n" +
|
||||
" <i class=\"layui-icon\"></i>\n" +
|
||||
" <div style=\"\">还没有任何对话</div>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>");
|
||||
}
|
||||
}
|
||||
|
||||
}).on('audit_message', function(data){
|
||||
// 会话监控:消息
|
||||
if($('#customerChatAudit').length > 0 && customerChatAudit != null && customerChatAudit.$ && customerChatAudit.$('#agentuserscca').length > 0){
|
||||
customerChatAudit.Proxy.newAgentUserMessage(data,"cca");
|
||||
if(data.type == 'message'){
|
||||
WebIM.audioplayer('audioplane', newmessage, false); // 播放
|
||||
if(customerChatAudit.isCcaAisuggest && customerChatAudit.isCcaAisuggest == "true"){
|
||||
customerChatAudit.Proxy.quickReply(data,"cca");
|
||||
}
|
||||
}
|
||||
}
|
||||
}).on('audit_new', function(data){
|
||||
// 会话监控:新建
|
||||
if(skills.indexOf(data.skill)>-1 && $('#customerChatAudit').length > 0 && customerChatAudit != null && customerChatAudit.$){
|
||||
customerChatAudit.Proxy.newAgentUserService(data,"cca");
|
||||
if(data.type == 'message'){
|
||||
WebIM.audioplayer('audioplane', newmessage, false); // 播放
|
||||
}
|
||||
}
|
||||
}).on('audit_end', function(data){
|
||||
// 会话监控:结束
|
||||
if($('#customerChatAudit').length > 0){
|
||||
if(customerChatAudit.document.getElementById('agentuserscca') != null){
|
||||
customerChatAudit.Proxy.endAgentUserService(data);
|
||||
}
|
||||
}
|
||||
}).on('end', function(data) {
|
||||
console.warn(111111, data)
|
||||
if($('#multiMediaDialogWin').length > 0){
|
||||
if(multiMediaDialogWin.document.getElementById('agentusers') != null){
|
||||
multiMediaDialogWin.Proxy.endAgentUserService(data);
|
||||
}
|
||||
}else{
|
||||
//来电弹屏
|
||||
$('#agentdesktop').attr('data-href', '/agent/index.html?userid='+data.userid).click();
|
||||
}
|
||||
}).on('leave', function(data){
|
||||
top.layer.msg('当前会话已经过期,稍后将自动登出!',{icon: 1, time: 2000});
|
||||
setTimeout(function(){
|
||||
// 执行登出
|
||||
window.location.href = "/logout.html?code=2";
|
||||
}, 2000);
|
||||
});
|
||||
/****每分钟执行一次,与服务器交互,保持会话****/
|
||||
setInterval(function(){
|
||||
WebIM.ping();
|
||||
} , 60000);
|
||||
if ($('#multiMediaDialogWin').length > 0 &&
|
||||
multiMediaDialogWin != null &&
|
||||
multiMediaDialogWin.$ &&
|
||||
multiMediaDialogWin.$('#agentusers').length > 0) {
|
||||
multiMediaDialogWin.Proxy.newAgentUserService(data, "agent");
|
||||
} else {
|
||||
//来电弹屏
|
||||
$('#agentdesktop').attr('data-href', '/agent/index.html?userid=' + data.userid + '&licenseVerifiedPass=' + data.licenseVerifiedPass + '&licenseBillingMsg=' + data.licenseBillingMsg).click();
|
||||
WebIM.audioplayer('audioplane', newuser, false); // 播放
|
||||
}
|
||||
}).on('status', function (data) {
|
||||
$.post('/lazyAgentStatus').success(function (html) {
|
||||
$('#agents_status').html(html);
|
||||
});
|
||||
// $('#agents_status').html("服务中的人数:"+data.users+"人,当前排队人数:"+data.inquene+"人,在线坐席数:"+data.agents+"人,坐席忙:"+data.busy+"人");
|
||||
}).on('message', function (data) {
|
||||
if ($('#multiMediaDialogWin').length > 0 && multiMediaDialogWin != null && multiMediaDialogWin.$ && multiMediaDialogWin.$('#agentusers').length > 0) {
|
||||
multiMediaDialogWin.Proxy.newAgentUserMessage(data, "agent");
|
||||
if (data.type == 'message') {
|
||||
WebIM.audioplayer('audioplane', newmessage, false); // 播放
|
||||
if (multiMediaDialogWin.isAisuggest && multiMediaDialogWin.isAisuggest == "true") {
|
||||
multiMediaDialogWin.Proxy.quickReply(data, "agent");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//来电弹屏
|
||||
$('#agentdesktop').attr('data-href', '/agent/index.html?userid=' + data.userid).click();
|
||||
}
|
||||
}).on('workorder', function (data) {
|
||||
|
||||
}).on('transout', function (data) {
|
||||
// TODO 坐席会话被转接出去
|
||||
if ($('#multiMediaDialogWin').length > 0) {
|
||||
if (multiMediaDialogWin.document.getElementById('agentusers') != null) {
|
||||
multiMediaDialogWin.Proxy.transoutAgentUserService(data);
|
||||
}
|
||||
}
|
||||
layer.msg("您与" + data.username + "的会话已被转接给" + data.agentname, {time: 1500})
|
||||
|
||||
}).on('audit_message', function (data) {
|
||||
// 会话监控:消息
|
||||
if ($('#customerChatAudit').length > 0 && customerChatAudit != null && customerChatAudit.$ && customerChatAudit.$('#agentuserscca').length > 0) {
|
||||
customerChatAudit.Proxy.newAgentUserMessage(data, "cca");
|
||||
if (data.type == 'message') {
|
||||
WebIM.audioplayer('audioplane', newmessage, false); // 播放
|
||||
if (customerChatAudit.isCcaAisuggest && customerChatAudit.isCcaAisuggest == "true") {
|
||||
customerChatAudit.Proxy.quickReply(data, "cca");
|
||||
}
|
||||
}
|
||||
}
|
||||
}).on('audit_new', function (data) {
|
||||
// 会话监控:新建
|
||||
if (skills.indexOf(data.skill) > -1 && $('#customerChatAudit').length > 0 && customerChatAudit != null && customerChatAudit.$) {
|
||||
customerChatAudit.Proxy.newAgentUserService(data, "cca");
|
||||
if (data.type == 'message') {
|
||||
WebIM.audioplayer('audioplane', newmessage, false); // 播放
|
||||
}
|
||||
}
|
||||
}).on('audit_end', function (data) {
|
||||
// 会话监控:结束
|
||||
if ($('#customerChatAudit').length > 0) {
|
||||
if (customerChatAudit.document.getElementById('agentuserscca') != null) {
|
||||
customerChatAudit.Proxy.endAgentUserService(data);
|
||||
}
|
||||
}
|
||||
}).on('end', function (data) {
|
||||
console.warn(111111, data)
|
||||
if ($('#multiMediaDialogWin').length > 0) {
|
||||
if (multiMediaDialogWin.document.getElementById('agentusers') != null) {
|
||||
multiMediaDialogWin.Proxy.endAgentUserService(data);
|
||||
}
|
||||
} else {
|
||||
//来电弹屏
|
||||
$('#agentdesktop').attr('data-href', '/agent/index.html?userid=' + data.userid).click();
|
||||
}
|
||||
}).on('leave', function (data) {
|
||||
top.layer.msg('当前会话已经过期,稍后将自动登出!', {icon: 1, time: 2000});
|
||||
setTimeout(function () {
|
||||
// 执行登出
|
||||
window.location.href = "/logout.html?code=2";
|
||||
}, 2000);
|
||||
});
|
||||
/****每分钟执行一次,与服务器交互,保持会话****/
|
||||
setInterval(function () {
|
||||
WebIM.ping();
|
||||
}, 60000);
|
||||
});
|
||||
|
||||
var WebIM = {
|
||||
sendMessage:function(message , userid , appid , session , touser , agentstatus, agentuserid){
|
||||
WebIM.sendTypeMessage(message, userid, appid, session, touser, agentstatus, null , null,agentuserid) ;
|
||||
},
|
||||
sendTypeMessage:function(message , userid , appid , session , touser , agentstatus , msgtype , attachmentid,agentuserid){
|
||||
socket.emit('message', {
|
||||
appid : appid ,
|
||||
userid:userid,
|
||||
sign:session,
|
||||
touser:touser,
|
||||
session: session,
|
||||
username:agentstatus,
|
||||
nickname:agentstatus,
|
||||
message : message,
|
||||
msgtype:msgtype,
|
||||
attachmentid:attachmentid,
|
||||
agentuser:agentuserid
|
||||
sendMessage: function (message, userid, appid, session, touser, agentstatus, agentuserid) {
|
||||
WebIM.sendTypeMessage(message, userid, appid, session, touser, agentstatus, null, null, agentuserid);
|
||||
},
|
||||
sendTypeMessage: function (message, userid, appid, session, touser, agentstatus, msgtype, attachmentid, agentuserid) {
|
||||
socket.emit('message', {
|
||||
appid: appid,
|
||||
userid: userid,
|
||||
sign: session,
|
||||
touser: touser,
|
||||
session: session,
|
||||
username: agentstatus,
|
||||
nickname: agentstatus,
|
||||
message: message,
|
||||
msgtype: msgtype,
|
||||
attachmentid: attachmentid,
|
||||
agentuser: agentuserid
|
||||
});
|
||||
},
|
||||
// 发送会话监控干预消息
|
||||
sendIntervention: function(supervisorid, agentuserid, session, msgtype, content, extra){
|
||||
socket.emit('intervention', {
|
||||
supervisorid: supervisorid, // 坐席监控人员ID,必须
|
||||
agentuserid: agentuserid, // 坐席访客会话ID,必须
|
||||
msgtype: msgtype, // 干预消息类型, 必须
|
||||
content: content, // 干预消息内容, 必须
|
||||
session: session, // 登录会话ID
|
||||
extra: extra // 其它可选属性, 可选
|
||||
});
|
||||
},
|
||||
ping : function(){
|
||||
loadURL("/message/ping.html");
|
||||
console.log("[IM] heartbeat:" + new Date().getTime());
|
||||
},
|
||||
audioplayer:function(id, file, loop) {
|
||||
var audioplayer = document.getElementById(id);
|
||||
if (audioplayer != null) {
|
||||
document.body.removeChild(audioplayer);
|
||||
}
|
||||
},
|
||||
// 发送会话监控干预消息
|
||||
sendIntervention: function (supervisorid, agentuserid, session, msgtype, content, extra) {
|
||||
socket.emit('intervention', {
|
||||
supervisorid: supervisorid, // 坐席监控人员ID,必须
|
||||
agentuserid: agentuserid, // 坐席访客会话ID,必须
|
||||
msgtype: msgtype, // 干预消息类型, 必须
|
||||
content: content, // 干预消息内容, 必须
|
||||
session: session, // 登录会话ID
|
||||
extra: extra // 其它可选属性, 可选
|
||||
});
|
||||
},
|
||||
ping: function () {
|
||||
loadURL("/message/ping.html");
|
||||
console.log("[IM] heartbeat:" + new Date().getTime());
|
||||
},
|
||||
audioplayer: function (id, file, loop) {
|
||||
var audioplayer = document.getElementById(id);
|
||||
if (audioplayer != null) {
|
||||
document.body.removeChild(audioplayer);
|
||||
}
|
||||
|
||||
if (typeof(file) != 'undefined') {
|
||||
if (navigator.userAgent.indexOf("MSIE") > 0) { // IE
|
||||
var player = document.createElement('bgsound');
|
||||
player.id = id;
|
||||
player.src = file['mp3'];
|
||||
player.setAttribute('autostart', 'true');
|
||||
if (loop) {
|
||||
player.setAttribute('loop', 'infinite');
|
||||
}
|
||||
document.body.appendChild(player);
|
||||
if (typeof (file) != 'undefined') {
|
||||
if (navigator.userAgent.indexOf("MSIE") > 0) { // IE
|
||||
var player = document.createElement('bgsound');
|
||||
player.id = id;
|
||||
player.src = file['mp3'];
|
||||
player.setAttribute('autostart', 'true');
|
||||
if (loop) {
|
||||
player.setAttribute('loop', 'infinite');
|
||||
}
|
||||
document.body.appendChild(player);
|
||||
|
||||
} else { // Other FF Chome Safari Opera
|
||||
var player = document.createElement('audio');
|
||||
player.id = id;
|
||||
player.setAttribute('autoplay', 'autoplay');
|
||||
if (loop) {
|
||||
player.setAttribute('loop', 'loop');
|
||||
}
|
||||
document.body.appendChild(player);
|
||||
} else { // Other FF Chome Safari Opera
|
||||
var player = document.createElement('audio');
|
||||
player.id = id;
|
||||
player.setAttribute('autoplay', 'autoplay');
|
||||
if (loop) {
|
||||
player.setAttribute('loop', 'loop');
|
||||
}
|
||||
document.body.appendChild(player);
|
||||
|
||||
var mp3 = document.createElement('source');
|
||||
mp3.src = file['mp3'];
|
||||
mp3.type = 'audio/mpeg';
|
||||
player.appendChild(mp3);
|
||||
}
|
||||
}
|
||||
}
|
||||
var mp3 = document.createElement('source');
|
||||
mp3.src = file['mp3'];
|
||||
mp3.type = 'audio/mpeg';
|
||||
player.appendChild(mp3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -343,22 +343,23 @@ function newMessageScorllBottom(type, msgType) {
|
||||
|
||||
var Proxy = {
|
||||
newAgentUserService: function (data, type) {
|
||||
console.log("newAgentUserService data type", data, type)
|
||||
if ($('#tip_message_' + data.userid).length > 0) {
|
||||
var channel = data.channeltype
|
||||
if (channel) {
|
||||
if (channel === 'phone') {
|
||||
$('#tip_icon_phone_' + data.userid).attr("src", "/images/phone-ico.png");
|
||||
} else {
|
||||
$('#tip_icon_' +channel+ '_' + data.userid).removeClass('ukefu-channel-icon-end').addClass("ukefu-channel-icon");
|
||||
$('#tip_icon_' + channel + '_' + data.userid).removeClass('ukefu-channel-icon-end').addClass("ukefu-channel-icon");
|
||||
}
|
||||
}
|
||||
$('#tip_message_' + data.userid).removeClass('bg-gray').addClass("bg-green").text('在线');
|
||||
} else {
|
||||
if ($('.chat-list-item.active').length > 0) {
|
||||
var id = $('.chat-list-item.active').data('id');
|
||||
type == "agent" ? loadURL('/agent/agentusers.html?newuser=true&userid=' + id, '#agentusers') : loadURL('/apps/cca/agentusers.html?newuser=true&userid=' + id, '#agentuserscca');
|
||||
type == "agent" ? loadURL('/agent/agentusers.html?newuser=true&userid=' + id + '&licenseVerifiedPass=' + data.licenseVerifiedPass + '&licenseBillingMsg=' + data.licenseBillingMsg, '#agentusers') : loadURL('/apps/cca/agentusers.html?newuser=true&userid=' + id + "&licenseVerifiedPass=" + data.licenseVerifiedPass + "&licenseBillingMsg=" + data.licenseBillingMsg, '#agentuserscca');
|
||||
} else {
|
||||
type == "agent" ? location.href = "/agent/index.html?newuser=true" : location.href = "/apps/cca/index.html?newuser=true";
|
||||
type == "agent" ? location.href = "/agent/index.html?newuser=true&licenseVerifiedPass=" + data.licenseVerifiedPass + "&licenseBillingMsg=" + data.licenseBillingMsg : location.href = "/apps/cca/index.html?newuser=true&licenseVerifiedPass=" + data.licenseVerifiedPass + "&licenseBillingMsg=" + data.licenseBillingMsg;
|
||||
}
|
||||
}
|
||||
if (data.userid == cursession) {
|
||||
@ -485,7 +486,7 @@ var Proxy = {
|
||||
if (channel === 'phone') {
|
||||
$('#tip_icon_phone_' + data.userid).attr("src", "/images/cde-ico-gray.png");
|
||||
} else {
|
||||
$('#tip_icon_'+ channel +'_' + data.userid).removeClass("ukefu-channel-icon").addClass('ukefu-channel-icon-end');
|
||||
$('#tip_icon_' + channel + '_' + data.userid).removeClass("ukefu-channel-icon").addClass('ukefu-channel-icon-end');
|
||||
}
|
||||
}
|
||||
$('#tip_message_' + data.userid).removeClass("bg-green").addClass('bg-gray').text('离开');
|
||||
@ -504,7 +505,11 @@ var Proxy = {
|
||||
}
|
||||
},
|
||||
tipMsgForm: function (href) {
|
||||
top.layer.prompt({formType: 2, title: '请输入拉黑原因', area: ['300px', '50px']}, function (value, index, elem) {
|
||||
top.layer.prompt({
|
||||
formType: 2,
|
||||
title: '请输入拉黑原因',
|
||||
area: ['300px', '50px']
|
||||
}, function (value, index, elem) {
|
||||
location.href = href + "&description=" + encodeURIComponent(value);
|
||||
top.layer.close(index);
|
||||
});
|
||||
|
@ -312,6 +312,21 @@ var arrayListPrototype = {
|
||||
|
||||
ArrayList.prototype = arrayListPrototype;
|
||||
|
||||
/**
|
||||
* 复制值到系统粘贴板
|
||||
* https://www.freecodecamp.org/news/copy-text-to-clipboard-javascript/
|
||||
* @param val
|
||||
*/
|
||||
function copyValue2ClipboardOnOS(val, cb) {
|
||||
navigator.clipboard.writeText(val).then(() => {
|
||||
/* Resolved - text copied to clipboard successfully */
|
||||
if(cb && typeof cb === 'function') cb();
|
||||
},(err) => {
|
||||
/* Rejected - text failed to copy to the clipboard */
|
||||
if(cb && typeof cb === 'function') cb(err || "Fail");
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
Math.uuid.js (v1.4)
|
||||
|
||||
|
@ -63,13 +63,13 @@ block content
|
||||
|
||||
layui.use('layer', function () {
|
||||
var layer = layui.layer;
|
||||
console.log(window.location.href)
|
||||
var status = '#{status}';
|
||||
if (status == 'new_webim_success')
|
||||
layer.msg('网站添加成功', {icon: 1, time: 1000})
|
||||
else if (status == 'new_webim_fail')
|
||||
layer.msg('网站添加失败', {icon: 2, time: 3000})
|
||||
|
||||
else
|
||||
handleGeneralCodeInQueryPathOrApiResp(status);
|
||||
});
|
||||
layui.use(['laypage', 'layer'], function () {
|
||||
var laypage = layui.laypage
|
||||
|
@ -26,12 +26,14 @@ html(xmlns='http://www.w3.org/1999/xhtml', xmlns:th='http://www.thymeleaf.org',
|
||||
link(rel='stylesheet', href='/css/layui.css')
|
||||
link(rel='stylesheet', href='/res/css.html')
|
||||
link(rel='stylesheet', href='/css/flexboxgrid.min.css')
|
||||
script(src='/js/utils.js')
|
||||
script(src='/js/jquery-1.10.2.min.js')
|
||||
script(src='/js/jquery.form.js')
|
||||
script(src='/js/ztree/jquery.ztree.all.min.js')
|
||||
script(src='/js/echarts.common.min.js')
|
||||
script(language='javascript', src='/js/theme/wonderland.js')
|
||||
script(src='/layui.js')
|
||||
script(src="/js/CSKeFu_Admin.v1.js")
|
||||
script(src='/js/cskefu.js')
|
||||
if userExpTelemetry == 'on'
|
||||
script(src='https://www.googletagmanager.com/gtag/js?id=G-SBBX10RKTC' async)
|
||||
|
@ -51,6 +51,13 @@ ul.layui-nav.layui-nav-tree(lay-filter='demo')
|
||||
dd(class={'layui-this': subtype == 'interf'})
|
||||
a(href='/admin/weixin/interf.html') 接口管理
|
||||
| –>
|
||||
if user.superadmin
|
||||
li.layui-nav-item.layui-nav-itemed
|
||||
a.layui-nav-title(href='javascript:;') 人工智能
|
||||
dl.layui-nav-child
|
||||
if models.contains("chatbot") && (user.roleAuthMap["A09"] || user.admin)
|
||||
dd
|
||||
a(href='javascript:void(0)',data-title="智能机器人",onclick="openChatbot()",data-href="/admin/system/chatbot/index.html",class="iframe_btn",data-id="chatbotIntegrationWin", data-type="tabAdd") 智能机器人
|
||||
if user.superadmin
|
||||
li.layui-nav-item.layui-nav-itemed
|
||||
a.layui-nav-title(href='javascript:;') 系统设置
|
||||
@ -61,16 +68,20 @@ ul.layui-nav.layui-nav-tree(lay-filter='demo')
|
||||
a(href='/admin/sysdic/index.html') 字典管理
|
||||
dd(class={'layui-this': subtype == 'metadata'})
|
||||
a(href='/admin/metadata/index.html') 元数据
|
||||
if models.contains("chatbot") && (user.roleAuthMap["A09"] || user.admin)
|
||||
dd
|
||||
a(href='javascript:void(0)',data-title="智能机器人",onclick="openChatbot()",data-href="/admin/system/chatbot/index.html",class="iframe_btn",data-id="chatbotIntegrationWin", data-type="tabAdd") 智能机器人
|
||||
dd(class={'layui-this': subtype == 'template'})
|
||||
a(href='/admin/template/index.html') 系统模板
|
||||
dd(class={'layui-this': subtype == 'email'})
|
||||
a(href='/admin/email/index.html') 邮件通知设置
|
||||
dd(class={'layui-this': subtype == 'sms'})
|
||||
a(href='/admin/sms/index.html') 短信通知设置
|
||||
|
||||
//dd(class={'layui-this': subtype == 'email'})
|
||||
// a(href='/admin/email/index.html') 邮件通知设置
|
||||
//dd(class={'layui-this': subtype == 'sms'})
|
||||
// a(href='/admin/sms/index.html') 短信通知设置
|
||||
if user.superadmin
|
||||
li.layui-nav-item.layui-nav-itemed
|
||||
a.layui-nav-title(href='javascript:;') 使用授权
|
||||
dl.layui-nav-child
|
||||
dd(class={'layui-this': subtype == 'licenseInst'})
|
||||
a(href='/admin/license/instance.html') 实例信息
|
||||
dd(class={'layui-this': subtype == 'licenseList'})
|
||||
a(href='/admin/license/index.html') 授权证书列表
|
||||
script.
|
||||
function openChatbot() {
|
||||
window.parent.active.tabAdd($(".iframe_btn").data('href'), $(".iframe_btn").data('title'), $(".iframe_btn").data('id'));
|
||||
|
@ -0,0 +1,31 @@
|
||||
//- 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.
|
||||
.uk-layui-form
|
||||
form.layui-form(action='/admin/license/save.html', method='post')
|
||||
.layui-form-item(style='margin-top:10px;')
|
||||
.layui-inline
|
||||
label.layui-form-label(style='width:150px;') 证书标识:
|
||||
.layui-input-inline
|
||||
input.layui-input(type='text', name='licenseShortId', required, lay-verify='required', autocomplete='off')
|
||||
.layui-form-mid.layui-word-aux
|
||||
font(color='red') *
|
||||
.layui-form-button
|
||||
.layui-button-block
|
||||
button.layui-btn(lay-submit, lay-filter='formNewLicense') 立即提交
|
||||
button.layui-btn.layui-btn-original(type='reset') 重置
|
||||
|
||||
script.
|
||||
layui.use('form', function () {
|
||||
var form = layui.form();
|
||||
form.render(); //更新全部
|
||||
});
|
||||
layui.use('element', function () {
|
||||
var element = layui.element();
|
||||
});
|
||||
|
@ -0,0 +1,100 @@
|
||||
//- 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.
|
||||
extends /admin/include/layout.pug
|
||||
block content
|
||||
.row: .col-lg-12
|
||||
h1.site-h1(style='background-color:#FFFFFF;')
|
||||
| 使用授权证书列表 (#{licenses.size()})
|
||||
| ,更新时间 #{pugHelper.formatDate('yyyy-MM-dd HH:mm:ss', updateTime)}
|
||||
span(style='float:right;')
|
||||
.layui-btn-group.ukefu-btn-group
|
||||
button.layui-btn.layui-btn-small(href='/admin/license/add.html', data-toggle='ajax', data-width='550', data-height='450', data-title='添加使用授权证书')
|
||||
i.layui-icon 
|
||||
| 导入
|
||||
button.layui-btn.layui-btn-small(onclick='location.reload()')
|
||||
span 刷新
|
||||
button.layui-btn.layui-btn-warm.layui-btn-small(onclick='openLicenseStorePage()')
|
||||
span 购买使用授权证书
|
||||
|
||||
.row(style='padding:5px;')
|
||||
blockquote.layui-elem-quote.layui-quote-nm
|
||||
i.layui-icon(style="color:gray") 
|
||||
font(color="#999").layui-word-aux 春松客服使用授权证书是通过 Chatopera 证书商店(https://store.chatopera.com)分发的对【春松客服计费资源】进行管理的凭证,在使用春松客服的过程中,春松客服与 Chatopera 证书商店集成,完成证书购买、证书绑定、配额扣除、配额同步和开具发票等。
|
||||
|
||||
.col-lg-12
|
||||
table.layui-table(lay-skin='line')
|
||||
colgroup
|
||||
col(width='10%')
|
||||
col(width='10%')
|
||||
col(width='10%')
|
||||
col(width='20%')
|
||||
col(width='10%')
|
||||
col(width='10%')
|
||||
col(width='10%')
|
||||
col(width='10%')
|
||||
col(width='10%')
|
||||
thead
|
||||
tr
|
||||
th 证书 ID
|
||||
th 状态
|
||||
th 产品标识
|
||||
th 产品名称
|
||||
th 有效期截止
|
||||
th 配额剩余
|
||||
th 所属人昵称
|
||||
th 添加时间
|
||||
th(style='white-space:nowrap;', nowrap) 操作
|
||||
tbody
|
||||
for item in licenses
|
||||
tr
|
||||
- var messupLicenseShortId = pugHelper.messupStringWithStars(item.license.shortId)
|
||||
td= messupLicenseShortId
|
||||
td= pugHelper.getLicstatusInChinese(item.license.status)
|
||||
td= item.product.shortId
|
||||
td= item.product.name
|
||||
td= pugHelper.splitStringAndJoinWith(item.license.effectivedateend, " ", 1, "")
|
||||
td= item.license.quotaeffectiveremaining
|
||||
td= item.user.nickname
|
||||
td= pugHelper.formatDate('yyyy-MM-dd', item.addDate)
|
||||
td(style="white-space:nowrap;" nowrap="nowrap")
|
||||
a(href="#", onclick="copyLicenseId2ClipboardOnOS('" + item.license.shortId + "');return false;")
|
||||
i.layui-icon 
|
||||
span 复制 ID
|
||||
a(href="/admin/license/delete/" + item.license.shortId + ".html" style="margin-left:10px;" data-toggle="tip" title="请确认是否删除使用授权证书 " + messupLicenseShortId + "?")
|
||||
i.layui-icon(style="color:red;") ဆ
|
||||
span 删除
|
||||
.row(style='padding:5px;')
|
||||
.col-lg-12#page(style='text-align:center;')
|
||||
|
||||
script.
|
||||
var msg = '#{msg}';
|
||||
if (msg == 'already_added')
|
||||
top.layer.alert('已经添加,不需要再次执行。', {icon: 1});
|
||||
else if (msg == 'product_added_already')
|
||||
top.layer.alert('同产品证书已经添加,不支持继续添加。', {icon: 2});
|
||||
else if (msg == 'invalid_id')
|
||||
top.layer.alert('不合法的证书标识', {icon: 2});
|
||||
else if (msg == 'notfound_id')
|
||||
top.layer.alert('不存在该证书信息', {icon: 2});
|
||||
|
||||
function copyLicenseId2ClipboardOnOS(val){
|
||||
copyValue2ClipboardOnOS(val, (err) => {
|
||||
top.layer.msg('复制完成', {icon: 1, time: 2000, offset: 't'});
|
||||
})
|
||||
}
|
||||
|
||||
function openLicenseStorePage() {
|
||||
var licenseStoreProvider = "#{licenseStoreProvider}/product/cskefu001";
|
||||
window.open(licenseStoreProvider, "_blank");
|
||||
}
|
||||
|
||||
layui.use(['laypage', 'layer'], function () {
|
||||
var laypage = layui.laypage
|
||||
, layer = layui.layer;
|
||||
});
|
@ -0,0 +1,59 @@
|
||||
//- 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.
|
||||
extends /admin/include/layout.pug
|
||||
block content
|
||||
.row: .col-lg-12
|
||||
h1.site-h1(style='background-color:#FFFFFF;')
|
||||
| 实例信息
|
||||
span(style='float:right;')
|
||||
.layui-btn-group.ukefu-btn-group
|
||||
button.layui-btn.layui-btn-warm.layui-btn-small(onclick='openLicenseStorePage()')
|
||||
span 购买使用授权证书
|
||||
|
||||
.row(style='padding:5px;')
|
||||
blockquote.layui-elem-quote.layui-quote-nm
|
||||
i.layui-icon(style="color:gray") 
|
||||
font(color="#999").layui-word-aux 春松客服实例信息,代表该春松客服软件运行的标识信息。在春松客服使用授权中,每个实例为一个可以绑定授权证书的单位。
|
||||
|
||||
.col-lg-12
|
||||
fieldset.layui-elem-field
|
||||
legend(style='font-size:13px!important;') 实例 ID
|
||||
span.layui-field-box #{SERVERINSTID}
|
||||
span
|
||||
button(style='font-size:10px!important;' onclick='copyServerinstId2ClipboardOnOS("' +SERVERINSTID + '")')
|
||||
span 复制
|
||||
|
||||
fieldset.layui-elem-field
|
||||
legend(style='font-size:13px!important;') 实例名称
|
||||
span.layui-field-box #{SERVICENAME}
|
||||
span
|
||||
button(style='font-size:10px!important;' onclick='copyServicename2ClipboardOnOS("' +SERVICENAME + '")')
|
||||
span 复制
|
||||
script.
|
||||
layui.use(['laypage', 'layer'], function () {
|
||||
var laypage = layui.laypage
|
||||
, layer = layui.layer;
|
||||
});
|
||||
|
||||
function copyServerinstId2ClipboardOnOS(val){
|
||||
copyValue2ClipboardOnOS(val, (err) => {
|
||||
top.layer.msg('复制完成', {icon: 1, time: 2000, offset: 't'});
|
||||
})
|
||||
}
|
||||
|
||||
function copyServicename2ClipboardOnOS(val){
|
||||
copyValue2ClipboardOnOS(val, (err) => {
|
||||
top.layer.msg('复制完成', {icon: 1, time: 2000, offset: 't'});
|
||||
})
|
||||
}
|
||||
|
||||
function openLicenseStorePage() {
|
||||
var licenseStoreProvider = "#{licenseStoreProvider}/product/cskefu001";
|
||||
window.open(licenseStoreProvider, "_blank");
|
||||
}
|
@ -189,6 +189,8 @@ block content
|
||||
layer.msg('修改无法完成,上级机构选择错误', {icon: 2, time: 2000})
|
||||
} else if (msg == 'not_allow_remove_user') {
|
||||
layer.msg('用户只有一个组织,不允许移除', {icon: 2, time: 2000})
|
||||
} else {
|
||||
handleGeneralCodeInQueryPathOrApiResp(msg);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -121,6 +121,8 @@ block content
|
||||
layer.msg('新用户创建成功',{icon: 1, time: 1000})
|
||||
else if (msg == 'edit_user_success')
|
||||
layer.msg('用户编辑成功', {icon: 1, time: 1000})
|
||||
else if (msg == 'billingquotaexception.no_license_found')
|
||||
layer.msg('证书不存在,联系系统超级管理员导入授权使用证书',{icon: 2, time: 3000})
|
||||
});
|
||||
layui.use(['laypage', 'layer'], function(){
|
||||
var laypage = layui.laypage
|
||||
|
@ -80,3 +80,11 @@
|
||||
.last-msg
|
||||
small.ukefu-badge.bg-red(id="last_msg_" + agentuser.userid,style="#{(agentuser.tokenum == 0 || (curagentuser && curagentuser.id == agentuser.id)) ? 'display:none' : ''}")
|
||||
| #{agentuser.tokenum ? agentuser.tokenum : 0}
|
||||
script(language="javascript").
|
||||
$(document).ready(function () {
|
||||
var licenseVerifiedPass = #{licenseVerifiedPass};
|
||||
var licenseBillingMsg = '#{licenseBillingMsg}';
|
||||
if (licenseBillingMsg) {
|
||||
handleGeneralCodeInQueryPathOrApiResp(licenseBillingMsg);
|
||||
}
|
||||
});
|
@ -24,7 +24,6 @@ html
|
||||
h1.site-h1(style='background-color:#FFFFFF;') 新建联系人
|
||||
form.layui-form(action='/agent/calloutcontact/save.html?agentuser=${curagentuser.id!\'\'}', method='post')
|
||||
input(hidden, name='calloutcontact')
|
||||
input(type='hidden', name='shares', value='all')
|
||||
.layui-collapse
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
|
@ -23,7 +23,6 @@ html
|
||||
h1.site-h1(style='background-color:#FFFFFF;') 编辑联系人
|
||||
form.layui-form(action='/agent/calloutcontact/update.html', method='post')
|
||||
input(type='hidden', name='id', value='${contacts.id!\'\'}')
|
||||
input(type='hidden', name='shares', value='all')
|
||||
.layui-collapse
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
|
@ -23,7 +23,6 @@ html
|
||||
h1.site-h1(style='background-color:#FFFFFF;') 新建联系人
|
||||
form.layui-form(action='/agent/calloutcontact/save.html?agentuser=${curagentuser.id!\'\'}', method='post')
|
||||
input(hidden, name='calloutcontact')
|
||||
input(type='hidden', name='shares', value='all')
|
||||
.layui-collapse
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
|
@ -23,7 +23,6 @@ html
|
||||
h1.site-h1(style='background-color:#FFFFFF;') 编辑联系人
|
||||
form.layui-form(action='/agent/calloutcontact/update.html', method='post')
|
||||
input(type='hidden', name='id', value='${contacts.id!\'\'}')
|
||||
input(type='hidden', name='shares', value='all')
|
||||
.layui-collapse
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
|
@ -13,7 +13,6 @@ include /mixins/dic.mixin.pug
|
||||
|
||||
.uk-layui-form
|
||||
form.layui-form(action='/apps/contacts/save.html', method='post')
|
||||
input(type='hidden', name='shares', value='all')
|
||||
.layui-collapse
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
@ -79,11 +78,6 @@ include /mixins/dic.mixin.pug
|
||||
label.layui-form-label 电子邮件:
|
||||
.layui-input-inline(style='margin-left:5px;')
|
||||
input.layui-input(type='text', name='email', lay-verify='entemail', autocomplete='off')
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label#contacts_skypeid(style='widht:80px;') Skype ID:
|
||||
.layui-input-inline
|
||||
input#skypeid.layui-input(type='text', name='skypeid', lay-verify='skypeid', autocomplete='off')
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label 联系人地址:
|
||||
|
@ -21,7 +21,6 @@ include /mixins/dic.mixin.pug
|
||||
input(type='hidden', name='wlcompany_name', value=contacts.wlcompany_name)
|
||||
input(type='hidden', name='wlsid', value=contacts.wlsid)
|
||||
input(type='hidden', name='wlsystem_name', value=contacts.wlsystem_name)
|
||||
input(type='hidden', name='shares', value='all')
|
||||
.layui-collapse
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
|
@ -13,7 +13,6 @@ include /mixins/dic.mixin.pug
|
||||
.uk-layui-form
|
||||
form.layui-form(action='/apps/contacts/embed/save.html', data-toggle='ajax-form', data-close='false', data-target='#mainajaxwin', method='post')
|
||||
input(type='hidden', name='agentserviceid', value=agentserviceid)
|
||||
input(type='hidden', name='shares', value='all')
|
||||
.layui-collapse
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
|
@ -14,7 +14,6 @@ include /mixins/dic.mixin.pug
|
||||
form.layui-form(action='/apps/contacts/embed/update.html', data-toggle="ajax-form" data-close="false" data-target="#mainajaxwin" method="post")
|
||||
input(type='hidden', name='id', value=contacts.id)
|
||||
input(type='hidden', name='agentserviceid', value=agentserviceid)
|
||||
input(type='hidden', name='shares', value='all')
|
||||
.layui-collapse
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
|
@ -103,14 +103,6 @@ block content
|
||||
td #{contacts.ckind && uKeFuDic[contacts.ckind] ? uKeFuDic[contacts.ckind].name : ""}
|
||||
td #{contacts.user ? contacts.user.username : ""}
|
||||
td
|
||||
if approachable.contains(contacts.id)
|
||||
a(href="#", onclick="openDialogWinByContactid('" + contacts.id + "')")
|
||||
i.layui-icon 
|
||||
| 聊天
|
||||
else
|
||||
a.disabled(href="#", onclick="unreachableDialogWinByContactid('" + contacts.id + "')")
|
||||
i.layui-icon 
|
||||
| 聊天
|
||||
a(href="/apps/contacts/detail.pug?id=" + (contacts.id ? contacts.id : ""), style="margin-left:10px;")
|
||||
i.layui-icon 
|
||||
| 详情
|
||||
@ -186,6 +178,8 @@ block content
|
||||
layer.msg('联系人编辑成功', {icon: 1, time: 1500})
|
||||
else if (msg && msg == 'edit_contacts_fail')
|
||||
layer.msg('联系人编辑失败,因为存在相同Skype ID', {icon: 2, time: 1500})
|
||||
else
|
||||
handleGeneralCodeInQueryPathOrApiResp(msg);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -14,7 +14,6 @@ include /mixins/dic.mixin.pug
|
||||
.uk-layui-form
|
||||
form.layui-form(action="/apps/customer/save.html", method="post")
|
||||
input(type="hidden", name="entcustomer.shares", value="all")
|
||||
input(type="hidden", name="contacts.shares", value="all")
|
||||
.layui-collapse
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
|
@ -16,7 +16,6 @@ include /mixins/dic.mixin.pug
|
||||
input(type="hidden", name="entcustomer.id", value=account.id)
|
||||
input(type="hidden", name="ekindId", value=ekindId)
|
||||
input(type="hidden", name="entcustomer.shares", value="all")
|
||||
input(type="hidden", name="contacts.shares", value="all")
|
||||
.layui-collapse
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
|
@ -36,6 +36,7 @@ html(xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xm
|
||||
script(src="/js/select/js/i18n/zh-CN.js")
|
||||
script(src="/layui.js")
|
||||
script(src="/js/cskefu.js")
|
||||
script(src="/js/CSKeFu_Admin.v1.js")
|
||||
script(type="text/javascript" src="/js/kindeditor/kindeditor.js")
|
||||
script(type="text/javascript" src="/js/kindeditor/lang/zh-CN.js")
|
||||
script(type="text/javascript" src="/js/kindeditor-suggest.js")
|
||||
|
@ -38,8 +38,6 @@ html(xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xm
|
||||
script(src="/js/moment-timezone.js")
|
||||
script(src="/js/moment-timezone-with-data.js")
|
||||
script(src="/layui.js")
|
||||
//- #894 和原生Map行为不一致
|
||||
//- <script src="/js/utils.js"></script>
|
||||
script(src="/js/cskefu.js")
|
||||
script(src="/im/js/socket.io.js")
|
||||
script(src="/js/CSKeFu_IM.v1.js")
|
||||
@ -81,7 +79,7 @@ html(xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xm
|
||||
else if (msg == 't1')
|
||||
layer.msg('当前用户坐席就绪或对话未结束,不能切换为非坐席', {icon: 2, time: 3000})
|
||||
|
||||
if ('#{models.contains("entim")}') {
|
||||
if (#{models.contains("entim")}) {
|
||||
var imDialogHelper = {
|
||||
open: function () {
|
||||
layinx = layer.open({
|
||||
@ -196,7 +194,7 @@ html(xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xm
|
||||
dd
|
||||
a(href="javascript:void(0)" onclick="showSystemBuildInfo()") 关于产品
|
||||
dd
|
||||
a(href="https://docs.cskefu.com/" target="_blank") 使用指南
|
||||
a(href="https://docs.cskefu.com/docs/licenses" target="_blank") 使用授权
|
||||
dd
|
||||
a(href="https://github.com/cskefu/cskefu/issues" target="_blank") 反馈建议
|
||||
dd
|
||||
|
@ -138,6 +138,24 @@ CREATE TABLE `cs_fb_otn_follow` (
|
||||
-- Records of cs_fb_otn_follow
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for cs_metakv
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `cs_metakv`;
|
||||
CREATE TABLE `cs_metakv` (
|
||||
`metakey` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '元数据字段名,唯一标识',
|
||||
`metavalue` text COLLATE utf8mb4_unicode_ci COMMENT '元数据值',
|
||||
`createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updatetime` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`datatype` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'string' COMMENT '数据类型',
|
||||
`comment` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '字段备注描述',
|
||||
PRIMARY KEY (`metakey`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统内置元数据';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of cs_metakv
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for cs_organ_user
|
||||
-- ----------------------------
|
||||
@ -2211,7 +2229,6 @@ CREATE TABLE `uk_organ` (
|
||||
-- Records of uk_organ
|
||||
-- ----------------------------
|
||||
INSERT INTO `uk_organ` VALUES ('2c9e80867d65eb5c017d65f17ceb0019', '售前坐席A组', null, null, null, null, null, null, '4028a0866f9403f1016f9405a05d000e', '1', '');
|
||||
INSERT INTO `uk_organ` VALUES ('40288296874ae16101874ae4f2670016', '机器人平台', null, null, null, null, null, null, '4028a0866f9403f1016f9405a05d000e', '0', '');
|
||||
INSERT INTO `uk_organ` VALUES ('4028a0866f9403f1016f9405a05d000e', '我的企业', null, null, null, null, 'cskefu', null, '0', '0', '');
|
||||
|
||||
-- ----------------------------
|
||||
|
@ -0,0 +1,14 @@
|
||||
USE `cosinee`;
|
||||
-- -----------------
|
||||
-- prepare variables
|
||||
-- -----------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `cs_metakey` (
|
||||
`metakey` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '元数据字段名,唯一标识',
|
||||
`metavalue` text COLLATE utf8mb4_unicode_ci COMMENT '元数据值',
|
||||
`createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updatetime` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`datatype` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'string' COMMENT '数据类型',
|
||||
`comment` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '字段备注描述',
|
||||
PRIMARY KEY (`metakey`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统内置元数据';
|
@ -400,11 +400,18 @@
|
||||
<artifactId>compose4j</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.chatopera.bot</groupId>
|
||||
<artifactId>sdk</artifactId>
|
||||
<version>3.5.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.chatopera.store</groupId>
|
||||
<artifactId>store-sdk</artifactId>
|
||||
<version>1.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Required for Java 11 https://github.com/cskefu/cskefu/issues/714 -->
|
||||
<dependency>
|
||||
|
@ -42,6 +42,7 @@ services:
|
||||
- BOT_THRESHOLD_FAQ_BEST_REPLY=${BOT_THRESHOLD_FAQ_BEST_REPLY:-0.9}
|
||||
- BOT_THRESHOLD_FAQ_SUGG_REPLY=${BOT_THRESHOLD_FAQ_SUGG_REPLY:-0.1}
|
||||
- CSKEFU_SETTINGS_WEBIM_VISITOR_SEPARATE=true
|
||||
- LICENSE_STORE_PROVIDER=${LICENSE_STORE_PROVIDER:-https://store.chatopera.com}
|
||||
- TONGJI_BAIDU_SITEKEY=${TONGJI_BAIDU_SITEKEY:-placeholder}
|
||||
- EXTRAS_LOGIN_BANNER=${NOTICE_LOGIN_BANNER:-off}
|
||||
- EXTRAS_LOGIN_CHATBOX=${EXTRAS_LOGIN_CHATBOX:-off}
|
||||
|
BIN
public/assets/44915582-eb8d2c80-ad65-11e8-8876-86c8b5bb5cc7.png
Normal file
BIN
public/assets/44915582-eb8d2c80-ad65-11e8-8876-86c8b5bb5cc7.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 320 KiB |
BIN
public/assets/44915711-432b9800-ad66-11e8-899b-1ea02244925d.png
Normal file
BIN
public/assets/44915711-432b9800-ad66-11e8-899b-1ea02244925d.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 192 KiB |
BIN
public/assets/51080565-4b82df00-1719-11e9-8cc4-dbbec0459224.png
Normal file
BIN
public/assets/51080565-4b82df00-1719-11e9-8cc4-dbbec0459224.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 315 KiB |
BIN
public/assets/51080567-50479300-1719-11e9-85d8-d209370c9d10.png
Normal file
BIN
public/assets/51080567-50479300-1719-11e9-85d8-d209370c9d10.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 840 KiB |
BIN
public/assets/cskefu-screen-1.jpg
Normal file
BIN
public/assets/cskefu-screen-1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
2
public/pr1st.md
Normal file
2
public/pr1st.md
Normal file
@ -0,0 +1,2 @@
|
||||
# 第一个 PR 改动文件,新手任务,添加一行:昵称 @ 日期,e.g.
|
||||
Hai Liang W. @ 2023-09-11
|
@ -30,6 +30,7 @@ CACHE_SETUP_STRATEGY=create_by_force
|
||||
BOT_THRESHOLD_FAQ_BEST_REPLY=0.8
|
||||
BOT_THRESHOLD_FAQ_SUGG_REPLY=0.6
|
||||
|
||||
LICENSE_STORE_PROVIDER=https://store.chatopera.com
|
||||
TONGJI_BAIDU_SITEKEY=placeholder
|
||||
EXTRAS_LOGIN_BANNER=""
|
||||
EXTRAS_LOGIN_CHATBOX=
|
||||
|
Loading…
x
Reference in New Issue
Block a user