Showing preview only (405K chars total). Download the full file or copy to clipboard to get everything.
Repository: wildfirechat/im-app_server
Branch: master
Commit: f06dc8460a5f
Files: 148
Total size: 341.2 KB
Directory structure:
gitextract_dfvped34/
├── .github/
│ └── workflows/
│ └── build.yml
├── .gitignore
├── LICENSE
├── README.md
├── aliyun_sms.md
├── build_release.sh
├── config/
│ ├── aliyun_sms.properties
│ ├── application.properties
│ ├── im.properties
│ └── tencent_sms.properties
├── deb/
│ └── control/
│ ├── control
│ ├── postinst
│ └── postrm
├── docker/
│ ├── Dockerfile
│ └── README.md
├── mvnw
├── mvnw.cmd
├── nginx/
│ └── appserver.conf
├── pom.xml
├── release_note.md
├── src/
│ ├── lib/
│ │ ├── DmDialect-for-hibernate5.4.jar
│ │ ├── DmJdbcDriver8.jar
│ │ ├── common-1.4.4.jar
│ │ └── sdk-1.4.4.jar
│ ├── main/
│ │ ├── java/
│ │ │ └── cn/
│ │ │ └── wildfirechat/
│ │ │ └── app/
│ │ │ ├── AppController.java
│ │ │ ├── Application.java
│ │ │ ├── AudioController.java
│ │ │ ├── ForbiddenException.java
│ │ │ ├── IMCallbackController.java
│ │ │ ├── IMConfig.java
│ │ │ ├── IMExceptionEventController.java
│ │ │ ├── RestResult.java
│ │ │ ├── Service.java
│ │ │ ├── ServiceImpl.java
│ │ │ ├── conference/
│ │ │ │ ├── ConferenceCleanupService.java
│ │ │ │ ├── ConferenceController.java
│ │ │ │ ├── ConferenceService.java
│ │ │ │ └── ConferenceServiceImpl.java
│ │ │ ├── jpa/
│ │ │ │ ├── Announcement.java
│ │ │ │ ├── AnnouncementRepository.java
│ │ │ │ ├── ConferenceEntity.java
│ │ │ │ ├── ConferenceEntityRepository.java
│ │ │ │ ├── ConferenceRecord.java
│ │ │ │ ├── ConferenceRecordRepository.java
│ │ │ │ ├── FavoriteItem.java
│ │ │ │ ├── FavoriteRepository.java
│ │ │ │ ├── PCSession.java
│ │ │ │ ├── PCSessionRepository.java
│ │ │ │ ├── Record.java
│ │ │ │ ├── RecordRepository.java
│ │ │ │ ├── ShiroSession.java
│ │ │ │ ├── ShiroSessionRepository.java
│ │ │ │ ├── SlideVerify.java
│ │ │ │ ├── SlideVerifyRepository.java
│ │ │ │ ├── UserConference.java
│ │ │ │ ├── UserConferenceQuota.java
│ │ │ │ ├── UserConferenceQuotaRepository.java
│ │ │ │ ├── UserConferenceRepository.java
│ │ │ │ ├── UserNameEntry.java
│ │ │ │ ├── UserNameRepository.java
│ │ │ │ ├── UserPassword.java
│ │ │ │ ├── UserPasswordRepository.java
│ │ │ │ ├── UserPrivateConferenceId.java
│ │ │ │ ├── UserPrivateConferenceIdRepository.java
│ │ │ │ ├── UserQuotaUsage.java
│ │ │ │ └── UserQuotaUsageRepository.java
│ │ │ ├── model/
│ │ │ │ └── ConferenceDTO.java
│ │ │ ├── pojo/
│ │ │ │ ├── CancelSessionRequest.java
│ │ │ │ ├── ChangeNameRequest.java
│ │ │ │ ├── ChangePasswordRequest.java
│ │ │ │ ├── ComplainRequest.java
│ │ │ │ ├── ConferenceInfo.java
│ │ │ │ ├── ConferenceInfoRequest.java
│ │ │ │ ├── ConferenceQuotaResponse.java
│ │ │ │ ├── ConfirmSessionRequest.java
│ │ │ │ ├── CreateSessionRequest.java
│ │ │ │ ├── DestroyRequest.java
│ │ │ │ ├── GroupAnnouncementPojo.java
│ │ │ │ ├── GroupIdPojo.java
│ │ │ │ ├── LoadFavoriteRequest.java
│ │ │ │ ├── LoadFavoriteResponse.java
│ │ │ │ ├── LoginResponse.java
│ │ │ │ ├── PhoneCodeLoginRequest.java
│ │ │ │ ├── PhoneCodeLoginRequestWithSlideVerify.java
│ │ │ │ ├── RecordingRequest.java
│ │ │ │ ├── ResetPasswordRequest.java
│ │ │ │ ├── SendCodeRequest.java
│ │ │ │ ├── SendCodeRequestWithSlideVerify.java
│ │ │ │ ├── SendDestroyCodeRequest.java
│ │ │ │ ├── SendMessageRequest.java
│ │ │ │ ├── SessionOutput.java
│ │ │ │ ├── SlideVerifyRequest.java
│ │ │ │ ├── SlideVerifyResponse.java
│ │ │ │ ├── UploadFileResponse.java
│ │ │ │ ├── UserIdNamePortraitPojo.java
│ │ │ │ ├── UserIdPojo.java
│ │ │ │ ├── UserPasswordLoginRequest.java
│ │ │ │ └── UserPasswordLoginRequestWithSlideVerify.java
│ │ │ ├── shiro/
│ │ │ │ ├── AuthDataSource.java
│ │ │ │ ├── CorsFilter.java
│ │ │ │ ├── DBSessionDao.java
│ │ │ │ ├── JsonAuthLoginFilter.java
│ │ │ │ ├── LdapMatcher.java
│ │ │ │ ├── LdapRealm.java
│ │ │ │ ├── LdapToken.java
│ │ │ │ ├── PhoneCodeRealm.java
│ │ │ │ ├── PhoneCodeToken.java
│ │ │ │ ├── ScanCodeRealm.java
│ │ │ │ ├── ShiroConfig.java
│ │ │ │ ├── ShiroSessionManager.java
│ │ │ │ ├── TokenAuthenticationToken.java
│ │ │ │ ├── TokenMatcher.java
│ │ │ │ └── UserPasswordRealm.java
│ │ │ ├── slide/
│ │ │ │ ├── SlideVerifyCleanupService.java
│ │ │ │ └── SlideVerifyService.java
│ │ │ ├── sms/
│ │ │ │ ├── AliyunSMSConfig.java
│ │ │ │ ├── SmsService.java
│ │ │ │ ├── SmsServiceImpl.java
│ │ │ │ └── TencentSMSConfig.java
│ │ │ └── tools/
│ │ │ ├── LdapUser.java
│ │ │ ├── LdapUtil.java
│ │ │ ├── NumericIdGenerator.java
│ │ │ ├── OrderedIdUserNameGenerator.java
│ │ │ ├── PhoneNumberUserNameGenerator.java
│ │ │ ├── RateLimiter.java
│ │ │ ├── ShortUUIDGenerator.java
│ │ │ ├── SpinLock.java
│ │ │ ├── UUIDUserNameGenerator.java
│ │ │ ├── UserNameGenerator.java
│ │ │ └── Utils.java
│ │ └── resources/
│ │ └── application.properties
│ └── test/
│ └── java/
│ └── cn/
│ └── wildfirechat/
│ └── app/
│ ├── ApplicationTests.java
│ ├── jpa/
│ │ ├── AnnouncementTest.java
│ │ ├── ConferenceEntityTest.java
│ │ ├── FavoriteItemTest.java
│ │ ├── PCSessionTest.java
│ │ ├── RecordTest.java
│ │ ├── ShiroSessionTest.java
│ │ ├── SlideVerifyRepositoryTest.java
│ │ ├── SlideVerifyTest.java
│ │ ├── UserConferenceTest.java
│ │ ├── UserNameEntryTest.java
│ │ ├── UserPasswordTest.java
│ │ └── UserPrivateConferenceIdTest.java
│ └── slide/
│ ├── SlideVerifyCleanupServiceTest.java
│ └── SlideVerifyServiceTest.java
└── systemd/
├── README.md
└── app-server.service
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/build.yml
================================================
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn -B package --file pom.xml
================================================
FILE: .gitignore
================================================
target
.idea
appdata.mv.db
nohup.out
appdata.trace.db
avatar/
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 wildfirechat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
1. ikidou/TypeBuilder
Copyright 2016 ikidou
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
## 野火IM解决方案
野火IM是专业级即时通讯和实时音视频整体解决方案,由北京野火无限网络科技有限公司维护和支持。
主要特性有:私有部署安全可靠,性能强大,功能齐全,全平台支持,开源率高,部署运维简单,二次开发友好,方便与第三方系统对接或者嵌入现有系统中。详细情况请参考[在线文档](https://docs.wildfirechat.cn)。
主要包括一下项目:
| [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 Server | |
| [android-chat](https://github.com/wildfirechat/android-chat) | [android-chat](https://gitee.com/wfchat/android-chat) | 野火IM Android SDK源码和App源码 | 可以很方便地进行二次开发,或集成到现有应用当中 |
| [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS SDK源码和App源码 | 可以很方便地进行二次开发,或集成到现有应用当中 |
| [pc-chat](https://github.com/wildfirechat/vue-pc-chat) | [pc-chat](https://gitee.com/wfchat/vue-pc-chat) | 基于[Electron](https://electronjs.org/)开发的PC 端 | |
| [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](http://web.wildfirechat.cn) | |
| [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台) | |
| [app server](https://github.com/wildfirechat/app_server) | [app server](https://gitee.com/wfchat/app_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) | 推送服务器 | |
| [docs](https://github.com/wildfirechat/docs) | [docs](https://gitee.com/wfchat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明,[在线查看](https://docs.wildfirechat.cn/) | |
## 野火IM后端应用
作为野火IM的后端应用的演示,本工程具有如下功能:
1. 短信登陆和注册功能,用来演示登陆应用,获取token的场景.
2. PC端扫码登录的功能.
3. 群公告的获取和更新功能.
4. 客户端上传日志功能.
> 本工程为Demo工程,实际使用时需要把对应功能移植到您的应用服务中。如果需要直接使用,请按照后面的说明解决掉性能瓶颈问题。
#### 编译
```
mvn clean package
```
## 打包RPM格式
打包会生成Java包和deb安装包,如果需要rpm安装包,请在```pom.xml```中取消注释生成rpm包的plugin。另外还需要本地安装有rpm,在linux或者mac系统中很容易安装,在windows系统需要安装cygwin并安装rpm,具体信息请百度查询。
修改之后运行编译命令```mvn clean package```,rpm包生成在```target```目录下。
#### 短信资源
应用使用的是腾讯云短信功能,需要申请到```appid/appkey/templateId```这三个参数,并配置到```tencent_sms.properties```中去。用户也可以自行更换为自己喜欢的短信提供商。在没有短信供应商的情况下,为了测试可以使用```superCode```,设置好后,客户端可以直接使用```superCode```进行登陆。上线时一定要注意删掉```superCode```。
#### 修改配置
本演示服务有4个配置文件在工程的```config```目录下,分别是```application.properties```, ```im.properties```, ```aliyun_sms.properties```和```tencent_sms.properties```。请正确配置放到jar包所在的目录下的```config```目录下。
> ```application.properties```配置中的```sms.verdor```决定是使用那个短信服务商,1为腾讯短信,2为阿里云短信
#### 运行
在```target```目录找到```app-XXXX.jar```,把jar包和放置配置文件的```config```目录放到一起,然后执行下面命令:
```
java -jar app-XXXXX.jar
```
#### 性能瓶颈
本服务最早只提供获取token功能,后来逐渐增加了群公告/Shiro等功能,需要引入数据库。为了提高用户体验的便利性,引入了数据库[H2](http://www.h2database.com),让用户可以无需安装任何软件就可以直接运行(JRE还是需要的),另外shiro的session也存储在h2数据库中。提高了便利性的同时导致一方面性能有瓶颈,另外一方面也不能水平扩展和高可用。因此需要使用本工程上线必须修改2个地方。
1. 切换到MySQL,切换方法请参考 ```application.properties``` 文件中的描述。
2. 使用RedisSessionDao,详情请参考 https://www.baidu.com/s?wd=shiro+redis&tn=84053098_3_dg&ie=utf-8
3. 从0.53版本开始,应用服务改为无状态服务,可以集群部署。验证码和PC会话等信息都存放到数据库中,如果压力较大,可以二开引入redis缓存。
#### 版本兼容
+ 0.40版本引入了shiro功能,在升级本服务之前,需要确保客户端已经引入了本工程0.40版本发布时或之后的移动客户端。并且在升级之后,客户端需要退出重新登录一次以便保存session(退出登录时调用disconnect,需要使用false值,这样重新登录才能保留历史聊天记录,一定要在新版本中改成这样)。如果是旧版本或者没有重新登录,群公告和扫码登录功能将不可用。为了系统的安全性,建议升级。
+ 0.43版本把Web和PC登录的短轮询改为长轮询,如果应用服务升级需要对Web和PC进行对应修改。
+ 0.45.1 配置文件中添加了```wfc.all_client_support_ssl```开关,当升级到这个版本或之后时,需要配置文件中添加这个开关。
+ 0.51版本添加了token认证。可以同时支持token和cookies认证,客户端也做了对应修改,优先使用token。注意做好兼容。
+ 从0.53版本开始,所以数据都存储在数据库中,因此应用服务为无状态服务,可以部署多台应用服务做高可用和水平扩展。需要注意数据都是存储在数据库中,如果用户量较大或者业务量比较大,可以自己二开应用服务,添加redis缓存。
#### 使用LDAP统一认证
代码中添加了AD域登录的示例代码,可以在```application.properties```配置文件中打开ldap的配置并正确配置。需要ldap用户信息中,包含有电话号码。当登录时,先用电话号码查询到用户的dn,再用这个dn登录。如果遇到问题,请自己调试一下。调试时重点关注一下类```LdapMatcher```和```loginWithLdap```方法。
#### 修改其他登录方式
野火把登录功能从IM服务剥离,放到了应用服务中,目的是为了让客户更灵活的接入各种业务系统中进行登录。可以修改这个服务的任意代码,只要确保登录后从IM服务获取IM token返回给用户即可。
#### 注意事项
服务中对同一个IP的请求会有限频,默认是一个ip一小时可以请求200次,可以根据您的实际情况调整(搜索rateLimiter字符串就能找到)。如果使用了nginx做反向代理需要注意把用户真实ip传递过去(使用X-Real-IP或X-Forwarded-For),避免获取不到真实ip从而影响正常使用。
#### 使用到的开源代码
1. [TypeBuilder](https://github.com/ikidou/TypeBuilder) 一个用于生成泛型的简易Builder
#### LICENSE
UNDER MIT LICENSE. 详情见LICENSE文件
#### 使用阿里云短信
请参考说明[使用阿里云短信](./aliyun_sms.md)
================================================
FILE: aliyun_sms.md
================================================
# 阿里云短信功能说明
## 短信对接
1. 在[这里](https://usercenter.console.aliyun.com/#/manage/ak)申请阿里云***accessKeyId***和***accessSecret***
2. 开通短信服务,并申请短信签名和短信模版。注意申请短信签名和模版都是需要审核的,可以同时申请,以便节省您的时间
3. 修改```config```目录下的```aliyun_sms.properities```,填入上述四个参数。比如
```$xslt
alisms.accessKeyId=LTXXXXXXXXXXXXtW
alisms.accessSecret=4pXXXXXXXXXXXXXXXXXXXXXXXXXXXXyU
alisms.signName=野火IM
alisms.templateCode=SMS_170000000
```
4. 修改默认使用阿里云短信,在```application.properites```文件中修改```sms.vendor```为***2***
5. 运行测试。
> 上述几个参数如果不明白,可以参考[阿里云文档](https://help.aliyun.com/document_detail/55284.html?spm=a2c4e.11153987.0.0.5861aeecePRLPH)
## 迁移阿里云短信功能
指导如何把阿里云短信功能迁移到客户应用服务中
1. 引入jar包
```$xslt
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.1.0</version>
</dependency>
```
2. 拷贝除了```Application.java```以外的所有源码到客户应用服务器.
3. 拷贝配置文件到客户应用服务,需要注意配置文件会依赖特定的路径,请放置正确的路径
================================================
FILE: build_release.sh
================================================
if [ $# -eq 0 ]; then
echo "Usage: sh build_release.sh version"
exit -1
fi
echo "build release $1"
mvn clean package
cd target
cp -af ../config ./
cp -af ../systemd ./
cp -af ../nginx ./
cp -af ../release_note.md ./
tar -czvf app-server-release-$1.tar.gz app-$1.jar config systemd release_note.md
cp app-server-release-$1.tar.gz app-server-release-latest.tar.gz
cp app-server-$1.rpm app-server-latest.rpm
cp app-server-$1.deb app-server-latest.deb
================================================
FILE: config/aliyun_sms.properties
================================================
alisms.accessKeyId=MTAI82gOTQQTuKtW
alisms.accessSecret=4p7HlgMTOQWHsX82IICabcea556677
alisms.signName=\u91CE\u706BIM
alisms.templateCode=SMS_170843232
================================================
FILE: config/application.properties
================================================
spring.message.encoding=UTF-8
server.port=8888
## 给服务添加统一的路径前缀,方便代理统一转换。
## 注意,如果这里改了,在客户端配置文件中修改APP_SERVER_ADDRESS,加上这个地址
#server.servlet.context-path=/wfapp_api
# 短信服务提供商,1是腾讯,2是阿里云
sms.verdor=1
# 在没有短信服务器时可以使用super code进行登录,上线时需要置为空(禁止超级验证码登录),或者改为较为严格的密码
# 但是不能直接把这一行直接删除,或者注释了
sms.super_code=66666
# json序列化时去掉为null的属性,避免iOS出现NSNull的问题
spring.jackson.default-property-inclusion=NON_NULL
# h2适合开发使用,上线时请切换到mysql。切换时把下面h2部分配置注释掉,打开mysql部署配置。
##*********************** h2 DB begin ***************************
spring.datasource.url=jdbc:h2:file:./appdata
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=update
##*********************** h2 DB end ******************************
# mysql默认配置
# mysql需要手动创建数据库,mysql命令行下执行 create database appdata; appdata可以换为别的库名,但注意不能使用IM服务器使用的数据库"wfchat",否则会引起冲突。
##*********************** mysql DB begin *************************
#spring.datasource.url=jdbc:mysql://localhost:3306/appdata?serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false
#spring.datasource.username=root
#spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.jpa.database=mysql
#spring.jpa.hibernate.ddl-auto=update
## 遇到后面的报错时,请打开下面的注释:Storage engine MyISAM is disabled (Table creation is disallowed).
##spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
##*********************** mysql DB end ***************************
# 达梦数据库配置
# 达梦数据库需要手动创建数据库,使用达梦客户端工具创建数据库
##*********************** 达梦 DB begin *************************
#spring.datasource.url=jdbc:dm://localhost:5236?useUnicode=true&characterEncoding=utf-8
#spring.datasource.username=SYSDBA
#spring.datasource.password=SYSDBA001
#spring.datasource.driver-class-name=dm.jdbc.driver.DmDriver
#spring.jpa.hibernate.ddl-auto=update
#spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.format_sql=true
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.DmDialect
##*********************** 达梦 DB end ***************************
# ldap登录的配置
## 是否开启ldap登录。开启后普通密码登录就失效了,只能用ldap登录。另外客户端代码也需要修改一下,去掉短信登录和修改密码等相关代码。
ldap.enable=false
ldap.admin_dn=cn=admin,dc=wildfirechat,dc=net
ldap.admin_password=123456@abc
ldap.ldap_url=ldap://192.168.1.48:389
ldap.search_base=dc=wildfirechat,dc=net
# PC快速登录兼容旧的版本。仅当已经有未支持PC快速登录的移动端上线了,需要兼容时打开此开关。
wfc.compat_pc_quick_login=false
# 用户上传协议日志存放目录,上线时请修改可用路径
logs.user_logs_path=/Users/imhao/wildfire_user_logs/
# *************************** 上线必看 *********************************
# demo工程为了方便大家运行测试,使用了数据库作为SessionDao的缓存,上线后,当用户较多时会是一个瓶颈,请在上线前切换成redis的缓存。
# 细节请参考 https://www.baidu.com/s?wd=shiro+redis&tn=84053098_3_dg&ie=utf-8
# 小程序不能播放amr格式的音频,需要将amr转化成mp3格式
# amr转mp3缓存目录,本目录会存储转换后的mp3文件,可以定时清理
wfc.audio.cache.dir=/data/wfc/audio/cache
# 是否支持SSL,如果所有客户端调用appserver都支持https,请把下面开关设置为true,否则为false。
# 如果为false,在Web端和wx端的appserve的群公告等功能将不可用。
# 详情请参考 https://www.baidu.com/s?wd=cookie+SameSite&ie=utf-8
wfc.all_client_support_ssl=false
## 是否添加用户默认密码。可以开启此配置,使用手机号码的后六位作为初始密码。首次登录之后必须修改密码。其他情况不用打开此开关。
## 用户设置密码时,不能设置为手机号码的后6位
wfc.default_user_password=false
## 是否禁止用户注册。默认为false(允许注册)。
## 当设置为true时,如果登录时用户不存在,不会自动创建用户,而是返回用户不存在的错误
wfc.user_register_forbidden=true
## iOS系统使用share extension来处理分享,客户端无法调用SDK发送消息和文件,只能通过应用服务来进行。
## 这里配置为了满足iOS设备在share extension中进行上传文件的需求。
## 存储使用类型,0使用内置文件服务器(这里无法使用),1使用七牛云存储,2使用阿里云对象存储,3野火私有对象存储,
## 4野火对象存储网关(当使用4时,需要处理 uploadMedia和putFavoriteItem方法),5腾讯云存储。
## 默认的七牛/阿里OSS/野火私有存储账户信息不可用,请按照下面说明配置
## https://docs.wildfirechat.cn/server/oss.html
media.server.media_type=1
## 滑动验证配置
## 是否强制开启滑动验证(true=强制要求滑动验证,false=可选滑动验证)
## true: 发送验证码和登录都强制要求滑动验证
## false: 发送验证码和登录时,如果客户端传了slideVerifyToken就验证,没传就不验证
slide.verify.force=false
# 使用这个目录作为临时目录,必须配置有效目录。
local.media.temp_storage=/Users/imhao/wildfire_upload_tmp/
## OSS配置,可以是七牛/阿里云OSS/野火私有OSS。
## 注意与IM服务的配置格式不太一样,这里是用"Key=Vaue"的格式,IM服务配置里是"Key Value",拷贝粘贴时要注意修改。
## 配置请参考IM服务
## 下面是七牛云的示例,如果是腾讯云或者阿里云,server_url应该是 cos.ap-nanjing.myqcloud.com 或 oss-cn-beijing.aliyuncs.com 这样。
media.server_url=http://up.qbox.me
media.access_key=tU3vdBK5BL5j4N7jI5N5uZgq_HQDo170w5C9Amnn
media.secret_key=YfQIJdgp5YGhwEw14vGpaD2HJZsuJldWtqens7i5
## bucket名字及Domain
media.bucket_general_name=media
media.bucket_general_domain=http://cdn.wildfirechat.cn
media.bucket_image_name=media
media.bucket_image_domain=http://cdn.wildfirechat.cn
media.bucket_voice_name=media
media.bucket_voice_domain=http://cdn.wildfirechat.cn
media.bucket_video_name=media
media.bucket_video_domain=http://cdn.wildfirechat.cn
media.bucket_file_name=media
media.bucket_file_domain=http://cdn.wildfirechat.cn
media.bucket_sticker_name=media
media.bucket_sticker_domain=http://cdn.wildfirechat.cn
media.bucket_moments_name=media
media.bucket_moments_domain=http://cdn.wildfirechat.cn
media.bucket_portrait_name=storage
media.bucket_portrait_domain=http://cdn2.wildfirechat.cn
media.bucket_favorite_name=storage
media.bucket_favorite_domain=http://cdn2.wildfirechat.cn
# 报警发送邮件配置
# 当IM服务异常时,会把异常信息推送到应用服务,由应用服务来给运维人员发送邮件,建议上线时调通次功能
spring.mail.host=smtp.wildfirechat.com
spring.mail.username=admin@wildfirechat.cn
# 注意有些邮件服务商会提供客户端授权码,不能用邮箱账户密码。
spring.mail.password=xxxxxxxx
spring.mail.port=465
spring.mail.protocol=smtp
spring.mail.default-encoding=UTF-8
spring.mail.test-connection=false
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.imap.ssl.socketFactory.fallback=false
# 邮箱必须是有效邮箱,如果是无效邮箱可能会发送失败
spring.mail.to_lists=admin1@wildfirechat.cn,admin2@wildfirechat.cn,admin3@wildfirechat.cn
# 头像背景颜色可选列表,逗号分隔,中间不能有空格
avatar.bg.corlors=#D32F2F,#D81B60,#880E4F,#9C27B0,#6A1B9A,#4A148C,#AA00FF,#C51162,#673AB7,#311B92,#651FFF,#5C6BC0,#283593,#1A237E,#304FFE,#1976D2,#0D47A1,#2962FF,#0D47A1,#0277BD,#01579B
# 会议配额配置:默认会议额度(分钟),当用户没有单独配置配额时使用。0表示不限制
conference.default_quota_minutes=0
================================================
FILE: config/im.properties
================================================
im.admin_url=http://localhost:18080
#需要和im server里面配置的http.admin.secret_key一致
im.admin_secret=123456
#发送通知消息的管理员用户ID
im.admin_user_id=admin
#如果发送消息为乱码,请检查服务器是否支持中文
#如果您不需要登录欢迎消息,请删掉下面两行
im.welcome_for_new_user=欢迎您的加入!(如果您自己部署的这个服务,可以在应用服务的配置文件im.properties文件中修改这条欢迎语)
im.welcome_for_back_user=欢迎您的归来!。(如果您自己部署的这个服务,可以在应用服务的配置文件im.properties文件中修改这条欢迎语)
#是否使用随机用户名
im.use_random_name=true
# 新用户注册时,自动添加机器人为好友。这里可以修改为添加销售人员id,或者客服人员id,用户跟客户进行沟通。
# 如果不需要要设置机器人为好友和发送欢迎信息,请删除下面三行
im.new_user_robot_friend=true
im.robot_friend_id=FireRobot
im.robot_welcome=您好,我是人见人爱、花见花开、天下第一帅的机器人小火!可以跟我聊天哦!\n\n也可以给我打音视频电话,我现在可以接听电话了,我会延迟三秒播放您的声音。\n\n给我发送 流式文本 四个字,我会像 ChatGPT 那样采用流式输出的方式回复你。
# 用户登录后发送广告语,一条文本消息,再加上一条图片消息。如果为空就不发送。
im.prompt_text=
im.image_msg_url=
im.image_msg_base64_thumbnail=
# 新用户注册时,自动关注频道,频道ID不能加双引号。如果不需要关注,下面配置内容设置为空就OK了。
# im.new_user_subscribe_channel_id=vwzqmws2k
im.new_user_subscribe_channel_id=
# 用户再次登陆时,自动关注频道,频道ID不能加双引号。如果不需要关注,下面配置内容设置为空就OK了。
im.back_user_subscribe_channel_id=
================================================
FILE: config/tencent_sms.properties
================================================
sms.secretId=AKIsaepMSEL91dsMESAUMO2smphIdgSxB8oD
sms.secretKey=91dADocdksuw23AEFCD78lsdudf35ta0
sms.appId=1432000001
sms.templateId=592276
sms.sign=北京野火无限网络科技
================================================
FILE: deb/control/control
================================================
Package: app-server
Version: [[version]]
Section: misc
Priority: optional
Architecture: all
Maintainer: Wildfirechat <support@wildfirechat.cn>
Description: App Server
Distribution: development
Depends: openjdk-8-jre-headless
Homepage: https://wildfirechat.cn
================================================
FILE: deb/control/postinst
================================================
mv -f /opt/app-server/app-*.jar /opt/app-server/app-server.jar
systemctl daemon-reload
================================================
FILE: deb/control/postrm
================================================
rm -rf /opt/app-server
rm -rf /usr/lib/systemd/system/app-server.service
systemctl daemon-reload
================================================
FILE: docker/Dockerfile
================================================
FROM openjdk:8-jre-alpine
COPY ../target/app-*.jar /opt/app-server/app.jar
COPY ../config /opt/app-server/config
WORKDIR /opt/app-server
VOLUME /opt/app-server/config
VOLUME /opt/app-server/h2db
EXPOSE 8888/tcp
ENV JVM_XMX 256M
ENV JVM_XMS 256M
CMD java -server -Xmx$JVM_XMX -Xms$JVM_XMS -jar app.jar
================================================
FILE: docker/README.md
================================================
# 野火应用服务docker使用说明
## 编译镜像
首先需要先编译应用服务,使用下面命令编译
```
mvn clean package
```
然后进入到docker目录编译镜像
```
sudo docker build -t app-server -f Dockerfile ..
```
## 运行
直接运行:
```
sudo docker run -it -p 8888:8888 -e JVM_XMX=256M -e JVM_XMS=256M app-server
```
配置:
如果配置需要修改,可以修改config目录下的配置,然后重新打包镜像,也可以手动指定配置目录,这样不用重新打包镜像。手动指定配置目录的方法如下,注意路径需要绝对路径
```
sudo docker run -it -v $PATH_TO_CONFIG:/opt/app-server/config -v $PATH_TO_H2DB:/opt/app-server/h2db -e JVM_XMX=256M -e JVM_XMS=256M -p 8888:8888 app-server
```
================================================
FILE: mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Migwn, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
echo $MAVEN_PROJECTBASEDIR
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
================================================
FILE: mvnw.cmd
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%
================================================
FILE: nginx/appserver.conf
================================================
server {
listen 80;
server_name apptest.wildfirechat.cn;
rewrite ^(.*)$ https://apptest.wildfirechat.cn permanent;
location ~ / {
index index.html index.php index.htm;
}
}
server {
listen 443 ssl;
server_name apptest.wildfirechat.cn;
root html;
index index.html index.htm;
client_max_body_size 30m; #文件最大大小
ssl_certificate cert/app.pem;
ssl_certificate_key cert/app.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
## 不需要添加 add_header Access-Control-Allow-Origin $http_origin; 等添加跨域相关 header 的配置,app-server 已经处理了跨域了,所有请求透传过去即可
##
## send request back to app server ##
location / {
# 扫码超时时间是 1 分钟,配置了大于一分钟
proxy_read_timeout 100s;
proxy_pass http://127.0.0.1:8888;
}
## 如果需要通过 path 来分流的话,请参考下的配置,path后面的/和 8888 后面的/ 都不能省略,否则会提示 没有登录
# 可参考这儿:https://www.jb51.net/article/244331.htm
#location /app/ {
# proxy_pass http://127.0.0.1:8888/;
#}
}
================================================
FILE: pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.wildfirechat</groupId>
<artifactId>app</artifactId>
<version>0.72</version>
<packaging>jar</packaging>
<name>app</name>
<description>Demo project for Wildfire chat app server</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<log4j2.version>2.17.2</log4j2.version>
<wfc.sdk.version>1.4.4</wfc.sdk.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- 达梦数据库 JDBC 驱动 -->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver8</artifactId>
<version>8.1.3.162</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/lib/DmJdbcDriver8.jar</systemPath>
</dependency>
<!-- Hibernate 达梦方言 -->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmDialect-for-hibernate5.3</artifactId>
<version>8.1.3.162</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/lib/DmDialect-for-hibernate5.4.jar</systemPath>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>uk.org.lidalia</groupId>
<artifactId>slf4j-test</artifactId>
<version>1.0.0-jdk6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<type>jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-sms</artifactId>
<version>3.1.410</version>
</dependency>
<dependency>
<groupId>cn.wildfirechat</groupId>
<artifactId>sdk</artifactId>
<version>${wfc.sdk.version}</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/lib/sdk-${wfc.sdk.version}.jar</systemPath>
</dependency>
<dependency>
<groupId>cn.wildfirechat</groupId>
<artifactId>common</artifactId>
<version>${wfc.sdk.version}</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/lib/common-${wfc.sdk.version}.jar</systemPath>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.3.0</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.0.2</version>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.28</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-nativebin-linux64</artifactId>
<version>2.7.3</version>
</dependency>
<!-- 为了减少包的大小,ws.schild只打包目标架构的库。如果您使用非Linux,请把上面注释掉,然后下面打开自己平台-->
<!-- <dependency>-->
<!-- <groupId>ws.schild</groupId>-->
<!-- <artifactId>jave-nativebin-win64</artifactId>-->
<!-- <version>2.7.3</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>ws.schild</groupId>-->
<!-- <artifactId>jave-nativebin-osx64</artifactId>-->
<!-- <version>2.7.3</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>ws.schild</groupId>-->
<!-- <artifactId>jave-all-deps</artifactId>-->
<!-- <version>2.7.3</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</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}/deb/control</controlDir>
<skipPOMs>false</skipPOMs>
<deb>${project.build.directory}/app-server-${project.version}.deb</deb>
<dataSet>
<data>
<src>${project.build.directory}/${project.name}-${project.version}.jar</src>
<type>file</type>
<mapper>
<type>perm</type>
<prefix>/opt/app-server</prefix>
</mapper>
</data>
<data>
<src>${project.basedir}/config</src>
<type>directory</type>
<mapper>
<type>perm</type>
<prefix>/opt/app-server/config</prefix>
</mapper>
</data>
<data>
<src>${project.basedir}/systemd/app-server.service</src>
<type>file</type>
<mapper>
<type>perm</type>
<prefix>/usr/lib/systemd/system</prefix>
</mapper>
</data>
</dataSet>
</configuration>
</execution>
</executions>
</plugin>
<!-- RPM Plugin 开始-->
<!-- 打包RPM 包需要本地有rpm命令才可以,linux和mac都可以安装rpm。如果是windows需要cygwin安装rpm才可以,如果需要rpm包,可以把这个plugin取消注释-->
<!-- <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>app-server</name>-->
<!-- <needarch>noarch</needarch>-->
<!-- <targetOS>linux</targetOS>-->
<!-- <prefix>/opt/app-server</prefix>-->
<!-- <defineStatements>-->
<!-- <defineStatement>_unpackaged_files_terminate_build 0</defineStatement>-->
<!-- </defineStatements>-->
<!-- <copyTo>-->
<!-- target/app-server-${project.version}.rpm-->
<!-- </copyTo>-->
<!-- <requires>-->
<!-- <require>java-1.8.0-openjdk-headless</require>-->
<!-- </requires>-->
<!-- <mappings>-->
<!-- <mapping>-->
<!-- <directory>/opt/app-server</directory>-->
<!-- <filemode>755</filemode>-->
<!-- <sources>-->
<!-- <source>-->
<!-- <location>${project.build.directory}/${project.name}-${project.version}.jar</location>-->
<!-- </source>-->
<!-- </sources>-->
<!-- </mapping>-->
<!-- <mapping>-->
<!-- <directory>/opt/app-server/config</directory>-->
<!-- <filemode>644</filemode>-->
<!-- <sources>-->
<!-- <source>-->
<!-- <location>${project.basedir}/config</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/app-server.service</location>-->
<!-- </source>-->
<!-- </sources>-->
<!-- </mapping>-->
<!-- </mappings>-->
<!-- <postinstallScriptlet>-->
<!-- <script>-->
<!-- mv -f /opt/app-server/app-*.jar /opt/app-server/app-server.jar ; echo "Im server installed in /opt/app-server" ; systemctl daemon-reload-->
<!-- </script>-->
<!-- </postinstallScriptlet>-->
<!-- <postremoveScriptlet>-->
<!-- <script>-->
<!-- echo "Remove files..." ; cd /opt/app-server ; rm -rf * ; rm -rf /usr/lib/systemd/system/app-server.service ; systemctl daemon-reload-->
<!-- </script>-->
<!-- </postremoveScriptlet>-->
<!-- </configuration>-->
<!-- </plugin>-->
<!-- RPM Plugin 结束-->
</plugins>
</build>
</project>
================================================
FILE: release_note.md
================================================
# 当前版本更新记录
0.70 Release note:
1. 解决上传文件可能存在的漏洞
# 升级注意事项
1. 如果从0.40以前版本升级上来,需要注意升级兼容有问题 请参考Readme中的兼容问题说明
2. 如果从0.42以前版本升级上来,需要注意升级兼容有问题 请参考Readme中的兼容问题说明
3. 如果从0.45以前版本升级上来,需要注意升级PC和Web代码到0.45版本发布日志之后的版本
4. 如果从0.45.1以前版本升级上来,需要注意添加配置文件中的wfc.all_client_support_ssl开关
5. 0.51版本添加了token认证。可以同时支持token和cookies认证,客户端也做了对应修改,优先使用token。注意做好兼容。
6. 从0.53版本开始,所以数据都存储在数据库中,因此应用服务为无状态服务,可以部署多台应用服务做高可用和水平扩展。需要注意数据都是存储在数据库中,如果用户量较大或者业务量比较大,可以自己二开应用服务,添加redis缓存。
7. 从0.54版本开始,替换了腾讯云SDK,使用腾讯云短信的客户需要注意修改配置文件调试短信功能
# 历史更新记录
-------------
0.72 Release note:
1. 添加LDAP示例代码
2. 解决等待PC扫码长轮训超时问题
3. 添加配置应用前缀
4. 添加滑动验证功能
5. 升级IM Server SDK
-------------
0.71 Release note:
1. 升级IM SDK到1.3.8
2. 获取群组成员头像接口额外返回用户名称
3. 解决ios分享绕过限制问题
-------------
0.70 Release note:
1. 解决上传文件漏洞问题
-------------
0.69 Release note:
1. 升级IM SDK
2. 每次登录时都要发送机器人欢迎语
3. amr2mp3允许匿名访问
-------------
0.68 Release note:
1. 调整 amr转 mp3 失败时的状态码。
2. 添加默认密码功能。
3. 配置json去掉null的属性。
4. 升级IM server SDK。
-------------
0.67 Release note:
1. 修改群公告长度到2000。
2. 升级IM server SDK。
3. 生成头像的接口允许匿名访问。
-------------
0.66 Release note:
1. 添加生成用户和群组头像功能。
2. 修正使用超级验证码无法更改密码的问题。
-------------
0.65 Release note:
1. 通过应用服务发送消息时,返回消息ID和消息时间信息
-------------
0.64 Release note:
1. 当用户名登录时,当用户不存在时也返回密码错误。
2. 会议设置最大参与人数参数
-------------
0.63 Release note:
1. 删掉部分无用依赖减少程序大小
2. 会议支持设置焦点用户功能
3. 添加nginx示例配置
4. 升级IM SDK
5. 定期清理无效的pcsession
-------------
0.62 Release note:
1. 回归用户自动关注官方频道
2. 优化报警通知
3. 发送登录验证短信之前先检查是否被封禁。
4. 升级IM SDK
5. 添加会议录制接口
6. 添加rpm和deb格式
-------------
0.61 Release note:
1. 销毁用户时删掉密码
-------------
0.60 Release note:
1. 添加当新用户注册时关注频道功能
-------------
0.59 Release note:
1. mysql connector升级到8.0.28
2. commons-io升级到2.7
3. 添加密码登录方式
4. 升级im server sdk到0.92
-------------
0.58 Release note:
1. 升级log4j2版本到2.17.2
2. 升级server sdk到0.89
3. 添加删除账户功能
-------------
0.57 Release note:
1. 升级log4j2版本到2.17.1
2. 升级server sdk到0.86
3. 添加获取群组成员接口用来拼接头像
4. 添加对腾讯云对象存储的支持
-------------
0.56 Release note:
1. 升级log4j2版本到2.17.0
-------------
0.55 Release note:
1. 升级log4j2版本
-------------
0.54 Release note:
1. 更新腾讯云短信SDK,使用新的SDK进行接入短信,使用腾讯云短信的客户需要重新配置和调试短信功能。
6. 升级野火Server SDK。
--------------
0.53 Release note:
1. 短信验证码存储到数据库中
3. PC和Web端登录的session存储到数据库中
7. 支持会议相关接口
--------------
0.52 Release note:
1. 升级IM SDK
2. 修正token鉴权方式错误
--------------
0.51 Release note:
1. 优化收藏功能
2. 添加token认证方式
--------------
0.50 Release note:
1. 升级IM Server SDK
--------------
0.49 Release note:
1. 解决某些情况下mysql连不上的问题
2. 解决收藏错误问题
4. 添加第三方敏感词接口示例
8. 更新腾讯短信接口
--------------
0.48 Release note:
1. 添加IM服务异常报警功能
2. 修改PC快速登录消息有效时间为1分钟
--------------
0.47.1 Release note:
1. 修正收藏内容没有区分用户的问题,0.47版本都需要升级。
--------------
0.47 Release note:
1. 添加收藏功能
--------------
0.46.1 Release note:
1. 紧急修复PC端同时显示二维码太多无法登录的问题
--------------
0.46 Release note:
1. 升级spring boot版本
2. 支持iOS客户端通过appserver分享
--------------
0.45.1 Release note:
1. 添加配置,客户端是否支持SSL
--------------
0.45 Release note:
1. PC和Web增加快速登录功能
2. 解决PC和Web群公告获取失败的问题
--------------
0.44 Release note:
1. 添加针对请求IP限频。
2. 升级server api版本。
--------------
0.43 Release note:
1. 添加语音转码功能,支持微信小程序语音消息。
2. 修改pc或web端登录方式,改为长轮训方式。
3. 修改用户名生成方式,默认使用短ID。
--------------
0.42 Release note:
1. 更新到IM server SDK 0.41
2. 添加对物联网设备的支持(仅支持专业版)
--------------
0.41 Release note:
1. 生成用户账户时,使用uuid作为账户名称
--------------
0.40 Release note:
1. 增加阿里云短信配置,可以选择阿里云或者腾讯云短信
2. 增加了客户端上传日志功能,移动端在设置中选择上传日志
3. 为扫码登录和群公告添加shiro控制,提供系统的安全性。
================================================
FILE: src/main/java/cn/wildfirechat/app/AppController.java
================================================
package cn.wildfirechat.app;
import cn.wildfirechat.app.jpa.FavoriteItem;
import cn.wildfirechat.app.pojo.*;
import cn.wildfirechat.pojos.InputCreateDevice;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.h2.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@RestController
public class AppController {
private static final Logger LOG = LoggerFactory.getLogger(AppController.class);
@Autowired
private Service mService;
@GetMapping()
public Object health() {
return "Ok";
}
/*
移动端登录
*/
// 生成滑动验证码
@PostMapping(value = "/slide_verify/generate", produces = "application/json;charset=UTF-8")
public Object generateSlideVerify() {
return mService.generateSlideVerify();
}
// 验证滑动验证码
@PostMapping(value = "/slide_verify/verify", produces = "application/json;charset=UTF-8")
public Object verifySlide(@RequestBody SlideVerifyRequest request) {
return mService.verifySlide(request.getToken(), request.getX());
}
@PostMapping(value = "/send_code", produces = "application/json;charset=UTF-8")
public Object sendLoginCode(@RequestBody SendCodeRequestWithSlideVerify request) {
return mService.sendLoginCode(request.getMobile(), request.getSlideVerifyToken());
}
@PostMapping(value = "/login", produces = "application/json;charset=UTF-8")
public Object loginWithMobileCode(@RequestBody PhoneCodeLoginRequestWithSlideVerify request, HttpServletResponse response) {
return mService.loginWithMobileCode(response, request.getMobile(), request.getCode(), request.getClientId(), request.getPlatform() == null ? 0 : request.getPlatform(), request.getSlideVerifyToken());
}
@PostMapping(value = "/login_pwd", produces = "application/json;charset=UTF-8")
public Object loginWithPassword(@RequestBody UserPasswordLoginRequestWithSlideVerify request, HttpServletResponse response) {
return mService.loginWithPassword(response, request.getMobile(), request.getPassword(), request.getClientId(), request.getPlatform() == null ? 0 : request.getPlatform(), request.getSlideVerifyToken());
}
@PostMapping(value = "/change_pwd", produces = "application/json;charset=UTF-8")
public Object changePassword(@RequestBody ChangePasswordRequest request) {
return mService.changePassword(request.getOldPassword(), request.getNewPassword(), request.getSlideVerifyToken());
}
@PostMapping(value = "/send_reset_code", produces = "application/json;charset=UTF-8")
public Object sendResetCode(@RequestBody SendCodeRequest request) {
return mService.sendResetCode(request.getMobile(), request.getSlideVerifyToken());
}
@PostMapping(value = "/reset_pwd", produces = "application/json;charset=UTF-8")
public Object resetPassword(@RequestBody ResetPasswordRequest request) {
return mService.resetPassword(request.getMobile(), request.getResetCode(), request.getNewPassword());
}
@PostMapping(value = "/send_destroy_code", produces = "application/json;charset=UTF-8")
public Object sendDestroyCode(@RequestBody SendDestroyCodeRequest request) {
return mService.sendDestroyCode(request.getSlideVerifyToken());
}
@PostMapping(value = "/destroy", produces = "application/json;charset=UTF-8")
public Object destroy(@RequestBody DestroyRequest code, HttpServletResponse response) {
return mService.destroy(response, code.getCode());
}
/* PC扫码操作
1, PC -> App 创建会话
2, PC -> App 轮询调用session_login进行登陆,如果已经扫码确认返回token,否则返回错误码9(已经扫码还没确认)或者10(还没有被扫码)
*/
@CrossOrigin
@PostMapping(value = "/pc_session", produces = "application/json;charset=UTF-8")
public Object createPcSession(@RequestBody CreateSessionRequest request) {
return mService.createPcSession(request);
}
@CrossOrigin
@PostMapping(value = "/session_login/{token}", produces = "application/json;charset=UTF-8")
public Object loginWithSession(@PathVariable("token") String token) {
LOG.info("receive login with session key {}", token);
RestResult timeoutResult = RestResult.error(RestResult.RestCode.ERROR_SESSION_EXPIRED);
ResponseEntity<RestResult> timeoutResponseEntity = new ResponseEntity<>(timeoutResult, HttpStatus.OK);
int timeoutSecond = 50;
DeferredResult<ResponseEntity> deferredResult = new DeferredResult<>(timeoutSecond * 1000L, timeoutResponseEntity);
CompletableFuture.runAsync(() -> {
try {
int i = 0;
while (i < timeoutSecond) {
RestResult restResult = mService.loginWithSession(token);
if (restResult.getCode() == RestResult.RestCode.ERROR_SESSION_NOT_VERIFIED.code && restResult.getResult() != null) {
deferredResult.setResult(new ResponseEntity(restResult, HttpStatus.OK));
break;
} else if (restResult.getCode() == RestResult.RestCode.SUCCESS.code
|| restResult.getCode() == RestResult.RestCode.ERROR_SESSION_EXPIRED.code
|| restResult.getCode() == RestResult.RestCode.ERROR_SERVER_ERROR.code
|| restResult.getCode() == RestResult.RestCode.ERROR_SESSION_CANCELED.code
|| restResult.getCode() == RestResult.RestCode.ERROR_CODE_INCORRECT.code) {
ResponseEntity.BodyBuilder builder =ResponseEntity.ok();
if(restResult.getCode() == RestResult.RestCode.SUCCESS.code){
Subject subject = SecurityUtils.getSubject();
Object sessionId = subject.getSession().getId();
builder.header("authToken", sessionId.toString());
}
deferredResult.setResult(builder.body(restResult));
break;
} else {
TimeUnit.SECONDS.sleep(1);
}
i ++;
}
} catch (Exception ex) {
ex.printStackTrace();
deferredResult.setResult(new ResponseEntity(RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR), HttpStatus.OK));
}
}, Executors.newCachedThreadPool());
return deferredResult;
}
/* 手机扫码操作
1,扫码,调用/scan_pc接口。
2,调用/confirm_pc 接口进行确认
*/
@PostMapping(value = "/scan_pc/{token}", produces = "application/json;charset=UTF-8")
public Object scanPc(@PathVariable("token") String token) {
return mService.scanPc(token);
}
@PostMapping(value = "/confirm_pc", produces = "application/json;charset=UTF-8")
public Object confirmPc(@RequestBody ConfirmSessionRequest request) {
return mService.confirmPc(request);
}
@PostMapping(value = "/cancel_pc", produces = "application/json;charset=UTF-8")
public Object cancelPc(@RequestBody CancelSessionRequest request) {
return mService.cancelPc(request);
}
/*
修改野火账户
*/
@CrossOrigin
@PostMapping(value = "/change_name", produces = "application/json;charset=UTF-8")
public Object changeName(@RequestBody ChangeNameRequest request) {
if (StringUtils.isNullOrEmpty(request.getNewName())) {
return RestResult.error(RestResult.RestCode.ERROR_INVALID_PARAMETER);
}
return mService.changeName(request.getNewName());
}
/*
群公告相关接口
*/
@CrossOrigin
@PostMapping(value = "/put_group_announcement", produces = "application/json;charset=UTF-8")
public Object putGroupAnnouncement(@RequestBody GroupAnnouncementPojo request) {
return mService.putGroupAnnouncement(request);
}
@CrossOrigin
@PostMapping(value = "/get_group_announcement", produces = "application/json;charset=UTF-8")
public Object getGroupAnnouncement(@RequestBody GroupIdPojo request) {
return mService.getGroupAnnouncement(request.groupId);
}
/*
客户端上传协议栈日志
*/
@PostMapping(value = "/logs/{userId}/upload")
public Object uploadFiles(@RequestParam("file") MultipartFile file, @PathVariable("userId") String userId) throws IOException {
return mService.saveUserLogs(userId, file);
}
/*
投诉和建议
*/
@CrossOrigin
@PostMapping(value = "/complain", produces = "application/json;charset=UTF-8")
public Object complain(@RequestBody ComplainRequest request) {
return mService.complain(request.text);
}
/*
物联网相关接口
*/
@PostMapping(value = "/things/add_device")
public Object addDevice(@RequestBody InputCreateDevice createDevice) {
return mService.addDevice(createDevice);
}
@PostMapping(value = "/things/list_device")
public Object getDeviceList() {
return mService.getDeviceList();
}
@PostMapping(value = "/things/del_device")
public Object delDevice(@RequestBody InputCreateDevice createDevice) {
return mService.delDevice(createDevice);
}
/*
发送消息
*/
@PostMapping(value = "/messages/send")
public Object sendUserMessage(@RequestBody SendMessageRequest sendMessageRequest) {
return mService.sendUserMessage(sendMessageRequest);
}
/*
iOS设备Share extension分享图片文件等使用
*/
@PostMapping(value = "/media/upload/{media_type}")
public Object uploadMedia(@RequestParam("file") MultipartFile file, @PathVariable("media_type") int mediaType) throws IOException {
return mService.uploadMedia(mediaType, file);
}
@CrossOrigin
@PostMapping(value = "/fav/add", produces = "application/json;charset=UTF-8")
public Object putFavoriteItem(@RequestBody FavoriteItem request) {
return mService.putFavoriteItem(request);
}
@CrossOrigin
@PostMapping(value = "/fav/del/{fav_id}", produces = "application/json;charset=UTF-8")
public Object removeFavoriteItem(@PathVariable("fav_id") int favId) {
return mService.removeFavoriteItems(favId);
}
@CrossOrigin
@PostMapping(value = "/fav/list", produces = "application/json;charset=UTF-8")
public Object getFavoriteItems(@RequestBody LoadFavoriteRequest request) {
return mService.getFavoriteItems(request.id, request.count);
}
@CrossOrigin
@PostMapping(value = "/group/members_for_portrait", produces = "application/json;charset=UTF-8")
public Object getGroupMembersForPortrait(@RequestBody GroupIdPojo groupIdPojo) {
return mService.getGroupMembersForPortrait(groupIdPojo.groupId);
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/Application.java
================================================
package cn.wildfirechat.app;
import cn.wildfirechat.app.jpa.PCSessionRepository;
import cn.wildfirechat.app.slide.SlideVerifyCleanupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.unit.DataSize;
import javax.servlet.MultipartConfigElement;
@SpringBootApplication
@ServletComponentScan
@EnableScheduling
public class Application {
@Autowired
private PCSessionRepository pcSessionRepository;
@Autowired
private SlideVerifyCleanupService slideVerifyCleanupService;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
/**
* 文件上传配置
* @return
*/
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
//单个文件最大
factory.setMaxFileSize(DataSize.ofMegabytes(20)); //20MB
/// 设置总上传数据总大小
factory.setMaxRequestSize(DataSize.ofMegabytes(100));
return factory.createMultipartConfig();
}
@Scheduled(fixedRate = 60 * 60 * 1000)
public void clearPCSession(){
pcSessionRepository.deleteByCreateDtBefore(System.currentTimeMillis() - 60 * 60 * 1000);
}
@Scheduled(fixedRate = 60 * 60 * 1000)
public void cleanExpiredSlideVerify(){
slideVerifyCleanupService.cleanupExpired();
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/AudioController.java
================================================
package cn.wildfirechat.app;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ws.schild.jave.*;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
@RestController
public class AudioController {
@Value("${wfc.audio.cache.dir}")
String cacheDirPath;
private File cacheDir;
@PostConstruct
public void init() {
cacheDir = new File(cacheDirPath);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
}
@GetMapping("amr2mp3")
public CompletableFuture<ResponseEntity<InputStreamResource>> amr2mp3(@RequestParam("path") String amrUrl) throws FileNotFoundException {
MediaType mediaType = new MediaType("audio", "mp3");
String mp3FileName = amrUrl.substring(amrUrl.lastIndexOf('/') + 1) + ".mp3";
File mp3File = new File(cacheDir, mp3FileName);
if (mp3File.exists()) {
InputStreamResource resource = new InputStreamResource(new FileInputStream(mp3File));
return CompletableFuture.completedFuture(ResponseEntity.ok()
// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + mp3File.getName())
.contentType(mediaType)
.contentLength(mp3File.length())
.body(resource));
}
return CompletableFuture.supplyAsync(new Supplier<ResponseEntity<InputStreamResource>>() {
/**
* Gets a result.
*
* @return a result
*/
@Override
public ResponseEntity<InputStreamResource> get() {
try {
amr2mp3(amrUrl, mp3File);
InputStreamResource resource = new InputStreamResource(new FileInputStream(mp3File));
return ResponseEntity.ok()
// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + mp3File.getName())
.contentType(mediaType)
.contentLength(mp3File.length())
.body(resource);
} catch (MalformedURLException e) {
System.out.println(amrUrl);
e.printStackTrace();
} catch (EncoderException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return ResponseEntity.status(500).build();
}
});
}
private static void amr2mp3(String sourceUrl, File target) throws MalformedURLException, EncoderException {
//Audio Attributes
AudioAttributes audio = new AudioAttributes();
audio.setCodec("libmp3lame");
audio.setBitRate(128000);
audio.setChannels(2);
audio.setSamplingRate(44100);
//Encoding attributes
EncodingAttributes attrs = new EncodingAttributes();
attrs.setFormat("mp3");
attrs.setAudioAttributes(audio);
//Encode
Encoder encoder = new Encoder();
encoder.encode(new MultimediaObject(new URL(sourceUrl)), target, attrs);
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/ForbiddenException.java
================================================
package cn.wildfirechat.app;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason="Forbidden")
public class ForbiddenException extends RuntimeException {
}
================================================
FILE: src/main/java/cn/wildfirechat/app/IMCallbackController.java
================================================
package cn.wildfirechat.app;
import cn.wildfirechat.pojos.*;
import cn.wildfirechat.pojos.moments.CommentPojo;
import cn.wildfirechat.pojos.moments.FeedPojo;
import cn.wildfirechat.pojos.moments.IdPojo;
import com.google.gson.Gson;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/*
IM对应事件发生时,会回调到配置地址。需要注意IM服务单线程进行回调,如果接收方处理太慢会导致推送线程被阻塞,导致延迟发生,甚至导致IM系统异常。
建议异步处理快速返回,这里收到后转到异步线程处理,并且立即返回。另外两个服务器的ping值不能太大。
*/
@RestController()
public class IMCallbackController {
/*
用户在线状态回调
*/
@PostMapping(value = "/im_event/user/online")
public Object onUserOnlineEvent(@RequestBody UserOnlineStatus event) {
System.out.println("User:" + event.userId + " on device:" + event.clientId + " online status:" + event.status);
return "ok";
}
/*
用户关系变更回调
*/
@PostMapping(value = "/im_event/user/relation")
public Object onUserRelationUpdated(@RequestBody RelationUpdateEvent event) {
System.out.println("User relation updated:" + event.userId);
return "ok";
}
/*
用户信息更新回调
*/
@PostMapping(value = "/im_event/user/info")
public Object onUserInfoUpdated(@RequestBody InputOutputUserInfo event) {
System.out.println("User info updated:" + event.getUserId());
return "ok";
}
/*
发送消息回调
*/
@PostMapping(value = "/im_event/message")
public Object onMessage(@RequestBody OutputMessageData event) {
System.out.println("message:" +event.getMessageId());
return "ok";
}
/*
发送消息回调
*/
@PostMapping(value = "/im_event/recall_message")
public Object onRecallMessage(@RequestBody OutputRecallMessageData event) {
System.out.println("recall message:" +event.getUserId());
return "ok";
}
/*
物联网消息回调
*/
@PostMapping(value = "/im_event/things/message")
public Object onThingsMessage(@RequestBody OutputMessageData event) {
System.out.println("message:" + event.getMessageId());
return "ok";
}
/*
消息已读回调
*/
@PostMapping(value = "/im_event/message_read")
public Object onMessageRead(@RequestBody OutputReadData event) {
System.out.println("message:" +event.user);
return "ok";
}
/*
群组信息更新回调
*/
@PostMapping(value = "/im_event/group/info")
public Object onGroupInfoUpdated(@RequestBody GroupUpdateEvent event) {
System.out.println("group info updated:" + event.type);
return "ok";
}
/*
群组成员更新回调
*/
@PostMapping(value = "/im_event/group/member")
public Object onGroupMemberUpdated(@RequestBody GroupMemberUpdateEvent event) {
System.out.println("group member updated:" + event.type);
return "ok";
}
/*
频道信息更新回调
*/
@PostMapping(value = "/im_event/channel/info")
public Object onChannelInfoUpdated(@RequestBody ChannelUpdateEvent event) {
System.out.println("channel info updated:" + event.type);
return "ok";
}
/*
聊天室信息更新回调
*/
@PostMapping(value = "/im_event/chatroom/info")
public Object onChatroomInfoUpdated(@RequestBody ChatroomUpdateEvent event) {
System.out.println("chatroom info updated:" + event.type);
return "ok";
}
/*
聊天室成员更新回调
*/
@PostMapping(value = "/im_event/chatroom/member")
public Object onChatroomMemberUpdated(@RequestBody ChatroomMemberUpdateEvent event) {
System.out.println("chatroom member updated:" + event.type);
return "ok";
}
/*
消息审查示例。
如果允许发送,返回状态码为200,内容为空;如果替换内容发送,返回状态码200,内容为替换过的payload内容。如果不允许发送,返回状态码403。
注意如果没有替换内容运行原消息发送,要返回空内容,不要返回原消息!!!
*/
@PostMapping(value = "/message/censor")
public Object censorMessage(@RequestBody OutputMessageData event) {
System.out.println("message:" +event.getMessageId());
if(event.getPayload().getSearchableContent() != null && event.getPayload().getSearchableContent().contains("testkongbufenzi")) {
throw new ForbiddenException();
}
if(event.getPayload().getSearchableContent() != null && event.getPayload().getSearchableContent().contains("testzhaopian")) {
event.getPayload().setSearchableContent(event.getPayload().getSearchableContent().replace("zhaopian", "照片"));
return new Gson().toJson(event.getPayload());
}
return "";
}
@PostMapping(value = "/im_event/conference/create")
public Object onConferenceCreated(@RequestBody ConferenceCreateEvent event) {
System.out.println("conference created:" + event);
return "ok";
}
@PostMapping(value = "/im_event/conference/destroy")
public Object onConferenceDestroyed(@RequestBody ConferenceDestroyEvent event) {
System.out.println("conference destroyed:" + event);
return "ok";
}
@PostMapping(value = "/im_event/conference/member_join")
public Object onConferenceMemberJoined(@RequestBody ConferenceJoinEvent event) {
System.out.println("conference member joined:" + event);
return "ok";
}
@PostMapping(value = "/im_event/conference/member_leave")
public Object onConferenceMemberLeaved(@RequestBody ConferenceLeaveEvent event) {
System.out.println("conference member leaved:" + event);
return "ok";
}
@PostMapping(value = "/im_event/conference/member_publish")
public Object onConferenceMemberPublished(@RequestBody ConferencePublishEvent event) {
System.out.println("conference member published:" + event);
return "ok";
}
@PostMapping(value = "/im_event/conference/member_unpublish")
public Object onConferenceMemberUnpublished(@RequestBody ConferenceUnpublishEvent event) {
System.out.println("conference member unpublished:" + event);
return "ok";
}
@PostMapping(value = "/im_event/moments_feed")
public Object onMomentsFeed(@RequestBody FeedPojo event) {
System.out.println("feed posted:" + event.sender + ", " + event.feedId + ", " + event.text);
return "ok";
}
@PostMapping(value = "/im_event/moments_feed_recall")
public Object onMomentsFeedRecall(@RequestBody IdPojo event) {
System.out.println("recall feed:" + event.id);
return "ok";
}
@PostMapping(value = "/im_event/moments_comment")
public Object onMomentsComment(@RequestBody CommentPojo event) {
System.out.println("feed posted:" + event.sender + ", " + event.commentId + ", " + event.feedId + ", " + event.text);
return "ok";
}
@PostMapping(value = "/im_event/moments_comment_recall")
public Object onMomentsCommentRecall(@RequestBody IdPojo event) {
System.out.println("recall comment:" + event.id + "," + event.id2);
return "ok";
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/IMConfig.java
================================================
package cn.wildfirechat.app;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ConfigurationProperties(prefix="im")
@PropertySource(value = "file:config/im.properties", encoding = "UTF-8")
public class IMConfig {
public String admin_url;
public String admin_secret;
public String admin_user_id;
public boolean isUse_random_name() {
return use_random_name;
}
public void setUse_random_name(boolean use_random_name) {
this.use_random_name = use_random_name;
}
boolean use_random_name;
String welcome_for_new_user;
String welcome_for_back_user;
boolean new_user_robot_friend;
String robot_friend_id;
String robot_welcome;
String prompt_text;
String image_msg_url;
String image_msg_base64_thumbnail;
String new_user_subscribe_channel_id;
String back_user_subscribe_channel_id;
public String getAdmin_url() {
return admin_url;
}
public void setAdmin_url(String admin_url) {
this.admin_url = admin_url;
}
public String getAdmin_secret() {
return admin_secret;
}
public void setAdmin_secret(String admin_secret) {
this.admin_secret = admin_secret;
}
public String getWelcome_for_new_user() {
return welcome_for_new_user;
}
public void setWelcome_for_new_user(String welcome_for_new_user) {
this.welcome_for_new_user = welcome_for_new_user;
}
public String getWelcome_for_back_user() {
return welcome_for_back_user;
}
public void setWelcome_for_back_user(String welcome_for_back_user) {
this.welcome_for_back_user = welcome_for_back_user;
}
public boolean isNew_user_robot_friend() {
return new_user_robot_friend;
}
public void setNew_user_robot_friend(boolean new_user_robot_friend) {
this.new_user_robot_friend = new_user_robot_friend;
}
public String getRobot_friend_id() {
return robot_friend_id;
}
public void setRobot_friend_id(String robot_friend_id) {
this.robot_friend_id = robot_friend_id;
}
public String getRobot_welcome() {
return robot_welcome;
}
public void setRobot_welcome(String robot_welcome) {
this.robot_welcome = robot_welcome;
}
public String getNew_user_subscribe_channel_id() {
return new_user_subscribe_channel_id;
}
public void setNew_user_subscribe_channel_id(String new_user_subscribe_channel_id) {
this.new_user_subscribe_channel_id = new_user_subscribe_channel_id;
}
public String getBack_user_subscribe_channel_id() {
return back_user_subscribe_channel_id;
}
public void setBack_user_subscribe_channel_id(String back_user_subscribe_channel_id) {
this.back_user_subscribe_channel_id = back_user_subscribe_channel_id;
}
public String getAdmin_user_id() {
return admin_user_id;
}
public void setAdmin_user_id(String admin_user_id) {
this.admin_user_id = admin_user_id;
}
public String getPrompt_text() {
return prompt_text;
}
public void setPrompt_text(String prompt_text) {
this.prompt_text = prompt_text;
}
public String getImage_msg_url() {
return image_msg_url;
}
public void setImage_msg_url(String image_msg_url) {
this.image_msg_url = image_msg_url;
}
public String getImage_msg_base64_thumbnail() {
return image_msg_base64_thumbnail;
}
public void setImage_msg_base64_thumbnail(String image_msg_base64_thumbnail) {
this.image_msg_base64_thumbnail = image_msg_base64_thumbnail;
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/IMExceptionEventController.java
================================================
package cn.wildfirechat.app;
import cn.wildfirechat.common.IMExceptionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.web.bind.annotation.*;
import ws.schild.jave.*;
import javax.annotation.PostConstruct;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.function.Supplier;
@RestController
public class IMExceptionEventController {
private BlockingDeque<IMExceptionEvent> events = new LinkedBlockingDeque<>();
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${spring.mail.username}")
private String from;
@Value("${spring.mail.to_lists}")
private String toLists;
@Autowired
private JavaMailSender mailSender;
@PostConstruct
void init() {
new Thread(()->{
while (true) {
try {
IMExceptionEvent event = events.take();
if (event.event_type == IMExceptionEvent.EventType.HEART_BEAT) {
sendTextMail("恭喜您,您的服务已经连续24小时没有异常发生了", "恭喜您,您的服务已经连续24小时没有异常发生了");
} else {
sendTextMail("IM服务报警通知:节点" + event.node_id + ",发生" + event.count + "次 " + event.msg, "call stack:" + event.call_stack);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
@PostMapping("im_exception_event")
public String onIMException(@RequestBody IMExceptionEvent event) {
System.out.println(event);
events.add(event);
return "ok";
}
/**
* 文本邮件
* @param subject 邮件主题
* @param content 邮件内容
*/
public void sendTextMail(String subject, String content){
SimpleMailMessage message = new SimpleMailMessage();
String[] tos = toLists.split(",");
message.setTo(tos);
message.setSubject(subject);
message.setText(content);
message.setFrom(from);
mailSender.send(message);
}
//content HTML内容
public void sendHtmlMail(String subject, String content) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
String[] tos = toLists.split(",");
helper.setTo(tos);
helper.setSubject(subject);
helper.setText(content, true);
helper.setFrom(from);
mailSender.send(message);
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/RestResult.java
================================================
package cn.wildfirechat.app;
public class RestResult {
public enum RestCode {
SUCCESS(0, "success"),
ERROR_INVALID_MOBILE(1, "无效的电话号码"),
ERROR_SEND_SMS_OVER_FREQUENCY(3, "请求验证码太频繁"),
ERROR_SERVER_ERROR(4, "服务器异常"),
ERROR_CODE_EXPIRED(5, "验证码已过期"),
ERROR_CODE_INCORRECT(6, "验证码或密码错误"),
ERROR_SERVER_CONFIG_ERROR(7, "服务器配置错误"),
ERROR_SESSION_EXPIRED(8, "会话不存在或已过期"),
ERROR_SESSION_NOT_VERIFIED(9, "会话没有验证"),
ERROR_SESSION_NOT_SCANED(10, "会话没有被扫码"),
ERROR_SERVER_NOT_IMPLEMENT(11, "功能没有实现"),
ERROR_GROUP_ANNOUNCEMENT_NOT_EXIST(12, "群公告不存在"),
ERROR_NOT_LOGIN(13, "没有登录"),
ERROR_NO_RIGHT(14, "没有权限"),
ERROR_INVALID_PARAMETER(15, "无效参数"),
ERROR_NOT_EXIST(16, "对象不存在"),
ERROR_USER_NAME_ALREADY_EXIST(17, "用户名已经存在"),
ERROR_SESSION_CANCELED(18, "会话已经取消"),
ERROR_PASSWORD_INCORRECT(19, "密码错误"),
ERROR_FAILURE_TOO_MUCH_TIMES(20, "密码错误次数太多,请等5分钟再试试"),
ERROR_USER_FORBIDDEN(21, "用户被封禁"),
ERROR_SLIDE_VERIFY_NOT_PASS(22, "滑动验证未通过"),
ERROR_CONFERENCE_QUOTA_EXCEEDED(23, "会议额度已用完");
public int code;
public String msg;
RestCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
private int code;
private String message;
private Object result;
public static RestResult ok() {
return new RestResult(RestCode.SUCCESS, null);
}
public static RestResult ok(Object object) {
return new RestResult(RestCode.SUCCESS, object);
}
public static RestResult error(RestCode code) {
return new RestResult(code, null);
}
public static RestResult result(RestCode code, Object object){
return new RestResult(code, object);
}
public static RestResult result(int code, String message, Object object){
RestResult r = new RestResult(RestCode.SUCCESS, object);
r.code = code;
r.message = message;
return r;
}
private RestResult(RestCode code, Object result) {
this.code = code.code;
this.message = code.msg;
this.result = result;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/Service.java
================================================
package cn.wildfirechat.app;
import cn.wildfirechat.app.jpa.FavoriteItem;
import cn.wildfirechat.app.pojo.*;
import cn.wildfirechat.pojos.InputCreateDevice;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
public interface Service {
RestResult sendLoginCode(String mobile);
RestResult sendLoginCode(String mobile, String slideVerifyToken);
RestResult sendResetCode(String mobile, String slideVerifyToken);
RestResult loginWithMobileCode(HttpServletResponse response, String mobile, String code, String clientId, int platform, String slideVerifyToken);
RestResult loginWithPassword(HttpServletResponse response, String mobile, String password, String clientId, int platform, String slideVerifyToken);
RestResult changePassword(String oldPwd, String newPwd, String slideVerifyToken);
RestResult resetPassword(String mobile, String resetCode, String newPwd);
RestResult sendDestroyCode();
RestResult sendDestroyCode(String slideVerifyToken);
RestResult destroy(HttpServletResponse response, String code);
RestResult createPcSession(CreateSessionRequest request);
RestResult loginWithSession(String token);
RestResult scanPc(String token);
RestResult confirmPc(ConfirmSessionRequest request);
RestResult cancelPc(CancelSessionRequest request);
RestResult changeName(String newName);
RestResult complain(String text);
RestResult putGroupAnnouncement(GroupAnnouncementPojo request);
RestResult getGroupAnnouncement(String groupId);
RestResult saveUserLogs(String userId, MultipartFile file);
RestResult generateSlideVerify();
RestResult verifySlide(String token, int x);
RestResult addDevice(InputCreateDevice createDevice);
RestResult getDeviceList();
RestResult delDevice(InputCreateDevice createDevice);
RestResult sendUserMessage(SendMessageRequest request);
RestResult uploadMedia(int mediaType, MultipartFile file);
RestResult putFavoriteItem(FavoriteItem request);
RestResult removeFavoriteItems(long id);
RestResult getFavoriteItems(long id, int count);
RestResult getGroupMembersForPortrait(String groupId);
}
================================================
FILE: src/main/java/cn/wildfirechat/app/ServiceImpl.java
================================================
package cn.wildfirechat.app;
import cn.wildfirechat.app.jpa.*;
import cn.wildfirechat.app.pojo.*;
import cn.wildfirechat.app.shiro.AuthDataSource;
import cn.wildfirechat.app.shiro.LdapToken;
import cn.wildfirechat.app.shiro.PhoneCodeToken;
import cn.wildfirechat.app.shiro.TokenAuthenticationToken;
import cn.wildfirechat.app.sms.SmsService;
import cn.wildfirechat.app.tools.*;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.*;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.sdk.*;
import cn.wildfirechat.sdk.model.IMResult;
import com.aliyun.oss.*;
import com.aliyun.oss.model.PutObjectRequest;
import com.google.gson.Gson;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.http.HttpProtocol;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import io.minio.MinioClient;
import io.minio.PutObjectOptions;
import io.minio.errors.MinioException;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.crypto.hash.Sha1Hash;
import org.apache.shiro.subject.Subject;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static cn.wildfirechat.app.RestResult.RestCode.*;
import static cn.wildfirechat.app.jpa.PCSession.PCSessionStatus.*;
@org.springframework.stereotype.Service
public class ServiceImpl implements Service {
private static final Logger LOG = LoggerFactory.getLogger(ServiceImpl.class);
@Autowired
private SmsService smsService;
@Autowired
private IMConfig mIMConfig;
@Autowired
private AnnouncementRepository announcementRepository;
@Autowired
private FavoriteRepository favoriteRepository;
@Autowired
private UserPasswordRepository userPasswordRepository;
@Autowired
private cn.wildfirechat.app.slide.SlideVerifyService slideVerifyService;
@Value("${slide.verify.force:false}")
private boolean forceSlideVerify;
@Value("${sms.super_code}")
private String superCode;
@Value("${logs.user_logs_path}")
private String userLogPath;
@Value("${im.admin_url}")
private String adminUrl;
@Value("${wfc.default_user_password}")
private boolean defaultUserPwd;
@Value("${ldap.enable}")
private boolean enableLdap;
@Value("${ldap.admin_dn}")
private String ADMIN_DN;
@Value("${ldap.admin_password}")
private String ADMIN_PWD;
@Value("${ldap.ldap_url}")
private String LDAP_URL;
@Value("${ldap.search_base}")
private String SEARCH_BASE;
@Value("${wfc.user_register_forbidden:false}")
private boolean userRegisterForbidden;
@Autowired
private ShortUUIDGenerator userNameGenerator;
@Autowired
private AuthDataSource authDataSource;
private RateLimiter rateLimiter;
@Value("${wfc.compat_pc_quick_login}")
protected boolean compatPcQuickLogin;
@Value("${media.server.media_type}")
private int ossType;
@Value("${media.server_url}")
private String ossUrl;
@Value("${media.access_key}")
private String ossAccessKey;
@Value("${media.secret_key}")
private String ossSecretKey;
@Value("${media.bucket_general_name}")
private String ossGeneralBucket;
@Value("${media.bucket_general_domain}")
private String ossGeneralBucketDomain;
@Value("${media.bucket_image_name}")
private String ossImageBucket;
@Value("${media.bucket_image_domain}")
private String ossImageBucketDomain;
@Value("${media.bucket_voice_name}")
private String ossVoiceBucket;
@Value("${media.bucket_voice_domain}")
private String ossVoiceBucketDomain;
@Value("${media.bucket_video_name}")
private String ossVideoBucket;
@Value("${media.bucket_video_domain}")
private String ossVideoBucketDomain;
@Value("${media.bucket_file_name}")
private String ossFileBucket;
@Value("${media.bucket_file_domain}")
private String ossFileBucketDomain;
@Value("${media.bucket_sticker_name}")
private String ossStickerBucket;
@Value("${media.bucket_sticker_domain}")
private String ossStickerBucketDomain;
@Value("${media.bucket_moments_name}")
private String ossMomentsBucket;
@Value("${media.bucket_moments_domain}")
private String ossMomentsBucketDomain;
@Value("${media.bucket_favorite_name}")
private String ossFavoriteBucket;
@Value("${media.bucket_favorite_domain}")
private String ossFavoriteBucketDomain;
@Value("${local.media.temp_storage}")
private String ossTempPath;
private ConcurrentHashMap<String, Boolean> supportPCQuickLoginUsers = new ConcurrentHashMap<>();
@PostConstruct
private void init() {
AdminConfig.initAdmin(mIMConfig.admin_url, mIMConfig.admin_secret);
rateLimiter = new RateLimiter(60, 200);
if(StringUtils.isEmpty(mIMConfig.admin_user_id)) {
mIMConfig.admin_user_id = "admin";
}
}
private String getIp() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String ip = request.getHeader("X-Real-IP");
if (!StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个IP值,第一个为真实IP。
int index = ip.indexOf(',');
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
} else {
return request.getRemoteAddr();
}
}
private int getUserStatus(String mobile) {
try {
IMResult<InputOutputUserInfo> inputOutputUserInfoIMResult = UserAdmin.getUserByMobile(mobile);
if(inputOutputUserInfoIMResult != null && inputOutputUserInfoIMResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
IMResult<OutputUserStatus> outputUserStatusIMResult = UserAdmin.checkUserBlockStatus(inputOutputUserInfoIMResult.getResult().getUserId());
if(outputUserStatusIMResult != null && outputUserStatusIMResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
return outputUserStatusIMResult.getResult().getStatus();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
@Override
public RestResult sendLoginCode(String mobile) {
return sendLoginCode(mobile, null);
}
@Override
public RestResult sendLoginCode(String mobile, String slideVerifyToken) {
// 验证滑动验证码
if (forceSlideVerify) {
// 强制模式:必须提供token且验证通过
if (slideVerifyToken == null || slideVerifyToken.isEmpty()) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
} else {
// 可选模式:如果提供了token就验证
if (slideVerifyToken != null && !slideVerifyToken.isEmpty()) {
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
}
}
String remoteIp = getIp();
LOG.info("request send sms from {} {}", mobile, remoteIp);
//判断当前IP发送是否超频。
//另外 cn.wildfirechat.app.shiro.AuthDataSource.Count 会对用户发送消息限频
if (!rateLimiter.isGranted(remoteIp)) {
return RestResult.result(ERROR_SEND_SMS_OVER_FREQUENCY.code, "IP " + remoteIp + " 请求短信超频", null);
}
try {
//检查用户是否被封禁
//https://docs.wildfirechat.cn/server/admin_api/user_api.html#查询用户状态
int userStatus = getUserStatus(mobile);
if(userStatus == 2) {
return RestResult.error(ERROR_USER_FORBIDDEN);
}
// 如果禁止注册,检查用户是否存在
if (userRegisterForbidden) {
try {
IMResult<InputOutputUserInfo> userResult = UserAdmin.getUserByMobile(mobile);
if (userResult.getErrorCode() == ErrorCode.ERROR_CODE_NOT_EXIST) {
LOG.info("User not exist and register is forbidden, cannot send login code");
return RestResult.error(ERROR_NOT_EXIST);
}
} catch (Exception e) {
LOG.error("Check user exist error", e);
return RestResult.error(ERROR_SERVER_ERROR);
}
}
String code = Utils.getRandomCode(6);
RestResult.RestCode restCode = authDataSource.insertRecord(mobile, code);
if (restCode != SUCCESS) {
return RestResult.error(restCode);
}
restCode = smsService.sendCode(mobile, code);
if (restCode == RestResult.RestCode.SUCCESS) {
return RestResult.ok(restCode);
} else {
authDataSource.clearRecode(mobile);
return RestResult.error(restCode);
}
} catch (Exception e) {
// json解析错误
e.printStackTrace();
authDataSource.clearRecode(mobile);
}
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
@Override
public RestResult generateSlideVerify() {
try {
Map<String, Object> result = slideVerifyService.generateSlideVerify();
return RestResult.ok(result);
} catch (Exception e) {
LOG.error("生成滑动验证码失败", e);
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
}
@Override
public RestResult verifySlide(String token, int x) {
boolean success = slideVerifyService.verifySlide(token, x);
if (success) {
return RestResult.ok();
} else {
return RestResult.error(RestResult.RestCode.ERROR_SLIDE_VERIFY_NOT_PASS);
}
}
@Override
public RestResult sendResetCode(String mobile, String slideVerifyToken) {
// 验证滑动验证码
if (forceSlideVerify) {
// 强制模式:必须提供token且验证通过
if (slideVerifyToken == null || slideVerifyToken.isEmpty()) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
} else {
// 可选模式:如果提供了token就验证
if (slideVerifyToken != null && !slideVerifyToken.isEmpty()) {
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
}
}
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
String remoteIp = getIp();
LOG.info("request send sms from {}", remoteIp);
if (StringUtils.isEmpty(userId)) {
if (StringUtils.isEmpty(mobile)) {
return RestResult.error(ERROR_INVALID_PARAMETER);
}
} else {
try {
IMResult<InputOutputUserInfo> outputUserInfoIMResult = UserAdmin.getUserByUserId(userId);
if (outputUserInfoIMResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
mobile = outputUserInfoIMResult.getResult().getMobile();
} else {
if (StringUtils.isEmpty(mobile)) {
return RestResult.error(ERROR_NOT_EXIST);
}
}
} catch (Exception e) {
e.printStackTrace();
if (StringUtils.isEmpty(mobile)) {
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
}
}
//判断当前IP发送是否超频。
//另外 cn.wildfirechat.app.shiro.AuthDataSource.Count 会对用户发送消息限频
if (!rateLimiter.isGranted(remoteIp)) {
return RestResult.result(ERROR_SEND_SMS_OVER_FREQUENCY.code, "IP " + remoteIp + " 请求短信超频", null);
}
//检查用户是否被封禁
//https://docs.wildfirechat.cn/server/admin_api/user_api.html#查询用户状态
int userStatus = getUserStatus(mobile);
if(userStatus == 2) {
return RestResult.error(ERROR_USER_FORBIDDEN);
}
// 如果禁止注册,检查用户是否存在
if (userRegisterForbidden) {
try {
IMResult<InputOutputUserInfo> userResult = UserAdmin.getUserByMobile(mobile);
if (userResult.getErrorCode() == ErrorCode.ERROR_CODE_NOT_EXIST) {
LOG.info("User not exist and register is forbidden, cannot send reset code");
return RestResult.error(ERROR_NOT_EXIST);
}
} catch (Exception e) {
LOG.error("Check user exist error", e);
return RestResult.error(ERROR_SERVER_ERROR);
}
}
try {
String code = Utils.getRandomCode(6);
RestResult.RestCode restCode = smsService.sendCode(mobile, code);
if (restCode == RestResult.RestCode.SUCCESS) {
Optional<UserPassword> optional = userPasswordRepository.findById(userId);
UserPassword up = optional.orElseGet(() -> new UserPassword(userId));
up.setResetCode(code);
up.setResetCodeTime(System.currentTimeMillis());
userPasswordRepository.save(up);
return RestResult.ok(restCode);
} else {
return RestResult.error(restCode);
}
} catch (Exception e) {
// json解析错误
e.printStackTrace();
}
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
@Override
public RestResult loginWithMobileCode(HttpServletResponse httpResponse, String mobile, String code, String clientId, int platform, String slideVerifyToken) {
// 验证滑动验证码
if (forceSlideVerify) {
// 强制模式:必须提供token且验证通过
if (slideVerifyToken == null || slideVerifyToken.isEmpty()) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
} else {
// 可选模式:如果提供了token就验证
if (slideVerifyToken != null && !slideVerifyToken.isEmpty()) {
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
}
}
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
PhoneCodeToken token = new PhoneCodeToken(mobile, code);
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
} catch (IncorrectCredentialsException ice) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (LockedAccountException lae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (ExcessiveAttemptsException eae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (AuthenticationException ae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
}
if (subject.isAuthenticated()) {
long timeout = subject.getSession().getTimeout();
LOG.info("Login success " + timeout);
authDataSource.clearRecode(mobile);
} else {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
}
return onLoginSuccess(httpResponse, mobile, clientId, platform, true);
}
public RestResult loginWithLdap(HttpServletResponse httpResponse, String mobile, String password, String clientId, int platform) {
List<LdapUser> users;
try {
users = LdapUtil.findUserByPhone(mobile, LDAP_URL, SEARCH_BASE, ADMIN_DN, ADMIN_PWD);
} catch (NamingException e) {
return RestResult.error(ERROR_SERVER_ERROR);
}
if(users.isEmpty()) {
return RestResult.error(ERROR_NOT_EXIST);
}
LdapUser user = users.get(0);
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
LdapToken token = new LdapToken(user.dn, password, LDAP_URL);
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
} catch (IncorrectCredentialsException ice) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (LockedAccountException lae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (ExcessiveAttemptsException eae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (AuthenticationException ae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
}
if (subject.isAuthenticated()) {
long timeout = subject.getSession().getTimeout();
LOG.info("Login success " + timeout);
authDataSource.clearRecode(mobile);
} else {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
}
return onLoginSuccess(httpResponse, mobile, clientId, platform, false);
}
private String getUserDefaultPassword(String mobile) {
return mobile.length()>6?mobile.substring(mobile.length()-6):mobile;
}
@Override
public RestResult loginWithPassword(HttpServletResponse response, String mobile, String password, String clientId, int platform, String slideVerifyToken) {
// 验证滑动验证码
if (forceSlideVerify) {
// 强制模式:必须提供token且验证通过
if (slideVerifyToken == null || slideVerifyToken.isEmpty()) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
} else {
// 可选模式:如果提供了token就验证
if (slideVerifyToken != null && !slideVerifyToken.isEmpty()) {
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
}
}
if(enableLdap) {
return loginWithLdap(response, mobile, password, clientId, platform);
}
boolean isUseDefaultPwd = false;
try {
IMResult<InputOutputUserInfo> userResult = UserAdmin.getUserByMobile(mobile);
if (userResult.getErrorCode() == ErrorCode.ERROR_CODE_NOT_EXIST) {
//当用户不存在或者密码不存在时,返回密码错误。避免被攻击遍历登录获取用户名。
return RestResult.error(ERROR_CODE_INCORRECT);
}
if (userResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
Optional<UserPassword> optional = userPasswordRepository.findById(userResult.getResult().getUserId());
String defaultPwd = getUserDefaultPassword(mobile);
if (!optional.isPresent()) {
if (defaultUserPwd) {
UserPassword up = new UserPassword(userResult.getResult().getUserId());
up = changePassword(up, defaultPwd);
optional = Optional.of(up);
isUseDefaultPwd = true;
} else {
//当用户不存在或者密码不存在时,返回密码错误。避免被攻击遍历登录获取用户名。
return RestResult.error(ERROR_CODE_INCORRECT);
}
} else {
if (defaultUserPwd) {
if (defaultPwd.equals(password)) {
isUseDefaultPwd = true;
}
}
}
UserPassword up = optional.get();
if (up.getTryCount() > 5) {
if (System.currentTimeMillis() - up.getLastTryTime() < 5 * 60 * 1000) {
return RestResult.error(ERROR_FAILURE_TOO_MUCH_TIMES);
}
up.setTryCount(0);
}
up.setTryCount(up.getTryCount()+1);
up.setLastTryTime(System.currentTimeMillis());
userPasswordRepository.save(up);
//检查用户是否被封禁
int userStatus = getUserStatus(mobile);
if(userStatus == 2) {
return RestResult.error(ERROR_USER_FORBIDDEN);
}
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(userResult.getResult().getUserId(), password);
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
} catch (IncorrectCredentialsException ice) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (LockedAccountException lae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (ExcessiveAttemptsException eae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (AuthenticationException ae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
}
if (subject.isAuthenticated()) {
long timeout = subject.getSession().getTimeout();
LOG.info("Login success " + timeout);
up.setTryCount(0);
up.setLastTryTime(0);
userPasswordRepository.save(up);
} else {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
}
} catch (Exception e) {
e.printStackTrace();
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
return onLoginSuccess(response, mobile, clientId, platform, isUseDefaultPwd);
}
@Override
public RestResult changePassword(String oldPwd, String newPwd, String slideVerifyToken) {
// 验证滑动验证码
if (forceSlideVerify) {
// 强制模式:必须提供token且验证通过
if (slideVerifyToken == null || slideVerifyToken.isEmpty()) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
} else {
// 非强制模式:如果提供了token,则必须验证通过
if (slideVerifyToken != null && !slideVerifyToken.isEmpty()) {
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
}
}
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
Optional<UserPassword> optional = userPasswordRepository.findById(userId);
if (optional.isPresent()) {
try {
if(verifyPassword(optional.get(), oldPwd)) {
changePassword(optional.get(), newPwd);
return RestResult.ok(null);
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
return RestResult.error(ERROR_NOT_EXIST);
}
return RestResult.error(ERROR_SERVER_ERROR);
}
@Override
public RestResult resetPassword(String mobile, String resetCode, String newPwd) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
if (!StringUtils.isEmpty(mobile)) {
try {
IMResult<InputOutputUserInfo> userResult = UserAdmin.getUserByMobile(mobile);
if (userResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
return RestResult.error(ERROR_SERVER_ERROR);
}
if (StringUtils.isEmpty(userId)) {
userId = userResult.getResult().getUserId();
} else {
if(!userId.equals(userResult.getResult().getUserId())) {
//错误。。。。
LOG.error("reset password error, user is correct {}, {}", userId, userResult.getResult().getUserId());
return RestResult.error(ERROR_SERVER_ERROR);
}
}
} catch (Exception e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
}
}
Optional<UserPassword> optional = userPasswordRepository.findById(userId);
if (optional.isPresent()) {
UserPassword up = optional.get();
if (resetCode.equals(up.getResetCode()) && System.currentTimeMillis() - up.getResetCodeTime() > 10 * 60 * 60 * 1000){
return RestResult.error(ERROR_CODE_EXPIRED);
}
if(resetCode.equals(up.getResetCode()) || (!StringUtils.isEmpty(superCode) && resetCode.equals(superCode))) {
try {
changePassword(up, newPwd);
up.setResetCode(null);
userPasswordRepository.save(up);
return RestResult.ok(null);
} catch (Exception e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
}
} else {
return RestResult.error(ERROR_CODE_INCORRECT);
}
} else {
return RestResult.error(ERROR_NOT_EXIST);
}
}
private UserPassword changePassword(UserPassword up, String password) throws Exception {
MessageDigest digest = MessageDigest.getInstance(Sha1Hash.ALGORITHM_NAME);
digest.reset();
String salt = UUID.randomUUID().toString();
digest.update(salt.getBytes(StandardCharsets.UTF_8));
byte[] hashed = digest.digest(password.getBytes(StandardCharsets.UTF_8));
String hashedPwd = Base64.getEncoder().encodeToString(hashed);
up.setPassword(hashedPwd);
up.setSalt(salt);
userPasswordRepository.save(up);
return up;
}
private boolean verifyPassword(UserPassword up, String password) throws Exception {
String salt = up.getSalt();
MessageDigest digest = MessageDigest.getInstance(Sha1Hash.ALGORITHM_NAME);
if (salt != null) {
digest.reset();
digest.update(salt.getBytes(StandardCharsets.UTF_8));
}
byte[] hashed = digest.digest(password.getBytes(StandardCharsets.UTF_8));
String hashedPwd = Base64.getEncoder().encodeToString(hashed);
return hashedPwd.equals(up.getPassword());
}
private RestResult onLoginSuccess(HttpServletResponse httpResponse, String mobile, String clientId, int platform, boolean withResetCode) {
Subject subject = SecurityUtils.getSubject();
try {
//使用电话号码查询用户信息。
IMResult<InputOutputUserInfo> userResult = UserAdmin.getUserByMobile(mobile, true);
//如果用户信息不存在,创建用户
InputOutputUserInfo user;
boolean isNewUser = false;
if (userResult.getErrorCode() == ErrorCode.ERROR_CODE_NOT_EXIST) {
if (userRegisterForbidden) {
LOG.info("User not exist and register is forbidden");
return RestResult.error(RestResult.RestCode.ERROR_NOT_EXIST);
}
LOG.info("User not exist, try to create");
//获取用户名。如果用的是shortUUID生成器,是有极小概率会重复的,所以需要去检查是否已经存在相同的userName。
//ShortUUIDGenerator内的main函数有测试代码,可以观察一下碰撞的概率,这个重复是理论上的,作者测试了几千万次次都没有产生碰撞。
//另外由于并发的问题,也有同时生成相同的id并同时去检查的并同时通过的情况,但这种情况概率极低,可以忽略不计。
String userName;
int tryCount = 0;
do {
tryCount++;
userName = userNameGenerator.getUserName(mobile);
if (tryCount > 10) {
return RestResult.error(ERROR_SERVER_ERROR);
}
} while (!isUsernameAvailable(userName));
user = new InputOutputUserInfo();
user.setName(userName);
if (mIMConfig.use_random_name) {
String displayName = "用户" + (int) (Math.random() * 10000);
user.setDisplayName(displayName);
} else {
user.setDisplayName(mobile);
}
user.setMobile(mobile);
IMResult<OutputCreateUser> userIdResult = UserAdmin.createUser(user);
if (userIdResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
user.setUserId(userIdResult.getResult().getUserId());
isNewUser = true;
} else {
LOG.info("Create user failure {}", userIdResult.code);
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
} else if (userResult.getCode() != 0) {
LOG.error("Get user failure {}", userResult.code);
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
} else {
user = userResult.getResult();
if(user.getDeleted() > 0) {
user.setDeleted(0);
IMResult<OutputCreateUser> userIdResult = UserAdmin.createUser(user);
if (userIdResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
isNewUser = true;
} else {
LOG.info("Create user failure {}", userIdResult.code);
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
}
}
//使用用户id获取token
IMResult<OutputGetIMTokenData> tokenResult = UserAdmin.getUserToken(user.getUserId(), clientId, platform);
if (tokenResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
LOG.error("Get user token failure {}", tokenResult.code);
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
} else {
LOG.info("Get token success, userId: {}, server label: {}", user.getUserId(), tokenResult.getResult().getServerLabel());
}
subject.getSession().setAttribute("userId", user.getUserId());
//返回用户id,token和是否新建
LoginResponse response = new LoginResponse();
response.setUserId(user.getUserId());
response.setToken(tokenResult.getResult().getToken());
response.setRegister(isNewUser);
response.setPortrait(user.getPortrait());
response.setUserName(user.getName());
if (withResetCode) {
String code = Utils.getRandomCode(6);
Optional<UserPassword> optional = userPasswordRepository.findById(user.getUserId());
UserPassword up;
if (optional.isPresent()) {
up = optional.get();
} else {
up = new UserPassword(user.getUserId(), null, null);
}
up.setResetCode(code);
up.setResetCodeTime(System.currentTimeMillis());
userPasswordRepository.save(up);
response.setResetCode(code);
}
if (isNewUser) {
if (!StringUtils.isEmpty(mIMConfig.welcome_for_new_user)) {
sendTextMessage(mIMConfig.admin_user_id, user.getUserId(), mIMConfig.welcome_for_new_user);
}
if (mIMConfig.new_user_robot_friend && !StringUtils.isEmpty(mIMConfig.robot_friend_id)) {
RelationAdmin.setUserFriend(user.getUserId(), mIMConfig.robot_friend_id, true, null);
}
if (!StringUtils.isEmpty(mIMConfig.robot_welcome)) {
sendTextMessage(mIMConfig.robot_friend_id, user.getUserId(), mIMConfig.robot_welcome);
}
if (!StringUtils.isEmpty(mIMConfig.new_user_subscribe_channel_id)) {
try {
GeneralAdmin.subscribeChannel(mIMConfig.getNew_user_subscribe_channel_id(), user.getUserId());
} catch (Exception e) {
}
}
} else {
if (!StringUtils.isEmpty(mIMConfig.welcome_for_back_user)) {
sendTextMessage(mIMConfig.admin_user_id, user.getUserId(), mIMConfig.welcome_for_back_user);
}
if (!StringUtils.isEmpty(mIMConfig.robot_welcome)) {
sendTextMessage(mIMConfig.robot_friend_id, user.getUserId(), mIMConfig.robot_welcome);
}
if (!StringUtils.isEmpty(mIMConfig.back_user_subscribe_channel_id)) {
try {
IMResult<OutputBooleanValue> booleanValueIMResult = GeneralAdmin.isUserSubscribedChannel(user.getUserId(), mIMConfig.getBack_user_subscribe_channel_id());
if (booleanValueIMResult != null && booleanValueIMResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS && !booleanValueIMResult.getResult().value) {
GeneralAdmin.subscribeChannel(mIMConfig.back_user_subscribe_channel_id, user.getUserId());
}
} catch (Exception e) {
}
}
}
if(!StringUtils.isEmpty(mIMConfig.prompt_text)) {
sendTextMessage(mIMConfig.admin_user_id, user.getUserId(), mIMConfig.prompt_text);
}
if(!StringUtils.isEmpty(mIMConfig.image_msg_url) && !StringUtils.isEmpty(mIMConfig.image_msg_base64_thumbnail)) {
sendImageMessage(mIMConfig.admin_user_id, user.getUserId(), mIMConfig.image_msg_url, mIMConfig.image_msg_base64_thumbnail);
}
LOG.info("login with session success, userId {}, clientId {}, platform {}, adminUrl {}", user.getUserId(), clientId, platform, adminUrl);
Object sessionId = subject.getSession().getId();
httpResponse.setHeader("authToken", sessionId.toString());
return RestResult.ok(response);
} catch (Exception e) {
e.printStackTrace();
LOG.error("Exception happens {}", e);
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
}
@Override
public RestResult sendDestroyCode() {
return sendDestroyCode(null);
}
@Override
public RestResult sendDestroyCode(String slideVerifyToken) {
// 验证滑动验证码
if (forceSlideVerify) {
// 强制模式:必须提供token且验证通过
if (slideVerifyToken == null || slideVerifyToken.isEmpty()) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
} else {
// 可选模式:如果提供了token就验证
if (slideVerifyToken != null && !slideVerifyToken.isEmpty()) {
if (!slideVerifyService.isVerified(slideVerifyToken)) {
return RestResult.error(ERROR_SLIDE_VERIFY_NOT_PASS);
}
}
}
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
try {
IMResult<InputOutputUserInfo> getUserResult = UserAdmin.getUserByUserId(userId);
if(getUserResult != null && getUserResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
String mobile = getUserResult.getResult().getMobile();
if(!StringUtils.isEmpty(mobile)) {
return sendLoginCode(mobile);
}
}
} catch (Exception e) {
e.printStackTrace();
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
return RestResult.error(RestResult.RestCode.ERROR_NOT_EXIST);
}
@Override
public RestResult destroy(HttpServletResponse response, String code) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
try {
IMResult<InputOutputUserInfo> getUserResult = UserAdmin.getUserByUserId(userId);
if(getUserResult != null && getUserResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
String mobile = getUserResult.getResult().getMobile();
if(!StringUtils.isEmpty(mobile)) {
if(authDataSource.verifyCode(mobile, code) == SUCCESS) {
UserAdmin.destroyUser(userId);
authDataSource.clearRecode(mobile);
userPasswordRepository.deleteById(userId);
subject.logout();
return RestResult.ok(null);
}
}
}
} catch (Exception e) {
e.printStackTrace();
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
return RestResult.error(RestResult.RestCode.ERROR_NOT_EXIST);
}
private boolean isUsernameAvailable(String username) {
try {
IMResult<InputOutputUserInfo> existUser = UserAdmin.getUserByName(username);
if (existUser.code == ErrorCode.ERROR_CODE_NOT_EXIST.code) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private void sendPcLoginRequestMessage(String fromUser, String toUser, int platform, String token) {
Conversation conversation = new Conversation();
conversation.setTarget(toUser);
conversation.setType(ProtoConstants.ConversationType.ConversationType_Private);
MessagePayload payload = new MessagePayload();
payload.setType(94);
if (platform == ProtoConstants.Platform.Platform_WEB) {
payload.setPushContent("Web端登录请求");
} else if (platform == ProtoConstants.Platform.Platform_OSX) {
payload.setPushContent("Mac 端登录请求");
} else if (platform == ProtoConstants.Platform.Platform_LINUX) {
payload.setPushContent("Linux 端登录请求");
} else if (platform == ProtoConstants.Platform.Platform_Windows) {
payload.setPushContent("Windows 端登录请求");
} else {
payload.setPushContent("PC 端登录请求");
}
payload.setExpireDuration(60 * 1000);
payload.setPersistFlag(ProtoConstants.PersistFlag.Not_Persist);
JSONObject data = new JSONObject();
data.put("p", platform);
data.put("t", token);
payload.setBase64edData(Base64Utils.encodeToString(data.toString().getBytes()));
try {
IMResult<SendMessageResult> resultSendMessage = MessageAdmin.sendMessage(fromUser, conversation, payload);
if (resultSendMessage != null && resultSendMessage.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
LOG.info("send message success");
} else {
LOG.error("send message error {}", resultSendMessage != null ? resultSendMessage.getErrorCode().code : "unknown");
}
} catch (Exception e) {
e.printStackTrace();
LOG.error("send message error {}", e.getLocalizedMessage());
}
}
private void sendTextMessage(String fromUser, String toUser, String text) {
Conversation conversation = new Conversation();
conversation.setTarget(toUser);
conversation.setType(ProtoConstants.ConversationType.ConversationType_Private);
MessagePayload payload = new MessagePayload();
payload.setType(1);
payload.setSearchableContent(text);
sendMessage(fromUser, conversation, payload);
}
private void sendImageMessage(String fromUser, String toUser, String url, String base64Thumbnail) {
Conversation conversation = new Conversation();
conversation.setTarget(toUser);
conversation.setType(ProtoConstants.ConversationType.ConversationType_Private);
MessagePayload payload = new MessagePayload();
payload.setType(3);
payload.setRemoteMediaUrl(url);
payload.setBase64edData(base64Thumbnail);
payload.setMediaType(1);
payload.setSearchableContent("[图片]");
sendMessage(fromUser, conversation, payload);
}
private void sendMessage(String fromUser, Conversation conversation, MessagePayload payload) {
try {
IMResult<SendMessageResult> resultSendMessage = MessageAdmin.sendMessage(fromUser, conversation, payload);
if (resultSendMessage != null && resultSendMessage.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
LOG.info("send message success");
} else {
LOG.error("send message error {}", resultSendMessage != null ? resultSendMessage.getErrorCode().code : "unknown");
}
} catch (Exception e) {
e.printStackTrace();
LOG.error("send message error {}", e.getLocalizedMessage());
}
}
@Override
public RestResult createPcSession(CreateSessionRequest request) {
String userId = request.getUserId();
// pc端切换登录用户时,还会带上之前的cookie,通过请求里面是否带有userId来判断是否是切换到新用户
if (request.getFlag() == 1 && !StringUtils.isEmpty(userId)) {
Subject subject = SecurityUtils.getSubject();
userId = (String) subject.getSession().getAttribute("userId");
}
if (compatPcQuickLogin) {
if (userId != null && supportPCQuickLoginUsers.get(userId) == null) {
userId = null;
}
}
PCSession session = authDataSource.createSession(userId, request.getClientId(), request.getToken(), request.getPlatform());
if (userId != null) {
sendPcLoginRequestMessage(mIMConfig.admin_user_id, userId, request.getPlatform(), session.getToken());
}
SessionOutput output = session.toOutput();
LOG.info("client {} create pc session, key is {}", request.getClientId(), output.getToken());
return RestResult.ok(output);
}
@Override
public RestResult loginWithSession(String token) {
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
// comment start 如果确定登录不成功,就不通过Shiro尝试登录了
TokenAuthenticationToken tt = new TokenAuthenticationToken(token);
PCSession session = authDataSource.getSession(token, false);
if (session == null) {
return RestResult.error(ERROR_CODE_EXPIRED);
} else if (session.getStatus() == Session_Created) {
return RestResult.error(ERROR_SESSION_NOT_SCANED);
} else if (session.getStatus() == Session_Scanned) {
session.setStatus(Session_Pre_Verify);
authDataSource.saveSession(session);
LoginResponse response = new LoginResponse();
try {
IMResult<InputOutputUserInfo> result = UserAdmin.getUserByUserId(session.getConfirmedUserId());
if (result.getCode() == 0) {
response.setUserName(result.getResult().getDisplayName());
response.setPortrait(result.getResult().getPortrait());
}
} catch (Exception e) {
e.printStackTrace();
}
return RestResult.result(ERROR_SESSION_NOT_VERIFIED, response);
} else if (session.getStatus() == Session_Pre_Verify) {
return RestResult.error(ERROR_SESSION_NOT_VERIFIED);
} else if (session.getStatus() == Session_Canceled) {
return RestResult.error(ERROR_SESSION_CANCELED);
}
// comment end
// 执行认证登陆
// comment start 由于PC端登录之后,可以请求app server创建群公告等。为了保证安全, PC端登录时,也需要在app server创建session。
try {
subject.login(tt);
} catch (UnknownAccountException uae) {
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
} catch (IncorrectCredentialsException ice) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (LockedAccountException lae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (ExcessiveAttemptsException eae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
} catch (AuthenticationException ae) {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
}
if (subject.isAuthenticated()) {
LOG.info("Login success");
} else {
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
}
// comment end
session = authDataSource.getSession(token, true);
if (session == null) {
subject.logout();
return RestResult.error(RestResult.RestCode.ERROR_CODE_EXPIRED);
}
subject.getSession().setAttribute("userId", session.getConfirmedUserId());
try {
//使用用户id获取token
IMResult<OutputGetIMTokenData> tokenResult = UserAdmin.getUserToken(session.getConfirmedUserId(), session.getClientId(), session.getPlatform());
if (tokenResult.getCode() != 0) {
LOG.error("Get user token failure {}", tokenResult.code);
subject.logout();
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
//返回用户id,token和是否新建
LoginResponse response = new LoginResponse();
response.setUserId(session.getConfirmedUserId());
response.setToken(tokenResult.getResult().getToken());
LOG.info("login with session success, userId {}, clientId {}, platform {}, adminUrl {}", session.getConfirmedUserId(), session.getClientId(), session.getPlatform(), adminUrl);
return RestResult.ok(response);
} catch (Exception e) {
e.printStackTrace();
subject.logout();
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
}
@Override
public RestResult scanPc(String token) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
LOG.info("user {} scan pc, session is {}", userId, token);
return authDataSource.scanPc(userId, token);
}
@Override
public RestResult confirmPc(ConfirmSessionRequest request) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
if (compatPcQuickLogin) {
if (request.getQuick_login() > 0) {
supportPCQuickLoginUsers.put(userId, true);
} else {
supportPCQuickLoginUsers.remove(userId);
}
}
LOG.info("user {} confirm pc, session is {}", userId, request.getToken());
return authDataSource.confirmPc(userId, request.getToken());
}
@Override
public RestResult cancelPc(CancelSessionRequest request) {
return authDataSource.cancelPc(request.getToken());
}
@Override
public RestResult changeName(String newName) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
try {
IMResult<InputOutputUserInfo> existUser = UserAdmin.getUserByName(newName);
if (existUser != null) {
if (existUser.code == ErrorCode.ERROR_CODE_SUCCESS.code) {
if (userId.equals(existUser.getResult().getUserId())) {
return RestResult.ok(null);
} else {
return RestResult.error(ERROR_USER_NAME_ALREADY_EXIST);
}
} else if (existUser.code == ErrorCode.ERROR_CODE_NOT_EXIST.code) {
existUser = UserAdmin.getUserByUserId(userId);
if (existUser == null || existUser.code != ErrorCode.ERROR_CODE_SUCCESS.code || existUser.getResult() == null) {
return RestResult.error(ERROR_SERVER_ERROR);
}
existUser.getResult().setName(newName);
IMResult<OutputCreateUser> createUser = UserAdmin.createUser(existUser.getResult());
if (createUser.code == ErrorCode.ERROR_CODE_SUCCESS.code) {
return RestResult.ok(null);
} else {
return RestResult.error(ERROR_SERVER_ERROR);
}
} else {
return RestResult.error(ERROR_SERVER_ERROR);
}
} else {
return RestResult.error(ERROR_SERVER_ERROR);
}
} catch (Exception e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
}
}
@Override
public RestResult complain(String text) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
LOG.error("Complain from user {} where content {}", userId, text);
sendTextMessage(userId, "cgc8c8VV", text);
return RestResult.ok(null);
}
@Override
public RestResult getGroupAnnouncement(String groupId) {
Optional<Announcement> announcement = announcementRepository.findById(groupId);
if (announcement.isPresent()) {
GroupAnnouncementPojo pojo = new GroupAnnouncementPojo();
pojo.groupId = announcement.get().getGroupId();
pojo.author = announcement.get().getAuthor();
pojo.text = announcement.get().getAnnouncement();
pojo.timestamp = announcement.get().getTimestamp();
return RestResult.ok(pojo);
} else {
return RestResult.error(ERROR_GROUP_ANNOUNCEMENT_NOT_EXIST);
}
}
@Override
public RestResult putGroupAnnouncement(GroupAnnouncementPojo request) {
if (!StringUtils.isEmpty(request.text)) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
boolean isGroupMember = false;
try {
IMResult<OutputGroupMemberList> imResult = GroupAdmin.getGroupMembers(request.groupId);
if (imResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS && imResult.getResult() != null && imResult.getResult().getMembers() != null) {
for (PojoGroupMember member : imResult.getResult().getMembers()) {
if (member.getMember_id().equals(userId)) {
if (member.getType() != ProtoConstants.GroupMemberType.GroupMemberType_Removed
&& member.getType() != ProtoConstants.GroupMemberType.GroupMemberType_Silent) {
isGroupMember = true;
}
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (!isGroupMember) {
return RestResult.error(ERROR_NO_RIGHT);
}
Conversation conversation = new Conversation();
conversation.setTarget(request.groupId);
conversation.setType(ProtoConstants.ConversationType.ConversationType_Group);
MessagePayload payload = new MessagePayload();
payload.setType(1);
payload.setSearchableContent("@所有人 " + request.text);
payload.setMentionedType(2);
try {
IMResult<SendMessageResult> resultSendMessage = MessageAdmin.sendMessage(request.author, conversation, payload);
if (resultSendMessage != null && resultSendMessage.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
LOG.info("send message success");
} else {
LOG.error("send message error {}", resultSendMessage != null ? resultSendMessage.getErrorCode().code : "unknown");
return RestResult.error(ERROR_SERVER_ERROR);
}
} catch (Exception e) {
e.printStackTrace();
LOG.error("send message error {}", e.getLocalizedMessage());
return RestResult.error(ERROR_SERVER_ERROR);
}
}
Announcement announcement = new Announcement();
announcement.setGroupId(request.groupId);
announcement.setAuthor(request.author);
announcement.setAnnouncement(request.text);
request.timestamp = System.currentTimeMillis();
announcement.setTimestamp(request.timestamp);
announcementRepository.save(announcement);
return RestResult.ok(request);
}
@Override
public RestResult saveUserLogs(String userId, MultipartFile file) {
File localFile = new File(userLogPath, userId + "_" + Utils.getSafeFileName(file.getOriginalFilename()));
try {
file.transferTo(localFile);
} catch (IOException e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
}
return RestResult.ok(null);
}
@Override
public RestResult addDevice(InputCreateDevice createDevice) {
try {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
if (!StringUtils.isEmpty(createDevice.getDeviceId())) {
IMResult<OutputDevice> outputDeviceIMResult = UserAdmin.getDevice(createDevice.getDeviceId());
if (outputDeviceIMResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
if (!createDevice.getOwners().contains(userId)) {
return RestResult.error(ERROR_NO_RIGHT);
}
} else if (outputDeviceIMResult.getErrorCode() != ErrorCode.ERROR_CODE_NOT_EXIST) {
return RestResult.error(ERROR_SERVER_ERROR);
}
}
IMResult<OutputCreateDevice> result = UserAdmin.createOrUpdateDevice(createDevice);
if (result != null && result.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
return RestResult.ok(result.getResult());
}
} catch (Exception e) {
e.printStackTrace();
}
return RestResult.error(ERROR_SERVER_ERROR);
}
@Override
public RestResult getDeviceList() {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
try {
IMResult<OutputDeviceList> imResult = UserAdmin.getUserDevices(userId);
if (imResult != null && imResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
return RestResult.ok(imResult.getResult().getDevices());
}
} catch (Exception e) {
e.printStackTrace();
}
return RestResult.error(ERROR_SERVER_ERROR);
}
@Override
public RestResult delDevice(InputCreateDevice createDevice) {
try {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
if (!StringUtils.isEmpty(createDevice.getDeviceId())) {
IMResult<OutputDevice> outputDeviceIMResult = UserAdmin.getDevice(createDevice.getDeviceId());
if (outputDeviceIMResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
if (outputDeviceIMResult.getResult().getOwners().contains(userId)) {
createDevice.setExtra(outputDeviceIMResult.getResult().getExtra());
outputDeviceIMResult.getResult().getOwners().remove(userId);
createDevice.setOwners(outputDeviceIMResult.getResult().getOwners());
IMResult<OutputCreateDevice> result = UserAdmin.createOrUpdateDevice(createDevice);
if (result != null && result.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
return RestResult.ok(result.getResult());
} else {
return RestResult.error(ERROR_SERVER_ERROR);
}
} else {
return RestResult.error(ERROR_NO_RIGHT);
}
} else {
if (outputDeviceIMResult.getErrorCode() != ErrorCode.ERROR_CODE_NOT_EXIST) {
return RestResult.error(ERROR_SERVER_ERROR);
} else {
return RestResult.error(ERROR_NOT_EXIST);
}
}
} else {
return RestResult.error(ERROR_INVALID_PARAMETER);
}
} catch (Exception e) {
e.printStackTrace();
}
return RestResult.error(ERROR_SERVER_ERROR);
}
@Override
public RestResult sendUserMessage(SendMessageRequest request) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
Conversation conversation = new Conversation();
conversation.setType(request.type);
conversation.setTarget(request.target);
conversation.setLine(request.line);
MessagePayload payload = new MessagePayload();
payload.setType(request.content_type);
payload.setSearchableContent(request.content_searchable);
payload.setPushContent(request.content_push);
payload.setPushData(request.content_push_data);
payload.setContent(request.content);
payload.setBase64edData(request.content_binary);
payload.setMediaType(request.content_media_type);
payload.setRemoteMediaUrl(request.content_remote_url);
payload.setMentionedType(request.content_mentioned_type);
payload.setMentionedTarget(request.content_mentioned_targets);
payload.setExtra(request.content_extra);
try {
IMResult<SendMessageResult> imResult = MessageAdmin.sendMessage(userId, conversation, payload, null, true);
if (imResult != null && imResult.getCode() == ErrorCode.ERROR_CODE_SUCCESS.code) {
return RestResult.ok(imResult.getResult());
}
} catch (Exception e) {
e.printStackTrace();
}
return RestResult.error(ERROR_SERVER_ERROR);
}
@Override
public RestResult uploadMedia(int mediaType, MultipartFile file) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
String uuid = new ShortUUIDGenerator().getUserName(userId);
String fileName = userId + "-" + System.currentTimeMillis() + "-" + uuid + "-" + Utils.getSafeFileName(file.getOriginalFilename());
File localFile = new File(ossTempPath, fileName);
try {
file.transferTo(localFile);
} catch (IOException e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
}
/*
#Media_Type_GENERAL = 0,
#Media_Type_IMAGE = 1,
#Media_Type_VOICE = 2,
#Media_Type_VIDEO = 3,
#Media_Type_FILE = 4,
#Media_Type_PORTRAIT = 5,
#Media_Type_FAVORITE = 6,
#Media_Type_STICKER = 7,
#Media_Type_MOMENTS = 8
*/
String bucket;
String bucketDomain;
switch (mediaType) {
case 0:
default:
bucket = ossGeneralBucket;
bucketDomain = ossGeneralBucketDomain;
break;
case 1:
bucket = ossImageBucket;
bucketDomain = ossImageBucketDomain;
break;
case 2:
bucket = ossVoiceBucket;
bucketDomain = ossVideoBucketDomain;
break;
case 3:
bucket = ossVideoBucket;
bucketDomain = ossVideoBucketDomain;
break;
case 4:
bucket = ossFileBucket;
bucketDomain = ossFileBucketDomain;
break;
case 7:
bucket = ossMomentsBucket;
bucketDomain = ossMomentsBucketDomain;
break;
case 8:
bucket = ossStickerBucket;
bucketDomain = ossStickerBucketDomain;
break;
}
String url = bucketDomain + "/" + fileName;
if (ossType == 1) {
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.region0());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
//如果是Windows情况下,格式是 D:\\qiniu\\test.png
String localFilePath = localFile.getAbsolutePath();
//默认不指定key的情况下,以文件内容的hash值作为文件名
String key = fileName;
Auth auth = Auth.create(ossAccessKey, ossSecretKey);
String upToken = auth.uploadToken(bucket);
try {
Response response = uploadManager.put(localFilePath, key, upToken);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
System.out.println(putRet.key);
System.out.println(putRet.hash);
} catch (QiniuException ex) {
Response r = ex.response;
System.err.println(r.toString());
try {
System.err.println(r.bodyString());
} catch (QiniuException ex2) {
//ignore
}
return RestResult.error(ERROR_SERVER_ERROR);
}
} else if (ossType == 2) {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(ossUrl, ossAccessKey, ossSecretKey);
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, fileName, localFile);
// 上传文件。
try {
ossClient.putObject(putObjectRequest);
} catch (OSSException | ClientException e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
}
// 关闭OSSClient。
ossClient.shutdown();
} else if (ossType == 3) {
try {
// 使用MinIO服务的URL,端口,Access key和Secret key创建一个MinioClient对象
// MinioClient minioClient = new MinioClient("https://play.min.io", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG");
MinioClient minioClient = new MinioClient(ossUrl, ossAccessKey, ossSecretKey);
// 使用putObject上传一个文件到存储桶中。
// minioClient.putObject("asiatrip",fileName, localFile.getAbsolutePath(), new PutObjectOptions(PutObjectOptions.MAX_OBJECT_SIZE, PutObjectOptions.MIN_MULTIPART_SIZE));
minioClient.putObject(bucket, fileName, localFile.getAbsolutePath(), new PutObjectOptions(file.getSize(), 0));
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
return RestResult.error(ERROR_SERVER_ERROR);
} catch (NoSuchAlgorithmException | IOException | InvalidKeyException e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
} catch (Exception e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
}
} else if(ossType == 4) {
//Todo 需要把文件上传到文件服务器。
} else if(ossType == 5) {
COSCredentials cred = new BasicCOSCredentials(ossAccessKey, ossSecretKey);
ClientConfig clientConfig = new ClientConfig();
String [] ss = ossUrl.split("\\.");
if(ss.length > 3) {
if(!ss[1].equals("accelerate")) {
clientConfig.setRegion(new com.qcloud.cos.region.Region(ss[1]));
} else {
clientConfig.setRegion(new com.qcloud.cos.region.Region("ap-shanghai"));
try {
URL u = new URL(ossUrl);
clientConfig.setEndPointSuffix(u.getHost());
} catch (MalformedURLException e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
}
}
}
clientConfig.setHttpProtocol(HttpProtocol.https);
COSClient cosClient = new COSClient(cred, clientConfig);
try {
cosClient.putObject(bucket, fileName, localFile.getAbsoluteFile());
} catch (CosClientException e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
} finally {
cosClient.shutdown();
}
}
UploadFileResponse response = new UploadFileResponse();
response.url = url;
return RestResult.ok(response);
}
@Override
public RestResult putFavoriteItem(FavoriteItem request) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
if(!StringUtils.isEmpty(request.url)){
try {
//收藏时需要把对象拷贝到收藏bucket。
URL mediaURL = new URL(request.url);
String bucket = null;
if (mediaURL.getHost().equals(new URL(ossGeneralBucketDomain).getHost())) {
bucket = ossGeneralBucket;
} else if (mediaURL.getHost().equals(new URL(ossImageBucketDomain).getHost())) {
bucket = ossImageBucket;
} else if (mediaURL.getHost().equals(new URL(ossVoiceBucketDomain).getHost())) {
bucket = ossVoiceBucket;
} else if (mediaURL.getHost().equals(new URL(ossVideoBucketDomain).getHost())) {
bucket = ossVideoBucket;
} else if (mediaURL.getHost().equals(new URL(ossFileBucketDomain).getHost())) {
bucket = ossFileBucket;
} else if (mediaURL.getHost().equals(new URL(ossMomentsBucketDomain).getHost())) {
bucket = ossMomentsBucket;
} else if (mediaURL.getHost().equals(new URL(ossStickerBucketDomain).getHost())) {
bucket = ossStickerBucket;
} else if (mediaURL.getHost().equals(new URL(ossFavoriteBucketDomain).getHost())) {
//It's already in fav bucket, no need to copy
//bucket = ossFavoriteBucket;
}
if (bucket != null) {
String path = mediaURL.getPath();
if (ossType == 1) {
Configuration cfg = new Configuration(Region.region0());
String fromKey = path.substring(1);
Auth auth = Auth.create(ossAccessKey, ossSecretKey);
String toBucket = ossFavoriteBucket;
String toKey = fromKey;
if (!toKey.startsWith(userId)) {
toKey = userId + "-" + toKey;
}
BucketManager bucketManager = new BucketManager(auth, cfg);
bucketManager.copy(bucket, fromKey, toBucket, toKey);
request.url = ossFavoriteBucketDomain + "/" + fromKey;
} else if (ossType == 2) {
OSS ossClient = new OSSClient(ossUrl, ossAccessKey, ossSecretKey);
path = path.substring(1);
String objectName = path;
String toKey = path;
if (!toKey.startsWith(userId)) {
toKey = userId + "-" + toKey;
}
ossClient.copyObject(bucket, objectName, ossFavoriteBucket, toKey);
request.url = ossFavoriteBucketDomain + "/" + toKey;
ossClient.shutdown();
} else if (ossType == 3) {
path = path.substring(bucket.length() + 2);
String objectName = path;
String toKey = path;
if (!toKey.startsWith(userId)) {
toKey = userId + "-" + toKey;
}
MinioClient minioClient = new MinioClient(ossUrl, ossAccessKey, ossSecretKey);
minioClient.copyObject(ossFavoriteBucket, toKey, null, null, bucket, objectName, null, null);
request.url = ossFavoriteBucketDomain + "/" + toKey;
} else if(ossType == 4) {
//Todo 需要把收藏的文件保存为永久存储。
} else if(ossType == 5) {
COSCredentials cred = new BasicCOSCredentials(ossAccessKey, ossSecretKey);
ClientConfig clientConfig = new ClientConfig();
String [] ss = ossUrl.split("\\.");
if(ss.length > 3) {
if(!ss[1].equals("accelerate")) {
clientConfig.setRegion(new com.qcloud.cos.region.Region(ss[1]));
} else {
clientConfig.setRegion(new com.qcloud.cos.region.Region("ap-shanghai"));
try {
URL u = new URL(ossUrl);
clientConfig.setEndPointSuffix(u.getHost());
} catch (MalformedURLException e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
}
}
}
clientConfig.setHttpProtocol(HttpProtocol.https);
COSClient cosClient = new COSClient(cred, clientConfig);
path = path.substring(1);
String objectName = path;
String toKey = path;
if (!toKey.startsWith(userId)) {
toKey = userId + "-" + toKey;
}
try {
cosClient.copyObject(bucket, objectName, ossFavoriteBucket, toKey);
request.url = ossFavoriteBucketDomain + "/" + toKey;
} catch (CosClientException e) {
e.printStackTrace();
return RestResult.error(ERROR_SERVER_ERROR);
} finally {
cosClient.shutdown();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
request.userId = userId;
request.timestamp = System.currentTimeMillis();
favoriteRepository.save(request);
return RestResult.ok(null);
}
@Override
public RestResult removeFavoriteItems(long id) {
favoriteRepository.deleteById(id);
return RestResult.ok(null);
}
@Override
public RestResult getFavoriteItems(long id, int count) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
id = id > 0 ? id : Long.MAX_VALUE;
List<FavoriteItem> favs = favoriteRepository.loadFav(userId, id, count);
LoadFavoriteResponse response = new LoadFavoriteResponse();
response.items = favs;
response.hasMore = favs.size() == count;
return RestResult.ok(response);
}
@Override
public RestResult getGroupMembersForPortrait(String groupId) {
try {
IMResult<OutputGroupMemberList> groupMemberListIMResult = GroupAdmin.getGroupMembers(groupId);
if(groupMemberListIMResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
LOG.error("getGroupMembersForPortrait failure {},{}", groupMemberListIMResult.getErrorCode().getCode(), groupMemberListIMResult.getErrorCode().getMsg());
return RestResult.error(ERROR_SERVER_ERROR);
}
List<PojoGroupMember> groupMembers = new ArrayList<>();
for (PojoGroupMember member:groupMemberListIMResult.getResult().getMembers()) {
if(member.getType() != 4)
groupMembers.add(member);
}
if (groupMembers.size() > 9) {
groupMembers.sort((o1, o2) -> {
if(o1.getType() == 2)
return -1;
if(o2.getType() == 2)
return 1;
if(o1.getType() == 1 && o2.getType() != 1)
return -1;
if(o2.getType() == 1 && o1.getType() != 1)
return 1;
return Long.compare(o1.getCreateDt(), o2.getCreateDt());
});
groupMembers = groupMembers.subList(0, 9);
}
List<UserIdNamePortraitPojo> mids = new ArrayList<>();
for (PojoGroupMember member:groupMembers) {
IMResult<InputOutputUserInfo> userInfoIMResult = UserAdmin.getUserByUserId(member.getMember_id());
if(userInfoIMResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
mids.add(new UserIdNamePortraitPojo(member.getMember_id(), userInfoIMResult.result.getDisplayName(), userInfoIMResult.result.getPortrait()));
} else {
mids.add(new UserIdNamePortraitPojo(member.getMember_id(),"", ""));
}
}
return RestResult.ok(mids);
} catch (Exception e) {
e.printStackTrace();
LOG.error("getGroupMembersForPortrait exception", e);
return RestResult.error(ERROR_SERVER_ERROR);
}
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/conference/ConferenceCleanupService.java
================================================
package cn.wildfirechat.app.conference;
import cn.wildfirechat.app.jpa.ConferenceEntity;
import cn.wildfirechat.app.jpa.ConferenceEntityRepository;
import cn.wildfirechat.app.jpa.UserConferenceRepository;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.sdk.ConferenceAdmin;
import cn.wildfirechat.sdk.model.IMResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class ConferenceCleanupService {
private static final Logger LOG = LoggerFactory.getLogger(ConferenceCleanupService.class);
@Autowired
private ConferenceEntityRepository conferenceEntityRepository;
@Autowired
private ConferenceServiceImpl conferenceServiceImpl;
@Autowired
private UserConferenceRepository userConferenceRepository;
/**
* 每5分钟检查一次过期会议,并调用SDK销毁
*/
@Scheduled(fixedRate = 5 * 60 * 1000)
@Transactional
public void cleanupExpiredConferences() {
long currentTime = System.currentTimeMillis() / 1000; // 转换为秒
LOG.info("开始检查过期会议,当前时间: {} 秒", currentTime);
List<ConferenceEntity> expiredConferences = conferenceEntityRepository.findExpiredConferences(currentTime);
LOG.info("发现 {} 个过期会议", expiredConferences.size());
for (ConferenceEntity conference : expiredConferences) {
try {
LOG.info("正在销毁过期会议: {}, endTime: {}", conference.id, conference.endTime);
// 调用SDK销毁会议
IMResult<Void> result = ConferenceAdmin.destroy(conference.id, conference.advance);
if (result != null && result.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
LOG.info("成功销毁会议: {}", conference.id);
} else {
LOG.warn("销毁会议 {} 返回错误: {}", conference.id,
result != null ? result.getErrorCode().getMsg() : "null result");
}
Thread.sleep(100);
// 记录会议结束并更新使用量(使用计划的endTime作为实际结束时间)
conferenceServiceImpl.endConferenceAndUpdateUsage(conference.id, conference.endTime);
// 删除该会议的所有收藏记录
userConferenceRepository.deleteByConferenceId(conference.id);
LOG.info("已删除会议的收藏记录: {}", conference.id);
// 从数据库删除会议记录
conferenceEntityRepository.delete(conference);
LOG.info("已从数据库删除会议记录: {}", conference.id);
} catch (Exception e) {
LOG.error("销毁会议 {} 时发生异常", conference.id, e);
}
}
LOG.info("过期会议清理完成,共处理 {} 个会议", expiredConferences.size());
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/conference/ConferenceController.java
================================================
package cn.wildfirechat.app.conference;
import cn.wildfirechat.app.Service;
import cn.wildfirechat.app.pojo.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@RestController
public class ConferenceController {
private static final Logger LOG = LoggerFactory.getLogger(ConferenceController.class);
@Autowired
private ConferenceService mService;
@CrossOrigin
@PostMapping(value = "/conference/get_id/{userId}")
public Object getUserConferenceId(@PathVariable("userId") String userId) throws IOException {
return mService.getUserConferenceId(userId);
}
@CrossOrigin
@PostMapping(value = "/conference/get_my_id")
public Object getMyConferenceId() throws IOException {
return mService.getMyConferenceId();
}
@CrossOrigin
@PostMapping(value = "/conference/info")
public Object getConferenceInfo(@RequestBody ConferenceInfoRequest request) throws IOException {
return mService.getConferenceInfo(request.conferenceId, request.password);
}
@CrossOrigin
@PostMapping(value = "/conference/put_info")
public Object putConferenceInfo(@RequestBody ConferenceInfo info) throws IOException {
return mService.putConferenceInfo(info);
}
@CrossOrigin
@PostMapping(value = "/conference/create")
public Object createConference(@RequestBody ConferenceInfo info) throws IOException {
return mService.createConference(info);
}
@CrossOrigin
@PostMapping(value = "/conference/destroy/{conferenceId}")
public Object destroyConference(@PathVariable("conferenceId") String conferenceId) throws IOException {
return mService.destroyConference(conferenceId);
}
@CrossOrigin
@PostMapping(value = "/conference/recording/{conferenceId}")
public Object recordingConference(@PathVariable("conferenceId") String conferenceId, @RequestBody RecordingRequest recordingRequest) throws IOException {
return mService.recordingConference(conferenceId, recordingRequest.recording);
}
@CrossOrigin
@PostMapping(value = "/conference/focus/{conferenceId}")
public Object focusConference(@PathVariable("conferenceId") String conferenceId, @RequestBody UserIdPojo request) throws IOException {
return mService.focusConference(conferenceId, request.userId);
}
@CrossOrigin
@PostMapping(value = "/conference/fav/{conferenceId}")
public Object favConference(@PathVariable("conferenceId") String conferenceId) throws IOException {
return mService.favConference(conferenceId);
}
@CrossOrigin
@PostMapping(value = "/conference/unfav/{conferenceId}")
public Object unfavConference(@PathVariable("conferenceId") String conferenceId) throws IOException {
return mService.unfavConference(conferenceId);
}
@CrossOrigin
@PostMapping(value = "/conference/is_fav/{conferenceId}")
public Object isFavConference(@PathVariable("conferenceId") String conferenceId) throws IOException {
return mService.isFavConference(conferenceId);
}
@CrossOrigin
@PostMapping(value = "/conference/fav_conferences")
public Object getFavConferences() throws IOException {
return mService.getFavConferences();
}
@CrossOrigin
@PostMapping(value = "/conference/quota")
public Object getMyConferenceQuota() throws IOException {
return mService.getMyConferenceQuota();
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/conference/ConferenceService.java
================================================
package cn.wildfirechat.app.conference;
import cn.wildfirechat.app.RestResult;
import cn.wildfirechat.app.jpa.FavoriteItem;
import cn.wildfirechat.app.pojo.*;
import cn.wildfirechat.pojos.InputCreateDevice;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
public interface ConferenceService {
RestResult getUserConferenceId(String userId);
RestResult getMyConferenceId();
RestResult getConferenceInfo(String conferenceId, String password);
RestResult putConferenceInfo(ConferenceInfo info);
RestResult createConference(ConferenceInfo info);
RestResult destroyConference(String conferenceId);
RestResult recordingConference(String conferenceId, boolean recording);
RestResult focusConference(String conferenceId, String userId);
RestResult favConference(String conferenceId);
RestResult unfavConference(String conferenceId);
RestResult getFavConferences();
RestResult isFavConference(String conferenceId);
/**
* 查询当前用户的会议额度
* @return 额度信息(包含总额度、已使用、剩余额度)
*/
RestResult getMyConferenceQuota();
}
================================================
FILE: src/main/java/cn/wildfirechat/app/conference/ConferenceServiceImpl.java
================================================
package cn.wildfirechat.app.conference;
import cn.wildfirechat.app.IMConfig;
import cn.wildfirechat.app.RestResult;
import cn.wildfirechat.app.jpa.*;
import cn.wildfirechat.app.model.ConferenceDTO;
import cn.wildfirechat.app.pojo.*;
import cn.wildfirechat.app.tools.NumericIdGenerator;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.PojoConferenceInfo;
import cn.wildfirechat.pojos.PojoConferenceInfoList;
import cn.wildfirechat.sdk.*;
import cn.wildfirechat.sdk.model.IMResult;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@org.springframework.stereotype.Service
public class ConferenceServiceImpl implements ConferenceService {
private static final Logger LOG = LoggerFactory.getLogger(ConferenceServiceImpl.class);
@Autowired
private IMConfig mIMConfig;
@Autowired
private ConferenceEntityRepository conferenceEntityRepository;
@Autowired
private UserPrivateConferenceIdRepository userPrivateConferenceIdRepository;
@Autowired
private UserConferenceRepository userConferenceRepository;
@Autowired
private UserConferenceQuotaRepository userConferenceQuotaRepository;
@Autowired
private UserQuotaUsageRepository userQuotaUsageRepository;
@Autowired
private ConferenceRecordRepository conferenceRecordRepository;
@Value("${conference.default_quota_minutes:0}")
private int defaultQuotaMinutes;
@PostConstruct
private void init() {
AdminConfig.initAdmin(mIMConfig.admin_url, mIMConfig.admin_secret);
}
@Override
public RestResult getUserConferenceId(String userId) {
String conferenceId = getPrivateConferenceId(userId);
return RestResult.ok(conferenceId);
}
@Override
public RestResult getMyConferenceId() {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
return getUserConferenceId(userId);
}
private String getPrivateConferenceId(String userId) {
Optional<UserPrivateConferenceId> privateConferenceIdOptional = userPrivateConferenceIdRepository.findById(userId);
if(privateConferenceIdOptional.isPresent()) {
return privateConferenceIdOptional.get().getConferenceId();
}
String conferenceId = NumericIdGenerator.getId(null, Arrays.asList(0), 8);
userPrivateConferenceIdRepository.save(new UserPrivateConferenceId(userId, conferenceId));
return conferenceId;
}
@Override
public RestResult getConferenceInfo(String conferenceId, String password) {
Optional<ConferenceEntity> conferenceEntityOptional = conferenceEntityRepository.findById(conferenceId);
if(conferenceEntityOptional.isPresent()) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
ConferenceEntity entity = conferenceEntityOptional.get();
if(StringUtils.isEmpty(entity.password) || entity.password.equals(password) || userId.equals(entity.owner)) {
return RestResult.ok(convertConference(entity));
}
}
return RestResult.error(RestResult.RestCode.ERROR_NOT_EXIST);
}
@Override
public RestResult putConferenceInfo(ConferenceInfo info) {
Optional<ConferenceEntity> conferenceEntityOptional = conferenceEntityRepository.findById(info.conferenceId);
if(conferenceEntityOptional.isPresent()) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
ConferenceEntity entity = conferenceEntityOptional.get();
if(userId.equals(entity.owner)) {
conferenceEntityRepository.save(convertConference(info));
} else {
return RestResult.error(RestResult.RestCode.ERROR_NO_RIGHT);
}
} else {
conferenceEntityRepository.save(convertConference(info));
}
return RestResult.ok(null);
}
@Override
public RestResult createConference(ConferenceInfo info) {
String userId = getUserId();
if(!StringUtils.isEmpty(info.owner)) {
if(!info.owner.equals(userId)) {
return RestResult.error(RestResult.RestCode.ERROR_INVALID_PARAMETER);
}
} else {
info.owner = userId;
}
//如果开始时间小于当前时间,改成当前时间
if(info.startTime < System.currentTimeMillis()/1000) {
info.startTime = System.currentTimeMillis()/1000;
}
//如果没有指定最大参与者人数,默认指定为20
if(info.maxParticipants <= 0) {
info.maxParticipants = 20;
}
// 检查配额(仅当会议有结束时间时)
if (info.endTime > 0) {
// 计算计划时长,calculateDurationMinutes 内部会处理开始时间
int plannedMinutes = calculateDurationMinutes(info.startTime, info.endTime);
LOG.info("用户 {} 创建会议,计划时长 {} 分钟(原始开始时间: {}, 结束时间: {})",
userId, plannedMinutes, info.startTime, info.endTime);
QuotaCheckResult checkResult = checkUserQuota(userId, plannedMinutes);
if (!checkResult.isEnough()) {
LOG.warn("用户 {} 会议额度不足,需要 {} 分钟,剩余 {} 分钟",
userId, plannedMinutes, checkResult.getRemaining());
return RestResult.error(RestResult.RestCode.ERROR_CONFERENCE_QUOTA_EXCEEDED);
}
LOG.info("用户 {} 配额检查通过,计划使用 {} 分钟,剩余 {} 分钟",
userId, plannedMinutes, checkResult.getRemaining());
} else {
LOG.info("用户 {} 创建永久会议(无结束时间),跳过配额检查", userId);
}
if(StringUtils.isEmpty(info.conferenceId)) {
/*
没有传来会议ID,这里生成随机会议ID。个人会议的长度是8位,随机会议ID是10位
*/
String conferenceId = null;
do {
conferenceId = NumericIdGenerator.getId(null, Arrays.asList(0), 10);
if(!conferenceEntityRepository.findById(conferenceId).isPresent()) {
break;
}
} while (true);
info.conferenceId = conferenceId;
} else {
Optional<ConferenceEntity> conferenceEntityOptional = conferenceEntityRepository.findById(info.conferenceId);
if(conferenceEntityOptional.isPresent()) {
ConferenceEntity entity = conferenceEntityOptional.get();
if(!userId.equals(entity.owner)) {
return RestResult.error(RestResult.RestCode.ERROR_NO_RIGHT);
}
}
}
try {
IMResult<Void> result = ConferenceAdmin.createRoom(info.conferenceId, info.conferenceTitle, info.pin, info.maxParticipants, info.advance, 0, info.recording, false);
if(result != null && result.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
conferenceEntityRepository.save(convertConference(info));
LOG.info("会议创建成功: conferenceId={}, owner={}, title={}",
info.conferenceId, userId, info.conferenceTitle);
// 创建会议记录(仅当会议有结束时间时)
if (info.endTime > 0) {
createConferenceRecord(info);
}
favConference(info.conferenceId);
return RestResult.ok(info.conferenceId);
} else {
LOG.error("创建会议失败: conferenceId={}, errorCode={}, errorMsg={}",
info.conferenceId,
result != null ? result.getErrorCode().code : "null",
result != null ? result.getErrorCode().msg : "null result");
}
} catch (Exception e) {
LOG.error("创建会议异常: conferenceId={}", info.conferenceId, e);
}
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
@Override
@Transactional
public RestResult destroyConference(String conferenceId) {
String userId = getUserId();
LOG.info("用户 {} 请求销毁会议: {}", userId, conferenceId);
Optional<ConferenceEntity> conferenceEntityOptional = conferenceEntityRepository.findById(conferenceId);
if(conferenceEntityOptional.isPresent()) {
ConferenceEntity entity = conferenceEntityOptional.get();
if(!userId.equals(entity.owner)) {
LOG.warn("用户 {} 无权销毁会议 {},会议所有者是 {}", userId, conferenceId, entity.owner);
return RestResult.error(RestResult.RestCode.ERROR_NO_RIGHT);
}
try {
ConferenceAdmin.destroy(entity.id, entity.advance);
LOG.info("SDK销毁会议成功: conferenceId={}", conferenceId);
} catch (Exception e) {
LOG.error("SDK销毁会议失败: conferenceId={}", conferenceId, e);
}
long actualEndTime = System.currentTimeMillis() / 1000;
// 记录会议结束,更新使用时长
endConferenceAndUpdateUsage(conferenceId, actualEndTime);
// 删除该会议的所有收藏记录
userConferenceRepository.deleteByConferenceId(conferenceId);
LOG.info("已删除会议的收藏记录: conferenceId={}", conferenceId);
conferenceEntityRepository.deleteById(conferenceId);
LOG.info("会议已从数据库删除: conferenceId={}", conferenceId);
} else {
LOG.warn("销毁会议时未找到会议记录: conferenceId={}", conferenceId);
try {
IMResult<PojoConferenceInfoList> conferenceInfoListIMResult = ConferenceAdmin.listConferences(1000, 0);
if(conferenceInfoListIMResult != null && conferenceInfoListIMResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
for (PojoConferenceInfo info : conferenceInfoListIMResult.getResult().conferenceInfoList) {
if(info.roomId.equals(conferenceId)) {
ConferenceAdmin.destroy(info.roomId, info.advance);
LOG.info("通过列表找到并销毁会议: conferenceId={}", conferenceId);
break;
}
}
}
} catch (Exception e) {
LOG.error("销毁会议异常: conferenceId={}", conferenceId, e);
}
}
return RestResult.ok(null);
}
@Override
public RestResult recordingConference(String conferenceId, boolean recording) {
Optional<ConferenceEntity> conferenceEntityOptional = conferenceEntityRepository.findById(conferenceId);
if(conferenceEntityOptional.isPresent()) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
ConferenceEntity entity = conferenceEntityOptional.get();
if(userId.equals(entity.owner)) {
if(entity.isRecording() == recording) {
return RestResult.ok();
} else {
entity.setRecording(recording);
try {
IMResult<Void> voidIMResult = ConferenceAdmin.enableRecording(entity.getId(), entity.isAdvance(), entity.recording);
if(voidIMResult != null & voidIMResult.getErrorCode() == ErrorCode.ERROR_CODE_SUCCESS) {
conferenceEntityRepository.save(entity);
return RestResult.ok();
} else {
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
} catch (Exception e) {
e.printStackTrace();
return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
}
}
} else {
return RestResult.error(RestResult.RestCode.ERROR_NO_RIGHT);
}
} else {
return RestResult.error(RestResult.RestCode.ERROR_NOT_EXIST);
}
}
@Override
public RestResult focusConference(String conferenceId, String focusedUserId) {
Optional<ConferenceEntity> conferenceEntityOptional = conferenceEntityRepository.findById(conferenceId);
if(conferenceEntityOptional.isPresent()) {
Subject subject = SecurityUtils.getSubject();
String userId = (String) subject.getSession().getAttribute("userId");
ConferenceEntity entity = conferenceEntityOptional.get();
if(userId.equals(entity.owner)) {
entity.setFocus(focusedUserId);
conferenceEntityRepository.save(entity);
} else {
return RestResult.error(RestResult.RestCode.ERROR_NO_RIGHT);
}
} else {
return RestResult.error(RestResult.RestCode.ERROR_NOT_EXIST);
}
return RestResult.error(RestResult.RestCode.SUCCESS);
}
@Override
public RestResult favConference(String conferenceId) {
String userId = getUserId();
UserConference userConference = new UserConference(userId, conferenceId);
userConferenceRepository.save(userConference);
return RestResult.ok(null);
}
@Override
public RestResult unfavConference(String conferenceId) {
userConferenceRepository.deleteByUserIdAndConferenceId(getUserId(), conferenceId);
return RestResult.ok(null);
}
@Override
public RestResult getFavConferences() {
List<ConferenceDTO> ucs = userConferenceRepository.findByUserId(getUserId(), System.currentTimeMillis()/1000);
List<ConferenceInfo> infos = new ArrayList<>();
for (ConferenceDTO dto:ucs) {
ConferenceInfo info = new ConferenceInfo();
info.conferenceId = dto.getId();
info.conferenceTitle = dto.getConference_title();
info.password = dto.getPassword();
info.pin = dto.getPin();
info.owner = dto.getOwner();
info.startTime = dto.getStart_time();
info.endTime = dto.getEnd_time();
info.audience = dto.isAudience();
info.advance = dto.isAdvance();
info.allowSwitchMode = dto.isAllow_switch_mode();
info.noJoinBeforeStart = dto.isNo_join_before_start();
info.recording = dto.isRecording();
info.focus = dto.getFocus();
info.maxParticipants = dto.getMax_participants();
String managers = dto.getManages();
if(!StringUtils.isEmpty(managers)) {
info.managers = Arrays.asList(managers.split(","));
}
infos.add(info);
}
return RestResult.ok(infos);
}
@Override
public RestResult isFavConference(String conferenceId) {
Optional<UserConference> userConference = userConferenceRepository.findByUserIdAndConferenceId(getUserId(), conferenceId);
if(userConference.isPresent())
return RestResult.ok(null);
return RestResult.error(RestResult.RestCode.ERROR_NOT_EXIST);
}
@Override
public RestResult getMyConferenceQuota() {
String userId = getUserId();
String currentYearMonth = getCurrentYearMonth();
LOG.info("用户 {} 查询会议额度,年月: {}", userId, currentYearMonth);
ConferenceQuotaResponse response = new ConferenceQuotaResponse();
response.setYearMonth(currentYearMonth);
// 获取用户配额
Optional<UserConferenceQuota> quotaOptional = userConferenceQuotaRepository.findByUserId(userId);
int totalQuota;
String quotaSource;
if (quotaOptional.isPresent()) {
totalQuota = quotaOptional.get().getTotalMinutes();
quotaSource = "用户自定义配额";
} else {
totalQuota = defaultQuotaMinutes;
quotaSource = "默认配额";
}
response.setTotalQuota(totalQuota);
// 配额为0表示不限制
if (totalQuota == 0) {
response.setUnlimited(true);
response.setUsedMinutes(0);
response.setRemainingMinutes(0);
LOG.info("用户 {} 查询额度结果: 来源={}, 无限制", userId, quotaSource);
} else {
response.setUnlimited(false);
// 查询当月已使用额度
Optional<UserQuotaUsage> usageOptional = userQuotaUsageRepository.findByUserIdAndYearMonth(userId, currentYearMonth);
int usedMinutes = usageOptional.map(UserQuotaUsage::getUsedMinutes).orElse(0);
int remaining = totalQuota - usedMinutes;
response.setUsedMinutes(usedMinutes);
response.setRemainingMinutes(remaining);
LOG.info("用户 {} 查询额度结果: 来源={}, 总额度={}分钟, 已使用={}分钟, 剩余={}分钟",
userId, quotaSource, totalQuota, usedMinutes, remaining);
}
return RestResult.ok(response);
}
/**
* 检查用户配额是否充足
* 配额不分月份,但使用量按月统计
*/
private QuotaCheckResult checkUserQuota(String userId, int needMinutes) {
// 获取用户配额(优先查用户自定义配额,没有则使用默认配置)
Optional<UserConferenceQuota> quotaOptional = userConferenceQuotaRepository.findByUserId(userId);
int totalQuota;
String quotaSource;
if (quotaOptional.isPresent()) {
totalQuota = quotaOptional.get().getTotalMinutes();
quotaSource = "用户自定义配额";
} else {
totalQuota = defaultQuotaMinutes;
quotaSource = "默认配额";
}
LOG.debug("用户 {} 配额检查: 来源={}, 总额度={}分钟, 需要={}分钟",
userId, quotaSource, totalQuota, needMinutes);
// 默认配额为0表示不限制
if (totalQuota == 0) {
LOG.debug("用户 {} 使用默认配额0,不限制会议时长", userId);
return new QuotaCheckResult(true, 0, 0);
}
// 查询当月已使用额度
String currentYearMonth = getCurrentYearMonth();
Optional<UserQuotaUsage> usageOptional = userQuotaUsageRepository.findByUserIdAndYearMonth(userId, currentYearMonth);
int usedMinutes = usageOptional.map(UserQuotaUsage::getUsedMinutes).orElse(0);
// 检查是否足够
boolean enough = (usedMinutes + needMinutes) <= totalQuota;
int remaining = totalQuota - usedMinutes;
LOG.debug("用户 {} 配额详情: 年月={}, 总额度={}, 已使用={}, 需要={}, 剩余={}, 结果={}",
userId, currentYearMonth, totalQuota, usedMinutes, needMinutes, remaining,
enough ? "通过" : "不足");
return new QuotaCheckResult(enough, remaining, totalQuota);
}
/**
* 创建会议记录
*/
private void createConferenceRecord(ConferenceInfo info) {
try {
ConferenceRecord record = new ConferenceRecord();
record.setConferenceId(info.conferenceId);
record.setOwner(info.owner);
long actualStartTime = info.startTime;
record.setStartTime(actualStartTime);
record.setEndTime(info.endTime);
// 使用 actualStartTime 计算计划时长,确保与配额检查时一致
record.setPlannedDuration(calculateDurationMinutes(actualStartTime, info.endTime));
record.setActualDuration(0);
record.setStatus(ConferenceRecord.Status.ONGOING.getValue());
record.setYearMonth(getCurrentYearMonth());
conferenceRecordRepository.save(record);
LOG.info("创建会议记录: conferenceId={}, owner={}, startTime={}, plannedDuration={}分钟",
info.conferenceId, info.owner, actualStartTime, record.getPlannedDuration());
} catch (Exception e) {
LOG.error("创建会议记录失败: conferenceId={}", info.conferenceId, e);
}
}
/**
* 结束会议并更新使用量
*/
@Transactional
public void endConferenceAndUpdateUsage(String conferenceId, long actualEndTime) {
try {
Optional<ConferenceRecord> recordOptional = conferenceRecordRepository.findByConferenceId(conferenceId);
if (!recordOptional.isPresent()) {
LOG.warn("未找到会议记录: conferenceId={}", conferenceId);
return;
}
ConferenceRecord record = recordOptional.get();
if (record.getStatus() == ConferenceRecord.Status.ENDED.getValue()) {
LOG.warn("会议已结束,跳过重复处理: conferenceId={}", conferenceId);
return;
}
// 如果开始时间为0,使用当前时间作为开始时间(兼容老数据)
long startTime = record.getStartTime();
if (startTime <= 0) {
startTime = actualEndTime; // 如果开始时间为0,结束时间作为开始时间,时长为0
LOG.warn("会议记录开始时间为0,使用结束时间作为开始时间: conferenceId={}", conferenceId);
}
// 计算实际时长(分钟)
int actualDuration = calculateDurationMinutes(startTime, actualEndTime);
// 更新会议记录
record.setActualDuration(actualDuration);
record.setEndTime(actualEndTime);
record.setStatus(ConferenceRecord.Status.ENDED.getValue());
conferenceRecordRepository.save(record);
// 更新用户使用量
updateQuotaUsage(record.getOwner(), record.getYearMonth(), actualDuration);
LOG.info("会议结束并更新使用量: conferenceId={}, owner={}, startTime={}, endTime={}, actualDuration={}分钟",
conferenceId, record.getOwner(), startTime, actualEndTime, actualDuration);
} catch (Exception e) {
LOG.error("结束会议并更新使用量失败: conferenceId={}", conferenceId, e);
}
}
/**
* 更新用户配额使用量
*/
@Transactional
public void updateQuotaUsage(String userId, String yearMonth, int minutes) {
if (minutes <= 0) {
LOG.debug("更新使用量跳过: 用户={}, 时长={} 分钟(小于等于0)", userId, minutes);
return;
}
Optional<UserQuotaUsage> usageOptional = userQuotaUsageRepository.findByUserIdAndYearMonth(userId, yearMonth);
if (usageOptional.isPresent()) {
UserQuotaUsage usage = usageOptional.get();
int oldMinutes = usage.getUsedMinutes();
usage.setUsedMinutes(oldMinutes + minutes);
userQuotaUsageRepository.save(usage);
LOG.info("更新用户使用量: 用户={}, 年月={}, 新增 {} 分钟, 原使用 {} 分钟, 现使用 {} 分钟",
userId, yearMonth, minutes, oldMinutes, usage.getUsedMinutes());
} else {
UserQuotaUsage usage = new UserQuotaUsage(userId, yearMonth, minutes);
userQuotaUsageRepository.save(usage);
LOG.info("创建用户使用量记录: 用户={}, 年月={}, 使用 {} 分钟",
userId, yearMonth, minutes);
}
}
/**
* 计算时长(分钟)
*/
private int calculateDurationMinutes(long startTime, long endTime) {
long durationSeconds = endTime - startTime;
if (durationSeconds <= 0) {
return 0;
}
// 转换为分钟,向上取整
return (int) ((durationSeconds + 59) / 60);
}
/**
* 获取当前年月 (yyyyMM格式)
*/
private String getCurrentYearMonth() {
return Instant.now()
.atZone(ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyyMM"));
}
private ConferenceEntity convertConference(ConferenceInfo info) {
ConferenceEntity entity = new ConferenceEntity();
entity.id = info.conferenceId;
entity.conferenceTitle = info.conferenceTitle;
entity.password = info.password;
entity.pin = info.pin;
entity.owner = info.owner;
entity.startTime = info.startTime;
entity.endTime = info.endTime;
entity.audience = info.audience;
entity.advance = info.advance;
entity.allowSwitchMode = info.allowSwitchMode;
entity.noJoinBeforeStart = info.noJoinBeforeStart;
entity.recording = info.recording;
entity.focus = info.focus;
entity.maxParticipants = info.maxParticipants;
if(info.managers != null && !info.managers.isEmpty()) {
entity.manages = String.join(",", info.managers);
}
return entity;
}
private ConferenceInfo convertConference(ConferenceEntity entity) {
ConferenceInfo info = new ConferenceInfo();
info.conferenceId = entity.id;
info.conferenceTitle = entity.conferenceTitle;
info.password = entity.password;
info.pin = entity.pin;
info.owner = entity.owner;
info.startTime = entity.startTime;
info.endTime = entity.endTime;
info.audience = entity.audience;
info.advance = entity.advance;
info.allowSwitchMode = entity.allowSwitchMode;
info.noJoinBeforeStart = entity.noJoinBeforeStart;
info.recording = entity.recording;
info.focus = entity.focus;
info.maxParticipants = entity.maxParticipants;
if(!StringUtils.isEmpty(info.managers)) {
info.managers = Arrays.asList(entity.manages.split(","));
}
return info;
}
private String getUserId() {
Subject subject = SecurityUtils.getSubject();
return (String) subject.getSession().getAttribute("userId");
}
/**
* 配额检查结果
*/
private static class QuotaCheckResult {
private final boolean enough;
private final int remaining;
private final int total;
public QuotaCheckResult(boolean enough, int remaining, int total) {
this.enough = enough;
this.remaining = remaining;
this.total = total;
}
public boolean isEnough() {
return enough;
}
public int getRemaining() {
return remaining;
}
public int getTotal() {
return total;
}
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/jpa/Announcement.java
================================================
package cn.wildfirechat.app.jpa;
import javax.persistence.*;
@Entity
@Table(name = "text")
public class Announcement {
@Id
@Column(length = 128)
private String groupId;
private String author;
@Column(length = 2048)
private String announcement;
private long timestamp;
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getAnnouncement() {
return announcement;
}
public void setAnnouncement(String announcement) {
this.announcement = announcement;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
================================================
FILE: src/main/java/cn/wildfirechat/app/jpa/AnnouncementRepository.java
================================================
package cn.wildfirechat.app.jpa;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import java.util.List;
@RepositoryRestResource()
public interface AnnouncementRepository extends PagingAndSortingRepository<Announcement, String> {
}
================================================
FILE: src/main/java/cn/wildfirechat/app/jpa/ConferenceEntity.java
================================================
package cn.wildfirechat.app.jpa;
import javax.persistence.*;
@Entity
@Table(name = "conference")
public class ConferenceEntity {
@Id
@Column(length = 12)
public String id;
public String conferenceTitle;
public String password;
public String pin;
public String owner;
public String manages;
public long startTime;
public long endTime;
public boolean audience;
public boolean advance;
public boolean allowSwitchMode;
public boolean noJoinBeforeStart;
public boolean recording;
public String focus;
public int maxParticipants;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getConferenceTitle() {
return conferenceTitle;
}
public void setConferenceTitle(String conferenceTitle) {
gitextract_dfvped34/
├── .github/
│ └── workflows/
│ └── build.yml
├── .gitignore
├── LICENSE
├── README.md
├── aliyun_sms.md
├── build_release.sh
├── config/
│ ├── aliyun_sms.properties
│ ├── application.properties
│ ├── im.properties
│ └── tencent_sms.properties
├── deb/
│ └── control/
│ ├── control
│ ├── postinst
│ └── postrm
├── docker/
│ ├── Dockerfile
│ └── README.md
├── mvnw
├── mvnw.cmd
├── nginx/
│ └── appserver.conf
├── pom.xml
├── release_note.md
├── src/
│ ├── lib/
│ │ ├── DmDialect-for-hibernate5.4.jar
│ │ ├── DmJdbcDriver8.jar
│ │ ├── common-1.4.4.jar
│ │ └── sdk-1.4.4.jar
│ ├── main/
│ │ ├── java/
│ │ │ └── cn/
│ │ │ └── wildfirechat/
│ │ │ └── app/
│ │ │ ├── AppController.java
│ │ │ ├── Application.java
│ │ │ ├── AudioController.java
│ │ │ ├── ForbiddenException.java
│ │ │ ├── IMCallbackController.java
│ │ │ ├── IMConfig.java
│ │ │ ├── IMExceptionEventController.java
│ │ │ ├── RestResult.java
│ │ │ ├── Service.java
│ │ │ ├── ServiceImpl.java
│ │ │ ├── conference/
│ │ │ │ ├── ConferenceCleanupService.java
│ │ │ │ ├── ConferenceController.java
│ │ │ │ ├── ConferenceService.java
│ │ │ │ └── ConferenceServiceImpl.java
│ │ │ ├── jpa/
│ │ │ │ ├── Announcement.java
│ │ │ │ ├── AnnouncementRepository.java
│ │ │ │ ├── ConferenceEntity.java
│ │ │ │ ├── ConferenceEntityRepository.java
│ │ │ │ ├── ConferenceRecord.java
│ │ │ │ ├── ConferenceRecordRepository.java
│ │ │ │ ├── FavoriteItem.java
│ │ │ │ ├── FavoriteRepository.java
│ │ │ │ ├── PCSession.java
│ │ │ │ ├── PCSessionRepository.java
│ │ │ │ ├── Record.java
│ │ │ │ ├── RecordRepository.java
│ │ │ │ ├── ShiroSession.java
│ │ │ │ ├── ShiroSessionRepository.java
│ │ │ │ ├── SlideVerify.java
│ │ │ │ ├── SlideVerifyRepository.java
│ │ │ │ ├── UserConference.java
│ │ │ │ ├── UserConferenceQuota.java
│ │ │ │ ├── UserConferenceQuotaRepository.java
│ │ │ │ ├── UserConferenceRepository.java
│ │ │ │ ├── UserNameEntry.java
│ │ │ │ ├── UserNameRepository.java
│ │ │ │ ├── UserPassword.java
│ │ │ │ ├── UserPasswordRepository.java
│ │ │ │ ├── UserPrivateConferenceId.java
│ │ │ │ ├── UserPrivateConferenceIdRepository.java
│ │ │ │ ├── UserQuotaUsage.java
│ │ │ │ └── UserQuotaUsageRepository.java
│ │ │ ├── model/
│ │ │ │ └── ConferenceDTO.java
│ │ │ ├── pojo/
│ │ │ │ ├── CancelSessionRequest.java
│ │ │ │ ├── ChangeNameRequest.java
│ │ │ │ ├── ChangePasswordRequest.java
│ │ │ │ ├── ComplainRequest.java
│ │ │ │ ├── ConferenceInfo.java
│ │ │ │ ├── ConferenceInfoRequest.java
│ │ │ │ ├── ConferenceQuotaResponse.java
│ │ │ │ ├── ConfirmSessionRequest.java
│ │ │ │ ├── CreateSessionRequest.java
│ │ │ │ ├── DestroyRequest.java
│ │ │ │ ├── GroupAnnouncementPojo.java
│ │ │ │ ├── GroupIdPojo.java
│ │ │ │ ├── LoadFavoriteRequest.java
│ │ │ │ ├── LoadFavoriteResponse.java
│ │ │ │ ├── LoginResponse.java
│ │ │ │ ├── PhoneCodeLoginRequest.java
│ │ │ │ ├── PhoneCodeLoginRequestWithSlideVerify.java
│ │ │ │ ├── RecordingRequest.java
│ │ │ │ ├── ResetPasswordRequest.java
│ │ │ │ ├── SendCodeRequest.java
│ │ │ │ ├── SendCodeRequestWithSlideVerify.java
│ │ │ │ ├── SendDestroyCodeRequest.java
│ │ │ │ ├── SendMessageRequest.java
│ │ │ │ ├── SessionOutput.java
│ │ │ │ ├── SlideVerifyRequest.java
│ │ │ │ ├── SlideVerifyResponse.java
│ │ │ │ ├── UploadFileResponse.java
│ │ │ │ ├── UserIdNamePortraitPojo.java
│ │ │ │ ├── UserIdPojo.java
│ │ │ │ ├── UserPasswordLoginRequest.java
│ │ │ │ └── UserPasswordLoginRequestWithSlideVerify.java
│ │ │ ├── shiro/
│ │ │ │ ├── AuthDataSource.java
│ │ │ │ ├── CorsFilter.java
│ │ │ │ ├── DBSessionDao.java
│ │ │ │ ├── JsonAuthLoginFilter.java
│ │ │ │ ├── LdapMatcher.java
│ │ │ │ ├── LdapRealm.java
│ │ │ │ ├── LdapToken.java
│ │ │ │ ├── PhoneCodeRealm.java
│ │ │ │ ├── PhoneCodeToken.java
│ │ │ │ ├── ScanCodeRealm.java
│ │ │ │ ├── ShiroConfig.java
│ │ │ │ ├── ShiroSessionManager.java
│ │ │ │ ├── TokenAuthenticationToken.java
│ │ │ │ ├── TokenMatcher.java
│ │ │ │ └── UserPasswordRealm.java
│ │ │ ├── slide/
│ │ │ │ ├── SlideVerifyCleanupService.java
│ │ │ │ └── SlideVerifyService.java
│ │ │ ├── sms/
│ │ │ │ ├── AliyunSMSConfig.java
│ │ │ │ ├── SmsService.java
│ │ │ │ ├── SmsServiceImpl.java
│ │ │ │ └── TencentSMSConfig.java
│ │ │ └── tools/
│ │ │ ├── LdapUser.java
│ │ │ ├── LdapUtil.java
│ │ │ ├── NumericIdGenerator.java
│ │ │ ├── OrderedIdUserNameGenerator.java
│ │ │ ├── PhoneNumberUserNameGenerator.java
│ │ │ ├── RateLimiter.java
│ │ │ ├── ShortUUIDGenerator.java
│ │ │ ├── SpinLock.java
│ │ │ ├── UUIDUserNameGenerator.java
│ │ │ ├── UserNameGenerator.java
│ │ │ └── Utils.java
│ │ └── resources/
│ │ └── application.properties
│ └── test/
│ └── java/
│ └── cn/
│ └── wildfirechat/
│ └── app/
│ ├── ApplicationTests.java
│ ├── jpa/
│ │ ├── AnnouncementTest.java
│ │ ├── ConferenceEntityTest.java
│ │ ├── FavoriteItemTest.java
│ │ ├── PCSessionTest.java
│ │ ├── RecordTest.java
│ │ ├── ShiroSessionTest.java
│ │ ├── SlideVerifyRepositoryTest.java
│ │ ├── SlideVerifyTest.java
│ │ ├── UserConferenceTest.java
│ │ ├── UserNameEntryTest.java
│ │ ├── UserPasswordTest.java
│ │ └── UserPrivateConferenceIdTest.java
│ └── slide/
│ ├── SlideVerifyCleanupServiceTest.java
│ └── SlideVerifyServiceTest.java
└── systemd/
├── README.md
└── app-server.service
SYMBOL INDEX (883 symbols across 121 files)
FILE: src/main/java/cn/wildfirechat/app/AppController.java
class AppController (line 24) | @RestController
method health (line 30) | @GetMapping()
method generateSlideVerify (line 39) | @PostMapping(value = "/slide_verify/generate", produces = "application...
method verifySlide (line 45) | @PostMapping(value = "/slide_verify/verify", produces = "application/j...
method sendLoginCode (line 50) | @PostMapping(value = "/send_code", produces = "application/json;charse...
method loginWithMobileCode (line 55) | @PostMapping(value = "/login", produces = "application/json;charset=UT...
method loginWithPassword (line 60) | @PostMapping(value = "/login_pwd", produces = "application/json;charse...
method changePassword (line 65) | @PostMapping(value = "/change_pwd", produces = "application/json;chars...
method sendResetCode (line 70) | @PostMapping(value = "/send_reset_code", produces = "application/json;...
method resetPassword (line 75) | @PostMapping(value = "/reset_pwd", produces = "application/json;charse...
method sendDestroyCode (line 80) | @PostMapping(value = "/send_destroy_code", produces = "application/jso...
method destroy (line 85) | @PostMapping(value = "/destroy", produces = "application/json;charset=...
method createPcSession (line 94) | @CrossOrigin
method loginWithSession (line 100) | @CrossOrigin
method scanPc (line 146) | @PostMapping(value = "/scan_pc/{token}", produces = "application/json;...
method confirmPc (line 151) | @PostMapping(value = "/confirm_pc", produces = "application/json;chars...
method cancelPc (line 155) | @PostMapping(value = "/cancel_pc", produces = "application/json;charse...
method changeName (line 163) | @CrossOrigin
method putGroupAnnouncement (line 176) | @CrossOrigin
method getGroupAnnouncement (line 182) | @CrossOrigin
method uploadFiles (line 191) | @PostMapping(value = "/logs/{userId}/upload")
method complain (line 199) | @CrossOrigin
method addDevice (line 208) | @PostMapping(value = "/things/add_device")
method getDeviceList (line 213) | @PostMapping(value = "/things/list_device")
method delDevice (line 218) | @PostMapping(value = "/things/del_device")
method sendUserMessage (line 226) | @PostMapping(value = "/messages/send")
method uploadMedia (line 234) | @PostMapping(value = "/media/upload/{media_type}")
method putFavoriteItem (line 239) | @CrossOrigin
method removeFavoriteItem (line 245) | @CrossOrigin
method getFavoriteItems (line 251) | @CrossOrigin
method getGroupMembersForPortrait (line 257) | @CrossOrigin
FILE: src/main/java/cn/wildfirechat/app/Application.java
class Application (line 17) | @SpringBootApplication
method main (line 27) | public static void main(String[] args) {
method multipartConfigElement (line 36) | @Bean
method clearPCSession (line 46) | @Scheduled(fixedRate = 60 * 60 * 1000)
method cleanExpiredSlideVerify (line 51) | @Scheduled(fixedRate = 60 * 60 * 1000)
FILE: src/main/java/cn/wildfirechat/app/AudioController.java
class AudioController (line 21) | @RestController
method init (line 28) | @PostConstruct
method amr2mp3 (line 36) | @GetMapping("amr2mp3")
method amr2mp3 (line 82) | private static void amr2mp3(String sourceUrl, File target) throws Malf...
FILE: src/main/java/cn/wildfirechat/app/ForbiddenException.java
class ForbiddenException (line 6) | @ResponseStatus(value = HttpStatus.FORBIDDEN, reason="Forbidden")
FILE: src/main/java/cn/wildfirechat/app/IMCallbackController.java
class IMCallbackController (line 16) | @RestController()
method onUserOnlineEvent (line 21) | @PostMapping(value = "/im_event/user/online")
method onUserRelationUpdated (line 30) | @PostMapping(value = "/im_event/user/relation")
method onUserInfoUpdated (line 39) | @PostMapping(value = "/im_event/user/info")
method onMessage (line 48) | @PostMapping(value = "/im_event/message")
method onRecallMessage (line 57) | @PostMapping(value = "/im_event/recall_message")
method onThingsMessage (line 66) | @PostMapping(value = "/im_event/things/message")
method onMessageRead (line 75) | @PostMapping(value = "/im_event/message_read")
method onGroupInfoUpdated (line 84) | @PostMapping(value = "/im_event/group/info")
method onGroupMemberUpdated (line 93) | @PostMapping(value = "/im_event/group/member")
method onChannelInfoUpdated (line 102) | @PostMapping(value = "/im_event/channel/info")
method onChatroomInfoUpdated (line 111) | @PostMapping(value = "/im_event/chatroom/info")
method onChatroomMemberUpdated (line 120) | @PostMapping(value = "/im_event/chatroom/member")
method censorMessage (line 132) | @PostMapping(value = "/message/censor")
method onConferenceCreated (line 145) | @PostMapping(value = "/im_event/conference/create")
method onConferenceDestroyed (line 151) | @PostMapping(value = "/im_event/conference/destroy")
method onConferenceMemberJoined (line 157) | @PostMapping(value = "/im_event/conference/member_join")
method onConferenceMemberLeaved (line 163) | @PostMapping(value = "/im_event/conference/member_leave")
method onConferenceMemberPublished (line 169) | @PostMapping(value = "/im_event/conference/member_publish")
method onConferenceMemberUnpublished (line 175) | @PostMapping(value = "/im_event/conference/member_unpublish")
method onMomentsFeed (line 181) | @PostMapping(value = "/im_event/moments_feed")
method onMomentsFeedRecall (line 187) | @PostMapping(value = "/im_event/moments_feed_recall")
method onMomentsComment (line 193) | @PostMapping(value = "/im_event/moments_comment")
method onMomentsCommentRecall (line 199) | @PostMapping(value = "/im_event/moments_comment_recall")
FILE: src/main/java/cn/wildfirechat/app/IMConfig.java
class IMConfig (line 7) | @Configuration
method isUse_random_name (line 16) | public boolean isUse_random_name() {
method setUse_random_name (line 20) | public void setUse_random_name(boolean use_random_name) {
method getAdmin_url (line 39) | public String getAdmin_url() {
method setAdmin_url (line 43) | public void setAdmin_url(String admin_url) {
method getAdmin_secret (line 47) | public String getAdmin_secret() {
method setAdmin_secret (line 51) | public void setAdmin_secret(String admin_secret) {
method getWelcome_for_new_user (line 55) | public String getWelcome_for_new_user() {
method setWelcome_for_new_user (line 59) | public void setWelcome_for_new_user(String welcome_for_new_user) {
method getWelcome_for_back_user (line 63) | public String getWelcome_for_back_user() {
method setWelcome_for_back_user (line 67) | public void setWelcome_for_back_user(String welcome_for_back_user) {
method isNew_user_robot_friend (line 71) | public boolean isNew_user_robot_friend() {
method setNew_user_robot_friend (line 75) | public void setNew_user_robot_friend(boolean new_user_robot_friend) {
method getRobot_friend_id (line 79) | public String getRobot_friend_id() {
method setRobot_friend_id (line 83) | public void setRobot_friend_id(String robot_friend_id) {
method getRobot_welcome (line 87) | public String getRobot_welcome() {
method setRobot_welcome (line 91) | public void setRobot_welcome(String robot_welcome) {
method getNew_user_subscribe_channel_id (line 95) | public String getNew_user_subscribe_channel_id() {
method setNew_user_subscribe_channel_id (line 99) | public void setNew_user_subscribe_channel_id(String new_user_subscribe...
method getBack_user_subscribe_channel_id (line 103) | public String getBack_user_subscribe_channel_id() {
method setBack_user_subscribe_channel_id (line 107) | public void setBack_user_subscribe_channel_id(String back_user_subscri...
method getAdmin_user_id (line 111) | public String getAdmin_user_id() {
method setAdmin_user_id (line 115) | public void setAdmin_user_id(String admin_user_id) {
method getPrompt_text (line 119) | public String getPrompt_text() {
method setPrompt_text (line 123) | public void setPrompt_text(String prompt_text) {
method getImage_msg_url (line 127) | public String getImage_msg_url() {
method setImage_msg_url (line 131) | public void setImage_msg_url(String image_msg_url) {
method getImage_msg_base64_thumbnail (line 135) | public String getImage_msg_base64_thumbnail() {
method setImage_msg_base64_thumbnail (line 139) | public void setImage_msg_base64_thumbnail(String image_msg_base64_thum...
FILE: src/main/java/cn/wildfirechat/app/IMExceptionEventController.java
class IMExceptionEventController (line 33) | @RestController
method init (line 47) | @PostConstruct
method onIMException (line 65) | @PostMapping("im_exception_event")
method sendTextMail (line 77) | public void sendTextMail(String subject, String content){
method sendHtmlMail (line 89) | public void sendHtmlMail(String subject, String content) throws Messag...
FILE: src/main/java/cn/wildfirechat/app/RestResult.java
class RestResult (line 3) | public class RestResult {
type RestCode (line 4) | public enum RestCode {
method RestCode (line 31) | RestCode(int code, String msg) {
method ok (line 41) | public static RestResult ok() {
method ok (line 45) | public static RestResult ok(Object object) {
method error (line 49) | public static RestResult error(RestCode code) {
method result (line 53) | public static RestResult result(RestCode code, Object object){
method result (line 57) | public static RestResult result(int code, String message, Object object){
method RestResult (line 64) | private RestResult(RestCode code, Object result) {
method getCode (line 70) | public int getCode() {
method setCode (line 74) | public void setCode(int code) {
method getMessage (line 78) | public String getMessage() {
method setMessage (line 82) | public void setMessage(String message) {
method getResult (line 86) | public Object getResult() {
method setResult (line 90) | public void setResult(Object result) {
FILE: src/main/java/cn/wildfirechat/app/Service.java
type Service (line 11) | public interface Service {
method sendLoginCode (line 12) | RestResult sendLoginCode(String mobile);
method sendLoginCode (line 13) | RestResult sendLoginCode(String mobile, String slideVerifyToken);
method sendResetCode (line 14) | RestResult sendResetCode(String mobile, String slideVerifyToken);
method loginWithMobileCode (line 15) | RestResult loginWithMobileCode(HttpServletResponse response, String mo...
method loginWithPassword (line 16) | RestResult loginWithPassword(HttpServletResponse response, String mobi...
method changePassword (line 17) | RestResult changePassword(String oldPwd, String newPwd, String slideVe...
method resetPassword (line 18) | RestResult resetPassword(String mobile, String resetCode, String newPwd);
method sendDestroyCode (line 19) | RestResult sendDestroyCode();
method sendDestroyCode (line 20) | RestResult sendDestroyCode(String slideVerifyToken);
method destroy (line 21) | RestResult destroy(HttpServletResponse response, String code);
method createPcSession (line 23) | RestResult createPcSession(CreateSessionRequest request);
method loginWithSession (line 24) | RestResult loginWithSession(String token);
method scanPc (line 26) | RestResult scanPc(String token);
method confirmPc (line 27) | RestResult confirmPc(ConfirmSessionRequest request);
method cancelPc (line 28) | RestResult cancelPc(CancelSessionRequest request);
method changeName (line 30) | RestResult changeName(String newName);
method complain (line 31) | RestResult complain(String text);
method putGroupAnnouncement (line 33) | RestResult putGroupAnnouncement(GroupAnnouncementPojo request);
method getGroupAnnouncement (line 34) | RestResult getGroupAnnouncement(String groupId);
method saveUserLogs (line 36) | RestResult saveUserLogs(String userId, MultipartFile file);
method generateSlideVerify (line 38) | RestResult generateSlideVerify();
method verifySlide (line 39) | RestResult verifySlide(String token, int x);
method addDevice (line 41) | RestResult addDevice(InputCreateDevice createDevice);
method getDeviceList (line 42) | RestResult getDeviceList();
method delDevice (line 43) | RestResult delDevice(InputCreateDevice createDevice);
method sendUserMessage (line 45) | RestResult sendUserMessage(SendMessageRequest request);
method uploadMedia (line 46) | RestResult uploadMedia(int mediaType, MultipartFile file);
method putFavoriteItem (line 48) | RestResult putFavoriteItem(FavoriteItem request);
method removeFavoriteItems (line 49) | RestResult removeFavoriteItems(long id);
method getFavoriteItems (line 50) | RestResult getFavoriteItems(long id, int count);
method getGroupMembersForPortrait (line 51) | RestResult getGroupMembersForPortrait(String groupId);
FILE: src/main/java/cn/wildfirechat/app/ServiceImpl.java
class ServiceImpl (line 70) | @org.springframework.stereotype.Service
method init (line 195) | @PostConstruct
method getIp (line 204) | private String getIp() {
method getUserStatus (line 225) | private int getUserStatus(String mobile) {
method sendLoginCode (line 239) | @Override
method sendLoginCode (line 244) | @Override
method generateSlideVerify (line 318) | @Override
method verifySlide (line 329) | @Override
method sendResetCode (line 339) | @Override
method loginWithMobileCode (line 433) | @Override
method loginWithLdap (line 481) | public RestResult loginWithLdap(HttpServletResponse httpResponse, Stri...
method getUserDefaultPassword (line 521) | private String getUserDefaultPassword(String mobile) {
method loginWithPassword (line 525) | @Override
method changePassword (line 629) | @Override
method resetPassword (line 667) | @Override
method changePassword (line 717) | private UserPassword changePassword(UserPassword up, String password) ...
method verifyPassword (line 730) | private boolean verifyPassword(UserPassword up, String password) throw...
method onLoginSuccess (line 743) | private RestResult onLoginSuccess(HttpServletResponse httpResponse, St...
method sendDestroyCode (line 897) | @Override
method sendDestroyCode (line 902) | @Override
method destroy (line 939) | @Override
method isUsernameAvailable (line 964) | private boolean isUsernameAvailable(String username) {
method sendPcLoginRequestMessage (line 976) | private void sendPcLoginRequestMessage(String fromUser, String toUser,...
method sendTextMessage (line 1015) | private void sendTextMessage(String fromUser, String toUser, String te...
method sendImageMessage (line 1026) | private void sendImageMessage(String fromUser, String toUser, String u...
method sendMessage (line 1040) | private void sendMessage(String fromUser, Conversation conversation, M...
method createPcSession (line 1055) | @Override
method loginWithSession (line 1079) | @Override
method scanPc (line 1162) | @Override
method confirmPc (line 1171) | @Override
method cancelPc (line 1187) | @Override
method changeName (line 1192) | @Override
method complain (line 1230) | @Override
method getGroupAnnouncement (line 1239) | @Override
method putGroupAnnouncement (line 1254) | @Override
method saveUserLogs (line 1315) | @Override
method addDevice (line 1329) | @Override
method getDeviceList (line 1356) | @Override
method delDevice (line 1372) | @Override
method sendUserMessage (line 1411) | @Override
method uploadMedia (line 1445) | @Override
method putFavoriteItem (line 1608) | @Override
method removeFavoriteItems (line 1729) | @Override
method getFavoriteItems (line 1735) | @Override
method getGroupMembersForPortrait (line 1748) | @Override
FILE: src/main/java/cn/wildfirechat/app/conference/ConferenceCleanupService.java
class ConferenceCleanupService (line 18) | @Service
method cleanupExpiredConferences (line 34) | @Scheduled(fixedRate = 5 * 60 * 1000)
FILE: src/main/java/cn/wildfirechat/app/conference/ConferenceController.java
class ConferenceController (line 12) | @RestController
method getUserConferenceId (line 18) | @CrossOrigin
method getMyConferenceId (line 24) | @CrossOrigin
method getConferenceInfo (line 30) | @CrossOrigin
method putConferenceInfo (line 36) | @CrossOrigin
method createConference (line 42) | @CrossOrigin
method destroyConference (line 48) | @CrossOrigin
method recordingConference (line 54) | @CrossOrigin
method focusConference (line 60) | @CrossOrigin
method favConference (line 66) | @CrossOrigin
method unfavConference (line 72) | @CrossOrigin
method isFavConference (line 78) | @CrossOrigin
method getFavConferences (line 84) | @CrossOrigin
method getMyConferenceQuota (line 90) | @CrossOrigin
FILE: src/main/java/cn/wildfirechat/app/conference/ConferenceService.java
type ConferenceService (line 12) | public interface ConferenceService {
method getUserConferenceId (line 13) | RestResult getUserConferenceId(String userId);
method getMyConferenceId (line 14) | RestResult getMyConferenceId();
method getConferenceInfo (line 15) | RestResult getConferenceInfo(String conferenceId, String password);
method putConferenceInfo (line 16) | RestResult putConferenceInfo(ConferenceInfo info);
method createConference (line 17) | RestResult createConference(ConferenceInfo info);
method destroyConference (line 18) | RestResult destroyConference(String conferenceId);
method recordingConference (line 19) | RestResult recordingConference(String conferenceId, boolean recording);
method focusConference (line 20) | RestResult focusConference(String conferenceId, String userId);
method favConference (line 21) | RestResult favConference(String conferenceId);
method unfavConference (line 22) | RestResult unfavConference(String conferenceId);
method getFavConferences (line 23) | RestResult getFavConferences();
method isFavConference (line 24) | RestResult isFavConference(String conferenceId);
method getMyConferenceQuota (line 30) | RestResult getMyConferenceQuota();
FILE: src/main/java/cn/wildfirechat/app/conference/ConferenceServiceImpl.java
class ConferenceServiceImpl (line 33) | @org.springframework.stereotype.Service
method init (line 61) | @PostConstruct
method getUserConferenceId (line 66) | @Override
method getMyConferenceId (line 72) | @Override
method getPrivateConferenceId (line 79) | private String getPrivateConferenceId(String userId) {
method getConferenceInfo (line 90) | @Override
method putConferenceInfo (line 105) | @Override
method createConference (line 124) | @Override
method destroyConference (line 211) | @Override
method recordingConference (line 261) | @Override
method focusConference (line 294) | @Override
method favConference (line 313) | @Override
method unfavConference (line 321) | @Override
method getFavConferences (line 327) | @Override
method isFavConference (line 356) | @Override
method getMyConferenceQuota (line 365) | @Override
method checkUserQuota (line 416) | private QuotaCheckResult checkUserQuota(String userId, int needMinutes) {
method createConferenceRecord (line 458) | private void createConferenceRecord(ConferenceInfo info) {
method endConferenceAndUpdateUsage (line 484) | @Transactional
method updateQuotaUsage (line 529) | @Transactional
method calculateDurationMinutes (line 555) | private int calculateDurationMinutes(long startTime, long endTime) {
method getCurrentYearMonth (line 567) | private String getCurrentYearMonth() {
method convertConference (line 573) | private ConferenceEntity convertConference(ConferenceInfo info) {
method convertConference (line 595) | private ConferenceInfo convertConference(ConferenceEntity entity) {
method getUserId (line 617) | private String getUserId() {
class QuotaCheckResult (line 625) | private static class QuotaCheckResult {
method QuotaCheckResult (line 630) | public QuotaCheckResult(boolean enough, int remaining, int total) {
method isEnough (line 636) | public boolean isEnough() {
method getRemaining (line 640) | public int getRemaining() {
method getTotal (line 644) | public int getTotal() {
FILE: src/main/java/cn/wildfirechat/app/jpa/Announcement.java
class Announcement (line 5) | @Entity
method getGroupId (line 19) | public String getGroupId() {
method setGroupId (line 23) | public void setGroupId(String groupId) {
method getAnnouncement (line 27) | public String getAnnouncement() {
method setAnnouncement (line 31) | public void setAnnouncement(String announcement) {
method getAuthor (line 35) | public String getAuthor() {
method setAuthor (line 39) | public void setAuthor(String author) {
method getTimestamp (line 43) | public long getTimestamp() {
method setTimestamp (line 47) | public void setTimestamp(long timestamp) {
FILE: src/main/java/cn/wildfirechat/app/jpa/AnnouncementRepository.java
type AnnouncementRepository (line 9) | @RepositoryRestResource()
FILE: src/main/java/cn/wildfirechat/app/jpa/ConferenceEntity.java
class ConferenceEntity (line 5) | @Entity
method getId (line 26) | public String getId() {
method setId (line 30) | public void setId(String id) {
method getConferenceTitle (line 34) | public String getConferenceTitle() {
method setConferenceTitle (line 38) | public void setConferenceTitle(String conferenceTitle) {
method getPassword (line 42) | public String getPassword() {
method setPassword (line 46) | public void setPassword(String password) {
method getPin (line 50) | public String getPin() {
method setPin (line 54) | public void setPin(String pin) {
method getOwner (line 58) | public String getOwner() {
method setOwner (line 62) | public void setOwner(String owner) {
method getStartTime (line 66) | public long getStartTime() {
method setStartTime (line 70) | public void setStartTime(long startTime) {
method getEndTime (line 74) | public long getEndTime() {
method setEndTime (line 78) | public void setEndTime(long endTime) {
method isAudience (line 82) | public boolean isAudience() {
method setAudience (line 86) | public void setAudience(boolean audience) {
method isAdvance (line 90) | public boolean isAdvance() {
method setAdvance (line 94) | public void setAdvance(boolean advance) {
method isAllowSwitchMode (line 98) | public boolean isAllowSwitchMode() {
method setAllowSwitchMode (line 102) | public void setAllowSwitchMode(boolean allowSwitchMode) {
method isNoJoinBeforeStart (line 106) | public boolean isNoJoinBeforeStart() {
method setNoJoinBeforeStart (line 110) | public void setNoJoinBeforeStart(boolean noJoinBeforeStart) {
method isRecording (line 114) | public boolean isRecording() {
method setRecording (line 118) | public void setRecording(boolean recording) {
method getManages (line 122) | public String getManages() {
method setManages (line 126) | public void setManages(String manages) {
method getFocus (line 130) | public String getFocus() {
method setFocus (line 134) | public void setFocus(String focus) {
method getMaxParticipants (line 138) | public int getMaxParticipants() {
method setMaxParticipants (line 142) | public void setMaxParticipants(int maxParticipants) {
FILE: src/main/java/cn/wildfirechat/app/jpa/ConferenceEntityRepository.java
type ConferenceEntityRepository (line 9) | @RepositoryRestResource()
method findExpiredConferences (line 17) | @Query("SELECT c FROM ConferenceEntity c WHERE c.endTime > 0 AND c.end...
FILE: src/main/java/cn/wildfirechat/app/jpa/ConferenceRecord.java
class ConferenceRecord (line 10) | @Entity
type Status (line 14) | public enum Status {
method Status (line 20) | Status(int value) {
method getValue (line 24) | public int getValue() {
method onCreate (line 62) | @PrePersist
method onUpdate (line 68) | @PreUpdate
method ConferenceRecord (line 73) | public ConferenceRecord() {
method getConferenceId (line 76) | public String getConferenceId() {
method setConferenceId (line 80) | public void setConferenceId(String conferenceId) {
method getOwner (line 84) | public String getOwner() {
method setOwner (line 88) | public void setOwner(String owner) {
method getStartTime (line 92) | public long getStartTime() {
method setStartTime (line 96) | public void setStartTime(long startTime) {
method getEndTime (line 100) | public long getEndTime() {
method setEndTime (line 104) | public void setEndTime(long endTime) {
method getPlannedDuration (line 108) | public int getPlannedDuration() {
method setPlannedDuration (line 112) | public void setPlannedDuration(int plannedDuration) {
method getActualDuration (line 116) | public int getActualDuration() {
method setActualDuration (line 120) | public void setActualDuration(int actualDuration) {
method getStatus (line 124) | public int getStatus() {
method setStatus (line 128) | public void setStatus(int status) {
method getYearMonth (line 132) | public String getYearMonth() {
method setYearMonth (line 136) | public void setYearMonth(String yearMonth) {
method getCreatedAt (line 140) | public Date getCreatedAt() {
method setCreatedAt (line 144) | public void setCreatedAt(Date createdAt) {
method getUpdatedAt (line 148) | public Date getUpdatedAt() {
method setUpdatedAt (line 152) | public void setUpdatedAt(Date updatedAt) {
FILE: src/main/java/cn/wildfirechat/app/jpa/ConferenceRecordRepository.java
type ConferenceRecordRepository (line 13) | @Repository
method findByConferenceId (line 19) | Optional<ConferenceRecord> findByConferenceId(String conferenceId);
method endConference (line 24) | @Modifying
FILE: src/main/java/cn/wildfirechat/app/jpa/FavoriteItem.java
class FavoriteItem (line 6) | @Entity
FILE: src/main/java/cn/wildfirechat/app/jpa/FavoriteRepository.java
type FavoriteRepository (line 9) | @RepositoryRestResource()
method loadFav (line 12) | @Query(value = "select * from t_favorites where user_id = ?1 and id < ...
FILE: src/main/java/cn/wildfirechat/app/jpa/PCSession.java
class PCSession (line 10) | @Entity
type PCSessionStatus (line 13) | public interface PCSessionStatus {
method getPlatform (line 33) | public int getPlatform() {
method setPlatform (line 37) | public void setPlatform(int platform) {
method getToken (line 41) | public String getToken() {
method setToken (line 45) | public void setToken(String token) {
method getClientId (line 49) | public String getClientId() {
method setClientId (line 53) | public void setClientId(String clientId) {
method getCreateDt (line 57) | public long getCreateDt() {
method setCreateDt (line 61) | public void setCreateDt(long createDt) {
method getDuration (line 65) | public long getDuration() {
method setDuration (line 69) | public void setDuration(long duration) {
method getStatus (line 73) | public int getStatus() {
method setStatus (line 77) | public void setStatus(int status) {
method getConfirmedUserId (line 81) | public String getConfirmedUserId() {
method setConfirmedUserId (line 85) | public void setConfirmedUserId(String confirmedUserId) {
method getDevice_name (line 89) | public String getDevice_name() {
method setDevice_name (line 93) | public void setDevice_name(String device_name) {
method toOutput (line 97) | public SessionOutput toOutput() {
FILE: src/main/java/cn/wildfirechat/app/jpa/PCSessionRepository.java
type PCSessionRepository (line 7) | public interface PCSessionRepository extends CrudRepository<PCSession, S...
method deleteByCreateDtBefore (line 9) | @Modifying
FILE: src/main/java/cn/wildfirechat/app/jpa/Record.java
class Record (line 8) | @Entity
method Record (line 22) | public Record(String code, String mobile) {
method Record (line 30) | public Record() {
method increaseAndCheck (line 33) | public boolean increaseAndCheck() {
method reset (line 45) | public void reset() {
method getRequestCount (line 50) | public int getRequestCount() {
method getCode (line 54) | public String getCode() {
method setCode (line 58) | public void setCode(String code) {
method getMobile (line 62) | public String getMobile() {
method getTimestamp (line 66) | public long getTimestamp() {
method setTimestamp (line 70) | public void setTimestamp(long timestamp) {
method getStartTime (line 74) | public long getStartTime() {
method setStartTime (line 78) | public void setStartTime(long startTime) {
FILE: src/main/java/cn/wildfirechat/app/jpa/RecordRepository.java
type RecordRepository (line 5) | public interface RecordRepository extends CrudRepository<Record, String> {
FILE: src/main/java/cn/wildfirechat/app/jpa/ShiroSession.java
class ShiroSession (line 7) | @Entity
method ShiroSession (line 19) | public ShiroSession(String sessionId, byte[] sessionData) {
method ShiroSession (line 24) | public ShiroSession() {
method getSessionId (line 27) | public String getSessionId() {
method setSessionId (line 31) | public void setSessionId(String sessionId) {
method getSessionData (line 35) | public byte[] getSessionData() {
method setSessionData (line 39) | public void setSessionData(byte[] sessionData) {
FILE: src/main/java/cn/wildfirechat/app/jpa/ShiroSessionRepository.java
type ShiroSessionRepository (line 5) | public interface ShiroSessionRepository extends CrudRepository<ShiroSess...
FILE: src/main/java/cn/wildfirechat/app/jpa/SlideVerify.java
class SlideVerify (line 6) | @Entity
method SlideVerify (line 22) | public SlideVerify() {
method SlideVerify (line 25) | public SlideVerify(String token, int x, long timestamp) {
method getToken (line 32) | public String getToken() {
method setToken (line 36) | public void setToken(String token) {
method getX (line 40) | public int getX() {
method setX (line 44) | public void setX(int x) {
method getTimestamp (line 48) | public long getTimestamp() {
method setTimestamp (line 52) | public void setTimestamp(long timestamp) {
method isVerified (line 56) | public boolean isVerified() {
method setVerified (line 60) | public void setVerified(boolean verified) {
method isExpired (line 64) | public boolean isExpired(int timeoutSeconds) {
FILE: src/main/java/cn/wildfirechat/app/jpa/SlideVerifyRepository.java
type SlideVerifyRepository (line 11) | @Repository
method findByToken (line 14) | Optional<SlideVerify> findByToken(String token);
method deleteExpired (line 16) | @Modifying
FILE: src/main/java/cn/wildfirechat/app/jpa/UserConference.java
class UserConference (line 9) | @Entity
method UserConference (line 24) | public UserConference() {
method UserConference (line 27) | public UserConference(String userId, String conferenceId) {
method getUserId (line 33) | public String getUserId() {
method setUserId (line 37) | public void setUserId(String userId) {
method getConferenceId (line 41) | public String getConferenceId() {
method setConferenceId (line 45) | public void setConferenceId(String conferenceId) {
FILE: src/main/java/cn/wildfirechat/app/jpa/UserConferenceQuota.java
class UserConferenceQuota (line 10) | @Entity
method onCreate (line 29) | @PrePersist
method onUpdate (line 35) | @PreUpdate
method UserConferenceQuota (line 40) | public UserConferenceQuota() {
method UserConferenceQuota (line 43) | public UserConferenceQuota(String userId, int totalMinutes) {
method getUserId (line 48) | public String getUserId() {
method setUserId (line 52) | public void setUserId(String userId) {
method getTotalMinutes (line 56) | public int getTotalMinutes() {
method setTotalMinutes (line 60) | public void setTotalMinutes(int totalMinutes) {
method getCreatedAt (line 64) | public Date getCreatedAt() {
method setCreatedAt (line 68) | public void setCreatedAt(Date createdAt) {
method getUpdatedAt (line 72) | public Date getUpdatedAt() {
method setUpdatedAt (line 76) | public void setUpdatedAt(Date updatedAt) {
FILE: src/main/java/cn/wildfirechat/app/jpa/UserConferenceQuotaRepository.java
type UserConferenceQuotaRepository (line 11) | @Repository
method findByUserId (line 17) | Optional<UserConferenceQuota> findByUserId(String userId);
FILE: src/main/java/cn/wildfirechat/app/jpa/UserConferenceRepository.java
type UserConferenceRepository (line 13) | @RepositoryRestResource()
method deleteByUserIdAndConferenceId (line 15) | @Transactional
method deleteByConferenceId (line 20) | @Transactional
method findByUserId (line 25) | @Query(value = "select c.* from user_conference uc, conference c where...
method findByUserIdAndConferenceId (line 28) | Optional<UserConference> findByUserIdAndConferenceId(String userId, St...
FILE: src/main/java/cn/wildfirechat/app/jpa/UserNameEntry.java
class UserNameEntry (line 5) | @Entity
method getId (line 13) | public Integer getId() {
method setId (line 17) | public void setId(Integer id) {
FILE: src/main/java/cn/wildfirechat/app/jpa/UserNameRepository.java
type UserNameRepository (line 6) | @RepositoryRestResource()
FILE: src/main/java/cn/wildfirechat/app/jpa/UserPassword.java
class UserPassword (line 8) | @Entity
method UserPassword (line 27) | public UserPassword() {
method UserPassword (line 30) | public UserPassword(String userId) {
method UserPassword (line 34) | public UserPassword(String userId, String password, String salt) {
method UserPassword (line 43) | public UserPassword(String userId, String password, String salt, Strin...
method getUserId (line 53) | public String getUserId() {
method setUserId (line 57) | public void setUserId(String userId) {
method getPassword (line 61) | public String getPassword() {
method setPassword (line 65) | public void setPassword(String password) {
method getSalt (line 69) | public String getSalt() {
method setSalt (line 73) | public void setSalt(String salt) {
method getResetCode (line 77) | public String getResetCode() {
method setResetCode (line 81) | public void setResetCode(String resetCode) {
method getResetCodeTime (line 85) | public long getResetCodeTime() {
method setResetCodeTime (line 89) | public void setResetCodeTime(long resetCodeTime) {
method getTryCount (line 93) | public int getTryCount() {
method setTryCount (line 97) | public void setTryCount(int tryCount) {
method getLastTryTime (line 101) | public long getLastTryTime() {
method setLastTryTime (line 105) | public void setLastTryTime(long lastTryTime) {
FILE: src/main/java/cn/wildfirechat/app/jpa/UserPasswordRepository.java
type UserPasswordRepository (line 7) | @RepositoryRestResource()
FILE: src/main/java/cn/wildfirechat/app/jpa/UserPrivateConferenceId.java
class UserPrivateConferenceId (line 8) | @Entity
method UserPrivateConferenceId (line 17) | public UserPrivateConferenceId() {
method UserPrivateConferenceId (line 20) | public UserPrivateConferenceId(String userId, String conferenceId) {
method getUserId (line 25) | public String getUserId() {
method setUserId (line 29) | public void setUserId(String userId) {
method getConferenceId (line 33) | public String getConferenceId() {
method setConferenceId (line 37) | public void setConferenceId(String conferenceId) {
FILE: src/main/java/cn/wildfirechat/app/jpa/UserPrivateConferenceIdRepository.java
type UserPrivateConferenceIdRepository (line 6) | @RepositoryRestResource()
FILE: src/main/java/cn/wildfirechat/app/jpa/UserQuotaUsage.java
class UserQuotaUsage (line 10) | @Entity
method onCreate (line 35) | @PrePersist
method onUpdate (line 41) | @PreUpdate
method UserQuotaUsage (line 46) | public UserQuotaUsage() {
method UserQuotaUsage (line 49) | public UserQuotaUsage(String userId, String yearMonth, int usedMinutes) {
method getId (line 55) | public Long getId() {
method setId (line 59) | public void setId(Long id) {
method getUserId (line 63) | public String getUserId() {
method setUserId (line 67) | public void setUserId(String userId) {
method getYearMonth (line 71) | public String getYearMonth() {
method setYearMonth (line 75) | public void setYearMonth(String yearMonth) {
method getUsedMinutes (line 79) | public int getUsedMinutes() {
method setUsedMinutes (line 83) | public void setUsedMinutes(int usedMinutes) {
method getCreatedAt (line 87) | public Date getCreatedAt() {
method setCreatedAt (line 91) | public void setCreatedAt(Date createdAt) {
method getUpdatedAt (line 95) | public Date getUpdatedAt() {
method setUpdatedAt (line 99) | public void setUpdatedAt(Date updatedAt) {
FILE: src/main/java/cn/wildfirechat/app/jpa/UserQuotaUsageRepository.java
type UserQuotaUsageRepository (line 13) | @Repository
method findByUserIdAndYearMonth (line 19) | Optional<UserQuotaUsage> findByUserIdAndYearMonth(String userId, Strin...
method addUsedMinutes (line 24) | @Modifying
FILE: src/main/java/cn/wildfirechat/app/model/ConferenceDTO.java
type ConferenceDTO (line 3) | public interface ConferenceDTO {
method getId (line 4) | String getId();
method getConference_title (line 5) | String getConference_title();
method getPassword (line 6) | String getPassword();
method getPin (line 7) | String getPin();
method getOwner (line 8) | String getOwner();
method getManages (line 9) | public String getManages();
method getStart_time (line 10) | long getStart_time();
method getEnd_time (line 11) | long getEnd_time();
method isAudience (line 12) | boolean isAudience();
method isAdvance (line 13) | boolean isAdvance();
method isAllow_switch_mode (line 14) | boolean isAllow_switch_mode();
method isNo_join_before_start (line 15) | boolean isNo_join_before_start();
method isRecording (line 16) | boolean isRecording();
method getFocus (line 17) | String getFocus();
method getMax_participants (line 18) | int getMax_participants();
FILE: src/main/java/cn/wildfirechat/app/pojo/CancelSessionRequest.java
class CancelSessionRequest (line 3) | public class CancelSessionRequest {
method getToken (line 6) | public String getToken() {
method setToken (line 10) | public void setToken(String token) {
FILE: src/main/java/cn/wildfirechat/app/pojo/ChangeNameRequest.java
class ChangeNameRequest (line 3) | public class ChangeNameRequest {
method getNewName (line 6) | public String getNewName() {
method setNewName (line 10) | public void setNewName(String newName) {
FILE: src/main/java/cn/wildfirechat/app/pojo/ChangePasswordRequest.java
class ChangePasswordRequest (line 3) | public class ChangePasswordRequest {
method getOldPassword (line 8) | public String getOldPassword() {
method setOldPassword (line 12) | public void setOldPassword(String oldPassword) {
method getNewPassword (line 16) | public String getNewPassword() {
method setNewPassword (line 20) | public void setNewPassword(String newPassword) {
method getSlideVerifyToken (line 24) | public String getSlideVerifyToken() {
method setSlideVerifyToken (line 28) | public void setSlideVerifyToken(String slideVerifyToken) {
FILE: src/main/java/cn/wildfirechat/app/pojo/ComplainRequest.java
class ComplainRequest (line 3) | public class ComplainRequest {
FILE: src/main/java/cn/wildfirechat/app/pojo/ConferenceInfo.java
class ConferenceInfo (line 5) | public class ConferenceInfo {
FILE: src/main/java/cn/wildfirechat/app/pojo/ConferenceInfoRequest.java
class ConferenceInfoRequest (line 3) | public class ConferenceInfoRequest {
FILE: src/main/java/cn/wildfirechat/app/pojo/ConferenceQuotaResponse.java
class ConferenceQuotaResponse (line 6) | public class ConferenceQuotaResponse {
method ConferenceQuotaResponse (line 23) | public ConferenceQuotaResponse() {
method getTotalQuota (line 26) | public int getTotalQuota() {
method setTotalQuota (line 30) | public void setTotalQuota(int totalQuota) {
method getUsedMinutes (line 34) | public int getUsedMinutes() {
method setUsedMinutes (line 38) | public void setUsedMinutes(int usedMinutes) {
method getRemainingMinutes (line 42) | public int getRemainingMinutes() {
method setRemainingMinutes (line 46) | public void setRemainingMinutes(int remainingMinutes) {
method isUnlimited (line 50) | public boolean isUnlimited() {
method setUnlimited (line 54) | public void setUnlimited(boolean unlimited) {
method getYearMonth (line 58) | public String getYearMonth() {
method setYearMonth (line 62) | public void setYearMonth(String yearMonth) {
FILE: src/main/java/cn/wildfirechat/app/pojo/ConfirmSessionRequest.java
class ConfirmSessionRequest (line 3) | public class ConfirmSessionRequest {
method getToken (line 9) | public String getToken() {
method setToken (line 13) | public void setToken(String token) {
method getUser_id (line 17) | public String getUser_id() {
method setUser_id (line 21) | public void setUser_id(String user_id) {
method getQuick_login (line 25) | public int getQuick_login() {
method setQuick_login (line 29) | public void setQuick_login(int quick_login) {
FILE: src/main/java/cn/wildfirechat/app/pojo/CreateSessionRequest.java
class CreateSessionRequest (line 3) | public class CreateSessionRequest {
method getPlatform (line 12) | public int getPlatform() {
method setPlatform (line 16) | public void setPlatform(int platform) {
method getToken (line 20) | public String getToken() {
method setToken (line 24) | public void setToken(String token) {
method getDevice_name (line 28) | public String getDevice_name() {
method setDevice_name (line 32) | public void setDevice_name(String device_name) {
method getClientId (line 36) | public String getClientId() {
method setClientId (line 40) | public void setClientId(String clientId) {
method getFlag (line 44) | public int getFlag() {
method setFlag (line 48) | public void setFlag(int flag) {
method getUserId (line 52) | public String getUserId() {
FILE: src/main/java/cn/wildfirechat/app/pojo/DestroyRequest.java
class DestroyRequest (line 3) | public class DestroyRequest {
method getCode (line 6) | public String getCode() {
method setCode (line 10) | public void setCode(String code) {
FILE: src/main/java/cn/wildfirechat/app/pojo/GroupAnnouncementPojo.java
class GroupAnnouncementPojo (line 3) | public class GroupAnnouncementPojo {
FILE: src/main/java/cn/wildfirechat/app/pojo/GroupIdPojo.java
class GroupIdPojo (line 3) | public class GroupIdPojo {
FILE: src/main/java/cn/wildfirechat/app/pojo/LoadFavoriteRequest.java
class LoadFavoriteRequest (line 3) | public class LoadFavoriteRequest {
FILE: src/main/java/cn/wildfirechat/app/pojo/LoadFavoriteResponse.java
class LoadFavoriteResponse (line 7) | public class LoadFavoriteResponse {
FILE: src/main/java/cn/wildfirechat/app/pojo/LoginResponse.java
class LoginResponse (line 3) | public class LoginResponse {
method getUserId (line 11) | public String getUserId() {
method setUserId (line 15) | public void setUserId(String userId) {
method getToken (line 19) | public String getToken() {
method setToken (line 23) | public void setToken(String token) {
method isRegister (line 27) | public boolean isRegister() {
method setRegister (line 31) | public void setRegister(boolean register) {
method getUserName (line 35) | public String getUserName() {
method setUserName (line 39) | public void setUserName(String userName) {
method getPortrait (line 43) | public String getPortrait() {
method setPortrait (line 47) | public void setPortrait(String portrait) {
method getResetCode (line 51) | public String getResetCode() {
method setResetCode (line 55) | public void setResetCode(String resetCode) {
FILE: src/main/java/cn/wildfirechat/app/pojo/PhoneCodeLoginRequest.java
class PhoneCodeLoginRequest (line 3) | public class PhoneCodeLoginRequest {
method getClientId (line 9) | public String getClientId() {
method setClientId (line 13) | public void setClientId(String clientId) {
method getMobile (line 17) | public String getMobile() {
method setMobile (line 21) | public void setMobile(String mobile) {
method getCode (line 25) | public String getCode() {
method getPlatform (line 29) | public Integer getPlatform() {
method setPlatform (line 33) | public void setPlatform(Integer platform) {
method setCode (line 37) | public void setCode(String code) {
FILE: src/main/java/cn/wildfirechat/app/pojo/PhoneCodeLoginRequestWithSlideVerify.java
class PhoneCodeLoginRequestWithSlideVerify (line 3) | public class PhoneCodeLoginRequestWithSlideVerify {
method getMobile (line 10) | public String getMobile() {
method setMobile (line 14) | public void setMobile(String mobile) {
method getCode (line 18) | public String getCode() {
method setCode (line 22) | public void setCode(String code) {
method getClientId (line 26) | public String getClientId() {
method setClientId (line 30) | public void setClientId(String clientId) {
method getPlatform (line 34) | public Integer getPlatform() {
method setPlatform (line 38) | public void setPlatform(Integer platform) {
method getSlideVerifyToken (line 42) | public String getSlideVerifyToken() {
method setSlideVerifyToken (line 46) | public void setSlideVerifyToken(String slideVerifyToken) {
FILE: src/main/java/cn/wildfirechat/app/pojo/RecordingRequest.java
class RecordingRequest (line 3) | public class RecordingRequest {
FILE: src/main/java/cn/wildfirechat/app/pojo/ResetPasswordRequest.java
class ResetPasswordRequest (line 3) | public class ResetPasswordRequest {
method getMobile (line 8) | public String getMobile() {
method setMobile (line 12) | public void setMobile(String mobile) {
method getResetCode (line 16) | public String getResetCode() {
method setResetCode (line 20) | public void setResetCode(String resetCode) {
method getNewPassword (line 24) | public String getNewPassword() {
method setNewPassword (line 28) | public void setNewPassword(String newPassword) {
FILE: src/main/java/cn/wildfirechat/app/pojo/SendCodeRequest.java
class SendCodeRequest (line 3) | public class SendCodeRequest {
method getMobile (line 7) | public String getMobile() {
method setMobile (line 11) | public void setMobile(String mobile) {
method getSlideVerifyToken (line 15) | public String getSlideVerifyToken() {
method setSlideVerifyToken (line 19) | public void setSlideVerifyToken(String slideVerifyToken) {
FILE: src/main/java/cn/wildfirechat/app/pojo/SendCodeRequestWithSlideVerify.java
class SendCodeRequestWithSlideVerify (line 3) | public class SendCodeRequestWithSlideVerify {
method getMobile (line 7) | public String getMobile() {
method setMobile (line 11) | public void setMobile(String mobile) {
method getSlideVerifyToken (line 15) | public String getSlideVerifyToken() {
method setSlideVerifyToken (line 19) | public void setSlideVerifyToken(String slideVerifyToken) {
FILE: src/main/java/cn/wildfirechat/app/pojo/SendDestroyCodeRequest.java
class SendDestroyCodeRequest (line 3) | public class SendDestroyCodeRequest {
method getSlideVerifyToken (line 6) | public String getSlideVerifyToken() {
method setSlideVerifyToken (line 10) | public void setSlideVerifyToken(String slideVerifyToken) {
FILE: src/main/java/cn/wildfirechat/app/pojo/SendMessageRequest.java
class SendMessageRequest (line 5) | public class SendMessageRequest {
FILE: src/main/java/cn/wildfirechat/app/pojo/SessionOutput.java
class SessionOutput (line 3) | public class SessionOutput {
method getUserId (line 11) | public String getUserId() {
method setUserId (line 15) | public void setUserId(String userId) {
method SessionOutput (line 19) | public SessionOutput() {
method SessionOutput (line 22) | public SessionOutput(String userId, String token, int status, long exp...
method getToken (line 31) | public String getToken() {
method setToken (line 35) | public void setToken(String token) {
method getStatus (line 39) | public int getStatus() {
method setStatus (line 43) | public void setStatus(int status) {
method getExpired (line 47) | public long getExpired() {
method setExpired (line 51) | public void setExpired(long expired) {
method getDevice_name (line 55) | public String getDevice_name() {
method setDevice_name (line 59) | public void setDevice_name(String device_name) {
method getPlatform (line 63) | public int getPlatform() {
method setPlatform (line 67) | public void setPlatform(int platform) {
FILE: src/main/java/cn/wildfirechat/app/pojo/SlideVerifyRequest.java
class SlideVerifyRequest (line 3) | public class SlideVerifyRequest {
method getToken (line 7) | public String getToken() {
method setToken (line 11) | public void setToken(String token) {
method getX (line 15) | public int getX() {
method setX (line 19) | public void setX(int x) {
FILE: src/main/java/cn/wildfirechat/app/pojo/SlideVerifyResponse.java
class SlideVerifyResponse (line 3) | public class SlideVerifyResponse {
method getToken (line 9) | public String getToken() {
method setToken (line 13) | public void setToken(String token) {
method getBackgroundImage (line 17) | public String getBackgroundImage() {
method setBackgroundImage (line 21) | public void setBackgroundImage(String backgroundImage) {
method getSliderImage (line 25) | public String getSliderImage() {
method setSliderImage (line 29) | public void setSliderImage(String sliderImage) {
method getY (line 33) | public int getY() {
method setY (line 37) | public void setY(int y) {
FILE: src/main/java/cn/wildfirechat/app/pojo/UploadFileResponse.java
class UploadFileResponse (line 3) | public class UploadFileResponse {
FILE: src/main/java/cn/wildfirechat/app/pojo/UserIdNamePortraitPojo.java
class UserIdNamePortraitPojo (line 3) | public class UserIdNamePortraitPojo {
method UserIdNamePortraitPojo (line 8) | public UserIdNamePortraitPojo() {
method UserIdNamePortraitPojo (line 11) | public UserIdNamePortraitPojo(String userId, String name, String portr...
FILE: src/main/java/cn/wildfirechat/app/pojo/UserIdPojo.java
class UserIdPojo (line 3) | public class UserIdPojo {
FILE: src/main/java/cn/wildfirechat/app/pojo/UserPasswordLoginRequest.java
class UserPasswordLoginRequest (line 3) | public class UserPasswordLoginRequest {
method getClientId (line 9) | public String getClientId() {
method setClientId (line 13) | public void setClientId(String clientId) {
method getMobile (line 17) | public String getMobile() {
method setMobile (line 21) | public void setMobile(String mobile) {
method getPassword (line 25) | public String getPassword() {
method getPlatform (line 29) | public Integer getPlatform() {
method setPlatform (line 33) | public void setPlatform(Integer platform) {
method setPassword (line 37) | public void setPassword(String password) {
FILE: src/main/java/cn/wildfirechat/app/pojo/UserPasswordLoginRequestWithSlideVerify.java
class UserPasswordLoginRequestWithSlideVerify (line 3) | public class UserPasswordLoginRequestWithSlideVerify {
method getMobile (line 10) | public String getMobile() {
method setMobile (line 14) | public void setMobile(String mobile) {
method getPassword (line 18) | public String getPassword() {
method setPassword (line 22) | public void setPassword(String password) {
method getClientId (line 26) | public String getClientId() {
method setClientId (line 30) | public void setClientId(String clientId) {
method getPlatform (line 34) | public Integer getPlatform() {
method setPlatform (line 38) | public void setPlatform(Integer platform) {
method getSlideVerifyToken (line 42) | public String getSlideVerifyToken() {
method setSlideVerifyToken (line 46) | public void setSlideVerifyToken(String slideVerifyToken) {
FILE: src/main/java/cn/wildfirechat/app/shiro/AuthDataSource.java
class AuthDataSource (line 25) | @Service
method insertRecord (line 37) | public RestResult.RestCode insertRecord(String mobile, String code) {
method clearRecode (line 63) | public void clearRecode(String mobile) {
method verifyCode (line 70) | public RestResult.RestCode verifyCode(String mobile, String code) {
method createSession (line 90) | public PCSession createSession(String userId, String clientId, String ...
method getSession (line 109) | public PCSession getSession(String token, boolean clear) {
method saveSession (line 117) | public void saveSession(PCSession session) {
method scanPc (line 121) | public RestResult scanPc(String userId, String token) {
method confirmPc (line 142) | public RestResult confirmPc(String userId, String token) {
method cancelPc (line 162) | public RestResult cancelPc(String token) {
method checkPcSession (line 173) | public RestResult.RestCode checkPcSession(String token) {
method getUserId (line 193) | public String getUserId(String token, boolean clear) {
FILE: src/main/java/cn/wildfirechat/app/shiro/CorsFilter.java
class CorsFilter (line 13) | @Component
method doFilterInternal (line 15) | @Override
FILE: src/main/java/cn/wildfirechat/app/shiro/DBSessionDao.java
class DBSessionDao (line 19) | @Component
method create (line 26) | @Override
method readSession (line 33) | @Override
method update (line 44) | @Override
method delete (line 52) | @Override
method getActiveSessions (line 57) | @Override
method sessionToByte (line 63) | private byte[] sessionToByte(Session session){
method byteToSession (line 77) | private Session byteToSession(byte[] bytes){
FILE: src/main/java/cn/wildfirechat/app/shiro/JsonAuthLoginFilter.java
class JsonAuthLoginFilter (line 16) | public class JsonAuthLoginFilter extends AccessControlFilter {
method isAccessAllowed (line 18) | @Override
method onAccessDenied (line 41) | @Override
FILE: src/main/java/cn/wildfirechat/app/shiro/LdapMatcher.java
class LdapMatcher (line 19) | @Service
method doCredentialsMatch (line 22) | @Override
method authenticate (line 46) | public static boolean authenticate(String ldapUrl, String dn, String p...
method main (line 69) | public static void main(String[] args) {
FILE: src/main/java/cn/wildfirechat/app/shiro/LdapRealm.java
class LdapRealm (line 20) | @Service
method initMatcher (line 25) | @PostConstruct
method doGetAuthorizationInfo (line 30) | @Override
method supports (line 40) | @Override
method doGetAuthenticationInfo (line 47) | @Override
FILE: src/main/java/cn/wildfirechat/app/shiro/LdapToken.java
class LdapToken (line 5) | public class LdapToken implements AuthenticationToken {
method LdapToken (line 10) | public LdapToken(String phone, String password, String ldapUrl) {
method getPrincipal (line 16) | @Override
method getCredentials (line 21) | @Override
method getLdapUrl (line 26) | public String getLdapUrl() {
FILE: src/main/java/cn/wildfirechat/app/shiro/PhoneCodeRealm.java
class PhoneCodeRealm (line 15) | @Service
method initRealm (line 21) | @PostConstruct
method doGetAuthorizationInfo (line 26) | @Override
method doGetAuthenticationInfo (line 36) | @Override
FILE: src/main/java/cn/wildfirechat/app/shiro/PhoneCodeToken.java
class PhoneCodeToken (line 5) | public class PhoneCodeToken implements AuthenticationToken {
method PhoneCodeToken (line 9) | public PhoneCodeToken(String phone, String code) {
method getPrincipal (line 14) | @Override
method getCredentials (line 19) | @Override
FILE: src/main/java/cn/wildfirechat/app/shiro/ScanCodeRealm.java
class ScanCodeRealm (line 15) | @Service
method initMatcher (line 24) | @PostConstruct
method doGetAuthorizationInfo (line 29) | @Override
method supports (line 39) | @Override
method doGetAuthenticationInfo (line 46) | @Override
FILE: src/main/java/cn/wildfirechat/app/shiro/ShiroConfig.java
class ShiroConfig (line 21) | @Configuration
method shiroFilter (line 42) | @Bean(name = "shiroFilter")
method securityManager (line 90) | @Bean
FILE: src/main/java/cn/wildfirechat/app/shiro/ShiroSessionManager.java
class ShiroSessionManager (line 13) | public class ShiroSessionManager extends DefaultWebSessionManager {
method ShiroSessionManager (line 19) | public ShiroSessionManager(){
method getSessionId (line 23) | @Override
FILE: src/main/java/cn/wildfirechat/app/shiro/TokenAuthenticationToken.java
class TokenAuthenticationToken (line 5) | public class TokenAuthenticationToken implements AuthenticationToken {
method TokenAuthenticationToken (line 8) | public TokenAuthenticationToken(String token) {
method getToken (line 12) | public String getToken() {
method setToken (line 16) | public void setToken(String token) {
method getPrincipal (line 20) | @Override
method getCredentials (line 25) | @Override
FILE: src/main/java/cn/wildfirechat/app/shiro/TokenMatcher.java
class TokenMatcher (line 14) | @Service
method doCredentialsMatch (line 19) | @Override
method main (line 31) | public static void main(String[] args) {
FILE: src/main/java/cn/wildfirechat/app/shiro/UserPasswordRealm.java
class UserPasswordRealm (line 24) | @Service
method initMatcher (line 29) | @PostConstruct
method doGetAuthorizationInfo (line 35) | @Override
method doGetAuthenticationInfo (line 45) | @Override
FILE: src/main/java/cn/wildfirechat/app/slide/SlideVerifyCleanupService.java
class SlideVerifyCleanupService (line 8) | @Service
method cleanupExpired (line 14) | @Transactional
FILE: src/main/java/cn/wildfirechat/app/slide/SlideVerifyService.java
class SlideVerifyService (line 21) | @Service
method generateSlideVerify (line 43) | public Map<String, Object> generateSlideVerify() {
method verifySlide (line 83) | public boolean verifySlide(String token, int userX) {
method isVerified (line 121) | public boolean isVerified(String token) {
method cleanExpiredData (line 140) | public void cleanExpiredData() {
method generateBackgroundWithHole (line 148) | private String generateBackgroundWithHole(int x, int y) throws IOExcep...
method generateSlider (line 175) | private String generateSlider(int x, int y) throws IOException {
method drawHole (line 194) | private void drawHole(Graphics2D g, int x, int y) {
method drawSliderShape (line 210) | private void drawSliderShape(Graphics2D g) {
method addRandomShapes (line 231) | private void addRandomShapes(Graphics2D g) {
method getRandomColor (line 256) | private Color getRandomColor() {
method imageToBase64 (line 267) | private String imageToBase64(BufferedImage image) throws IOException {
FILE: src/main/java/cn/wildfirechat/app/sms/AliyunSMSConfig.java
class AliyunSMSConfig (line 7) | @Configuration
method getAccessKeyId (line 16) | public String getAccessKeyId() {
method setAccessKeyId (line 20) | public void setAccessKeyId(String accessKeyId) {
method getAccessSecret (line 24) | public String getAccessSecret() {
method setAccessSecret (line 28) | public void setAccessSecret(String accessSecret) {
method getSignName (line 32) | public String getSignName() {
method setSignName (line 36) | public void setSignName(String signName) {
method getTemplateCode (line 40) | public String getTemplateCode() {
method setTemplateCode (line 44) | public void setTemplateCode(String templateCode) {
FILE: src/main/java/cn/wildfirechat/app/sms/SmsService.java
type SmsService (line 6) | public interface SmsService {
method sendCode (line 7) | RestResult.RestCode sendCode(String mobile, String code);
FILE: src/main/java/cn/wildfirechat/app/sms/SmsServiceImpl.java
class SmsServiceImpl (line 28) | @Service
class AliyunCommonResponse (line 33) | private static class AliyunCommonResponse {
method sendCode (line 47) | @Override
method sendTencentCode (line 58) | private RestResult.RestCode sendTencentCode(String mobile, String code) {
method sendAliyunCode (line 152) | private RestResult.RestCode sendAliyunCode(String mobile, String code) {
FILE: src/main/java/cn/wildfirechat/app/sms/TencentSMSConfig.java
class TencentSMSConfig (line 7) | @Configuration
method getSecretId (line 17) | public String getSecretId() {
method setSecretId (line 21) | public void setSecretId(String secretId) {
method getSecretKey (line 25) | public String getSecretKey() {
method setSecretKey (line 29) | public void setSecretKey(String secretKey) {
method getAppId (line 33) | public String getAppId() {
method setAppId (line 37) | public void setAppId(String appId) {
method getTemplateId (line 41) | public String getTemplateId() {
method setTemplateId (line 45) | public void setTemplateId(String templateId) {
method getSign (line 49) | public String getSign() {
method setSign (line 53) | public void setSign(String sign) {
FILE: src/main/java/cn/wildfirechat/app/tools/LdapUser.java
class LdapUser (line 3) | public class LdapUser {
method LdapUser (line 5) | public LdapUser(String uid, String cn, String mail, String phone, Stri...
method toString (line 8) | @Override public String toString() {
FILE: src/main/java/cn/wildfirechat/app/tools/LdapUtil.java
class LdapUtil (line 13) | public class LdapUtil {
method findUserByPhone (line 16) | public static List<LdapUser> findUserByPhone(String phone, String ldap...
method encodeSshaPassword (line 59) | private static String encodeSshaPassword(String password) {
method getAttr (line 84) | private static String getAttr(Attributes attrs, String name) throws Na...
FILE: src/main/java/cn/wildfirechat/app/tools/NumericIdGenerator.java
class NumericIdGenerator (line 7) | public class NumericIdGenerator {
method getId (line 8) | public static String getId(List<Integer> firstNumber, List<Integer> fi...
method main (line 38) | public static void main(String[] args) {
FILE: src/main/java/cn/wildfirechat/app/tools/OrderedIdUserNameGenerator.java
class OrderedIdUserNameGenerator (line 8) | @Component
method getUserName (line 13) | @Override
FILE: src/main/java/cn/wildfirechat/app/tools/PhoneNumberUserNameGenerator.java
class PhoneNumberUserNameGenerator (line 5) | @Component
method getUserName (line 7) | @Override
FILE: src/main/java/cn/wildfirechat/app/tools/RateLimiter.java
class RateLimiter (line 12) | public class RateLimiter {
method RateLimiter (line 24) | public RateLimiter() {
method RateLimiter (line 28) | public RateLimiter(int limitTimeSecond, int limitCount) {
method isGranted (line 39) | public boolean isGranted(String userId) {
method cleanUp (line 71) | private void cleanUp(long current) {
method main (line 84) | public static void main(String[] args) throws InterruptedException {
FILE: src/main/java/cn/wildfirechat/app/tools/ShortUUIDGenerator.java
class ShortUUIDGenerator (line 9) | @Component
method getUserName (line 16) | @Override
method getShortUUID (line 21) | public String getShortUUID() {
method main (line 31) | public static void main(String[] args) {
FILE: src/main/java/cn/wildfirechat/app/tools/SpinLock.java
class SpinLock (line 5) | public class SpinLock {
method lock (line 9) | public void lock() {
method unLock (line 17) | public void unLock() {
FILE: src/main/java/cn/wildfirechat/app/tools/UUIDUserNameGenerator.java
class UUIDUserNameGenerator (line 7) | @Component
method getUserName (line 9) | @Override
FILE: src/main/java/cn/wildfirechat/app/tools/UserNameGenerator.java
type UserNameGenerator (line 3) | public interface UserNameGenerator {
method getUserName (line 4) | String getUserName(String phone);
FILE: src/main/java/cn/wildfirechat/app/tools/Utils.java
class Utils (line 9) | public class Utils {
method getRandomCode (line 10) | public static String getRandomCode(int length) {
method isMobile (line 17) | public static boolean isMobile(String mobile) {
method getSafeFileName (line 29) | public static String getSafeFileName(String fileName) {
method main (line 47) | public static void main(String[] args) {
FILE: src/test/java/cn/wildfirechat/app/ApplicationTests.java
class ApplicationTests (line 26) | @RunWith(SpringRunner.class)
method contextLoads (line 30) | @Test
class H2TestConfig (line 36) | @TestConfiguration
method dataSource (line 38) | @Bean
FILE: src/test/java/cn/wildfirechat/app/jpa/AnnouncementTest.java
class AnnouncementTest (line 7) | public class AnnouncementTest {
method testConstructorAndGetters (line 9) | @Test
method testEmptyAnnouncement (line 31) | @Test
method testLongContent (line 43) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/ConferenceEntityTest.java
class ConferenceEntityTest (line 7) | public class ConferenceEntityTest {
method testDefaultValues (line 9) | @Test
method testSettersAndGetters (line 22) | @Test
method testBooleanFlags (line 63) | @Test
method testMaxParticipantsBoundary (line 89) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/FavoriteItemTest.java
class FavoriteItemTest (line 7) | public class FavoriteItemTest {
method testDefaultConstructor (line 9) | @Test
method testSettersAndGetters (line 24) | @Test
method testDifferentTypes (line 61) | @Test
method testLongUrls (line 74) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/PCSessionTest.java
class PCSessionTest (line 7) | public class PCSessionTest {
method testDefaultConstructor (line 9) | @Test
method testSettersAndGetters (line 22) | @Test
method testSessionStatusConstants (line 49) | @Test
method testStatusTransitions (line 58) | @Test
method testPlatformValues (line 80) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/RecordTest.java
class RecordTest (line 7) | public class RecordTest {
method testDefaultConstructor (line 9) | @Test
method testConstructorWithParams (line 20) | @Test
method testIncreaseAndCheckWithinLimit (line 36) | @Test
method testIncreaseAndCheckExceedsLimit (line 50) | @Test
method testReset (line 65) | @Test
method testSettersAndGetters (line 82) | @Test
method testResetAfter24Hours (line 98) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/ShiroSessionTest.java
class ShiroSessionTest (line 9) | public class ShiroSessionTest {
method testDefaultConstructor (line 11) | @Test
method testConstructorWithParams (line 21) | @Test
method testSetSessionData (line 35) | @Test
method testEmptySessionData (line 50) | @Test
method testBinarySessionData (line 60) | @Test
method testLargeSessionData (line 74) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/SlideVerifyRepositoryTest.java
class SlideVerifyRepositoryTest (line 21) | @RunWith(MockitoJUnitRunner.class)
method setUp (line 29) | @Before
method testFindByTokenExists (line 41) | @Test
method testFindByTokenNotExists (line 57) | @Test
method testSave (line 70) | @Test
method testDelete (line 84) | @Test
method testCleanupExpired (line 96) | @Test
method testUpdateVerified (line 109) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/SlideVerifyTest.java
class SlideVerifyTest (line 7) | public class SlideVerifyTest {
method testConstructor (line 9) | @Test
method testSettersAndGetters (line 26) | @Test
method testIsExpiredNotExpired (line 48) | @Test
method testIsExpiredJustExpired (line 61) | @Test
method testIsExpiredLongAgo (line 74) | @Test
method testDefaultConstructor (line 87) | @Test
method testVerifiedStateToggle (line 99) | @Test
method testBoundaryConditions (line 120) | @Test
method testTimestampBoundary (line 130) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/UserConferenceTest.java
class UserConferenceTest (line 7) | public class UserConferenceTest {
method testDefaultConstructor (line 9) | @Test
method testConstructorWithParams (line 19) | @Test
method testSettersAndGetters (line 33) | @Test
method testMultipleUserConferences (line 47) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/UserNameEntryTest.java
class UserNameEntryTest (line 7) | public class UserNameEntryTest {
method testDefaultConstructor (line 9) | @Test
method testSetAndGetId (line 18) | @Test
method testDifferentIds (line 30) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/UserPasswordTest.java
class UserPasswordTest (line 7) | public class UserPasswordTest {
method testDefaultConstructor (line 9) | @Test
method testConstructorWithUserId (line 24) | @Test
method testConstructorWithAllParams (line 38) | @Test
method testSettersAndGetters (line 59) | @Test
method testTryCountIncrementation (line 83) | @Test
FILE: src/test/java/cn/wildfirechat/app/jpa/UserPrivateConferenceIdTest.java
class UserPrivateConferenceIdTest (line 7) | public class UserPrivateConferenceIdTest {
method testDefaultConstructor (line 9) | @Test
method testConstructorWithParams (line 19) | @Test
method testSettersAndGetters (line 33) | @Test
method testUpdateConferenceId (line 47) | @Test
FILE: src/test/java/cn/wildfirechat/app/slide/SlideVerifyCleanupServiceTest.java
class SlideVerifyCleanupServiceTest (line 14) | @RunWith(MockitoJUnitRunner.class)
method setUp (line 22) | @Before
method testCleanupExpired (line 34) | @Test
method testCleanupExpiredNoData (line 47) | @Test
FILE: src/test/java/cn/wildfirechat/app/slide/SlideVerifyServiceTest.java
class SlideVerifyServiceTest (line 19) | @RunWith(MockitoJUnitRunner.class)
method setUp (line 27) | @Before
method testGenerateSlideVerify (line 40) | @Test
method testVerifySlideSuccess (line 63) | @Test
method testVerifySlideFailure (line 81) | @Test
method testVerifySlideTokenNotFound (line 99) | @Test
method testVerifySlideAlreadyVerified (line 114) | @Test
method testIsVerifiedWhenVerified (line 132) | @Test
method testIsVerifiedWhenNotVerified (line 149) | @Test
method testIsVerifiedWhenNotFound (line 166) | @Test
method testIsVerifiedWhenExpired (line 179) | @Test
Condensed preview — 148 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (398K chars).
[
{
"path": ".github/workflows/build.yml",
"chars": 542,
"preview": "# This workflow will build a Java project with Maven\n# For more information see: https://help.github.com/actions/languag"
},
{
"path": ".gitignore",
"chars": 62,
"preview": "target\n.idea\nappdata.mv.db\nnohup.out\nappdata.trace.db\navatar/\n"
},
{
"path": "LICENSE",
"chars": 1640,
"preview": "MIT License\n\nCopyright (c) 2019 wildfirechat\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 5834,
"preview": "## 野火IM解决方案\n\n野火IM是专业级即时通讯和实时音视频整体解决方案,由北京野火无限网络科技有限公司维护和支持。\n\n主要特性有:私有部署安全可靠,性能强大,功能齐全,全平台支持,开源率高,部署运维简单,二次开发友好,方便与第三方系统对"
},
{
"path": "aliyun_sms.md",
"chars": 941,
"preview": "# 阿里云短信功能说明\n\n## 短信对接\n1. 在[这里](https://usercenter.console.aliyun.com/#/manage/ak)申请阿里云***accessKeyId***和***accessSecret**"
},
{
"path": "build_release.sh",
"chars": 457,
"preview": "if [ $# -eq 0 ]; then\n echo \"Usage: sh build_release.sh version\"\n exit -1\nfi\necho \"build release $1\"\n\nmvn clean pa"
},
{
"path": "config/aliyun_sms.properties",
"chars": 152,
"preview": "alisms.accessKeyId=MTAI82gOTQQTuKtW\nalisms.accessSecret=4p7HlgMTOQWHsX82IICabcea556677\nalisms.signName=\\u91CE\\u706BIM\nal"
},
{
"path": "config/application.properties",
"chars": 6010,
"preview": "spring.message.encoding=UTF-8\nserver.port=8888\n\n## 给服务添加统一的路径前缀,方便代理统一转换。\n## 注意,如果这里改了,在客户端配置文件中修改APP_SERVER_ADDRESS,加上这"
},
{
"path": "config/im.properties",
"chars": 1000,
"preview": "im.admin_url=http://localhost:18080\n#需要和im server里面配置的http.admin.secret_key一致\nim.admin_secret=123456\n\n#发送通知消息的管理员用户ID\nim"
},
{
"path": "config/tencent_sms.properties",
"chars": 159,
"preview": "sms.secretId=AKIsaepMSEL91dsMESAUMO2smphIdgSxB8oD\nsms.secretKey=91dADocdksuw23AEFCD78lsdudf35ta0\nsms.appId=1432000001\nsm"
},
{
"path": "deb/control/control",
"chars": 260,
"preview": "Package: app-server\nVersion: [[version]]\nSection: misc\nPriority: optional\nArchitecture: all\nMaintainer: Wildfirechat <su"
},
{
"path": "deb/control/postinst",
"chars": 87,
"preview": "mv -f /opt/app-server/app-*.jar /opt/app-server/app-server.jar\nsystemctl daemon-reload\n"
},
{
"path": "deb/control/postrm",
"chars": 96,
"preview": "rm -rf /opt/app-server\nrm -rf /usr/lib/systemd/system/app-server.service\nsystemctl daemon-reload"
},
{
"path": "docker/Dockerfile",
"chars": 308,
"preview": "FROM openjdk:8-jre-alpine\n\nCOPY ../target/app-*.jar /opt/app-server/app.jar\nCOPY ../config /opt/app-server/config\n\nWORK"
},
{
"path": "docker/README.md",
"chars": 500,
"preview": "# 野火应用服务docker使用说明\n\n## 编译镜像\n首先需要先编译应用服务,使用下面命令编译\n```\nmvn clean package\n```\n\n然后进入到docker目录编译镜像\n```\nsudo docker build -t a"
},
{
"path": "mvnw",
"chars": 6468,
"preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
},
{
"path": "mvnw.cmd",
"chars": 4994,
"preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
},
{
"path": "nginx/appserver.conf",
"chars": 1250,
"preview": "server {\n listen 80;\n server_name apptest.wildfirechat.cn;\n rewrite ^(.*)$ https://apptest.wildfir"
},
{
"path": "pom.xml",
"chars": 11451,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
},
{
"path": "release_note.md",
"chars": 3400,
"preview": "# 当前版本更新记录\n0.70 Release note:\n1. 解决上传文件可能存在的漏洞\n\n# 升级注意事项\n1. 如果从0.40以前版本升级上来,需要注意升级兼容有问题 请参考Readme中的兼容问题说明\n\n2. 如果从0.42以前版"
},
{
"path": "src/main/java/cn/wildfirechat/app/AppController.java",
"chars": 11193,
"preview": "package cn.wildfirechat.app;\n\nimport cn.wildfirechat.app.jpa.FavoriteItem;\nimport cn.wildfirechat.app.pojo.*;\nimport cn."
},
{
"path": "src/main/java/cn/wildfirechat/app/Application.java",
"chars": 1749,
"preview": "package cn.wildfirechat.app;\n\nimport cn.wildfirechat.app.jpa.PCSessionRepository;\nimport cn.wildfirechat.app.slide.Slide"
},
{
"path": "src/main/java/cn/wildfirechat/app/AudioController.java",
"chars": 3692,
"preview": "package cn.wildfirechat.app;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.core"
},
{
"path": "src/main/java/cn/wildfirechat/app/ForbiddenException.java",
"chars": 265,
"preview": "package cn.wildfirechat.app;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.web.bind.annotation"
},
{
"path": "src/main/java/cn/wildfirechat/app/IMCallbackController.java",
"chars": 6927,
"preview": "package cn.wildfirechat.app;\n\nimport cn.wildfirechat.pojos.*;\nimport cn.wildfirechat.pojos.moments.CommentPojo;\nimport c"
},
{
"path": "src/main/java/cn/wildfirechat/app/IMConfig.java",
"chars": 3814,
"preview": "package cn.wildfirechat.app;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.spr"
},
{
"path": "src/main/java/cn/wildfirechat/app/IMExceptionEventController.java",
"chars": 3360,
"preview": "package cn.wildfirechat.app;\n\nimport cn.wildfirechat.common.IMExceptionEvent;\nimport org.slf4j.Logger;\nimport org.slf4j."
},
{
"path": "src/main/java/cn/wildfirechat/app/RestResult.java",
"chars": 2627,
"preview": "package cn.wildfirechat.app;\n\npublic class RestResult {\n public enum RestCode {\n SUCCESS(0, \"success\"),\n "
},
{
"path": "src/main/java/cn/wildfirechat/app/Service.java",
"chars": 2204,
"preview": "package cn.wildfirechat.app;\n\n\nimport cn.wildfirechat.app.jpa.FavoriteItem;\nimport cn.wildfirechat.app.pojo.*;\nimport cn"
},
{
"path": "src/main/java/cn/wildfirechat/app/ServiceImpl.java",
"chars": 77515,
"preview": "package cn.wildfirechat.app;\n\n\nimport cn.wildfirechat.app.jpa.*;\nimport cn.wildfirechat.app.pojo.*;\nimport cn.wildfirech"
},
{
"path": "src/main/java/cn/wildfirechat/app/conference/ConferenceCleanupService.java",
"chars": 2921,
"preview": "package cn.wildfirechat.app.conference;\n\nimport cn.wildfirechat.app.jpa.ConferenceEntity;\nimport cn.wildfirechat.app.jpa"
},
{
"path": "src/main/java/cn/wildfirechat/app/conference/ConferenceController.java",
"chars": 3574,
"preview": "package cn.wildfirechat.app.conference;\n\nimport cn.wildfirechat.app.Service;\nimport cn.wildfirechat.app.pojo.*;\nimport o"
},
{
"path": "src/main/java/cn/wildfirechat/app/conference/ConferenceService.java",
"chars": 1128,
"preview": "package cn.wildfirechat.app.conference;\n\n\nimport cn.wildfirechat.app.RestResult;\nimport cn.wildfirechat.app.jpa.Favorite"
},
{
"path": "src/main/java/cn/wildfirechat/app/conference/ConferenceServiceImpl.java",
"chars": 26175,
"preview": "package cn.wildfirechat.app.conference;\n\n\nimport cn.wildfirechat.app.IMConfig;\nimport cn.wildfirechat.app.RestResult;\nim"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/Announcement.java",
"chars": 810,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.*;\n\n@Entity\n@Table(name = \"text\")\npublic class Announcement {"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/AnnouncementRepository.java",
"chars": 392,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.repository.PagingAndSortingRepository;\nimport org.spri"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/ConferenceEntity.java",
"chars": 2558,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.*;\n\n@Entity\n@Table(name = \"conference\")\npublic class Conferen"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/ConferenceEntityRepository.java",
"chars": 671,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.jpa.repository.Query;\nimport org.springframework.data."
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/ConferenceRecord.java",
"chars": 3172,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.*;\nimport java.util.Date;\n\n/**\n * 会议记录表\n * 记录每一次会议的详细信息,用于计费和"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/ConferenceRecordRepository.java",
"chars": 807,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.jpa.repository.Modifying;\nimport org.springframework.d"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/FavoriteItem.java",
"chars": 1058,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.annotation.Nullable;\nimport javax.persistence.*;\n\n@Entity\n@Table(name = \""
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/FavoriteRepository.java",
"chars": 556,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.jpa.repository.Query;\nimport org.springframework.data."
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/PCSession.java",
"chars": 2200,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport cn.wildfirechat.app.pojo.SessionOutput;\n\nimport javax.persistence.Column;\nimpor"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/PCSessionRepository.java",
"chars": 384,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.jpa.repository.Modifying;\nimport org.springframework.d"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/Record.java",
"chars": 1686,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.Column;\nimport javax.persistence.Entity;\nimport javax.persist"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/RecordRepository.java",
"chars": 171,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.repository.CrudRepository;\n\npublic interface RecordRep"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/ShiroSession.java",
"chars": 795,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.hibernate.annotations.Type;\n\nimport javax.persistence.*;\n\n@Entity\n@Table(na"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/ShiroSessionRepository.java",
"chars": 183,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.repository.CrudRepository;\n\npublic interface ShiroSess"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/SlideVerify.java",
"chars": 1241,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.*;\nimport java.sql.Timestamp;\n\n@Entity\n@Table(name = \"slide_v"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/SlideVerifyRepository.java",
"chars": 630,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.jpa.repository.Modifying;\nimport org.springframework.d"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserConference.java",
"chars": 967,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.*;\n\nimport java.util.List;\n\nimport static javax.persistence.C"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserConferenceQuota.java",
"chars": 1580,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.*;\nimport java.util.Date;\n\n/**\n * 用户会议额度表\n * 存储用户的会议分钟数配额(不分月"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserConferenceQuotaRepository.java",
"chars": 415,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.repository.CrudRepository;\nimport org.springframework."
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserConferenceRepository.java",
"chars": 1351,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport cn.wildfirechat.app.model.ConferenceDTO;\nimport org.springframework.data.jpa.re"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserNameEntry.java",
"chars": 335,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.*;\n\n@Entity\n@Table(name = \"t_user_name\")\npublic class UserNam"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserNameRepository.java",
"chars": 280,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.repository.CrudRepository;\nimport org.springframework."
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserPassword.java",
"chars": 1962,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.Column;\nimport javax.persistence.Entity;\nimport javax.persist"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserPasswordRepository.java",
"chars": 359,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.repository.CrudRepository;\nimport org.springframework."
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserPrivateConferenceId.java",
"chars": 784,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.Column;\nimport javax.persistence.Entity;\nimport javax.persist"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserPrivateConferenceIdRepository.java",
"chars": 334,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.repository.PagingAndSortingRepository;\nimport org.spri"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserQuotaUsage.java",
"chars": 2064,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport javax.persistence.*;\nimport java.util.Date;\n\n/**\n * 用户额度使用表\n * 记录每个月用户实际使用的会议分钟"
},
{
"path": "src/main/java/cn/wildfirechat/app/jpa/UserQuotaUsageRepository.java",
"chars": 780,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.springframework.data.jpa.repository.Modifying;\nimport org.springframework.d"
},
{
"path": "src/main/java/cn/wildfirechat/app/model/ConferenceDTO.java",
"chars": 482,
"preview": "package cn.wildfirechat.app.model;\n\npublic interface ConferenceDTO {\n String getId();\n String getConference_title("
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/CancelSessionRequest.java",
"chars": 235,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class CancelSessionRequest {\n private String token;\n\n public String getT"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/ChangeNameRequest.java",
"chars": 246,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class ChangeNameRequest {\n private String newName;\n\n public String getNe"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/ChangePasswordRequest.java",
"chars": 721,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class ChangePasswordRequest {\n private String oldPassword;\n private Stri"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/ComplainRequest.java",
"chars": 92,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class ComplainRequest {\n public String text;\n}\n"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/ConferenceInfo.java",
"chars": 538,
"preview": "package cn.wildfirechat.app.pojo;\n\nimport java.util.List;\n\npublic class ConferenceInfo {\n public String conferenceId;"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/ConferenceInfoRequest.java",
"chars": 134,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class ConferenceInfoRequest {\n public String conferenceId;\n public Strin"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/ConferenceQuotaResponse.java",
"chars": 1265,
"preview": "package cn.wildfirechat.app.pojo;\n\n/**\n * 会议额度查询响应\n */\npublic class ConferenceQuotaResponse {\n \n // 用户月度额度(分钟)\n "
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/ConfirmSessionRequest.java",
"chars": 702,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class ConfirmSessionRequest {\n private String token;\n private String use"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/CreateSessionRequest.java",
"chars": 1077,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class CreateSessionRequest {\n private String token;\n private String devi"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/DestroyRequest.java",
"chars": 222,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class DestroyRequest {\n private String code;\n\n public String getCode() {"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/GroupAnnouncementPojo.java",
"chars": 178,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class GroupAnnouncementPojo {\n public String groupId;\n public String aut"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/GroupIdPojo.java",
"chars": 91,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class GroupIdPojo {\n public String groupId;\n}\n"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/LoadFavoriteRequest.java",
"chars": 114,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class LoadFavoriteRequest {\n public long id;\n public int count;\n}\n"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/LoadFavoriteResponse.java",
"chars": 208,
"preview": "package cn.wildfirechat.app.pojo;\n\nimport cn.wildfirechat.app.jpa.FavoriteItem;\n\nimport java.util.List;\n\npublic class Lo"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/LoginResponse.java",
"chars": 1138,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class LoginResponse {\n private String userId;\n private String token;\n "
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/PhoneCodeLoginRequest.java",
"chars": 767,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class PhoneCodeLoginRequest {\n private String mobile;\n private String co"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/PhoneCodeLoginRequestWithSlideVerify.java",
"chars": 1021,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class PhoneCodeLoginRequestWithSlideVerify {\n private String mobile;\n pr"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/RecordingRequest.java",
"chars": 99,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class RecordingRequest {\n public boolean recording;\n}\n"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/ResetPasswordRequest.java",
"chars": 636,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class ResetPasswordRequest {\n private String mobile;\n private String res"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/SendCodeRequest.java",
"chars": 476,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class SendCodeRequest {\n private String mobile;\n private String slideVer"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/SendCodeRequestWithSlideVerify.java",
"chars": 491,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class SendCodeRequestWithSlideVerify {\n private String mobile;\n private "
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/SendDestroyCodeRequest.java",
"chars": 314,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class SendDestroyCodeRequest {\n private String slideVerifyToken;\n\n publi"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/SendMessageRequest.java",
"chars": 557,
"preview": "package cn.wildfirechat.app.pojo;\n\nimport java.util.List;\n\npublic class SendMessageRequest {\n public int type;\n pu"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/SessionOutput.java",
"chars": 1458,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class SessionOutput {\n private String token;\n private int status;\n pr"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/SlideVerifyRequest.java",
"chars": 371,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class SlideVerifyRequest {\n private String token;\n private int x; // 滑动块"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/SlideVerifyResponse.java",
"chars": 866,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class SlideVerifyResponse {\n private String token;\n private String backg"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/UploadFileResponse.java",
"chars": 94,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class UploadFileResponse {\n public String url;\n}\n"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/UserIdNamePortraitPojo.java",
"chars": 376,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class UserIdNamePortraitPojo {\n public String userId;\n public String nam"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/UserIdPojo.java",
"chars": 89,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class UserIdPojo {\n public String userId;\n}\n"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/UserPasswordLoginRequest.java",
"chars": 798,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class UserPasswordLoginRequest {\n private String mobile;\n private String"
},
{
"path": "src/main/java/cn/wildfirechat/app/pojo/UserPasswordLoginRequestWithSlideVerify.java",
"chars": 1052,
"preview": "package cn.wildfirechat.app.pojo;\n\npublic class UserPasswordLoginRequestWithSlideVerify {\n private String mobile;\n "
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/AuthDataSource.java",
"chars": 7943,
"preview": "package cn.wildfirechat.app.shiro;\n\nimport cn.wildfirechat.app.RestResult;\nimport cn.wildfirechat.app.jpa.PCSession;\nimp"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/CorsFilter.java",
"chars": 1178,
"preview": "package cn.wildfirechat.app.shiro;\n\n\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.fil"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/DBSessionDao.java",
"chars": 2995,
"preview": "package cn.wildfirechat.app.shiro;\n\nimport cn.wildfirechat.app.jpa.ShiroSession;\nimport cn.wildfirechat.app.jpa.ShiroSes"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/JsonAuthLoginFilter.java",
"chars": 1793,
"preview": "package cn.wildfirechat.app.shiro;\n\nimport cn.wildfirechat.app.RestResult;\nimport com.google.gson.Gson;\nimport org.apach"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/LdapMatcher.java",
"chars": 2805,
"preview": "package cn.wildfirechat.app.shiro;\n\nimport cn.wildfirechat.app.tools.LdapUser;\nimport org.apache.shiro.authc.Authenticat"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/LdapRealm.java",
"chars": 2025,
"preview": "package cn.wildfirechat.app.shiro;\n\n\nimport cn.wildfirechat.app.jpa.UserPassword;\nimport cn.wildfirechat.app.jpa.UserPas"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/LdapToken.java",
"chars": 643,
"preview": "package cn.wildfirechat.app.shiro;\n\nimport org.apache.shiro.authc.AuthenticationToken;\n\npublic class LdapToken implement"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/PhoneCodeRealm.java",
"chars": 1750,
"preview": "package cn.wildfirechat.app.shiro;\n\n\nimport cn.wildfirechat.app.RestResult;\nimport org.apache.shiro.authc.*;\nimport org."
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/PhoneCodeToken.java",
"chars": 487,
"preview": "package cn.wildfirechat.app.shiro;\n\nimport org.apache.shiro.authc.AuthenticationToken;\n\npublic class PhoneCodeToken impl"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/ScanCodeRealm.java",
"chars": 1774,
"preview": "package cn.wildfirechat.app.shiro;\n\n\nimport cn.wildfirechat.app.jpa.PCSession;\nimport org.apache.shiro.authc.*;\nimport o"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/ShiroConfig.java",
"chars": 4720,
"preview": "package cn.wildfirechat.app.shiro;\n\n\nimport org.apache.shiro.SecurityUtils;\nimport org.apache.shiro.mgt.SecurityManager;"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/ShiroSessionManager.java",
"chars": 1427,
"preview": "package cn.wildfirechat.app.shiro;\n\n\nimport com.aliyuncs.utils.StringUtils;\nimport org.apache.shiro.web.servlet.ShiroHtt"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/TokenAuthenticationToken.java",
"chars": 567,
"preview": "package cn.wildfirechat.app.shiro;\n\nimport org.apache.shiro.authc.AuthenticationToken;\n\npublic class TokenAuthentication"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/TokenMatcher.java",
"chars": 1488,
"preview": "package cn.wildfirechat.app.shiro;\n\nimport cn.wildfirechat.app.RestResult;\nimport cn.wildfirechat.pojos.InputOutputUserI"
},
{
"path": "src/main/java/cn/wildfirechat/app/shiro/UserPasswordRealm.java",
"chars": 2384,
"preview": "package cn.wildfirechat.app.shiro;\n\n\nimport cn.wildfirechat.app.RestResult;\nimport cn.wildfirechat.app.jpa.ShiroSession;"
},
{
"path": "src/main/java/cn/wildfirechat/app/slide/SlideVerifyCleanupService.java",
"chars": 538,
"preview": "package cn.wildfirechat.app.slide;\n\nimport cn.wildfirechat.app.jpa.SlideVerifyRepository;\nimport org.springframework.bea"
},
{
"path": "src/main/java/cn/wildfirechat/app/slide/SlideVerifyService.java",
"chars": 8063,
"preview": "package cn.wildfirechat.app.slide;\n\nimport cn.wildfirechat.app.jpa.SlideVerify;\nimport cn.wildfirechat.app.jpa.SlideVeri"
},
{
"path": "src/main/java/cn/wildfirechat/app/sms/AliyunSMSConfig.java",
"chars": 1181,
"preview": "package cn.wildfirechat.app.sms;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org"
},
{
"path": "src/main/java/cn/wildfirechat/app/sms/SmsService.java",
"chars": 169,
"preview": "package cn.wildfirechat.app.sms;\n\n\nimport cn.wildfirechat.app.RestResult;\n\npublic interface SmsService {\n RestResult."
},
{
"path": "src/main/java/cn/wildfirechat/app/sms/SmsServiceImpl.java",
"chars": 7609,
"preview": "package cn.wildfirechat.app.sms;\n\nimport cn.wildfirechat.app.RestResult;\nimport com.aliyuncs.CommonRequest;\nimport com.a"
},
{
"path": "src/main/java/cn/wildfirechat/app/sms/TencentSMSConfig.java",
"chars": 1286,
"preview": "package cn.wildfirechat.app.sms;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org"
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/LdapUser.java",
"chars": 453,
"preview": "package cn.wildfirechat.app.tools;\n\npublic class LdapUser {\n public final String uid, cn, mail, phone, dn;\n public"
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/LdapUtil.java",
"chars": 3630,
"preview": "package cn.wildfirechat.app.tools;\n\nimport javax.naming.Context;\nimport javax.naming.NamingEnumeration;\nimport javax.nam"
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/NumericIdGenerator.java",
"chars": 1348,
"preview": "package cn.wildfirechat.app.tools;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic "
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/OrderedIdUserNameGenerator.java",
"chars": 585,
"preview": "package cn.wildfirechat.app.tools;\n\nimport cn.wildfirechat.app.jpa.UserNameEntry;\nimport cn.wildfirechat.app.jpa.UserNam"
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/PhoneNumberUserNameGenerator.java",
"chars": 260,
"preview": "package cn.wildfirechat.app.tools;\n\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class PhoneNumbe"
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/RateLimiter.java",
"chars": 4064,
"preview": "package cn.wildfirechat.app.tools;\n\n\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\n\n/**\n * "
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/ShortUUIDGenerator.java",
"chars": 1526,
"preview": "package cn.wildfirechat.app.tools;\n\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashSet;\nimport j"
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/SpinLock.java",
"chars": 654,
"preview": "package cn.wildfirechat.app.tools;\n\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic class SpinLock {\n //j"
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/UUIDUserNameGenerator.java",
"chars": 330,
"preview": "package cn.wildfirechat.app.tools;\n\nimport org.springframework.stereotype.Component;\n\nimport java.util.UUID;\n\n@Component"
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/UserNameGenerator.java",
"chars": 113,
"preview": "package cn.wildfirechat.app.tools;\n\npublic interface UserNameGenerator {\n String getUserName(String phone);\n}\n"
},
{
"path": "src/main/java/cn/wildfirechat/app/tools/Utils.java",
"chars": 1561,
"preview": "package cn.wildfirechat.app.tools;\n\nimport java.nio.file.Paths;\nimport java.util.Random;\nimport java.util.UUID;\nimport j"
},
{
"path": "src/main/resources/application.properties",
"chars": 0,
"preview": ""
},
{
"path": "src/test/java/cn/wildfirechat/app/ApplicationTests.java",
"chars": 2569,
"preview": "package cn.wildfirechat.app;\n\nimport com.zaxxer.hikari.HikariDataSource;\nimport org.junit.Test;\nimport org.junit.runner."
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/AnnouncementTest.java",
"chars": 1718,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class AnnouncementTe"
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/ConferenceEntityTest.java",
"chars": 3455,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class ConferenceEnti"
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/FavoriteItemTest.java",
"chars": 2497,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class FavoriteItemTe"
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/PCSessionTest.java",
"chars": 3301,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class PCSessionTest "
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/RecordTest.java",
"chars": 2958,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class RecordTest {\n\n"
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/ShiroSessionTest.java",
"chars": 2336,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport java.util.Arrays;\n\nimport static org.junit.Assert.*;\n\np"
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/SlideVerifyRepositoryTest.java",
"chars": 3569,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport cn.wildfirechat.app.slide.SlideVerifyCleanupService;\nimport cn.wildfirechat.app"
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/SlideVerifyTest.java",
"chars": 3991,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class SlideVerifyTes"
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/UserConferenceTest.java",
"chars": 1674,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class UserConference"
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/UserNameEntryTest.java",
"chars": 864,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class UserNameEntryT"
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/UserPasswordTest.java",
"chars": 3167,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class UserPasswordTe"
},
{
"path": "src/test/java/cn/wildfirechat/app/jpa/UserPrivateConferenceIdTest.java",
"chars": 1445,
"preview": "package cn.wildfirechat.app.jpa;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\npublic class UserPrivateCon"
},
{
"path": "src/test/java/cn/wildfirechat/app/slide/SlideVerifyCleanupServiceTest.java",
"chars": 1664,
"preview": "package cn.wildfirechat.app.slide;\n\nimport cn.wildfirechat.app.jpa.SlideVerify;\nimport cn.wildfirechat.app.jpa.SlideVeri"
},
{
"path": "src/test/java/cn/wildfirechat/app/slide/SlideVerifyServiceTest.java",
"chars": 6132,
"preview": "package cn.wildfirechat.app.slide;\n\nimport cn.wildfirechat.app.jpa.SlideVerify;\nimport cn.wildfirechat.app.jpa.SlideVeri"
},
{
"path": "systemd/README.md",
"chars": 1770,
"preview": "# Linux Service 方式运行\n除了命令行方式直接执行APP服务外,还可以以linux systemd service方式来运行,注意以这种方式运行,APP服务的配置还是需要按照常规方法来配置。\n\n## 获取软件包\n下载野火rel"
},
{
"path": "systemd/app-server.service",
"chars": 700,
"preview": "[Unit]\nDescription=WildfirechatAPP\nDocumentation=https://docs.wildfirechat.cn\nWants=network-online.target\nAfter=network-"
}
]
// ... and 4 more files (download for full content)
About this extraction
This page contains the full source code of the wildfirechat/im-app_server GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 148 files (341.2 KB), approximately 86.4k tokens, and a symbol index with 883 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.