1
0
mirror of https://github.com/chatopera/cosin.git synced 2025-06-16 18:30:03 +08:00

add previous code for v3.10.0

This commit is contained in:
Hai Liang Wang 2019-11-01 19:02:15 +08:00
commit 26ddc81aa8
2680 changed files with 693342 additions and 0 deletions

20
.drone.yml Normal file
View File

@ -0,0 +1,20 @@
pipeline:
build-contact-center:
group: build
image: plugins/docker
context: contact-center
dockerfile: contact-center/Dockerfile
repo: chatopera/contact-center
tags: develop
secrets: [ docker_username, docker_password ]
build-cc-switch:
group: build
image: plugins/docker
context: cc-switch/app
dockerfile: cc-switch/app/Dockerfile
repo: chatopera/cc-switch
tags: develop
secrets: [ docker_username, docker_password ]
branches: develop

12
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,12 @@
# 描述
## 现在行为
## 预期行为
# 解决方案
# 环境
* 代码版本:
Git commit hash (`git rev-parse HEAD`)

29
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,29 @@
<!--- 在标题中简略说明问题 -->
## 描述
<!--- 详细的描述变更 -->
## 解决的问题
<!--- 为什么变更是必要的? -->
<!--- 如果这个PR解决了其他Issue添加链接 -->
## 测试情况
<!--- 详细介绍怎么测试变更了 -->
<!--- 介绍测试环境 -->
<!--- 变更对其他代码的影响 -->
## 截屏
## 变更的类型
<!--- 变更有哪些特点,添加 `x` 到下面的对应项目中: -->
- [ ] 解决Bug
- [ ] 新功能(不影响其他功能)
- [ ] 对其他功能有影响
## 检查:
<!--- 检查下面,各项,添加 `x` 到下面的对应项目中: -->
- [ ] 我的变更和代码规范一致
- [ ] 我的变更需要更新文档
- [ ] 我已经更新了对应的文档
- [ ] 我增加的代码有单元测试
- [ ] 所有单元测试都能通过

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
*.swp
*.swo
*.sublime-*
*.pyc
jmeter.log
__pycache__
tmp/
node_modules/
sftp-config.json
.DS_Store
*.iml
*.ipr
*.iws
*.idea
~$*.xls*
~$*.ppt*
~$*.doc*

View File

@ -0,0 +1,37 @@
<!--- Provide a general summary of the issue in the Title above -->
## 预期行为
<!--- Tell us what should happen -->
## 实际行为
<!--- Tell us what happens instead of the expected behavior -->
## 解决方案
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
## 重现步骤
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
### 第一步
### 第二步
### 第三步
### 第四步
## 环境
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
<!--- Provide a general summary of the issue in the Title above -->
### 版本
<!--- Git commit hash (`git rev-parse HEAD`) -->

View File

@ -0,0 +1,5 @@
# description
## parent #
# solution

View File

@ -0,0 +1,6 @@
# description
# Others
企业聊天机器人/产品需求汇总
https://wiki.chatopera.com/pages/viewpage.action?pageId=4686818

View File

@ -0,0 +1,5 @@
# 描述
## 关联问题
<!-- BUG Issue 链接 -->

View File

@ -0,0 +1,5 @@
# 描述
## 新功能
<!-- 提交说明 -->

View File

@ -0,0 +1,5 @@
# 描述
## 关联问题
<!-- BUG Issue 链接 -->

View File

@ -0,0 +1,5 @@
# 描述
## 更新日志
<!-- 新版本 -->

15
.vscode/sftp.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"name": "sftp",
"remotePath": "git/cosinee",
"protocol": "sftp",
"uploadOnSave": true,
"port": 22,
"profiles": {
"gamera": {
"name": "gamera",
"host": "gamera",
"username": "hain",
"privateKeyPath": "/Users/hain/.ssh/id_rsa"
}
}
}

0
CHANGELOG.md Normal file
View File

20
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,20 @@
# 贡献注意事项
* 在[Issue](https://github.com/chatopera/cosin/issues)中查看有无相关问题
* 详细的介绍问题
* 注意文明用语
# 感谢您有意加入春松客服开源社区
<p align="center">
<b>春松客服QQ交流群185659917 <a href="https://jq.qq.com/?_wv=1027&k=5I1cJLP" target="_blank">点击链接加入群聊</a></b><br>
<img src="https://user-images.githubusercontent.com/3538629/44917177-432d9700-ad6a-11e8-9420-46b0281073e6.png" width="200">
</p>
[![chatoper banner][co-banner-image]][co-url]
[co-banner-image]: https://user-images.githubusercontent.com/3538629/42383104-da925942-8168-11e8-8195-868d5fcec170.png
[co-url]: https://www.chatopera.com

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (2018) 北京华夏春松科技有限公司
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

252
README.md Normal file
View File

@ -0,0 +1,252 @@
[![Docker Layers](https://images.microbadger.com/badges/image/chatopera/contact-center:develop.svg)](https://microbadger.com/images/chatopera/contact-center:develop "Get your own image badge on microbadger.com") [![Docker Version](https://images.microbadger.com/badges/version/chatopera/contact-center:develop.svg)](https://microbadger.com/images/chatopera/contact-center:develop "Get your own version badge on microbadger.com") [![Docker Pulls](https://img.shields.io/docker/pulls/chatopera/contact-center.svg)](https://hub.docker.com/r/chatopera/contact-center/) [![Docker Stars](https://img.shields.io/docker/stars/chatopera/contact-center.svg)](https://hub.docker.com/r/chatopera/contact-center/) [![Docker Commit](https://images.microbadger.com/badges/commit/chatopera/contact-center:develop.svg)](https://microbadger.com/images/chatopera/contact-center:develop "Get your own commit badge on microbadger.com")
<p align="center">
<b>春松客服QQ交流群185659917 <a href="https://jq.qq.com/?_wv=1027&k=5I1cJLP" target="_blank">点击链接加入群聊</a></b><br>
<img src="https://user-images.githubusercontent.com/3538629/44917177-432d9700-ad6a-11e8-9420-46b0281073e6.png" width="200">
</p>
# 春松客服: 全渠道智能客服
春松客服是帮助中小型企业快速而低成本的获得好用的智能客服系统。
<img src="https://user-images.githubusercontent.com/3538629/61031891-fc311900-a3f2-11e9-80cf-c8d0700538a0.png" width="600">
春松客服是Chatopera自主研发以及基于且增强其它开源软件的方式实现的春松客服会不断增强客服系统的智能化这包括利用自然语言处理、机器学习和语音识别等技术让客服工作更有效率、客服满意度更高、成本更低。
**开源项目地址:** [https://github.com/chatopera/cosin](https://github.com/chatopera/cosin)
**开发环境搭建:** [https://github.com/chatopera/cosin/wiki/春松客服:开发环境](https://github.com/chatopera/cosin/wiki/%E6%98%A5%E6%9D%BE%E5%AE%A2%E6%9C%8D%EF%BC%9A%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83)
## 内容结构
[产品演示](https://github.com/chatopera/cosin#%E4%BA%A7%E5%93%81%E6%BC%94%E7%A4%BA)
[功能](https://github.com/chatopera/cosin#%E5%8A%9F%E8%83%BD)
[开发文档](https://github.com/chatopera/cosin#%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3)
[产品截图](https://github.com/chatopera/cosin#%E4%BA%A7%E5%93%81%E6%88%AA%E5%9B%BE)
[产品体系](https://github.com/chatopera/cosin#%E4%BA%A7%E5%93%81%E4%BD%93%E7%B3%BB)
[立即部署](https://github.com/chatopera/cosin#%E7%AB%8B%E5%8D%B3%E9%83%A8%E7%BD%B2)
[鸣谢](https://github.com/chatopera/cosin#%E9%B8%A3%E8%B0%A2)
[开源许可协议](https://github.com/chatopera/cosin#%E5%BC%80%E6%BA%90%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE)
## 产品演示
* 坐席工作台
[http://cc.chatopera.com/](http://cc.chatopera.com/)
| **登录账号** | **密码** |
| --- | --- |
| admin | admin1234 |
* 网页端访客程序
[http://cc.chatopera.com/testclient.html](http://cc.chatopera.com/testclient.html)
## 功能
* 账号及组织机构管理:按组织、角色分配账号权限
* 联系人管理:细粒度维护客户信息
* 网页聊天组件:一分钟接入对话窗口
* 坐席工作台:汇聚多渠道访客请求
* 机器人客服:集成机器人平台服务,完成多轮对话和知识库问答
* 外呼系统:自动外呼,手动外呼,监听和报表等
<b> 《春松客服产品系列视频》 </b>
<table align="center">
<tr>
<th>序号</th>
<th>内容</th>
<th>腾讯视频</th>
<th>百度网盘</th>
</tr>
<tr>
<td>No. 1</td>
<td>产品概述</td>
<td><a href="https://v.qq.com/x/page/z0776g0osqu.html" target="_blank">观看</a></td>
<td><a href="https://pan.baidu.com/s/1BzUveFSkCtfyeU1gUIjp1Q" target="_blank">下载</a></td>
</tr>
<tr>
<td>No. 2</td>
<td>安装部署</td>
<td><a href="https://v.qq.com/x/page/b07760u7f8t.html" target="_blank">观看</a></td>
<td><a href="https://pan.baidu.com/s/1CnZBIIuWpDWATjKTMluzUA" target="_blank">下载</a></td>
</tr>
<tr>
<td>No. 3</td>
<td>功能演示</td>
<td><a href="https://v.qq.com/x/page/h077670ceg2.html" target="_blank">观看</a></td>
<td><a href="https://pan.baidu.com/s/1bCAqUWwk_KQGyfUyvUMt9w" target="_blank">下载</a></td>
</tr>
<tr>
<td>No. 4</td>
<td>账号体系</td>
<td><a href="https://v.qq.com/x/page/b0776jwl6w1.html" target="_blank">观看</a></td>
<td><a href="https://pan.baidu.com/s/18QA9jvXYtwa8Zt78Di9wrg" target="_blank">下载</a></td>
</tr>
<tr>
<td>No. 5</td>
<td>客户关系管理</td>
<td><a href="https://v.qq.com/x/page/d0776p8ghpr.html" target="_blank">观看</a></td>
<td><a href="https://pan.baidu.com/s/1stfkufWkF4byWqvF-ch9Dw" target="_blank">下载</a></td>
</tr>
<tr>
<td>No. 6</td>
<td>即时通信</td>
<td><a href="https://v.qq.com/x/page/r0776rdgt6z.html" target="_blank">观看</a></td>
<td><a href="https://pan.baidu.com/s/1eYEO5OtVu0gyxDI9a-xqJQ" target="_blank">下载</a></td>
</tr>
<tr>
<td>No. 7</td>
<td>呼叫中心</td>
<td><a href="https://v.qq.com/x/page/i07785u58jm.html" target="_blank">观看</a></td>
<td><a href="https://pan.baidu.com/s/1qQOSNMHgcNdXuq0Xib1wXA" target="_blank">下载</a></td>
</tr>
<tr>
<td>No. 8</td>
<td>数据报表</td>
<td><a href="https://v.qq.com/x/page/e0778ptg6b0.html" target="_blank">观看</a></td>
<td><a href="https://pan.baidu.com/s/1iQOz9HR3dkO4lgf5VI4CVw" target="_blank">下载</a></td>
</tr>
</table>
[*下载视频合集*](https://pan.baidu.com/s/1YH7d7nMm5wZQp7P8kID3KA)
## 使用说明
关于产品的具体使用说明,请参考[文档中心](https://docs.chatopera.com/omni-channel-customer-support-system.html)。
## 来自真实用户的反馈
```
项目代码写的挺好的,容易维护,是不错的开源项目。
```
-- 海洋 (深圳银之杰项目经理)
```
Amazing! 要的就是这个效果。
```
-- 常经理 (某电器世界五百强企业)
```
我要在APP内集成我看了好多项目了就你们这个最好基本就是一个商用化的项目。
```
-- Engine X (某二手车出售平台技术负责人)
## 开发文档
<p align="center">
<b><a href="https://github.com/chatopera/cosin/wiki" target="_blank">开发文档</a></b><br>
<a href="https://github.com/chatopera/cosin/wiki" target="_blank">
<img src="https://user-images.githubusercontent.com/3538629/44992890-38be0800-afcb-11e8-8fde-a5a671d29764.png" width="300">
</a>
</p>
## 产品截图
<p align="center">
<b>欢迎页</b><br>
<img src="https://user-images.githubusercontent.com/3538629/44915395-6bff5d80-ad65-11e8-817a-8abb812fb5ee.png" width="900">
</p>
<p align="center">
<b>坐席工作台</b><br>
<img src="https://user-images.githubusercontent.com/3538629/44915582-eb8d2c80-ad65-11e8-8876-86c8b5bb5cc7.png" width="900">
</p>
<p align="center">
<b>坐席监控</b><br>
<img src="https://user-images.githubusercontent.com/3538629/44915711-432b9800-ad66-11e8-899b-1ea02244925d.png" width="900">
</p>
<p align="center">
<b>外呼计划</b><br>
<img src="https://user-images.githubusercontent.com/3538629/44915831-ab7a7980-ad66-11e8-88a5-a2cd23b8c689.png" width="900">
</p>
<p align="center">
<b>通话记录</b><br>
<img src="https://user-images.githubusercontent.com/3538629/44915218-feebc800-ad64-11e8-90fc-36ce96b0c09a.png" width="900">
</p>
<p align="center">
<b>集成客服机器人</b><br>
<img src="https://user-images.githubusercontent.com/3538629/51080565-4b82df00-1719-11e9-8cc4-dbbec0459224.png" width="900">
</p>
<p align="center">
<b>客服机器人应答</b><br>
<img src="https://user-images.githubusercontent.com/3538629/51080567-50479300-1719-11e9-85d8-d209370c9d10.png" width="900">
</p>
<p align="center">
<b>更多功能,敬请期待 ...</b><br>
<img src="https://user-images.githubusercontent.com/3538629/44916014-28a5ee80-ad67-11e8-936a-a2cdbe62f529.png" width="900">
</p>
## 产品体系
<p align="center">
<b>观看视频介绍</b><br>
<a href="https://pan.baidu.com/s/1tqxqfYSvtjDGhh6bDQ-Vog" target="_blank">
<img src="https://user-images.githubusercontent.com/3538629/45403926-6a039b80-b68f-11e8-86e2-5d1f04e3a7c7.png" width="900">
</a>
</p>
## 立即部署
* 企业版
通过青云AppCenter部署青云AppCenter是开发运维一体化(DevOps)管理企业应用的平台Chatopera的春松客服在2018年10月登录AppCenter并借助PaaS平台强大的计算能力实现计算节点集群、存储节点HADR。从而保证了服务高可靠性、高性能、动态伸缩、一键备份和一键回滚等功能。
青云AppCenter以其提供的资源秒级计算特点企业使用AppCenter中的春松客服应用可以按需付费灵活升配和降配Chatopera也非常推荐客户使用青云服务。
<p align="center">
<b>春松客服 on QingCloud</b><br>
<a href="https://appcenter.qingcloud.com/apps/app-zdh88kz7/%E6%98%A5%E6%9D%BE%E5%AE%A2%E6%9C%8D" target="_blank">
<img src="https://user-images.githubusercontent.com/3538629/47984143-a17f4900-e110-11e8-95c9-d8302e000c34.png" width="900">
</a>
</p>
更为详细的部署文档见[春松客服上架青云AppCenter](https://github.com/chatopera/cosin/wiki/%E6%98%A5%E6%9D%BE%E5%AE%A2%E6%9C%8D%E4%B8%8A%E6%9E%B6%E9%9D%92%E4%BA%91AppCenter)。
* 社区版
参考部署[开源社区版本文档](https://github.com/chatopera/cosin/wiki/%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%83%A8%E7%BD%B2)。
## 鸣谢
[优客服](https://gitee.com/beimigame/ukefu)
[FreeSWITCH中国社区](http://www.freeswitch.org.cn/)
## 开源许可协议
Copyright (2018) <a href="https://www.chatopera.com/" target="_blank">北京华夏春松科技有限公司</a>
[Apache License Version 2.0](https://github.com/chatopera/cosin/blob/master/LICENSE)
[![chatoper banner][co-banner-image]][co-url]
[co-banner-image]: https://user-images.githubusercontent.com/3538629/42383104-da925942-8168-11e8-8195-868d5fcec170.png
[co-url]: https://www.chatopera.com

18
cc-chatbot/.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
*.swp
*.swo
*.sublime-*
*.pyc
jmeter.log
__pycache__
tmp/
node_modules/
sftp-config.json
.DS_Store
*.iml
*.ipr
*.iws
*.idea
~$*.xls*
~$*.ppt*
~$*.doc*
app/target/

73
cc-chatbot/README.md Normal file
View File

@ -0,0 +1,73 @@
# cc-chatbot
Chatopera智能问答引擎的Java SDK.
https://docs.chatopera.com/
支持
* 创建聊天机器人
* 查询聊天机器人列表
* 更新聊天机器人画像
* 查询聊天机器人使用情况
* 管理和检索多轮对话
* 管理和检索知识库
* 检索意图识别
# 配置
使用maven需要配置Chatopera的Nexus OSS仓库具体见[文档](https://github.com/chatopera/cosin/wiki/%E6%98%A5%E6%9D%BE%E5%AE%A2%E6%9C%8D%EF%BC%9A%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83#%E4%BF%AE%E6%94%B9maven2%E9%85%8D%E7%BD%AE)。
```
<dependency>
<groupId>com.chatopera.chatbot</groupId>
<artifactId>sdk</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
```
# API
## Chatbot v1
聊天机器人类,构造参数(tcp协议hostname, 端口, 版本)
### Chatbot#getChatbots
获取聊天机器人列表,支持检索查询,分页
### Chatbot#getChatbot
通过聊天机器人ID获得聊天机器人详情
### Chatbot#conversation
与指定的聊天机器人进行多轮对话
### Chatbot#faq
与指定的聊天机器人进行知识库问答
# 测试
```
mvn test
```
# 示例
```
Chatbot cb = new Chatbot("http", "lhc-dev", 8003, "v1");
JSONObject resp = cb.conversation("co_bot_1", "sdktest", "华夏春松在哪里", false);
```
返回值参考 [智能问答引擎文档](https://docs.chatopera.com/chatbot-engine.html)。
## 开源许可协议
Copyright (2018) <a href="https://www.chatopera.com/" target="_blank">北京华夏春松科技有限公司</a>
[Apache License Version 2.0](https://github.com/chatopera/cosin/blob/master/LICENSE)
[![chatoper banner][co-banner-image]][co-url]
[co-banner-image]: https://user-images.githubusercontent.com/3538629/42383104-da925942-8168-11e8-8195-868d5fcec170.png
[co-url]: https://www.chatopera.com

13
cc-chatbot/admin/deploy.sh Executable file
View File

@ -0,0 +1,13 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/../app
mvn clean deploy -Dmaven.test.skip=true

13
cc-chatbot/admin/gen-idea.sh Executable file
View File

@ -0,0 +1,13 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/../app
mvn idea:idea

13
cc-chatbot/admin/package.sh Executable file
View File

@ -0,0 +1,13 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/../app
mvn package

13
cc-chatbot/admin/test.sh Executable file
View File

@ -0,0 +1,13 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/../app
mvn test

44
cc-chatbot/app/.classpath Normal file
View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

23
cc-chatbot/app/.project Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>sdk</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,4 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/test/java=UTF-8
encoding/<project>=UTF-8

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
org.eclipse.jdt.apt.aptEnabled=false

View File

@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.processAnnotations=disabled
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8

View File

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

141
cc-chatbot/app/pom.xml Normal file
View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chatopera.chatbot</groupId>
<artifactId>sdk</artifactId>
<version>1.1.0</version>
<packaging>jar</packaging>
<name>sdk</name>
<description>Java SDK for Chatopera Conversational Engine.</description>
<url>https://www.chatopera.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.3.6</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20140107</version>
</dependency>
<dependency>
<groupId>com.mashape.unirest</groupId>
<artifactId>unirest-java</artifactId>
<version>1.4.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.9</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<distributionManagement>
<snapshotRepository>
<id>chatopera</id>
<url>http://192.168.2.217:8029/repository/maven-snapshots/</url>
</snapshotRepository>
<repository>
<id>chatopera</id>
<url>http://192.168.2.217:8029/repository/maven-releases/</url>
</repository>
</distributionManagement>
<developers>
<developer>
<id>hain</id>
<name>Hai Liang Wang</name>
<email>hailiang.hl.wang@gmail.com</email>
<url>https://github.com/Samurais</url>
<organization>Chatopera Inc.</organization>
<organizationUrl>http://www.chatopera.com</organizationUrl>
<roles>
<role>architect</role>
<role>developer</role>
</roles>
<timezone>Asia/Shanghai</timezone>
</developer>
</developers>
<reporting>
<plugins>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
</plugin>
</plugins>
</reporting>
</project>

View File

@ -0,0 +1,332 @@
/*
* Copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.chatbot;
import com.mashape.unirest.http.exceptions.UnirestException;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
public class ChatbotAPI {
private String schema;
private String hostname;
private int port;
private String baseUrl;
private ChatbotAPI() {
}
public ChatbotAPI(final String baseUrl) throws ChatbotAPIRuntimeException, MalformedURLException {
if (StringUtils.isBlank(baseUrl))
throw new ChatbotAPIRuntimeException("智能问答引擎URL不能为空。");
URL url = new URL(baseUrl);
this.schema = url.getProtocol();
this.hostname = url.getHost();
this.port = url.getPort();
if (port == -1) {
this.baseUrl = this.schema + "://" + this.hostname + "/api/v1";
} else {
this.baseUrl = this.schema + "://" + this.hostname + ":" + this.port + "/api/v1";
}
}
public ChatbotAPI(final String schema, final String hostname, final int port, final String version) {
this.schema = schema;
this.hostname = hostname;
this.port = port;
this.baseUrl = schema + "://" + hostname + ":" + Integer.toString(this.port) + "/api/" + version;
}
public ChatbotAPI(final String schema, final String hostname, final int port) {
this(schema, hostname, port, "v1");
}
public ChatbotAPI(final String hostname, final int port) {
this("http", hostname, port);
}
public String getSchema() {
return schema;
}
public String getHostname() {
return hostname;
}
public int getPort() {
return port;
}
public String getBaseUrl() {
return baseUrl;
}
/**
* 获取聊天机器人列表
*
* @return
* @throws ChatbotAPIRuntimeException
*/
public JSONObject getChatbots(final String fields, final String q, final int page, final int limit) throws ChatbotAPIRuntimeException {
try {
HashMap<String, Object> queryString = new HashMap<String, Object>();
if (StringUtils.isNotBlank(fields)) {
queryString.put("fields", fields);
}
if (StringUtils.isNotBlank(q)) {
queryString.put("q", q);
}
queryString.put("page", page);
if (limit > 0) {
queryString.put("limit", limit);
}
return RestAPI.get(this.getBaseUrl() + "/chatbot", queryString);
} catch (UnirestException e) {
throw new ChatbotAPIRuntimeException(e.toString());
}
}
/**
* 通过ChatbotID检查一个聊天机器人是否存在
*
* @param chatbotID
* @return
*/
public boolean exists(final String chatbotID) throws ChatbotAPIRuntimeException {
try {
JSONObject result = this.getChatbot(chatbotID);
int rc = result.getInt("rc");
if (rc == 0) {
return true;
} else if (rc == 3) {
return false;
} else {
throw new ChatbotAPIRuntimeException("查询聊天机器人异常返回。");
}
} catch (Exception e) {
throw new ChatbotAPIRuntimeException(e.toString());
}
}
/**
* 创建聊天机器人
*
* @param chatbotID 聊天机器人标识[a-zA-Z0-9-]组成字母开头
* @param name 拟人化的名字
* @param primaryLanguage 首选语言支持 [zh_CN|en_US]
* @param fallback 兜底回复
* @param description 描述
* @param welcome 欢迎语
* @return
*/
public JSONObject createBot(final String chatbotID,
final String name,
final String primaryLanguage,
final String fallback,
final String description,
final String welcome) throws ChatbotAPIRuntimeException {
HashMap<String, Object> body = new HashMap<String, Object>();
body.put("chatbotID", chatbotID);
body.put("name", name);
body.put("primaryLanguage", primaryLanguage);
body.put("description", description);
body.put("fallback", fallback);
body.put("welcome", welcome);
try {
return RestAPI.post(this.getBaseUrl() + "/chatbot/" + chatbotID, body);
} catch (UnirestException e) {
throw new ChatbotAPIRuntimeException(e.toString());
}
}
/**
* 更新聊天机器人
*
* @param chatbotID
* @param description
* @param fallback
* @param welcome
* @return
* @throws ChatbotAPIRuntimeException
*/
public boolean updateByChatbotID(final String chatbotID,
final String name,
final String description,
final String fallback,
final String welcome) throws ChatbotAPIRuntimeException {
if (StringUtils.isBlank(chatbotID))
throw new ChatbotAPIRuntimeException("不合法的参数【chatbotID】不能为空。");
HashMap<String, Object> body = new HashMap<String, Object>();
if (StringUtils.isNotBlank(description))
body.put("description", description);
if (StringUtils.isNotBlank(fallback))
body.put("fallback", fallback);
if (StringUtils.isNotBlank(welcome))
body.put("welcome", welcome);
if (StringUtils.isNotBlank(name))
body.put("name", name);
try {
JSONObject result = RestAPI.put(this.baseUrl + "/chatbot/" + chatbotID, body, null);
if (result.getInt("rc") == 0) {
return true;
} else {
return false;
}
} catch (UnirestException e) {
throw new ChatbotAPIRuntimeException(e.toString());
}
}
/**
* 删除聊天机器人
*
* @param chatbotID
* @return
* @throws ChatbotAPIRuntimeException
*/
public boolean deleteByChatbotID(final String chatbotID) throws ChatbotAPIRuntimeException {
if (StringUtils.isBlank(chatbotID))
throw new ChatbotAPIRuntimeException("聊天机器人ID不能为空。");
try {
JSONObject result = RestAPI.delete(this.getBaseUrl() + "/chatbot/" + chatbotID, null);
if (result.getInt("rc") == 0)
return true;
return false;
} catch (UnirestException e) {
throw new ChatbotAPIRuntimeException(e.toString());
}
}
/**
* 获取聊天机器人详情
*
* @param chatbotID
* @return
* @throws ChatbotAPIRuntimeException
*/
public JSONObject getChatbot(final String chatbotID) throws ChatbotAPIRuntimeException {
try {
return RestAPI.get(this.getBaseUrl() + "/chatbot/" + chatbotID);
} catch (UnirestException e) {
throw new ChatbotAPIRuntimeException(e.toString());
}
}
/**
* validate params
*
* @param chatbotID
* @param fromUserId
* @param textMessage
*/
private void v(final String chatbotID, final String fromUserId, final String textMessage) throws ChatbotAPIRuntimeException {
if (StringUtils.isBlank(chatbotID))
throw new ChatbotAPIRuntimeException("[conversation] 不合法的聊天机器人标识。");
if (StringUtils.isBlank(fromUserId))
throw new ChatbotAPIRuntimeException("[conversation] 不合法的用户标识。");
if (StringUtils.isBlank(textMessage))
throw new ChatbotAPIRuntimeException("[conversation] 不合法的消息内容。");
}
/**
* 与聊天机器人进行多轮对话
*
* @param fromUserId
* @param textMessage
* @param debug
* @return
*/
public JSONObject conversation(final String chatbotID, final String fromUserId, final String textMessage, boolean debug) throws ChatbotAPIRuntimeException {
v(chatbotID, fromUserId, textMessage);
HashMap<String, Object> body = new HashMap<String, Object>();
body.put("fromUserId", fromUserId);
body.put("textMessage", textMessage);
body.put("isDebug", debug);
try {
JSONObject resp = RestAPI.post(this.getBaseUrl() + "/chatbot/" + chatbotID + "/conversation/query", body);
return resp;
} catch (UnirestException e) {
throw new ChatbotAPIRuntimeException(e.toString());
}
}
/**
* 意图识别
* @param chatbotID
* @param clientId
* @param textMessage
* @return
* @throws UnirestException
*/
public JSONObject intent(final String chatbotID, final String clientId, final String textMessage) throws ChatbotAPIRuntimeException {
if(StringUtils.isBlank(chatbotID) || StringUtils.isBlank(clientId) || StringUtils.isBlank(textMessage))
throw new ChatbotAPIRuntimeException("参数不合法,不能为空。");
HashMap<String, Object> body = new HashMap<String, Object>();
body.put("clientId", clientId);
body.put("query", textMessage);
try {
JSONObject result = RestAPI.post(this.baseUrl + "/chatbot/" + chatbotID, body);
return result;
} catch (UnirestException e) {
throw new ChatbotAPIRuntimeException(e.toString());
}
}
/**
* 检索知识库
*
* @param chatbotID
* @param fromUserId
* @param textMessage
* @param isDebug
* @return
*/
public JSONObject faq(final String chatbotID, final String fromUserId, final String textMessage, final boolean isDebug) throws ChatbotAPIRuntimeException {
v(chatbotID, fromUserId, textMessage);
HashMap<String, Object> body = new HashMap<String, Object>();
body.put("fromUserId", fromUserId);
body.put("query", textMessage);
body.put("isDebug", isDebug);
try {
JSONObject resp = RestAPI.post(this.getBaseUrl() + "/chatbot/" + chatbotID + "/faq/query", body);
return resp;
} catch (UnirestException e) {
throw new ChatbotAPIRuntimeException(e.toString());
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.chatbot;
import com.mashape.unirest.http.exceptions.UnirestException;
public class ChatbotAPIRuntimeException extends Exception{
public ChatbotAPIRuntimeException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.chatbot;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import com.mashape.unirest.request.GetRequest;
import com.mashape.unirest.request.HttpRequestWithBody;
import org.json.JSONObject;
import java.util.HashMap;
/**
* RestAPI接口
*/
public class RestAPI {
/**
* patch headers
*
* @param headers
*/
private static void x(HashMap<String, String> headers) {
if (headers == null) {
headers = new HashMap<String, String>();
headers.put("accept", "application/json");
return;
}
if (!headers.containsKey("Content-Type"))
headers.put("Content-Type", "application/json");
if (!headers.containsKey("accept"))
headers.put("accept", "application/json");
}
/**
* Post
*
* @param url
* @param body
* @param query
* @param headers
* @return
* @throws UnirestException
*/
public static JSONObject post(final String url, final HashMap<String, Object> body, final HashMap<String, Object> query, HashMap<String, String> headers) throws UnirestException {
HttpRequestWithBody request = Unirest.post(url);
x(headers);
HttpResponse<JsonNode> resp = request
.headers(headers)
.queryString(query)
.fields(body)
.asJson();
// parse response
JSONObject obj = resp.getBody().getObject();
return obj;
}
public static JSONObject post(final String url, final HashMap<String, Object> body) throws UnirestException {
return post(url, body, null, null);
}
/**
* Get
*
* @param url
* @param queryString
* @param headers
* @return
* @throws UnirestException
*/
public static JSONObject get(final String url, final HashMap<String, Object> queryString, HashMap<String, String> headers) throws UnirestException {
GetRequest request = Unirest.get(url);
x(headers);
HttpResponse<JsonNode> resp = request
.headers(headers)
.queryString(queryString)
.asJson();
// parse response
JSONObject obj = resp.getBody().getObject();
return obj;
}
public static JSONObject get(final String url) throws UnirestException {
return get(url, null, null);
}
public static JSONObject get(final String url, HashMap<String, Object> queryString) throws UnirestException {
return get(url, queryString, null);
}
public static JSONObject delete(final String url, HashMap<String, String> headers) throws UnirestException {
x(headers);
return Unirest.delete(url).headers(headers).asJson().getBody().getObject();
}
public static JSONObject put(final String url, HashMap<String, Object> body, HashMap<String, String> headers) throws UnirestException {
x(headers);
return Unirest.put(url).headers(headers).fields(body).asJson().getBody().getObject();
}
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="sdk" xmlns="http://maven.apache.org/DECORATION/1.8.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.8.0 http://maven.apache.org/xsd/decoration-1.8.0.xsd">
<bannerLeft>
<name>sdk</name>
<src>https://maven.apache.org/images/apache-maven-project.png</src>
<href>https://www.apache.org/</href>
</bannerLeft>
<bannerRight>
<src>https://maven.apache.org/images/maven-logo-black-on-white.png</src>
<href>https://maven.apache.org/</href>
</bannerRight>
<skin>
<groupId>org.apache.maven.skins</groupId>
<artifactId>maven-fluido-skin</artifactId>
<version>1.7</version>
</skin>
<body>
<menu ref="parent" />
<menu ref="reports" />
</body>
</project>

View File

@ -0,0 +1,132 @@
/*
* Copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.chatbot;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.json.JSONObject;
import java.net.MalformedURLException;
/**
* Unit test for simple App.
*/
public class ChatbotAPITest
extends TestCase {
private ChatbotAPI cb;
/**
* Create the test case
*
* @param testName name of the test case
*/
public ChatbotAPITest(String testName) {
super(testName);
}
/**
* @return the suite of tests being tested
*/
public static Test suite() {
return new TestSuite(ChatbotAPITest.class);
}
public void setUp() {
this.cb = new ChatbotAPI("http", "lhc-dev", 8003, "v1");
}
/**
* Rigourous Test :-)
*/
public void testChatbot() {
assertEquals(this.cb.getPort(), 8003);
}
public void testGetChatbot() {
try {
JSONObject resp = this.cb.getChatbot("co_bot_1");
System.out.println("[testGetChatbot] " + resp.toString());
} catch (ChatbotAPIRuntimeException e) {
e.printStackTrace();
}
}
public void testGetChatbots() {
try {
JSONObject resp = this.cb.getChatbots("name chatbotID", null, 0, 10);
System.out.println("[testGetChatbots] resp " + resp.toString());
} catch (ChatbotAPIRuntimeException e) {
e.printStackTrace();
}
}
public void testConversation() {
try {
JSONObject resp = this.cb.conversation("co_bot_1", "sdktest", "华夏春松在哪里", false);
System.out.println("[testConversation] resp " + resp.toString());
} catch (ChatbotAPIRuntimeException e) {
e.printStackTrace();
}
}
public void testFaq() {
try {
JSONObject resp = this.cb.faq("co_bot_1", "sdktest", "华夏春松在哪里", false);
System.out.print("[testFaq] resp " + resp.toString());
} catch (ChatbotAPIRuntimeException e) {
e.printStackTrace();
}
}
public void testParseUrl() {
try {
ChatbotAPI c = new ChatbotAPI("https://local:8000/");
System.out.println("chatbot baseUrl " + c.getBaseUrl());
assertEquals("https://local:8000/api/v1", c.getBaseUrl());
} catch (ChatbotAPIRuntimeException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public void testExists() {
JSONObject profile = null;
try {
assertTrue(this.cb.exists("co_bot_1"));
} catch (ChatbotAPIRuntimeException e) {
e.printStackTrace();
}
}
public void testCreateBot() {
try {
JSONObject j = this.cb.createBot("cc_bot_2",
"小云2",
"zh_CN",
"我不了解。",
"小云机器人",
"你好,我是小云。");
} catch (ChatbotAPIRuntimeException e) {
e.printStackTrace();
}
}
}

14
cc-switch/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
*.swp
*.swo
*.sublime-*
*.pyc
__pycache__
node_modules/
sftp-config.json
.DS_Store
~$*
.vscode
.idea/
.composer.phar
app/config/dev.env
ax.js

1
cc-switch/README.md Normal file
View File

@ -0,0 +1 @@
# cc-switch

16
cc-switch/admin/dev.sh Executable file
View File

@ -0,0 +1,16 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
export ACTIVEMQ_HOST=corsair
export REDIS_HOST=corsair
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/../app
DEBUG=cc* npm run dev:start

14
cc-switch/admin/install.sh Executable file
View File

@ -0,0 +1,14 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/..
cd app
npm install

24
cc-switch/admin/test.sh Executable file
View File

@ -0,0 +1,24 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
export ACTIVEMQ_HOST=corsair
export REDIS_HOST=corsair
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/../app
# for x in {1..100}; do
# echo $x
# DEBUG=cc* ava --timeout=10hrs
# sleep 5
# done
DEBUG=cc* ava --timeout=10hrs $*

14
cc-switch/app/Dockerfile Normal file
View File

@ -0,0 +1,14 @@
FROM node:carbon-alpine
RUN apk add --no-cache tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json /usr/src/app/
RUN npm install && npm cache clean --force
COPY . /usr/src/app
CMD [ "npm", "start" ]

View File

@ -0,0 +1,7 @@
PBX_CHANNEL_ID=bxzq
FREESWITCH_HOST=freeswitch
REDIS_HOST=redis
MINIO_END_POINT=mini
MINIO_ACCESS_KEY=key
MINIO_SECRET_KEY=secret

View File

@ -0,0 +1,42 @@
const fs = require('fs');
const path = require('path');
const dotenv = require('dotenv');
const PBX_CHANNEL_ID = 'test';
const FREESWITCH_HOST = 'localhost';
const FREESWITCH_PORT = 8021;
const FREESWITCH_MAX_CHANNEL = 5;
const MINIO_END_POINT = 'localhost';
const MINIO_ACCESS_KEY = 'key';
const MINIO_SECRET_KEY = 'secret';
const REDIS_HOST = 'localhost';
const REDIS_PORT = 6379;
const config = {
PBX_CHANNEL_ID,
FREESWITCH_HOST,
FREESWITCH_PORT,
FREESWITCH_MAX_CHANNEL,
REDIS_HOST,
REDIS_PORT,
MINIO_END_POINT,
MINIO_ACCESS_KEY,
MINIO_SECRET_KEY,
};
let envFile = path.join(__dirname, 'dev.env');
if (fs.existsSync(envFile)) {
dotenv.config({ path: envFile });
}
for (let key in config) {
let value = process.env[key];
if (value) {
config[key] = value;
}
}
module.exports = exports = config;

81
cc-switch/app/control.js Normal file
View File

@ -0,0 +1,81 @@
const Redis = require('ioredis');
const _ = require('lodash');
const config = require('./config');
const debug = require('debug')('cc-switch:control');
const FS_SIP_STATUS = `pbx:${config.PBX_CHANNEL_ID}:sips`;
const FS_CHANNE_CC_TO_FS = `pbx:${config.PBX_CHANNEL_ID}:execute`;
const FS_DIALPLAN_STATUS = `pbx:${config.PBX_CHANNEL_ID}:status`;
const FS_DIALPLAN_TARGET = `pbx:${config.PBX_CHANNEL_ID}:targets`;
const FS_EVENT_TO_CC = `pbx:${config.PBX_CHANNEL_ID}:events`;
const sub = new Redis({
host: config.REDIS_HOST,
port: config.REDIS_PORT,
db: 2,
});
const redis = new Redis({
host: config.REDIS_HOST,
port: config.REDIS_PORT,
db: 2,
});
module.exports = exports = {
subExecute(fn) {
return new Promise((resolve, reject) => {
debug('订阅控制信道');
sub.subscribe(FS_CHANNE_CC_TO_FS, (err, count) => {
if (err) {
debug('订阅失败: %o', err);
reject(err);
} else {
debug('订阅成功: %s', count);
sub.on('message', (channel, message) => {
if (channel == FS_CHANNE_CC_TO_FS) {
message = JSON.parse(message);
fn(message);
}
});
resolve();
}
});
});
},
getStatus() {
return redis.hgetall(FS_DIALPLAN_STATUS).then(rows =>
_.map(rows, (v, k) => {
let obj = JSON.parse(v);
obj.id = k;
return obj;
}),
);
},
getNextCall(dialplanId) {
return redis
.rpop(`${FS_DIALPLAN_TARGET}:${dialplanId}`)
.then(data => JSON.parse(data));
},
getSips() {
return redis.hgetall(FS_SIP_STATUS).then(sips =>
_.map(sips, (v, k) => ({
no: k,
state: v,
})),
);
},
removeSips(nos) {
return redis.hdel(FS_SIP_STATUS, ...nos);
},
setSips(sips) {
let args = [];
_.forEach(sips, s => {
args.push(s.no);
args.push(s.state);
});
return redis.hmset(FS_SIP_STATUS, ...args);
},
sendEvent(data) {
return redis.publish(FS_EVENT_TO_CC, JSON.stringify(data));
},
};

109
cc-switch/app/dialplan.js Normal file
View File

@ -0,0 +1,109 @@
const _ = require('lodash');
const moment = require('moment');
const fs = require('./fs');
const control = require('./control');
const debug = require('debug')('cc-switch:dialplan');
const { access } = require('fs');
const minio = require('./minio');
class Dialplan {
constructor(id, concurrency, sips) {
this.id = id;
this.concurrency = concurrency;
this.tasks = [];
this.sips = sips;
this.state = 'create';
}
start(cb) {
debug('开始呼叫计划: %o', this.id);
this.time = setInterval(() => this.loop(), 1000);
this.start = 'start';
this.cb = cb;
}
callTask(to, channel) {
debug('呼叫目标号码: %s', to);
let message = { to, channel, dialplan: this.id, type: 'callout' };
let call = fs.call(to);
call.sips = this.sips;
let removeThisCall = () => _.remove(this.tasks, call);
call.on('channel_answer', uuid => {
debug('用户接听 %s', to);
message.uuid = uuid;
message.from = call.sip;
message.ops = 'answer';
message.createtime = moment().valueOf();
control.sendEvent(message);
});
call.on('channel_hangup', () => {
debug('用户挂机 %s', to);
message.ops = 'hangup';
message.createtime = moment().valueOf();
removeThisCall();
if (call.state != 'call') {
let file = `/usr/recordings/archive/${call.uuid}.wav`;
message.record = `${moment().format('YYYY-MM-DD')}/${call.uuid}.wav`;
access(file, err => {
if (err) {
debug('录音文件不存在: %s', call.uuid);
} else {
debug('上传录音: %s', call.uuid);
setTimeout(() => {
minio.fPutObject('chatopera', message.record, file).catch(err => {
debug('上传录音失败 %s error: %o', call.uuid, err);
});
}, 5000);
}
});
}
control.sendEvent(message);
});
call.on('error', () => {
debug('呼叫错误 %s', to);
message.ops = 'hangup';
removeThisCall();
control.sendEvent(message);
});
return call;
}
async loop() {
// debug('呼叫计划循环: %s', this.id);
while (this.tasks.length < this.concurrency) {
let info = await control.getNextCall(this.id);
if (info) {
let { to, channel } = info;
let call = this.callTask(to, channel);
this.tasks.push(call);
} else {
debug('完成呼叫计划: %s', this.id);
this.start = 'finish';
clearInterval(this.time);
if (this.cb) {
this.cb();
}
return;
}
}
}
async stop() {
debug('停止呼叫计划: %s', this.id);
clearInterval(this.time);
// await Promise.all(this.tasks);
}
}
module.exports = exports = Dialplan;

60
cc-switch/app/engine.js Normal file
View File

@ -0,0 +1,60 @@
const _ = require('lodash');
const debug = require('debug')('cc-switch:engine');
const config = require('./config');
const control = require('./control');
const Dialplan = require('./dialplan');
const fs = require('./fs');
class Engine {
constructor() {
this.dialplans = [];
}
createDialplan(id, concurrency, sips) {
debug('创建呼叫计划: %s ,并发: %s', id, concurrency);
if (!_.find(this.dialplans, { id })) {
let dialplan = new Dialplan(id, concurrency, sips);
this.dialplans.push(dialplan);
dialplan.start(() => _.remove(this.dialplans, dialplan));
}
}
async init() {
await fs.init();
await control.subExecute(message => {
debug('接收控制命令: %o', message);
let { ops, channel } = message;
if (channel == config.PBX_CHANNEL_ID) {
if (ops == 'start') {
this.createDialplan(
message.dialplan,
message.concurrency,
message.sips,
);
} else if (ops == 'monitor') {
fs.eavesdrop(message.sip, message.uuid);
} else {
let dialplan = _.find(this.dialplans, { id: message.dialplan });
if (dialplan) {
dialplan.stop();
_.remove(this.dialplans, dialplan);
}
}
}
});
debug('获取初始状态');
let dialplanTasks = await control.getStatus();
for (let task of dialplanTasks) {
let { id, concurrency, status } = task;
if (status == '执行中') {
this.createDialplan(id, concurrency);
}
}
}
}
module.exports = exports = Engine;

406
cc-switch/app/fs.js Normal file
View File

@ -0,0 +1,406 @@
const EventEmitter = require('events');
const _ = require('lodash');
const esl = require('modesl');
const debug = require('debug')('cc-switch:fs');
const config = require('./config');
const control = require('./control');
const { access } = require('fs');
const minio = require('./minio');
const moment = require('moment');
const { FREESWITCH_HOST, FREESWITCH_PORT, FREESWITCH_MAX_CHANNEL } = config;
const parseTable = body => {
let list = [];
let rows = body.split('\n');
let first = rows.shift();
if (first) {
let head = first.split(',');
for (let r of rows) {
if (r) {
let obj = {};
let cell = r.split(',');
for (let i = 0; i < head.length; i++) {
obj[head[i]] = cell[i];
}
list.push(obj);
} else break;
}
}
return list;
};
const parseHeaders = headers => {
let obj = {};
for (let h of headers) {
obj[h.name] = h.value;
}
return obj;
};
let conn;
const doConnect = reg =>
new Promise((resolve, reject) => {
conn = new esl.Connection(
FREESWITCH_HOST,
FREESWITCH_PORT,
'ClueCon',
() => {
reg();
resolve();
},
);
conn.once('error', err => {
debug('connect freeswitch error: %o', err);
reject(err);
setTimeout(() => doConnect(reg), 2000);
});
});
const show = cmd =>
new Promise((resolve, reject) => {
// debug('执行命令: %s', cmd);
conn.bgapi(cmd, ({ body }) => {
resolve(parseTable(body));
});
});
const showRegistrations = () => show('show registrations');
const showChannels = () => show('show channels');
const showCalls = () => show('show calls');
const record = uuid =>
new Promise((resolve, reject) => {
debug('开始录音: %s', uuid);
conn.bgapi(
`uuid_record ${uuid} start /usr/recordings/archive/${uuid}.wav`,
({ body }) => {
console.log(body);
resolve();
},
);
});
const callOut = phone =>
new Promise((resolve, reject) => {
debug('拨打电话: %s', phone);
conn.bgapi(`originate sofia/gateway/goipx/${phone} &park`, ({ body }) => {
let match = body.match(/([a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}?)/i);
if (match) {
resolve(match[1]);
} else {
reject(new Error(`呼叫 ${phone} 失败`));
}
});
});
const callSip = (phone, originate) =>
new Promise((resolve, reject) => {
debug('拨打SIP: %s', phone);
('originate user/1000 &park');
conn.bgapi(
`originate {origination_caller_id_number=${originate}}user/${phone} &park`,
({ body }) => {
let match = body.match(/([a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}?)/i);
if (match) {
resolve(match[1]);
} else {
reject(new Error(`呼叫SIP ${phone} 失败`));
}
},
);
});
const bridge = (luuid, ruuid) =>
new Promise((resolve, reject) => {
debug('桥接: %s <--> %s', luuid, ruuid);
conn.bgapi(`uuid_bridge ${luuid} ${ruuid}`, ({ body }) => {
if (/OK/.test(body)) {
resolve();
} else {
reject(new Error('桥接失败'));
}
});
});
const eavesdrop = (sip, uuid) =>
new Promise((resolve, reject) => {
debug('监听: %s <--> %s', sip, uuid);
conn.bgapi(`originate user/${sip} &eavesdrop(${uuid})`, ({ body }) => {
if (/OK/.test(body)) {
resolve();
} else {
reject(new Error('监听失败'));
}
});
});
class FreeSwitch extends EventEmitter {
constructor() {
super();
this.tasks = [];
this.wait = [];
this.sips = [];
}
async init() {
debug('初始化Freeswich');
await doConnect(() => this.regEvent());
setInterval(async () => {
let sips = await showRegistrations();
let toDel = _.chain(await control.getSips())
.map(s => s.no)
.filter(k => !_.find(sips, { reg_user: k }))
.value();
if (toDel.length > 0) {
await control.removeSips(toDel);
}
let channels = await showChannels();
let updateSips = _.map(sips, ({ reg_user }) => {
return {
no: reg_user,
state: _.find(channels, { dest: reg_user }) ? '通话' : '空闲',
};
});
if (updateSips.length > 0) {
await control.setSips(updateSips);
}
}, 2000);
}
regEvent() {
conn.events('plain CUSTOM sofia::register');
conn.on('esl::event::CUSTOM::*', ({ subclass, headers }) => {
if (subclass == 'sofia::register') {
let { username } = parseHeaders(headers);
debug('SIP %s 已上线', username);
control.setSips([{ no: username, state: '空闲' }]);
}
});
conn.on('esl::event::CHANNEL_ANSWER::*', async ({ headers }) => {
let variable = parseHeaders(headers);
let { variable_uuid } = variable;
debug('接通电话: %s', variable_uuid);
let call = _.find(this.tasks, { uuid: variable_uuid });
if (call) {
for (let i = 0; i < 3; i++) {
if (call.state == 'hangup') {
return;
}
try {
await call.linkSip();
call.emit('channel_answer', variable_uuid);
return;
} catch (e) {
debug('link sip error: %o', e);
}
}
// debug('link sip error: %o', err);
} else if (variable.variable_sip_gateway_name == 'goipx') {
let {
variable_bridge_channel,
variable_sip_to_user: sip_to_user,
variable_call_uuid,
} = variable;
let match = variable_bridge_channel.match(/sofia\/internal\/(\d+)@/);
if (match) {
let sip_from_user = match[1];
let call = new CallOut(
variable_uuid,
sip_to_user,
variable_call_uuid,
sip_from_user,
);
this.tasks.push(call);
call.emit('channel_answer', variable_uuid);
}
}
});
conn.on('esl::event::CHANNEL_HANGUP::*', ({ headers }) => {
let event = parseHeaders(headers);
let { variable_uuid } = event;
debug('挂断电话: %s', variable_uuid);
let call = _.find(this.tasks, { uuid: variable_uuid });
if (call) {
this.removeCall(call);
call.state = 'hangup';
call.emit('channel_hangup', variable_uuid);
} else if (event.variable_sip_gateway_name == 'goipx') {
let {
variable_bridge_channel,
variable_sip_to_user: sip_to_user,
variable_call_uuid,
} = event;
let match =
variable_bridge_channel &&
variable_bridge_channel.match(/sofia\/internal\/(\d+)@/);
if (match) {
let sip_from_user = match[1];
let call = new CallOut(
variable_uuid,
sip_to_user,
null,
sip_from_user,
);
call.emit('channel_hangup', variable_uuid);
}
}
if (
_.filter(this.tasks, { state: 'call' }).length <=
FREESWITCH_MAX_CHANNEL &&
this.wait.length > 0
) {
debug('执行队列');
let func = this.wait.shift();
func();
}
});
}
removeCall(call) {
_.remove(this.tasks, call);
}
call(phone) {
let call = new Call(phone);
this.tasks.push(call);
let doCall = async () => {
try {
await call.start();
} catch (err) {
this.removeCall(call);
call.emit('error', err);
}
};
if (this.tasks.length <= FREESWITCH_MAX_CHANNEL) {
doCall();
} else {
this.wait.push(doCall);
}
return call;
}
eavesdrop(sip, uuid) {
return eavesdrop(sip, uuid);
}
}
class Call extends EventEmitter {
constructor(phone) {
super();
this.sips = [];
this.phone = phone;
this.try = 0;
}
async start() {
this.uuid = await callOut(this.phone);
this.state = 'call';
}
async linkSip() {
debug('链接SIP电话');
let sips = _.chain(await control.getSips())
.filter({ state: '空闲' })
.map(s => s.no)
.value();
this.sip = _.sample(_.intersection(sips, this.sips));
if (!this.sip) {
throw new Error('无可用SIP');
}
await control.setSips([{ no: this.sip, state: '通话' }]);
this.sip_uuid = await callSip(this.sip, this.phone);
await bridge(this.uuid, this.sip_uuid);
await record(this.uuid);
this.state = 'answer';
}
}
class CallOut extends EventEmitter {
constructor(uuid, phone, sip_uuid, sip) {
super();
this.uuid = uuid;
this.phone = phone;
this.sip_uuid = sip_uuid;
this.sip = sip;
let message = {
uuid,
from: sip,
to: phone,
channel: config.PBX_CHANNEL_ID,
type: 'callout',
};
this.on('channel_answer', () => {
debug('用户接听 %s -> %s', sip, phone);
message.ops = 'answer';
message.createtime = moment().valueOf();
control.sendEvent(message);
});
this.on('channel_hangup', () => {
debug('用户挂机 %s -> %s', sip, phone);
message.ops = 'hangup';
message.createtime = moment().valueOf();
if (sip_uuid) {
let file = `/usr/recordings/archive/${sip_uuid}.wav`;
message.record = `${moment().format('YYYY-MM-DD')}/${uuid}.wav`;
access(file, err => {
if (err) {
debug('录音文件不存在: %s', uuid);
} else {
debug('上传录音: %s', uuid);
setTimeout(() => {
minio.fPutObject('chatopera', message.record, file).catch(err => {
debug('上传录音失败 %s error: %o', uuid, err);
});
}, 5000);
}
});
}
control.sendEvent(message);
});
}
}
module.exports = exports = new FreeSwitch();

14
cc-switch/app/index.js Normal file
View File

@ -0,0 +1,14 @@
const debug = require('debug')('cc-switch');
const Engine = require('./engine');
const engine = new Engine();
engine
.init()
.then(() => {
console.log('cc-switch started');
})
.catch(err => {
debug('cc-switch start error: %o', err);
process.exit();
});

12
cc-switch/app/minio.js Normal file
View File

@ -0,0 +1,12 @@
const Minio = require('minio');
const config = require('./config');
let client = new Minio.Client({
endPoint: config.MINIO_END_POINT,
accessKey: config.MINIO_ACCESS_KEY,
secretKey: config.MINIO_SECRET_KEY,
port: 9000,
useSSL: false,
});
module.exports = exports = client;

View File

@ -0,0 +1,26 @@
{
"name": "cc-switch",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev:start": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"debug": "^3.1.0",
"dotenv": "^6.0.0",
"ioredis": "^4.0.0",
"lodash": ">=4.17.11",
"minio": "^7.0.0",
"modesl": "^1.2.0",
"moment": "^2.22.2"
},
"devDependencies": {
"ava": "^0.25.0",
"nodemon": "^1.18.3"
}
}

View File

@ -0,0 +1,47 @@
/**
* Redis Test
*/
const test = require('ava');
const debug = require('debug')('cc-switch:test:redis');
const Redis = require('ioredis');
const config = require('../config');
const util = require('util');
const moment = require('moment');
const redis = new Redis({
host: config.REDIS_HOST,
port: config.REDIS_PORT,
db: 2,
});
const EVENT_TYPE_CALLOUT = "callout";
const EVENT_QUEUE_QUEUE_CC_TO_FS = "cc:to:freeswitch";
const CALLOUT_DIALPLAN_STATUS = "callout:dialplan:status";
const CALLOUT_DIALPLAN_TARGET = "freeswitch:%s:callout";
const CALLOUT_CC_FROM_FS = "pbx:bxzq:events";
test.only("Redis Test # 外呼接通", async(t) => {
let now = moment();
now.add(-3, "minutes");
console.log("接通时间:", now);
let payload = {
"uuid": "9a0cbc81-ccae-425e-8d3d-369b872a6481",
"to": "13213213213",
"from": "1003",
"type": "callout",
"channel": "bxzq",
"dialplan": "4028827365b2acec0165b307afe405de",
"createtime": now.valueOf(),
"ops": "answer"
}
redis.publish(CALLOUT_CC_FROM_FS, JSON.stringify(payload));
t.pass();
})

View File

@ -0,0 +1,41 @@
/**
* Redis Test
*/
const test = require('ava');
const debug = require('debug')('cc-switch:test:redis');
const Redis = require('ioredis');
const config = require('../config');
const util = require('util');
const moment = require('moment');
const redis = new Redis({
host: config.REDIS_HOST,
port: config.REDIS_PORT,
db: 2,
});
const EVENT_TYPE_CALLOUT = "callout";
const EVENT_QUEUE_QUEUE_CC_TO_FS = "cc:to:freeswitch";
const CALLOUT_DIALPLAN_STATUS = "callout:dialplan:status";
const CALLOUT_DIALPLAN_TARGET = "freeswitch:%s:callout";
const CALLOUT_CC_FROM_FS = "pbx:bxzq:events";
test.only("Redis Test # 外呼挂断", async(t) => {
let now = moment();
now.add(-3, "minutes");
console.log("挂断时间:", now);
let payload = {
"uuid": "9a0cbc81-ccae-425e-8d3d-369b872a6481",
"to": "13213213213",
"from": "1003",
"type": "callout",
"channel": "bxzq",
"dialplan": "4028827365b2acec0165b307afe405de",
"createtime": now.valueOf(),
"ops": "hangup",
"record":"chatopera/376bf70a-9449-46c8-ad3e-03ac41953946.wav"
}
redis.publish(CALLOUT_CC_FROM_FS, JSON.stringify(payload));
t.pass();
})

View File

@ -0,0 +1,5 @@
app/target
!app/target/contact-center-*.war.original
logs/
tmp/
data/

23
contact-center/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
*.swp
*.swo
*.sublime-*
*.pyc
jmeter.log
__pycache__
tmp/
node_modules/
sftp-config.json
.DS_Store
*.iml
*.ipr
*.iws
*.idea
~$*.xls*
~$*.ppt*
~$*.doc*
admin/localrc
app/target/
app/.classpath
app/.project
app/.settings/
logPath_IS_UNDEFINED/

51
contact-center/Dockerfile Normal file
View File

@ -0,0 +1,51 @@
FROM ubuntu:18.04
MAINTAINER Hai Liang Wang <hain@chatopera.com>
ARG DEBIAN_FRONTEND=noninteractive
ARG VCS_REF
LABEL org.label-schema.vcs-ref=$VCS_REF \
org.label-schema.vcs-url="https://github.com/chatopera/cosin"
# COPY $PWD/assets/aliyun.sources.list /etc/apt/sources.list
# install amazon jdk corretto
COPY $PWD/assets/install-corretto-8.sh /opt
RUN chmod +x /opt/install-corretto-8.sh && /opt/install-corretto-8.sh
# install maven
COPY $PWD/assets/install-maven.sh /opt
RUN chmod +x /opt/install-maven.sh && /opt/install-maven.sh
# configure timezone
RUN apt-get update && \
apt-get install --no-install-recommends -y tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure --frontend noninteractive tzdata && \
rm -rf /var/lib/apt/lists/*
# Set the locale
ENV LANG C.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL C.UTF-8
# set ENVs
ENV JAVA_HOME=/usr/lib/jvm/java-1.8.0-amazon-corretto
ENV MAVEN_HOME=/opt/maven
ENV PATH=$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
# create dirs
RUN /bin/bash -c "mkdir -p /{data,logs}"
# build WAR
COPY app /app
COPY config /config
WORKDIR /app
RUN mvn clean package && \
mkdir -p /opt/chatopera && \
mv target/contact-center-3.9.0.war /opt/chatopera && \
rm -rf /app && rm -rf /config && \
rm -rf /root/.m2
WORKDIR /opt/chatopera
EXPOSE 8030-8050
CMD ["java", "-jar", "contact-center-3.9.0.war"]

22
contact-center/README.md Normal file
View File

@ -0,0 +1,22 @@
# 春松客服:智能客服系统
前三代呼叫中心均是以电话为主要的服务渠道。在2000年伴随着互联网以及移动通信的发展与普及将电子邮件、互联网、手机短信等渠道接入呼叫中心成为第四代呼叫中心的标志。第四代呼叫中心也称为多媒体呼叫中心或联络中心Contact Center。它相对传统呼叫中心来说接入渠道丰富同时引入了多渠道接入与多渠道统一排队等概念。
## 文档
### ukefu
原始项目基于[ukefu](https://gitee.com/beimigame/ukefu)。
```
链接: https://pan.baidu.com/s/1wEPZeieZm4qaaFSWUb2Szg
密码: tdm2
```
### wiki
<a href="https://github.com/Samurais/chatopera.io/wiki/13.-ContactCenter%EF%BC%9A%E5%A4%9A%E5%AA%92%E4%BD%93%E5%91%BC%E5%8F%AB%E4%B8%AD%E5%BF%83">ContactCenter多媒体呼叫中心</a>
## 媒体
<a href="http://36kr.com/p/5144999.html" target="_blank">谷歌发布Contact Center AI智能客服真能不再“智障”了吗</a>

19
contact-center/admin/build.sh Executable file
View File

@ -0,0 +1,19 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
appHome=$baseDir/..
imagename=chatopera/contact-center
PACKAGE_VERSION=1.0.0
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
# build
cd $appHome
set -x
docker build --build-arg VCS_REF=`git rev-parse --short HEAD` --force-rm=true --tag $imagename:$PACKAGE_VERSION .
docker tag $imagename:$PACKAGE_VERSION $imagename:develop

13
contact-center/admin/compile.sh Executable file
View File

@ -0,0 +1,13 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/../app
mvn clean compile

30
contact-center/admin/dev.sh Executable file
View File

@ -0,0 +1,30 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
if [ -f $baseDir/localrc ]; then
echo "Load localrc for environment variables ..."
set -x
source $baseDir/localrc
else
echo $baseDir/localrc "not exist."
echo "First, copy and modify the rc template."
echo "cp " $baseDir/localrc.sample $baseDir/localrc
exit 1
fi
# functions
function start(){
cd $baseDir/../app
mvn spring-boot:run
}
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
start

View File

@ -0,0 +1,13 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/../app
mvn eclipse:eclipse

View File

@ -0,0 +1,13 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/../app
mvn idea:idea

View File

@ -0,0 +1,19 @@
export SERVER_PORT=8035
export SERVER_LOG_PATH=$baseDir/../logs
# Log Level: INFO, DEBUG, ERROR, STDOUT
export SERVER_LOG_LEVEL=INFO
export WEB_UPLOAD_PATH=$baseDir/../data
export SPRING_FREEMARKER_CACHE=false
export SPRING_DATA_ELASTICSEARCH_PROPERTIES_PATH_DATA=$baseDir/../data
export UK_IM_SERVER_PORT=8036
export UK_IM_SERVER_HOST=localhost
export UK_IM_SERVER_THREADS=10
export SPRING_DATASOURCE_TYPE=com.alibaba.druid.pool.DruidDataSource
export SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.jdbc.Driver
export SPRING_DATASOURCE_URL=jdbc:mysql://localhost:8889/ukefu-test?useUnicode=true&characterEncoding=UTF-8
export SPRING_DATASOURCE_USERNAME=root
export SPRING_DATASOURCE_PASSWORD=root
export MANAGEMENT_SECURITY_ENABLED=false
export SPRING_REDIS_DATABASE=2
export SPRING_REDIS_HOST=redis
export SPRING_REDIS_PORT=6379

13
contact-center/admin/package.sh Executable file
View File

@ -0,0 +1,13 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/../app
mvn clean package

16
contact-center/admin/push.sh Executable file
View File

@ -0,0 +1,16 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
imagename=chatopera/contact-center
PACKAGE_VERSION=1.0.0
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
docker push $imagename:$PACKAGE_VERSION
docker push $imagename:develop

38
contact-center/admin/run.sh Executable file
View File

@ -0,0 +1,38 @@
#! /bin/bash
###########################################
#
###########################################
# constants
baseDir=$(cd `dirname "$0"`;pwd)
# functions
# main
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
cd $baseDir/
docker run -it --rm \
-p 9035:8035 \
-p 9036:8036 \
-v $PWD/data:/data \
-v $PWD/logs:/logs \
-e "JAVA_OPTS=-Xmx12288m -Xms2048m -XX:PermSize=256m -XX:MaxPermSize=1024m -Djava.net.preferIPv4Stack=true" \
-e SERVER_PORT=8035 \
-e SERVER_LOG_PATH=/logs \
-e SERVER_LOG_LEVEL=INFO \
-e WEB_UPLOAD_PATH=/data \
-e SPRING_FREEMARKER_CACHE=true \
-e SPRING_DATA_ELASTICSEARCH_PROPERTIES_PATH_DATA=/data \
-e SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.jdbc.Driver \
-e "SPRING_DATASOURCE_URL=jdbc:mysql://mysql:8037/contactcenter?useUnicode=true&characterEncoding=UTF-8" \
-e SPRING_DATASOURCE_USERNAME=root \
-e SPRING_DATASOURCE_PASSWORD=123456 \
-e MANAGEMENT_SECURITY_ENABLED=false \
-e SPRING_REDIS_DATABASE=2 \
-e SPRING_REDIS_HOST=redis \
-e SPRING_REDIS_PORT=8041 \
-e CSKEFU_CALLOUT_WATCH_INTERVAL=60000 \
-e SPRING_DATA_ELASTICSEARCH_CLUSTER_NAME=elasticsearch \
-e SPRING_DATA_ELASTICSEARCH_CLUSTER_NODES=elasticsearch:8040 \
-e SPRING_DATA_ELASTICSEARCH_LOCAL=false \
-e SPRING_DATA_ELASTICSEARCH_REPOSITORIES_ENABLED=true \
chatopera/contact-center:develop

419
contact-center/app/pom.xml Normal file
View File

@ -0,0 +1,419 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chatopera.cc</groupId>
<artifactId>contact-center</artifactId>
<version>3.9.0</version>
<packaging>war</packaging>
<name>contact-center</name>
<description>Chatopera Contact Center多媒体呼叫中心下一代呼叫中心</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.25-incubating</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.jasypt</groupId>
<artifactId>jasypt</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org</groupId>
<artifactId>jaudiotagger</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>lt.jave</groupId>
<artifactId>jave</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-all</artifactId>
<version>3.10.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.27</version>
</dependency>
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>jcseg-core</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.6</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>2.6.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/de.odysseus.juel/juel-impl -->
<dependency>
<groupId>de.odysseus.juel</groupId>
<artifactId>juel-impl</artifactId>
<version>2.2.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>12.1.0.1-atlassian-hosted</version>
</dependency>
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.olap4j</groupId>
<artifactId>olap4j</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>mondrian</groupId>
<artifactId>mondrian</artifactId>
<version>3.7.0</version>
</dependency>
<!-- Distribute Storage Service https://github.com/minio/minio-java -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>5.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.googlecode.aviator/aviator -->
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.aliyun/aliyun-java-sdk-core -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.3.6</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20140107</version>
</dependency>
<dependency>
<groupId>com.mashape.unirest</groupId>
<artifactId>unirest-java</artifactId>
<version>1.4.9</version>
</dependency>
<dependency>
<groupId>com.chatopera.bot</groupId>
<artifactId>sdk</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.8.RELEASE</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<attachClasses>true</attachClasses>
<warSourceExcludes>**/WEB-INF</warSourceExcludes>
<packagingExcludes>**/WEB-INF,**/resources</packagingExcludes>
<webResources>
<resource>
<directory>../config/sql/</directory>
<includes>
<include>cskefu-MySQL-slim.sql</include>
</includes>
</resource>
</webResources>
</configuration>
<version>2.1.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
<defaultGoal>compile</defaultGoal>
</build>
<repositories>
<repository>
<id>chatopera</id>
<name>Chatopera Inc.</name>
<url>https://nexus.chatopera.com/repository/maven-public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<developers>
<developer>
<id>hain</id>
<name>Hai Liang Wang</name>
<email>hailiang.hl.wang@gmail.com</email>
<url>https://github.com/Samurais</url>
<organization>Chatopera Inc.</organization>
<organizationUrl>https://www.chatopera.com</organizationUrl>
<roles>
<role>architect</role>
<role>developer</role>
</roles>
<timezone>Asia/Shanghai</timezone>
</developer>
</developers>
</project>

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.aggregation;
import com.chatopera.cc.exception.CallOutRecordException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.BigInteger;
public class CallOutHangupAggsResult {
private final static Logger logger = LoggerFactory.getLogger(CallOutHangupAggsResult.class);
private String dialplan;
private String datestr;
private int total;
private int fails;
private int duration;
private CallOutHangupAggsResult() {
}
public CallOutHangupAggsResult(final String dialplan, // 呼叫计划
final String datestr, // 目标日期
final int total, // 总呼叫
final int fails, // 失败通话
final int duration) { // 总通话事件
this.dialplan = dialplan;
this.datestr = datestr;
this.total = total;
this.fails = fails;
this.duration = duration;
}
public static CallOutHangupAggsResult cast(Object[] x) throws CallOutRecordException {
CallOutHangupAggsResult y = new CallOutHangupAggsResult();
try {
y.setDialplan((String) x[0]);
y.setDatestr((String) x[1]);
y.setTotal(((BigInteger) x[2]).intValue());
y.setFails(((BigInteger) x[3]).intValue());
y.setDuration(((BigDecimal) x[4]).intValue());
} catch (Exception e) {
logger.error("[callout agg] cast error", e);
throw new CallOutRecordException("[Ljava.lang.Object; cannot be cast to " + CallOutHangupAggsResult.class.getSimpleName());
}
return y;
}
public String getDialplan() {
return dialplan;
}
public void setDialplan(String dialplan) {
this.dialplan = dialplan;
}
public String getDatestr() {
return datestr;
}
public void setDatestr(String datestr) {
this.datestr = datestr;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public int getFails() {
return fails;
}
public void setFails(int fails) {
this.fails = fails;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
}

View File

@ -0,0 +1,215 @@
/*
* Copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.aggregation;
import com.chatopera.cc.exception.CallOutRecordException;
import com.google.gson.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.BigInteger;
public class CallOutHangupAuditResult {
private final static Logger logger = LoggerFactory.getLogger(CallOutHangupAuditResult.class);
private final static String DIRECTION_ALL = "呼出和呼入";
private String agentId; // 坐席ID
private String agentName; // 坐席名字
private String direction; // 呼叫方向 ['呼入', '呼出']
private int dialplan; // 自动呼叫个数
private int total; // 总数
private int seconds; // 总时长单位
private int fails; // 失败数
private int gt60; // 长于1分钟个数
private int maxduration; // 最长通话时间单位
private int avgduration; // 平均时长单位
private CallOutHangupAuditResult() {
}
public CallOutHangupAuditResult(String agentId,
String direction,
int dialplan,
int total,
int seconds,
int fails,
int gt60,
int maxduration) {
this.agentId = agentId;
this.direction = direction;
this.dialplan = dialplan;
this.total = total;
this.seconds = seconds;
this.fails = fails;
this.gt60 = gt60;
this.maxduration = maxduration;
}
public static CallOutHangupAuditResult cast(Object[] x) throws CallOutRecordException {
CallOutHangupAuditResult y = new CallOutHangupAuditResult();
try {
y.setAgentId((String) x[0]);
y.setDirection((String) x[1]);
y.setDialplan(((BigInteger) x[2]).intValue());
y.setTotal(((BigInteger) x[3]).intValue());
y.setSeconds(((BigDecimal) x[4]).intValue());
y.setFails(((BigInteger) x[5]).intValue());
y.setGt60(((BigInteger) x[6]).intValue());
y.setMaxduration((int) x[7]);
y.setAvgduration(((BigDecimal) x[8]).intValue());
y.setAgentName((String) x[9]);
} catch (Exception e) {
logger.error("[callout audit] cast error ", e);
throw new CallOutRecordException("[Ljava.lang.Object; cannot be cast to " + CallOutHangupAuditResult.class.getSimpleName());
}
return y;
}
/**
* 合并同一个Agent的两个不同direction的数据
*/
public static CallOutHangupAuditResult mix(final CallOutHangupAuditResult x, final CallOutHangupAuditResult y) throws CallOutRecordException {
if (x == null)
return y;
if (y == null)
return x;
if (x.getDirection() == y.getDirection())
throw new CallOutRecordException("CallOutHangupAuditResult.mix 呼叫方向不能相同。");
if (!x.getAgentId().equals(y.getAgentId()))
throw new CallOutRecordException("CallOutHangupAuditResult.mix 坐席ID必须相同。");
CallOutHangupAuditResult z = new CallOutHangupAuditResult();
z.setDirection(DIRECTION_ALL);
z.setAgentId(x.getAgentId());
z.setAgentName(x.getAgentName());
z.setMaxduration(x.getMaxduration() > y.getMaxduration() ? x.getMaxduration() : y.getMaxduration());
z.setSeconds(x.getSeconds() + y.getSeconds());
z.setGt60(x.getGt60() + y.getGt60());
z.setTotal(x.getTotal() + y.getTotal());
z.setFails(x.getFails() + y.getFails());
z.setDialplan(x.getDialplan() + y.getDialplan());
z.setAvgduration((int) ((x.getAvgduration() + y.getAvgduration()) / 2));
return z;
}
public JsonObject toJson(boolean id, boolean name, boolean direction) {
JsonObject j = new JsonObject();
if (direction)
j.addProperty("direction", this.getDirection());
if (id)
j.addProperty("agentId", this.agentId);
if (name)
j.addProperty("agentName", this.agentName);
int succ = this.getTotal() - this.getFails();
j.addProperty("total", this.getTotal());
j.addProperty("answer", succ);
j.addProperty("rate", MathHelper.float_percentage_formatter(succ, this.getTotal()));
j.addProperty("dur", MathHelper.formatSeconds(this.getSeconds()));
j.addProperty("avg", MathHelper.formatSeconds(this.avgduration));
j.addProperty("max", MathHelper.formatSeconds(this.getMaxduration()));
j.addProperty("gt60", this.getGt60());
return j;
}
public String getAgentId() {
return agentId;
}
public void setAgentId(String agentId) {
this.agentId = agentId;
}
public String getDirection() {
return direction;
}
public void setDirection(String direction) {
this.direction = direction;
}
public int getDialplan() {
return dialplan;
}
public void setDialplan(int dialplan) {
this.dialplan = dialplan;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public int getSeconds() {
return seconds;
}
public void setSeconds(int seconds) {
this.seconds = seconds;
}
public int getFails() {
return fails;
}
public void setFails(int fails) {
this.fails = fails;
}
public int getGt60() {
return gt60;
}
public void setGt60(int gt60) {
this.gt60 = gt60;
}
public int getMaxduration() {
return maxduration;
}
public void setMaxduration(int maxduration) {
this.maxduration = maxduration;
}
public int getAvgduration() {
return avgduration;
}
public void setAvgduration(int avgduration) {
this.avgduration = avgduration;
}
public String getAgentName() {
return agentName;
}
public void setAgentName(String agentName) {
this.agentName = agentName;
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.aggregation;
import java.util.Date;
public class MathHelper {
public static final String FLOAT_PERCENTAGE_FORMATTER = "%.2f%%";
public static final String FLOAT_PERCENTAGE_INVALID = "NaN%";
public static final String FLOAT_PERCENTAGE_ZERO = "0.0%";
/**
* 计算两个int类型的数字的百分比字符串
*
* @param molecule
* @param denominator
* @return
*/
public static String float_percentage_formatter(final int molecule, final int denominator) {
String r = String.format(FLOAT_PERCENTAGE_FORMATTER, 100 * ((float) molecule / denominator));
if (FLOAT_PERCENTAGE_INVALID.equals(r))
r = FLOAT_PERCENTAGE_ZERO;
return r;
}
public static String formatSecondsBetweenTwoDates(Date pre, Date d){
if(d == null)
d = new Date();
return MathHelper.formatSeconds(((d.getTime() - pre.getTime()) / 1000));
}
public static String formatSeconds(Long timeInLong){
Long l = new Long(timeInLong);
return MathHelper.formatSeconds(l.intValue());
}
public static String formatSeconds(int timeInSeconds)
{
int hours = timeInSeconds / 3600;
int secondsLeft = timeInSeconds - hours * 3600;
int minutes = secondsLeft / 60;
int seconds = secondsLeft - minutes * 60;
StringBuffer sb = new StringBuffer();
if (hours < 10)
sb.append(0);
sb.append(hours);
sb.append(":");
if (minutes < 10)
sb.append(0);
sb.append(minutes);
sb.append(":");
if (seconds < 10)
sb.append(0);
sb.append(seconds);
return sb.toString();
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.aggregation.filter;
import com.chatopera.cc.app.cache.CacheHelper;
import org.apache.commons.lang.StringUtils;
import com.hazelcast.mapreduce.KeyPredicate;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.model.UKefuCallOutNames;
@SuppressWarnings("deprecation")
public class AgentCallOutFilter implements KeyPredicate<String>{
/**
*
*/
private static final long serialVersionUID = 1236581634096258855L;
private String orgi ;
/**
*
*/
public AgentCallOutFilter(String orgi){
this.orgi = orgi ;
}
public boolean evaluate(String key) {
UKefuCallOutNames ukefuCallOutNames = (UKefuCallOutNames) CacheHelper.getCallOutCacheBean().getCacheObject(key, orgi);
return ukefuCallOutNames !=null && !StringUtils.isBlank(orgi) && orgi.equals(ukefuCallOutNames.getOrgi()) && MainContext.CallOutType.AGENT.toString().equals(ukefuCallOutNames.getCalltype());
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.aggregation.filter;
import com.chatopera.cc.app.cache.CacheHelper;
import com.chatopera.cc.app.model.AgentStatus;
import org.apache.commons.lang.StringUtils;
import com.hazelcast.mapreduce.KeyPredicate;
@SuppressWarnings("deprecation")
public class AgentStatusBusyOrgiFilter implements KeyPredicate<String>{
/**
*
*/
private static final long serialVersionUID = 1236581634096258855L;
private String orgi ;
/**
*
*/
public AgentStatusBusyOrgiFilter(String orgi){
this.orgi = orgi ;
}
public boolean evaluate(String key) {
AgentStatus agent = (AgentStatus) CacheHelper.getAgentStatusCacheBean().getCacheObject(key, orgi);
return agent!=null && !StringUtils.isBlank(orgi) && orgi.equals(agent.getOrgi()) && agent.isBusy();
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.aggregation.filter;
import com.chatopera.cc.app.cache.CacheHelper;
import com.chatopera.cc.app.model.AgentStatus;
import org.apache.commons.lang.StringUtils;
import com.hazelcast.mapreduce.KeyPredicate;
@SuppressWarnings("deprecation")
public class AgentStatusOrgiFilter implements KeyPredicate<String>{
/**
*
*/
private static final long serialVersionUID = 1236581634096258855L;
private String orgi ;
/**
*
*/
public AgentStatusOrgiFilter(String orgi){
this.orgi = orgi ;
}
public boolean evaluate(String key) {
AgentStatus agent = (AgentStatus) CacheHelper.getAgentStatusCacheBean().getCacheObject(key, orgi);
return agent!=null && !StringUtils.isBlank(orgi) && orgi.equals(agent.getOrgi());
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.aggregation.filter;
import com.chatopera.cc.app.cache.CacheHelper;
import com.chatopera.cc.app.model.AgentUser;
import org.apache.commons.lang.StringUtils;
import com.hazelcast.mapreduce.KeyPredicate;
@SuppressWarnings("deprecation")
public class AgentUserOrgiFilter implements KeyPredicate<String>{
/**
*
*/
private static final long serialVersionUID = 1236581634096258855L;
private String orgi ;
private String status ;
/**
*
*/
public AgentUserOrgiFilter(String orgi , String status){
this.orgi = orgi ;
this.status = status ;
}
public boolean evaluate(String key) {
AgentUser user = (AgentUser) CacheHelper
.getAgentUserCacheBean().getCacheObject(key , orgi);
return user!=null && user.getStatus()!=null && !StringUtils.isBlank(orgi) && orgi.equals(user.getOrgi()) && user.getStatus()!=null && user.getStatus().equals(status);
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.aggregation.filter;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.cache.CacheHelper;
import com.chatopera.cc.app.model.UKefuCallOutNames;
import org.apache.commons.lang.StringUtils;
import com.hazelcast.mapreduce.KeyPredicate;
@SuppressWarnings("deprecation")
public class AiCallOutFilter implements KeyPredicate<String>{
/**
*
*/
private static final long serialVersionUID = 1236581634096258855L;
private String orgi ;
/**
*
*/
public AiCallOutFilter(String orgi){
this.orgi = orgi ;
}
public boolean evaluate(String key) {
UKefuCallOutNames ukefuCallOutNames = (UKefuCallOutNames) CacheHelper.getCallOutCacheBean().getCacheObject(key, orgi);
return ukefuCallOutNames !=null && !StringUtils.isBlank(orgi) && orgi.equals(ukefuCallOutNames.getOrgi()) && MainContext.CallOutType.AI.toString().equals(ukefuCallOutNames.getCalltype());
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.config.StartedEventListener;
import com.chatopera.cc.util.Constants;
import com.chatopera.cc.util.SystemEnvHelper;
import com.chatopera.cc.util.mobile.MobileNumberUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.servlet.MultipartConfigElement;
import java.io.IOException;
@SpringBootApplication
@EnableJpaRepositories("com.chatopera.cc.app.persistence.repository")
@EnableElasticsearchRepositories("com.chatopera.cc.app.persistence.es")
@EnableAsync
@EnableTransactionManagement
public class Application {
@Value("${web.upload-path}")
private String uploaddir;
@Value("${spring.servlet.multipart.max-file-size}")
private String multipartMaxUpload;
@Value("${spring.servlet.multipart.max-request-size}")
private String multipartMaxRequest;
/**
* 记载模块
*/
// 外呼模块
private final static boolean isCalloutModule = SystemEnvHelper.parseModuleFlag("CSKEFU_MODULE_CALLOUT");
// CRM模块
private final static boolean isContactsModule = SystemEnvHelper.parseModuleFlag("CSKEFU_MODULE_CONTACTS");
// 聊天机器人模块
private final static boolean isChatbotModule = SystemEnvHelper.parseModuleFlag("CSKEFU_MODULE_CHATBOT");
static {
// 外呼模块
if (isCalloutModule) {
MainContext.model.put(Constants.CSKEFU_MODULE_CALLOUT, true);
}
// CRM模块
if (isContactsModule) {
MainContext.model.put(Constants.CSKEFU_MODULE_CONTACTS, true);
}
// 聊天机器人模块
if (isChatbotModule) {
MainContext.model.put(Constants.CSKEFU_MODULE_CHATBOT, true);
}
}
/**
* Init local resources
*/
protected static void init() {
try {
System.out.println("init mobile number utils ...");
MobileNumberUtils.init();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize(multipartMaxUpload); //KB,MB
factory.setMaxRequestSize(multipartMaxRequest);
factory.setLocation(uploaddir);
return factory.createMultipartConfig();
}
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
ErrorPage error = new ErrorPage("/error.html");
container.addErrorPages(error);
}
};
}
public static void main(String[] args) {
Application.init();
SpringApplication app = new SpringApplication(Application.class);
app.setBannerMode(Banner.Mode.CONSOLE);
app.setAddCommandLineProperties(false);
app.addListeners(new StartedEventListener());
MainContext.setApplicationContext(app.run(args));
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer{
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}

View File

@ -0,0 +1,837 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.algorithm;
import com.chatopera.cc.aggregation.filter.AgentStatusBusyOrgiFilter;
import com.chatopera.cc.aggregation.filter.AgentStatusOrgiFilter;
import com.chatopera.cc.aggregation.filter.AgentUserOrgiFilter;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.basic.MainUtils;
import com.chatopera.cc.app.cache.CacheHelper;
import com.chatopera.cc.app.im.client.NettyClients;
import com.chatopera.cc.app.im.router.OutMessageRouter;
import com.chatopera.cc.app.model.*;
import com.chatopera.cc.app.persistence.repository.*;
import com.chatopera.cc.util.WebIMReport;
import com.corundumstudio.socketio.SocketIONamespace;
import com.hazelcast.core.IMap;
import com.hazelcast.mapreduce.aggregation.Aggregations;
import com.hazelcast.mapreduce.aggregation.Supplier;
import com.hazelcast.query.PagingPredicate;
import com.hazelcast.query.SqlPredicate;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.Lock;
/**
* Automatic Call Distribution
*/
@SuppressWarnings("deprecation")
public class AutomaticServiceDist {
private final static Logger logger = LoggerFactory.getLogger(AutomaticServiceDist.class);
/**
* 载入坐席 ACD策略配置
*
* @param orgi
* @return
*/
public static SessionConfig initSessionConfig(String orgi) {
SessionConfig sessionConfig = null;
if (MainContext.getContext() != null && (sessionConfig = (SessionConfig) CacheHelper.getSystemCacheBean().getCacheObject(MainContext.SYSTEM_CACHE_SESSION_CONFIG + "_" + orgi, orgi)) == null) {
SessionConfigRepository sessionConfigRes = MainContext.getContext().getBean(SessionConfigRepository.class);
sessionConfig = sessionConfigRes.findByOrgi(orgi);
if (sessionConfig == null) {
sessionConfig = new SessionConfig();
} else {
CacheHelper.getSystemCacheBean().put(MainContext.SYSTEM_CACHE_SESSION_CONFIG + "_" + orgi, sessionConfig, orgi);
}
}
return sessionConfig;
}
/**
* 载入坐席 ACD策略配置
*
* @return
*/
@SuppressWarnings("unchecked")
public static List<SessionConfig> initSessionConfigList() {
List<SessionConfig> sessionConfigList = null;
if (MainContext.getContext() != null && (sessionConfigList = (List<SessionConfig>) CacheHelper.getSystemCacheBean().getCacheObject(MainContext.SYSTEM_CACHE_SESSION_CONFIG_LIST, MainContext.SYSTEM_ORGI)) == null) {
SessionConfigRepository sessionConfigRes = MainContext.getContext().getBean(SessionConfigRepository.class);
sessionConfigList = sessionConfigRes.findAll();
if (sessionConfigList != null && sessionConfigList.size() > 0) {
CacheHelper.getSystemCacheBean().put(MainContext.SYSTEM_CACHE_SESSION_CONFIG_LIST, sessionConfigList, MainContext.SYSTEM_ORGI);
}
}
return sessionConfigList;
}
/**
* 获得 当前服务状态
*
* @param orgi
* @return
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static AgentReport getAgentReport(String orgi) {
/**
* 统计当前在线的坐席数量
*/
AgentReport report = new AgentReport();
IMap agentStatusMap = (IMap<String, Object>) CacheHelper.getAgentStatusCacheBean().getCache();
AgentStatusOrgiFilter filter = new AgentStatusOrgiFilter(orgi);
Long agents = (Long) agentStatusMap.aggregate(Supplier.fromKeyPredicate(filter), Aggregations.count());
report.setAgents(agents.intValue());
Long busyAgent = (Long) agentStatusMap.aggregate(Supplier.fromKeyPredicate(new AgentStatusBusyOrgiFilter(orgi)), Aggregations.count());
report.setBusy(busyAgent.intValue());
report.setOrgi(orgi);
/**
* 统计当前服务中的用户数量
*/
IMap agentUserMap = (IMap<String, Object>) CacheHelper.getAgentUserCacheBean().getCache();
Long users = (Long) agentUserMap.aggregate(Supplier.fromKeyPredicate(new AgentUserOrgiFilter(orgi, MainContext.AgentUserStatusEnum.INSERVICE.toString())), Aggregations.count());
report.setUsers(users.intValue());
Long queneUsers = (Long) agentUserMap.aggregate(Supplier.fromKeyPredicate(new AgentUserOrgiFilter(orgi, MainContext.AgentUserStatusEnum.INQUENE.toString())), Aggregations.count());
report.setInquene(queneUsers.intValue());
return report;
}
public static int getQueneIndex(String userid, String orgi, long ordertime) {
// IList<AgentUser> queneUserList = (IList<AgentUser>) CacheHelper.getQueneUserCacheBean().getCache() ;
int queneUsers = 0;
// for(AgentUser agentUser : queneUserList){
// if(agentUser.getOrgi().equals(orgi) && agentUser.getUserid().equals(userid)){
// queneUsers ++ ;
// }
// }
return queneUsers;
}
@SuppressWarnings("unchecked")
public static int getQueneIndex(String agent, String orgi, String skill) {
int queneUsers = 0;
PagingPredicate<String, AgentUser> pagingPredicate = null;
if (StringUtils.isNotBlank(skill)) {
pagingPredicate = new PagingPredicate<String, AgentUser>(new SqlPredicate("status = 'inquene' AND skill = '" + skill + "' AND orgi = '" + orgi + "'"), 100);
} else if (StringUtils.isNotBlank(agent)) {
pagingPredicate = new PagingPredicate<String, AgentUser>(new SqlPredicate("status = 'inquene' AND agent = '" + agent + "' AND orgi = '" + orgi + "'"), 100);
} else {
pagingPredicate = new PagingPredicate<String, AgentUser>(new SqlPredicate("status = 'inquene' AND orgi = '" + orgi + "'"), 100);
}
queneUsers = ((IMap<String, AgentUser>) CacheHelper.getAgentUserCacheBean().getCache()).values(pagingPredicate).size();
return queneUsers;
}
@SuppressWarnings("unchecked")
public static int getAgentUsers(String agent, String orgi) {
/**
* agentno自动是 服务的坐席 agent 是请求的坐席
*/
PagingPredicate<String, AgentUser> pagingPredicate = new PagingPredicate<String, AgentUser>(new SqlPredicate("status = 'inservice' AND agentno = '" + agent + "' AND orgi = '" + orgi + "'"), 100);
List<AgentUser> agentUserList = new ArrayList<AgentUser>();
agentUserList.addAll(((IMap<String, AgentUser>) CacheHelper.getAgentUserCacheBean().getCache()).values(pagingPredicate));
return agentUserList.size();
}
@SuppressWarnings("unchecked")
public static List<AgentStatus> getAgentStatus(String skill, String orgi) {
PagingPredicate<String, AgentStatus> pagingPredicate = new PagingPredicate<String, AgentStatus>(new SqlPredicate("orgi = '" + orgi + "'"), 100);
if (StringUtils.isNotBlank(skill)) {
pagingPredicate = new PagingPredicate<String, AgentStatus>(new SqlPredicate("skill = '" + skill + "' AND orgi = '" + orgi + "'"), 100);
}
List<AgentStatus> agentList = new ArrayList<AgentStatus>();
agentList.addAll(((IMap<String, AgentStatus>) CacheHelper.getAgentStatusCacheBean().getCache()).values(pagingPredicate));
return agentList;
}
/**
* 为坐席批量分配用户
*
* @param agentStatus
*/
@SuppressWarnings("unchecked")
public static void allotAgent(String agentno, String orgi) {
AgentStatus agentStatus = (AgentStatus) CacheHelper.getAgentStatusCacheBean().getCacheObject(agentno, orgi);
List<AgentUser> agentStatusList = new ArrayList<AgentUser>();
PagingPredicate<String, AgentUser> pagingPredicate = null;
if (agentStatus != null && StringUtils.isNotBlank(agentStatus.getSkill())) {
pagingPredicate = new PagingPredicate<String, AgentUser>(new SqlPredicate("status = 'inquene' AND ((agent = null AND skill = null) OR (skill = '" + agentStatus.getSkill() + "' AND agent = null) OR agent = '" + agentno + "') AND orgi = '" + orgi + "'"), 10);
} else {
pagingPredicate = new PagingPredicate<String, AgentUser>(new SqlPredicate("status = 'inquene' AND ((agent = null AND skill = null) OR agent = '" + agentno + "') AND orgi = '" + orgi + "'"), 10);
}
agentStatusList.addAll(((IMap<String, AgentUser>) CacheHelper.getAgentUserCacheBean().getCache()).values(pagingPredicate));
for (AgentUser agentUser : agentStatusList) {
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(orgi);
long maxusers = sessionConfig != null ? sessionConfig.getMaxuser() : MainContext.AGENT_STATUS_MAX_USER;
if (agentStatus != null && agentStatus.getUsers() < maxusers) { //坐席未达到最大咨询访客数量
CacheHelper.getAgentUserCacheBean().delete(agentUser.getUserid(), orgi); //从队列移除进入正在处理的队列 避免使用 分布式锁
try {
AgentService agentService = processAgentService(agentStatus, agentUser, orgi);
MessageOutContent outMessage = new MessageOutContent();
outMessage.setMessage(AutomaticServiceDist.getSuccessMessage(agentService, agentUser.getChannel(), orgi));
outMessage.setMessageType(MainContext.MediaTypeEnum.TEXT.toString());
outMessage.setCalltype(MainContext.CallTypeEnum.IN.toString());
outMessage.setNickName(agentStatus.getUsername());
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
if (StringUtils.isNotBlank(agentUser.getUserid())) {
OutMessageRouter router = null;
router = (OutMessageRouter) MainContext.getContext().getBean(agentUser.getChannel());
if (router != null) {
router.handler(agentUser.getUserid(), MainContext.MessageTypeEnum.MESSAGE.toString(), agentUser.getAppid(), outMessage);
}
}
// TODO #111 为坐席分配访客
NettyClients.getInstance().publishAgentEventMessage(agentService.getAgentno(), MainContext.MessageTypeEnum.NEW.toString(), agentUser);
} catch (Exception ex) {
ex.printStackTrace();
}
} else {
break;
}
}
publishMessage(orgi, "agent", "success", agentno);
}
/**
* 为坐席批量分配用户
*
* @param agentStatus
* @throws Exception
*/
public static void serviceFinish(AgentUser agentUser, String orgi) throws Exception {
if (agentUser != null) {
AgentStatus agentStatus = null;
if (MainContext.AgentUserStatusEnum.INSERVICE.toString().equals(agentUser.getStatus()) && agentUser.getAgentno() != null) {
agentStatus = (AgentStatus) CacheHelper.getAgentStatusCacheBean().getCacheObject(agentUser.getAgentno(), orgi);
}
CacheHelper.getAgentUserCacheBean().delete(agentUser.getUserid(), orgi);
AgentUserRepository agentUserRepository = MainContext.getContext().getBean(AgentUserRepository.class);
AgentUser agentUseDataBean = agentUserRepository.findByIdAndOrgi(agentUser.getId(), agentUser.getOrgi());
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(orgi);
if (agentUseDataBean != null) {
agentUseDataBean.setStatus(MainContext.AgentUserStatusEnum.END.toString());
if (agentUser.getServicetime() != null) {
agentUseDataBean.setSessiontimes(System.currentTimeMillis() - agentUser.getServicetime().getTime());
}
agentUserRepository.save(agentUseDataBean);
/**
* 更新OnlineUser对象变更为服务中不可邀请 , WebIM渠道专用
*/
if (MainContext.ChannelTypeEnum.WEBIM.toString().equals(agentUser.getChannel())) {
OnlineUserRepository onlineUserRes = MainContext.getContext().getBean(OnlineUserRepository.class);
List<OnlineUser> onlineUserList = onlineUserRes.findByUseridAndOrgi(agentUser.getUserid(), agentUser.getOrgi());
if (onlineUserList.size() > 0) {
OnlineUser onlineUser = onlineUserList.get(0);
onlineUser.setInvitestatus(MainContext.OnlineUserInviteStatus.DEFAULT.toString());
onlineUserRes.save(onlineUser);
}
}
}
final boolean isPhone = MainContext.ChannelTypeEnum.PHONE.toString().equals(agentUser.getChannel());
AgentServiceRepository agentServiceRes = MainContext.getContext().getBean(AgentServiceRepository.class);
AgentService service = null;
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
service = agentServiceRes.findByIdAndOrgi(agentUser.getAgentserviceid(), agentUser.getOrgi());
}
if (service == null) {//当做留言处理
service = processAgentService(agentStatus, agentUser, orgi, true);
}
if (service != null) {
service.setStatus(MainContext.AgentUserStatusEnum.END.toString());
service.setEndtime(new Date());
if (service.getServicetime() != null) {
service.setSessiontimes(System.currentTimeMillis() - service.getServicetime().getTime());
}
AgentUserTaskRepository agentUserTaskRes = MainContext.getContext().getBean(AgentUserTaskRepository.class);
List<AgentUserTask> agentUserTaskList = agentUserTaskRes.findByIdAndOrgi(agentUser.getId(), agentUser.getOrgi());
if (agentUserTaskList.size() > 0) {
AgentUserTask agentUserTask = agentUserTaskList.get(0);
service.setAgentreplyinterval(agentUserTask.getAgentreplyinterval());
service.setAgentreplytime(agentUserTask.getAgentreplytime());
service.setAvgreplyinterval(agentUserTask.getAvgreplyinterval());
service.setAvgreplytime(agentUserTask.getAvgreplytime());
service.setUserasks(agentUserTask.getUserasks());
service.setAgentreplys(agentUserTask.getAgentreplys());
}
/**
* 启用了质检任务开启质检
*/
if (sessionConfig.isQuality() && service.getUserasks() > 0) { //开启了质检并且是有效对话
service.setQualitystatus(MainContext.QualityStatus.NODIS.toString()); //未分配质检任务
} else {
service.setQualitystatus(MainContext.QualityStatus.NO.toString()); //未开启质检 或无效对话无需质检
}
agentServiceRes.save(service);
}
if (isPhone) { // 语音渠道强制发送
NettyClients.getInstance().sendCalloutEventMessage(agentUser.getAgentno(), MainContext.MessageTypeEnum.END.toString(), agentUser);
} else {
if (agentStatus != null) // WebIM 查看用户状态
// TODO #111 结束会话
NettyClients.getInstance().publishAgentEventMessage(agentUser.getAgentno(), MainContext.MessageTypeEnum.END.toString(), agentUser);
OutMessageRouter router = null;
router = (OutMessageRouter) MainContext.getContext().getBean(agentUser.getChannel());
if (router != null) {
MessageOutContent outMessage = new MessageOutContent();
outMessage.setMessage(AutomaticServiceDist.getServiceFinishMessage(agentUser.getChannel(), orgi));
outMessage.setMessageType(MainContext.AgentUserStatusEnum.END.toString());
outMessage.setCalltype(MainContext.CallTypeEnum.IN.toString());
if (agentStatus != null) {
outMessage.setNickName(agentStatus.getUsername());
} else {
outMessage.setNickName(agentUser.getUsername());
}
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
outMessage.setAgentserviceid(agentUser.getAgentserviceid());
router.handler(agentUser.getUserid(), MainContext.MessageTypeEnum.STATUS.toString(), agentUser.getAppid(), outMessage);
}
}
if (agentStatus != null) {
updateAgentStatus(agentStatus, agentUser, orgi, false);
long maxusers = sessionConfig != null ? sessionConfig.getMaxuser() : MainContext.AGENT_STATUS_MAX_USER;
if (agentStatus.getUsers() < maxusers) {
allotAgent(agentStatus.getAgentno(), orgi);
}
}
publishMessage(orgi, "end", "success", agentUser != null ? agentUser.getId() : null);
}
}
/**
* 更新坐席当前服务中的用户状态需要分布式锁
*
* @param agentStatus
* @param agentUser
* @param orgi
*/
public synchronized static void updateAgentStatus(AgentStatus agentStatus, AgentUser agentUser, String orgi, boolean in) {
int users = getAgentUsers(agentStatus.getAgentno(), orgi);
Lock lock = CacheHelper.getAgentStatusCacheBean().getLock("LOCK", orgi);
lock.lock();
try {
agentStatus.setUsers(users);
agentStatus.setUpdatetime(new Date());
CacheHelper.getAgentStatusCacheBean().put(agentStatus.getAgentno(), agentStatus, orgi);
} finally {
lock.unlock();
}
}
public static void publishMessage(String orgi, String worktype, String workresult, String dataid) {
/**
* 坐席状态改变通知监测服务
*/
AgentReport agentReport = AutomaticServiceDist.getAgentReport(orgi);
AgentReportRepository agentReportRes = MainContext.getContext().getBean(AgentReportRepository.class);
if (agentReportRes != null) {
agentReport.setOrgi(orgi);
agentReport.setWorktype(worktype);
agentReport.setWorkresult(workresult);
agentReport.setDataid(dataid);
agentReportRes.save(agentReport);
}
MainContext.getContext().getBean("agentNamespace", SocketIONamespace.class).getBroadcastOperations().sendEvent("status", agentReport);
}
/**
* @param agent 坐席
* @param skill 技能组
* @param userid 用户ID
* @param status 工作状态
* @param worktype 类型 语音OR 文本
* @param orgi
* @param lasttime
*/
public static void recordAgentStatus(String agent, String username, String extno, String skill, boolean admin, String userid, String status, String current, String worktype, String orgi, Date lasttime) {
WorkMonitorRepository workMonitorRes = MainContext.getContext().getBean(WorkMonitorRepository.class);
WorkMonitor workMonitor = new WorkMonitor();
if (StringUtils.isNotBlank(agent) && StringUtils.isNotBlank(status)) {
workMonitor.setAgent(agent);
workMonitor.setAgentno(agent);
workMonitor.setStatus(status);
workMonitor.setAdmin(admin);
workMonitor.setUsername(username);
workMonitor.setExtno(extno);
workMonitor.setWorktype(worktype);
if (lasttime != null) {
workMonitor.setDuration((int) (System.currentTimeMillis() - lasttime.getTime()) / 1000);
}
if (status.equals(MainContext.AgentStatusEnum.BUSY.toString())) {
workMonitor.setBusy(true);
}
if (status.equals(MainContext.AgentStatusEnum.READY.toString())) {
int count = workMonitorRes.countByAgentAndDatestrAndStatusAndOrgi(agent, MainUtils.simpleDateFormat.format(new Date()), MainContext.AgentStatusEnum.READY.toString(), orgi);
if (count == 0) {
workMonitor.setFirsttime(true);
}
}
if (current.equals(MainContext.AgentStatusEnum.NOTREADY.toString())) {
List<WorkMonitor> workMonitorList = workMonitorRes.findByOrgiAndAgentAndDatestrAndFirsttime(orgi, agent, MainUtils.simpleDateFormat.format(new Date()), true);
if (workMonitorList.size() > 0) {
WorkMonitor firstWorkMonitor = workMonitorList.get(0);
if (firstWorkMonitor.getFirsttimes() == 0) {
firstWorkMonitor.setFirsttimes((int) (System.currentTimeMillis() - firstWorkMonitor.getCreatetime().getTime()));
workMonitorRes.save(firstWorkMonitor);
}
}
}
workMonitor.setCreatetime(new Date());
workMonitor.setDatestr(MainUtils.simpleDateFormat.format(new Date()));
workMonitor.setName(agent);
workMonitor.setOrgi(orgi);
workMonitor.setSkill(skill);
workMonitor.setUserid(userid);
workMonitorRes.save(workMonitor);
}
}
/**
* 为用户分配坐席
*
* @param agentUser
*/
@SuppressWarnings("unchecked")
public static AgentService allotAgent(AgentUser agentUser, String orgi) {
/**
* 查询条件当前在线的 坐席并且 未达到最大 服务人数的坐席
*/
List<AgentStatus> agentStatusList = new ArrayList<AgentStatus>();
PagingPredicate<String, AgentStatus> pagingPredicate = null;
/**
* 处理ACD 技能组请求和 坐席请求
*/
if (StringUtils.isNotBlank(agentUser.getAgent())) {
pagingPredicate = new PagingPredicate<String, AgentStatus>(new SqlPredicate(" busy = false AND agentno = '" + agentUser.getAgent() + "' AND orgi = '" + orgi + "'"), 1);
} else if (StringUtils.isNotBlank(agentUser.getSkill())) {
pagingPredicate = new PagingPredicate<String, AgentStatus>(new SqlPredicate(" busy = false AND skill = '" + agentUser.getSkill() + "' AND orgi = '" + orgi + "'"), 1);
} else {
pagingPredicate = new PagingPredicate<String, AgentStatus>(new SqlPredicate(" busy = false AND orgi = '" + orgi + "'"), 1);
}
agentStatusList.addAll(((IMap<String, AgentStatus>) CacheHelper.getAgentStatusCacheBean().getCache()).values(pagingPredicate));
AgentStatus agentStatus = null;
AgentService agentService = null; //放入缓存的对象
if (agentStatusList.size() > 0) {
agentStatus = agentStatusList.get(0);
if (agentStatus.getUsers() >= initSessionConfig(orgi).getMaxuser()) {
agentStatus = null;
/**
* 判断当前有多少人排队中 分三种情况1请求技能组的2请求坐席的3默认请求的
*
*/
}
}
try {
agentService = processAgentService(agentStatus, agentUser, orgi);
if (agentService.getStatus().equals(MainContext.AgentUserStatusEnum.INQUENE.toString())) {
agentService.setQueneindex(getQueneIndex(agentUser.getAgent(), orgi, agentUser.getSkill()));
}
} catch (Exception ex) {
ex.printStackTrace();
}
publishMessage(orgi, "user", agentService != null && agentService.getStatus().equals(MainContext.AgentUserStatusEnum.INSERVICE.toString()) ? "inservice" : "inquene", agentUser.getId());
return agentService;
}
/**
* 邀请访客进入当前对话如果当前操作的 坐席是已就绪状态则直接加入到当前坐席的 对话列表中如果未登录则分配给其他坐席
*
* @param agentno
* @param agentUser
* @param orgi
* @return
* @throws Exception
*/
public static AgentService allotAgentForInvite(String agentno, AgentUser agentUser, String orgi) throws Exception {
AgentStatus agentStatus = (AgentStatus) CacheHelper.getAgentStatusCacheBean().getCacheObject(agentno, orgi);
AgentService agentService = null;
if (agentStatus != null) {
agentService = processAgentService(agentStatus, agentUser, orgi);
publishMessage(orgi, "invite", "success", agentno);
// TODO #111 为坐席分配邀请的访客
NettyClients.getInstance().publishAgentEventMessage(agentService.getAgentno(), MainContext.MessageTypeEnum.NEW.toString(), agentUser);
} else {
agentService = allotAgent(agentUser, orgi);
}
return agentService;
}
/**
* 为访客 分配坐席 ACD策略此处 AgentStatus 是建议 坐席 如果启用了 历史服务坐席 优先策略 则会默认检查历史坐席是否空闲如果空闲则分配如果不空闲 分配当前建议的坐席
*
* @param agentStatus
* @param agentUser
* @param orgi
* @return
* @throws Exception
*/
private static AgentService processAgentService(AgentStatus agentStatus, AgentUser agentUser, String orgi) throws Exception {
return processAgentService(agentStatus, agentUser, orgi, false);
}
/**
* 为访客 分配坐席 ACD策略此处 AgentStatus 是建议 坐席 如果启用了 历史服务坐席 优先策略 则会默认检查历史坐席是否空闲如果空闲则分配如果不空闲 分配当前建议的坐席
*
* @param agentUser
* @param orgi
* @return
* @throws Exception
*/
public static AgentService processChatbotService(final String botName, final AgentUser agentUser, final String orgi) {
AgentService agentService = new AgentService(); //放入缓存的对象
AgentServiceRepository agentServiceRes = MainContext.getContext().getBean(AgentServiceRepository.class);
Date now = new Date();
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
agentService = agentServiceRes.findByIdAndOrgi(agentUser.getAgentserviceid(), orgi);
agentService.setEndtime(now);
if (agentService.getServicetime() != null) {
agentService.setSessiontimes(System.currentTimeMillis() - agentService.getServicetime().getTime());
}
agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString());
} else {
agentService.setServicetime(now);
agentService.setLogindate(now);
agentService.setOrgi(orgi);
agentService.setOwner(agentUser.getContextid());
agentService.setSessionid(agentUser.getSessionid());
agentService.setRegion(agentUser.getRegion());
agentService.setUsername(agentUser.getUsername());
agentService.setChannel(agentUser.getChannel());
if (botName != null)
agentService.setAgentusername(botName);
if (StringUtils.isNotBlank(agentUser.getContextid())) {
agentService.setContextid(agentUser.getContextid());
} else {
agentService.setContextid(agentUser.getSessionid());
}
agentService.setUserid(agentUser.getUserid());
agentService.setAiid(agentUser.getAgentno());
agentService.setAiservice(true);
agentService.setStatus(MainContext.AgentUserStatusEnum.INSERVICE.toString());
agentService.setAppid(agentUser.getAppid());
agentService.setLeavemsg(false);
}
agentServiceRes.save(agentService);
return agentService;
}
/**
* 为访客 分配坐席 ACD策略此处 AgentStatus 是建议 坐席 如果启用了 历史服务坐席 优先策略 则会默认检查历史坐席是否空闲如果空闲则分配如果不空闲 分配当前建议的坐席
*
* @param agentStatus
* @param agentUser
* @param orgi
* @return
* @throws Exception
*/
private static AgentService processAgentService(AgentStatus agentStatus, AgentUser agentUser, String orgi, boolean finished) throws Exception {
AgentService agentService = new AgentService(); //放入缓存的对象
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
agentService.setId(agentUser.getAgentserviceid());
}
agentService.setOrgi(orgi);
MainUtils.copyProperties(agentUser, agentService); //复制属性
agentService.setChannel(agentUser.getChannel());
agentService.setSessionid(agentUser.getSessionid());
OnlineUserRepository onlineUserRes = MainContext.getContext().getBean(OnlineUserRepository.class);
agentUser.setLogindate(new Date());
List<OnlineUser> onlineUserList = onlineUserRes.findByUseridAndOrgi(agentUser.getUserid(), agentUser.getOrgi());
OnlineUser onlineUser = null;
if (onlineUserList.size() > 0) {
onlineUser = onlineUserList.get(0);
}
if (agentStatus != null) {
SessionConfig sessionConfig = initSessionConfig(orgi);
agentService.setAgent(agentStatus.getAgentno());
agentService.setSkill(agentUser.getSkill());
if (sessionConfig.isLastagent()) { //启用了历史坐席优先 查找 历史服务坐席
List<WebIMReport> webIMaggList = MainUtils.getWebIMDataAgg(onlineUserRes.findByOrgiForDistinctAgent(orgi, agentUser.getUserid()));
if (webIMaggList.size() > 0) {
for (WebIMReport report : webIMaggList) {
if (report.getData().equals(agentStatus.getAgentno())) {
break;
} else {
AgentStatus hisAgentStatus = (AgentStatus) CacheHelper.getAgentStatusCacheBean().getCacheObject(report.getData(), orgi);
if (hisAgentStatus != null && hisAgentStatus.getUsers() < hisAgentStatus.getMaxusers()) {
agentStatus = hisAgentStatus; //变更为 历史服务坐席
break;
}
}
}
}
}
agentUser.setStatus(MainContext.AgentUserStatusEnum.INSERVICE.toString());
agentService.setStatus(MainContext.AgentUserStatusEnum.INSERVICE.toString());
agentService.setSessiontype(MainContext.AgentUserStatusEnum.INSERVICE.toString());
agentService.setAgentno(agentStatus.getUserid());
agentService.setAgentusername(agentStatus.getUsername()); //agent
} else {
if (finished == true) {
agentUser.setStatus(MainContext.AgentUserStatusEnum.END.toString());
agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString());
agentService.setSessiontype(MainContext.AgentUserStatusEnum.END.toString());
if (agentStatus == null) {
agentService.setLeavemsg(true); //是留言
agentService.setLeavemsgstatus(MainContext.LeaveMsgStatus.NOTPROCESS.toString()); //未处理的留言
}
} else {
agentUser.setStatus(MainContext.AgentUserStatusEnum.INQUENE.toString());
agentService.setStatus(MainContext.AgentUserStatusEnum.INQUENE.toString());
agentService.setSessiontype(MainContext.AgentUserStatusEnum.INQUENE.toString());
}
}
if (finished || agentStatus != null) {
// agentService.setId(null);
agentService.setAgentuserid(agentUser.getId());
agentService.setInitiator(MainContext.ChatInitiatorType.USER.toString());
long waittingtime = 0;
if (agentUser.getWaittingtimestart() != null) {
waittingtime = System.currentTimeMillis() - agentUser.getWaittingtimestart().getTime();
} else if (agentUser.getCreatetime() != null) {
waittingtime = System.currentTimeMillis() - agentUser.getCreatetime().getTime();
}
agentUser.setWaittingtime((int) waittingtime);
agentUser.setServicetime(new Date());
agentService.setOwner(agentUser.getOwner());
agentService.setTimes(0);
agentUser.setAgentno(agentService.getAgentno());
AgentServiceRepository agentServiceRes = MainContext.getContext().getBean(AgentServiceRepository.class);
if (StringUtils.isNotBlank(agentUser.getName())) {
agentService.setName(agentUser.getName());
}
if (StringUtils.isNotBlank(agentUser.getPhone())) {
agentService.setPhone(agentUser.getPhone());
}
if (StringUtils.isNotBlank(agentUser.getEmail())) {
agentService.setEmail(agentUser.getEmail());
}
if (StringUtils.isNotBlank(agentUser.getResion())) {
agentService.setResion(agentUser.getResion());
}
if (StringUtils.isNotBlank(agentUser.getSkill())) {
agentService.setAgentskill(agentUser.getSkill());
} else if (agentStatus != null) {
agentService.setAgentskill(agentStatus.getSkill());
}
agentService.setServicetime(new Date());
if (agentUser.getCreatetime() != null) {
agentService.setWaittingtime((int) (System.currentTimeMillis() - agentUser.getCreatetime().getTime()));
agentUser.setWaittingtime(agentService.getWaittingtime());
}
if (onlineUser != null) {
agentService.setOsname(onlineUser.getOpersystem());
agentService.setBrowser(onlineUser.getBrowser());
agentService.setDataid(onlineUser.getId()); //记录 onlineuser 的id
}
agentService.setLogindate(agentUser.getCreatetime());
agentServiceRes.save(agentService);
agentUser.setAgentserviceid(agentService.getId());
agentUser.setLastgetmessage(new Date());
agentUser.setLastmessage(new Date());
}
agentService.setDataid(agentUser.getId());
/**
* 分配成功以后 将用户 和坐席的对应关系放入到 缓存
*/
/**
* AgentUser 放入到 当前坐席的 服务队列
*/
AgentUserRepository agentUserRepository = MainContext.getContext().getBean(AgentUserRepository.class);
/**
* 更新OnlineUser对象变更为服务中不可邀请
*/
if (onlineUser != null) {
onlineUser.setInvitestatus(MainContext.OnlineUserInviteStatus.INSERV.toString());
onlineUserRes.save(onlineUser);
}
/**
*
*/
agentUserRepository.save(agentUser);
CacheHelper.getAgentUserCacheBean().put(agentUser.getUserid(), agentUser, MainContext.SYSTEM_ORGI);
if (agentStatus != null) {
updateAgentStatus(agentStatus, agentUser, orgi, true);
}
return agentService;
}
public static AgentUser deleteAgentUser(AgentUser agentUser, String orgi)
throws Exception {
if (agentUser != null) {
if (!MainContext.AgentUserStatusEnum.END.toString().equals(
agentUser.getStatus())) {
serviceFinish(agentUser, orgi);
}
if (StringUtils.isNotBlank(agentUser.getId())) {
AgentUserRepository agentUserRes = MainContext.getContext().getBean(AgentUserRepository.class);
agentUser = agentUserRes.findByIdAndOrgi(agentUser.getId(), orgi);
if (agentUser != null) {
agentUserRes.delete(agentUser);
}
}
}
return agentUser;
}
/**
* @param agentStatus
* @return
*/
public static String getSuccessMessage(AgentService agentService, String channel, String orgi) {
String queneTip = "<span id='agentno'>" + agentService.getAgentusername() + "</span>";
if (!MainContext.ChannelTypeEnum.WEBIM.toString().equals(channel)) {
queneTip = agentService.getAgentusername();
}
SessionConfig sessionConfig = initSessionConfig(orgi);
String successMsg = "坐席分配成功," + queneTip + "为您服务。";
if (StringUtils.isNotBlank(sessionConfig.getSuccessmsg())) {
successMsg = sessionConfig.getSuccessmsg().replaceAll("\\{agent\\}", queneTip);
}
return successMsg;
}
/**
* @param agentStatus
* @return
*/
public static String getServiceFinishMessage(String channel, String orgi) {
SessionConfig sessionConfig = initSessionConfig(orgi);
String queneTip = "坐席已断开和您的对话";
if (StringUtils.isNotBlank(sessionConfig.getFinessmsg())) {
queneTip = sessionConfig.getFinessmsg();
}
return queneTip;
}
public static String getNoAgentMessage(int queneIndex, String channel, String orgi) {
if (queneIndex < 0) {
queneIndex = 0;
}
String queneTip = "<span id='queneindex'>" + queneIndex + "</span>";
if (!MainContext.ChannelTypeEnum.WEBIM.toString().equals(channel)) {
queneTip = String.valueOf(queneIndex);
}
SessionConfig sessionConfig = initSessionConfig(orgi);
String noAgentTipMsg = "坐席全忙,已进入等待队列,您也可以在其他时间再来咨询。";
if (StringUtils.isNotBlank(sessionConfig.getNoagentmsg())) {
noAgentTipMsg = sessionConfig.getNoagentmsg().replaceAll("\\{num\\}", queneTip);
}
return noAgentTipMsg;
}
public static String getQueneMessage(int queneIndex, String channel, String orgi) {
String queneTip = "<span id='queneindex'>" + queneIndex + "</span>";
if (!MainContext.ChannelTypeEnum.WEBIM.toString().equals(channel)) {
queneTip = String.valueOf(queneIndex);
}
SessionConfig sessionConfig = initSessionConfig(orgi);
String agentBusyTipMsg = "正在排队,请稍候,在您之前,还有 " + queneTip + " 位等待用户。";
if (StringUtils.isNotBlank(sessionConfig.getAgentbusymsg())) {
agentBusyTipMsg = sessionConfig.getAgentbusymsg().replaceAll("\\{num\\}", queneTip);
}
return agentBusyTipMsg;
}
/**
* 坐席离线
*
* @param userid
* @param status
*/
public static void deleteAgentStatus(String userid, String orgi, boolean isAdmin) {
AgentStatusRepository agentStatusRes = MainContext.getContext().getBean(AgentStatusRepository.class);
List<AgentStatus> agentStatusList = agentStatusRes.findByAgentnoAndOrgi(userid, orgi);
for (AgentStatus agentStatus : agentStatusList) {
AutomaticServiceDist.recordAgentStatus(agentStatus.getAgentno(), agentStatus.getUsername(), agentStatus.getAgentno(), agentStatus.getSkill(), isAdmin, agentStatus.getAgentno(), agentStatus.isBusy() ? MainContext.AgentStatusEnum.BUSY.toString() : MainContext.AgentStatusEnum.NOTREADY.toString(), MainContext.AgentStatusEnum.NOTREADY.toString(), MainContext.AgentWorkType.MEIDIACHAT.toString(), agentStatus.getOrgi(), agentStatus.getUpdatetime());
agentStatusRes.delete(agentStatus);
}
CacheHelper.getAgentStatusCacheBean().delete(userid, orgi);
AutomaticServiceDist.publishMessage(orgi, "agent", "leave", userid);
}
}

View File

@ -0,0 +1,999 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.basic;
import com.chatopera.cc.util.Constants;
import com.chatopera.cc.util.DateConverter;
import com.chatopera.cc.app.basic.resource.ActivityResource;
import com.chatopera.cc.app.basic.resource.BatchResource;
import com.chatopera.cc.app.model.Log;
import org.apache.commons.beanutils.ConvertUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class MainContext {
public static final String USER_SESSION_NAME = "user";
public static final String GUEST_USER = "guest";
public static final String IM_USER_SESSION_NAME = "im_user";
public static final String UKEFU_SYSTEM_DIC = "com.dic.system.template";
public static final String UKEFU_SYSTEM_AUTH_DIC = "com.dic.auth.resource";
public static final String UKEFU_SYSTEM_AREA_DIC = "com.dic.address.area";
public static final String UKEFU_SYSTEM_ADPOS_DIC = "com.dic.adv.type";
public static final String UKEFU_SYSTEM_COMMENT_DIC = "com.dic.app.comment";
public static final String UKEFU_SYSTEM_COMMENT_ITEM_DIC = "com.dic.app.comment.item";
public static final String UKEFU_SYSTEM_DIS_AI = "ownerai";
public static final String UKEFU_SYSTEM_DIS_AGENT = "owneruser";
public static final String UKEFU_SYSTEM_ASSUSER = "assuser";
public static final String UKEFU_SYSTEM_DIS_ORGAN = "ownerdept";
public static final String UKEFU_SYSTEM_DIS_TIME = "distime";
public static final String UKEFU_SYSTEM_COOKIES_FLAG = "uk_flagid";
public static final String UKEFU_SYSTEM_NO_AI_CONFIG = "NOTEXIST";
public static final String UKEFU_SYSTEM_NO_DAT = "NOTEXIST";
public static final String SYSTEM_INDEX = "uckefu";
public static final String UKEFU_SYSTEM_SECFIELD = "ukefu_sec_field";
public static final String UKEFU_SYSTEM_CALLCENTER = "callcenter";
public static final String UKEFU_SYSTEM_WORKORDEREMAIL = "workordermail";
public static final String UKEFU_SYSTEM_SMSEMAIL = "callcenter";
public static final String UKEFU_SYSTEM_AI_INPUT = "inputparam";
public static final String UKEFU_SYSTEM_AI_OUTPUT = "outputparam";
public static final String UKEFU_SYSTEM_INFOACQ = "infoacq"; //数据采集模式
public static final String GUEST_USER_ID_CODE = "R3GUESTUSEKEY";
public static final String WORKORDERS_CLOSED_STATUS = "uckefu_workorders_closed";
public static final String SERVICE_QUENE_NULL_STR = "service_quene_null";
public static final String DEFAULT_TYPE = "default"; //默认分类代码
public static final String START = "start"; //流程默认的开始节点
public static final String CACHE_SKILL = "cache_skill_"; //技能组的缓存
public static final String CACHE_AGENT = "cache_agent_"; //坐席列表的缓存
public static final String CUBE_TITLE_MEASURE = "指标";
public static final String UKEFU_SYSTEM_AREA = "uckefu_system_area";
public static final String UKEFU_SYSTEM_ADV = "uckefu_system_adv"; //系统广告位
public static final int MAX_IMAGE_WIDTH = 460;
private static boolean imServerRunning = false; //IM服务状态
public static final int AGENT_STATUS_MAX_USER = 10; //每个坐席 最大接待的 咨询数量
public static final String SYSTEM_CACHE_SESSION_CONFIG = "session_config";
public static final String SYSTEM_CACHE_SESSION_CONFIG_LIST = "session_config_list";
public static final String SYSTEM_CACHE_AI_CONFIG = "ai_config";
public static final String SYSTEM_CACHE_CALLOUT_CONFIG = "callout_config";
public static String SYSTEM_ORGI = "cskefu";
public static final String USER_CURRENT_ORGI_SESSION = "current_orgi";
public static Map<String, Boolean> model = new HashMap<String, Boolean>();
public static Map<String, Class<?>> uKeFuResourceMap = new HashMap<String, Class<?>>();
private static int WebIMPort = 8081;
private static ApplicationContext applicationContext;
private static ElasticsearchTemplate templet;
public static BlockingQueue<Log> tempLogQueue = new LinkedBlockingQueue<Log>();
static {
ConvertUtils.register(new DateConverter(), java.util.Date.class);
model.put("report", true);
uKeFuResourceMap.put(TaskType.ACTIVE.toString(), ActivityResource.class);
uKeFuResourceMap.put(TaskType.BATCH.toString(), BatchResource.class);
}
public enum AskSectionType {
DEFAULT;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum ActivityExecType {
DEFAULT, RECOVERY;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum AgentWorkType {
MEIDIACHAT,
CALLCENTER;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum SystemMessageType {
EMAIL, SMS;
public String toString() {
return super.toString().toLowerCase();
}
}
/**
* 名单分配状态已分配|未分配
*
* @author iceworld
*/
public enum NamesDisStatusType {
NOT, DISAGENT, DISORGAN, DISAI;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum ProcessType {
WORKORDER;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum QuickTypeEnum {
PUB,
PRI;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum NamesProcessStatus {
DIS,
PREVIEW,
CALLING,
CALLED,
CALLFAILD;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum FormFilterTypeEnum {
BATCH,
BUSINESS;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum NameStatusTypeEnum {
CALLED, //已拨打
NOTCALL //未拨打
;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum NamesCalledEnum {
SUCCESS,//拨打成功
FAILD, //拨打失败
NOANSWER,//无人接听
EMPNO, //空号
ARREARS,//欠费
APPO, //预约拨打
INVALID;//无效名单
public String toString() {
return super.toString().toLowerCase();
}
}
public enum TagTypeEnum {
QUALITY;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum StatusTypeEnum {
INBOUND,
OUTBOUND;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum LogTypeEnum {
REQUEST,
CREATE,
READ,
UPDATE,
DELETE,
OTHER,
INFO,
WARN,
ERROR;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum SalesNamesStatus {
DIST, //已分配
NOTDIST; //未分配
public String toString() {
return super.toString().toLowerCase();
}
}
public enum LeaveMsgStatus {
PROCESSED, //已处理
NOTPROCESS; //未处理
public String toString() {
return super.toString().toLowerCase();
}
}
public enum AdPosEnum {
POINT,
IMAGE,
WELCOME,
INVITE;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum QualityType {
CHAT,
VOICE;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum QualityStatus {
NO, //未开启质检
DIS, //已分配
NODIS; //未分配
public String toString() {
return super.toString().toLowerCase();
}
}
public enum CallCenterCallTypeEnum {
INSIDELINE("内线", 1),
ORGCALLOUT("部门外呼", 2),
ORGCALLIN("部门呼入", 3),
INSIDEQUENE("内线排队", 4),
INSIDETRANS("内线转接", 5), //已分配
OUTSIDELINE("外线", 6),
OUTSIDEQUENE("外线排队", 7),
OUTSIDETRANS("外线转接", 8); //未分配
private final String name;
private final int index;
private CallCenterCallTypeEnum(final String name, final int index) {
this.name = name;
this.index = index;
}
public String toLetters() {
return super.toString().toLowerCase();
}
public String toString() {
return this.name;
}
}
// 服务处理类型
public enum OptTypeEnum {
CHATBOT("机器人客服", 1),
HUMAN("人工客服", 2);
private final String name;
private final int index;
private OptTypeEnum(String name, int index) {
this.name = name;
this.index = index;
}
public String toLetters() {
return super.toString().toLowerCase();
}
public String toString() {
return this.name;
}
}
// 外呼计划状态
public enum CallOutDialplanStatusEnum {
RUNNING("执行中", 1),
STOPPED("已停止", 2),
STARTING("开始执行", 3),
STOPPING("停止中", 4),
INITIALIZATION("初始化", 5);
private final String name;
private final int index;
private CallOutDialplanStatusEnum(String name, int index) {
this.name = name;
this.index = index;
}
public String toLetters() {
return super.toString().toLowerCase();
}
public String toString() {
return this.name;
}
}
public enum CallWireEventType {
DIALPLAN_CONN("自动外呼接通", 1),
DIALPLAN_DISC("自动外呼挂断", 2),
DIALPLAN_FAIL("自动外呼失败", 3),
MANUDIAL_CONN("手动外呼接通", 4),
MANUDIAL_DISC("手动外呼挂断", 5),
MANUDIAL_FAIL("手动外呼失败", 6),
CALLIN_CONN("呼入接通", 7),
CALLIN_DIST("呼入挂断", 8),
CALLIN_FAIL("呼入失败", 9);
private String name;
private int index;
private CallWireEventType(final String name, final int index) {
this.name = name;
this.index = index;
}
public String toLetters() {
return super.toString().toLowerCase();
}
public String toString() {
return this.name;
}
public int getIndex() {
return this.index;
}
}
public enum CallServiceStatus {
INQUENE("就绪", 1),
RING("振铃", 2), //振铃
INCALL("应答", 3), //应答
BRIDGE("桥接", 4), //桥接
HOLD("已挂起", 5), //已挂起
HANGUP("已挂机", 6), //已挂机
OFFLINE("离线", 7); //离线
private final String name;
private final int index;
private CallServiceStatus(final String name, final int index) {
this.name = name;
this.index = index;
}
public String toLetters() {
return super.toString().toLowerCase();
}
public String toString() {
return this.name;
}
}
public enum CallChannelStatus {
EARLY,
DOWN;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum WxMpFileType {
JPG,
PNG;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum AgentInterType {
SKILL,
AGENT;
public String toString() {
return super.toString().toLowerCase();
}
}
/**
* 会话发起方
*
* @author iceworld
*/
public enum ChatInitiatorType {
AGENT,
USER;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum ExtentionType {
LINE,
IVR,
BUSINESS,
SKILL,
CONFERENCE,
QUENE;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum DTMFTypeEnum {
SATISF,
PASSWORD, //密码验证
IDCARD, //身份证号码
CARDNO; //银行卡号
public String toString() {
return super.toString().toLowerCase();
}
}
public enum ChatbotItemType {
USERINPUT,
BOTREPLY;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum MultiUpdateType {
SAVE,
UPDATE,
DELETE;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum ReportType {
REPORT;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum TaskType {
BATCH,
ACTIVE;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum WorkOrdersEventType {
ACCEPTUSER, //审批人变更
OTHER; //其他变更
public String toString() {
return super.toString().toLowerCase();
}
}
public enum BpmType {
WORKORDERS;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum AskOperatorType {
VIEWS,
COMMENTS,
UPS;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum ModelType {
USER,
WORKORDERS,
KBS,
SUMMARY,
CCSUMMARY, WEBIM, CALLOUT;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum AdapterType {
TEXT,
MEDIA,
AGENT,
XIAOE,
INTER;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum MetadataTableType {
UK_WORKORDERS;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum OnlineUserInviteStatus {
DEFAULT,
INVITE,
REFUSE,
INSERV,
ACCEPT;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum CustomerTypeEnum {
ENTERPRISE,
PERSONAL;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum WeiXinEventTypeEnum {
SUB,
UNSUB;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum ChannelTypeEnum {
WEIXIN,
WEIBO,
WEBIM,
PHONE,
EMAIL, AI;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum EventTypeEnum {
SUB,
UNSUB,
MENU;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum AgentStatusEnum {
READY("就绪", 1),
NOTREADY("未就绪", 2),
BUSY("置忙", 3),
NOTBUSY("不忙", 4),
IDLE("空闲", 5),
OFFLINE("离线", 6),
SERVICES("服务", 7);
private String name;
private int index;
private AgentStatusEnum(final String name, final int index) {
this.name = name;
this.index = index;
}
public String zh() {
return this.name;
}
public String toString() {
return super.toString().toLowerCase();
}
}
public enum WorkStatusEnum {
IDLE,
WAITTING,
CALLOUT,
PREVIEW,
OUTBOUNDCALL;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum TaskStatusType {
NORMAL("0"),
READ("1"),
QUEUE("5"),
RUNNING("2"),
END("3");
private String type;
TaskStatusType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String toString() {
return super.toString().toLowerCase();
}
}
public enum NameSpaceEnum {
IM("/im/user"),
AGENT("/im/agent"),
ENTIM("/im/ent"),
CHATBOT("/im/chatbot"),
CALLCENTER("/callcenter/exchange"),
CALLOUT("/callout/exchange");
private String namespace;
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
NameSpaceEnum(String namespace) {
this.namespace = namespace;
}
public String toString() {
return super.toString().toLowerCase();
}
}
public enum MessageTypeEnum {
NEW,
MESSAGE,
END,
TRANS, STATUS, AGENTSTATUS, SERVICE, WRITING;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum CallCenterResultStatusEnum {
OK;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum MediaTypeEnum {
TEXT,
EVENT,
IMAGE,
VIDIO,
VOICE, LOCATION, FILE, COOPERATION, ACTION;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum CallTypeEnum {
IN("呼入", 1),
OUT("呼出", 2),
SYSTEM("系统", 3),
INVITE("邀请", 4);
private final String name;
private final int index;
private CallTypeEnum(final String name, final int index) {
this.name = name;
this.index = index;
}
public String toLetters() {
return super.toString().toLowerCase();
}
public String toString() {
return this.name;
}
}
public enum OnlineUserOperatorStatus {
ONLINE,
OFFLINE,
REONLINE,
CHAT,
RECHAT,
BYE,
SEARCH,
ACCESS;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum OnlineUserTypeStatus {
USER,
WEBIM,
WEIXIN,
APP,
TELECOM,
OTHER,
WEIBO;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum AgentUserStatusEnum {
INSERVICE,
INQUENE, END;
public String toString() {
return super.toString().toLowerCase();
}
}
public static void setApplicationContext(ApplicationContext context) {
applicationContext = context;
}
public static ApplicationContext getContext() {
return applicationContext;
}
public static ElasticsearchTemplate getTemplet() {
return templet;
}
public static void setTemplet(ElasticsearchTemplate templet) {
MainContext.templet = templet;
}
public static int getWebIMPort() {
return WebIMPort;
}
public static void setWebIMPort(int webIMPort) {
WebIMPort = webIMPort;
}
/**
* 系统级的加密密码 从CA获取
*
* @return
*/
public static String getSystemSecrityPassword() {
return "UCKeFu";
}
public static void setIMServerStatus(boolean running) {
imServerRunning = running;
}
public static boolean getIMServerStatus() {
return imServerRunning;
}
public enum FilterConType {
DIMENSION,
MEASURE;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum FilterCompType {
NOT,
EQUAL;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum FilterValuefilterType {
COMPARE,
RANGE;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum FilterConValueType {
INPUT,
AUTO,
USERDEF;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum FilterModelType {
TEXT,
DATE,
SIGSEL,
SELECT;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum FilteFunType {
FILTER,
RANK;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum CallType {
AI,
AGENT;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum ChatbotType {
SMARTAI,
BUSINESSAI;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum ChatbotBussType {
SALE,
QUESURVEY;
public String toString() {
return super.toString().toLowerCase();
}
}
public enum CallOutType {
AGENT,
AI;
public String toString() {
return super.toString().toLowerCase();
}
}
/**
* @param resource
* @return
*/
public static Class<?> getResource(String resource) {
return uKeFuResourceMap.get(resource);
}
/**
* 是否开启外呼模块
* @return
*/
public static boolean isEnableCalloutModule() {
return model.containsKey(Constants.CSKEFU_MODULE_CALLOUT) && (model.get(Constants.CSKEFU_MODULE_CALLOUT).equals(true));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.basic;
public class Viewport {
private String page ;
private String templet;
public Viewport(String templet , String page){
this.templet = templet ;
this.page = page ;
}
public Viewport(String page){
this.page = page ;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
public String getTemplet() {
return templet;
}
public void setTemplet(String templet) {
this.templet = templet;
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.basic.aop;
import java.util.List;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.basic.MainUtils;
import com.chatopera.cc.util.UKeFuList;
import com.chatopera.cc.concurrent.multiupdate.MultiUpdateEvent;
import com.chatopera.cc.app.persistence.hibernate.BaseService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.chatopera.cc.app.model.ESBean;
@Aspect
@Component
public class SyncDatabaseExt {
@Autowired
private BaseService<?> dbDataRes ;
/**
* 定义拦截规则拦截org.springframework.data.elasticsearch.repository
*/
@Pointcut("execution(* org.springframework.data.elasticsearch.repository.*.save(*))")
public void syncSaveEsData(){}
/**
* 定义拦截规则拦截org.springframework.data.elasticsearch.repository
*/
@Pointcut("execution(* org.springframework.data.elasticsearch.repository.*.delete(*))")
public void syncDeleteEsData(){}
@SuppressWarnings("unchecked")
@Around("syncSaveEsData()")
public Object syncSaveEsData(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs() ;
if(args.length == 1){
Object data = args[0] ;
if(data!=null){
if(data instanceof UKeFuList){
/**只有一个地方用到从DB同步数据到ES**/
}else if(data instanceof List){
List<Object> dataList = (List<Object>)data ;
for(Object dbData : dataList){
MainUtils.multiupdate(new MultiUpdateEvent<Object>(dbData , dbDataRes, MainContext.MultiUpdateType.SAVE.toString()));
}
}else{
MainUtils.multiupdate(new MultiUpdateEvent<Object>(data, dbDataRes, MainContext.MultiUpdateType.SAVE.toString()));
}
}
}
return pjp.proceed();
}
@SuppressWarnings("unchecked")
@Around("syncDeleteEsData()")
public Object syncDeleteEsData(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs() ;
if(args.length == 1){
Object data = args[0] ;
if(data instanceof List){
List<Object> dataList = (List<Object>)data ;
for(Object dbData : dataList){
MainUtils.multiupdate(new MultiUpdateEvent<Object>(dbData , dbDataRes, MainContext.MultiUpdateType.DELETE.toString()));
}
}else{
if(data instanceof ESBean){
MainUtils.multiupdate(new MultiUpdateEvent<Object>(data, dbDataRes, MainContext.MultiUpdateType.DELETE.toString()));
}else{
MainUtils.multiupdate(new MultiUpdateEvent<Object>(data, dbDataRes, MainContext.MultiUpdateType.DELETE.toString()));
}
}
}
return pjp.proceed();
}
}

View File

@ -0,0 +1,392 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.basic.resource;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.model.JobDetail;
import com.chatopera.cc.app.basic.MainUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.PageImpl;
import com.chatopera.cc.util.es.SearchTools;
import com.chatopera.cc.util.es.UKDataBean;
import com.chatopera.cc.app.persistence.impl.BatchDataProcess;
import com.chatopera.cc.app.persistence.impl.ESDataExchangeImpl;
import com.chatopera.cc.app.persistence.repository.CallAgentRepository;
import com.chatopera.cc.app.persistence.repository.UKefuCallOutFilterRepository;
import com.chatopera.cc.app.persistence.repository.UKefuCallOutTaskRepository;
import com.chatopera.cc.app.persistence.repository.FormFilterItemRepository;
import com.chatopera.cc.app.persistence.repository.FormFilterRepository;
import com.chatopera.cc.app.persistence.repository.JobDetailRepository;
import com.chatopera.cc.app.persistence.repository.MetadataRepository;
import com.chatopera.cc.app.model.CallAgent;
import com.chatopera.cc.app.model.UKefuCallOutFilter;
import com.chatopera.cc.app.model.UKefuCallOutTask;
import com.chatopera.cc.app.model.FormFilter;
import com.chatopera.cc.app.model.FormFilterItem;
import com.chatopera.cc.app.model.MetadataTable;
public class ActivityResource extends Resource{
private JobDetail jobDetail ;
private FormFilterRepository formFilterRes ;
private FormFilterItemRepository formFilterItemRes ;
private PageImpl<UKDataBean> dataList ;
private MetadataTable metadataTable ;
private FormFilter formFilter = null ;
private List<CallAgent> callAgentList ;
private CallAgent current ;
private UKefuCallOutTask task ;
private UKefuCallOutFilter filter ;
private UKefuCallOutTaskRepository callOutTaskRes ;
private UKefuCallOutFilterRepository callOutFilterRes ;
private JobDetailRepository batchRes;
private MetadataRepository metadataRes ;
private JobDetail batch ;
private AtomicInteger assignorganInt = new AtomicInteger() /***分配到坐席***/, assignInt = new AtomicInteger() /***分配到部门***/ , assignAiInt = new AtomicInteger() /***分配到AI***/ ,atomInt = new AtomicInteger() ;
private BatchDataProcess batchDataProcess ;
public ActivityResource(JobDetail jobDetail) {
this.jobDetail = jobDetail ;
this.formFilterRes = MainContext.getContext().getBean(FormFilterRepository.class) ;
this.formFilterItemRes = MainContext.getContext().getBean(FormFilterItemRepository.class) ;
this.callOutTaskRes = MainContext.getContext().getBean(UKefuCallOutTaskRepository.class);
this.callOutFilterRes = MainContext.getContext().getBean(UKefuCallOutFilterRepository.class);
this.batchRes = MainContext.getContext().getBean(JobDetailRepository.class);
this.metadataRes = MainContext.getContext().getBean(MetadataRepository.class);
this.batchDataProcess = new BatchDataProcess(null , MainContext.getContext().getBean(ESDataExchangeImpl.class)) ;
}
@Override
public void begin() throws Exception {
if(!StringUtils.isBlank(jobDetail.getFilterid())) {
formFilter = formFilterRes.findByIdAndOrgi(jobDetail.getFilterid(), this.jobDetail.getOrgi()) ;
List<FormFilterItem> formFilterList = formFilterItemRes.findByOrgiAndFormfilterid(this.jobDetail.getOrgi(), jobDetail.getFilterid()) ;
if(formFilter!=null && !StringUtils.isBlank(formFilter.getFiltertype())) {
if(formFilter.getFiltertype().equals(MainContext.FormFilterTypeEnum.BATCH.toString())) {
batch = batchRes.findByIdAndOrgi(formFilter.getBatid(), this.jobDetail.getOrgi()) ;
if(batch!=null && !StringUtils.isBlank(batch.getActid())) {
metadataTable = metadataRes.findByTablename(batch.getActid()) ;
}
}else { //业务表
if(!StringUtils.isBlank(formFilter.getTableid())) {
metadataTable = metadataRes.findById(formFilter.getTableid()) ;
}
}
}
if(metadataTable!=null) {
/**
* 只加载 未分配的有效名单数据
*/
if(isRecovery()) {
//回收数据 , 需要传入回收的目标 包括 批次ID任务ID筛选ID活动ID
dataList = SearchTools.recoversearch(this.jobDetail.getOrgi(), this.jobDetail.getExectype(), this.jobDetail.getExectarget() , metadataTable ,(int) Math.ceil(this.jobDetail.getStartindex()/50000), 50000) ;
}else {
dataList = SearchTools.dissearch(this.jobDetail.getOrgi(), formFilter, formFilterList , metadataTable ,(int) Math.ceil(this.jobDetail.getStartindex()/50000), 50000) ;
}
}
this.callAgentList = MainContext.getContext().getBean(CallAgentRepository.class).findByActidAndOrgi(this.jobDetail.getId() , this.jobDetail.getOrgi()) ;
/**
* 生成 活动任务 然后完成分配 , 同时还需要生成 筛选表单的筛选记录 在后台管理界面上可以看到
*/
if(this.callAgentList!=null && this.callAgentList.size() > 0) {
this.current = this.callAgentList.remove(0) ;
}
this.jobDetail.setExecnum(this.jobDetail.getExecnum() + 1);
if(this.isRecovery() && !StringUtils.isBlank(this.jobDetail.getExectype()) && (this.jobDetail.getExectype().equals("filterid") || this.jobDetail.getExectype().equals("filterskill") || this.jobDetail.getExectype().equals("taskskill") || this.jobDetail.getExectype().equals("taskid"))) {
if(this.jobDetail.getExectype().equals("filterid") || this.jobDetail.getExectype().equals("filterskill")) {
this.filter = this.callOutFilterRes.findByIdAndOrgi(this.jobDetail.getExectarget(), this.jobDetail.getOrgi()) ;
}else if(this.jobDetail.getExectype().equals("taskid") || this.jobDetail.getExectype().equals("taskskill") ) {
this.task = this.callOutTaskRes.findByIdAndOrgi(this.jobDetail.getExectarget(), this.jobDetail.getOrgi()) ;
}
}else {
task = new UKefuCallOutTask() ;
task.setName(this.jobDetail.getName() + "_" + MainUtils.dateFormate.format(new Date()));
task.setBatid(formFilter.getBatid());
task.setOrgi(this.jobDetail.getOrgi());
if(this.isRecovery()) {
task.setExectype(MainContext.ActivityExecType.RECOVERY.toString());
}else {
task.setExectype(MainContext.ActivityExecType.DEFAULT.toString());
}
task.setFilterid(formFilter.getId());
task.setActid(this.jobDetail.getId());
task.setExecnum(this.jobDetail.getExecnum());
task.setOrgan(this.jobDetail.getOrgan());
task.setCreatetime(new Date());
if(this.dataList!=null) {
task.setNamenum((int) this.dataList.getTotalElements());
task.setNotassigned((int) this.dataList.getTotalElements());
}
this.callOutTaskRes.save(task) ;
filter = new UKefuCallOutFilter() ;
formFilter.setExecnum(formFilter.getExecnum() + 1);
MainUtils.copyProperties(task, filter);
filter.setName(this.formFilter.getName() + "_" + MainUtils.dateFormate.format(new Date()));
filter.setExecnum(formFilter.getExecnum());
this.callOutFilterRes.save(filter) ;
}
}
}
@Override
public void end(boolean clear) throws Exception {
if(this.atomInt.intValue() > 0) {
this.batchDataProcess.end();
}
//doNothing
/**
* FormFilter的执行信息更新执行次数
*/
if(formFilterRes!=null && this.formFilter != null) {
this.formFilter.setFilternum(this.formFilter.getFilternum()+1);
formFilterRes.save(this.formFilter) ;
}
/**
* 批次的信息更新批次剩余未分配的名单总数 已分配的名单总数
*/
if(this.batchRes!=null && this.batch != null) {
if(this.isRecovery()) {
batch.setAssigned(batch.getAssigned() - this.atomInt.intValue());
}else {
batch.setAssigned(batch.getAssigned() + this.atomInt.intValue());
}
batch.setNotassigned(batch.getNamenum() - batch.getAssigned());
this.batchRes.save(batch) ;
}
if(this.task!=null) {
if(this.isRecovery()) {
if(!StringUtils.isBlank(this.jobDetail.getExecto())) {
this.task.setReorgannum(this.atomInt.intValue());
}else {
this.task.setRenum(this.atomInt.intValue());
}
}else {
this.task.setAssigned(this.assignInt.intValue());
this.task.setAssignedorgan(this.assignorganInt.intValue());
this.task.setAssignedai(this.assignAiInt.intValue());
this.task.setNotassigned(this.task.getNamenum() - this.assignInt.intValue() - this.assignorganInt.intValue() - this.assignAiInt.intValue());
}
this.callOutTaskRes.save(this.task) ;
}
if(this.filter!=null) {
if(this.isRecovery()) {
if(!StringUtils.isBlank(this.jobDetail.getExecto())) {
this.filter.setReorgannum(this.atomInt.intValue());
}else {
this.filter.setRenum(this.atomInt.intValue());
}
}else {
this.filter.setAssigned(this.assignInt.intValue());
this.filter.setAssignedorgan(this.assignorganInt.intValue());
this.filter.setAssignedai(this.assignAiInt.intValue());
this.filter.setNotassigned(this.task.getNamenum() - this.assignInt.intValue() - this.assignorganInt.intValue() - this.assignAiInt.intValue());
}
this.callOutFilterRes.save(this.filter) ;
}
/**
* 更新任务状态记录生成的任务信息
*/
this.jobDetail.setExecmd(null);
this.jobDetail.setExectype(null);
this.jobDetail.setExectarget(null);
this.jobDetail.setExecto(null);
}
@Override
public JobDetail getJob() {
return this.jobDetail;
}
@Override
public void process(OutputTextFormat meta, JobDetail job) throws Exception {
/**
* 执行分配
*/
if(this.isRecovery()) {
if(!StringUtils.isBlank(this.jobDetail.getExecto())) {
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_AGENT, null) ;
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_AI, null) ;
// meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_ORGAN, this.jobDetail.getExecto()) ;
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_TIME, System.currentTimeMillis()) ;
meta.getDataBean().getValues().put("status", MainContext.NamesDisStatusType.DISORGAN.toString()) ;
}else {
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_AI, null) ;
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_AGENT, null) ;
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_ORGAN, null) ;
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_TIME, null) ;
meta.getDataBean().getValues().put("status", MainContext.NamesDisStatusType.NOT.toString()) ;
}
}else {
if(this.current!=null && meta!=null && meta.getDataBean()!=null) {
this.current.getDisnames().incrementAndGet() ;
/**
*
*/
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_TIME, System.currentTimeMillis()) ;
meta.getDataBean().getValues().put("actid", this.jobDetail.getId()) ;
meta.getDataBean().getValues().put("metaid", this.metadataTable.getTablename()) ;
meta.getDataBean().getValues().put("batid", this.formFilter.getBatid()) ;
meta.getDataBean().getValues().put("taskid", this.task.getId()) ;
meta.getDataBean().getValues().put("filterid", this.formFilter.getId()) ;
meta.getDataBean().getValues().put("calloutfilid", this.filter.getId()) ;
meta.getDataBean().getValues().put("taskid", this.task.getId()) ;
if(!StringUtils.isBlank(this.jobDetail.getUserid())){
meta.getDataBean().getValues().put("assuser", this.jobDetail.getUserid()) ;
}else{
meta.getDataBean().getValues().put("assuser", this.jobDetail.getCreater()) ;
}
/**
* 任务ID
*/
if("agent".equals(this.current.getDistype())) {
meta.getDataBean().getValues().put("status", MainContext.NamesDisStatusType.DISAGENT.toString()) ;
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_AGENT, this.current.getDistarget()) ;
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_ORGAN, this.current.getOrgan()) ;
this.assignInt.incrementAndGet() ;
}else if("skill".equals(this.current.getDistype())) {
meta.getDataBean().getValues().put("status", MainContext.NamesDisStatusType.DISORGAN.toString()) ;
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_ORGAN, this.current.getDistarget()) ;
this.assignorganInt.incrementAndGet() ;
}else if("ai".equals(this.current.getDistype())) {
meta.getDataBean().getValues().put("status", MainContext.NamesDisStatusType.DISAI.toString()) ;
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_AI, this.current.getDistarget()) ;
meta.getDataBean().getValues().put(MainContext.UKEFU_SYSTEM_DIS_ORGAN, this.current.getOrgan()) ;
this.assignAiInt.incrementAndGet() ;
}
}
}
meta.getDataBean().getValues().put("updatetime", System.currentTimeMillis()) ;
/**
* 更新记录是否同时保存分配信息以便于查看分配历史
*/
batchDataProcess.process(meta.getDataBean());
}
@Override
public OutputTextFormat next() throws Exception {
OutputTextFormat outputTextFormat = null;
if(this.dataList!=null && this.current!=null) {
synchronized (this.dataList) {
if(atomInt.intValue() < this.dataList.getContent().size()) {
if(this.isRecovery()) {
UKDataBean dataBean = this.dataList.getContent().get(atomInt.intValue()) ;
outputTextFormat = new OutputTextFormat(this.jobDetail);
if(this.formFilter!=null) {
outputTextFormat.setTitle(this.formFilter.getName());
}
outputTextFormat.setDataBean(dataBean);
atomInt.incrementAndGet() ;
}else if(this.dataList!=null) {
if(this.current.getDisnames().intValue() >= this.current.getDisnum() ) {
if(this.callAgentList.size() > 0) {
this.current = this.callAgentList.remove(0) ;
}else {
this.current = null ;
}
}
if(this.current != null) {
UKDataBean dataBean = this.dataList.getContent().get(atomInt.intValue()) ;
outputTextFormat = new OutputTextFormat(this.jobDetail);
if(this.formFilter!=null) {
outputTextFormat.setTitle(this.formFilter.getName());
}
outputTextFormat.setDataBean(dataBean);
atomInt.incrementAndGet() ;
/**
* 修改为平均分配的方式 每个坐席或者部门评价分配
*/
this.callAgentList.add(this.current) ;
if(this.callAgentList.size() > 0) {
this.current = this.callAgentList.remove(0) ;
}
}
}
}
}
}
return outputTextFormat;
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public OutputTextFormat getText(OutputTextFormat object) throws Exception {
return object;
}
@Override
public void rmResource() {
/**
* 啥也不做
*/
}
@Override
public void updateTask() throws Exception {
/**
* 更新任务状态记录生成的任务信息
*/
this.jobDetail.setExecmd(null);
this.jobDetail.setExectype(null);
this.jobDetail.setExectarget(null);
this.jobDetail.setExecto(null);
}
private boolean isRecovery() {
return !StringUtils.isBlank(this.jobDetail.getExecmd()) && this.jobDetail.getExecmd().equals("recovery") ;
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.basic.resource;
import java.io.File;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.persistence.impl.BatchDataProcess;
import com.chatopera.cc.app.model.JobDetail;
import com.chatopera.cc.app.basic.MainUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import com.chatopera.cc.concurrent.dsdata.DSData;
import com.chatopera.cc.concurrent.dsdata.DSDataEvent;
import com.chatopera.cc.concurrent.dsdata.ExcelImportProecess;
import com.chatopera.cc.app.persistence.impl.ESDataExchangeImpl;
import com.chatopera.cc.app.persistence.repository.MetadataRepository;
import com.chatopera.cc.app.persistence.repository.ReporterRepository;
import com.chatopera.cc.app.model.MetadataTable;
public class BatchResource extends Resource{
private JobDetail jobDetail ;
private MetadataTable metadataTable ;
private ESDataExchangeImpl esDataExchange = null ;
private MetadataRepository metadataRes ;
private ReporterRepository reporterRes ;
public BatchResource(JobDetail jobDetail) {
this.jobDetail = jobDetail ;
this.metadataRes = MainContext.getContext().getBean(MetadataRepository.class);
this.reporterRes = MainContext.getContext().getBean(ReporterRepository.class);
this.esDataExchange = MainContext.getContext().getBean(ESDataExchangeImpl.class);
}
@Override
public void begin() throws Exception {
if(!StringUtils.isBlank(jobDetail.getActid())) {
metadataTable = metadataRes.findByTablename(jobDetail.getActid()) ;
}
DSDataEvent event = new DSDataEvent();
String path = MainContext.getContext().getEnvironment().getProperty("web.upload-path") ;
File tempFile = null ;
if(metadataTable!=null && !StringUtils.isBlank(this.jobDetail.getBatchtype()) && this.jobDetail.getBatchtype().equals("plan")) {
if(!StringUtils.isBlank(this.jobDetail.getImptype())) {
if(this.jobDetail.getImptype().equals("local")) {
tempFile = new File(MainUtils.getTemplet(this.jobDetail.getImpurl(), new HashMap<String,Object>()));
}else if(this.jobDetail.getImptype().equals("remote")){
FileUtils.copyURLToFile(new URL(MainUtils.getTemplet(this.jobDetail.getImpurl(), new HashMap<String,Object>())), tempFile = File.createTempFile("UKeFu-CallOut-Temp", ".xls"));
}
}
if(tempFile.exists()) {
String fileName = "callout/batch/"+ MainUtils.getUUID() + tempFile.getName().substring(tempFile.getName().lastIndexOf(".")) ;
File excelFile = new File(path , fileName) ;
if(!excelFile.getParentFile().exists()){
excelFile.getParentFile().mkdirs() ;
}
event.setTablename(metadataTable.getTablename());
event.setDSData(new DSData(null ,excelFile , tempFile.getName(), null));
event.setOrgi(this.jobDetail.getOrgi());
event.getValues().put("creater", this.jobDetail.getCreater()) ;
FileUtils.copyFile(tempFile, new File(path , fileName));
event.getDSData().setTask(metadataTable);
event.getDSData().setProcess(new BatchDataProcess(metadataTable, esDataExchange));
event.setOrgi(this.jobDetail.getOrgi());
event.setBatid(this.jobDetail.getId());
event.getDSData().setJobDetail(this.jobDetail);
event.getDSData().getReport().setOrgi(this.jobDetail.getOrgi());
event.getDSData().getReport().setDataid(this.jobDetail.getId());
event.getDSData().getReport().setTitle(this.jobDetail.getName() + "_" + MainUtils.dateFormate.format(new Date()));
}else {
event.getDSData().getReport().setError(true);
if(tempFile!=null) {
event.getDSData().getReport().setErrormsg(tempFile.getAbsolutePath() + " Not Exist!");
}
}
reporterRes.save(event.getDSData().getReport()) ;
new ExcelImportProecess(event).process() ; //启动导入任务
}
}
@Override
public void end(boolean clear) throws Exception {
}
@Override
public JobDetail getJob() {
return this.jobDetail;
}
@Override
public void process(OutputTextFormat meta, JobDetail job) throws Exception {
}
@Override
public OutputTextFormat next() throws Exception {
return null ;
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public OutputTextFormat getText(OutputTextFormat object) throws Exception {
return object;
}
@Override
public void rmResource() {
/**
* 啥也不做
*/
}
@Override
public void updateTask() throws Exception {
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.basic.resource;
import java.util.HashMap;
import java.util.Map;
import com.chatopera.cc.util.es.UKDataBean;
import com.chatopera.cc.app.model.JobDetail;
public class OutputTextFormat {
private String id ;
private String title ;
private String parent ;
private Map<String , Object> data = new HashMap<String , Object>();
private JobDetail job ;
private UKDataBean dataBean ;
public OutputTextFormat(JobDetail job){
this.job = job ;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Map<String, Object> getData() {
return data;
}
public void setData(Map<String, Object> data) {
this.data = data;
}
public JobDetail getJob() {
return job;
}
public void setJob(JobDetail job) {
this.job = job;
}
public String getParent() {
return parent;
}
public void setParent(String parent) {
this.parent = parent;
}
public UKDataBean getDataBean() {
return dataBean;
}
public void setDataBean(UKDataBean dataBean) {
this.dataBean = dataBean;
}
}

View File

@ -0,0 +1,113 @@
/**
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* webapps/LICENSE-Rivulet
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.basic.resource;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Logger;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.model.JobDetail;
/**
* @author jaddy0302 Rivulet Resource.java 2010-3-6
*
*/
public abstract class Resource {
public static Logger log = Logger.getLogger(Resource.class.getName()) ;
public abstract void begin() throws Exception;
public abstract void end(boolean clear) throws Exception;
/**
* Re connection
*/
public abstract JobDetail getJob();
/**
* Re connection
*/
public abstract void process(OutputTextFormat meta , JobDetail job)throws Exception;
/**
* synchronized
* Single-mode single-threaded access to records under a record
*
* @return
*/
public abstract OutputTextFormat next() throws Exception;
/**
*
* @return
*/
public abstract boolean isAvailable() ;
/**
*
* @return
*/
public abstract OutputTextFormat getText(OutputTextFormat object) throws Exception;
/**
*
*/
public abstract void rmResource() ;
/**
*
*/
public abstract void updateTask()throws Exception ;
/**
*
* @param job
* @return
* @throws IllegalAccessException
* @throws InstantiationException
* @throws NoSuchMethodException
* @throws SecurityException
* @throws InvocationTargetException
* @throws IllegalArgumentException
*/
public static Resource getResource(JobDetail job)
throws Exception{
return job != null
&& MainContext.getResource(job.getTasktype()) != null ? (Resource) MainContext
.getResource(job.getTasktype()).getConstructor(
new Class[] { JobDetail.class }).newInstance(
new Object[] { job })
: null;
}
/**
* Filter
* @param file
* @param netFile
* @return
*/
public boolean val(String inputFile , String acceptDocType){
String file = inputFile!=null ? inputFile.toLowerCase() :null ;
return file!=null && acceptDocType!=null && ((acceptDocType.indexOf(file.substring(file.lastIndexOf(".")+1))>=0||acceptDocType.indexOf("all")>=0)) ;
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import com.hazelcast.com.eclipsesource.json.JsonObject;
public interface CacheBean {
/**
*
*/
public void put(String key , Object value , String orgi) ;
/**
*
*/
public void clear(String orgi);
public Object delete(String key , String orgi) ;
public void update(String key , String orgi , Object object) ;
/**
*
* @param key
* @param orgi
* @return
*/
public Object getCacheObject(String key, String orgi) ;
/**
*
* @param key
* @param orgi
* @return
*/
public Object getCacheObject(String key, String orgi,Object defaultValue) ;
/**
* 获取所有缓存对象
* @param orgi
* @return
*/
public Collection<?> getAllCacheObject(String orgi) ;
public CacheBean getCacheInstance(String cacheName);
public Object getCache();
public JsonObject getStatics();
public Lock getLock(String lock, String orgi);
public long getSize();
public long getAtomicLong(String cacheName) ;
public void setAtomicLong(String cacheName , long start) ; //初始化 发号器
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache;
import com.chatopera.cc.app.cache.hazelcast.HazlcastCacheHelper;
public class CacheHelper {
private static CacheHelper instance = new CacheHelper();
/**
* 获取缓存实例
*/
public static CacheHelper getInstance(){
return instance ;
}
private static CacheInstance cacheInstance = new HazlcastCacheHelper();
public static CacheBean getAgentStatusCacheBean() {
return cacheInstance!=null ? cacheInstance.getAgentStatusCacheBean() : null;
}
public static CacheBean getAgentUserCacheBean() {
return cacheInstance!=null ? cacheInstance.getAgentUserCacheBean() : null ;
}
public static CacheBean getOnlineUserCacheBean() {
return cacheInstance!=null ? cacheInstance.getOnlineCacheBean() : null;
}
public static CacheBean getSystemCacheBean() {
return cacheInstance!=null ? cacheInstance.getSystemCacheBean() : null ;
}
public static CacheBean getIMRCacheBean() {
return cacheInstance!=null ? cacheInstance.getIMRCacheBean() : null ;
}
public static CacheBean getCallCenterCacheBean() {
return cacheInstance!=null ? cacheInstance.getCallCenterCacheBean() : null ;
}
public static CacheBean getCallCenterAgentCacheBean() {
return cacheInstance!=null ? cacheInstance.getCallCenterAgentCacheBean() : null ;
}
public static CacheBean getApiUserCacheBean() {
return cacheInstance!=null ? cacheInstance.getApiUserCacheBean() : null ;
}
public static CacheBean getJobCacheBean() {
return cacheInstance!=null ? cacheInstance.getJobCacheBean(): null ;
}
public static CacheBean getCallOutCacheBean() {
return cacheInstance!=null ? cacheInstance.getCallOutCacheBean(): null ;
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache;
public interface CacheInstance {
/**
* 坐席状态
* @return
*/
public CacheBean getAgentStatusCacheBean() ;
/**
* 服务中用户
* @return
*/
public CacheBean getAgentUserCacheBean();
/**
* 在线用户
* @return
*/
public CacheBean getOnlineCacheBean();
/**
* 系统缓存
* @return
*/
public CacheBean getSystemCacheBean();
/**
* IMR指令
* @return
*/
public CacheBean getIMRCacheBean();
/**
* IMR指令
* @return
*/
public CacheBean getCallCenterCacheBean();
/**
* IMR指令
* @return
*/
public CacheBean getCallCenterAgentCacheBean();
/**
* IMR指令
* @return
*/
public CacheBean getApiUserCacheBean();
/**
* IMR指令
* @return
*/
public CacheBean getJobCacheBean();
/**
* 外呼
* @return
*/
public CacheBean getCallOutCacheBean();
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache.hazelcast;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.cache.CacheBean;
import com.chatopera.cc.app.cache.CacheInstance;
import com.chatopera.cc.app.cache.hazelcast.impl.AgentStatusCache;
import com.chatopera.cc.app.cache.hazelcast.impl.AgentUserCache;
import com.chatopera.cc.app.cache.hazelcast.impl.ApiUserCache;
import com.chatopera.cc.app.cache.hazelcast.impl.CallCenterCache;
import com.chatopera.cc.app.cache.hazelcast.impl.JobCache;
import com.chatopera.cc.app.cache.hazelcast.impl.MultiCache;
import com.chatopera.cc.app.cache.hazelcast.impl.OnlineCache;
import com.chatopera.cc.app.cache.hazelcast.impl.SystemCache;
/**
* Hazlcast缓存处理实例类
* @author admin
*
*/
public class HazlcastCacheHelper implements CacheInstance {
/**
* 服务类型枚举
* @author admin
*
*/
public enum CacheServiceEnum{
HAZLCAST_CLUSTER_AGENT_USER_CACHE, HAZLCAST_CLUSTER_AGENT_STATUS_CACHE, HAZLCAST_CLUSTER_QUENE_USER_CACHE,HAZLCAST_ONLINE_CACHE , HAZLCAST_CULUSTER_SYSTEM , HAZLCAST_IMR_CACHE , API_USER_CACHE , CALLCENTER_CURRENT_CALL ,CALLCENTER_AGENT,JOB_CACHE,HAZLCAST_CALLOUT_CACHE;
public String toString(){
return super.toString().toLowerCase();
}
}
@Override
public CacheBean getAgentStatusCacheBean() {
// TODO Auto-generated method stub
return MainContext.getContext().getBean(AgentStatusCache.class).getCacheInstance(CacheServiceEnum.HAZLCAST_CLUSTER_AGENT_STATUS_CACHE.toString()) ;
}
@Override
public CacheBean getAgentUserCacheBean() {
// TODO Auto-generated method stub
return MainContext.getContext().getBean(AgentUserCache.class).getCacheInstance(CacheServiceEnum.HAZLCAST_CLUSTER_QUENE_USER_CACHE.toString()) ;
}
@Override
public CacheBean getOnlineCacheBean() {
return MainContext.getContext().getBean(OnlineCache.class).getCacheInstance(CacheServiceEnum.HAZLCAST_ONLINE_CACHE.toString()) ;
}
@Override
public CacheBean getSystemCacheBean() {
return MainContext.getContext().getBean(SystemCache.class).getCacheInstance(CacheServiceEnum.HAZLCAST_CULUSTER_SYSTEM.toString()) ;
}
@Override
public CacheBean getIMRCacheBean() {
return MainContext.getContext().getBean(MultiCache.class).getCacheInstance(CacheServiceEnum.HAZLCAST_IMR_CACHE.toString()) ;
}
@Override
public CacheBean getCallCenterCacheBean() {
return MainContext.getContext().getBean(CallCenterCache.class).getCacheInstance(CacheServiceEnum.CALLCENTER_CURRENT_CALL.toString()) ;
}
@Override
public CacheBean getCallCenterAgentCacheBean() {
return MainContext.getContext().getBean(CallCenterCache.class).getCacheInstance(CacheServiceEnum.CALLCENTER_AGENT.toString()) ;
}
@Override
public CacheBean getApiUserCacheBean() {
return MainContext.getContext().getBean(ApiUserCache.class).getCacheInstance(CacheServiceEnum.API_USER_CACHE.toString()) ;
}
@Override
public CacheBean getJobCacheBean() {
return MainContext.getContext().getBean(JobCache.class).getCacheInstance(CacheServiceEnum.JOB_CACHE.toString()) ;
}
@Override
public CacheBean getCallOutCacheBean() {
// TODO Auto-generated method stub
return MainContext.getContext().getBean(JobCache.class).getCacheInstance(CacheServiceEnum.HAZLCAST_CALLOUT_CACHE.toString()) ;
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache.hazelcast.impl;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hazelcast.com.eclipsesource.json.JsonObject;
import com.hazelcast.core.HazelcastInstance;
import com.chatopera.cc.app.cache.CacheBean;
@Service("agentstatus_cache")
public class AgentStatusCache implements CacheBean{
@Autowired
public HazelcastInstance hazelcastInstance;
private String cacheName ;
public HazelcastInstance getInstance(){
return hazelcastInstance ;
}
public CacheBean getCacheInstance(String cacheName){
this.cacheName = cacheName ;
return this ;
}
@Override
public void put(String key, Object value, String orgi) {
getInstance().getMap(getName()).put(key, value) ;
}
@Override
public void clear(String orgi) {
getInstance().getMap(getName()).clear();
}
@Override
public Object delete(String key, String orgi) {
return getInstance().getMap(getName()).remove(key) ;
}
@Override
public void update(String key, String orgi, Object value) {
getInstance().getMap(getName()).put(key, value);
}
@Override
public Object getCacheObject(String key, String orgi) {
return getInstance().getMap(getName()).get(key);
}
public String getName() {
return cacheName ;
}
// @Override
public void service() throws Exception {
// TODO Auto-generated method stub
}
@Override
public Collection<?> getAllCacheObject(String orgi) {
return getInstance().getMap(getName()).keySet();
}
@Override
public Object getCacheObject(String key, String orgi, Object defaultValue) {
return getCacheObject(key, orgi);
}
@Override
public Object getCache() {
return getInstance().getMap(cacheName);
}
@Override
public Lock getLock(String lock , String orgi) {
// TODO Auto-generated method stub
return getInstance().getLock(lock);
}
@Override
public long getSize() {
return getInstance().getMap(getName()).size();
}
@Override
public long getAtomicLong(String cacheName) {
return getInstance().getAtomicLong(getName()).incrementAndGet();
}
@Override
public void setAtomicLong(String cacheName, long start) {
getInstance().getAtomicLong(getName()).set(start);
}
@Override
public JsonObject getStatics() {
// TODO Auto-generated method stub
return getInstance().getMap(getName()).getLocalMapStats().toJson();
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache.hazelcast.impl;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hazelcast.com.eclipsesource.json.JsonObject;
import com.hazelcast.core.HazelcastInstance;
import com.chatopera.cc.app.cache.CacheBean;
@Service("agentuser_cache")
public class AgentUserCache implements CacheBean{
@Autowired
public HazelcastInstance hazelcastInstance;
private String cacheName ;
public HazelcastInstance getInstance(){
return hazelcastInstance ;
}
public CacheBean getCacheInstance(String cacheName){
this.cacheName = cacheName ;
return this ;
}
@Override
public void put(String key, Object value, String orgi) {
getInstance().getMap(getName()).put(key, value) ;
}
@Override
public void clear(String orgi) {
getInstance().getMap(getName()).clear();
}
@Override
public Object delete(String key, String orgi) {
return getInstance().getMap(getName()).remove(key) ;
}
@Override
public void update(String key, String orgi, Object value) {
getInstance().getMap(getName()).put(key, value);
}
@Override
public Object getCacheObject(String key, String orgi) {
return getInstance().getMap(getName()).get(key);
}
public String getName() {
return cacheName ;
}
// @Override
public void service() throws Exception {
// TODO Auto-generated method stub
}
@Override
public Collection<?> getAllCacheObject(String orgi) {
return getInstance().getMap(getName()).keySet();
}
@Override
public Object getCacheObject(String key, String orgi, Object defaultValue) {
return getCacheObject(key, orgi);
}
@Override
public Object getCache() {
return getInstance().getMap(cacheName);
}
@Override
public Lock getLock(String lock , String orgi) {
// TODO Auto-generated method stub
return getInstance().getLock(lock);
}
@Override
public long getSize() {
return getInstance().getMap(getName()).size();
}
@Override
public long getAtomicLong(String cacheName) {
return getInstance().getAtomicLong(getName()).incrementAndGet();
}
@Override
public void setAtomicLong(String cacheName, long start) {
getInstance().getAtomicLong(getName()).set(start);
}
@Override
public JsonObject getStatics() {
// TODO Auto-generated method stub
return getInstance().getMap(getName()).getLocalMapStats().toJson();
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache.hazelcast.impl;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hazelcast.com.eclipsesource.json.JsonObject;
import com.hazelcast.core.HazelcastInstance;
import com.chatopera.cc.app.cache.CacheBean;
@Service("api_user_cache")
public class ApiUserCache implements CacheBean{
@Autowired
public HazelcastInstance hazelcastInstance;
private String cacheName ;
public HazelcastInstance getInstance(){
return hazelcastInstance ;
}
public CacheBean getCacheInstance(String cacheName){
this.cacheName = cacheName ;
return this ;
}
@Override
public void put(String key, Object value, String orgi) {
getInstance().getMap(getName()).put(key, value) ;
}
@Override
public void clear(String orgi) {
getInstance().getMap(getName()).clear();
}
@Override
public Object delete(String key, String orgi) {
return getInstance().getMap(getName()).remove(key) ;
}
@Override
public void update(String key, String orgi, Object value) {
getInstance().getMap(getName()).put(key, value);
}
@Override
public Object getCacheObject(String key, String orgi) {
return getInstance().getMap(getName()).get(key);
}
public String getName() {
return cacheName ;
}
// @Override
public void service() throws Exception {
// TODO Auto-generated method stub
}
@Override
public Collection<?> getAllCacheObject(String orgi) {
return getInstance().getMap(getName()).keySet();
}
@Override
public Object getCacheObject(String key, String orgi, Object defaultValue) {
return getCacheObject(key, orgi);
}
@Override
public Object getCache() {
return getInstance().getMap(cacheName);
}
@Override
public Lock getLock(String lock , String orgi) {
// TODO Auto-generated method stub
return getInstance().getLock(lock);
}
@Override
public long getSize() {
return getInstance().getMap(getName()).size();
}
@Override
public long getAtomicLong(String cacheName) {
return getInstance().getAtomicLong(getName()).incrementAndGet();
}
@Override
public void setAtomicLong(String cacheName, long start) {
getInstance().getAtomicLong(getName()).set(start);
}
@Override
public JsonObject getStatics() {
// TODO Auto-generated method stub
return getInstance().getMap(getName()).getLocalMapStats().toJson();
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache.hazelcast.impl;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hazelcast.com.eclipsesource.json.JsonObject;
import com.hazelcast.core.HazelcastInstance;
import com.chatopera.cc.app.cache.CacheBean;
@Service("callcenter_current_call")
public class CallCenterCache implements CacheBean{
@Autowired
public HazelcastInstance hazelcastInstance;
private String cacheName ;
public HazelcastInstance getInstance(){
return hazelcastInstance ;
}
public CacheBean getCacheInstance(String cacheName){
this.cacheName = cacheName ;
return this ;
}
@Override
public void put(String key, Object value, String orgi) {
getInstance().getMap(getName()).put(key, value) ;
}
@Override
public void clear(String orgi) {
getInstance().getMap(getName()).clear();
}
@Override
public Object delete(String key, String orgi) {
return getInstance().getMap(getName()).remove(key) ;
}
@Override
public void update(String key, String orgi, Object value) {
getInstance().getMap(getName()).put(key, value);
}
@Override
public Object getCacheObject(String key, String orgi) {
return getInstance().getMap(getName()).get(key);
}
public String getName() {
return cacheName ;
}
// @Override
public void service() throws Exception {
// TODO Auto-generated method stub
}
@Override
public Collection<?> getAllCacheObject(String orgi) {
return getInstance().getMap(getName()).keySet();
}
@Override
public Object getCacheObject(String key, String orgi, Object defaultValue) {
return getCacheObject(key, orgi);
}
@Override
public Object getCache() {
return getInstance().getMap(cacheName);
}
@Override
public Lock getLock(String lock , String orgi) {
// TODO Auto-generated method stub
return getInstance().getLock(lock);
}
@Override
public long getSize() {
return getInstance().getMap(getName()).size();
}
@Override
public long getAtomicLong(String cacheName) {
return getInstance().getAtomicLong(getName()).incrementAndGet();
}
@Override
public void setAtomicLong(String cacheName, long start) {
getInstance().getAtomicLong(getName()).set(start);
}
@Override
public JsonObject getStatics() {
// TODO Auto-generated method stub
return getInstance().getMap(getName()).getLocalMapStats().toJson();
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache.hazelcast.impl;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hazelcast.com.eclipsesource.json.JsonObject;
import com.hazelcast.core.HazelcastInstance;
import com.chatopera.cc.app.cache.CacheBean;
@Service("job_cache")
public class JobCache implements CacheBean{
@Autowired
public HazelcastInstance hazelcastInstance;
private String cacheName ;
public HazelcastInstance getInstance(){
return hazelcastInstance ;
}
public CacheBean getCacheInstance(String cacheName){
this.cacheName = cacheName ;
return this ;
}
@Override
public void put(String key, Object value, String orgi) {
getInstance().getMap(getName()).put(key, value) ;
}
@Override
public void clear(String orgi) {
getInstance().getMap(getName()).clear();
}
@Override
public Object delete(String key, String orgi) {
return getInstance().getMap(getName()).remove(key) ;
}
@Override
public void update(String key, String orgi, Object value) {
getInstance().getMap(getName()).put(key, value);
}
@Override
public Object getCacheObject(String key, String orgi) {
return getInstance().getMap(getName()).get(key);
}
public String getName() {
return cacheName ;
}
// @Override
public void service() throws Exception {
// TODO Auto-generated method stub
}
@Override
public Collection<?> getAllCacheObject(String orgi) {
return getInstance().getMap(getName()).keySet();
}
@Override
public Object getCacheObject(String key, String orgi, Object defaultValue) {
return getCacheObject(key, orgi);
}
@Override
public Object getCache() {
return getInstance().getMap(cacheName);
}
@Override
public Lock getLock(String lock , String orgi) {
// TODO Auto-generated method stub
return getInstance().getLock(lock);
}
@Override
public long getSize() {
return getInstance().getMap(getName()).size();
}
@Override
public long getAtomicLong(String cacheName) {
return getInstance().getAtomicLong(getName()).incrementAndGet();
}
@Override
public void setAtomicLong(String cacheName, long start) {
getInstance().getAtomicLong(getName()).set(start);
}
@Override
public JsonObject getStatics() {
// TODO Auto-generated method stub
return getInstance().getMap(getName()).getLocalMapStats().toJson();
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache.hazelcast.impl;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import com.chatopera.cc.app.cache.CacheBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hazelcast.com.eclipsesource.json.JsonObject;
import com.hazelcast.core.HazelcastInstance;
@Service("multi_cache")
public class MultiCache implements CacheBean {
@Autowired
public HazelcastInstance hazelcastInstance;
private String cacheName ;
public HazelcastInstance getInstance(){
return hazelcastInstance ;
}
public CacheBean getCacheInstance(String cacheName){
this.cacheName = cacheName ;
return this ;
}
@Override
public void put(String key, Object value, String orgi) {
getInstance().getMultiMap(getName()).put(key, value) ;
}
@Override
public void clear(String orgi) {
getInstance().getMultiMap(getName()).clear();
}
@Override
public Object delete(String key, String orgi) {
return getInstance().getMultiMap(getName()).remove(key) ;
}
@Override
public void update(String key, String orgi, Object value) {
getInstance().getMultiMap(getName()).put(key, value);
}
@Override
public Object getCacheObject(String key, String orgi) {
return getInstance().getMultiMap(getName()).get(key);
}
public String getName() {
return cacheName ;
}
// @Override
public void service() throws Exception {
// TODO Auto-generated method stub
}
@Override
public Collection<?> getAllCacheObject(String orgi) {
return getInstance().getMultiMap(getName()).keySet();
}
@Override
public Object getCacheObject(String key, String orgi, Object defaultValue) {
return getCacheObject(key, orgi);
}
@Override
public Object getCache() {
return getInstance().getMultiMap(cacheName);
}
@Override
public Lock getLock(String lock , String orgi) {
// TODO Auto-generated method stub
return getInstance().getLock(lock);
}
@Override
public long getSize() {
return getInstance().getMultiMap(getName()).size();
}
@Override
public long getAtomicLong(String cacheName) {
return getInstance().getAtomicLong(getName()).incrementAndGet();
}
@Override
public void setAtomicLong(String cacheName, long start) {
getInstance().getAtomicLong(getName()).set(start);
}
@Override
public JsonObject getStatics() {
// TODO Auto-generated method stub
return getInstance().getMultiMap(getName()).getLocalMultiMapStats().toJson();
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache.hazelcast.impl;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hazelcast.com.eclipsesource.json.JsonObject;
import com.hazelcast.core.HazelcastInstance;
import com.chatopera.cc.app.cache.CacheBean;
@Service("online_cache")
public class OnlineCache implements CacheBean{
@Autowired
public HazelcastInstance hazelcastInstance;
private String cacheName ;
public HazelcastInstance getInstance(){
return hazelcastInstance ;
}
public CacheBean getCacheInstance(String cacheName){
this.cacheName = cacheName ;
return this ;
}
@Override
public void put(String key, Object value, String orgi) {
getInstance().getMap(getName()).put(key, value) ;
}
@Override
public void clear(String orgi) {
getInstance().getMap(getName()).clear();
}
@Override
public Object delete(String key, String orgi) {
return getInstance().getMap(getName()).remove(key) ;
}
@Override
public void update(String key, String orgi, Object value) {
getInstance().getMap(getName()).put(key, value);
}
@Override
public Object getCacheObject(String key, String orgi) {
return getInstance().getMap(getName()).get(key);
}
public String getName() {
return cacheName ;
}
// @Override
public void service() throws Exception {
// TODO Auto-generated method stub
}
@Override
public Collection<?> getAllCacheObject(String orgi) {
return getInstance().getMap(getName()).keySet();
}
@Override
public Object getCacheObject(String key, String orgi, Object defaultValue) {
return getCacheObject(key, orgi);
}
@Override
public Object getCache() {
return getInstance().getMap(cacheName);
}
@Override
public Lock getLock(String lock , String orgi) {
// TODO Auto-generated method stub
return getInstance().getLock(lock);
}
@Override
public long getSize() {
return getInstance().getMap(getName()).size();
}
@Override
public long getAtomicLong(String cacheName) {
return getInstance().getAtomicLong(getName()).incrementAndGet();
}
@Override
public void setAtomicLong(String cacheName, long start) {
getInstance().getAtomicLong(getName()).set(start);
}
@Override
public JsonObject getStatics() {
// TODO Auto-generated method stub
return getInstance().getMap(getName()).getLocalMapStats().toJson();
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.cache.hazelcast.impl;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import com.chatopera.cc.app.cache.CacheBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hazelcast.com.eclipsesource.json.JsonObject;
import com.hazelcast.core.HazelcastInstance;
@Service("system_cache")
public class SystemCache implements CacheBean {
@Autowired
public HazelcastInstance hazelcastInstance;
private String cacheName ;
public HazelcastInstance getInstance(){
return hazelcastInstance ;
}
public CacheBean getCacheInstance(String cacheName){
this.cacheName = cacheName ;
return this ;
}
@Override
public void put(String key, Object value, String orgi) {
getInstance().getMap(getName()).put(key, value) ;
}
@Override
public void clear(String orgi) {
getInstance().getMap(getName()).clear();
}
@Override
public Object delete(String key, String orgi) {
return getInstance().getMap(getName()).remove(key) ;
}
@Override
public void update(String key, String orgi, Object value) {
getInstance().getMap(getName()).put(key, value);
}
@Override
public Object getCacheObject(String key, String orgi) {
return getInstance().getMap(getName()).get(key);
}
public String getName() {
return cacheName ;
}
// @Override
public void service() throws Exception {
// TODO Auto-generated method stub
}
@Override
public Collection<?> getAllCacheObject(String orgi) {
return getInstance().getMap(getName()).keySet();
}
@Override
public Object getCacheObject(String key, String orgi, Object defaultValue) {
return getCacheObject(key, orgi);
}
@Override
public Object getCache() {
return getInstance().getMap(cacheName);
}
@Override
public Lock getLock(String lock , String orgi) {
// TODO Auto-generated method stub
return getInstance().getLock(lock);
}
@Override
public long getSize() {
return getInstance().getMap(getName()).size();
}
@Override
public long getAtomicLong(String cacheName) {
return getInstance().getAtomicLong(getName()).incrementAndGet();
}
@Override
public void setAtomicLong(String cacheName, long start) {
getInstance().getAtomicLong(getName()).set(start);
}
@Override
public JsonObject getStatics() {
// TODO Auto-generated method stub
return getInstance().getMap(getName()).getLocalMapStats().toJson();
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class ApiConfigure {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("CSKefu")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.chatopera.cc.app.handler.api.rest"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("春松客服")
.description("春松客服 基于 JAVA 语言开发是一个全渠道融合的客户支持服务平台聚合企业内部多个客服渠道帮助各种行业各种规模的企业建立完整客服体系。通过将邮件、短信、电话语音、WebIM 在线客服、微信、微博、H5 页面、APP 接口等多个渠道来源的客户服务请求与对话汇聚在一个管理平台,用统一的方式来响应和支撑客户服务。")
.termsOfServiceUrl("http://docs.chatopera.com/")
.contact("春松客服")
.version("1.0.0")
.build();
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.config;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.chatopera.cc.app.cache.CacheHelper;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.util.matcher.RequestMatcher;
import com.chatopera.cc.app.basic.MainContext;
public class ApiRequestMatchingFilter implements Filter {
private RequestMatcher[] ignoredRequests;
public ApiRequestMatchingFilter(RequestMatcher... matcher) {
this.ignoredRequests = matcher;
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String method = request.getMethod() ;
if(!StringUtils.isBlank(method) && method.equalsIgnoreCase("options")){
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,accept,authorization,content-type");
response.setHeader("X-Frame-Options", "SAMEORIGIN");
response.setStatus(HttpStatus.ACCEPTED.value());
}else{
boolean matchAnyRoles = false ;
for(RequestMatcher anyRequest : ignoredRequests ){
if(anyRequest.matches(request)){
matchAnyRoles = true ;
}
}
if(matchAnyRoles){
String authorization = request.getHeader("authorization") ;
if(StringUtils.isBlank(authorization)){
authorization = request.getParameter("authorization") ;
}
if(!StringUtils.isBlank(authorization) && CacheHelper.getApiUserCacheBean().getCacheObject(authorization, MainContext.SYSTEM_ORGI) != null){
chain.doFilter(req,resp);
}else{
response.sendRedirect("/tokens/error");
}
}else{
chain.doFilter(req,resp);
}
}
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.config;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.model.Favorites;
import com.chatopera.cc.app.model.WorkOrders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.stereotype.Component;
@Component
public class ApplicationStartupListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired ElasticsearchTemplate elasticSearchTemplate;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!elasticSearchTemplate.indexExists(WorkOrders.class)) {
elasticSearchTemplate.createIndex(WorkOrders.class);
}
if (!elasticSearchTemplate.indexExists(Favorites.class)) {
elasticSearchTemplate.createIndex(Favorites.class);
}
try {
elasticSearchTemplate.getMapping(WorkOrders.class);
} catch (ElasticsearchException e) {
elasticSearchTemplate.putMapping(Favorites.class);
elasticSearchTemplate.putMapping(WorkOrders.class);
}
MainContext.setTemplet(elasticSearchTemplate);
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2017 优客服-多渠道客服系统
* Modifications copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chatopera.cc.app.config;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.chatopera.cc.app.basic.MainContext;
import com.chatopera.cc.app.model.User;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.security.web.util.matcher.RequestMatcher;
public class DelegateRequestMatchingFilter implements Filter {
private RequestMatcher[] ignoredRequests;
public DelegateRequestMatchingFilter(RequestMatcher... matcher) {
this.ignoredRequests = matcher;
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
boolean matchAnyRoles = false ;
for(RequestMatcher anyRequest : ignoredRequests ){
if(anyRequest.matches(request)){
matchAnyRoles = true ;
}
}
User user = (User) request.getSession().getAttribute(MainContext.USER_SESSION_NAME) ;
if(matchAnyRoles){
if(user !=null && "0".equals(user.getUsertype())){
chain.doFilter(req,resp);
}else{
//重定向到 无权限执行操作的页面
HttpServletResponse response = (HttpServletResponse) resp ;
response.sendRedirect("/?msg=security");
}
}else{
try{
chain.doFilter(req,resp);
}catch(ClientAbortException ex){
//Tomcat异常不做处理
}
}
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}

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