Compare commits

...
85 Commits
Author SHA1 Message Date
heavyrian2012 6e1ba9440e 更新版本号和release note 2023-02-15 15:25:33 +08:00
heavyrian2012 c284272b06 文件存储路径中加上分钟和秒 2023-02-15 15:20:57 +08:00
heavyrian2012 6746a54659 优化内置存储功能 2023-02-14 21:35:58 +08:00
heavyrian2012 93be667e17 日志文件精确到分钟,避免出现问题时被复写 2023-02-14 08:45:34 +08:00
heavyrian2012 f11da6225d 添加修改频道菜单接口 2023-02-12 11:06:35 +08:00
heavyrian2012 e28a4c6cbf server api发送消息检查对象是否存在 2023-02-11 13:51:05 +08:00
heavyrian2012 fa8a2bef32 channel api添加检查是否是订阅者的接口 2023-02-08 11:13:32 +08:00
heavyrian2012 1c9d238734 update readme 2023-01-19 16:14:17 +08:00
heavyrian2012 2f7f4e6456 update readme 2023-01-10 11:57:35 +08:00
heavyrian2012 6e83ec85c7 server sdk http request add log 2023-01-04 09:20:12 +08:00
heavyrian2012 cc561b846e 更新版本号和release note 2022-12-28 08:49:40 +08:00
heavyrian2012 4628b3b0b6 sdk add conference rtp forward api 2022-12-22 11:53:38 +08:00
heavyrian2012 9c69fbc7d4 sdk add conference rtp forward api 2022-12-13 17:38:21 +08:00
heavyrian2012 9047020d69 添加停止命令 2022-12-12 12:37:31 +08:00
heavyrian2012 1c18d862a1 会议离开加上原因 2022-12-07 16:25:18 +08:00
heavyrian2012 b0ddca985a route时可能不能携带正确的推送类型 2022-12-06 14:35:16 +08:00
heavyrian2012 996e06c451 添加禁止客户端群操作配置 2022-11-26 22:21:43 +08:00
imndx 612747203a 创建群组时,支持组织群 2022-11-25 10:48:29 +08:00
heavyrian2012 f7853ae299 解决短连接端口被扫描时打印出异常日志的问题 2022-11-24 19:06:27 +08:00
heavyrian2012 7443db3078 检查工具添加help命令 2022-11-22 10:12:09 +08:00
heavyrian2012 ad5f6cb228 添加注释 2022-11-21 08:34:08 +08:00
heavyrian2012 1486b19ce1 更新版本和release note 2022-11-19 09:44:09 +08:00
heavyrian2012 3e1095f745 移动release note文件的位置 2022-11-19 09:38:26 +08:00
heavyrian2012 17039bd3c1 http请求错误时不打印数据内容,防止内容泄露 2022-11-19 09:31:22 +08:00
heavyrian2012 b5d8feab62 客户端连接时检查是否跟获取token的平台一致 2022-11-18 22:10:53 +08:00
heavyrian2012 95b08ff7a3 Revert "mqtt proto version error"
This reverts commit ab8ae9d0c9.
2022-11-18 21:55:49 +08:00
heavyrian2012 ab8ae9d0c9 mqtt proto version error 2022-11-18 21:27:18 +08:00
heavyrian2012 b4d163e144 添加android客户端签名验证功能 2022-11-18 16:01:17 +08:00
heavyrian2012 3ccfd9a29b 解决user setting并发问题,fix #566 2022-11-15 17:12:51 +08:00
heavyrian2012 90504d1305 打包dockerfile 2022-11-14 15:47:42 +08:00
heavyrian2012 88199189a2 当禁止陌生人聊天时,添加例外允许用户id,用于比如管理员、文件助手等场景 2022-11-14 15:23:03 +08:00
heavyrian2012 0b6612eacc 内置对象存储支持绝对路径和相对路径 2022-11-14 15:13:06 +08:00
heavyrian2012 d706f545d2 解决启动脚本中的小错误 2022-11-14 15:04:51 +08:00
heavyrian2012 ae8d63b162 update docker readme 2022-11-14 14:42:04 +08:00
heavyrian2012 77e742e812 添加server sdk检查server api的工具 2022-11-12 19:25:24 +08:00
heavyrian2012 b2ee8b8d94 linux安装包创建日志目录 2022-11-12 19:24:57 +08:00
heavyrian2012 7080d85605 只有server api才能创建组织群 2022-11-12 15:01:57 +08:00
heavyrian2012 1275b2109f fix build error 2022-11-12 14:18:26 +08:00
heavyrian2012 b2eab28cb9 组织群客户端不能自主退出 2022-11-12 14:17:15 +08:00
heavyrian2012 67234ceaa6 添加新的群组类型:组织群 2022-11-12 14:00:24 +08:00
heavyrian2012 45f9e20e97 支持转发消息include/exclude类型,支持配置消息区间 2022-11-12 12:51:25 +08:00
heavyrian2012 6b7edc713a API获取用户信息时,过滤掉被删除的用户 2022-11-09 10:36:52 +08:00
heavyrian2012 1ef8f94d67 忽略掉无效的请求和日志 2022-11-05 17:05:31 +08:00
heavyrian2012 9b0b644cd1 update version and release note 2022-11-01 08:47:34 +08:00
heavyrian2012 b233c49d73 解决字符编码错误问题 2022-11-01 08:40:07 +08:00
heavyrian2012 b8d8d2f8ed 群组成员表添加成员索引和获取2个人共同群组的接口 2022-10-28 21:56:20 +08:00
heavyrian2012 731020ba0f update readme 2022-10-27 08:40:47 +08:00
heavyrian2012 4c00955639 更新readme 2022-10-27 08:33:09 +08:00
heavyrian2012 cbc150905f update screenshots 2022-10-26 10:32:48 +08:00
heavyrian2012 a71b8cef73 add more screenshots 2022-10-26 10:27:03 +08:00
heavyrian2012 66c67fa8d1 pc 截图大小 2022-10-26 09:29:36 +08:00
heavyrian2012 3ca7c70faa add screenshots 2022-10-26 09:26:43 +08:00
heavyrian2012 d4c6b1f616 update release note 2022-10-25 19:28:42 +08:00
imndx ae3eb9c2df linux 多端登录处理错误 2022-10-25 15:03:12 +08:00
heavyrian2012 771a4d751f version 0.97 and release note 2022-10-24 10:54:11 +08:00
heavyrian2012 2bed06cf43 更新升级说明 2022-10-24 10:49:30 +08:00
heavyrian2012 50f0330884 解决版本信息错误问题 2022-10-24 10:25:01 +08:00
heavyrian2012 acd21faa9c 优化野火IM服务安装目录 2022-10-24 09:53:44 +08:00
heavyrian2012 6e570c74ae 更新docker说明 2022-10-23 20:55:30 +08:00
heavyrian2012 893b58cef4 update dockerfile 2022-10-23 20:26:09 +08:00
heavyrian2012 bd2b037edb 删除旧的好友欢迎语的多语言处理方式 2022-10-23 19:22:54 +08:00
heavyrian2012 6acb1c968a 添加h2db路径的配置 2022-10-23 09:48:13 +08:00
heavyrian2012 6048748300 更新readme 2022-10-22 11:07:58 +08:00
heavyrian2012 9be49c3d41 update readme 2022-10-22 09:47:00 +08:00
heavyrian2012 d9eccd39de update readme 2022-10-22 08:52:35 +08:00
heavyrian2012 ffaffc8fc1 update systemctl service file 2022-10-21 19:46:10 +08:00
heavyrian2012 5e1f4fe6c9 update systectl service readme 2022-10-21 19:36:43 +08:00
heavyrian2012 b5d884f9f6 优化deb和rpm包 2022-10-21 19:12:11 +08:00
heavyrian2012 c0840f3a73 添加RPM打包方式 2022-10-21 16:38:58 +08:00
heavyrian2012 34bd59f195 打包输出deb包 2022-10-21 11:07:30 +08:00
heavyrian2012 8e1d56b2ca 添加service运行方式 2022-10-21 09:31:39 +08:00
heavyrian2012 a4eb64edd5 添加配置注释说明 2022-10-20 15:47:59 +08:00
heavyrian2012 b141990661 应用平台的部分错误日志改成error级别 2022-10-20 15:44:47 +08:00
imndx 0f9df0659f 名片消息推送 2022-10-18 22:07:19 +08:00
heavyrian2012 a2d4bf7d43 http回调失败用error打印日志 2022-10-12 17:08:35 +08:00
heavyrain.lee 898c4f2419 Merge pull request #379 from wildfirechat/dependabot/maven/broker/org.apache.httpcomponents-httpclient-4.5.13
Bump httpclient from 4.4.1 to 4.5.13 in /broker
2022-10-12 15:54:59 +08:00
heavyrain.lee e1a8a040df Merge pull request #381 from wildfirechat/dependabot/maven/sdk/org.apache.httpcomponents-httpclient-4.5.13
Bump httpclient from 4.5.3 to 4.5.13 in /sdk
2022-10-12 15:54:14 +08:00
heavyrain.lee adb4f082f1 Merge pull request #364 from wildfirechat/dependabot/maven/sdk/commons-io-commons-io-2.7
Bump commons-io from 2.5 to 2.7 in /sdk
2022-10-12 15:52:40 +08:00
heavyrain.lee 2724c79a79 Merge pull request #363 from wildfirechat/dependabot/maven/broker/commons-io-commons-io-2.7
Bump commons-io from 2.5 to 2.7 in /broker
2022-10-12 15:52:12 +08:00
heavyrain.lee ba01c90171 Merge pull request #544 from ren-jq101/oscs_fix_cd3626oau51of7vbl0vg
fix(sec): upgrade commons-codec:commons-codec to 1.13
2022-10-12 15:49:19 +08:00
ren-jq101 b8967ccaff update commons-codec:commons-codec 1.10 to 1.13 2022-10-12 14:39:29 +08:00
dependabot[bot] 20f71f5a21 Bump httpclient from 4.5.3 to 4.5.13 in /sdk
Bumps httpclient from 4.5.3 to 4.5.13.

---
updated-dependencies:
- dependency-name: org.apache.httpcomponents:httpclient
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-06-04 02:11:35 +00:00
dependabot[bot] 326f76afd8 Bump httpclient from 4.4.1 to 4.5.13 in /broker
Bumps httpclient from 4.4.1 to 4.5.13.

---
updated-dependencies:
- dependency-name: org.apache.httpcomponents:httpclient
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-06-04 01:50:00 +00:00
dependabot[bot] 9f36d47ca8 Bump commons-io from 2.5 to 2.7 in /sdk
Bumps commons-io from 2.5 to 2.7.

Signed-off-by: dependabot[bot] <[email protected]>
2021-04-26 19:30:47 +00:00
dependabot[bot] 57cdd57a30 Bump commons-io from 2.5 to 2.7 in /broker
Bumps commons-io from 2.5 to 2.7.

Signed-off-by: dependabot[bot] <[email protected]>
2021-04-26 18:54:43 +00:00
112 changed files with 1599 additions and 416 deletions
+141 -45
View File
@@ -4,10 +4,11 @@
## 功能特性
* 极致地硬件利用率,IM服务最低128M内存即可运行,上不封顶。
* 协议先进,采用MQTT+Protobuf组合,流量和性能极致优化。
* 性能强大,专业版IM服务支持百万在线和集群部署。
* 性能强大,专业版IM服务支持百万在线和集群部署,业内顶尖性能水平,详情参考野火性能报告。
* 部署运维简单,依赖服务少,稍加配置一键启动。
* 安全加密。网络连接AES加密。客户端数据库SqlCipher加密。安全无小事。
* 全平台客户端,四端同时在线(移动端,pc端,web端和小程序端),数据和状态多端完美同步。
* 全平台客户端,支持Android、iOS、Web、小程序、UniApp、Flutter、Windows、Mac OS、Linux等所有常见平台。
* 四端同时在线(移动端,pc端,web端和小程序端),数据和状态多端完美同步。
* 支持国产化。支持国产化操作系统、国产化芯片和国产操作系统。支持国密加密。
* 客户端使用微信[mars](https://github.com/tencent/mars)连接库,野火IM可能是最适应中国网络国情的即时通讯服务。
* 支持加速点加速,即可用于全球应用,也可用于政企内外双网复杂的网络环境。
@@ -15,19 +16,22 @@
* 音视频多种解决方案,丰俭由人,可自由选择。
* 高级音视频功能强大,支持9人以上的群组视频通话,支持1080P视频,支持会议模式,支持百人以上会议,支持服务器端录制。
* 全私有部署,可不依赖任何第三方服务,完全内网部署。
* 功能齐全,涵盖所有常见即时通讯功能。另外具有强大的可扩展能力。Demo成熟完善,基本可以做到开箱即用,也可把SDK嵌入其它应用。
* 功能齐全,涵盖所有常见即时通讯功能。另外具有强大的可扩展能力。代码开源率高,二次开发简单。
* 结构设计合理,客户端分功能库、UI库和应用三层。应用成熟完善,开箱即用,也可把SDK嵌入其它应用。
* 拥有应用开放平台,可以开发和创建自建应用,扩展您的工作台。
* API丰富,方便与其它服务系统的对接。
* 拥有机器人和频道功能。
* 代码开源率高,二次开发简单。可以先试用3到6个月,二开搞定后再购买。
* 拥有机器人和公众号功能。
* 社区版IM服务和移动端免费商用,可以免费构建出完备的移动互联网应用。
* 收费项目非常便宜,野火独有的试用模式,可以先部署到客户环境试用6个月,试用满意后再购买,避免上当受骗。
* 技术支持专业和高效,核心研发人员及时处理issue和论坛回复。
## 野火开源项目
主要包括以下项目:
| [GitHub仓库地址](https://github.com/wildfirechat) | [码云仓库地址](https://gitee.com/wfchat) | 说明
| [GitHub仓库](https://github.com/wildfirechat) | [码云仓库](https://gitee.com/wfchat) | 说明
| ------------------------------------------------------------ | ----------------------------------------------------- | --------------------------------------------------------------------------
| [im-server](https://github.com/wildfirechat/im-server) | [im-server](https://gitee.com/wfchat/im-server) | 野火社区版IM服务,野火IM的核心服务,处理所有IM相关业务。 |
| [app_server](https://github.com/wildfirechat/app_server) | [app_server](https://gitee.com/wfchat/app_server) | Demo应用服务,模拟客户的应用服登陆处理逻辑及部分二次开发示例。 |
| [app-server](https://github.com/wildfirechat/app-server) | [app-server](https://gitee.com/wfchat/app-server) | Demo应用服务,模拟客户的应用服登陆处理逻辑及部分二次开发示例。 |
| [robot_server](https://github.com/wildfirechat/robot_server) | [robot_server](https://gitee.com/wfchat/robot_server) | 机器人服务,演示野火机器人对接其它系统的方法。 |
| [push_server](https://github.com/wildfirechat/push_server) | [push_server](https://gitee.com/wfchat/push_server) | 推送服务器,可以对接所有的系统厂商推送服务或者第三方推送服务。 |
| [wf-minio](https://github.com/wildfirechat/WF-minio) | [wf-minio](https://gitee.com/wfchat/WF-minio) | 私有对象存储服务,用来支持野火IM专业版的文件存储。 |
@@ -35,19 +39,23 @@
| [open-platform](https://github.com/wildfirechat/open-platform) | [open-platform](https://gitee.com/wfchat/open-platform) | 野火开放平台服务。 |
| [daily-report](https://github.com/wildfirechat/daily-report) | [daily-report](https://github.com/wildfirechat/daily-report) | 野火开放平台日报 demo服务。 |
| [channel-platform](https://github.com/wildfirechat/channel-platform) | [channel-platform](https://gitee.com/wfchat/channel-platform) | 野火频道(公众号)平台服务。 |
| [organization-platform](https://github.com/wildfirechat/organization-platform) | [organization-platform](https://gitee.com/wfchat/organization-platform) | 野火组织通讯录服务。 |
| [android-chat](https://github.com/wildfirechat/android-chat) | [android-chat](https://gitee.com/wfchat/android-chat) | 野火IM Android SDK源码和App源码。 |
| [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS SDK源码和App源码。|
| [pc-chat](https://github.com/wildfirechat/vue-pc-chat) | [pc-chat](https://gitee.com/wfchat/vue-pc-chat) | 基于[Electron](https://electronjs.org/)的PC 端,支持Windows、Mac、Linux(包括国产化linux系统和CPU)。 |
| [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](https://web.wildfirechat.cn)。 |
| [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台)。 |
| [uni-chat](https://github.com/wildfirechat/uni-chat) | [uni-chat](https://gitee.com/wfchat/uni-chat) | UniApp平台移动端应用(使用原生插件)。 |
| [uni-wfc-client](https://github.com/wildfirechat/uni-wfc-client) | [uni-wfc-client](https://gitee.com/wfchat/uni-wfc-client) | UniApp平台原生插件。 |
| [flutter_imclient](https://github.com/wildfirechat/flutter_imclient) | [flutter_imclient](https://gitee.com/wfchat/flutter_imclient) | Flutter平台原生插件。 |
| [uni-wfc-client](https://github.com/wildfirechat/uni-wfc-client) | [uni-wfc-client](https://gitee.com/wfchat/uni-wfc-client) | UniApp平台原生插件。已[商店](https://ext.dcloud.net.cn/plugin?id=7895)上架 |
| [wfc_flutter_plugins](https://github.com/wildfirechat/wfc_flutter_plugins) | [flutter_imclient](https://gitee.com/wfchat/wfc_flutter_plugins) | Flutter平台原生插件,支持IM和音视频通话。 |
| [docs](https://github.com/wildfirechat/docs) | [docs](https://gitee.com/wfchat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明,[在线查看](https://docs.wildfirechat.cn/)。 |
## 野火开发文档
[在线文档](https://docs.wildfirechat.cn/)
## 野火性能测试报告及测试方法
[Github](https://github.com/wildfirechat/Performance_Test), [码云](https://gitee.com/wfchat/Performance_Test)。
## 野火Demo
请使用微信扫码下载安装体验野火IM移动客户端
@@ -62,73 +70,161 @@ PC客户端点[这里](https://github.com/wildfirechat/vue-pc-chat/releases)下
![野火IM](http://static.wildfirechat.net/wx.jpg)
## 快速使用
野火提供免费的社区版IM服务和移动端,包括完整的移动端即时通讯和实时音视频功能,可以无条件商用。您可以直接从开源项目里下载到完整的后端和移动端产品,包括社区版IM服务、应用服务、开放平台服务、频道(公众号)服务、机器人服务、推送服务以及Android和iOS端源码。可以按照[快速开始](https://docs.wildfirechat.cn/quick_start/)来部署应用服务和IM服务,然后打包移动端,以最小化运行。之后可以详细阅读文档部署其他支撑服务。
可以按照[快速开始](https://docs.wildfirechat.cn/quick_start/)来部署应用服务和IM服务,然后打包移动端,就可以完成聊天和通话功能。
## 应用截图
![ios-demo](http://static.wildfirechat.cn/ios-demo.gif)
之后再部署开放平台服务、频道(公众号)服务、机器人服务、推送服务、Turn服务(音视频服务)来实现完整的应用功能,详情可以详细阅读文档。
<img src="http://static.wildfirechat.cn/ios-message-view.png" width = 50% height = 50% />
## 联系我们
商务合作请微信联系:
<img src="http://static.wildfirechat.cn/ios-contact-view.png" width = 50% height = 50% />
* 微信1:wildfirechat
* 微信2:wfchat
<img src="http://static.wildfirechat.cn/ios-discover-view.png" width = 50% height = 50% />
## 问题交流
<img src="http://static.wildfirechat.cn/ios-settings-view.png" width = 50% height = 50% />
1. 如果大家发现bug,请在GitHub或码云提issue;如果有需求也请给我们提issue。
2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习
3. 关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。
<img src="http://static.wildfirechat.cn/ios-messagelist-view.png" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/wx_wfc_qrcode.jpg" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/ios-chat-setting-view.png" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/ios-takephoto-view.png" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/ios-record-voice-view.png" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/ios-location-view.png" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/ios-voip-view.png" width = 50% height = 50% />
我们有核心研发工程师轮流值班处理issue和论坛,会及时处理的,疑难Bug的修改和新需求的开发我们也会尽快解决。
## 编译
在安装JDK1.8以上及maven的前提下,在命令行中执行```mvn clean package```,生成的目标文件在```./distribution/target/distribution-xxxx-bundle-tar.tar.gz```
> 由于使用了一个git的maven插件,如果本地没有git信息就会编译出错,请使用```git clone```的方法下载代码,或者下载压缩包解压后在根目录创建```.git```的空目录。建议用```git clone```的方式下载代码。
```
mvn clean package
```
生成的目标文件在```./distribution/target/distribution-xxxx-bundle-tar.tar.gz```
## 配置
解压```distribution-xxxx-bundle-tar.tar.gz```,修改解压出来的```config```目录下的```wildfirechat.conf```。修改如下部分:
1. 修改```server.ip```为您的服务器的公网IP,或者域名。如果用域名需要做好域名解析。
2. 可以阅读配置文件注释和文档,对其它配置项进行调整。
解压```distribution-xxxx-bundle-tar.tar.gz```,修改解压出来的```config```目录下的```wildfirechat.conf```,可以阅读配置文件注释和文档,对其它配置项进行调整。
## 运行
在程序目录运行如下命令:
在解压```distribution-xxxx-bundle-tar.tar.gz```后的目录下运行如下命令:
```
./bin/wildfirechat.sh
```
## 验证
1. 在浏览器中输入地址 ```http://${ip}/api/version```可以看到返回一个json文件。
2. 部署[应用服务](应用服务),配置和编译[Android客户端](https://github.com/wildfirechat/android-chat)和[iOS客户端](https://github.com/wildfirechat/ios-chat)进行验证。详情可参考[快速开始](https://docs.wildfirechat.cn/quick_start/)。
2. 部署[应用服务](https://github.com/wildfirechat/app-server),配置和编译[Android客户端](https://github.com/wildfirechat/android-chat)和[iOS客户端](https://github.com/wildfirechat/ios-chat)进行验证。详情可参考[快速开始](https://docs.wildfirechat.cn/quick_start/)。
## 打包RPM格式
打包会生成Java包和deb安装包,如果需要rpm安装包,请在```distribution/pom.xml```中取消注释生成rpm包的plugin。另外还需要本地安装有rpm,在linux或者mac系统中很容易安装,在windows系统需要安装cygwin并安装rpm,具体信息请百度查询。
修改之后运行编译命令```mvn clean package```,rpm包生成在```distribution/target```目录下。
## 升级说明
1. 从0.42 版本增加了群成员数限制,默认为2000。如果您从之前的版本升级到这个版本或以后,需要注意到群成员数的限制。升级之后超出限制的群不受影响,但不能继续加人,如果您想修改默认值,可以在升级版本之后,修改t_setting表,把默认的大小改为您期望的人数。另外修改t_group表,把已经存在的群组max_member_count改成您期望的,然后重启。
2. 0.46和0.47版本升级到0.48及以后版本时,可能会提示flyway migrate 38错误,请执行 [修复脚本](https://github.com/wildfirechat/server/blob/wildfirechat/flyway_repaire_migrate_38.sql) 进行修复。0.46和0.47版本之外的版本不会出现此问题。
3. 从0.54之前版本升级到0.54及以后版本时,会提示flyway migrate错误。因为0.54版本删除了sql脚本中默认敏感词的内容,flyway checksum失败。请执行```update flyway_schema_history set checksum = 0 where script = 'V17__add_default_sensitive_word.sql';```来修复。
4. 从0.59之前的版本升级到之后的版本执行数据库升级时间比较长,请耐心等待提示运行成功,避免中途中断。
2. 0.50版本添加了是否允许客户端发送群操作通知的配置。如果您在客户端自定义群通知,需要在服务器端配置允许,没有使用自定义群操作通知的不受影响。***
3. 0.46和0.47版本升级到0.48及以后版本时,可能会提示flyway migrate 38错误,请执行 [修复脚本](https://github.com/wildfirechat/server/blob/wildfirechat/flyway_repaire_migrate_38.sql) 进行修复。0.46和0.47版本之外的版本不会出现此问题。
4. 从0.54之前版本升级到0.54及以后版本时,会提示flyway migrate错误。因为0.54版本删除了sql脚本中默认敏感词的内容,flyway checksum失败。请执行```update flyway_schema_history set checksum = 0 where script = 'V17__add_default_sensitive_word.sql';```来修复。
5. 从0.59之前的版本升级到之后的版本执行数据库升级时间比较长,请耐心等待提示运行成功,避免中途中断。
6. 0.62/0.63 版本有严重的问题,请使用0.64及以后版本,或者0.61版。
7. 从0.68 版本起添加了pc在线是否默认手机接收推送的开关,默认为开,与以前版本作用相反,请注意兼容(可以关掉与之前保持一致或者升级客户端)
8. 从0.78 版本起把MySQL数据库中关键字都改为大小写敏感,另外生成id的方法也做了改变,只生成小写的id,避免出现id重复的问题,建议所有客户都升级
9. 从0.79 版本起把log4j升级到log4j2,因为log4j已经不再维护而且还有已知的漏洞,建议所有客户都升级,升级时注意更新log4j2的配置文件
10. 0.97版本更改了启动脚本```wildfirechat.sh```,如果是升级服务,请注意更新启动脚本。
## 应用截图
登录界面
## 联系我们
<img src="./screenshots/1_login.png" width = 50% height = 50% />
商务合作请使用如下邮箱和微信联系:
会话列表
邮箱: support@wildfirechat.cn 微信1:wfchat 微信2:wildfirechat
<img src="./screenshots/2_conversation_list.png" width = 50% height = 50% />
## 问题交流
好友列表
1. 如果大家发现bug,请在GitHub提issue;如果有需求也请给我们提issue。
2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习
3. 关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。
<img src="./screenshots/3_contacts_list.png" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/wx_wfc_qrcode.jpg" width = 50% height = 50% />
工作台
> 我们有研发工程师轮流值班处理issue和论坛,一般简单问题几个小时就会回复一遍,疑难Bug的修改和新需求的开发我们也会尽快解决。
<img src="./screenshots/4_workplatform.png" width = 50% height = 50% />
发现
<img src="./screenshots/5_discover.png" width = 50% height = 50% />
我的页面
<img src="./screenshots/6_settings.png" width = 50% height = 50% />
消息聊天
<img src="./screenshots/7_conversation.png" width = 50% height = 50% />
动态表情
<img src="./screenshots/8_conversation_sticker.png" width = 50% height = 50% />
语音消息
<img src="./screenshots/9_conversation_voice_msg.png" width = 50% height = 50% />
位置消息
<img src="./screenshots/10_conversation_location.png" width = 50% height = 50% />
拍摄图片或者小视频
<img src="./screenshots/11_conversation_capture.jpeg" width = 50% height = 50% />
会话设置
<img src="./screenshots/13_conversation_settings.png" width = 50% height = 50% />
朋友圈
<img src="./screenshots/14_moments.png" width = 50% height = 50% />
新好友
<img src="./screenshots/15_new_friends.png" width = 50% height = 50% />
多人视频通话
<img src="./screenshots/16_videocall.png" width = 50% height = 50% />
会议主页
<img src="./screenshots/17_conference_home.png" width = 50% height = 50% />
会议详情
<img src="./screenshots/18_conference_info.png" width = 50% height = 50% />
会议界面
<img src="./screenshots/19_conference.png" width = 50% height = 50% />
PC客户端界面
<img src="./screenshots/20_pc.png"/>
移动客户端、PC客户端和Web客户端三端同框
<img src="./screenshots/21_three_platform.png" />
小程序客户端
<img src="./screenshots/22_micro_app.jpeg" width = 50% height = 50% />
公众号管理后台
<img src="./screenshots/23_channel.png"/>
开放平台管理后台
<img src="./screenshots/24_open_platform.png" />
IM服务管理后台
<img src="./screenshots/25_im_admin.png" />
> 更多详细信息请下载demo进行体验。
## 特别感谢
1. [moquette](https://github.com/moquette-io/moquette) 本项目是基于此项目二次开发而来,处理MQTT相关业务。
-3
View File
@@ -1,3 +0,0 @@
#native2ascii -encoding UTF-8 messages.properties temp_messages.properties
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
@@ -1,2 +0,0 @@
Above_Greeting_Message=This's the greeting
Friend_Can_Start_Chat=You're already friend, start chat now.
@@ -1,2 +0,0 @@
Above_Greeting_Message=This's the greeting
Friend_Can_Start_Chat=You're already friend, start chat now.
@@ -1,2 +0,0 @@
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
@@ -1,2 +0,0 @@
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
+22 -14
View File
@@ -42,6 +42,9 @@ embed.db 1
## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。
db.auto_clean_history_messages true
## h2db数据库的路径,默认为程序目录下的,可以指定其他目录。如果使用MySQL,可以忽略此配置
h2db.path ./h2db/wfchat
##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。
http.admin.secret_key 123456
@@ -56,6 +59,12 @@ token.key testim
##token的过期时间,单位为毫秒,默认为无限期。如果需要设置无限期,客户端上一定需要加上token过期的处理。token过期的处理请参考文档的常见问题
##token.expire_time 2592000000
##客户端(目前只有android)的应用签名(一个应用最取第一个签名),如果有多个签名(多个客户端),用英文逗号分开。获取签名方法请参考文档:https://docs.wildfirechat.cn/blogs/签名验证.html
#connect.client_signature_list eNkezQ1g9OsnhfLSFUY1vzKDzhs=,xykezQ1g9OsnhfLSFUY1vzKDzhs=
##是否拒绝空签名的android客户端, 默认为true。当有旧的客户端需要兼容时改成false,当所有都是新客户端时,改成true。
#connect.reject_empty_signature true
##首次登录,是否接收之前的历史消息。
##0 不接收历史消息,只接收${message.compensate_time_limit}毫秒以内的消息,由于服务器没有保存已经收取记录,所以如果有超过${message.compensate_time_limit}毫秒之前未收取的消息也不会收取下拉;
##1接收,会接收 message.max_queue 配置的条数的历史消息
@@ -76,6 +85,9 @@ message.chatroom_remote_history_message 1
##是否禁止陌生人聊天
message.disable_stranger_chat false
##当禁止陌生人聊天时,允许聊天的用户id,比如管理员或者文件传输助手等。用户id以英文逗号分割。
message.allow_stranger_chat_list admin,FireRobot,wfc_file_transfer
##黑名单策略,0 发送失败,返回被拉黑的错误码;1 发送成功但消息被服务器直接丢弃
message.blacklist.strategy 0
@@ -139,12 +151,6 @@ friend.reject_request_duration 2592000000
##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。
friend.request_expiration_duration 604800000
##添加好友后会发送欢迎消息,比如"你们已经是好友了,可以聊天了"。这个开关控制是用旧的方式还是用新的方式。
##旧的方式会发送notification消息(contenttype是80),内容会根据好友接收者的语言选择对应的语言,如果双方语言不一致会导致另外一方看到的不是本地语言。
##新的方式会发送添加好友消息(contenttype是93)和欢迎消息(contenttype是92),需要客户端实现这两种消息。
##如果客户端已经实现这两种消息可以打开这个开关(客户端2020.6.30添加了此消息类型)。
friend.new_welcome_message true
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000
@@ -164,6 +170,10 @@ group.allow_robot_custom_operation_notification true
## 0 不发送,1 退群时发送,2 踢人时发送,3 退群和踢人时都发送。
group.visible_quit_or_kickoff_notification 0
## 禁止客户端群操作。第1位是禁止创建群组,第2位是禁止销毁群组,3位禁止加入群,4位禁止退出群,5位禁止邀请群成员,6位禁止移出群成员,7位禁止转移群,8位禁止设置群管理员,9位禁止白名单处理,10位禁止群禁言,11位禁止修改群组信息,12位禁止群成员禁言。
## Server API不受此限制。
#group.forbidden_client_operation 0xFFF
## 隐藏用户信息属性类型,2是性别,3是电话号码,4是邮箱,5是地址,6是公司信息,7是社交账号,8是Extra信息。当需要隐藏多个时以英文逗号分开;
## 本配置是为了提高安全性,避免把对应信息同步到客户端,这样就能防止程序被破解从而拿到用户关键信息。仅对客户端有效,server api不受影响。
## 注意,修改本配置不会对已经同步的信息产生影响。
@@ -190,11 +200,6 @@ http.channel.rate_limit 1000
##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。
client.request_rate_limit 100
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#*********************************************************************
# Media server configuration
#*********************************************************************
@@ -301,14 +306,17 @@ push.ios.server.address http://localhost:8085/ios/push
##消息转发地址
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.forward.url http://localhost:8888/im_event/message
##需要转发的消息类型,当有多个时以英文逗号分割。如果转发所有消息,请注释掉配置或者设置为空
#message.forward.types 1,2,3
##需要转发的消息类型,当有多个时以英文逗号分割,可以用-来指定区间,注意区间不能太大,因为要把区间内的所有数字都放入到Set中。如果转发所有消息,请注释掉配置或者设置为空
#message.forward.types 1,2,3,5-10
##需要转发的消息类型,当有多个时以英文逗号分割,可以用-来指定区间,注意区间不能太大,因为要把区间内的所有数字都放入到Set中。
#message.forward.exclude_types 1,2,3,5-10
##敏感消息转发地址
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.sensitive.forward.url http://localhost:8888/im_event/message
##提醒消息转发地址,@全体用户和@某个用户消息会回调此地址。此转发不受${message.forward.types}限制。
##提醒消息转发地址,@全体用户和@某个用户消息会回调此地址。
##此转发不受${message.forward.types}和${message.forward.exclude_types}限制。
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.mentionmsg.forward.url http://localhost:8888/im_event/message
+1
View File
@@ -0,0 +1 @@
alter table `t_group_member` add INDEX `group_member_mid_index` (`_mid`);
+1
View File
@@ -0,0 +1 @@
alter table `t_group_member` add INDEX `group_member_mid_index` (`_mid`);
+4 -4
View File
@@ -1,11 +1,11 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<?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>
<parent>
<relativePath>../</relativePath>
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.96</version>
<version>1.1</version>
</parent>
<artifactId>moquette-broker</artifactId>
@@ -120,7 +120,7 @@
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
<version>1.13</version>
</dependency>
<dependency>
@@ -90,6 +90,8 @@ public class FileAction extends Action {
}
response.setContent(file);
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
return true;
}
@@ -187,6 +187,10 @@ public class UploadFileAction extends Action {
return false;
}
if(remoteFileName != null && remoteFileName.length() > 2 && remoteFileName.startsWith("'") && remoteFileName.endsWith("'")) {
remoteFileName = remoteFileName.substring(1, remoteFileName.length()-1);
}
if (StringUtil.isNullOrEmpty(remoteFileName)) {
logger.warn("remoteFileName is empty!");
@@ -235,12 +239,17 @@ public class UploadFileAction extends Action {
int offset = 0; // 断点续传开始位置
Date nowTime=new Date();
SimpleDateFormat time=new SimpleDateFormat("yyyy/MM/dd/HH");
SimpleDateFormat time=new SimpleDateFormat("yyyy/MM/dd/HH/mm/ss");
String datePath = time.format(nowTime);
datePath = "fs/" + bucket[0] + "/" + datePath; //add bucket
String dir = "./" + MediaServerConfig.FILE_STROAGE_ROOT + "/" + datePath;
String dir;
if(MediaServerConfig.FILE_STROAGE_ROOT.startsWith("/") || MediaServerConfig.FILE_STROAGE_ROOT.startsWith("~/") || MediaServerConfig.FILE_STROAGE_ROOT.startsWith("./")) {
dir = MediaServerConfig.FILE_STROAGE_ROOT + "/" + datePath;
} else {
dir = "./" + MediaServerConfig.FILE_STROAGE_ROOT + "/" + datePath;
}
File dirFile = new File(dir);
boolean bFile = dirFile.exists();
@@ -0,0 +1,55 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputUserId;
import cn.wildfirechat.pojos.OutputGroupIds;
import cn.wildfirechat.pojos.StringPairPojo;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.util.internal.StringUtil;
import java.util.ArrayList;
import java.util.Set;
@Route(APIPath.Get_Common_Groups)
@HttpMethod("POST")
public class GetCommonGroupsAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
StringPairPojo input = getRequestBody(request.getNettyRequest(), StringPairPojo.class);
if (input != null
&& (!StringUtil.isNullOrEmpty(input.getFirst())) && (!StringUtil.isNullOrEmpty(input.getSecond()))) {
Set<String> groupIds = messagesStore.getCommonGroupIds(input.getFirst(), input.getSecond());
OutputGroupIds outputGroupIds = new OutputGroupIds();
outputGroupIds.setGroupIds(new ArrayList<>(groupIds));
RestResult result = RestResult.ok(outputGroupIds);
setResponseContent(result, response);
} else {
setResponseContent(RestResult.resultOf(ErrorCode.INVALID_PARAMETER), response);
}
}
return true;
}
}
@@ -49,7 +49,7 @@ public class GetUserAction extends AdminAction {
}
RestResult result;
if (user == null) {
if (user == null || user.getDeleted() > 0) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else {
result = RestResult.ok(InputOutputUserInfo.fromPbUser(user));
@@ -0,0 +1,37 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.channel;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.pojos.InputChannelSubscribe;
import cn.wildfirechat.pojos.InputUserId;
import cn.wildfirechat.pojos.OutputStringList;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.netty.handler.codec.http.FullHttpRequest;
import java.util.ArrayList;
@Route(APIPath.Channel_Is_Subscriber)
@HttpMethod("POST")
public class GetIsChannelSubscriberAction extends ChannelAction {
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputUserId input = getRequestBody(request.getNettyRequest(), InputUserId.class);
boolean isSubscriber = messagesStore.checkUserInChannel(input.getUserId(), channelInfo.getTargetId());
setResponseContent(RestResult.ok(isSubscriber), response);
}
return true;
}
}
@@ -44,7 +44,7 @@ public class GetUserAction extends ChannelAction {
RestResult result;
if (user == null || StringUtil.isNullOrEmpty(user.getName())) {
if (user == null || StringUtil.isNullOrEmpty(user.getName()) || user.getDeleted() > 0) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else {
if (channelInfo.getOwner().equals(inputUserId.getUserId()) ||
@@ -44,7 +44,7 @@ public class GetUserAction extends RobotAction {
}
RestResult result;
if (user == null) {
if (user == null || user.getDeleted() > 0) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else {
result = RestResult.ok(InputOutputUserInfo.fromPbUser(user));
@@ -15,6 +15,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import win.liyufan.im.Utility;
@@ -34,7 +35,6 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
Logger.info("Http request whit url {}", fullHttpRequest.uri());
final Request request = Request.build(ctx, fullHttpRequest);
final Response response = Response.build(ctx, request);
@@ -109,7 +109,7 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
* @param response 响应对象
*/
private void doAction(ChannelHandlerContext ctx, Request request, Response response){
if(("/route".equalsIgnoreCase(request.getPath()) || request.getPath().startsWith("/fs/"))
if(("/route".equalsIgnoreCase(request.getPath()) || (!StringUtil.isNullOrEmpty(request.getPath()) && request.getPath().startsWith("/fs/")))
&& "OPTIONS".equalsIgnoreCase(request.getMethod())){
handleOptions(ctx, request, response);
return;
@@ -124,6 +124,9 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
Action action;
if (isValidePath(request.getPath())) {
action = ServerSetting.getAction(request.getPath(), request.getMethod().toUpperCase());
if (action != null) {
Logger.info("Http request whit url {}", request.getPath());
}
} else {
action = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
}
@@ -137,7 +140,11 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
action = ServerSetting.getAction(ServerSetting.MAPPING_ALL, request.getMethod());
if(null == action){
// 非Action方法,调用静态文件读取
action = Singleton.get(FileAction.class);
if(request.getPath().startsWith("/fs")) {
action = Singleton.get(FileAction.class);
} else {
action = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
}
}
}
action.ctx = ctx;
@@ -110,8 +110,13 @@ public final class BrokerConstants {
public static final String TOKEN_SECRET_KEY = "token.key";
public static final String TOKEN_EXPIRE_TIME = "token.expire_time";
public static final String CONNECT_CLIENT_SIGNATURE_LIST = "connect.client_signature_list";
public static final String CONNECT_REJECT_EMPTY_SIGNATURE = "connect.reject_empty_signature";
public static final String EMBED_DB_PROPERTY_NAME = "embed.db";
public static final String DB_AUTO_CLEAN_HISTORY_MESSAGES = "db.auto_clean_history_messages";
public static final String H2DB_PATH = "h2db.path";
public static final String SENSITIVE_Filter_Type = "sensitive.filter.type";
@@ -122,6 +127,7 @@ public final class BrokerConstants {
public static final String MESSAGE_Forward_Url = "message.forward.url";
public static final String MESSAGE_Sensitive_Forward_Url = "message.sensitive.forward.url";
public static final String MESSAGE_Forward_Types = "message.forward.types";
public static final String MESSAGE_Forward_Exclude_Types = "message.forward.exclude_types";
public static final String MESSAGE_MentionMsg_Forward_Url = "message.mentionmsg.forward.url";
@@ -139,6 +145,7 @@ public final class BrokerConstants {
public static final String MESSAGE_Max_Queue = "message.max_queue";
public static final String MESSAGE_Disable_Stranger_Chat = "message.disable_stranger_chat";
public static final String MESSAGE_Allow_Stranger_Chat_List = "message.allow_stranger_chat_list";
public static final String MESSAGE_Blacklist_Strategy = "message.blacklist.strategy";
@@ -154,7 +161,6 @@ public final class BrokerConstants {
public static final String FRIEND_Repeat_Request_Duration = "friend.repeat_request_duration";
public static final String FRIEND_Reject_Request_Duration = "friend.reject_request_duration";
public static final String FRIEND_Request_Expiration_Duration = "friend.request_expiration_duration";
public static final String FRIEND_New_Welcome_Message = "friend.new_welcome_message";
public static final String CHATROOM_Participant_Idle_Time = "chatroom.participant_idle_time";
public static final String CHATROOM_Rejoin_When_Active = "chatroom.rejoin_when_active";
@@ -165,6 +171,7 @@ public final class BrokerConstants {
public static final String GROUP_Allow_Robot_Custom_Operation_Notification = "group.allow_robot_custom_operation_notificatio";
public static final String GROUP_Visible_Quit_Kickoff_Notification = "group.visible_quit_or_kickoff_notification";
public static final String GROUP_Forbidden_Client_Operation = "group.forbidden_client_operation";
public static final String USER_HIDE_PROPERTIES = "user.hide_properties";
public static final String USER_KEEP_DISPLAY_NAME_WHEN_DESTROY = "user.keep_display_name_when_destroy";
@@ -24,6 +24,20 @@ public class AddGroupMember extends GroupHandler<WFCMessage.AddGroupMemberReques
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.AddGroupMemberRequest request, Qos1PublishHandler.IMCallback callback) {
boolean isAdmin = requestSourceType == ProtoConstants.RequestSourceType.Request_From_Admin;
if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_User) {
int forbiddenClientOperation = m_messagesStore.getGroupForbiddenClientOperation();
if(request.getAddedMemberList().size() == 1 && request.getAddedMember(0).getMemberId().equals(fromUser)) {
if ((forbiddenClientOperation & ProtoConstants.ForbiddenClientGroupOperationMask.Forbidden_Join_Group) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
} else {
if ((forbiddenClientOperation & ProtoConstants.ForbiddenClientGroupOperationMask.Forbidden_Invite_Group_Member) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
}
}
ErrorCode errorCode = m_messagesStore.addGroupMembers(fromUser, isAdmin, request.getGroupId(), request.getAddedMemberList(), request.getExtra());
if (errorCode == ERROR_CODE_SUCCESS) {
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -21,7 +21,7 @@ import win.liyufan.im.IMTopic;
public class CreateGroupHandler extends GroupHandler<WFCMessage.CreateGroupRequest> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.CreateGroupRequest request, Qos1PublishHandler.IMCallback callback) {
if(request.getGroup().getGroupInfo().getType() < 0 || request.getGroup().getGroupInfo().getType() > 2) {
if(request.getGroup().getGroupInfo().getType() < 0 || request.getGroup().getGroupInfo().getType() > 3) {
return ErrorCode.ERROR_CODE_INVALID_DATA;
}
@@ -40,6 +40,16 @@ public class CreateGroupHandler extends GroupHandler<WFCMessage.CreateGroupReque
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if(!isAdmin && request.getGroup().getGroupInfo().getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_User) {
int forbiddenClientOperation = m_messagesStore.getGroupForbiddenClientOperation();
if((forbiddenClientOperation & ProtoConstants.ForbiddenClientGroupOperationMask.Forbidden_Create_Group) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
}
WFCMessage.GroupInfo groupInfo = m_messagesStore.createGroup(fromUser, request.getGroup().getGroupInfo(), request.getGroup().getMembersList(), request.getMemberExtra(), isAdmin);
if (groupInfo != null) {
if(request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -26,7 +26,6 @@ public class DismissGroupHandler extends GroupHandler<WFCMessage.DismissGroupReq
ErrorCode errorCode;
if (groupInfo == null) {
errorCode = m_messagesStore.dismissGroup(fromUser, request.getGroupId(), isAdmin);
} else if (isAdmin || (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Normal || groupInfo.getType() == ProtoConstants.GroupType.GroupType_Restricted)
&& groupInfo.getOwner() != null && groupInfo.getOwner().equals(fromUser)) {
@@ -38,6 +37,13 @@ public class DismissGroupHandler extends GroupHandler<WFCMessage.DismissGroupReq
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_User) {
int forbiddenClientOperation = m_messagesStore.getGroupForbiddenClientOperation();
if((forbiddenClientOperation & ProtoConstants.ForbiddenClientGroupOperationMask.Forbidden_Dismiss_Group) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
}
//send notify message first, then dismiss group
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
sendGroupNotification(fromUser, groupInfo.getTargetId(), request.getToLineList(), request.getNotifyContent());
@@ -0,0 +1,33 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.imhandler;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf;
import win.liyufan.im.IMTopic;
import java.util.Set;
import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_SUCCESS;
@Handler(IMTopic.GetCommonGroupsTopic)
public class GetCommonGroupsHandler extends IMHandler<WFCMessage.IDBuf> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.IDBuf request, Qos1PublishHandler.IMCallback callback) {
WFCMessage.PullUserResult.Builder resultBuilder = WFCMessage.PullUserResult.newBuilder();
Set<String> strings = m_messagesStore.getCommonGroupIds(fromUser, request.getId());
WFCMessage.IDListBuf idListBuf = WFCMessage.IDListBuf.newBuilder().addAllId(strings).build();
byte[] data = idListBuf.toByteArray();
ackPayload.ensureWritable(data.length).writeBytes(data);
return ERROR_CODE_SUCCESS;
}
}
@@ -0,0 +1,33 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.imhandler;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf;
import win.liyufan.im.IMTopic;
import java.util.Set;
import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_SUCCESS;
@Handler(IMTopic.GetMyGroupsTopic)
public class GetMyGroupsHandler extends IMHandler<Void> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, Void request, Qos1PublishHandler.IMCallback callback) {
WFCMessage.PullUserResult.Builder resultBuilder = WFCMessage.PullUserResult.newBuilder();
Set<String> strings = m_messagesStore.getUserGroupIds(fromUser);
WFCMessage.IDListBuf idListBuf = WFCMessage.IDListBuf.newBuilder().addAllId(strings).build();
byte[] data = idListBuf.toByteArray();
ackPayload.ensureWritable(data.length).writeBytes(data);
return ERROR_CODE_SUCCESS;
}
}
@@ -10,12 +10,10 @@ package io.moquette.imhandler;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf;
import cn.wildfirechat.common.ErrorCode;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.I18n;
import win.liyufan.im.IMTopic;
import win.liyufan.im.MessageShardingUtil;
@@ -43,17 +41,8 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType);
}
MemorySessionStore.Session session = m_sessionsStore.getSession(clientID);
String language = "zh_CN";
if (session != null && !StringUtil.isNullOrEmpty(session.getLanguage())) {
language = session.getLanguage();
}
WFCMessage.MessageContent.Builder contentBuilder = WFCMessage.MessageContent.newBuilder();
if (m_messagesStore.isNewFriendWelcomeMessage()) {
contentBuilder.setType(92);
} else {
contentBuilder.setType(90).setContent(I18n.getString(language, "Above_Greeting_Message"));
}
contentBuilder.setType(92);
builder = WFCMessage.Message.newBuilder();
builder.setFromUser(request.getTargetUid());
@@ -64,13 +53,7 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId);
saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType);
if (m_messagesStore.isNewFriendWelcomeMessage()) {
contentBuilder.setType(93);
} else {
contentBuilder.setContent(I18n.getString(language, "Friend_Can_Start_Chat"));
}
contentBuilder.setType(93);
builder.setContent(contentBuilder);
messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId);
@@ -29,6 +29,10 @@ public class KickoffGroupMember extends GroupHandler<WFCMessage.RemoveGroupMembe
errorCode = ErrorCode.ERROR_CODE_NOT_EXIST;
} else {
boolean isAdmin = requestSourceType == ProtoConstants.RequestSourceType.Request_From_Admin;
if (!isAdmin && groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
boolean isAllow = isAdmin;
if (!isAllow) {
if (groupInfo.getOwner() != null) {
@@ -58,6 +62,13 @@ public class KickoffGroupMember extends GroupHandler<WFCMessage.RemoveGroupMembe
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_User) {
int forbiddenClientOperation = m_messagesStore.getGroupForbiddenClientOperation();
if((forbiddenClientOperation & ProtoConstants.ForbiddenClientGroupOperationMask.Forbidden_Kickoff_Group_Member) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
}
//send notify message first, then kickoff the member
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
sendGroupNotification(fromUser, groupInfo.getTargetId(), request.getToLineList(), request.getNotifyContent());
@@ -31,6 +31,19 @@ public class ModifyGroupInfoHandler extends GroupHandler<WFCMessage.ModifyGroupI
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_User) {
int forbiddenClientOperation = m_messagesStore.getGroupForbiddenClientOperation();
if(request.getType() == Modify_Group_Mute) {
if((forbiddenClientOperation & ProtoConstants.ForbiddenClientGroupOperationMask.Forbidden_Mute_Group) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
} else {
if((forbiddenClientOperation & ProtoConstants.ForbiddenClientGroupOperationMask.Forbidden_Modify_Group_Info) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
}
}
ErrorCode errorCode= m_messagesStore.modifyGroupInfo(fromUser, request.getGroupId(), request.getType(), request.getValue(), isAdmin);
if (errorCode == ERROR_CODE_SUCCESS) {
if(request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -31,7 +31,14 @@ public class QuitGroupHandler extends GroupHandler<WFCMessage.QuitGroupRequest>
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
ErrorCode errorCode = m_messagesStore.quitGroup(fromUser, request.getGroupId());
if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_User) {
int forbiddenClientOperation = m_messagesStore.getGroupForbiddenClientOperation();
if((forbiddenClientOperation & ProtoConstants.ForbiddenClientGroupOperationMask.Forbidden_Quit_Group) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
}
ErrorCode errorCode = m_messagesStore.quitGroup(fromUser, request.getGroupId(), isAdmin);
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
sendGroupNotification(fromUser, request.getGroupId(), request.getToLineList(), request.getNotifyContent());
@@ -40,6 +40,11 @@ public class RouteHandler extends IMHandler<WFCMessage.RouteRequest> {
return ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH;
}
if(request.getPlatform() != session.getPlatform() && m_messagesStore.existSignatures()) {
LOG.error("Session <{}, {}> platform is {} mismatch the request {}", session.getUsername(), session.getClientID(), session.getPlatform(), request.getPlatform());
return ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH;
}
String serverIp = mServer.getServerIp();
String longPort = mServer.getLongPort();
String shortPort = mServer.getShortPort();
@@ -22,6 +22,7 @@ import cn.wildfirechat.common.ErrorCode;
import io.netty.buffer.Unpooled;
import win.liyufan.im.*;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -33,6 +34,7 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
private String mForwardUrl = null;
private String mSensitiveMessageForwardUrl = null;
private Set<Integer> mForwardMessageTypes = new HashSet<>();
private Set<Integer> mForwardExcludeMessageTypes = new HashSet<>();
private String mMentionForwardUrl = null;
private int mBlacklistStrategy = 0; //黑名单中时,0失败,1吞掉。
private boolean mNoForwardAdminMessage = false;
@@ -47,12 +49,9 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
mForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Url);
if (!StringUtil.isNullOrEmpty(mForwardUrl)) {
String forwardTypes = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Types);
if (!StringUtil.isNullOrEmpty(forwardTypes)) {
String[] tss = forwardTypes.split(",");
for (String ts:tss) {
mForwardMessageTypes.add(Integer.parseInt(ts.trim()));
}
}
parseTypes(mForwardMessageTypes, forwardTypes);
String excludeTypes = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Exclude_Types);
parseTypes(mForwardExcludeMessageTypes, excludeTypes);
}
mSensitiveMessageForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Sensitive_Forward_Url);
mMentionForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_MentionMsg_Forward_Url);
@@ -97,6 +96,30 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
}
private void parseTypes(Collection<Integer> collection, String types) {
try {
if (!StringUtil.isNullOrEmpty(types)) {
String[] tss = types.split(",");
for (String ts:tss) {
if(ts.contains("-")) {
String[] ss = ts.split("-");
if(ss.length == 2) {
int begin = Integer.parseInt(ss[0]);
int end = Integer.parseInt(ss[1]);
for (int i = begin; i <= end ; i++) {
collection.add(i);
}
}
} else {
collection.add(Integer.parseInt(ts.trim()));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.Message message, Qos1PublishHandler.IMCallback callback) {
ErrorCode errorCode = ErrorCode.ERROR_CODE_SUCCESS;
@@ -159,6 +182,15 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
}
}
}
} else if (message.getConversation().getType() == ProtoConstants.ConversationType.ConversationType_Group) {
if(m_messagesStore.getGroupInfo(message.getConversation().getTarget()) == null) {
return ErrorCode.ERROR_CODE_NOT_EXIST;
}
} else if (message.getConversation().getType() == ProtoConstants.ConversationType.ConversationType_Channel) {
WFCMessage.ChannelInfo channelData = m_messagesStore.getChannelInfo(message.getConversation().getTarget());
if(channelData == null || channelData.getStatus() == ProtoConstants.ChannelState.Channel_State_Mask_Deleted) {
return ErrorCode.ERROR_CODE_NOT_EXIST;
}
}
long timestamp = System.currentTimeMillis();
@@ -179,7 +211,7 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
}
}
if (!StringUtil.isNullOrEmpty(mForwardUrl) && (mForwardMessageTypes.isEmpty() || mForwardMessageTypes.contains(message.getContent().getType())) && !(isAdmin && mNoForwardAdminMessage)) {
if (!StringUtil.isNullOrEmpty(mForwardUrl) && (mForwardMessageTypes.isEmpty() || mForwardMessageTypes.contains(message.getContent().getType())) && !(isAdmin && mNoForwardAdminMessage) && !mForwardExcludeMessageTypes.contains(message.getContent().getType())) {
publisher.forwardMessage(message, mForwardUrl, outputClient);
}
@@ -30,6 +30,13 @@ public class SetGroupManagerHandler extends GroupHandler<WFCMessage.SetGroupMana
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_User) {
int forbiddenClientOperation = m_messagesStore.getGroupForbiddenClientOperation();
if((forbiddenClientOperation & ProtoConstants.ForbiddenClientGroupOperationMask.Forbidden_Set_Group_Manage) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
}
ErrorCode errorCode = m_messagesStore.setGroupManager(fromUser, request.getGroupId(), request.getType(), request.getUserIdList(), isAdmin);
if (errorCode == ERROR_CODE_SUCCESS) {
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -30,6 +30,13 @@ public class TransferGroupHandler extends GroupHandler<WFCMessage.TransferGroupR
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_User) {
int forbiddenClientOperation = m_messagesStore.getGroupForbiddenClientOperation();
if((forbiddenClientOperation & ProtoConstants.ForbiddenClientGroupOperationMask.Forbidden_Transfer_Group) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
}
ErrorCode errorCode = m_messagesStore.transferGroup(fromUser, request.getGroupId(), request.getNewOwner(), isAdmin);
if (errorCode == ERROR_CODE_SUCCESS) {
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -25,9 +25,11 @@ public class UploadDeviceTokenHandler extends IMHandler<WFCMessage.UploadDeviceT
session.setPlatform(request.getPlatform());
session.setAppName(request.getAppName());
if (request.getPlatform() == ProtoConstants.Platform.Platform_iOS && request.getPushType() == 2) {
LOG.info("Set device token, userId {}", fromUser);
session.setVoipDeviceToken(request.getDeviceToken());
m_sessionsStore.updateSessionToken(session, true);
} else {
LOG.info("Set device token, userId {}, platform {}, pushType {}", fromUser, session.getPlatform(), request.getPushType());
session.setDeviceToken(request.getDeviceToken());
session.setPushType(request.getPushType());
m_sessionsStore.updateSessionToken(session, false);
@@ -1493,12 +1493,6 @@ public class DatabaseStore {
if (!strEqual(session.getAppName(), request.getApp())) {
sql += " `_package_name` = ?,";
}
if (session.getPlatform() != request.getPlatform()) {
sql += " `_platform` = ?,";
}
if (session.getPushType() != request.getPushType()) {
sql += " `_push_type` = ?,";
}
if (!strEqual(session.getDeviceName(), request.getDeviceName())) {
sql += " `_device_name` = ?,";
}
@@ -1526,12 +1520,6 @@ public class DatabaseStore {
if (!strEqual(session.getAppName(), request.getApp())) {
statement.setString(index++, request.getApp());
}
if (session.getPlatform() != request.getPlatform()) {
statement.setInt(index++, request.getPlatform());
}
if (session.getPushType() != request.getPushType()) {
statement.setInt(index++, request.getPushType() >= 32 ? 0 : request.getPushType());
}
if (!strEqual(session.getDeviceName(), request.getDeviceName())) {
statement.setString(index++, request.getDeviceName());
}
@@ -1712,6 +1700,35 @@ public class DatabaseStore {
return null;
}
Set<String> getCommonGroupIds(String userId1, String userId2) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet rs = null;
try {
connection = DBUtil.getConnection();
String sql = "select t1._gid from t_group_member t1, t_group_member t2 where t1._mid = ? and t2._mid = ? and t1._type <> 4 and t2._type <> 4 and t1._gid = t2._gid";
statement = connection.prepareStatement(sql);
statement.setString(1, userId1);
statement.setString(2, userId2);
rs = statement.executeQuery();
Set<String> out = new HashSet<>();
while (rs.next()) {
String uid = rs.getString(1);
out.add(uid);
}
return out;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Utility.printExecption(LOG, e, RDBS_Exception);
} finally {
DBUtil.closeDB(connection, statement, rs);
}
return null;
}
void updateGroupMemberDt(final String groupId, final long dt) {
Connection connection = null;
PreparedStatement statement = null;
@@ -49,6 +49,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
@@ -121,6 +122,8 @@ public class MemoryMessagesStore implements IMessagesStore {
private Lock mReadLock = mLock.readLock();
private Lock mWriteLock = mLock.writeLock();
private Lock mUserSettingLock = new ReentrantLock();
private final DatabaseStore databaseStore;
private ConcurrentHashMap<String, Long> userMaxPullSeq = new ConcurrentHashMap<>();
@@ -138,11 +141,14 @@ public class MemoryMessagesStore implements IMessagesStore {
private long mFriendRequestDuration = 7 * 24 * 60 * 60 * 1000;
private long mFriendRejectDuration = 30 * 24 * 60 * 60 * 1000;
private long mFriendRequestExpiration = 7 * 24 * 60 * 60 * 1000;
private boolean mFriendNewWelcomeMessage = true;
private boolean mMultiPlatformNotification = false;
private boolean mMobileDefaultSilentWhenPCOnline = true;
private boolean mDisableStrangerChat = false;
private Set<String> mAllowStrangerChatSet = new HashSet<>();
private Set<String> mClientSignatureSet = new HashSet<>();
private boolean mRejectEmptySignature = true;
private long mChatroomParticipantIdleTime = 900000;
private boolean mChatroomRejoinWhenActive = true;
@@ -165,6 +171,7 @@ public class MemoryMessagesStore implements IMessagesStore {
private boolean mGroupAllowClientCustomOperationNotification;
private boolean mGroupAllowRobotCustomOperationNotification;
private int mGroupVisibleQuitKickoffNotification;
private int mGroupForbiddenClientOperation;
private int mSyncDataPartSize = 0;
private boolean keepDisplayNameWhenDestroyUser = true;
@@ -188,6 +195,27 @@ public class MemoryMessagesStore implements IMessagesStore {
IS_CHATROOM_MESSAGE_REMOTE_HISTORY_MESSAGE = "1".equals(m_Server.getConfig().getProperty(MESSAGE_Remote_Chatroom_History_Message, "1"));
Constants.MAX_MESSAGE_QUEUE = Integer.parseInt(m_Server.getConfig().getProperty(MESSAGE_Max_Queue));
try {
String signatureList = m_Server.getConfig().getProperty(BrokerConstants.CONNECT_CLIENT_SIGNATURE_LIST);
if(!StringUtil.isNullOrEmptyAfterTrim(signatureList)) {
signatureList = signatureList.replace(",", ",");
for (String s : signatureList.split(",")) {
mClientSignatureSet.add(s.trim());
}
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if(!mClientSignatureSet.isEmpty()) {
mRejectEmptySignature = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CONNECT_REJECT_EMPTY_SIGNATURE, "true"));
} else {
mRejectEmptySignature = false;
}
} catch (Exception e) {
}
try {
mDisableStrangerChat = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.MESSAGE_Disable_Stranger_Chat));
} catch (Exception e) {
@@ -196,6 +224,18 @@ public class MemoryMessagesStore implements IMessagesStore {
printMissConfigLog(MESSAGE_Disable_Stranger_Chat, mDisableStrangerChat + "");
}
try {
String allowStrangerList = m_Server.getConfig().getProperty(BrokerConstants.MESSAGE_Allow_Stranger_Chat_List);
if(!StringUtil.isNullOrEmptyAfterTrim(allowStrangerList)) {
allowStrangerList = allowStrangerList.replace(",", ",");
for (String s : allowStrangerList.split(",")) {
mAllowStrangerChatSet.add(s.trim());
}
}
} catch (Exception e) {
e.printStackTrace();
}
try {
mMultiPlatformNotification = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.SERVER_MULTI_PLATFROM_NOTIFICATION, "false"));
} catch (Exception e) {
@@ -256,15 +296,6 @@ public class MemoryMessagesStore implements IMessagesStore {
printMissConfigLog(FRIEND_Request_Expiration_Duration, mFriendRequestExpiration + "");
}
try {
mFriendNewWelcomeMessage = Boolean.parseBoolean(m_Server.getConfig().getProperty(FRIEND_New_Welcome_Message));
} catch (Exception e) {
e.printStackTrace();
Utility.printExecption(LOG, e);
printMissConfigLog(FRIEND_New_Welcome_Message, mFriendNewWelcomeMessage + "");
}
try {
mChatroomRejoinWhenActive = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CHATROOM_Rejoin_When_Active));
} catch (Exception e) {
@@ -430,6 +461,35 @@ public class MemoryMessagesStore implements IMessagesStore {
}
try {
String s = server.getConfig().getProperty(GROUP_Forbidden_Client_Operation, null);
if(!StringUtil.isNullOrEmptyAfterTrim(s)) {
s = s.toLowerCase();
if(s.startsWith("0x")) {
s = s.substring(2);
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(s.length()-1-i);
mGroupForbiddenClientOperation <<= 4;
if(ch <= 9) {
mGroupForbiddenClientOperation += ch;
} else {
if(ch > 'f' || ch < 'a') {
mGroupForbiddenClientOperation = 0;
LOG.error("Invalid group.forbidden_client_operation {}", server.getConfig().getProperty(GROUP_Forbidden_Client_Operation, null));
break;
} else {
mGroupForbiddenClientOperation += (ch - 'a' + 10);
}
}
}
} else {
mGroupForbiddenClientOperation = Integer.parseInt(s);
}
}
} catch (NumberFormatException e) {
LOG.error("Invalid group.forbidden_client_operation {}", server.getConfig().getProperty(GROUP_Forbidden_Client_Operation, null));
}
try {
mSyncDataPartSize = Integer.parseInt(server.getConfig().getProperty(SYNC_Data_Part_Size, "0"));
} catch (Exception e) {
@@ -1276,6 +1336,9 @@ public class MemoryMessagesStore implements IMessagesStore {
int maxCount = Integer.MAX_VALUE;
if (!isAdmin) {
if (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
boolean isMember = false;
boolean isManager = false;
WFCMessage.GroupMember gm = getGroupMember(groupId, operator);
@@ -1416,18 +1479,22 @@ public class MemoryMessagesStore implements IMessagesStore {
void removeFavGroup(String groupId, List<String> memberIds) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
databaseStore.removeFavGroup(groupId, memberIds);
for (String member : memberIds) {
userSettingMap.remove(member);
try {
mUserSettingLock.lock();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
for (String member : memberIds) {
userSettingMap.remove(member);
}
} finally {
mUserSettingLock.unlock();
}
}
@Override
public ErrorCode quitGroup(String operator, String groupId) {
public ErrorCode quitGroup(String operator, String groupId, boolean admin) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
IMap<String, WFCMessage.GroupInfo> mIMap = hzInstance.getMap(GROUPS_MAP);
@@ -1447,6 +1514,10 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_NOT_EXIST;
}
if(!admin && groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if (groupInfo.getType() != ProtoConstants.GroupType.GroupType_Free && groupInfo.getOwner() != null && groupInfo.getOwner().equals(operator)) {
MultiMap<String, WFCMessage.GroupMember> groupMembers = hzInstance.getMultiMap(GROUP_MEMBERS);
Collection<WFCMessage.GroupMember> members = groupMembers.get(groupId);
@@ -1489,7 +1560,7 @@ public class MemoryMessagesStore implements IMessagesStore {
int[] removedCount = new int[1];
for (String groupId : groupIds) {
removedCount[0] = 0;
ErrorCode errorCode = quitGroup(userId, groupId);
ErrorCode errorCode = quitGroup(userId, groupId, true);
LOG.info("clear user {} group {} result {}", userId, groupId, errorCode);
}
}
@@ -1497,13 +1568,17 @@ public class MemoryMessagesStore implements IMessagesStore {
private void removeGroupUserSettings(String groupId, List<String> users) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
databaseStore.removeGroupUserSettings(groupId, users);
for (String userId:users) {
userSettingMap.remove(userId);
try {
mUserSettingLock.lock();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
for (String userId:users) {
userSettingMap.remove(userId);
}
} finally {
mUserSettingLock.unlock();
}
}
@Override
@@ -1521,6 +1596,7 @@ public class MemoryMessagesStore implements IMessagesStore {
}
if (!isAdmin && (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Free ||
groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization ||
(groupInfo.getType() == ProtoConstants.GroupType.GroupType_Restricted || groupInfo.getType() == ProtoConstants.GroupType.GroupType_Normal)
&& (groupInfo.getOwner() == null || !groupInfo.getOwner().equals(operator)))) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
@@ -1564,7 +1640,11 @@ public class MemoryMessagesStore implements IMessagesStore {
}
if (!isAdmin) {
if (oldInfo.getType() == ProtoConstants.GroupType.GroupType_Restricted) {
if (oldInfo.getType() == ProtoConstants.GroupType.GroupType_Organization && modifyType != Modify_Group_Extra && modifyType != Modify_Group_Mute && modifyType != Modify_Group_PrivateChat) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if (oldInfo.getType() == ProtoConstants.GroupType.GroupType_Restricted || oldInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
boolean isAllow = false;
if ((oldInfo.getOwner() != null && oldInfo.getOwner().equals(operator))) {
isAllow = true;
@@ -1841,6 +1921,11 @@ public class MemoryMessagesStore implements IMessagesStore {
return databaseStore.getUserGroupIds(userId);
}
@Override
public Set<String> getCommonGroupIds(String userId1, String userId2) {
return databaseStore.getCommonGroupIds(userId1, userId2);
}
@Override
public ErrorCode getGroupMembers(String fromUser, String groupId, long maxDt, List<WFCMessage.GroupMember> members) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
@@ -1925,6 +2010,10 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if (!isAdmin && groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
//check the new owner is in member list? is that necessary?
long updateDt = System.currentTimeMillis();
groupInfo = groupInfo.toBuilder().setOwner(newOwner).setUpdateDt(updateDt).setMemberUpdateDt(updateDt).build();
@@ -2789,6 +2878,11 @@ public class MemoryMessagesStore implements IMessagesStore {
if (mDisableStrangerChat) {
//在禁止私聊时,是否是允许私聊的用户id
if(mAllowStrangerChatSet.contains(targetUser)) {
return ErrorCode.ERROR_CODE_SUCCESS;
}
//在禁止私聊时,允许机器人,物联网设备及管理员进行私聊。
IMap<String, WFCMessage.User> mUserMap = hzInstance.getMap(USERS);
WFCMessage.User target = mUserMap.get(targetUser);
@@ -3372,10 +3466,12 @@ public class MemoryMessagesStore implements IMessagesStore {
if(robotData != null && !StringUtil.isNullOrEmpty(robotData.getCallback()) && !StringUtil.isNullOrEmpty(robotData.getSecret())) {
try {
URL url = new URL(robotData.getCallback());
if(url.getHost().equals(host)) {
if(url.getHost() == null) {
LOG.error("get application auth code error, application callback is invalid url");
} else if(url.getHost().equals(host)) {
secret = robotData.getSecret();
} else {
LOG.warn("get application auth code error, request host is not the same host with callback host");
LOG.error("get application auth code error, request host is not the same host with callback host");
}
} catch (MalformedURLException e) {
e.printStackTrace();
@@ -3396,10 +3492,12 @@ public class MemoryMessagesStore implements IMessagesStore {
if(channelData != null && !StringUtil.isNullOrEmpty(channelData.getCallback()) && !StringUtil.isNullOrEmpty(channelData.getSecret())) {
try {
URL url = new URL(channelData.getCallback());
if(url.getHost().equals(host)) {
if(url.getHost() == null) {
LOG.error("get application auth code error, application callback is invalid url");
} else if(url.getHost().equals(host)) {
secret = channelData.getSecret();
} else {
LOG.warn("get application auth code error, request host is not the same host with callback host");
LOG.error("get application auth code error, request host is not the same host with callback host");
}
} catch (MalformedURLException e) {
e.printStackTrace();
@@ -3505,11 +3603,6 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_AUTH_FAILURE;
}
@Override
public boolean isNewFriendWelcomeMessage() {
return mFriendNewWelcomeMessage;
}
@Override
public ErrorCode deleteFriend(String userId, String friendUid, long[] head) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
@@ -3559,9 +3652,15 @@ public class MemoryMessagesStore implements IMessagesStore {
IMap<String, WFCMessage.GroupInfo> mGroups = hzInstance.getMap(GROUPS_MAP);
IMap<String, WFCMessage.ChannelInfo> mChannels = hzInstance.getMap(CHANNELS);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
Collection<WFCMessage.UserSettingEntry> entries;
try {
mUserSettingLock.lock();
entries = userSettingMap.get(userId);
if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
}
} finally {
mUserSettingLock.unlock();
}
ErrorCode ec = ErrorCode.ERROR_CODE_NOT_MODIFIED;
@@ -3640,17 +3739,22 @@ public class MemoryMessagesStore implements IMessagesStore {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
}
try {
mUserSettingLock.lock();
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
}
if (entries != null) {
for (WFCMessage.UserSettingEntry entry : entries) {
if (entry.getScope() == scope && (key== null || key.equals(entry.getKey()))) {
return entry;
if (entries != null) {
for (WFCMessage.UserSettingEntry entry : entries) {
if (entry.getScope() == scope && (key== null || key.equals(entry.getKey()))) {
return entry;
}
}
}
} finally {
mUserSettingLock.unlock();
}
return null;
@@ -3661,19 +3765,25 @@ public class MemoryMessagesStore implements IMessagesStore {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
}
List<WFCMessage.UserSettingEntry> result;
try {
mUserSettingLock.lock();
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
}
List<WFCMessage.UserSettingEntry> result = new ArrayList<>();
if (entries != null) {
for (WFCMessage.UserSettingEntry entry : entries
) {
if (entry.getScope() == scope) {
result.add(entry);
result = new ArrayList<>();
if (entries != null) {
for (WFCMessage.UserSettingEntry entry : entries
) {
if (entry.getScope() == scope) {
result.add(entry);
}
}
}
} finally {
mUserSettingLock.unlock();
}
return result;
@@ -3684,28 +3794,34 @@ public class MemoryMessagesStore implements IMessagesStore {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
if (entries == null) {
entries = new ArrayList<>();
long updateDt = 0;
try {
mUserSettingLock.lock();
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
if (entries == null) {
entries = new ArrayList<>();
}
}
}
long updateDt = System.currentTimeMillis();
WFCMessage.UserSettingEntry settingEntry = WFCMessage.UserSettingEntry.newBuilder().setScope(request.getScope()).setKey(request.getKey()).setValue(request.getValue()).setUpdateDt(updateDt).build();
databaseStore.persistUserSetting(userId, settingEntry);
updateDt = System.currentTimeMillis();
WFCMessage.UserSettingEntry settingEntry = WFCMessage.UserSettingEntry.newBuilder().setScope(request.getScope()).setKey(request.getKey()).setValue(request.getValue()).setUpdateDt(updateDt).build();
databaseStore.persistUserSetting(userId, settingEntry);
for (WFCMessage.UserSettingEntry entry : entries
) {
if (entry.getScope() == request.getScope() && entry.getKey().equals(request.getKey())) {
userSettingMap.remove(userId, entry);
userSettingMap.put(userId, settingEntry);
break;
for (WFCMessage.UserSettingEntry entry : entries
) {
if (entry.getScope() == request.getScope() && entry.getKey().equals(request.getKey())) {
userSettingMap.remove(userId, entry);
userSettingMap.put(userId, settingEntry);
break;
}
}
}
userSettingMap.put(userId, settingEntry);
userSettingMap.put(userId, settingEntry);
} finally {
mUserSettingLock.unlock();
}
if(request.getScope() == UserSettingScope.kUserSettingConversationSilent) {
int firstSplit = request.getKey().indexOf("-");
@@ -3730,8 +3846,14 @@ public class MemoryMessagesStore implements IMessagesStore {
public void clearUserSettings(String userId) {
databaseStore.clearUserSetting(userId);
HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
userSettingMap.remove(userId);
try {
mUserSettingLock.lock();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
userSettingMap.remove(userId);
} finally {
mUserSettingLock.unlock();
}
}
@Override
@@ -4122,6 +4244,11 @@ public class MemoryMessagesStore implements IMessagesStore {
return mGroupVisibleQuitKickoffNotification;
}
@Override
public int getGroupForbiddenClientOperation() {
return mGroupForbiddenClientOperation;
}
@Override
public boolean isForwardMessageWithClientInfo() {
return mForwardMessageWithClientInfo;
@@ -4262,6 +4389,34 @@ public class MemoryMessagesStore implements IMessagesStore {
return UUIDGenerator.getUUID();
}
@Override
public boolean checkSignature(String signature) {
if(mClientSignatureSet.isEmpty()) {
return true;
}
if(StringUtil.isNullOrEmptyAfterTrim(signature)) {
if(mRejectEmptySignature) {
LOG.info("Failed check signature, no signature");
return false;
} else {
return true;
}
} else {
if(mClientSignatureSet.contains(signature)) {
return true;
} else {
LOG.info("Failed check signature, signature no matched");
return false;
}
}
}
@Override
public boolean existSignatures() {
return !mClientSignatureSet.isEmpty();
}
@Override
public void storeRetained(Topic topic, StoredMessage storedMessage) {
LOG.debug("Store retained message for topic={}, CId={}", topic, storedMessage.getClientID());
@@ -415,7 +415,7 @@ public class MemorySessionStore implements ISessionsStore {
remove = true;
}
} else if(platform == ProtoConstants.Platform.Platform_OSX || platform == ProtoConstants.Platform.Platform_Windows || platform == ProtoConstants.Platform.Platform_LINUX) {
if (s.getPlatform() == ProtoConstants.Platform.Platform_OSX || s.getPlatform() == ProtoConstants.Platform.Platform_Windows || platform == ProtoConstants.Platform.Platform_LINUX) {
if (s.getPlatform() == ProtoConstants.Platform.Platform_OSX || s.getPlatform() == ProtoConstants.Platform.Platform_Windows || s.getPlatform() == ProtoConstants.Platform.Platform_LINUX) {
remove = true;
}
} else {
@@ -378,7 +378,7 @@ public class Server {
LOG.info("Configuring embedded Hazelcast instance");
serverIp = getServerIp(config);
String hzConfigPath = "config/hazelcast.xml";
String hzConfigPath = System.getProperty("hazelcast.configuration", "config/hazelcast.xml");
if (hzConfigPath != null) {
boolean isHzConfigOnClasspath = this.getClass().getClassLoader().getResource(hzConfigPath) != null;
Config hzconfig = isHzConfigOnClasspath
@@ -110,7 +110,7 @@ public interface IMessagesStore {
WFCMessage.GroupInfo createGroup(String operator, WFCMessage.GroupInfo groupInfo, List<WFCMessage.GroupMember> memberList, String memberExtra, boolean isAdmin);
ErrorCode addGroupMembers(String operator, boolean isAdmin, String groupId, List<WFCMessage.GroupMember> memberList, String extra);
ErrorCode kickoffGroupMembers(String operator, boolean isAdmin, String groupId, List<String> memberList);
ErrorCode quitGroup(String operator, String groupId);
ErrorCode quitGroup(String operator, String groupId, boolean admin);
void clearUserGroups(String userId);
ErrorCode dismissGroup(String operator, String groupId, boolean isAdmin);
ErrorCode modifyGroupInfo(String operator, String groupId, int modifyType, String value, boolean isAdmin);
@@ -119,6 +119,7 @@ public interface IMessagesStore {
List<WFCMessage.GroupInfo> getGroupInfos(List<WFCMessage.UserRequest> requests);
WFCMessage.GroupInfo getGroupInfo(String groupId);
Set<String> getUserGroupIds(String userId);
Set<String> getCommonGroupIds(String userId1, String userId2);
ErrorCode getGroupMembers(String fromUser, String groupId, long maxDt, List<WFCMessage.GroupMember> members);
WFCMessage.GroupMember getGroupMember(String groupId, String memberId);
ErrorCode transferGroup(String operator, String groupId, String newOwner, boolean isAdmin);
@@ -179,7 +180,6 @@ public interface IMessagesStore {
ErrorCode saveAddFriendRequest(String userId, WFCMessage.AddFriendRequest request, long[] head, boolean isAdmin);
ErrorCode handleFriendRequest(String userId, WFCMessage.HandleFriendRequest request, WFCMessage.Message.Builder msgBuilder, long[] heads, boolean isAdmin);
boolean isNewFriendWelcomeMessage();
ErrorCode deleteFriend(String userId, String friendUid, long[] head);
ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head);
FriendData getFriendData(String fromUser, String targetUserId);
@@ -233,6 +233,8 @@ public interface IMessagesStore {
boolean isAllowRobotCustomGroupNotification();
int getVisibleQuitKickoffNotification();
int getGroupForbiddenClientOperation();
boolean isForwardMessageWithClientInfo();
boolean isRobotCallbackWithClientInfo();
boolean isChannelCallbackWithClientInfo();
@@ -249,6 +251,9 @@ public interface IMessagesStore {
//使用了数据库,会比较慢,仅能用户用户/群组等id的生成
String getShortUUID();
boolean checkSignature(String signature);
boolean existSignatures();
/**
* Used to initialize all persistent store structures
*/
@@ -652,6 +652,8 @@ public class MessagesPublisher {
pushContent = "[文件]";
} else if(type == ProtoConstants.ContentType.Sticker) {
pushContent = "[表情]";
} else if (type == ProtoConstants.ContentType.Name_Card) {
pushContent = "[名片]";
}
}
@@ -48,6 +48,7 @@ import win.liyufan.im.HttpUtils;
import win.liyufan.im.IMTopic;
import win.liyufan.im.Utility;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -339,6 +340,24 @@ public class ProtocolProcessor {
failedCredentials(channel);
return false;
}
if(session.getPlatform() == ProtoConstants.Platform.Platform_Android || session.getPlatform() == ProtoConstants.Platform.Platform_APad) {
byte[] signature = msg.payload().signatureInBytes();
String basedSign = null;
if (signature != null && signature.length > 0) {
signature = AES.AESDecrypt(signature, session.getSecret(), true);
}
if (signature != null && signature.length > 0) {
basedSign = Base64.getEncoder().encodeToString(signature);
}
if (!m_messagesStore.checkSignature(basedSign)) {
LOG.error("Bad signature of session <{}, {}>", session.getUsername(), session.getClientID());
failedBadSignature(channel);
return false;
}
}
session.setMqttVersion(mqttVersion);
} else {
LOG.error("Client didn't supply any password and MQTT anonymous mode is disabled CId={}", clientId);
@@ -446,6 +465,11 @@ public class ProtocolProcessor {
LOG.info("Client {} failed to connect with bad username or password.", session);
}
private void failedBadSignature(Channel session) {
session.writeAndFlush(connAck(CONNECTION_REFUSED_SIGNATURE_FAILURE));
LOG.info("Client {} failed to connect with bad signature.", session);
}
private void setIdleTime(ChannelPipeline pipeline, int idleTime) {
if (pipeline.names().contains("idleStateHandler")) {
pipeline.remove("idleStateHandler");
@@ -32,6 +32,7 @@ public final class MqttConnectPayload {
private final byte[] willMessage;
private final String userName;
private final byte[] password;
private final byte[] signature;
/**
* @deprecated use {@link MqttConnectPayload#MqttConnectPayload(String,
@@ -50,7 +51,7 @@ public final class MqttConnectPayload {
willTopic,
willMessage.getBytes(CharsetUtil.UTF_8),
userName,
password.getBytes(CharsetUtil.UTF_8));
password.getBytes(CharsetUtil.UTF_8), null);
}
public MqttConnectPayload(
@@ -64,7 +65,7 @@ public final class MqttConnectPayload {
willTopic,
willMessage,
userName,
password);
password, null);
}
public MqttConnectPayload(
@@ -73,13 +74,15 @@ public final class MqttConnectPayload {
String willTopic,
byte[] willMessage,
String userName,
byte[] password) {
byte[] password,
byte[] signature) {
this.clientIdentifier = clientIdentifier;
this.willProperties = MqttProperties.withEmptyDefaults(willProperties);
this.willTopic = willTopic;
this.willMessage = willMessage;
this.userName = userName;
this.password = password;
this.signature = signature;
}
public String clientIdentifier() {
@@ -122,6 +125,10 @@ public final class MqttConnectPayload {
return password;
}
public byte[] signatureInBytes() {
return signature;
}
@Override
public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this))
@@ -29,6 +29,7 @@ public enum MqttConnectReturnCode {
CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05),
CONNECTION_REFUSED_UNEXPECT_NODE((byte) 0X06),
CONNECTION_REFUSED_SESSION_NOT_EXIST((byte) 0x07),
CONNECTION_REFUSED_SIGNATURE_FAILURE((byte) 0x08),
//MQTT 5 codes
CONNECTION_REFUSED_UNSPECIFIED_ERROR((byte) 0x80),
CONNECTION_REFUSED_MALFORMED_PACKET((byte) 0x81),
@@ -146,7 +146,7 @@ public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState
* bytes for the remaining length.
*
* @see
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180841
* <a href="https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180841">...</a>
*
* @param buffer the buffer to decode from
* @return the fixed header
@@ -534,7 +534,7 @@ public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState
final MqttProperties willProperties;
if (mqttConnectVariableHeader.isWillFlag()) {
if (mqttVersion == MqttVersion.MQTT_5) {
if (mqttVersion.protocolLevel() <= MqttVersion.MQTT_5.protocolLevel()) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
willProperties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
@@ -550,6 +550,7 @@ public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState
}
Result<String> decodedUserName = null;
byte[] decodedPassword = null;
byte[] decodedSignature = null;
if (mqttConnectVariableHeader.hasUserName()) {
decodedUserName = decodeString(buffer);
numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed;
@@ -558,6 +559,10 @@ public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState
decodedPassword = decodeByteArray(buffer);
numberOfBytesConsumed += decodedPassword.length + 2;
}
if (mqttConnectVariableHeader.isWillRetain()) {
decodedSignature = decodeByteArray(buffer);
numberOfBytesConsumed += decodedSignature.length + 2;
}
final MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload(
@@ -566,7 +571,9 @@ public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState
decodedWillTopic != null ? decodedWillTopic.value : null,
decodedWillMessage,
decodedUserName != null ? decodedUserName.value : null,
decodedPassword);
decodedPassword,
decodedSignature
);
return new Result<MqttConnectPayload>(mqttConnectPayload, numberOfBytesConsumed);
}
@@ -208,7 +208,7 @@ public final class MqttMessageBuilders {
keepAliveSecs,
properties);
MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload(clientId, willProperties, willTopic, willMessage, username, password);
new MqttConnectPayload(clientId, willProperties, willTopic, willMessage, username, password, null);
return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload);
}
}
@@ -24,7 +24,6 @@ import com.mchange.v2.c3p0.ComboPooledDataSource;
import io.moquette.BrokerConstants;
import io.moquette.server.config.IConfig;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.configuration.FlywayConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,6 +49,7 @@ public class DBUtil {
public static void init(IConfig config) {
String embedDB = config.getProperty(BrokerConstants.EMBED_DB_PROPERTY_NAME);
String h2dbPath = config.getProperty(BrokerConstants.H2DB_PATH, "./h2db/wfchat");
boolean autoCleanMsgs = "true".equals(config.getProperty(BrokerConstants.DB_AUTO_CLEAN_HISTORY_MESSAGES));
if (embedDB != null && embedDB.equals("1")) {
IsEmbedDB = true;
@@ -68,7 +68,7 @@ public class DBUtil {
migrateLocation = "filesystem:./migrate/h2";
comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setJdbcUrl( "jdbc:h2:./h2db/wfchat;AUTO_SERVER=TRUE;MODE=MySQL" );
comboPooledDataSource.setJdbcUrl( "jdbc:h2:" + h2dbPath + ";AUTO_SERVER=TRUE;MODE=MySQL" );
comboPooledDataSource.setUser("SA");
comboPooledDataSource.setPassword("SA");
comboPooledDataSource.setMinPoolSize(5);
@@ -68,7 +68,9 @@ public class GitRepositoryState {
public static GitRepositoryState getGitRepositoryState() throws IOException {
Properties properties = new Properties();
try {
InputStream inputStream = new FileInputStream("config/git.properties");
String configPath = System.getProperty("wildfirechat.path", null);
File f = new File(configPath, "config/git.properties");
InputStream inputStream = new FileInputStream(f);
BufferedReader bf = new BufferedReader(new InputStreamReader(inputStream));
properties.load(bf);
} catch (IOException e) {
@@ -87,8 +87,8 @@ public class HttpUtils {
}
System.out.println(logText);
e.printStackTrace();
LOG.info(logText);
LOG.info("POST to {} with data {} failure", url, jsonStr);
LOG.error(logText);
LOG.error("POST to {} failed", url);
if(httpCallback != null) {
httpCallback.onFailure(-1, e.getMessage());
}
@@ -1,68 +0,0 @@
package win.liyufan.im;
import io.netty.util.internal.StringUtil;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class I18n {
private static ClassLoader Loader = null;
static {
String configPath = System.getProperty("wildfirechat.path", null);
String resBundlePaht = "config/i18n";
File file = new File(configPath, resBundlePaht);
URL[] urls = new URL[0];
try {
urls = new URL[]{file.toURI().toURL()};
Loader = new URLClassLoader(urls);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
private static Map<String, ResourceBundle> bundleMap = new ConcurrentHashMap<>();
public static String getString(String language, String key) {
ResourceBundle bundle = bundleMap.get(language);
if (StringUtil.isNullOrEmpty(language)) language = "zh_CN";
if (bundle == null) {
bundle = ResourceBundle.getBundle("messages", new Locale(language), Loader);
bundleMap.put(language, bundle);
}
try {
return bundle.getString(key);
} catch (Exception e) {
e.printStackTrace();
}
return "糟糕,字符串 " + key + " 没有找到";
}
public static void test() throws MalformedURLException {
// 设置定制的语言国家代码
Locale locale1 = new Locale("zh");
Locale locale2 = new Locale("en");
ResourceBundle rb = ResourceBundle.getBundle("messages", locale1, Loader);
// 获得相应的key值
String greeting = rb.getString("Above_Greeting_Message");
String userInfo = rb.getString("Friend_Can_Start_Chat");
System.out.println(greeting);
System.out.println(userInfo);
}
public static void main(String[] args) throws Exception {
test();
}
}
@@ -37,6 +37,8 @@ public interface IMTopic {
String GetGroupMemberTopic = "GPGM";
String TransferGroupTopic = "GTG";
String SetGroupManagerTopic = "GSM";
String GetMyGroupsTopic = "GMGS";
String GetCommonGroupsTopic = "GCGS";
String GetUserInfoTopic = "UPUI";
String ModifyMyInfoTopic = "MMI";
+2 -3
View File
@@ -6,7 +6,6 @@
<Properties>
<!--自定义一些常量,之后使用${变量名}引用-->
<Property name="MSG_LOG_HOME">./logs</Property>
<Property name="logFileName">test.log</Property>
<property name="charset">UTF-8</property>
<!--自定义的输出格式-->
<property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property>
@@ -21,14 +20,14 @@
<RollingRandomAccessFile name="RollingFileInfo" immediateFlush="false"
fileName="${MSG_LOG_HOME}/wildfirechat.log"
filePattern="${MSG_LOG_HOME}/backup/wildfirechat.%d{yyyyMMddHH}.zip">
filePattern="${MSG_LOG_HOME}/backup/wildfirechat.%d{yyyyMMddHHmm}.zip">
<Filters>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${pattern}" />
<Policies>
<!-- TimeBasedTriggeringPolicy :时间滚动策略,默认0点小时产生新的文件,interval="6" : 自定义文件滚动时间间隔,每隔6小时产生新文件, modulate="true" : 产生文件是否以0点偏移时间,即6点,12点,18点,0点-->
<!-- <TimeBasedTriggeringPolicy interval="6" modulate="true"/>-->
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<!-- SizeBasedTriggeringPolicy :文件大小滚动策略-->
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.96</version>
<version>1.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -77,6 +77,7 @@ public interface APIPath {
String Get_User_Groups = "/admin/group/of_user";
String Group_Set_Member_Alias = "/admin/group/member/set_alias";
String Group_Set_Member_Extra = "/admin/group/member/set_extra";
String Get_Common_Groups = "/admin/group/common_group";
String Create_Channel = "/admin/channel/create";
String Destroy_Channel = "/admin/channel/destroy";
@@ -93,6 +94,9 @@ public interface APIPath {
String Conference_Create = "/admin/conference/create";
String Conference_Destroy = "/admin/conference/destroy";
String Conference_Recording = "/admin/conference/recording";
String Conference_Rtp_Forward = "/admin/conference/rtp_forward";
String Conference_Stop_Rtp_Forward = "/admin/conference/stop_rtp_forward";
String Conference_List_Rtp_Forward = "/admin/conference/list_rtp_forward";
String Channel_User_Info = "/channel/user_info";
String Channel_Update_Profile = "/channel/update_profile";
@@ -100,6 +104,7 @@ public interface APIPath {
String Channel_Message_Send = "/channel/message/send";
String Channel_Subscribe = "/channel/subscribe";
String Channel_Subscriber_List = "/channel/subscriber_list";
String Channel_Is_Subscriber = "/channel/is_subscriber";
String Channel_Application_Get_UserInfo = "/channel/application/get_user_info";
String Robot_User_Info = "/robot/user_info";
@@ -61,6 +61,8 @@ public enum ErrorCode {
ERROR_CODE_SECRET_CHAT_MO_DISABLED(86, "origin side disable secret chat"),
ERROR_CODE_SECRET_CHAT_MT_DISABLED(87, "target side disable secret chat"),
ERROR_CODE_CONFERENCE_ERROR(90, "Conference server response error"),
ERROR_CODE_FUNCTION_DISABLED(220, "function disabled"),
ERROR_CODE_CHANNEL_NO_EXIST(236, "channel no exist"),
@@ -3,5 +3,7 @@ package cn.wildfirechat.pojos;
public class ConferenceLeaveEvent {
public String userId;
public String roomId;
public boolean rejoin;
public boolean kicked;
public boolean screenSharing;
}
@@ -1,6 +1,15 @@
package cn.wildfirechat.pojos;
import java.util.List;
public class PojoConferenceParticipant {
public static class Stream {
public String type;
public String mid;
public String codec;
}
public String userId;
public boolean publishing;
public List<Stream> streams;
}
@@ -0,0 +1,28 @@
package cn.wildfirechat.pojos;
public class PojoConferenceRtpForwardReq {
public String roomId;
public String publisherId;
public String host;
public int audioPort;
public int audioPt;
public long audioSSRC;
public int videoPort;
public int videoPt;
public long videoSSRC;
public PojoConferenceRtpForwardReq() {
}
public PojoConferenceRtpForwardReq(String roomId, String publisherId, String host, int audioPort, int audioPt, long audioSSRC, int videoPort, int videoPt, long videoSSRC) {
this.roomId = roomId;
this.publisherId = publisherId;
this.host = host;
this.audioPort = audioPort;
this.audioPt = audioPt;
this.audioSSRC = audioSSRC;
this.videoPort = videoPort;
this.videoPt = videoPt;
this.videoSSRC = videoSSRC;
}
}
@@ -0,0 +1,21 @@
package cn.wildfirechat.pojos;
import java.util.List;
public class PojoConferenceRtpForwarders {
public static class RtpForwarder {
public static class RtpStream {
public long streamId;
public String type;
public String host;
public int port;
public long ssrc;
public int pt;
}
public String publisherId;
public List<RtpStream> streams;
}
public String roomId;
public List<RtpForwarder> forwarders;
}
@@ -0,0 +1,16 @@
package cn.wildfirechat.pojos;
public class PojoConferenceStopRtpForwardReq {
public String roomId;
public String publisherId;
public long streamId;
public PojoConferenceStopRtpForwardReq() {
}
public PojoConferenceStopRtpForwardReq(String roomId, String publisherId, long streamId) {
this.roomId = roomId;
this.publisherId = publisherId;
this.streamId = streamId;
}
}
@@ -19,6 +19,8 @@ public class ProtoConstants {
int GroupType_Free = 1;
//member can only quit, owner can do all the operations
int GroupType_Restricted = 2;
//member can not do anything, server api manage the group
int GroupType_Organization = 3;
}
//message GroupMember -> type
@@ -290,6 +292,34 @@ public class ProtoConstants {
int ApplicationType_Admin = 2;
}
//禁止客户端群操作。
// 第1位是禁止创建群组,
// 第2位是禁止销毁群组,
// 3位禁止加入群,
// 4位禁止退出群,
// 5位禁止邀请群成员,
// 6位禁止移出群成员,
// 7位禁止转移群
// 8位禁止设置群管理员
// 9位禁止允许群成员(专业版支持),
// 10位禁止群禁言
// 11位禁止修改群组信息,
// 12位禁止群成员禁言(专业版支持)
public interface ForbiddenClientGroupOperationMask {
int Forbidden_Create_Group = 0x01;
int Forbidden_Dismiss_Group = 0x02;
int Forbidden_Join_Group = 0x04;
int Forbidden_Quit_Group = 0x08;
int Forbidden_Invite_Group_Member = 0x10;
int Forbidden_Kickoff_Group_Member = 0x20;
int Forbidden_Transfer_Group = 0x40;
int Forbidden_Set_Group_Manage = 0x80;
int Forbidden_Allow_Group_Member = 0x100;
int Forbidden_Mute_Group = 0x200;
int Forbidden_Modify_Group_Info = 0x400;
int Forbidden_Mute_Group_Member = 0x800;
}
public static final int MESSAGE_CONTENT_TYPE_CREATE_GROUP = 104;
public static final int MESSAGE_CONTENT_TYPE_ADD_GROUP_MEMBER = 105;
public static final int MESSAGE_CONTENT_TYPE_KICKOF_GROUP_MEMBER = 106;
+104 -2
View File
@@ -6,7 +6,7 @@
<!--<relativePath>../pom.xml</relativePath>-->
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.96</version>
<version>1.1</version>
</parent>
<artifactId>distribution</artifactId>
@@ -93,7 +93,109 @@
</execution>
</executions>
</plugin>
<plugin>
<artifactId>jdeb</artifactId>
<groupId>org.vafer</groupId>
<version>1.8</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jdeb</goal>
</goals>
<configuration>
<controlDir>${project.basedir}/src/main/deb/control</controlDir>
<skipPOMs>false</skipPOMs>
<deb>${project.build.directory}/im-server-${project.version}.deb</deb>
<dataSet>
<data>
<src>${project.build.directory}/distribution-${project.version}-bundle-tar.tar.gz</src>
<type>archive</type>
<mapper>
<type>perm</type>
<prefix>/opt/im-server</prefix>
</mapper>
</data>
<data>
<src>${project.basedir}/../systemd/im-server.service</src>
<type>file</type>
<mapper>
<type>perm</type>
<prefix>/usr/lib/systemd/system</prefix>
</mapper>
</data>
</dataSet>
</configuration>
</execution>
</executions>
</plugin>
<!-- 打包RPM 包需要本地有rpm命令才可以,linux和mac都可以安装rpm。如果是windows需要cygwin安装rpm才可以,如果不需要rpm包,可以把这个plugin注释掉-->
<!-- 打包RPM插件,如果需要打包RPM格式请打开下面注释 -->
<!-- <plugin>-->
<!-- <groupId>org.codehaus.mojo</groupId>-->
<!-- <artifactId>rpm-maven-plugin</artifactId>-->
<!-- <version>2.2.0</version>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <id>generate-rpm</id>-->
<!-- <goals>-->
<!-- <goal>rpm</goal>-->
<!-- </goals>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- <configuration>-->
<!-- <group>Applications/Chat</group>-->
<!-- <name>im-server</name>-->
<!-- <needarch>noarch</needarch>-->
<!-- <targetOS>linux</targetOS>-->
<!-- <prefix>/opt/im-server</prefix>-->
<!-- <defineStatements>-->
<!-- <defineStatement>_unpackaged_files_terminate_build 0</defineStatement>-->
<!-- </defineStatements>-->
<!-- <copyTo>-->
<!-- target/im-server-${project.version}.rpm-->
<!-- </copyTo>-->
<!-- <requires>-->
<!-- <require>java-1.8.0-openjdk-headless</require>-->
<!-- </requires>-->
<!-- <mappings>-->
<!-- <mapping>-->
<!-- <directory>/opt/im-server</directory>-->
<!-- <filemode>755</filemode>-->
<!-- <sources>-->
<!-- <source>-->
<!-- <location>${project.build.directory}/distribution-${project.version}-bundle-tar.tar.gz</location>-->
<!-- </source>-->
<!-- </sources>-->
<!-- </mapping>-->
<!-- <mapping>-->
<!-- <directory>/usr/lib/systemd/system</directory>-->
<!-- <filemode>644</filemode>-->
<!-- <username>root</username>-->
<!-- <groupname>root</groupname>-->
<!-- <directoryIncluded>false</directoryIncluded>-->
<!-- <sources>-->
<!-- <source>-->
<!-- <location>${project.basedir}/../systemd/im-server.service</location>-->
<!-- </source>-->
<!-- </sources>-->
<!-- </mapping>-->
<!-- </mappings>-->
<!-- <postinstallScriptlet>-->
<!-- <scriptFile>${project.basedir}/src/main/rpm/install.sh</scriptFile>-->
<!-- <fileEncoding>utf-8</fileEncoding>-->
<!-- <filter>true</filter>-->
<!-- </postinstallScriptlet>-->
<!-- <postremoveScriptlet>-->
<!-- <scriptFile>${project.basedir}/src/main/rpm/uninstall.sh</scriptFile>-->
<!-- <fileEncoding>utf-8</fileEncoding>-->
<!-- <filter>true</filter>-->
<!-- </postremoveScriptlet>-->
<!-- </configuration>-->
<!-- </plugin>-->
<!-- 打包RPM 结束-->
</plugins>
</build>
</project>
+31 -6
View File
@@ -21,13 +21,18 @@
</fileSet>
<fileSet>
<directory>../broker/config/i18n/</directory>
<outputDirectory>/config/i18n</outputDirectory>
<directory>src/main/resources/</directory>
<outputDirectory>config/</outputDirectory>
</fileSet>
<fileSet>
<directory>src/main/resources/</directory>
<outputDirectory>config/</outputDirectory>
<directory>../systemd/</directory>
<outputDirectory>/systemd</outputDirectory>
</fileSet>
<fileSet>
<directory>src/main/checker/</directory>
<outputDirectory>checker/</outputDirectory>
</fileSet>
</fileSets>
@@ -39,6 +44,12 @@
<fileMode>0755</fileMode>
</file>
<file>
<source>src/main/scripts/stop.sh</source>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
</file>
<file>
<source>src/main/scripts/wildfirechat.bat</source>
<outputDirectory>bin</outputDirectory>
@@ -49,7 +60,7 @@
</file>
<file>
<source>src/main/files/release_note.md</source>
<source>../release_note.md</source>
</file>
<file>
@@ -75,8 +86,22 @@
<outputDirectory>server_sdk/</outputDirectory>
<destName>common-${project.version}.jar</destName>
</file>
</files>
<file>
<source>../sdk/target/checker.jar</source>
<outputDirectory>checker/</outputDirectory>
</file>
<file>
<source>../docker/Dockerfile</source>
<outputDirectory>docker/</outputDirectory>
</file>
<file>
<source>../docker/README.md</source>
<outputDirectory>docker/</outputDirectory>
</file>
</files>
<dependencySets>
<dependencySet>
+5
View File
@@ -0,0 +1,5 @@
部署IM服务后,可以使用Server SDK通过API来检查服务的部署情况,可以执行:
```shell
java -jar checker.jar http://127.0.0.1:18080 123456 http://127.0.0.1 true false
```
来检查。检查之后会产生垃圾数据,请在检查之后停掉IM服务后,再删除掉IM服务数据库。此检查仅检查API,检查通过并不代表部署肯定成功,还需要客户端来验证。
+10
View File
@@ -0,0 +1,10 @@
Package: im-server
Version: [[version]]
Section: misc
Priority: optional
Architecture: all
Maintainer: Wildfirechat <[email protected]>
Description: IM Server
Distribution: development
Depends: openjdk-8-jre-headless
Homepage: https://wildfirechat.cn
@@ -0,0 +1,23 @@
set -e
sed -i 's/h2db.path .\/h2db\/wfchat/h2db.path \/var\/lib\/im-server\/h2db\/imdb/' /opt/im-server/config/wildfirechat.conf
sed -i 's/local.media.storage.root .\/media/local.media.storage.root \/var\/lib\/im-server\/media/' /opt/im-server/config/wildfirechat.conf
sed -i 's/<Property name="MSG_LOG_HOME">.\/logs<\/Property>/<Property name="MSG_LOG_HOME">\/var\/log\/im-server<\/Property>/' /opt/im-server/config/log4j2.xml
sed -i 's/WILDFIRECHAT_CONFIG_PATH=$WILDFIRECHAT_HOME/WILDFIRECHAT_CONFIG_PATH=\/etc\/im-server/' /opt/im-server/bin/wildfirechat.sh
if [ ! -d /etc/im-server ]; then
mkdir /etc/im-server
fi
if [ ! -d /var/log/im-server ]; then
mkdir /var/log/im-server
fi
mv -f /opt/im-server/config /etc/im-server
systemctl daemon-reload
echo "IM server folders:"
echo "/etc/im-server/config config"
echo "/opt/im-server binary files"
echo "/var/log/im-server logs"
echo "/var/lib/im-server/h2db embed db"
echo "/var/lib/im-server/media embed media files"
+16
View File
@@ -0,0 +1,16 @@
set -e
rm -rf /opt/im-server
rm -rf /etc/im-server
rm -rf /var/log/im-server
rm -rf /usr/lib/systemd/system/im-server.service
if [ -d /var/lib/im-server/h2db ]; then
echo "IM embed db file not deleted in path /var/lib/im-server/h2db, if you don't need it anymore, please remove it manually"
fi
if [ -d /var/lib/im-server/media ]; then
echo "IM embed media files not deleted in path /var/lib/im-server/media, if you don't need it anymore, please remove it manually"
fi
systemctl daemon-reload
+4 -5
View File
@@ -4,11 +4,10 @@
<!--monitorInterval="30" :间隔秒数,自动检测配置文件的变更和重新配置本身-->
<configuration status="WARN" monitorInterval="60">
<Properties>
<!--自定义一些常量,之后使用${变量名}引用-->
<!-- 日志路径-->
<Property name="MSG_LOG_HOME">./logs</Property>
<Property name="logFileName">test.log</Property>
<!-- <Property name="MSG_LOG_HOME">/var/log/wildfirechat/im/logs</Property>-->
<property name="charset">UTF-8</property>
<!--自定义的输出格式-->
<property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property>
</Properties>
<!--appenders:定义输出内容,输出格式,输出方式,日志保存策略等,常用其下三种标签[console,File,RollingFile]-->
@@ -21,14 +20,14 @@
<RollingRandomAccessFile name="RollingFileInfo" immediateFlush="false"
fileName="${MSG_LOG_HOME}/wildfirechat.log"
filePattern="${MSG_LOG_HOME}/backup/wildfirechat.%d{yyyyMMddHH}.zip">
filePattern="${MSG_LOG_HOME}/backup/wildfirechat.%d{yyyyMMddHHmm}.zip">
<Filters>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${pattern}" />
<Policies>
<!-- TimeBasedTriggeringPolicy :时间滚动策略,默认0点小时产生新的文件,interval="6" : 自定义文件滚动时间间隔,每隔6小时产生新文件, modulate="true" : 产生文件是否以0点偏移时间,即6点,12点,18点,0点-->
<!-- <TimeBasedTriggeringPolicy interval="6" modulate="true"/>-->
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<!-- SizeBasedTriggeringPolicy :文件大小滚动策略-->
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
@@ -31,6 +31,8 @@ local_port 80
## 是否使用内置DB。0使用mysql;1使用h2db。
## MySQL需要把事物隔离级别改成"Read committed",使用命令来修改"set global transaction_isolation='read-committed';",其他数据库默认已经是这个级别不用修改
## 如果使用MySQL,请在c3p0.xml文件中配置JDBC信息。
## 如果是MySQL5.7,需要开启长索引的支持,在数据库中执行命令:"set global innodb_large_prefix=on;"。其他版本默认支持,不用修改。
embed.db 1
## 是否自动清理历史消息记录,当为true时,IM服务会定时扫描t_messages_x和t_user_messages_x表,清除掉3年前的历史消息。
@@ -39,6 +41,9 @@ embed.db 1
## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。
db.auto_clean_history_messages true
## h2db数据库的路径,默认为程序目录下的,可以指定其他目录。如果使用MySQL,可以忽略此配置
h2db.path ./h2db/wfchat
##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。
http.admin.secret_key 123456
@@ -52,6 +57,12 @@ token.key testim
##token的过期时间,单位为毫秒,默认为无限期。如果需要设置无限期,客户端上一定需要加上token过期的处理。token过期的处理请参考文档的常见问题
##token.expire_time 2592000000
##客户端(目前只有android)的应用签名(一个应用最取第一个签名),如果有多个签名(多个客户端),用英文逗号分开。获取签名方法请参考文档:https://docs.wildfirechat.cn/blogs/签名验证.html
#connect.client_signature_list eNkezQ1g9OsnhfLSFUY1vzKDzhs=,xykezQ1g9OsnhfLSFUY1vzKDzhs=
##是否拒绝空签名的android客户端, 默认为true。当有旧的客户端需要兼容时改成false,当所有都是新客户端时,改成true。
#connect.reject_empty_signature true
##首次登录,是否接收之前的历史消息。
##0 不接收历史消息,只接收${message.compensate_time_limit}毫秒以内的消息,由于服务器没有保存已经收取记录,所以如果有超过${message.compensate_time_limit}毫秒之前未收取的消息也不会收取下拉;
##1接收,会接收 message.max_queue 配置的条数的历史消息
@@ -72,6 +83,9 @@ message.chatroom_remote_history_message 1
##是否禁止陌生人聊天
message.disable_stranger_chat false
##当禁止陌生人聊天时,允许聊天的用户id,比如管理员或者文件传输助手等。用户id以英文逗号分割。
message.allow_stranger_chat_list admin,FireRobot,wfc_file_transfer
##黑名单策略,0 发送失败,返回被拉黑的错误码;1 发送成功但消息被服务器直接丢弃
message.blacklist.strategy 0
@@ -135,12 +149,6 @@ friend.reject_request_duration 2592000000
##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。
friend.request_expiration_duration 604800000
##添加好友后会发送欢迎消息,比如"你们已经是好友了,可以聊天了"。这个开关控制是用旧的方式还是用新的方式。
##旧的方式会发送notification消息(contenttype是80),内容会根据好友接收者的语言选择对应的语言,如果双方语言不一致会导致另外一方看到的不是本地语言。
##新的方式会发送添加好友消息(contenttype是93)和欢迎消息(contenttype是92),需要客户端实现这两种消息。
##如果客户端已经实现这两种消息可以打开这个开关(客户端2020.6.30添加了此消息类型)。
friend.new_welcome_message true
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000
@@ -161,6 +169,10 @@ group.allow_robot_custom_operation_notification true
## 如果应用需要退群或者踢人不通知普通成员,请设置此参数为3,然后修改客户端踢人或退群消息的flag为not_presist。
group.visible_quit_or_kickoff_notification 0
## 禁止客户端群操作。第1位是禁止创建群组,第2位是禁止销毁群组,3位禁止加入群,4位禁止退出群,5位禁止邀请群成员,6位禁止移出群成员,7位禁止转移群,8位禁止设置群管理员,9位禁止白名单处理,10位禁止群禁言,11位禁止修改群组信息,12位禁止群成员禁言。
## 如果有客户端有被破解的风险,可以禁止客户端群操作,然后通过Server API操作。Server API不受限制。
#group.forbidden_client_operation 0xFFF
## 隐藏用户信息属性类型,2是性别,3是电话号码,4是邮箱,5是地址,6是公司信息,7是社交账号,8是Extra信息。当需要隐藏多个时以英文逗号分开;
## 本配置是为了提高安全性,避免把对应信息同步到客户端,这样就能防止程序被破解从而拿到用户关键信息。仅对客户端有效,server api不受影响。
## 注意,修改本配置不会对已经同步的信息产生影响。
@@ -184,11 +196,6 @@ http.channel.rate_limit 1000
##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。
client.request_rate_limit 100
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#*********************************************************************
# Media server configuration
#*********************************************************************
@@ -296,15 +303,18 @@ push.ios.server.address http://localhost:8085/ios/push
##消息转发地址
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.forward.url http://localhost:8087/im_event/message
##需要转发的消息类型,当有多个时以英文逗号分割。如果转发所有消息,请注释掉配置或者设置为空
#message.forward.types 1,2,3
#message.forward.url http://localhost:8888/im_event/message
##需要转发的消息类型,当有多个时以英文逗号分割,可以用-来指定区间,注意区间不能太大,因为要把区间内的所有数字都放入到Set中。如果转发所有消息,请注释掉配置或者设置为空
#message.forward.types 1,2,3,5-10
##需要转发的消息类型,当有多个时以英文逗号分割,可以用-来指定区间,注意区间不能太大,因为要把区间内的所有数字都放入到Set中。
#message.forward.exclude_types 1,2,3,5-10
##敏感消息转发地址
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.sensitive.forward.url http://localhost:8888/im_event/message
##提醒消息转发地址,@全体用户和@某个用户消息会回调此地址。此转发不受${message.forward.types}限制。
##提醒消息转发地址,@全体用户和@某个用户消息会回调此地址。
##此转发不受${message.forward.types}和${message.forward.exclude_types}限制。
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.mentionmsg.forward.url http://localhost:8888/im_event/message
+27
View File
@@ -0,0 +1,27 @@
set -e
cd /opt/im-server
tar -xzvf distribution*.tar.gz
sed -i 's/h2db.path .\/h2db\/wfchat/h2db.path \/var\/lib\/im-server\/h2db\/imdb/' /opt/im-server/config/wildfirechat.conf
sed -i 's/local.media.storage.root .\/media/local.media.storage.root \/var\/lib\/im-server\/media/' /opt/im-server/config/wildfirechat.conf
sed -i 's/<Property name="MSG_LOG_HOME">.\/logs<\/Property>/<Property name="MSG_LOG_HOME">\/var\/log\/im-server<\/Property>/' /opt/im-server/config/log4j2.xml
sed -i 's/WILDFIRECHAT_CONFIG_PATH=$WILDFIRECHAT_HOME/WILDFIRECHAT_CONFIG_PATH=\/etc\/im-server/' /opt/im-server/bin/wildfirechat.sh
if [ ! -d /etc/im-server ]; then
mkdir /etc/im-server
fi
if [ ! -d /var/log/im-server ]; then
mkdir /var/log/im-server
fi
mv -f /opt/im-server/config /etc/im-server
systemctl daemon-reload
echo "IM server folders:"
echo "/etc/im-server/config config"
echo "/opt/im-server binary files"
echo "/var/log/im-server logs"
echo "/var/lib/im-server/h2db embed db"
echo "/var/lib/im-server/media embed media files"
+16
View File
@@ -0,0 +1,16 @@
set -e
rm -rf /opt/im-server
rm -rf /etc/im-server
rm -rf /var/log/im-server
rm -rf /usr/lib/systemd/system/im-server.service
if [ -d /var/lib/im-server/h2db ]; then
echo "IM embed db file not deleted in path /var/lib/im-server/h2db, if you don't need it anymore, please remove it manually"
fi
if [ -d /var/lib/im-server/media ]; then
echo "IM embed media files not deleted in path /var/lib/im-server/media, if you don't need it anymore, please remove it manually"
fi
systemctl daemon-reload
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
pid=`ps -ef | grep wildfirechat.server.Server | grep -v grep | awk '{print $2}'`
if [ -z $pid ]; then
echo "野火IM服务不存在"
exit 0
fi
kill -15 $pid
for i in {1..15}
do
pid=`ps -ef | grep wildfirechat.server.Server | grep -v grep | awk '{print $2}'`
if [ -z $pid ]; then
echo "野火IM服务已结束"
exit 0
else
echo "正在结束中,请等待..."
sleep 1
fi
done
echo "正常结束失败,强制结束!"
kill -9 $pid
+84 -82
View File
@@ -1,82 +1,84 @@
@ECHO OFF
set "CURRENT_DIR=%cd%"
set "WILDFIRECHAT_HOME=%CURRENT_DIR%"
if exist "%WILDFIRECHAT_HOME%\bin\wildfirechat.bat" goto okHome
cd ..
set "WILDFIRECHAT_HOME=%cd%"
set "CURRENT_DIR=%cd%"
:gotHome
if exist "%WILDFIRECHAT_HOME%\bin\wildfirechat.bat" goto okHome
echo The WILDFIRECHAT_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome
rem Set JavaHome if it exists
if exist { "%JAVA_HOME%\bin\java" } (
set "JAVA="%JAVA_HOME%\bin\java""
) else {
set "JAVA="java""
}
echo Using JAVA_HOME: "%JAVA_HOME%"
echo Using WILDFIRECHAT_HOME: "%WILDFIRECHAT_HOME%"
rem set LOG_CONSOLE_LEVEL=info
rem set LOG_FILE_LEVEL=fine
set JAVA_OPTS=
set JAVA_OPTS_SCRIPT=-XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true
set WILDFIRECHAT_PATH=%WILDFIRECHAT_HOME%
set LOG_FILE=%WILDFIRECHAT_HOME%\config\log4j2.xml
set HZ_CONF_FILE=%WILDFIRECHAT_HOME%\config\hazelcast.xml
set C3P0_CONF_FILE=%WILDFIRECHAT_HOME%\config\c3p0-config.xml
rem Use the Hotspot garbage-first collector.
set JAVA_OPTS=%JAVA_OPTS% -XX:+UseG1GC
rem Have the JVM do less remembered set work during STW, instead
rem preferring concurrent GC. Reduces p99.9 latency.
set JAVA_OPTS=%JAVA_OPTS% -XX:G1RSetUpdatingPauseTimePercent=5
rem Main G1GC tunable: lowering the pause target will lower throughput and vise versa.
rem 200ms is the JVM default and lowest viable setting
rem 1000ms increases throughput. Keep it smaller than the timeouts.
set JAVA_OPTS=%JAVA_OPTS% -XX:MaxGCPauseMillis=500
rem Optional G1 Settings
rem Save CPU time on large (>= 16GB) heaps by delaying region scanning
rem until the heap is 70% full. The default in Hotspot 8u40 is 40%.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:InitiatingHeapOccupancyPercent=70
rem For systems with > 8 cores, the default ParallelGCThreads is 5/8 the number of logical cores.
rem Otherwise equal to the number of cores when 8 or less.
rem Machines with > 10 cores should try setting these to <= full cores.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:ParallelGCThreads=16
rem By default, ConcGCThreads is 1/4 of ParallelGCThreads.
rem Setting both to the same value can reduce STW durations.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:ConcGCThreads=16
rem rem GC logging options -- uncomment to enable
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDetails
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDateStamps
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintHeapAtGC
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintTenuringDistribution
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCApplicationStoppedTime
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintPromotionFailure
rem set JAVA_OPTS=%JAVA_OPTS% -XX:PrintFLSStatistics=1
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+UseGCLogFileRotation
rem set JAVA_OPTS=%JAVA_OPTS% -XX:NumberOfGCLogFiles=10
rem set JAVA_OPTS=%JAVA_OPTS% -XX:GCLogFileSize=10M"
echo
echo 请设置JVM参数Xmx和Xms!!!
echo
rem set JAVA_OPTS=%JAVA_OPTS% -Xmx2G
rem set JAVA_OPTS=%JAVA_OPTS% -Xms2G
%JAVA% -server %JAVA_OPTS% %JAVA_OPTS_SCRIPT% -Dlog4j.configurationFile=%LOG_FILE% -Dcom.mchange.v2.c3p0.cfg.xml=%C3P0_CONF_FILE% -Dhazelcast.configuration=file:%HZ_CONF_FILE% -Dwildfirechat.path=%WILDFIRECHAT_PATH% -cp %WILDFIRECHAT_HOME%\lib\* cn.wildfirechat.server.Server
@ECHO OFF
set "CURRENT_DIR=%cd%"
set "WILDFIRECHAT_HOME=%CURRENT_DIR%"
if exist "%WILDFIRECHAT_HOME%\bin\wildfirechat.bat" goto okHome
cd ..
set "WILDFIRECHAT_HOME=%cd%"
set "CURRENT_DIR=%cd%"
:gotHome
if exist "%WILDFIRECHAT_HOME%\bin\wildfirechat.bat" goto okHome
echo The WILDFIRECHAT_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome
rem Set JavaHome if it exists
if exist [ "%JAVA_HOME%\bin\java" ] (
set "JAVA="%JAVA_HOME%\bin\java""
) else (
set "JAVA="java""
)
echo Using JAVA_HOME: "%JAVA_HOME%"
echo Using WILDFIRECHAT_HOME: "%WILDFIRECHAT_HOME%"
rem set LOG_CONSOLE_LEVEL=info
rem set LOG_FILE_LEVEL=fine
set JAVA_OPTS=
set JAVA_OPTS_SCRIPT=-XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true
set WILDFIRECHAT_PATH=%WILDFIRECHAT_HOME%
set LOG_FILE=%WILDFIRECHAT_HOME%\config\log4j2.xml
set HZ_CONF_FILE=%WILDFIRECHAT_HOME%\config\hazelcast.xml
set C3P0_CONF_FILE=%WILDFIRECHAT_HOME%\config\c3p0-config.xml
rem Use the Hotspot garbage-first collector.
set JAVA_OPTS=%JAVA_OPTS% -XX:+UseG1GC
rem Have the JVM do less remembered set work during STW, instead
rem preferring concurrent GC. Reduces p99.9 latency.
set JAVA_OPTS=%JAVA_OPTS% -XX:G1RSetUpdatingPauseTimePercent=5
rem Main G1GC tunable: lowering the pause target will lower throughput and vise versa.
rem 200ms is the JVM default and lowest viable setting
rem 1000ms increases throughput. Keep it smaller than the timeouts.
set JAVA_OPTS=%JAVA_OPTS% -XX:MaxGCPauseMillis=500
rem Optional G1 Settings
rem Save CPU time on large (>= 16GB) heaps by delaying region scanning
rem until the heap is 70% full. The default in Hotspot 8u40 is 40%.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:InitiatingHeapOccupancyPercent=70
rem For systems with > 8 cores, the default ParallelGCThreads is 5/8 the number of logical cores.
rem Otherwise equal to the number of cores when 8 or less.
rem Machines with > 10 cores should try setting these to <= full cores.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:ParallelGCThreads=16
rem By default, ConcGCThreads is 1/4 of ParallelGCThreads.
rem Setting both to the same value can reduce STW durations.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:ConcGCThreads=16
rem rem GC logging options -- uncomment to enable
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDetails
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDateStamps
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintHeapAtGC
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintTenuringDistribution
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCApplicationStoppedTime
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintPromotionFailure
rem set JAVA_OPTS=%JAVA_OPTS% -XX:PrintFLSStatistics=1
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+UseGCLogFileRotation
rem set JAVA_OPTS=%JAVA_OPTS% -XX:NumberOfGCLogFiles=10
rem set JAVA_OPTS=%JAVA_OPTS% -XX:GCLogFileSize=10M"
echo
echo "Please set Xmx and Xms parameters!"
echo "Please set Xmx and Xms parameters!"
echo "Please set Xmx and Xms parameters!"
echo
rem set JAVA_OPTS=%JAVA_OPTS% -Xmx2G
rem set JAVA_OPTS=%JAVA_OPTS% -Xms2G
%JAVA% -server %JAVA_OPTS% %JAVA_OPTS_SCRIPT% -Dlog4j.configurationFile=%LOG_FILE% -Dcom.mchange.v2.c3p0.cfg.xml=%C3P0_CONF_FILE% -Dhazelcast.configuration=%HZ_CONF_FILE% -Dwildfirechat.path=%WILDFIRECHAT_PATH% -cp %WILDFIRECHAT_HOME%\lib\* cn.wildfirechat.server.Server
@@ -18,7 +18,6 @@ while [ -h "$PRG" ]; do
done
PRGDIR=`dirname "$PRG"`
cd $PRGDIR
cd ..
WILDFIRECHAT_HOME=`pwd`
@@ -34,11 +33,13 @@ else
fi
export JAVA
LOG_FILE=$WILDFIRECHAT_HOME/config/log4j2.xml
HZ_CONF_FILE=$WILDFIRECHAT_HOME/config/hazelcast.xml
C3P0_CONF_FILE=$WILDFIRECHAT_HOME/config/c3p0-config.xml
WILDFIRECHAT_CONFIG_PATH=$WILDFIRECHAT_HOME
LOG_FILE=$WILDFIRECHAT_CONFIG_PATH/config/log4j2.xml
HZ_CONF_FILE=$WILDFIRECHAT_CONFIG_PATH/config/hazelcast.xml
C3P0_CONF_FILE=$WILDFIRECHAT_CONFIG_PATH/config/c3p0-config.xml
WILDFIRECHAT_PATH=$WILDFIRECHAT_HOME/
#LOG_CONSOLE_LEVEL=info
#LOG_FILE_LEVEL=fine
JAVA_OPTS_SCRIPT="-XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true"
@@ -91,4 +92,4 @@ echo ""
#JAVA_OPTS="$JAVA_OPTS -Xms2G"
$JAVA -server $JAVA_OPTS $JAVA_OPTS_SCRIPT -Dlog4j.configurationFile="file:$LOG_FILE" -Dlog4j2.formatMsgNoLookups=true -Dcom.mchange.v2.c3p0.cfg.xml="$C3P0_CONF_FILE" -Dhazelcast.configuration="file:$HZ_CONF_FILE" -Dwildfirechat.path="$WILDFIRECHAT_PATH" -cp "$WILDFIRECHAT_HOME/lib/*" cn.wildfirechat.server.Server
$JAVA -server $JAVA_OPTS $JAVA_OPTS_SCRIPT -Dlog4j.configurationFile="file:$LOG_FILE" -Dlog4j2.formatMsgNoLookups=true -Dcom.mchange.v2.c3p0.cfg.xml="$C3P0_CONF_FILE" -Dhazelcast.configuration=$HZ_CONF_FILE -Dwildfirechat.path="$WILDFIRECHAT_CONFIG_PATH" -cp "$WILDFIRECHAT_HOME/lib/*" cn.wildfirechat.server.Server
+3 -1
View File
@@ -1,6 +1,6 @@
FROM openjdk:8-jre-alpine
ADD ../distribution/target/distribution-*-bundle-tar.tar.gz /opt/im-server/
ADD distribution-*-bundle-tar.tar.gz /opt/im-server/
RUN sed -i 's/#JAVA_OPTS="$JAVA_OPTS -Xmx128M"/JAVA_OPTS="$JAVA_OPTS -Xmx$JVM_XMX"/g' /opt/im-server/bin/wildfirechat.sh
RUN sed -i 's/#JAVA_OPTS="$JAVA_OPTS -Xms128M"/JAVA_OPTS="$JAVA_OPTS -Xms$JVM_XMS"/g' /opt/im-server/bin/wildfirechat.sh
@@ -9,6 +9,8 @@ WORKDIR /opt/im-server
VOLUME /opt/im-server/config
VOLUME /opt/im-server/logs
VOLUME /opt/im-server/h2db
VOLUME /opt/im-server/media
EXPOSE 80/tcp 1883/tcp 8083/tcp 8084/tcp 18080/tcp
+14 -3
View File
@@ -5,15 +5,26 @@
```
mvn clean package
```
生成的软件包在```distribution/target/distribution-XX-bundle-tar.tar.gz```,把此软件包拷贝到docker目录下。
然后进入到docker目录下,编译镜像
```
sudo docker build -t wildfire_im .
sudo docker build -t im-server .
```
## 运行
配置:
运行需要手动指定配置目录,这样不用重新打包镜像。手动指定配置目录的方法如下,其中$PATH_TO_CONFIG为im配置目录,模版为```distribution/src/main/resources```目录:
运行需要手动指定配置目录,手动指定配置目录的方法如下,其中$PATH_TO_CONFIG为im配置目录,需要为绝对路径,模版为```distribution/src/main/resources```目录。另外需要创建h2数据库目录(如果用mysql就不需要了)、日志目录、内置存储服务目录(如果使用其它对象存储服务就不需要了)。
```
sudo docker run -it -v $PATH_TO_CONFIG:/opt/im-server/config -v $PATH_TO_LOGS:/opt/im-server/logs -e JVM_XMX=256M -e JVM_XMS=256M -p 80:80 -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 18080:18080 wildfire_im
sudo docker run -it --name im-server -v $PATH_TO_CONFIG:/opt/im-server/config -v $PATH_TO_LOGS:/opt/im-server/logs -v $PATH_TO_H2DB:/opt/im-server/h2db -v $PATH_TO_MEDIA:/opt/im-server/media -e JVM_XMX=256M -e JVM_XMS=256M -p 80:80 -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 18080:18080 im-server
```
## 导出镜像
```
sudo docker save -o im-server.tar im-server
```
## 导入镜像
```
sudo docker load -i im-server.tar
```
+1 -1
View File
@@ -12,7 +12,7 @@
<artifactId>wildfirechat-parent</artifactId>
<packaging>pom</packaging>
<version>0.96</version>
<version>1.1</version>
<name>Wildfire Chat</name>
<description>Wildfire Chat Server</description>
<inceptionYear>2019</inceptionYear>
@@ -7,10 +7,51 @@
6. *** 0.62/0.63 版本有严重的问题,请使用0.64及以后版本,或者0.61版。 ***
7. *** 从0.68 版本起添加了pc在线是否默认手机接收推送的开关,默认为开,与以前版本作用相反,请注意兼容(可以关掉与之前保持一致或者升级客户端) ***
8. *** 从0.78 版本起把MySQL数据库中关键字都改为大小写敏感,另外生成id的方法也做了改变,只生成小写的id,避免出现id重复的问题,建议所有客户都升级 ***
9 *** 从0.79 版本起把log4j升级到log4j2,因为log4j已经不再维护而且还有已知的漏洞,建议所有客户都升级,升级时注意更新log4j2的配置文件 ***
9. *** 从0.79 版本起把log4j升级到log4j2,因为log4j已经不再维护而且还有已知的漏洞,建议所有客户都升级,升级时注意更新log4j2的配置文件 ***
10. *** 5. 0.97版本更改了启动脚本```wildfirechat.sh```,如果是升级服务,请注意更新启动脚本。***
# 更新记录
--------------
Release note 1.1:
1. Channel API添加检查用户是否订阅接口
2. Server API发送消息时添加检查对象是否存在
3. Channel API添加修改频道菜单接口
4. 内置对象存储的优化
--------------
Release note 1.0:
1. SDK检查工具添加help命令
2. 去掉短连接端口被网络爬虫扫描输出的异常日志
3. API支持创建组织群
4. 添加配置禁止客户端进行某些群操作
5. 解决route请求时可能不能正确携带推送类型的问题
6. 会议成员离开加上离开原因
7. 添加停止程序的命令
8. 添加RTP Forward的API
--------------
Release note 0.99:
1. API获取用户信息时,过滤掉已经删除用户。
2. 消息转发功能支持设置include/exclude功能,支持按区间过滤。
3. 添加新的群组类型:组织群。组织群适用于公司部门群场景。
4. 内置对象存储支持绝对路径和相对路径配置
5. 当开启禁止陌生人聊天时,可以添加例外人员
6. 解决user setting并发问题
7. 添加可选的android平台签名验证功能。
--------------
Release note 0.98:
1. 解决linux多端登录错误问题
2. 添加获取用户群组和共同群组功能
3. 解决windows下脚本编码错误问题
--------------
Release note 0.97:
1. 升级部分依赖库到最新版本
2. 添加名片消息默认推送信息
3. 添加deb和rpm格式软件包
--------------
Release note 0.96:
1. 文章消息添加摘要字段
Binary file not shown.

After

Width:  |  Height:  |  Size: 998 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

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