Compare commits

..
90 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
heavyrian2012 0e6e23da13 更新版本号和release note 2022-10-06 13:39:04 +08:00
heavyrian2012 88652af565 添加会议录制接口 2022-10-06 11:41:51 +08:00
heavyrian2012 af58bf91a4 单聊消息可以定向发送,只发送给其中一个用户 2022-09-29 08:36:01 +08:00
heavyrian2012 9d86539d01 update issue template 2022-09-23 22:25:48 +08:00
heavyrian2012 2559fa7e48 文章消息添加摘要信息 2022-09-20 11:03:14 +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
116 changed files with 1659 additions and 519 deletions
+4 -1
View File
@@ -11,10 +11,13 @@
你认为正确的表现应该是什么样的。 你认为正确的表现应该是什么样的。
#### 补充条件 #### 补充条件
是否是毕现的,还是偶现的?是否只有在特殊的网络/设备/平台上出现,还是所有的都出现。还有您用的版本是什么时候的,是不是最新版 是否是必现的,还是偶现的?是否只有在特殊的网络/设备/平台上出现,还是所有的都出现。还有您用的版本是什么时候的,是不是最新版
#### demo对比结果 #### demo对比结果
请用demo对比测试,demo上是什么状态。 请用demo对比测试,demo上是什么状态。
#### 检索已有问题 #### 检索已有问题
除了可以检索已有issue外,还可以去野火论坛查找已有问题,论坛地址为 https://bbs.wildfirechat.cn 除了可以检索已有issue外,还可以去野火论坛查找已有问题,论坛地址为 https://bbs.wildfirechat.cn
#### star 我们
给我们点个star,可以及时接收到我们回复信息,另外也是对我们支持人员辛苦工作的认可,激励我们更好更快地为大家服务。
+141 -45
View File
@@ -4,10 +4,11 @@
## 功能特性 ## 功能特性
* 极致地硬件利用率,IM服务最低128M内存即可运行,上不封顶。 * 极致地硬件利用率,IM服务最低128M内存即可运行,上不封顶。
* 协议先进,采用MQTT+Protobuf组合,流量和性能极致优化。 * 协议先进,采用MQTT+Protobuf组合,流量和性能极致优化。
* 性能强大,专业版IM服务支持百万在线和集群部署。 * 性能强大,专业版IM服务支持百万在线和集群部署,业内顶尖性能水平,详情参考野火性能报告。
* 部署运维简单,依赖服务少,稍加配置一键启动。 * 部署运维简单,依赖服务少,稍加配置一键启动。
* 安全加密。网络连接AES加密。客户端数据库SqlCipher加密。安全无小事。 * 安全加密。网络连接AES加密。客户端数据库SqlCipher加密。安全无小事。
* 全平台客户端,四端同时在线(移动端,pc端,web端和小程序端),数据和状态多端完美同步。 * 全平台客户端,支持Android、iOS、Web、小程序、UniApp、Flutter、Windows、Mac OS、Linux等所有常见平台。
* 四端同时在线(移动端,pc端,web端和小程序端),数据和状态多端完美同步。
* 支持国产化。支持国产化操作系统、国产化芯片和国产操作系统。支持国密加密。 * 支持国产化。支持国产化操作系统、国产化芯片和国产操作系统。支持国密加密。
* 客户端使用微信[mars](https://github.com/tencent/mars)连接库,野火IM可能是最适应中国网络国情的即时通讯服务。 * 客户端使用微信[mars](https://github.com/tencent/mars)连接库,野火IM可能是最适应中国网络国情的即时通讯服务。
* 支持加速点加速,即可用于全球应用,也可用于政企内外双网复杂的网络环境。 * 支持加速点加速,即可用于全球应用,也可用于政企内外双网复杂的网络环境。
@@ -15,19 +16,22 @@
* 音视频多种解决方案,丰俭由人,可自由选择。 * 音视频多种解决方案,丰俭由人,可自由选择。
* 高级音视频功能强大,支持9人以上的群组视频通话,支持1080P视频,支持会议模式,支持百人以上会议,支持服务器端录制。 * 高级音视频功能强大,支持9人以上的群组视频通话,支持1080P视频,支持会议模式,支持百人以上会议,支持服务器端录制。
* 全私有部署,可不依赖任何第三方服务,完全内网部署。 * 全私有部署,可不依赖任何第三方服务,完全内网部署。
* 功能齐全,涵盖所有常见即时通讯功能。另外具有强大的可扩展能力。Demo成熟完善,基本可以做到开箱即用,也可把SDK嵌入其它应用。 * 功能齐全,涵盖所有常见即时通讯功能。另外具有强大的可扩展能力。代码开源率高,二次开发简单。
* 结构设计合理,客户端分功能库、UI库和应用三层。应用成熟完善,开箱即用,也可把SDK嵌入其它应用。
* 拥有应用开放平台,可以开发和创建自建应用,扩展您的工作台。 * 拥有应用开放平台,可以开发和创建自建应用,扩展您的工作台。
* API丰富,方便与其它服务系统的对接。 * 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相关业务。 | | [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) | 机器人服务,演示野火机器人对接其它系统的方法。 | | [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) | 推送服务器,可以对接所有的系统厂商推送服务或者第三方推送服务。 | | [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专业版的文件存储。 | | [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) | 野火开放平台服务。 | | [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服务。 | | [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) | 野火频道(公众号)平台服务。 | | [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源码。 | | [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源码。| | [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)。 | | [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)。 | | [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 等小程序平台)。 | | [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-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平台原生插件。 | | [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)上架 |
| [flutter_imclient](https://github.com/wildfirechat/flutter_imclient) | [flutter_imclient](https://gitee.com/wfchat/flutter_imclient) | Flutter平台原生插件。 | | [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/)。 | | [docs](https://github.com/wildfirechat/docs) | [docs](https://gitee.com/wfchat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明,[在线查看](https://docs.wildfirechat.cn/)。 |
## 野火开发文档 ## 野火开发文档
[在线文档](https://docs.wildfirechat.cn/) [在线文档](https://docs.wildfirechat.cn/)
## 野火性能测试报告及测试方法
[Github](https://github.com/wildfirechat/Performance_Test), [码云](https://gitee.com/wfchat/Performance_Test)。
## 野火Demo ## 野火Demo
请使用微信扫码下载安装体验野火IM移动客户端 请使用微信扫码下载安装体验野火IM移动客户端
@@ -62,73 +70,161 @@ PC客户端点[这里](https://github.com/wildfirechat/vue-pc-chat/releases)下
![野火IM](http://static.wildfirechat.net/wx.jpg) ![野火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服务,然后打包移动端,就可以完成聊天和通话功能。
## 应用截图 之后再部署开放平台服务、频道(公众号)服务、机器人服务、推送服务、Turn服务(音视频服务)来实现完整的应用功能,详情可以详细阅读文档。
![ios-demo](http://static.wildfirechat.cn/ios-demo.gif)
<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% /> 我们有核心研发工程师轮流值班处理issue和论坛,会及时处理的,疑难Bug的修改和新需求的开发我们也会尽快解决。
<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% />
## 编译 ## 编译
在安装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```。修改如下部分: 解压```distribution-xxxx-bundle-tar.tar.gz```,修改解压出来的```config```目录下的```wildfirechat.conf```,可以阅读配置文件注释和文档,对其它配置项进行调整。
1. 修改```server.ip```为您的服务器的公网IP,或者域名。如果用域名需要做好域名解析。
2. 可以阅读配置文件注释和文档,对其它配置项进行调整。
## 运行 ## 运行
在程序目录运行如下命令: 在解压```distribution-xxxx-bundle-tar.tar.gz```后的目录下运行如下命令:
``` ```
./bin/wildfirechat.sh ./bin/wildfirechat.sh
``` ```
## 验证 ## 验证
1. 在浏览器中输入地址 ```http://${ip}/api/version```可以看到返回一个json文件。 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改成您期望的,然后重启。 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版本之外的版本不会出现此问题。 2. 0.50版本添加了是否允许客户端发送群操作通知的配置。如果您在客户端自定义群通知,需要在服务器端配置允许,没有使用自定义群操作通知的不受影响。***
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';```来修复。 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.59之前的版本升级到之后的版本执行数据库升级时间比较长,请耐心等待提示运行成功,避免中途中断。 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。 <img src="./screenshots/3_contacts_list.png" width = 50% height = 50% />
2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习
3. 关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。
<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相关业务。 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)。 ## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。
db.auto_clean_history_messages true db.auto_clean_history_messages true
## h2db数据库的路径,默认为程序目录下的,可以指定其他目录。如果使用MySQL,可以忽略此配置
h2db.path ./h2db/wfchat
##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。 ##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。
http.admin.secret_key 123456 http.admin.secret_key 123456
@@ -56,6 +59,12 @@ token.key testim
##token的过期时间,单位为毫秒,默认为无限期。如果需要设置无限期,客户端上一定需要加上token过期的处理。token过期的处理请参考文档的常见问题 ##token的过期时间,单位为毫秒,默认为无限期。如果需要设置无限期,客户端上一定需要加上token过期的处理。token过期的处理请参考文档的常见问题
##token.expire_time 2592000000 ##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}毫秒之前未收取的消息也不会收取下拉; ##0 不接收历史消息,只接收${message.compensate_time_limit}毫秒以内的消息,由于服务器没有保存已经收取记录,所以如果有超过${message.compensate_time_limit}毫秒之前未收取的消息也不会收取下拉;
##1接收,会接收 message.max_queue 配置的条数的历史消息 ##1接收,会接收 message.max_queue 配置的条数的历史消息
@@ -76,6 +85,9 @@ message.chatroom_remote_history_message 1
##是否禁止陌生人聊天 ##是否禁止陌生人聊天
message.disable_stranger_chat false message.disable_stranger_chat false
##当禁止陌生人聊天时,允许聊天的用户id,比如管理员或者文件传输助手等。用户id以英文逗号分割。
message.allow_stranger_chat_list admin,FireRobot,wfc_file_transfer
##黑名单策略,0 发送失败,返回被拉黑的错误码;1 发送成功但消息被服务器直接丢弃 ##黑名单策略,0 发送失败,返回被拉黑的错误码;1 发送成功但消息被服务器直接丢弃
message.blacklist.strategy 0 message.blacklist.strategy 0
@@ -139,12 +151,6 @@ friend.reject_request_duration 2592000000
##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。 ##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。
friend.request_expiration_duration 604800000 friend.request_expiration_duration 604800000
##添加好友后会发送欢迎消息,比如"你们已经是好友了,可以聊天了"。这个开关控制是用旧的方式还是用新的方式。
##旧的方式会发送notification消息(contenttype是80),内容会根据好友接收者的语言选择对应的语言,如果双方语言不一致会导致另外一方看到的不是本地语言。
##新的方式会发送添加好友消息(contenttype是93)和欢迎消息(contenttype是92),需要客户端实现这两种消息。
##如果客户端已经实现这两种消息可以打开这个开关(客户端2020.6.30添加了此消息类型)。
friend.new_welcome_message true
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出 ## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000 chatroom.participant_idle_time 900000
@@ -164,6 +170,10 @@ group.allow_robot_custom_operation_notification true
## 0 不发送,1 退群时发送,2 踢人时发送,3 退群和踢人时都发送。 ## 0 不发送,1 退群时发送,2 踢人时发送,3 退群和踢人时都发送。
group.visible_quit_or_kickoff_notification 0 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信息。当需要隐藏多个时以英文逗号分开; ## 隐藏用户信息属性类型,2是性别,3是电话号码,4是邮箱,5是地址,6是公司信息,7是社交账号,8是Extra信息。当需要隐藏多个时以英文逗号分开;
## 本配置是为了提高安全性,避免把对应信息同步到客户端,这样就能防止程序被破解从而拿到用户关键信息。仅对客户端有效,server api不受影响。 ## 本配置是为了提高安全性,避免把对应信息同步到客户端,这样就能防止程序被破解从而拿到用户关键信息。仅对客户端有效,server api不受影响。
## 注意,修改本配置不会对已经同步的信息产生影响。 ## 注意,修改本配置不会对已经同步的信息产生影响。
@@ -190,11 +200,6 @@ http.channel.rate_limit 1000
##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。 ##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。
client.request_rate_limit 100 client.request_rate_limit 100
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#********************************************************************* #*********************************************************************
# Media server configuration # 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.url http://localhost:8888/im_event/message
##需要转发的消息类型,当有多个时以英文逗号分割。如果转发所有消息,请注释掉配置或者设置为空 ##需要转发的消息类型,当有多个时以英文逗号分割,可以用-来指定区间,注意区间不能太大,因为要把区间内的所有数字都放入到Set中。如果转发所有消息,请注释掉配置或者设置为空
#message.forward.types 1,2,3 #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.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 #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" <?xml version="1.0" encoding="UTF-8"?>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<relativePath>../</relativePath> <relativePath>../</relativePath>
<artifactId>wildfirechat-parent</artifactId> <artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId> <groupId>cn.wildfirechat</groupId>
<version>0.95</version> <version>1.1</version>
</parent> </parent>
<artifactId>moquette-broker</artifactId> <artifactId>moquette-broker</artifactId>
@@ -120,7 +120,7 @@
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>1.10</version> <version>1.13</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -90,6 +90,8 @@ public class FileAction extends Action {
} }
response.setContent(file); response.setContent(file);
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
return true; return true;
} }
@@ -187,6 +187,10 @@ public class UploadFileAction extends Action {
return false; return false;
} }
if(remoteFileName != null && remoteFileName.length() > 2 && remoteFileName.startsWith("'") && remoteFileName.endsWith("'")) {
remoteFileName = remoteFileName.substring(1, remoteFileName.length()-1);
}
if (StringUtil.isNullOrEmpty(remoteFileName)) { if (StringUtil.isNullOrEmpty(remoteFileName)) {
logger.warn("remoteFileName is empty!"); logger.warn("remoteFileName is empty!");
@@ -235,12 +239,17 @@ public class UploadFileAction extends Action {
int offset = 0; // 断点续传开始位置 int offset = 0; // 断点续传开始位置
Date nowTime=new Date(); 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); String datePath = time.format(nowTime);
datePath = "fs/" + bucket[0] + "/" + datePath; //add bucket 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); File dirFile = new File(dir);
boolean bFile = dirFile.exists(); 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; RestResult result;
if (user == null) { if (user == null || user.getDeleted() > 0) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST); result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else { } else {
result = RestResult.ok(InputOutputUserInfo.fromPbUser(user)); 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; 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); result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else { } else {
if (channelInfo.getOwner().equals(inputUserId.getUserId()) || if (channelInfo.getOwner().equals(inputUserId.getUserId()) ||
@@ -44,7 +44,7 @@ public class GetUserAction extends RobotAction {
} }
RestResult result; RestResult result;
if (user == null) { if (user == null || user.getDeleted() > 0) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST); result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else { } else {
result = RestResult.ok(InputOutputUserInfo.fromPbUser(user)); result = RestResult.ok(InputOutputUserInfo.fromPbUser(user));
@@ -15,6 +15,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import win.liyufan.im.Utility; import win.liyufan.im.Utility;
@@ -34,7 +35,6 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception { 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 Request request = Request.build(ctx, fullHttpRequest);
final Response response = Response.build(ctx, request); final Response response = Response.build(ctx, request);
@@ -109,7 +109,7 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
* @param response 响应对象 * @param response 响应对象
*/ */
private void doAction(ChannelHandlerContext ctx, Request request, Response 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())){ && "OPTIONS".equalsIgnoreCase(request.getMethod())){
handleOptions(ctx, request, response); handleOptions(ctx, request, response);
return; return;
@@ -124,6 +124,9 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
Action action; Action action;
if (isValidePath(request.getPath())) { if (isValidePath(request.getPath())) {
action = ServerSetting.getAction(request.getPath(), request.getMethod().toUpperCase()); action = ServerSetting.getAction(request.getPath(), request.getMethod().toUpperCase());
if (action != null) {
Logger.info("Http request whit url {}", request.getPath());
}
} else { } else {
action = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR); 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()); action = ServerSetting.getAction(ServerSetting.MAPPING_ALL, request.getMethod());
if(null == action){ if(null == action){
// 非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; 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_SECRET_KEY = "token.key";
public static final String TOKEN_EXPIRE_TIME = "token.expire_time"; 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 EMBED_DB_PROPERTY_NAME = "embed.db";
public static final String DB_AUTO_CLEAN_HISTORY_MESSAGES = "db.auto_clean_history_messages"; 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"; 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_Forward_Url = "message.forward.url";
public static final String MESSAGE_Sensitive_Forward_Url = "message.sensitive.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_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"; 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_Max_Queue = "message.max_queue";
public static final String MESSAGE_Disable_Stranger_Chat = "message.disable_stranger_chat"; 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"; 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_Repeat_Request_Duration = "friend.repeat_request_duration";
public static final String FRIEND_Reject_Request_Duration = "friend.reject_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_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_Participant_Idle_Time = "chatroom.participant_idle_time";
public static final String CHATROOM_Rejoin_When_Active = "chatroom.rejoin_when_active"; 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_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_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_HIDE_PROPERTIES = "user.hide_properties";
public static final String USER_KEEP_DISPLAY_NAME_WHEN_DESTROY = "user.keep_display_name_when_destroy"; 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 @Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.AddGroupMemberRequest request, Qos1PublishHandler.IMCallback callback) { 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; 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()); ErrorCode errorCode = m_messagesStore.addGroupMembers(fromUser, isAdmin, request.getGroupId(), request.getAddedMemberList(), request.getExtra());
if (errorCode == ERROR_CODE_SUCCESS) { if (errorCode == ERROR_CODE_SUCCESS) {
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) { if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -21,7 +21,7 @@ import win.liyufan.im.IMTopic;
public class CreateGroupHandler extends GroupHandler<WFCMessage.CreateGroupRequest> { public class CreateGroupHandler extends GroupHandler<WFCMessage.CreateGroupRequest> {
@Override @Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.CreateGroupRequest request, Qos1PublishHandler.IMCallback callback) { 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; return ErrorCode.ERROR_CODE_INVALID_DATA;
} }
@@ -40,6 +40,16 @@ public class CreateGroupHandler extends GroupHandler<WFCMessage.CreateGroupReque
return ErrorCode.ERROR_CODE_NOT_RIGHT; 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); WFCMessage.GroupInfo groupInfo = m_messagesStore.createGroup(fromUser, request.getGroup().getGroupInfo(), request.getGroup().getMembersList(), request.getMemberExtra(), isAdmin);
if (groupInfo != null) { if (groupInfo != null) {
if(request.hasNotifyContent() && request.getNotifyContent().getType() > 0) { if(request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -26,7 +26,6 @@ public class DismissGroupHandler extends GroupHandler<WFCMessage.DismissGroupReq
ErrorCode errorCode; ErrorCode errorCode;
if (groupInfo == null) { if (groupInfo == null) {
errorCode = m_messagesStore.dismissGroup(fromUser, request.getGroupId(), isAdmin); errorCode = m_messagesStore.dismissGroup(fromUser, request.getGroupId(), isAdmin);
} else if (isAdmin || (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Normal || groupInfo.getType() == ProtoConstants.GroupType.GroupType_Restricted) } else if (isAdmin || (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Normal || groupInfo.getType() == ProtoConstants.GroupType.GroupType_Restricted)
&& groupInfo.getOwner() != null && groupInfo.getOwner().equals(fromUser)) { && groupInfo.getOwner() != null && groupInfo.getOwner().equals(fromUser)) {
@@ -38,6 +37,13 @@ public class DismissGroupHandler extends GroupHandler<WFCMessage.DismissGroupReq
return ErrorCode.ERROR_CODE_NOT_RIGHT; 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 //send notify message first, then dismiss group
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) { if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
sendGroupNotification(fromUser, groupInfo.getTargetId(), request.getToLineList(), request.getNotifyContent()); 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.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage; import cn.wildfirechat.proto.WFCMessage;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.spi.impl.Qos1PublishHandler; import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import cn.wildfirechat.common.ErrorCode; import cn.wildfirechat.common.ErrorCode;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import win.liyufan.im.I18n;
import win.liyufan.im.IMTopic; import win.liyufan.im.IMTopic;
import win.liyufan.im.MessageShardingUtil; import win.liyufan.im.MessageShardingUtil;
@@ -43,17 +41,8 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType); 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(); WFCMessage.MessageContent.Builder contentBuilder = WFCMessage.MessageContent.newBuilder();
if (m_messagesStore.isNewFriendWelcomeMessage()) { contentBuilder.setType(92);
contentBuilder.setType(92);
} else {
contentBuilder.setType(90).setContent(I18n.getString(language, "Above_Greeting_Message"));
}
builder = WFCMessage.Message.newBuilder(); builder = WFCMessage.Message.newBuilder();
builder.setFromUser(request.getTargetUid()); builder.setFromUser(request.getTargetUid());
@@ -64,13 +53,7 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
messageId = MessageShardingUtil.generateId(); messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId); builder.setMessageId(messageId);
saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType); saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType);
contentBuilder.setType(93);
if (m_messagesStore.isNewFriendWelcomeMessage()) {
contentBuilder.setType(93);
} else {
contentBuilder.setContent(I18n.getString(language, "Friend_Can_Start_Chat"));
}
builder.setContent(contentBuilder); builder.setContent(contentBuilder);
messageId = MessageShardingUtil.generateId(); messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId); builder.setMessageId(messageId);
@@ -29,6 +29,10 @@ public class KickoffGroupMember extends GroupHandler<WFCMessage.RemoveGroupMembe
errorCode = ErrorCode.ERROR_CODE_NOT_EXIST; errorCode = ErrorCode.ERROR_CODE_NOT_EXIST;
} else { } else {
boolean isAdmin = requestSourceType == ProtoConstants.RequestSourceType.Request_From_Admin; 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; boolean isAllow = isAdmin;
if (!isAllow) { if (!isAllow) {
if (groupInfo.getOwner() != null) { if (groupInfo.getOwner() != null) {
@@ -58,6 +62,13 @@ public class KickoffGroupMember extends GroupHandler<WFCMessage.RemoveGroupMembe
return ErrorCode.ERROR_CODE_NOT_RIGHT; 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 //send notify message first, then kickoff the member
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) { if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
sendGroupNotification(fromUser, groupInfo.getTargetId(), request.getToLineList(), request.getNotifyContent()); 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; 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); ErrorCode errorCode= m_messagesStore.modifyGroupInfo(fromUser, request.getGroupId(), request.getType(), request.getValue(), isAdmin);
if (errorCode == ERROR_CODE_SUCCESS) { if (errorCode == ERROR_CODE_SUCCESS) {
if(request.hasNotifyContent() && request.getNotifyContent().getType() > 0) { if(request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -31,7 +31,14 @@ public class QuitGroupHandler extends GroupHandler<WFCMessage.QuitGroupRequest>
return ErrorCode.ERROR_CODE_NOT_RIGHT; 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 (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) { if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
sendGroupNotification(fromUser, request.getGroupId(), request.getToLineList(), request.getNotifyContent()); 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; 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 serverIp = mServer.getServerIp();
String longPort = mServer.getLongPort(); String longPort = mServer.getLongPort();
String shortPort = mServer.getShortPort(); String shortPort = mServer.getShortPort();
@@ -22,6 +22,7 @@ import cn.wildfirechat.common.ErrorCode;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import win.liyufan.im.*; import win.liyufan.im.*;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -33,6 +34,7 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
private String mForwardUrl = null; private String mForwardUrl = null;
private String mSensitiveMessageForwardUrl = null; private String mSensitiveMessageForwardUrl = null;
private Set<Integer> mForwardMessageTypes = new HashSet<>(); private Set<Integer> mForwardMessageTypes = new HashSet<>();
private Set<Integer> mForwardExcludeMessageTypes = new HashSet<>();
private String mMentionForwardUrl = null; private String mMentionForwardUrl = null;
private int mBlacklistStrategy = 0; //黑名单中时,0失败,1吞掉。 private int mBlacklistStrategy = 0; //黑名单中时,0失败,1吞掉。
private boolean mNoForwardAdminMessage = false; private boolean mNoForwardAdminMessage = false;
@@ -47,12 +49,9 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
mForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Url); mForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Url);
if (!StringUtil.isNullOrEmpty(mForwardUrl)) { if (!StringUtil.isNullOrEmpty(mForwardUrl)) {
String forwardTypes = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Types); String forwardTypes = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Types);
if (!StringUtil.isNullOrEmpty(forwardTypes)) { parseTypes(mForwardMessageTypes, forwardTypes);
String[] tss = forwardTypes.split(","); String excludeTypes = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Exclude_Types);
for (String ts:tss) { parseTypes(mForwardExcludeMessageTypes, excludeTypes);
mForwardMessageTypes.add(Integer.parseInt(ts.trim()));
}
}
} }
mSensitiveMessageForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Sensitive_Forward_Url); mSensitiveMessageForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Sensitive_Forward_Url);
mMentionForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_MentionMsg_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 @Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.Message message, Qos1PublishHandler.IMCallback callback) { public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.Message message, Qos1PublishHandler.IMCallback callback) {
ErrorCode errorCode = ErrorCode.ERROR_CODE_SUCCESS; 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(); 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); publisher.forwardMessage(message, mForwardUrl, outputClient);
} }
@@ -30,6 +30,13 @@ public class SetGroupManagerHandler extends GroupHandler<WFCMessage.SetGroupMana
return ErrorCode.ERROR_CODE_NOT_RIGHT; 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); ErrorCode errorCode = m_messagesStore.setGroupManager(fromUser, request.getGroupId(), request.getType(), request.getUserIdList(), isAdmin);
if (errorCode == ERROR_CODE_SUCCESS) { if (errorCode == ERROR_CODE_SUCCESS) {
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) { if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -30,6 +30,13 @@ public class TransferGroupHandler extends GroupHandler<WFCMessage.TransferGroupR
return ErrorCode.ERROR_CODE_NOT_RIGHT; 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); ErrorCode errorCode = m_messagesStore.transferGroup(fromUser, request.getGroupId(), request.getNewOwner(), isAdmin);
if (errorCode == ERROR_CODE_SUCCESS) { if (errorCode == ERROR_CODE_SUCCESS) {
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) { if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -25,9 +25,11 @@ public class UploadDeviceTokenHandler extends IMHandler<WFCMessage.UploadDeviceT
session.setPlatform(request.getPlatform()); session.setPlatform(request.getPlatform());
session.setAppName(request.getAppName()); session.setAppName(request.getAppName());
if (request.getPlatform() == ProtoConstants.Platform.Platform_iOS && request.getPushType() == 2) { if (request.getPlatform() == ProtoConstants.Platform.Platform_iOS && request.getPushType() == 2) {
LOG.info("Set device token, userId {}", fromUser);
session.setVoipDeviceToken(request.getDeviceToken()); session.setVoipDeviceToken(request.getDeviceToken());
m_sessionsStore.updateSessionToken(session, true); m_sessionsStore.updateSessionToken(session, true);
} else { } else {
LOG.info("Set device token, userId {}, platform {}, pushType {}", fromUser, session.getPlatform(), request.getPushType());
session.setDeviceToken(request.getDeviceToken()); session.setDeviceToken(request.getDeviceToken());
session.setPushType(request.getPushType()); session.setPushType(request.getPushType());
m_sessionsStore.updateSessionToken(session, false); m_sessionsStore.updateSessionToken(session, false);
@@ -1493,12 +1493,6 @@ public class DatabaseStore {
if (!strEqual(session.getAppName(), request.getApp())) { if (!strEqual(session.getAppName(), request.getApp())) {
sql += " `_package_name` = ?,"; sql += " `_package_name` = ?,";
} }
if (session.getPlatform() != request.getPlatform()) {
sql += " `_platform` = ?,";
}
if (session.getPushType() != request.getPushType()) {
sql += " `_push_type` = ?,";
}
if (!strEqual(session.getDeviceName(), request.getDeviceName())) { if (!strEqual(session.getDeviceName(), request.getDeviceName())) {
sql += " `_device_name` = ?,"; sql += " `_device_name` = ?,";
} }
@@ -1526,12 +1520,6 @@ public class DatabaseStore {
if (!strEqual(session.getAppName(), request.getApp())) { if (!strEqual(session.getAppName(), request.getApp())) {
statement.setString(index++, 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())) { if (!strEqual(session.getDeviceName(), request.getDeviceName())) {
statement.setString(index++, request.getDeviceName()); statement.setString(index++, request.getDeviceName());
} }
@@ -1712,6 +1700,35 @@ public class DatabaseStore {
return null; 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) { void updateGroupMemberDt(final String groupId, final long dt) {
Connection connection = null; Connection connection = null;
PreparedStatement statement = null; PreparedStatement statement = null;
@@ -49,6 +49,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -121,6 +122,8 @@ public class MemoryMessagesStore implements IMessagesStore {
private Lock mReadLock = mLock.readLock(); private Lock mReadLock = mLock.readLock();
private Lock mWriteLock = mLock.writeLock(); private Lock mWriteLock = mLock.writeLock();
private Lock mUserSettingLock = new ReentrantLock();
private final DatabaseStore databaseStore; private final DatabaseStore databaseStore;
private ConcurrentHashMap<String, Long> userMaxPullSeq = new ConcurrentHashMap<>(); 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 mFriendRequestDuration = 7 * 24 * 60 * 60 * 1000;
private long mFriendRejectDuration = 30 * 24 * 60 * 60 * 1000; private long mFriendRejectDuration = 30 * 24 * 60 * 60 * 1000;
private long mFriendRequestExpiration = 7 * 24 * 60 * 60 * 1000; private long mFriendRequestExpiration = 7 * 24 * 60 * 60 * 1000;
private boolean mFriendNewWelcomeMessage = true;
private boolean mMultiPlatformNotification = false; private boolean mMultiPlatformNotification = false;
private boolean mMobileDefaultSilentWhenPCOnline = true; private boolean mMobileDefaultSilentWhenPCOnline = true;
private boolean mDisableStrangerChat = false; 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 long mChatroomParticipantIdleTime = 900000;
private boolean mChatroomRejoinWhenActive = true; private boolean mChatroomRejoinWhenActive = true;
@@ -165,6 +171,7 @@ public class MemoryMessagesStore implements IMessagesStore {
private boolean mGroupAllowClientCustomOperationNotification; private boolean mGroupAllowClientCustomOperationNotification;
private boolean mGroupAllowRobotCustomOperationNotification; private boolean mGroupAllowRobotCustomOperationNotification;
private int mGroupVisibleQuitKickoffNotification; private int mGroupVisibleQuitKickoffNotification;
private int mGroupForbiddenClientOperation;
private int mSyncDataPartSize = 0; private int mSyncDataPartSize = 0;
private boolean keepDisplayNameWhenDestroyUser = true; 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")); 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)); 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 { try {
mDisableStrangerChat = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.MESSAGE_Disable_Stranger_Chat)); mDisableStrangerChat = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.MESSAGE_Disable_Stranger_Chat));
} catch (Exception e) { } catch (Exception e) {
@@ -196,6 +224,18 @@ public class MemoryMessagesStore implements IMessagesStore {
printMissConfigLog(MESSAGE_Disable_Stranger_Chat, mDisableStrangerChat + ""); 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 { try {
mMultiPlatformNotification = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.SERVER_MULTI_PLATFROM_NOTIFICATION, "false")); mMultiPlatformNotification = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.SERVER_MULTI_PLATFROM_NOTIFICATION, "false"));
} catch (Exception e) { } catch (Exception e) {
@@ -256,15 +296,6 @@ public class MemoryMessagesStore implements IMessagesStore {
printMissConfigLog(FRIEND_Request_Expiration_Duration, mFriendRequestExpiration + ""); 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 { try {
mChatroomRejoinWhenActive = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CHATROOM_Rejoin_When_Active)); mChatroomRejoinWhenActive = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CHATROOM_Rejoin_When_Active));
} catch (Exception e) { } 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 { try {
mSyncDataPartSize = Integer.parseInt(server.getConfig().getProperty(SYNC_Data_Part_Size, "0")); mSyncDataPartSize = Integer.parseInt(server.getConfig().getProperty(SYNC_Data_Part_Size, "0"));
} catch (Exception e) { } catch (Exception e) {
@@ -539,8 +599,17 @@ public class MemoryMessagesStore implements IMessagesStore {
int pullType = ProtoConstants.PullType.Pull_Normal; int pullType = ProtoConstants.PullType.Pull_Normal;
if (type == ProtoConstants.ConversationType.ConversationType_Private) { if (type == ProtoConstants.ConversationType.ConversationType_Private) {
notifyReceivers.add(fromUser); if(message.getToCount() > 0) {
notifyReceivers.add(message.getConversation().getTarget()); if(message.getToList().contains(fromUser)) {
notifyReceivers.add(fromUser);
}
if(message.getToList().contains(message.getConversation().getTarget())) {
notifyReceivers.add(message.getConversation().getTarget());
}
} else {
notifyReceivers.add(fromUser);
notifyReceivers.add(message.getConversation().getTarget());
}
pullType = ProtoConstants.PullType.Pull_Normal; pullType = ProtoConstants.PullType.Pull_Normal;
} else if (type == ProtoConstants.ConversationType.ConversationType_Group) { } else if (type == ProtoConstants.ConversationType.ConversationType_Group) {
notifyReceivers.add(fromUser); notifyReceivers.add(fromUser);
@@ -1267,6 +1336,9 @@ public class MemoryMessagesStore implements IMessagesStore {
int maxCount = Integer.MAX_VALUE; int maxCount = Integer.MAX_VALUE;
if (!isAdmin) { if (!isAdmin) {
if (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
boolean isMember = false; boolean isMember = false;
boolean isManager = false; boolean isManager = false;
WFCMessage.GroupMember gm = getGroupMember(groupId, operator); WFCMessage.GroupMember gm = getGroupMember(groupId, operator);
@@ -1407,18 +1479,22 @@ public class MemoryMessagesStore implements IMessagesStore {
void removeFavGroup(String groupId, List<String> memberIds) { void removeFavGroup(String groupId, List<String> memberIds) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
databaseStore.removeFavGroup(groupId, memberIds); databaseStore.removeFavGroup(groupId, memberIds);
try {
for (String member : memberIds) { mUserSettingLock.lock();
userSettingMap.remove(member); MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
for (String member : memberIds) {
userSettingMap.remove(member);
}
} finally {
mUserSettingLock.unlock();
} }
} }
@Override @Override
public ErrorCode quitGroup(String operator, String groupId) { public ErrorCode quitGroup(String operator, String groupId, boolean admin) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
IMap<String, WFCMessage.GroupInfo> mIMap = hzInstance.getMap(GROUPS_MAP); IMap<String, WFCMessage.GroupInfo> mIMap = hzInstance.getMap(GROUPS_MAP);
@@ -1438,6 +1514,10 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_NOT_EXIST; 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)) { if (groupInfo.getType() != ProtoConstants.GroupType.GroupType_Free && groupInfo.getOwner() != null && groupInfo.getOwner().equals(operator)) {
MultiMap<String, WFCMessage.GroupMember> groupMembers = hzInstance.getMultiMap(GROUP_MEMBERS); MultiMap<String, WFCMessage.GroupMember> groupMembers = hzInstance.getMultiMap(GROUP_MEMBERS);
Collection<WFCMessage.GroupMember> members = groupMembers.get(groupId); Collection<WFCMessage.GroupMember> members = groupMembers.get(groupId);
@@ -1480,7 +1560,7 @@ public class MemoryMessagesStore implements IMessagesStore {
int[] removedCount = new int[1]; int[] removedCount = new int[1];
for (String groupId : groupIds) { for (String groupId : groupIds) {
removedCount[0] = 0; removedCount[0] = 0;
ErrorCode errorCode = quitGroup(userId, groupId); ErrorCode errorCode = quitGroup(userId, groupId, true);
LOG.info("clear user {} group {} result {}", userId, groupId, errorCode); LOG.info("clear user {} group {} result {}", userId, groupId, errorCode);
} }
} }
@@ -1488,13 +1568,17 @@ public class MemoryMessagesStore implements IMessagesStore {
private void removeGroupUserSettings(String groupId, List<String> users) { private void removeGroupUserSettings(String groupId, List<String> users) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
databaseStore.removeGroupUserSettings(groupId, users); databaseStore.removeGroupUserSettings(groupId, users);
for (String userId:users) { try {
userSettingMap.remove(userId); mUserSettingLock.lock();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
for (String userId:users) {
userSettingMap.remove(userId);
}
} finally {
mUserSettingLock.unlock();
} }
} }
@Override @Override
@@ -1512,6 +1596,7 @@ public class MemoryMessagesStore implements IMessagesStore {
} }
if (!isAdmin && (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Free || 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.getType() == ProtoConstants.GroupType.GroupType_Restricted || groupInfo.getType() == ProtoConstants.GroupType.GroupType_Normal)
&& (groupInfo.getOwner() == null || !groupInfo.getOwner().equals(operator)))) { && (groupInfo.getOwner() == null || !groupInfo.getOwner().equals(operator)))) {
return ErrorCode.ERROR_CODE_NOT_RIGHT; return ErrorCode.ERROR_CODE_NOT_RIGHT;
@@ -1555,7 +1640,11 @@ public class MemoryMessagesStore implements IMessagesStore {
} }
if (!isAdmin) { 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; boolean isAllow = false;
if ((oldInfo.getOwner() != null && oldInfo.getOwner().equals(operator))) { if ((oldInfo.getOwner() != null && oldInfo.getOwner().equals(operator))) {
isAllow = true; isAllow = true;
@@ -1832,6 +1921,11 @@ public class MemoryMessagesStore implements IMessagesStore {
return databaseStore.getUserGroupIds(userId); return databaseStore.getUserGroupIds(userId);
} }
@Override
public Set<String> getCommonGroupIds(String userId1, String userId2) {
return databaseStore.getCommonGroupIds(userId1, userId2);
}
@Override @Override
public ErrorCode getGroupMembers(String fromUser, String groupId, long maxDt, List<WFCMessage.GroupMember> members) { public ErrorCode getGroupMembers(String fromUser, String groupId, long maxDt, List<WFCMessage.GroupMember> members) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
@@ -1916,6 +2010,10 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_NOT_RIGHT; 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? //check the new owner is in member list? is that necessary?
long updateDt = System.currentTimeMillis(); long updateDt = System.currentTimeMillis();
groupInfo = groupInfo.toBuilder().setOwner(newOwner).setUpdateDt(updateDt).setMemberUpdateDt(updateDt).build(); groupInfo = groupInfo.toBuilder().setOwner(newOwner).setUpdateDt(updateDt).setMemberUpdateDt(updateDt).build();
@@ -2780,6 +2878,11 @@ public class MemoryMessagesStore implements IMessagesStore {
if (mDisableStrangerChat) { if (mDisableStrangerChat) {
//在禁止私聊时,是否是允许私聊的用户id
if(mAllowStrangerChatSet.contains(targetUser)) {
return ErrorCode.ERROR_CODE_SUCCESS;
}
//在禁止私聊时,允许机器人,物联网设备及管理员进行私聊。 //在禁止私聊时,允许机器人,物联网设备及管理员进行私聊。
IMap<String, WFCMessage.User> mUserMap = hzInstance.getMap(USERS); IMap<String, WFCMessage.User> mUserMap = hzInstance.getMap(USERS);
WFCMessage.User target = mUserMap.get(targetUser); WFCMessage.User target = mUserMap.get(targetUser);
@@ -3363,10 +3466,12 @@ public class MemoryMessagesStore implements IMessagesStore {
if(robotData != null && !StringUtil.isNullOrEmpty(robotData.getCallback()) && !StringUtil.isNullOrEmpty(robotData.getSecret())) { if(robotData != null && !StringUtil.isNullOrEmpty(robotData.getCallback()) && !StringUtil.isNullOrEmpty(robotData.getSecret())) {
try { try {
URL url = new URL(robotData.getCallback()); 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(); secret = robotData.getSecret();
} else { } 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) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
@@ -3387,10 +3492,12 @@ public class MemoryMessagesStore implements IMessagesStore {
if(channelData != null && !StringUtil.isNullOrEmpty(channelData.getCallback()) && !StringUtil.isNullOrEmpty(channelData.getSecret())) { if(channelData != null && !StringUtil.isNullOrEmpty(channelData.getCallback()) && !StringUtil.isNullOrEmpty(channelData.getSecret())) {
try { try {
URL url = new URL(channelData.getCallback()); 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(); secret = channelData.getSecret();
} else { } 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) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
@@ -3496,11 +3603,6 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_AUTH_FAILURE; return ErrorCode.ERROR_CODE_AUTH_FAILURE;
} }
@Override
public boolean isNewFriendWelcomeMessage() {
return mFriendNewWelcomeMessage;
}
@Override @Override
public ErrorCode deleteFriend(String userId, String friendUid, long[] head) { public ErrorCode deleteFriend(String userId, String friendUid, long[] head) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
@@ -3550,9 +3652,15 @@ public class MemoryMessagesStore implements IMessagesStore {
IMap<String, WFCMessage.GroupInfo> mGroups = hzInstance.getMap(GROUPS_MAP); IMap<String, WFCMessage.GroupInfo> mGroups = hzInstance.getMap(GROUPS_MAP);
IMap<String, WFCMessage.ChannelInfo> mChannels = hzInstance.getMap(CHANNELS); IMap<String, WFCMessage.ChannelInfo> mChannels = hzInstance.getMap(CHANNELS);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId); Collection<WFCMessage.UserSettingEntry> entries;
if (entries == null || entries.size() == 0) { try {
entries = loadPersistedUserSettings(userId, userSettingMap); 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; ErrorCode ec = ErrorCode.ERROR_CODE_NOT_MODIFIED;
@@ -3631,17 +3739,22 @@ public class MemoryMessagesStore implements IMessagesStore {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING); MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId); try {
if (entries == null || entries.size() == 0) { mUserSettingLock.lock();
entries = loadPersistedUserSettings(userId, userSettingMap); Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
} if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
}
if (entries != null) { if (entries != null) {
for (WFCMessage.UserSettingEntry entry : entries) { for (WFCMessage.UserSettingEntry entry : entries) {
if (entry.getScope() == scope && (key== null || key.equals(entry.getKey()))) { if (entry.getScope() == scope && (key== null || key.equals(entry.getKey()))) {
return entry; return entry;
}
} }
} }
} finally {
mUserSettingLock.unlock();
} }
return null; return null;
@@ -3652,19 +3765,25 @@ public class MemoryMessagesStore implements IMessagesStore {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING); MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId); List<WFCMessage.UserSettingEntry> result;
if (entries == null || entries.size() == 0) { try {
entries = loadPersistedUserSettings(userId, userSettingMap); 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<>(); result = new ArrayList<>();
if (entries != null) { if (entries != null) {
for (WFCMessage.UserSettingEntry entry : entries for (WFCMessage.UserSettingEntry entry : entries
) { ) {
if (entry.getScope() == scope) { if (entry.getScope() == scope) {
result.add(entry); result.add(entry);
}
} }
} }
} finally {
mUserSettingLock.unlock();
} }
return result; return result;
@@ -3675,28 +3794,34 @@ public class MemoryMessagesStore implements IMessagesStore {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING); MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId); long updateDt = 0;
if (entries == null || entries.size() == 0) { try {
entries = loadPersistedUserSettings(userId, userSettingMap); mUserSettingLock.lock();
if (entries == null) { Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
entries = new ArrayList<>(); if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
if (entries == null) {
entries = new ArrayList<>();
}
} }
}
long updateDt = System.currentTimeMillis(); updateDt = System.currentTimeMillis();
WFCMessage.UserSettingEntry settingEntry = WFCMessage.UserSettingEntry.newBuilder().setScope(request.getScope()).setKey(request.getKey()).setValue(request.getValue()).setUpdateDt(updateDt).build(); WFCMessage.UserSettingEntry settingEntry = WFCMessage.UserSettingEntry.newBuilder().setScope(request.getScope()).setKey(request.getKey()).setValue(request.getValue()).setUpdateDt(updateDt).build();
databaseStore.persistUserSetting(userId, settingEntry); databaseStore.persistUserSetting(userId, settingEntry);
for (WFCMessage.UserSettingEntry entry : entries for (WFCMessage.UserSettingEntry entry : entries
) { ) {
if (entry.getScope() == request.getScope() && entry.getKey().equals(request.getKey())) { if (entry.getScope() == request.getScope() && entry.getKey().equals(request.getKey())) {
userSettingMap.remove(userId, entry); userSettingMap.remove(userId, entry);
userSettingMap.put(userId, settingEntry); userSettingMap.put(userId, settingEntry);
break; break;
}
} }
}
userSettingMap.put(userId, settingEntry); userSettingMap.put(userId, settingEntry);
} finally {
mUserSettingLock.unlock();
}
if(request.getScope() == UserSettingScope.kUserSettingConversationSilent) { if(request.getScope() == UserSettingScope.kUserSettingConversationSilent) {
int firstSplit = request.getKey().indexOf("-"); int firstSplit = request.getKey().indexOf("-");
@@ -3721,8 +3846,14 @@ public class MemoryMessagesStore implements IMessagesStore {
public void clearUserSettings(String userId) { public void clearUserSettings(String userId) {
databaseStore.clearUserSetting(userId); databaseStore.clearUserSetting(userId);
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); 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 @Override
@@ -4113,6 +4244,11 @@ public class MemoryMessagesStore implements IMessagesStore {
return mGroupVisibleQuitKickoffNotification; return mGroupVisibleQuitKickoffNotification;
} }
@Override
public int getGroupForbiddenClientOperation() {
return mGroupForbiddenClientOperation;
}
@Override @Override
public boolean isForwardMessageWithClientInfo() { public boolean isForwardMessageWithClientInfo() {
return mForwardMessageWithClientInfo; return mForwardMessageWithClientInfo;
@@ -4253,6 +4389,34 @@ public class MemoryMessagesStore implements IMessagesStore {
return UUIDGenerator.getUUID(); 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 @Override
public void storeRetained(Topic topic, StoredMessage storedMessage) { public void storeRetained(Topic topic, StoredMessage storedMessage) {
LOG.debug("Store retained message for topic={}, CId={}", topic, storedMessage.getClientID()); LOG.debug("Store retained message for topic={}, CId={}", topic, storedMessage.getClientID());
@@ -415,7 +415,7 @@ public class MemorySessionStore implements ISessionsStore {
remove = true; remove = true;
} }
} else if(platform == ProtoConstants.Platform.Platform_OSX || platform == ProtoConstants.Platform.Platform_Windows || platform == ProtoConstants.Platform.Platform_LINUX) { } 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; remove = true;
} }
} else { } else {
@@ -378,7 +378,7 @@ public class Server {
LOG.info("Configuring embedded Hazelcast instance"); LOG.info("Configuring embedded Hazelcast instance");
serverIp = getServerIp(config); serverIp = getServerIp(config);
String hzConfigPath = "config/hazelcast.xml"; String hzConfigPath = System.getProperty("hazelcast.configuration", "config/hazelcast.xml");
if (hzConfigPath != null) { if (hzConfigPath != null) {
boolean isHzConfigOnClasspath = this.getClass().getClassLoader().getResource(hzConfigPath) != null; boolean isHzConfigOnClasspath = this.getClass().getClassLoader().getResource(hzConfigPath) != null;
Config hzconfig = isHzConfigOnClasspath 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); 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 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 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); void clearUserGroups(String userId);
ErrorCode dismissGroup(String operator, String groupId, boolean isAdmin); ErrorCode dismissGroup(String operator, String groupId, boolean isAdmin);
ErrorCode modifyGroupInfo(String operator, String groupId, int modifyType, String value, 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); List<WFCMessage.GroupInfo> getGroupInfos(List<WFCMessage.UserRequest> requests);
WFCMessage.GroupInfo getGroupInfo(String groupId); WFCMessage.GroupInfo getGroupInfo(String groupId);
Set<String> getUserGroupIds(String userId); Set<String> getUserGroupIds(String userId);
Set<String> getCommonGroupIds(String userId1, String userId2);
ErrorCode getGroupMembers(String fromUser, String groupId, long maxDt, List<WFCMessage.GroupMember> members); ErrorCode getGroupMembers(String fromUser, String groupId, long maxDt, List<WFCMessage.GroupMember> members);
WFCMessage.GroupMember getGroupMember(String groupId, String memberId); WFCMessage.GroupMember getGroupMember(String groupId, String memberId);
ErrorCode transferGroup(String operator, String groupId, String newOwner, boolean isAdmin); 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 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); 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 deleteFriend(String userId, String friendUid, long[] head);
ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head); ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head);
FriendData getFriendData(String fromUser, String targetUserId); FriendData getFriendData(String fromUser, String targetUserId);
@@ -233,6 +233,8 @@ public interface IMessagesStore {
boolean isAllowRobotCustomGroupNotification(); boolean isAllowRobotCustomGroupNotification();
int getVisibleQuitKickoffNotification(); int getVisibleQuitKickoffNotification();
int getGroupForbiddenClientOperation();
boolean isForwardMessageWithClientInfo(); boolean isForwardMessageWithClientInfo();
boolean isRobotCallbackWithClientInfo(); boolean isRobotCallbackWithClientInfo();
boolean isChannelCallbackWithClientInfo(); boolean isChannelCallbackWithClientInfo();
@@ -249,6 +251,9 @@ public interface IMessagesStore {
//使用了数据库,会比较慢,仅能用户用户/群组等id的生成 //使用了数据库,会比较慢,仅能用户用户/群组等id的生成
String getShortUUID(); String getShortUUID();
boolean checkSignature(String signature);
boolean existSignatures();
/** /**
* Used to initialize all persistent store structures * Used to initialize all persistent store structures
*/ */
@@ -652,6 +652,8 @@ public class MessagesPublisher {
pushContent = "[文件]"; pushContent = "[文件]";
} else if(type == ProtoConstants.ContentType.Sticker) { } else if(type == ProtoConstants.ContentType.Sticker) {
pushContent = "[表情]"; 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.IMTopic;
import win.liyufan.im.Utility; import win.liyufan.im.Utility;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@@ -339,6 +340,24 @@ public class ProtocolProcessor {
failedCredentials(channel); failedCredentials(channel);
return false; 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); session.setMqttVersion(mqttVersion);
} else { } else {
LOG.error("Client didn't supply any password and MQTT anonymous mode is disabled CId={}", clientId); 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); 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) { private void setIdleTime(ChannelPipeline pipeline, int idleTime) {
if (pipeline.names().contains("idleStateHandler")) { if (pipeline.names().contains("idleStateHandler")) {
pipeline.remove("idleStateHandler"); pipeline.remove("idleStateHandler");
@@ -32,6 +32,7 @@ public final class MqttConnectPayload {
private final byte[] willMessage; private final byte[] willMessage;
private final String userName; private final String userName;
private final byte[] password; private final byte[] password;
private final byte[] signature;
/** /**
* @deprecated use {@link MqttConnectPayload#MqttConnectPayload(String, * @deprecated use {@link MqttConnectPayload#MqttConnectPayload(String,
@@ -50,7 +51,7 @@ public final class MqttConnectPayload {
willTopic, willTopic,
willMessage.getBytes(CharsetUtil.UTF_8), willMessage.getBytes(CharsetUtil.UTF_8),
userName, userName,
password.getBytes(CharsetUtil.UTF_8)); password.getBytes(CharsetUtil.UTF_8), null);
} }
public MqttConnectPayload( public MqttConnectPayload(
@@ -64,7 +65,7 @@ public final class MqttConnectPayload {
willTopic, willTopic,
willMessage, willMessage,
userName, userName,
password); password, null);
} }
public MqttConnectPayload( public MqttConnectPayload(
@@ -73,13 +74,15 @@ public final class MqttConnectPayload {
String willTopic, String willTopic,
byte[] willMessage, byte[] willMessage,
String userName, String userName,
byte[] password) { byte[] password,
byte[] signature) {
this.clientIdentifier = clientIdentifier; this.clientIdentifier = clientIdentifier;
this.willProperties = MqttProperties.withEmptyDefaults(willProperties); this.willProperties = MqttProperties.withEmptyDefaults(willProperties);
this.willTopic = willTopic; this.willTopic = willTopic;
this.willMessage = willMessage; this.willMessage = willMessage;
this.userName = userName; this.userName = userName;
this.password = password; this.password = password;
this.signature = signature;
} }
public String clientIdentifier() { public String clientIdentifier() {
@@ -122,6 +125,10 @@ public final class MqttConnectPayload {
return password; return password;
} }
public byte[] signatureInBytes() {
return signature;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
@@ -29,6 +29,7 @@ public enum MqttConnectReturnCode {
CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05), CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05),
CONNECTION_REFUSED_UNEXPECT_NODE((byte) 0X06), CONNECTION_REFUSED_UNEXPECT_NODE((byte) 0X06),
CONNECTION_REFUSED_SESSION_NOT_EXIST((byte) 0x07), CONNECTION_REFUSED_SESSION_NOT_EXIST((byte) 0x07),
CONNECTION_REFUSED_SIGNATURE_FAILURE((byte) 0x08),
//MQTT 5 codes //MQTT 5 codes
CONNECTION_REFUSED_UNSPECIFIED_ERROR((byte) 0x80), CONNECTION_REFUSED_UNSPECIFIED_ERROR((byte) 0x80),
CONNECTION_REFUSED_MALFORMED_PACKET((byte) 0x81), CONNECTION_REFUSED_MALFORMED_PACKET((byte) 0x81),
@@ -146,7 +146,7 @@ public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState
* bytes for the remaining length. * bytes for the remaining length.
* *
* @see * @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 * @param buffer the buffer to decode from
* @return the fixed header * @return the fixed header
@@ -534,7 +534,7 @@ public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState
final MqttProperties willProperties; final MqttProperties willProperties;
if (mqttConnectVariableHeader.isWillFlag()) { if (mqttConnectVariableHeader.isWillFlag()) {
if (mqttVersion == MqttVersion.MQTT_5) { if (mqttVersion.protocolLevel() <= MqttVersion.MQTT_5.protocolLevel()) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer); final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
willProperties = propertiesResult.value; willProperties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed; numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
@@ -550,6 +550,7 @@ public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState
} }
Result<String> decodedUserName = null; Result<String> decodedUserName = null;
byte[] decodedPassword = null; byte[] decodedPassword = null;
byte[] decodedSignature = null;
if (mqttConnectVariableHeader.hasUserName()) { if (mqttConnectVariableHeader.hasUserName()) {
decodedUserName = decodeString(buffer); decodedUserName = decodeString(buffer);
numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed; numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed;
@@ -558,6 +559,10 @@ public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState
decodedPassword = decodeByteArray(buffer); decodedPassword = decodeByteArray(buffer);
numberOfBytesConsumed += decodedPassword.length + 2; numberOfBytesConsumed += decodedPassword.length + 2;
} }
if (mqttConnectVariableHeader.isWillRetain()) {
decodedSignature = decodeByteArray(buffer);
numberOfBytesConsumed += decodedSignature.length + 2;
}
final MqttConnectPayload mqttConnectPayload = final MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload( new MqttConnectPayload(
@@ -566,7 +571,9 @@ public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState
decodedWillTopic != null ? decodedWillTopic.value : null, decodedWillTopic != null ? decodedWillTopic.value : null,
decodedWillMessage, decodedWillMessage,
decodedUserName != null ? decodedUserName.value : null, decodedUserName != null ? decodedUserName.value : null,
decodedPassword); decodedPassword,
decodedSignature
);
return new Result<MqttConnectPayload>(mqttConnectPayload, numberOfBytesConsumed); return new Result<MqttConnectPayload>(mqttConnectPayload, numberOfBytesConsumed);
} }
@@ -208,7 +208,7 @@ public final class MqttMessageBuilders {
keepAliveSecs, keepAliveSecs,
properties); properties);
MqttConnectPayload mqttConnectPayload = 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); return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload);
} }
} }
@@ -24,7 +24,6 @@ import com.mchange.v2.c3p0.ComboPooledDataSource;
import io.moquette.BrokerConstants; import io.moquette.BrokerConstants;
import io.moquette.server.config.IConfig; import io.moquette.server.config.IConfig;
import org.flywaydb.core.Flyway; import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.configuration.FlywayConfiguration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -50,6 +49,7 @@ public class DBUtil {
public static void init(IConfig config) { public static void init(IConfig config) {
String embedDB = config.getProperty(BrokerConstants.EMBED_DB_PROPERTY_NAME); 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)); boolean autoCleanMsgs = "true".equals(config.getProperty(BrokerConstants.DB_AUTO_CLEAN_HISTORY_MESSAGES));
if (embedDB != null && embedDB.equals("1")) { if (embedDB != null && embedDB.equals("1")) {
IsEmbedDB = true; IsEmbedDB = true;
@@ -68,7 +68,7 @@ public class DBUtil {
migrateLocation = "filesystem:./migrate/h2"; migrateLocation = "filesystem:./migrate/h2";
comboPooledDataSource = new ComboPooledDataSource(); 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.setUser("SA");
comboPooledDataSource.setPassword("SA"); comboPooledDataSource.setPassword("SA");
comboPooledDataSource.setMinPoolSize(5); comboPooledDataSource.setMinPoolSize(5);
@@ -68,7 +68,9 @@ public class GitRepositoryState {
public static GitRepositoryState getGitRepositoryState() throws IOException { public static GitRepositoryState getGitRepositoryState() throws IOException {
Properties properties = new Properties(); Properties properties = new Properties();
try { 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)); BufferedReader bf = new BufferedReader(new InputStreamReader(inputStream));
properties.load(bf); properties.load(bf);
} catch (IOException e) { } catch (IOException e) {
@@ -87,8 +87,8 @@ public class HttpUtils {
} }
System.out.println(logText); System.out.println(logText);
e.printStackTrace(); e.printStackTrace();
LOG.info(logText); LOG.error(logText);
LOG.info("POST to {} with data {} failure", url, jsonStr); LOG.error("POST to {} failed", url);
if(httpCallback != null) { if(httpCallback != null) {
httpCallback.onFailure(-1, e.getMessage()); 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 GetGroupMemberTopic = "GPGM";
String TransferGroupTopic = "GTG"; String TransferGroupTopic = "GTG";
String SetGroupManagerTopic = "GSM"; String SetGroupManagerTopic = "GSM";
String GetMyGroupsTopic = "GMGS";
String GetCommonGroupsTopic = "GCGS";
String GetUserInfoTopic = "UPUI"; String GetUserInfoTopic = "UPUI";
String ModifyMyInfoTopic = "MMI"; String ModifyMyInfoTopic = "MMI";
+2 -3
View File
@@ -6,7 +6,6 @@
<Properties> <Properties>
<!--自定义一些常量,之后使用${变量名}引用--> <!--自定义一些常量,之后使用${变量名}引用-->
<Property name="MSG_LOG_HOME">./logs</Property> <Property name="MSG_LOG_HOME">./logs</Property>
<Property name="logFileName">test.log</Property>
<property name="charset">UTF-8</property> <property name="charset">UTF-8</property>
<!--自定义的输出格式--> <!--自定义的输出格式-->
<property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property> <property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property>
@@ -21,14 +20,14 @@
<RollingRandomAccessFile name="RollingFileInfo" immediateFlush="false" <RollingRandomAccessFile name="RollingFileInfo" immediateFlush="false"
fileName="${MSG_LOG_HOME}/wildfirechat.log" 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> <Filters>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/> <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters> </Filters>
<PatternLayout pattern="${pattern}" /> <PatternLayout pattern="${pattern}" />
<Policies> <Policies>
<!-- TimeBasedTriggeringPolicy :时间滚动策略,默认0点小时产生新的文件,interval="6" : 自定义文件滚动时间间隔,每隔6小时产生新文件, modulate="true" : 产生文件是否以0点偏移时间,即6点,12点,18点,0点--> <!-- TimeBasedTriggeringPolicy :时间滚动策略,默认0点小时产生新的文件,interval="6" : 自定义文件滚动时间间隔,每隔6小时产生新文件, modulate="true" : 产生文件是否以0点偏移时间,即6点,12点,18点,0点-->
<!-- <TimeBasedTriggeringPolicy interval="6" modulate="true"/>--> <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<!-- SizeBasedTriggeringPolicy :文件大小滚动策略--> <!-- SizeBasedTriggeringPolicy :文件大小滚动策略-->
<SizeBasedTriggeringPolicy size="100 MB"/> <SizeBasedTriggeringPolicy size="100 MB"/>
</Policies> </Policies>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>wildfirechat-parent</artifactId> <artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId> <groupId>cn.wildfirechat</groupId>
<version>0.95</version> <version>1.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -77,6 +77,7 @@ public interface APIPath {
String Get_User_Groups = "/admin/group/of_user"; String Get_User_Groups = "/admin/group/of_user";
String Group_Set_Member_Alias = "/admin/group/member/set_alias"; String Group_Set_Member_Alias = "/admin/group/member/set_alias";
String Group_Set_Member_Extra = "/admin/group/member/set_extra"; 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 Create_Channel = "/admin/channel/create";
String Destroy_Channel = "/admin/channel/destroy"; String Destroy_Channel = "/admin/channel/destroy";
@@ -92,6 +93,10 @@ public interface APIPath {
String Conference_List_Participant = "/admin/conference/list_participant"; String Conference_List_Participant = "/admin/conference/list_participant";
String Conference_Create = "/admin/conference/create"; String Conference_Create = "/admin/conference/create";
String Conference_Destroy = "/admin/conference/destroy"; 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_User_Info = "/channel/user_info";
String Channel_Update_Profile = "/channel/update_profile"; String Channel_Update_Profile = "/channel/update_profile";
@@ -99,6 +104,7 @@ public interface APIPath {
String Channel_Message_Send = "/channel/message/send"; String Channel_Message_Send = "/channel/message/send";
String Channel_Subscribe = "/channel/subscribe"; String Channel_Subscribe = "/channel/subscribe";
String Channel_Subscriber_List = "/channel/subscriber_list"; 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 Channel_Application_Get_UserInfo = "/channel/application/get_user_info";
String Robot_User_Info = "/robot/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_MO_DISABLED(86, "origin side disable secret chat"),
ERROR_CODE_SECRET_CHAT_MT_DISABLED(87, "target 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_FUNCTION_DISABLED(220, "function disabled"),
ERROR_CODE_CHANNEL_NO_EXIST(236, "channel no exist"), ERROR_CODE_CHANNEL_NO_EXIST(236, "channel no exist"),
@@ -1,88 +0,0 @@
package cn.wildfirechat.messagecontentbuilder;
import cn.wildfirechat.pojos.MessagePayload;
import cn.wildfirechat.proto.ProtoConstants;
import io.netty.util.internal.StringUtil;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.function.Consumer;
public class ArticlesContentBuilder extends MessageContentBuilder{
public static class Article {
public String id;
public String cover;
public String title;
public String digest;
public String url;
public boolean readReport;
}
public Article topArticle;
public List<Article> subArticles;
public static ArticlesContentBuilder newBuilder(String id, String cover, String title, String digest, String url, boolean readReport) {
ArticlesContentBuilder builder = new ArticlesContentBuilder();
builder.topArticle = new Article();
builder.topArticle.id = id;
builder.topArticle.cover = cover;
builder.topArticle.title = title;
builder.topArticle.digest = digest;
builder.topArticle.url = url;
builder.topArticle.readReport = readReport;
return builder;
}
public ArticlesContentBuilder addSubArticle(String id, String cover, String title, String digest, String url, boolean readReport) {
Article article = new Article();
article.id = id;
article.cover = cover;
article.title = title;
article.digest = digest;
article.url = url;
article.readReport = readReport;
if(subArticles == null) {
subArticles = new ArrayList<>();
}
subArticles.add(article);
return this;
}
private static JSONObject toJSON(Article article) {
JSONObject top = new JSONObject();
if(!StringUtil.isNullOrEmpty(article.id))
top.put("id", article.id);
if(!StringUtil.isNullOrEmpty(article.cover))
top.put("cover", article.cover);
if(!StringUtil.isNullOrEmpty(article.title))
top.put("title", article.title);
if(!StringUtil.isNullOrEmpty(article.digest))
top.put("digest", article.digest);
if(!StringUtil.isNullOrEmpty(article.url))
top.put("url", article.url);
if(article.readReport)
top.put("rr", article.readReport);
return top;
}
@Override
public MessagePayload build() {
MessagePayload payload = encodeBase();
payload.setType(ProtoConstants.ContentType.Articles);
payload.setPersistFlag(ProtoConstants.PersistFlag.Persist_And_Count);
payload.setSearchableContent(topArticle.title);
JSONObject jsonObject = new JSONObject();
jsonObject.put("top", toJSON(topArticle));
if(subArticles != null && !subArticles.isEmpty()) {
JSONArray jsonArray = new JSONArray();
subArticles.forEach(article -> jsonArray.add(toJSON(article)));
jsonObject.put("subArticles", jsonArray);
}
payload.setBase64edData(Base64.getEncoder().encodeToString(jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8)));
return payload;
}
}
@@ -13,16 +13,18 @@ public class ArticleContent {
public String id; public String id;
public String cover; public String cover;
public String title; public String title;
public String digest;
public String url; public String url;
public boolean rr; public boolean rr;
public Article() { public Article() {
} }
public Article(String id, String cover, String title, String url, boolean rr) { public Article(String id, String cover, String title, String digest, String url, boolean rr) {
this.id = id; this.id = id;
this.cover = cover; this.cover = cover;
this.title = title; this.title = title;
this.digest = digest;
this.url = url; this.url = url;
this.rr = rr; this.rr = rr;
} }
@@ -33,14 +35,14 @@ public class ArticleContent {
public ArticleContent() { public ArticleContent() {
} }
public ArticleContent(String id, String cover, String title, String url, boolean rr) { public ArticleContent(String id, String cover, String title, String digest, String url, boolean rr) {
this.top = new Article(id, cover, title, url, rr); this.top = new Article(id, cover, title, digest, url, rr);
} }
public ArticleContent addSubArticle(String id, String cover, String title, String url, boolean rr) { public ArticleContent addSubArticle(String id, String cover, String title, String digest, String url, boolean rr) {
if (subArticles == null) if (subArticles == null)
subArticles = new ArrayList<>(); subArticles = new ArrayList<>();
subArticles.add(new Article(id, cover, title, url, rr)); subArticles.add(new Article(id, cover, title, digest, url, rr));
return this; return this;
} }
@@ -3,5 +3,7 @@ package cn.wildfirechat.pojos;
public class ConferenceLeaveEvent { public class ConferenceLeaveEvent {
public String userId; public String userId;
public String roomId; public String roomId;
public boolean rejoin;
public boolean kicked;
public boolean screenSharing; public boolean screenSharing;
} }
@@ -1,6 +1,15 @@
package cn.wildfirechat.pojos; package cn.wildfirechat.pojos;
import java.util.List;
public class PojoConferenceParticipant { public class PojoConferenceParticipant {
public static class Stream {
public String type;
public String mid;
public String codec;
}
public String userId; public String userId;
public boolean publishing; public boolean publishing;
public List<Stream> streams;
} }
@@ -0,0 +1,7 @@
package cn.wildfirechat.pojos;
public class PojoConferenceRecording {
public String roomId;
public boolean recording;
public boolean advance;
}
@@ -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; int GroupType_Free = 1;
//member can only quit, owner can do all the operations //member can only quit, owner can do all the operations
int GroupType_Restricted = 2; int GroupType_Restricted = 2;
//member can not do anything, server api manage the group
int GroupType_Organization = 3;
} }
//message GroupMember -> type //message GroupMember -> type
@@ -290,6 +292,34 @@ public class ProtoConstants {
int ApplicationType_Admin = 2; 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_CREATE_GROUP = 104;
public static final int MESSAGE_CONTENT_TYPE_ADD_GROUP_MEMBER = 105; public static final int MESSAGE_CONTENT_TYPE_ADD_GROUP_MEMBER = 105;
public static final int MESSAGE_CONTENT_TYPE_KICKOF_GROUP_MEMBER = 106; public static final int MESSAGE_CONTENT_TYPE_KICKOF_GROUP_MEMBER = 106;
+104 -2
View File
@@ -6,7 +6,7 @@
<!--<relativePath>../pom.xml</relativePath>--> <!--<relativePath>../pom.xml</relativePath>-->
<artifactId>wildfirechat-parent</artifactId> <artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId> <groupId>cn.wildfirechat</groupId>
<version>0.95</version> <version>1.1</version>
</parent> </parent>
<artifactId>distribution</artifactId> <artifactId>distribution</artifactId>
@@ -93,7 +93,109 @@
</execution> </execution>
</executions> </executions>
</plugin> </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> </plugins>
</build> </build>
</project> </project>
+31 -6
View File
@@ -21,13 +21,18 @@
</fileSet> </fileSet>
<fileSet> <fileSet>
<directory>../broker/config/i18n/</directory> <directory>src/main/resources/</directory>
<outputDirectory>/config/i18n</outputDirectory> <outputDirectory>config/</outputDirectory>
</fileSet> </fileSet>
<fileSet> <fileSet>
<directory>src/main/resources/</directory> <directory>../systemd/</directory>
<outputDirectory>config/</outputDirectory> <outputDirectory>/systemd</outputDirectory>
</fileSet>
<fileSet>
<directory>src/main/checker/</directory>
<outputDirectory>checker/</outputDirectory>
</fileSet> </fileSet>
</fileSets> </fileSets>
@@ -39,6 +44,12 @@
<fileMode>0755</fileMode> <fileMode>0755</fileMode>
</file> </file>
<file>
<source>src/main/scripts/stop.sh</source>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
</file>
<file> <file>
<source>src/main/scripts/wildfirechat.bat</source> <source>src/main/scripts/wildfirechat.bat</source>
<outputDirectory>bin</outputDirectory> <outputDirectory>bin</outputDirectory>
@@ -49,7 +60,7 @@
</file> </file>
<file> <file>
<source>src/main/files/release_note.md</source> <source>../release_note.md</source>
</file> </file>
<file> <file>
@@ -75,8 +86,22 @@
<outputDirectory>server_sdk/</outputDirectory> <outputDirectory>server_sdk/</outputDirectory>
<destName>common-${project.version}.jar</destName> <destName>common-${project.version}.jar</destName>
</file> </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> <dependencySets>
<dependencySet> <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" :间隔秒数,自动检测配置文件的变更和重新配置本身--> <!--monitorInterval="30" :间隔秒数,自动检测配置文件的变更和重新配置本身-->
<configuration status="WARN" monitorInterval="60"> <configuration status="WARN" monitorInterval="60">
<Properties> <Properties>
<!--自定义一些常量,之后使用${变量名}引用--> <!-- 日志路径-->
<Property name="MSG_LOG_HOME">./logs</Property> <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="charset">UTF-8</property>
<!--自定义的输出格式-->
<property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property> <property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property>
</Properties> </Properties>
<!--appenders:定义输出内容,输出格式,输出方式,日志保存策略等,常用其下三种标签[console,File,RollingFile]--> <!--appenders:定义输出内容,输出格式,输出方式,日志保存策略等,常用其下三种标签[console,File,RollingFile]-->
@@ -21,14 +20,14 @@
<RollingRandomAccessFile name="RollingFileInfo" immediateFlush="false" <RollingRandomAccessFile name="RollingFileInfo" immediateFlush="false"
fileName="${MSG_LOG_HOME}/wildfirechat.log" 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> <Filters>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/> <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters> </Filters>
<PatternLayout pattern="${pattern}" /> <PatternLayout pattern="${pattern}" />
<Policies> <Policies>
<!-- TimeBasedTriggeringPolicy :时间滚动策略,默认0点小时产生新的文件,interval="6" : 自定义文件滚动时间间隔,每隔6小时产生新文件, modulate="true" : 产生文件是否以0点偏移时间,即6点,12点,18点,0点--> <!-- TimeBasedTriggeringPolicy :时间滚动策略,默认0点小时产生新的文件,interval="6" : 自定义文件滚动时间间隔,每隔6小时产生新文件, modulate="true" : 产生文件是否以0点偏移时间,即6点,12点,18点,0点-->
<!-- <TimeBasedTriggeringPolicy interval="6" modulate="true"/>--> <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<!-- SizeBasedTriggeringPolicy :文件大小滚动策略--> <!-- SizeBasedTriggeringPolicy :文件大小滚动策略-->
<SizeBasedTriggeringPolicy size="100 MB"/> <SizeBasedTriggeringPolicy size="100 MB"/>
</Policies> </Policies>
@@ -31,6 +31,8 @@ local_port 80
## 是否使用内置DB。0使用mysql;1使用h2db。 ## 是否使用内置DB。0使用mysql;1使用h2db。
## MySQL需要把事物隔离级别改成"Read committed",使用命令来修改"set global transaction_isolation='read-committed';",其他数据库默认已经是这个级别不用修改 ## MySQL需要把事物隔离级别改成"Read committed",使用命令来修改"set global transaction_isolation='read-committed';",其他数据库默认已经是这个级别不用修改
## 如果使用MySQL,请在c3p0.xml文件中配置JDBC信息。
## 如果是MySQL5.7,需要开启长索引的支持,在数据库中执行命令:"set global innodb_large_prefix=on;"。其他版本默认支持,不用修改。
embed.db 1 embed.db 1
## 是否自动清理历史消息记录,当为true时,IM服务会定时扫描t_messages_x和t_user_messages_x表,清除掉3年前的历史消息。 ## 是否自动清理历史消息记录,当为true时,IM服务会定时扫描t_messages_x和t_user_messages_x表,清除掉3年前的历史消息。
@@ -39,6 +41,9 @@ embed.db 1
## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。 ## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。
db.auto_clean_history_messages true db.auto_clean_history_messages true
## h2db数据库的路径,默认为程序目录下的,可以指定其他目录。如果使用MySQL,可以忽略此配置
h2db.path ./h2db/wfchat
##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。 ##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。
http.admin.secret_key 123456 http.admin.secret_key 123456
@@ -52,6 +57,12 @@ token.key testim
##token的过期时间,单位为毫秒,默认为无限期。如果需要设置无限期,客户端上一定需要加上token过期的处理。token过期的处理请参考文档的常见问题 ##token的过期时间,单位为毫秒,默认为无限期。如果需要设置无限期,客户端上一定需要加上token过期的处理。token过期的处理请参考文档的常见问题
##token.expire_time 2592000000 ##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}毫秒之前未收取的消息也不会收取下拉; ##0 不接收历史消息,只接收${message.compensate_time_limit}毫秒以内的消息,由于服务器没有保存已经收取记录,所以如果有超过${message.compensate_time_limit}毫秒之前未收取的消息也不会收取下拉;
##1接收,会接收 message.max_queue 配置的条数的历史消息 ##1接收,会接收 message.max_queue 配置的条数的历史消息
@@ -72,6 +83,9 @@ message.chatroom_remote_history_message 1
##是否禁止陌生人聊天 ##是否禁止陌生人聊天
message.disable_stranger_chat false message.disable_stranger_chat false
##当禁止陌生人聊天时,允许聊天的用户id,比如管理员或者文件传输助手等。用户id以英文逗号分割。
message.allow_stranger_chat_list admin,FireRobot,wfc_file_transfer
##黑名单策略,0 发送失败,返回被拉黑的错误码;1 发送成功但消息被服务器直接丢弃 ##黑名单策略,0 发送失败,返回被拉黑的错误码;1 发送成功但消息被服务器直接丢弃
message.blacklist.strategy 0 message.blacklist.strategy 0
@@ -135,12 +149,6 @@ friend.reject_request_duration 2592000000
##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。 ##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。
friend.request_expiration_duration 604800000 friend.request_expiration_duration 604800000
##添加好友后会发送欢迎消息,比如"你们已经是好友了,可以聊天了"。这个开关控制是用旧的方式还是用新的方式。
##旧的方式会发送notification消息(contenttype是80),内容会根据好友接收者的语言选择对应的语言,如果双方语言不一致会导致另外一方看到的不是本地语言。
##新的方式会发送添加好友消息(contenttype是93)和欢迎消息(contenttype是92),需要客户端实现这两种消息。
##如果客户端已经实现这两种消息可以打开这个开关(客户端2020.6.30添加了此消息类型)。
friend.new_welcome_message true
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出 ## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000 chatroom.participant_idle_time 900000
@@ -161,6 +169,10 @@ group.allow_robot_custom_operation_notification true
## 如果应用需要退群或者踢人不通知普通成员,请设置此参数为3,然后修改客户端踢人或退群消息的flag为not_presist。 ## 如果应用需要退群或者踢人不通知普通成员,请设置此参数为3,然后修改客户端踢人或退群消息的flag为not_presist。
group.visible_quit_or_kickoff_notification 0 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信息。当需要隐藏多个时以英文逗号分开; ## 隐藏用户信息属性类型,2是性别,3是电话号码,4是邮箱,5是地址,6是公司信息,7是社交账号,8是Extra信息。当需要隐藏多个时以英文逗号分开;
## 本配置是为了提高安全性,避免把对应信息同步到客户端,这样就能防止程序被破解从而拿到用户关键信息。仅对客户端有效,server api不受影响。 ## 本配置是为了提高安全性,避免把对应信息同步到客户端,这样就能防止程序被破解从而拿到用户关键信息。仅对客户端有效,server api不受影响。
## 注意,修改本配置不会对已经同步的信息产生影响。 ## 注意,修改本配置不会对已经同步的信息产生影响。
@@ -184,11 +196,6 @@ http.channel.rate_limit 1000
##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。 ##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。
client.request_rate_limit 100 client.request_rate_limit 100
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#********************************************************************* #*********************************************************************
# Media server configuration # 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.url http://localhost:8888/im_event/message
##需要转发的消息类型,当有多个时以英文逗号分割。如果转发所有消息,请注释掉配置或者设置为空 ##需要转发的消息类型,当有多个时以英文逗号分割,可以用-来指定区间,注意区间不能太大,因为要把区间内的所有数字都放入到Set中。如果转发所有消息,请注释掉配置或者设置为空
#message.forward.types 1,2,3 #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.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 #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
@@ -14,11 +14,11 @@ goto end
:okHome :okHome
rem Set JavaHome if it exists rem Set JavaHome if it exists
if exist { "%JAVA_HOME%\bin\java" } ( if exist [ "%JAVA_HOME%\bin\java" ] (
set "JAVA="%JAVA_HOME%\bin\java"" set "JAVA="%JAVA_HOME%\bin\java""
) else { ) else (
set "JAVA="java"" set "JAVA="java""
} )
echo Using JAVA_HOME: "%JAVA_HOME%" echo Using JAVA_HOME: "%JAVA_HOME%"
echo Using WILDFIRECHAT_HOME: "%WILDFIRECHAT_HOME%" echo Using WILDFIRECHAT_HOME: "%WILDFIRECHAT_HOME%"
@@ -73,10 +73,12 @@ rem set JAVA_OPTS=%JAVA_OPTS% -XX:NumberOfGCLogFiles=10
rem set JAVA_OPTS=%JAVA_OPTS% -XX:GCLogFileSize=10M" rem set JAVA_OPTS=%JAVA_OPTS% -XX:GCLogFileSize=10M"
echo echo
echo 请设置JVM参数Xmx和Xms!!! echo "Please set Xmx and Xms parameters!"
echo "Please set Xmx and Xms parameters!"
echo "Please set Xmx and Xms parameters!"
echo echo
rem set JAVA_OPTS=%JAVA_OPTS% -Xmx2G rem set JAVA_OPTS=%JAVA_OPTS% -Xmx2G
rem set JAVA_OPTS=%JAVA_OPTS% -Xms2G 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 %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 done
PRGDIR=`dirname "$PRG"` PRGDIR=`dirname "$PRG"`
cd $PRGDIR
cd .. cd ..
WILDFIRECHAT_HOME=`pwd` WILDFIRECHAT_HOME=`pwd`
@@ -34,11 +33,13 @@ else
fi fi
export JAVA export JAVA
LOG_FILE=$WILDFIRECHAT_HOME/config/log4j2.xml WILDFIRECHAT_CONFIG_PATH=$WILDFIRECHAT_HOME
HZ_CONF_FILE=$WILDFIRECHAT_HOME/config/hazelcast.xml
C3P0_CONF_FILE=$WILDFIRECHAT_HOME/config/c3p0-config.xml 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_CONSOLE_LEVEL=info
#LOG_FILE_LEVEL=fine #LOG_FILE_LEVEL=fine
JAVA_OPTS_SCRIPT="-XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true" JAVA_OPTS_SCRIPT="-XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true"
@@ -91,4 +92,4 @@ echo ""
#JAVA_OPTS="$JAVA_OPTS -Xms2G" #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 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 -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 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/config
VOLUME /opt/im-server/logs 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 EXPOSE 80/tcp 1883/tcp 8083/tcp 8084/tcp 18080/tcp
+14 -3
View File
@@ -5,15 +5,26 @@
``` ```
mvn clean package mvn clean package
``` ```
生成的软件包在```distribution/target/distribution-XX-bundle-tar.tar.gz```,把此软件包拷贝到docker目录下。
然后进入到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> <artifactId>wildfirechat-parent</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>0.95</version> <version>1.1</version>
<name>Wildfire Chat</name> <name>Wildfire Chat</name>
<description>Wildfire Chat Server</description> <description>Wildfire Chat Server</description>
<inceptionYear>2019</inceptionYear> <inceptionYear>2019</inceptionYear>
@@ -7,10 +7,57 @@
6. *** 0.62/0.63 版本有严重的问题,请使用0.64及以后版本,或者0.61版。 *** 6. *** 0.62/0.63 版本有严重的问题,请使用0.64及以后版本,或者0.61版。 ***
7. *** 从0.68 版本起添加了pc在线是否默认手机接收推送的开关,默认为开,与以前版本作用相反,请注意兼容(可以关掉与之前保持一致或者升级客户端) *** 7. *** 从0.68 版本起添加了pc在线是否默认手机接收推送的开关,默认为开,与以前版本作用相反,请注意兼容(可以关掉与之前保持一致或者升级客户端) ***
8. *** 从0.78 版本起把MySQL数据库中关键字都改为大小写敏感,另外生成id的方法也做了改变,只生成小写的id,避免出现id重复的问题,建议所有客户都升级 *** 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. 文章消息添加摘要字段
2. 单聊消息支持server api只发送给其中一方
3. Server api添加会议录制/停止录制接口
-------------- --------------
Release note 0.95: Release note 0.95:
1. 添加按照用户删除会话接口 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

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