Commit 19d280e3 by giaogiao

Merge branch '1.4' of https://gitlab.aillo.cc/hewei/wecloud_im_server into feature-cluster

 Conflicts:
	common/src/main/java/com/wecloud/im/controller/ImApplicationController.java
	common/src/main/java/com/wecloud/im/netty/core/WsReadHandler.java
	开发记录.md
parents 3d2582d2 a807bfcf
...@@ -30,6 +30,9 @@ docker run -p 3306:3306 --name mysql57 -v $PWD/dockerData/mysql57/data:/var/lib/ ...@@ -30,6 +30,9 @@ docker run -p 3306:3306 --name mysql57 -v $PWD/dockerData/mysql57/data:/var/lib/
--- ---
tail -f /data0/java_projects_jenkins/logs/spring-boot-plus-error.log
## 开发规范 ## 开发规范
### 关于模块 ### 关于模块
......
package io.geekidea.springbootplus.test;
import cn.hutool.core.codec.Base64;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 读取系统文件目录 生成apns字符串
*/
public class ApnsTest {
@Test
public void test() throws Exception {
File file = new File("/Users/giaogiao/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/f31cd9e6d7da1d15c57c40575f5c85db/Message/MessageTemp/e1180914825140f051a87348bda5cbb5/File/hipro_test_push.p12");
InputStream certificate = new FileInputStream(file);
String encode = Base64.encode(certificate);
}
}
package io.geekidea.springbootplus.test;
import cn.hutool.core.codec.Base64;
import com.turo.pushy.apns.DeliveryPriority;
import com.turo.pushy.apns.PushType;
import com.wecloud.im.ws.sender.IosPush;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class IosApnsBase64Test {
public static void main(String[] args) throws IOException {
// apnsCertificatePath 证书
// * @param productFlag 环境
// * @param deviceToken 设备token
// * @param alertTitle 标题
// * @param alertBody 副标题
// * @param contentAvailable Boolean.FALSE
// * @param customProperty 自定义属性
// * @param badge 角标数量
// * @param priority DeliveryPriority.IMMEDIATE
// * @param pushType PushType.ALERT
// * @param topicBundleId undleId
// * @param sound rtc= "call.caf"; 否则为default
Map<String, Object> customProperty = new HashMap<String, Object>(10);
// String apnsCertificatePath = "frogsell_push_dev.p12";
String deviceToken = "5b761f954efe7493de0bc751942e1a8355853771b66a512f5687ca05e7335e99";
String alertTitle = "你好333";
String alertBody = "hi333";
int badge = 1;
String topicBundleId = "com.xteng.Hibro";
boolean contentAvailable = false;
// InputStream certificate = IosPush.getApnsCertificate(apnsCertificatePath);
// String encode = Base64.encode(certificate);
File file = new File("/Users/giaogiao/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/f31cd9e6d7da1d15c57c40575f5c85db/Message/MessageTemp/e1180914825140f051a87348bda5cbb5/File/hipro_test_push.p12");
InputStream in = new FileInputStream(file);
String encode = Base64.encode(in);
// 解码
byte[] decode = Base64.decode(encode);
InputStream inputStream2 = new ByteArrayInputStream(decode);
IosPush.push("123456", inputStream2, Boolean.FALSE, deviceToken, alertTitle, alertBody,
contentAvailable, customProperty, badge
, DeliveryPriority.IMMEDIATE, PushType.ALERT, topicBundleId,
"default");
}
// public static byte[] readInputStream(InputStream inputStream) throws IOException {
// byte[] buffer = new byte[1024];
// int len = 0;
// ByteArrayOutputStream bos = new ByteArrayOutputStream();
// while ((len = inputStream.read(buffer)) != -1) {
// bos.write(buffer, 0, len);
// }
// bos.close();
// return bos.toByteArray();
// }
// private static String inputToString(InputStream is) throws IOException {
// final int bufferSize = 1024;
// final char[] buffer = new char[bufferSize];
// final StringBuilder out = new StringBuilder();
// Reader in = new InputStreamReader(is, StandardCharsets.UTF_8);
// for (; ; ) {
// int rsz = in.read(buffer, 0, buffer.length);
// if (rsz < 0)
// break;
// out.append(buffer, 0, rsz);
// }
// return out.toString();
// }
}
package io.geekidea.springbootplus.test;
import com.turo.pushy.apns.DeliveryPriority;
import com.turo.pushy.apns.PushType;
import com.wecloud.im.ws.sender.IosPush;
import java.util.HashMap;
import java.util.Map;
public class IosApnsPushTest {
public static void main(String[] args) {
// * @param apnsCertificatePath 证书
// * @param productFlag 环境,测试=Boolean.FALSE,正式=Boolean.TRUE
// * @param deviceToken 设备token
// * @param alertTitle 标题
// * @param alertBody 副标题
// * @param contentAvailable Boolean.FALSE
// * @param customProperty 自定义属性
// * @param badge 角标数量
// * @param priority DeliveryPriority.IMMEDIATE
// * @param pushType PushType.ALERT
// * @param topicBundleId undleId
// * @param sound rtc= "call.caf"; 否则为default
Map<String, Object> customProperty = new HashMap<String, Object>(10);
String apnsCertificatePath = "frogsell_push_dev.p12";
String deviceToken = "27c93ca84bbf17d9ff8eb05df0576ac49822db2ae1c02aa0afea83b5c3861276";
String alertTitle = "你好22";
String alertBody = "hi";
int badge = 1;
String topicBundleId = "com.jdw.frogsell";
boolean contentAvailable = false;
IosPush.push(apnsCertificatePath, Boolean.FALSE, deviceToken, alertTitle, alertBody,
contentAvailable, customProperty, badge
, DeliveryPriority.IMMEDIATE, PushType.ALERT, topicBundleId,
"default");
}
}
package io.geekidea.springbootplus.test;
import cn.hutool.core.codec.Base64;
import com.wecloud.im.entity.ImIosApns;
import com.wecloud.im.service.ImIosApnsService;
import com.wecloud.im.ws.sender.IosPush;
import io.geekidea.springbootplus.framework.shiro.util.SnowflakeUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.InputStream;
/**
* 生成apns字符串 并存入数据库
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class SaveApnsTest {
@Autowired
private ImIosApnsService imIosApnsService;
@Test
public void test() throws Exception {
String apnsCertificatePath = "frogsell_push.p12";
String topicBundleId = "com.jdw.frogsell";
String pwd = "123456";
InputStream certificate = IosPush.getApnsCertificate(apnsCertificatePath);
String encode = Base64.encode(certificate);
ImIosApns imIosApns = new ImIosApns();
imIosApns.setId(SnowflakeUtil.getId());
imIosApns.setFkAppId(0L);
imIosApns.setApnsFileValue(encode);
imIosApns.setEnv(1);
imIosApns.setBundleId(topicBundleId);
imIosApns.setPwd(pwd);
imIosApnsService.save(imIosApns);
}
}
package io.geekidea.springbootplus.test; package io.geekidea.springbootplus.test;
import cn.hutool.crypto.digest.MD5;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import com.wecloud.im.ws.model.request.ReceiveModel; import com.wecloud.im.ws.model.request.ReceiveModel;
import org.springframework.util.DigestUtils;
import java.util.Date; import java.util.Date;
...@@ -24,7 +24,10 @@ public class SignTest { ...@@ -24,7 +24,10 @@ public class SignTest {
*/ */
private static void getSign(String timestamp, String clientId, String appKey, String appSecret) { private static void getSign(String timestamp, String clientId, String appKey, String appSecret) {
String sign = new MD5().digestHex(timestamp + clientId + appKey + appSecret); String data = timestamp + clientId + appKey + appSecret;
// String sign = new MD5().digestHex(data);
String sign = DigestUtils.md5DigestAsHex(data.getBytes());
System.out.println("timestamp:" + timestamp); System.out.println("timestamp:" + timestamp);
System.out.println("sign:" + sign); System.out.println("sign:" + sign);
......
...@@ -115,6 +115,14 @@ ...@@ -115,6 +115,14 @@
<!-- 友盟 end --> <!-- 友盟 end -->
<!-- https://mvnrepository.com/artifact/com.turo/pushy -->
<!-- apns推送-->
<dependency>
<groupId>com.turo</groupId>
<artifactId>pushy</artifactId>
<version>0.13.10</version>
</dependency>
<!-- wecloud短信 start--> <!-- wecloud短信 start-->
<dependency> <dependency>
...@@ -131,5 +139,34 @@ ...@@ -131,5 +139,34 @@
<!-- wecloud短信 end--> <!-- wecloud短信 end-->
</dependencies> </dependencies>
<!-- <build>-->
<!-- <resources>-->
<!-- <resource>-->
<!-- <directory>src/main/resources</directory>-->
<!-- <includes>-->
<!-- <include>**/*.p12</include>-->
<!-- </includes>-->
<!-- <filtering>false</filtering>-->
<!-- </resource>-->
<!-- </resources>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <groupId>org.apache.maven.plugins</groupId>-->
<!-- <artifactId>maven-resources-plugin</artifactId>-->
<!-- <configuration>-->
<!-- <encoding>UTF-8</encoding>-->
<!-- &lt;!&ndash; 过滤后缀为pem、pfx的证书文件 &ndash;&gt;-->
<!-- &lt;!&ndash; 打包编译是过滤掉这些证书文件,不再自动篡改&ndash;&gt;-->
<!-- <nonFilteredFileExtensions>-->
<!-- <nonFilteredFileExtension>p12</nonFilteredFileExtension>-->
<!-- <nonFilteredFileExtension>pem</nonFilteredFileExtension>-->
<!-- <nonFilteredFileExtension>pfx</nonFilteredFileExtension>-->
<!-- </nonFilteredFileExtensions>-->
<!-- </configuration>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </build>-->
</project> </project>
\ No newline at end of file
...@@ -52,7 +52,7 @@ public class ImClientController extends BaseController { ...@@ -52,7 +52,7 @@ public class ImClientController extends BaseController {
@PostMapping("/addDeviceInfo") @PostMapping("/addDeviceInfo")
@ApiOperation(value = "添加或修改推送设备信息(每次请求都会覆盖之前的数据)") @ApiOperation(value = "添加或修改推送设备信息(每次请求都会覆盖之前的数据)")
public ApiResult<Boolean> addDeviceInfo(@Validated(Add.class) @RequestBody ImClientDeviceInfoAdd imClientDevice) throws Exception { public ApiResult<Boolean> addDeviceInfo(@Validated(Add.class) @RequestBody ImClientDeviceInfoAdd imClientDevice) throws Exception {
boolean flag = imClientService.addDeviceInfo(imClientDevice); boolean flag = imClientService.updateDeviceInfo(imClientDevice);
return ApiResult.result(flag); return ApiResult.result(flag);
} }
......
//package com.wecloud.im.controller;
//
//import com.wecloud.im.service.ImIosApnsService;
//import io.geekidea.springbootplus.framework.common.controller.BaseController;
//import io.swagger.annotations.Api;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.web.bind.annotation.RequestMapping;
//import org.springframework.web.bind.annotation.RestController;
//
///**
// * apns配置表 控制器
// *
// * @author wei
// * @since 2021-09-18
// */
//@Slf4j
//@RestController
//@RequestMapping("/imIosApns")
//@Api(value = "apns配置表API", tags = {"apns配置表"})
//public class ImIosApnsController extends BaseController {
//
// @Autowired
// private ImIosApnsService imIosApnsService;
////
//// /**
//// * 添加apns配置表
//// */
//// @PostMapping("/add")
//// @OperationLog(name = "添加apns配置表", type = OperationLogType.ADD)
//// @ApiOperation(value = "添加apns配置表")
//// public ApiResult<Boolean> addImIosApns(@Validated(Add.class) @RequestBody ImIosApns imIosApns) throws Exception {
//// boolean flag = imIosApnsService.saveImIosApns(imIosApns);
//// return ApiResult.result(flag);
//// }
////
//// /**
//// * 修改apns配置表
//// */
//// @PostMapping("/update")
//// @OperationLog(name = "修改apns配置表", type = OperationLogType.UPDATE)
//// @ApiOperation(value = "修改apns配置表")
//// public ApiResult<Boolean> updateImIosApns(@Validated(Update.class) @RequestBody ImIosApns imIosApns) throws Exception {
//// boolean flag = imIosApnsService.updateImIosApns(imIosApns);
//// return ApiResult.result(flag);
//// }
////
//// /**
//// * 删除apns配置表
//// */
//// @PostMapping("/delete/{id}")
//// @OperationLog(name = "删除apns配置表", type = OperationLogType.DELETE)
//// @ApiOperation(value = "删除apns配置表")
//// public ApiResult<Boolean> deleteImIosApns(@PathVariable("id") Long id) throws Exception {
//// boolean flag = imIosApnsService.deleteImIosApns(id);
//// return ApiResult.result(flag);
//// }
////
//// /**
//// * 获取apns配置表详情
//// */
//// @GetMapping("/info/{id}")
//// @OperationLog(name = "apns配置表详情", type = OperationLogType.INFO)
//// @ApiOperation(value = "apns配置表详情")
//// public ApiResult<ImIosApnsQueryVo> getImIosApns(@PathVariable("id") Long id) throws Exception {
//// ImIosApnsQueryVo imIosApnsQueryVo = imIosApnsService.getImIosApnsById(id);
//// return ApiResult.ok(imIosApnsQueryVo);
//// }
//
//// /**
//// * apns配置表分页列表
//// */
//// @PostMapping("/getPageList")
//// @OperationLog(name = "apns配置表分页列表", type = OperationLogType.PAGE)
//// @ApiOperation(value = "apns配置表分页列表")
//// public ApiResult<Paging<ImIosApnsQueryVo>>getImIosApnsPageList(@Validated @RequestBody ImIosApnsPageParam imIosApnsPageParam)throws Exception{
//// Paging<ImIosApnsQueryVo> paging = imIosApnsService.getImIosApnsPageList(imIosApnsPageParam);
//// return ApiResult.ok(paging);
//// }
////
//}
//
package com.wecloud.im.controller; package com.wecloud.im.controller;
import com.wecloud.im.param.ImHistoryMessagePageParam; import com.wecloud.im.param.ImHistoryMessagePageParam;
import com.wecloud.im.param.add.ImMsgRecall;
import com.wecloud.im.param.add.ImMsgUpdate; import com.wecloud.im.param.add.ImMsgUpdate;
import com.wecloud.im.service.ImMessageService; import com.wecloud.im.service.ImMessageService;
import com.wecloud.im.vo.ImMessageOfflineListVo; import com.wecloud.im.vo.ImMessageOfflineListVo;
...@@ -18,7 +19,6 @@ import org.springframework.validation.annotation.Validated; ...@@ -18,7 +19,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
...@@ -43,8 +43,8 @@ public class ImMessageController extends BaseController { ...@@ -43,8 +43,8 @@ public class ImMessageController extends BaseController {
*/ */
@PostMapping("/withdraw") @PostMapping("/withdraw")
@ApiOperation(value = "消息撤回", notes = "只能撤回客户端自己发送的消息") @ApiOperation(value = "消息撤回", notes = "只能撤回客户端自己发送的消息")
public ApiResult<Boolean> updateMsgWithdrawById(@RequestParam Long msgId) throws Exception { public ApiResult<Boolean> updateMsgWithdrawById(@RequestBody ImMsgRecall imMsgRecall) throws Exception {
return imMessageService.updateMsgWithdrawById(msgId); return imMessageService.updateMsgWithdrawById(imMsgRecall);
} }
/** /**
......
package com.wecloud.im.controller;
import com.wecloud.im.param.rtc.CandidateForwardParam;
import com.wecloud.im.param.rtc.CreateRtcChannelParam;
import com.wecloud.im.param.rtc.CreateRtcChannelResult;
import com.wecloud.im.param.rtc.JoinRtcChannelParam;
import com.wecloud.im.param.rtc.LeaveRtcChannelParam;
import com.wecloud.im.param.rtc.RejectRtcChannelParam;
import com.wecloud.im.param.rtc.SdpForwardParam;
import com.wecloud.rtc.service.RtcService;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.common.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 音视频通话 控制器
*
* @author wei
* @since 2021-10-18
*/
@Slf4j
@RestController
@RequestMapping("/rtc")
@Api(value = "音视频通话", tags = {"音视频通话"})
public class ImRtcController extends BaseController {
@Autowired
private RtcService rtcService;
/**
* 创建频道,并邀请客户端加入
*/
@PostMapping("/createAndCall")
@ApiOperation(value = "创建频道,并邀请客户端加入", notes = "创建频道,并邀请客户端加入")
public ApiResult<CreateRtcChannelResult> createAndCall(@RequestBody CreateRtcChannelParam createRtcChannelParam) throws Exception {
return rtcService.createAndCall(createRtcChannelParam);
}
@PostMapping("/join")
@ApiOperation(value = "同意进入频道", notes = "")
public ApiResult<Boolean> join(@RequestBody JoinRtcChannelParam joinRtcChannelParam) {
return rtcService.join(joinRtcChannelParam);
}
@PostMapping("/reject")
@ApiOperation(value = "拒接进入频道", notes = "")
public ApiResult<Boolean> reject(@RequestBody RejectRtcChannelParam rejectRtcChannelParam) {
return rtcService.reject(rejectRtcChannelParam);
}
@PostMapping("/leave")
@ApiOperation(value = "主动挂断(离开频道)", notes = "")
public ApiResult<Boolean> leave(@RequestBody LeaveRtcChannelParam leaveRtcChannelParam) {
return rtcService.leave(leaveRtcChannelParam);
}
@PostMapping("/sdpForward")
@ApiOperation(value = "SDP数据转发", notes = "")
public ApiResult<Boolean> sdpForward(@RequestBody SdpForwardParam sdpForwardParam) {
return rtcService.sdpForward(sdpForwardParam);
}
@PostMapping("/candidateForward")
@ApiOperation(value = "candidate候选者数据转发", notes = "")
public ApiResult<Boolean> candidateForward(@RequestBody CandidateForwardParam candidateForwardParam) {
return rtcService.candidateForward(candidateForwardParam);
}
}
...@@ -8,7 +8,7 @@ import io.swagger.annotations.Api; ...@@ -8,7 +8,7 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
...@@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j @Slf4j
@RestController @RestController
@RequestMapping("/signDemo") @RequestMapping("/signDemo")
@Api(value = "sign API", tags = {"获取sign(Demo)"}) @Api(value = "signAPI", tags = {"获取sign(Demo)"})
public class SignController extends BaseController { public class SignController extends BaseController {
@Autowired @Autowired
...@@ -31,9 +31,9 @@ public class SignController extends BaseController { ...@@ -31,9 +31,9 @@ public class SignController extends BaseController {
/** /**
* 根据客户方生成签名字符串 验证通过则下发token * 根据客户方生成签名字符串 验证通过则下发token
*/ */
@GetMapping("/get") @PostMapping("/get")
@ApiOperation(value = "获取sign(仅测试使用)", notes = "生成签名") @ApiOperation(value = "获取sign(仅提供测试调试使用)", notes = "生成签名测试,在生产环境中,此步骤需要第三方应用的服务端进行生成")
public String verify(@RequestBody GetSignParam getSignParam) throws Exception { public String get(@RequestBody GetSignParam getSignParam) throws Exception {
return new MD5().digestHex(getSignParam.getTimestamp() + getSignParam.getClientId() + getSignParam.getAppKey() + getSignParam.getAppSecret()); return new MD5().digestHex(getSignParam.getTimestamp() + getSignParam.getClientId() + getSignParam.getAppKey() + getSignParam.getAppSecret());
} }
......
...@@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j @Slf4j
@RestController @RestController
@RequestMapping("/token") @RequestMapping("/token")
@Api(value = "token API", tags = {"token相关"}) @Api(value = "token API", tags = {"申请token相关"})
public class TokenController extends BaseController { public class TokenController extends BaseController {
@Autowired @Autowired
...@@ -33,8 +33,8 @@ public class TokenController extends BaseController { ...@@ -33,8 +33,8 @@ public class TokenController extends BaseController {
* 根据客户方生成签名字符串 验证通过则下发token * 根据客户方生成签名字符串 验证通过则下发token
*/ */
@PostMapping("/verify") @PostMapping("/verify")
@ApiOperation(value = "验证sign,并返回token", notes = "根据客户方生成签名字符串 验证通过则下发token") @ApiOperation(value = "根据sign申请token", notes = "校验客户方生成的签名字符串,验证通过则下发token")
public ApiResult<TokenVo> verify(@RequestBody ImTokenVerify imTokenVerify) throws Exception { public ApiResult<TokenVo> verify(@RequestBody ImTokenVerify imTokenVerify) {
return imClientLoginService.verifySign(imTokenVerify); return imClientLoginService.verifySign(imTokenVerify);
} }
......
package com.wecloud.im.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.geekidea.springbootplus.framework.common.entity.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
* apns配置表
*
* @author wei
* @since 2021-09-18
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "ImIosApns对象")
public class ImIosApns extends BaseEntity {
private static final long serialVersionUID = 1L;
@NotNull(message = "id不能为空")
@ApiModelProperty("id")
@TableId(value = "id", type = IdType.INPUT)
private Long id;
@NotNull(message = "应用appid不能为空")
@ApiModelProperty("应用appid")
private Long fkAppId;
@ApiModelProperty("Base64(apns.p12)")
private String apnsFileValue;
@ApiModelProperty("环境,正式1,测试0")
private Integer env;
@ApiModelProperty("bundle_id")
private String bundleId;
@ApiModelProperty("证书密码")
private String pwd;
}
...@@ -65,6 +65,16 @@ public interface ImConversationMapper extends BaseMapper<ImConversation> { ...@@ -65,6 +65,16 @@ public interface ImConversationMapper extends BaseMapper<ImConversation> {
*/ */
Integer getRepetitionConversation(@Param("clientId1") Long clientId1, @Param("clientId2") Long clientId2); Integer getRepetitionConversation(@Param("clientId1") Long clientId1, @Param("clientId2") Long clientId2);
/**
* 判断重复会话中的Attributes是否一样
*
* @param clientId1
* @param clientId2
* @param attributes
* @return 大于等于1为有重复会话
*/
Long getRepetitionConversationAttributes(@Param("clientId1") Long clientId1, @Param("clientId2") Long clientId2, @Param("attributes") String attributes);
/** /**
* 查询已经存在的会话信息 * 查询已经存在的会话信息
......
...@@ -42,4 +42,12 @@ public interface ImInboxMapper extends BaseMapper<ImInbox> { ...@@ -42,4 +42,12 @@ public interface ImInboxMapper extends BaseMapper<ImInbox> {
Long updateImMsgReceivedByIds(@Param("clientId") Long clientId, @Param("msgIds") List<Long> msgIds); Long updateImMsgReceivedByIds(@Param("clientId") Long clientId, @Param("msgIds") List<Long> msgIds);
Long updateImMsgReadByIds(@Param("clientId") Long clientId, @Param("msgIds") List<Long> msgIds); Long updateImMsgReadByIds(@Param("clientId") Long clientId, @Param("msgIds") List<Long> msgIds);
/**
* 统计未读消息数量
*
* @param clientId
* @return
*/
Integer countMyNotReadCount(@Param("clientId") Long clientId);
} }
package com.wecloud.im.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wecloud.im.entity.ImIosApns;
import com.wecloud.im.param.ImIosApnsPageParam;
import com.wecloud.im.param.ImIosApnsQueryVo;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.io.Serializable;
/**
* apns配置表 Mapper 接口
*
* @author wei
* @since 2021-09-18
*/
@Repository
public interface ImIosApnsMapper extends BaseMapper<ImIosApns> {
/**
* 根据ID获取查询对象
*
* @param id
* @return
*/
ImIosApnsQueryVo getImIosApnsById(Serializable id);
/**
* 获取分页对象
*
* @param page
* @param imIosApnsPageParam
* @return
*/
IPage<ImIosApnsQueryVo> getImIosApnsPageList(@Param("page") Page page, @Param("param") ImIosApnsPageParam imIosApnsPageParam);
}
...@@ -8,10 +8,11 @@ import io.netty.handler.codec.http.HttpObjectAggregator; ...@@ -8,10 +8,11 @@ import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.IdleStateHandler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component @Component
public class NettyChannelInitializer extends ChannelInitializer<SocketChannel> { public class NettyChannelInitializer extends ChannelInitializer<SocketChannel> {
...@@ -30,10 +31,20 @@ public class NettyChannelInitializer extends ChannelInitializer<SocketChannel> { ...@@ -30,10 +31,20 @@ public class NettyChannelInitializer extends ChannelInitializer<SocketChannel> {
pipeline.addLast("SingleHttpRequestHandler", channelInboundHandler); pipeline.addLast("SingleHttpRequestHandler", channelInboundHandler);
// 连接超时管理 (判断通道是否有数据写入) // 连接超时管理 (判断通道是否有数据写入)
pipeline.addLast("readTimeoutHandler", new ReadTimeoutHandler(60)); // pipeline.addLast("readTimeoutHandler", new ReadTimeoutHandler(60));
// "/appws"路径 升级长连接 // "/appws"路径 升级长连接
pipeline.addLast("appWebSocketServerotocolHandler", new WebSocketServerProtocolHandler(WsConstants.WS_URL)); pipeline.addLast("appWebSocketServerotocolHandler", new WebSocketServerProtocolHandler(WsConstants.WS_URL));
/*
* 心跳机制
* observeOutput -当评估写空闲时是否应该考虑字节的消耗。默认为false。
* readerIdleTime—状态为IdleState的IdleStateEvent。当在指定的时间内没有执行读操作时,将触发READER_IDLE。指定0禁用。
* writerIdleTime—状态为IdleState的IdleStateEvent。当在指定的时间内没有执行写操作时,会触发WRITER_IDLE。指定0禁用。
* allIdleTime—状态为IdleState的IdleStateEvent。在一定时间内不读不写会触发ALL_IDLE。指定0禁用。
* unit—readerIdleTime、writeIdleTime和allIdleTime的时间单位
*/
pipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
} }
} }
...@@ -4,13 +4,14 @@ import cn.hutool.core.thread.ThreadFactoryBuilder; ...@@ -4,13 +4,14 @@ import cn.hutool.core.thread.ThreadFactoryBuilder;
import com.wecloud.im.ws.model.WsConstants; import com.wecloud.im.ws.model.WsConstants;
import com.wecloud.im.ws.receive.ReadWsData; import com.wecloud.im.ws.receive.ReadWsData;
import com.wecloud.im.ws.service.MangerChannelService; import com.wecloud.im.ws.service.MangerChannelService;
import io.netty.channel.ChannelFuture; import com.wecloud.rtc.service.RtcService;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
...@@ -30,9 +31,15 @@ import java.util.concurrent.TimeUnit; ...@@ -30,9 +31,15 @@ import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private static final String PING = "ping";
private static final String PONG = "pong";
@Resource @Resource
private ReadWsData readWsData; private ReadWsData readWsData;
@Autowired
private RtcService rtcService;
@Resource @Resource
private MangerChannelService mangerChannelService; private MangerChannelService mangerChannelService;
...@@ -44,12 +51,14 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram ...@@ -44,12 +51,14 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram
* io密集型任务配置尽可能多的线程数量 * io密集型任务配置尽可能多的线程数量
*/ */
private final static ExecutorService TASK_THREAD_POOL_EXECUTOR = private final static ExecutorService TASK_THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(WsConstants.CPU_PROCESSORS * 5, WsConstants.CPU_PROCESSORS * 50, new ThreadPoolExecutor(WsConstants.CPU_PROCESSORS * 5, WsConstants.CPU_PROCESSORS * 10,
3L, TimeUnit.MILLISECONDS, 10L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1), NAMED_THREAD_FACTORY, new ThreadPoolExecutor.CallerRunsPolicy()); new LinkedBlockingQueue<Runnable>(10), NAMED_THREAD_FACTORY, new ThreadPoolExecutor.CallerRunsPolicy());
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) { protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
ctx.channel().attr(MangerChannelService.READ_IDLE_TIMES).set(0);// 读空闲的计数清零
String data = msg.text(); String data = msg.text();
try { try {
if (data.isEmpty()) { if (data.isEmpty()) {
...@@ -64,7 +73,7 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram ...@@ -64,7 +73,7 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram
} catch (Exception e) { } catch (Exception e) {
//返回错误 //返回错误
ctx.channel().writeAndFlush(new TextWebSocketFrame("error=" + e.toString() + ",data=" + data)); ctx.channel().writeAndFlush(new TextWebSocketFrame("error=" + e + ",data=" + data));
log.error(e.getMessage() + data, e); log.error(e.getMessage() + data, e);
} }
} }
...@@ -73,12 +82,59 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram ...@@ -73,12 +82,59 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram
String appKey = ctx.channel().attr(MangerChannelService.APP_KEY).get(); String appKey = ctx.channel().attr(MangerChannelService.APP_KEY).get();
String clientId = ctx.channel().attr(MangerChannelService.CLIENT_ID).get(); String clientId = ctx.channel().attr(MangerChannelService.CLIENT_ID).get();
try { try {
if (PING.equals(data)) {
log.info("收到心跳clientId:" + clientId);
ctx.channel().writeAndFlush(new TextWebSocketFrame(PONG));
return;
}
if (PONG.equals(data)) {
log.info("收到心跳应用Pong,clientId:" + clientId);
return;
}
readWsData.convertModel(data, ctx, appKey, clientId); readWsData.convertModel(data, ctx, appKey, clientId);
} catch (Exception e) { } catch (Exception e) {
log.error("系统繁忙data:" + data + ",appKey:" + appKey + ",clientId:" + clientId + log.error("系统繁忙data:" + data + ",appKey:" + appKey + ",clientId:" + clientId +
",channelId:" + ctx.channel().id().asShortText(), e); ",channelId:" + ctx.channel().id().asLongText(), e);
} }
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
String clientId = ctx.channel().attr(MangerChannelService.CLIENT_ID).get();
//读超时计时器
Integer readIdleTimes = ctx.channel().attr(MangerChannelService.READ_IDLE_TIMES).get();
IdleStateEvent event = (IdleStateEvent) evt;
String eventType = null;
switch (event.state()) {
case READER_IDLE:
eventType = "读空闲:readIdleTimes=" + readIdleTimes;
ctx.channel().attr(MangerChannelService.READ_IDLE_TIMES).set(readIdleTimes + 1);// 读空闲的计数加1
// 发ping
ctx.channel().writeAndFlush(new TextWebSocketFrame(PING));
break;
case WRITER_IDLE:
eventType = "写空闲";
// 不处理
break;
case ALL_IDLE:
eventType = "读写空闲";
// 不处理
break;
}
log.info(clientId + "超时事件:" + eventType);
if (readIdleTimes >= 5) {
log.info(clientId + ".读空闲超过5次关闭连接");
ctx.channel().close();
}
} }
...@@ -92,13 +148,13 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram ...@@ -92,13 +148,13 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram
// @Override // @Override
// public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// String userIdByChannel = mangerChannelService.getInfoByChannel(ctx); // String userIdByChannel = mangerChannelService.getInfoByChannel(ctx);
// log.info("uid:" + userIdByChannel + ",ws异常,channelId:" + ctx.channel().id().asShortText(), cause); // log.info("uid:" + userIdByChannel + ",ws异常,channelId:" + ctx.channel().id().asLongText(), cause);
// } // }
@Override @Override
public void handlerAdded(ChannelHandlerContext ctx) { public void handlerAdded(ChannelHandlerContext ctx) {
String userIdByChannel = mangerChannelService.getInfoByChannel(ctx); String userIdByChannel = mangerChannelService.getInfoByChannel(ctx);
log.info("连接WS成功handlerAdded,uid:" + userIdByChannel + "," + ",channelId:" + ctx.channel().id().asShortText()); log.info("连接WS成功handlerAdded,uid:" + userIdByChannel + "," + ",channelId:" + ctx.channel().id().asLongText());
} }
...@@ -111,7 +167,7 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram ...@@ -111,7 +167,7 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram
// @Override // @Override
// public void channelInactive(ChannelHandlerContext ctx) { // public void channelInactive(ChannelHandlerContext ctx) {
// String userIdByChannel = mangerChannelService.getInfoByChannel(ctx); // String userIdByChannel = mangerChannelService.getInfoByChannel(ctx);
// log.info("uid:" + userIdByChannel + "," + "channelInactive" + ",channelId:" + ctx.channel().id().asShortText()); // log.info("uid:" + userIdByChannel + "," + "channelInactive" + ",channelId:" + ctx.channel().id().asLongText());
// } // }
/** /**
...@@ -119,16 +175,16 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram ...@@ -119,16 +175,16 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram
*/ */
@Override @Override
public void handlerRemoved(ChannelHandlerContext ctx) { public void handlerRemoved(ChannelHandlerContext ctx) {
String appKey = ctx.channel().attr(MangerChannelService.APP_KEY).get();
String clientId = ctx.channel().attr(MangerChannelService.CLIENT_ID).get();
String userIdByChannel = mangerChannelService.getInfoByChannel(ctx); String userIdByChannel = mangerChannelService.getInfoByChannel(ctx);
String asLongText = ctx.channel().id().asLongText(); log.info("uid:" + userIdByChannel + "," + "handlerRemoved" + ",channelId:" + ctx.channel().id().asLongText());
log.info("uid:" + userIdByChannel + "," + "handlerRemoved-begin" + ",channelId:" + asLongText);
// 关掉连接 // 关掉连接
ChannelFuture close = ctx.close(); ctx.close();
close.addListener(
//执行后回调的方法 // rtc清空缓存
(ChannelFutureListener) channelFuture1 -> { rtcService.clientOffline(appKey, clientId);
log.info("uid:" + userIdByChannel + "," + "handlerRemoved-success" + ",channelId:" + asLongText);
});
} }
} }
...@@ -128,6 +128,7 @@ public class NettyApiRequest { ...@@ -128,6 +128,7 @@ public class NettyApiRequest {
// 设置属性值 userid - channel // 设置属性值 userid - channel
ctx.channel().attr(MangerChannelService.CLIENT_ID).set(clientId); ctx.channel().attr(MangerChannelService.CLIENT_ID).set(clientId);
ctx.channel().attr(MangerChannelService.APP_KEY).set(appKey); ctx.channel().attr(MangerChannelService.APP_KEY).set(appKey);
ctx.channel().attr(MangerChannelService.READ_IDLE_TIMES).set(0);// 读空闲的计数=0
// 添加长连接handler // 添加长连接handler
ctx.pipeline().addLast("appImHandler", appImReadHandler); ctx.pipeline().addLast("appImHandler", appImReadHandler);
......
...@@ -4,6 +4,8 @@ import io.swagger.annotations.ApiModel; ...@@ -4,6 +4,8 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
/** /**
* sign * sign
* *
...@@ -12,7 +14,7 @@ import lombok.Data; ...@@ -12,7 +14,7 @@ import lombok.Data;
*/ */
@Data @Data
@ApiModel(value = "GetSignParam") @ApiModel(value = "GetSignParam")
public class GetSignParam { public class GetSignParam implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ApiModelProperty("时间戳") @ApiModelProperty("时间戳")
......
package com.wecloud.im.param;
import io.geekidea.springbootplus.framework.core.pagination.BasePageOrderParam;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <pre>
* apns配置表 分页参数对象
* </pre>
*
* @author wei
* @date 2021-09-18
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "apns配置表分页参数")
public class ImIosApnsPageParam extends BasePageOrderParam {
private static final long serialVersionUID = 1L;
}
package com.wecloud.im.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* <pre>
* apns配置表 查询结果对象
* </pre>
*
* @author wei
* @date 2021-09-18
*/
@Data
@Accessors(chain = true)
@ApiModel(value = "ImIosApnsQueryVo对象")
public class ImIosApnsQueryVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("应用appid")
private Long fkAppId;
@ApiModelProperty("Base64(apns.p12)")
private String apnsFileValue;
@ApiModelProperty("环境,正式1,测试0")
private Integer env;
@ApiModelProperty("bundle_id")
private String bundleId;
@ApiModelProperty("证书密码")
private String pwd;
}
\ No newline at end of file
...@@ -15,13 +15,13 @@ import lombok.Data; ...@@ -15,13 +15,13 @@ import lombok.Data;
public class ImTokenVerify { public class ImTokenVerify {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ApiModelProperty("时间戳") @ApiModelProperty("时间戳,需与生成sign时的值一致")
private String timestamp; private String timestamp;
@ApiModelProperty("client客户端id") @ApiModelProperty("client客户端id,需与生成sign时的值一致")
private String clientId; private String clientId;
@ApiModelProperty("appkey") @ApiModelProperty("appkey,需与生成sign时的值一致")
private String appKey; private String appKey;
@ApiModelProperty("签名sign") @ApiModelProperty("签名sign")
......
...@@ -23,7 +23,7 @@ public class ImClientDeviceInfoAdd extends BaseEntity { ...@@ -23,7 +23,7 @@ public class ImClientDeviceInfoAdd extends BaseEntity {
@ApiModelProperty("设备不想收到推送提醒, 1想, 0不想") @ApiModelProperty("设备不想收到推送提醒, 1想, 0不想")
private Integer valid; private Integer valid;
@ApiModelProperty("设备类型1:ios; 2:android") @ApiModelProperty("设备类型1:ios; 2:android; 3:web")
private Integer deviceType; private Integer deviceType;
@ApiModelProperty("设备推送token") @ApiModelProperty("设备推送token")
......
...@@ -7,7 +7,7 @@ import lombok.Data; ...@@ -7,7 +7,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.util.LinkedHashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
/** /**
...@@ -27,7 +27,7 @@ public class ImConversationCreate extends BaseEntity { ...@@ -27,7 +27,7 @@ public class ImConversationCreate extends BaseEntity {
private String name; private String name;
@ApiModelProperty("json格式,可选 自定义属性,供开发者扩展使用。") @ApiModelProperty("json格式,可选 自定义属性,供开发者扩展使用。")
private LinkedHashMap attributes; private HashMap attributes;
@ApiModelProperty("可选 邀请加入会话的客户端,如创建单聊,则填入对方的clientId") @ApiModelProperty("可选 邀请加入会话的客户端,如创建单聊,则填入对方的clientId")
private List<String> clientIds; private List<String> clientIds;
......
package com.wecloud.im.param.add;
import io.geekidea.springbootplus.framework.common.entity.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.HashMap;
/**
* 撤回消息
*
* @author wei
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "ImMsgRecall")
public class ImMsgRecall extends BaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty("消息id")
private Long msgId;
@ApiModelProperty("自定义推送字段")
private HashMap pushMap;
}
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "CandidateForwardParam")
public class CandidateForwardParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("频道id")
private Long channelId;
/**
* 转发的候选者数据
*/
private String candidateData;
}
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 创建频道请求参数
*
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "CreateRtcChannelParam")
public class CreateRtcChannelParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "被邀请的客户端ID", required = true)
private String toClient;
@ApiModelProperty(value = "客户端自定义数据", required = false)
private String attrs;
@ApiModelProperty(value = "类型: video或voice", required = true)
private String type;
@ApiModelProperty(value = "绑定的会话id,可选", required = false)
private Long conversationId;
@ApiModelProperty(value = "接收方展示的系统推送内容,可", required = false)
private String push;
@ApiModelProperty(value = "是否需要给对方发系统通知", required = true)
private Boolean pushCall;
}
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 创建频道返回参数
*
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "CreateRtcChannelResult")
public class CreateRtcChannelResult implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("频道id")
private Long channelId;
}
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 加入频道请求参数
*
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "JoinRtcChannelParam")
public class JoinRtcChannelParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("频道id")
private Long channelId;
}
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 退出频道 请求
*
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "LeaveRtcChannelParam")
public class LeaveRtcChannelParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("频道id")
private Long channelId;
}
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 拒接加入频道 请求
*
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "RejectRtcChannelParam")
public class RejectRtcChannelParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("频道id")
private Long channelId;
}
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "SdpForwardParam")
public class SdpForwardParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("频道id")
private Long channelId;
@ApiModelProperty("sdp转发的数据")
private String sdpData;
@ApiModelProperty("sdp类型: Offer或Answer")
private String sdpType;
}
...@@ -30,7 +30,7 @@ public interface ImClientService extends BaseService<ImClient> { ...@@ -30,7 +30,7 @@ public interface ImClientService extends BaseService<ImClient> {
* @param imClientDevice * @param imClientDevice
* @return * @return
*/ */
boolean addDeviceInfo(ImClientDeviceInfoAdd imClientDevice); boolean updateDeviceInfo(ImClientDeviceInfoAdd imClientDevice);
/** /**
* 修改 * 修改
...@@ -76,4 +76,8 @@ public interface ImClientService extends BaseService<ImClient> { ...@@ -76,4 +76,8 @@ public interface ImClientService extends BaseService<ImClient> {
*/ */
ImClient getCurentClient(); ImClient getCurentClient();
ImClient getCacheImClient(Long applicationId, String clientId);
void deleteCacheImClient(Long applicationId, String clientId);
} }
...@@ -102,5 +102,15 @@ public interface ImConversationService extends BaseService<ImConversation> { ...@@ -102,5 +102,15 @@ public interface ImConversationService extends BaseService<ImConversation> {
*/ */
Integer getRepetitionConversation(Long clientId1, Long clientId2); Integer getRepetitionConversation(Long clientId1, Long clientId2);
/**
* 判断重复会话中的Attributes是否一样
*
* @param clientId1
* @param clientId2
* @param attributes
* @return 大于等于1为有重复会话
*/
Long getRepetitionConversationAttributes(Long clientId1, Long clientId2, String attributes);
} }
...@@ -70,6 +70,14 @@ public interface ImInboxService extends BaseService<ImInbox> { ...@@ -70,6 +70,14 @@ public interface ImInboxService extends BaseService<ImInbox> {
ApiResult<Boolean> updateImMsgReceived(ImMsgReceivedStatusUpdate imMsgReceivedUpdate); ApiResult<Boolean> updateImMsgReceived(ImMsgReceivedStatusUpdate imMsgReceivedUpdate);
/** /**
* 统计未读消息数量
*
* @param clientId
* @return
*/
Integer countMyNotReadCount(Long clientId);
/**
* 消息修改为已读状态 * 消息修改为已读状态
* *
* @return * @return
......
package com.wecloud.im.service;
import com.wecloud.im.entity.ImIosApns;
import com.wecloud.im.param.ImIosApnsQueryVo;
import io.geekidea.springbootplus.framework.common.service.BaseService;
/**
* apns配置表 服务类
*
* @author wei
* @since 2021-09-18
*/
public interface ImIosApnsService extends BaseService<ImIosApns> {
/**
* 保存
*
* @param imIosApns
* @return
* @throws Exception
*/
boolean saveImIosApns(ImIosApns imIosApns) throws Exception;
/**
* 修改
*
* @param imIosApns
* @return
* @throws Exception
*/
boolean updateImIosApns(ImIosApns imIosApns) throws Exception;
/**
* 删除
*
* @param id
* @return
* @throws Exception
*/
boolean deleteImIosApns(Long id) throws Exception;
/**
* 根据ID获取查询对象
*
* @param id
* @return
* @throws Exception
*/
ImIosApnsQueryVo getImIosApnsById(Long id) throws Exception;
ImIosApns getImIosApnsByAppId(Long appId);
/**
* 获取分页对象
*
* @param imIosApnsPageParam
* @return
* @throws Exception
*/
// Paging<ImIosApnsQueryVo> getImIosApnsPageList(ImIosApnsPageParam imIosApnsPageParam) throws Exception;
}
...@@ -2,6 +2,7 @@ package com.wecloud.im.service; ...@@ -2,6 +2,7 @@ package com.wecloud.im.service;
import com.wecloud.im.entity.ImMessage; import com.wecloud.im.entity.ImMessage;
import com.wecloud.im.param.ImHistoryMessagePageParam; import com.wecloud.im.param.ImHistoryMessagePageParam;
import com.wecloud.im.param.add.ImMsgRecall;
import com.wecloud.im.param.add.ImMsgUpdate; import com.wecloud.im.param.add.ImMsgUpdate;
import com.wecloud.im.vo.ImMessageOfflineListVo; import com.wecloud.im.vo.ImMessageOfflineListVo;
import com.wecloud.im.vo.OfflineMsgDto; import com.wecloud.im.vo.OfflineMsgDto;
...@@ -23,10 +24,9 @@ public interface ImMessageService extends BaseService<ImMessage> { ...@@ -23,10 +24,9 @@ public interface ImMessageService extends BaseService<ImMessage> {
/** /**
* 消息撤回 只能撤回客户端自己发送的消息 * 消息撤回 只能撤回客户端自己发送的消息
* *
* @param msgId
* @return * @return
*/ */
ApiResult<Boolean> updateMsgWithdrawById(Long msgId); ApiResult<Boolean> updateMsgWithdrawById(ImMsgRecall imMsgRecall);
/** /**
* 修改消息体 * 修改消息体
......
...@@ -20,6 +20,9 @@ import io.geekidea.springbootplus.framework.shiro.util.JwtUtil; ...@@ -20,6 +20,9 @@ import io.geekidea.springbootplus.framework.shiro.util.JwtUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
...@@ -31,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional; ...@@ -31,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional;
*/ */
@Slf4j @Slf4j
@Service @Service
@CacheConfig(cacheNames = "client")
public class ImClientServiceImpl extends BaseServiceImpl<ImClientMapper, ImClient> implements ImClientService { public class ImClientServiceImpl extends BaseServiceImpl<ImClientMapper, ImClient> implements ImClientService {
@Autowired @Autowired
...@@ -46,18 +50,22 @@ public class ImClientServiceImpl extends BaseServiceImpl<ImClientMapper, ImClien ...@@ -46,18 +50,22 @@ public class ImClientServiceImpl extends BaseServiceImpl<ImClientMapper, ImClien
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean addDeviceInfo(ImClientDeviceInfoAdd imClientDevice) { public boolean updateDeviceInfo(ImClientDeviceInfoAdd imClientDevice) {
ImClient client = getCurentClient(); ImClient client = getCurentClient();
ImClient clientNew = new ImClient(); ImClient clientNew = new ImClient();
BeanUtils.copyProperties(imClientDevice, clientNew); BeanUtils.copyProperties(imClientDevice, clientNew);
clientNew.setId(client.getId()); clientNew.setId(client.getId());
return this.saveOrUpdate(clientNew);
// 清楚缓存
deleteCacheImClient(client.getFkAppid(), client.getClientId());
// 修改
return this.updateImClient(clientNew);
} }
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public boolean updateImClient(ImClient imClient) throws Exception { public boolean updateImClient(ImClient imClient) {
return super.updateById(imClient); return super.updateById(imClient);
} }
...@@ -76,7 +84,7 @@ public class ImClientServiceImpl extends BaseServiceImpl<ImClientMapper, ImClien ...@@ -76,7 +84,7 @@ public class ImClientServiceImpl extends BaseServiceImpl<ImClientMapper, ImClien
public Paging<ImClientQueryVo> getImClientPageList(ImClientPageParam imClientPageParam) throws Exception { public Paging<ImClientQueryVo> getImClientPageList(ImClientPageParam imClientPageParam) throws Exception {
Page<ImClientQueryVo> page = new PageInfo<>(imClientPageParam, OrderItem.desc(getLambdaColumn(ImClient::getCreateTime))); Page<ImClientQueryVo> page = new PageInfo<>(imClientPageParam, OrderItem.desc(getLambdaColumn(ImClient::getCreateTime)));
IPage<ImClientQueryVo> iPage = imClientMapper.getImClientPageList(page, imClientPageParam); IPage<ImClientQueryVo> iPage = imClientMapper.getImClientPageList(page, imClientPageParam);
return new Paging<ImClientQueryVo>(iPage); return new Paging<>(iPage);
} }
@Override @Override
...@@ -84,13 +92,22 @@ public class ImClientServiceImpl extends BaseServiceImpl<ImClientMapper, ImClien ...@@ -84,13 +92,22 @@ public class ImClientServiceImpl extends BaseServiceImpl<ImClientMapper, ImClien
// shiro线程中获取当前token // shiro线程中获取当前token
JwtToken curentJwtToken = JwtUtil.getCurentJwtToken(); JwtToken curentJwtToken = JwtUtil.getCurentJwtToken();
// 根据appKey查询appid // 根据appKey查询appid
ImApplication imApplication = imApplicationService.getOneByAppKey(curentJwtToken.getAppKey()); ImApplication imApplication = imApplicationService.getOneByAppKey(curentJwtToken.getAppKey());
return getCacheImClient(imApplication.getId(), curentJwtToken.getClientId());
}
@Override
@Cacheable(key = "#p0+#p1")
public ImClient getCacheImClient(Long applicationId, String clientId) {
return this.getOne(new QueryWrapper<ImClient>().lambda() return this.getOne(new QueryWrapper<ImClient>().lambda()
.eq(ImClient::getFkAppid, imApplication.getId()) .eq(ImClient::getFkAppid, applicationId)
.eq(ImClient::getClientId, curentJwtToken.getClientId())); .eq(ImClient::getClientId, clientId));
}
@Override
@CacheEvict(key = "#p0+#p1")
public void deleteCacheImClient(Long applicationId, String clientId) {
} }
} }
...@@ -40,7 +40,6 @@ import org.springframework.transaction.annotation.Transactional; ...@@ -40,7 +40,6 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
/** /**
...@@ -83,6 +82,11 @@ public class ImConversationServiceImpl extends BaseServiceImpl<ImConversationMap ...@@ -83,6 +82,11 @@ public class ImConversationServiceImpl extends BaseServiceImpl<ImConversationMap
ImClient createClient = imClientService.getCurentClient(); ImClient createClient = imClientService.getCurentClient();
if (imConversationCreate.getClientIds().size() == 0) {
log.info("没有成员");
return ApiResult.result(ApiCode.CLIENT_NOT_FOUNT, null);
}
// 成员不存在,不能创建会话 // 成员不存在,不能创建会话
for (String id : imConversationCreate.getClientIds()) { for (String id : imConversationCreate.getClientIds()) {
ImClient imClient = imClientService.getOne(new QueryWrapper<ImClient>().lambda() ImClient imClient = imClientService.getOne(new QueryWrapper<ImClient>().lambda()
...@@ -124,31 +128,30 @@ public class ImConversationServiceImpl extends BaseServiceImpl<ImConversationMap ...@@ -124,31 +128,30 @@ public class ImConversationServiceImpl extends BaseServiceImpl<ImConversationMap
} }
} else { } else {
//创建重复会话时对比扩展字段 0不 1是 //创建重复会话时对比扩展字段 1是
if (imApplication.getContrastExtendedFieldStatus() == 1) { if (imApplication.getContrastExtendedFieldStatus() == 1) {
// 被邀请client // 被邀请client
ImClient inviteClient = imClientService.getOne(new QueryWrapper<ImClient>().lambda() ImClient inviteClient = imClientService.getOne(new QueryWrapper<ImClient>().lambda()
.eq(ImClient::getFkAppid, createClient.getFkAppid()) .eq(ImClient::getFkAppid, createClient.getFkAppid())
.eq(ImClient::getClientId, imConversationCreate.getClientIds().get(0))); .eq(ImClient::getClientId, imConversationCreate.getClientIds().get(0)));
JsonMapper jsonMapper = new JsonMapper();
String asString = jsonMapper.writeValueAsString(imConversationCreate.getAttributes());
log.info("RequestAttributes:" + asString);
// 是否存在重复会话
Integer repetitionConversation = getRepetitionConversation(createClient.getId(), inviteClient.getId()); Long repetitionConversation = getRepetitionConversationAttributes(createClient.getId(), inviteClient.getId(), asString);
if (repetitionConversation != 0) {
ImConversation repetitionConversationInfo = imConversationMapper.getRepetitionConversationInfo(createClient.getId(), inviteClient.getId()); // 存在重复会话
if (repetitionConversation != null) {
JsonMapper jsonMapper = new JsonMapper(); log.info("出现Attributes重复");
LinkedHashMap linkedHashMap = jsonMapper.readValue(repetitionConversationInfo.getAttributes(), LinkedHashMap.class); ImConversationCreateVo imConversationCreateVo = new ImConversationCreateVo();
imConversationCreateVo.setId(repetitionConversation);
if (linkedHashMap.equals(imConversationCreate.getAttributes())) { // 为重复
log.info(imConversationCreate.getAttributes() + ",出现重复"); return ApiResult.ok(imConversationCreateVo);
ImConversationCreateVo imConversationCreateVo = new ImConversationCreateVo();
imConversationCreateVo.setId(repetitionConversationInfo.getId());
// 为重复
return ApiResult.ok(imConversationCreateVo);
}
} }
} }
} }
// 会话id // 会话id
...@@ -284,4 +287,9 @@ public class ImConversationServiceImpl extends BaseServiceImpl<ImConversationMap ...@@ -284,4 +287,9 @@ public class ImConversationServiceImpl extends BaseServiceImpl<ImConversationMap
return imConversationMapper.getRepetitionConversation(clientId1, clientId2); return imConversationMapper.getRepetitionConversation(clientId1, clientId2);
} }
@Override
public Long getRepetitionConversationAttributes(Long clientId1, Long clientId2, String attributes) {
return imConversationMapper.getRepetitionConversationAttributes(clientId1, clientId2, attributes);
}
} }
package com.wecloud.im.service.impl; package com.wecloud.im.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wecloud.im.entity.ImApplication; import com.wecloud.im.entity.ImApplication;
import com.wecloud.im.entity.ImClient; import com.wecloud.im.entity.ImClient;
...@@ -14,7 +15,8 @@ import com.wecloud.im.service.ImClientService; ...@@ -14,7 +15,8 @@ import com.wecloud.im.service.ImClientService;
import com.wecloud.im.service.ImConversationMembersService; import com.wecloud.im.service.ImConversationMembersService;
import com.wecloud.im.service.ImInboxService; import com.wecloud.im.service.ImInboxService;
import com.wecloud.im.service.ImMessageService; import com.wecloud.im.service.ImMessageService;
import com.wecloud.im.ws.model.ResponseModel; import com.wecloud.im.ws.enums.WsResponseCmdEnum;
import com.wecloud.im.ws.model.WsResponseModel;
import com.wecloud.im.ws.service.WriteDataService; import com.wecloud.im.ws.service.WriteDataService;
import io.geekidea.springbootplus.framework.common.api.ApiCode; import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult; import io.geekidea.springbootplus.framework.common.api.ApiResult;
...@@ -116,6 +118,19 @@ public class ImInboxServiceImpl extends BaseServiceImpl<ImInboxMapper, ImInbox> ...@@ -116,6 +118,19 @@ public class ImInboxServiceImpl extends BaseServiceImpl<ImInboxMapper, ImInbox>
return ApiResult.ok(); return ApiResult.ok();
} }
/**
* 统计未读消息数量
*
* @param clientId
* @return
*/
@Override
public Integer countMyNotReadCount(Long clientId) {
return imInboxMapper.countMyNotReadCount(clientId);
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public ApiResult<Boolean> updateImMsgRead(ImMsgReadStatusUpdate imMsgReadStatusUpdate) { public ApiResult<Boolean> updateImMsgRead(ImMsgReadStatusUpdate imMsgReadStatusUpdate) {
...@@ -190,8 +205,8 @@ public class ImInboxServiceImpl extends BaseServiceImpl<ImInboxMapper, ImInbox> ...@@ -190,8 +205,8 @@ public class ImInboxServiceImpl extends BaseServiceImpl<ImInboxMapper, ImInbox>
continue; continue;
} }
// 向接收方推送 // 向接收方推送
ResponseModel<ImMessageOnlineSend> responseModel = new ResponseModel<>(); WsResponseModel<ImMessageOnlineSend> responseModel = new WsResponseModel<>();
responseModel.setCmd(ResponseModel.ONLINE_EVENT_MSG); responseModel.setCmd(WsResponseCmdEnum.ONLINE_EVENT_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS); ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode()); responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage()); responseModel.setMsg(result.getMessage());
......
package com.wecloud.im.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wecloud.im.entity.ImIosApns;
import com.wecloud.im.mapper.ImIosApnsMapper;
import com.wecloud.im.param.ImIosApnsQueryVo;
import com.wecloud.im.service.ImIosApnsService;
import io.geekidea.springbootplus.framework.common.service.impl.BaseServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* apns配置表 服务实现类
*
* @author wei
* @since 2021-09-18
*/
@Slf4j
@Service
@CacheConfig(cacheNames = "apns")
public class ImIosApnsServiceImpl extends BaseServiceImpl<ImIosApnsMapper, ImIosApns> implements ImIosApnsService {
@Autowired
private ImIosApnsMapper imIosApnsMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveImIosApns(ImIosApns imIosApns) throws Exception {
return super.save(imIosApns);
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean updateImIosApns(ImIosApns imIosApns) throws Exception {
return super.updateById(imIosApns);
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean deleteImIosApns(Long id) throws Exception {
return super.removeById(id);
}
@Override
public ImIosApnsQueryVo getImIosApnsById(Long id) throws Exception {
return imIosApnsMapper.getImIosApnsById(id);
}
@Override
@Cacheable(key = "#p0")
public ImIosApns getImIosApnsByAppId(Long appId) {
return this.getOne(new QueryWrapper<ImIosApns>().lambda().eq(
ImIosApns::getFkAppId, appId
));
}
// @Override
// public Paging<ImIosApnsQueryVo> getImIosApnsPageList(ImIosApnsPageParam imIosApnsPageParam) throws Exception {
// Page<ImIosApnsQueryVo> page;
// IPage<ImIosApnsQueryVo> iPage = imIosApnsMapper.getImIosApnsPageList(page, imIosApnsPageParam);
// return new Paging<ImIosApnsQueryVo>(iPage);
// }
}
package com.wecloud.im.service.impl; package com.wecloud.im.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import com.wecloud.im.entity.ImApplication;
import com.wecloud.im.entity.ImClient; import com.wecloud.im.entity.ImClient;
import com.wecloud.im.entity.ImConversation; import com.wecloud.im.entity.ImConversation;
import com.wecloud.im.entity.ImConversationMembers;
import com.wecloud.im.entity.ImMessage; import com.wecloud.im.entity.ImMessage;
import com.wecloud.im.entity.ImMessageOnlineSend;
import com.wecloud.im.mapper.ImMessageMapper; import com.wecloud.im.mapper.ImMessageMapper;
import com.wecloud.im.param.ImHistoryMessagePageParam; import com.wecloud.im.param.ImHistoryMessagePageParam;
import com.wecloud.im.param.add.ImMsgRecall;
import com.wecloud.im.param.add.ImMsgUpdate; import com.wecloud.im.param.add.ImMsgUpdate;
import com.wecloud.im.service.ImApplicationService;
import com.wecloud.im.service.ImClientService; import com.wecloud.im.service.ImClientService;
import com.wecloud.im.service.ImConversationMembersService;
import com.wecloud.im.service.ImConversationService; import com.wecloud.im.service.ImConversationService;
import com.wecloud.im.service.ImMessageService; import com.wecloud.im.service.ImMessageService;
import com.wecloud.im.vo.ImMessageOfflineListVo; import com.wecloud.im.vo.ImMessageOfflineListVo;
import com.wecloud.im.vo.OfflineMsgDto; import com.wecloud.im.vo.OfflineMsgDto;
import com.wecloud.im.ws.model.ResponseModel;
import com.wecloud.im.ws.sender.PushTask;
import com.wecloud.im.ws.service.WriteDataService;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult; import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.common.service.impl.BaseServiceImpl; import io.geekidea.springbootplus.framework.common.service.impl.BaseServiceImpl;
import io.geekidea.springbootplus.framework.core.pagination.PageInfo; import io.geekidea.springbootplus.framework.core.pagination.PageInfo;
import io.geekidea.springbootplus.framework.core.pagination.Paging; import io.geekidea.springbootplus.framework.core.pagination.Paging;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import static com.wecloud.im.ws.strategy.concrete.ImConcreteReceiveStrategy.PUSH_KEY;
/** /**
* 消息存储表 服务实现类 * 消息存储表 服务实现类
* *
...@@ -48,14 +63,34 @@ public class ImMessageServiceImpl extends BaseServiceImpl<ImMessageMapper, ImMes ...@@ -48,14 +63,34 @@ public class ImMessageServiceImpl extends BaseServiceImpl<ImMessageMapper, ImMes
@Autowired @Autowired
private ImConversationService imConversationService; private ImConversationService imConversationService;
@Autowired
private PushTask pushTask;
@Autowired
private ImApplicationService imApplicationService;
@Autowired
private ImConversationMembersService imConversationMembersService;
@Autowired
private WriteDataService writeDataService;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public ApiResult<Boolean> updateMsgWithdrawById(Long msgId) { public ApiResult<Boolean> updateMsgWithdrawById(ImMsgRecall imMsgRecall) {
ImClient client = imClientService.getCurentClient(); ImClient imClientSender = imClientService.getCurentClient();
// 查询imApplication
ImApplication imApplication = imApplicationService.getById(imClientSender.getFkAppid());
if (imApplication == null) {
log.info("imApplication为空");
return ApiResult.fail();
}
// 判断该消息是否是该客户端发送 // 判断该消息是否是该客户端发送
ImMessage messageById = this.getById(msgId); ImMessage messageById = this.getById(imMsgRecall.getMsgId());
if (!messageById.getSender().equals(client.getId())) { if (!messageById.getSender().equals(imClientSender.getId())) {
log.error("判断该消息是否是该客户端发送"); log.error("判断该消息是否是该客户端发送");
return ApiResult.fail(); return ApiResult.fail();
} }
...@@ -63,13 +98,78 @@ public class ImMessageServiceImpl extends BaseServiceImpl<ImMessageMapper, ImMes ...@@ -63,13 +98,78 @@ public class ImMessageServiceImpl extends BaseServiceImpl<ImMessageMapper, ImMes
return ApiResult.ok(); return ApiResult.ok();
} }
// 修改消息体
messageById.setWithdraw(Boolean.TRUE); messageById.setWithdraw(Boolean.TRUE);
messageById.setWithdrawTime(new Date()); messageById.setWithdrawTime(new Date());
// 清空消息
messageById.setContent("{}"); messageById.setContent("{}");
boolean saveOk = this.updateById(messageById);
if (saveOk) {
// 获取自定义推送字段
HashMap<String, String> pushMap = null;
if (imMsgRecall.getPushMap().get(PUSH_KEY) != null) {
pushMap = (HashMap<String, String>) imMsgRecall.getPushMap().get(PUSH_KEY);
}
boolean b = this.updateById(messageById); // 查询该会话所有成员
List<ImConversationMembers> membersList = imConversationMembersService.list(
new QueryWrapper<ImConversationMembers>().lambda()
.eq(ImConversationMembers::getFkConversationId, messageById.getFkConversationId())
.notIn(ImConversationMembers::getFkClientId, imClientSender.getId())
);
if (membersList.isEmpty()) {
log.info("membersList为空,toConversationId:" + messageById.getFkConversationId());
return ApiResult.fail();
}
// 遍历发送
for (ImConversationMembers conversationMembers : membersList) {
// // 保存收件箱
// long imInboxId = SnowflakeUtil.getId();
// ImInbox imInbox = new ImInbox();
// imInbox.setId(imInboxId);
// imInbox.setCreateTime(new Date());
// imInbox.setFkAppid(imApplication.getId());
// imInbox.setReceiver(conversationMembers.getFkClientId());
// imInbox.setFkMsgId(messageId);
// imInbox.setReadMsgStatus(0);
// imInbox.setReceiverMsgStatus(0);
// imInbox.setFkConversationId(toConversationId);
// imInboxService.save(imInbox);
// 查询接收方
ImClient imClientReceiver = imClientService.getOne(new QueryWrapper<ImClient>().lambda()
.eq(ImClient::getFkAppid, imApplication.getId())
.eq(ImClient::getId, conversationMembers.getFkClientId()));
if (imClientReceiver == null) {
continue;
}
// 封装响应的实体
ImMessageOnlineSend imMessageOnlineSend = new ImMessageOnlineSend();
BeanUtils.copyProperties(messageById, imMessageOnlineSend);
imMessageOnlineSend.setMsgId(messageById.getId());
imMessageOnlineSend.setSender(imClientSender.getClientId());
imMessageOnlineSend.setContent(null);
imMessageOnlineSend.setConversationId(conversationMembers.getFkConversationId());
imMessageOnlineSend.setWithdraw(Boolean.TRUE);
imMessageOnlineSend.setEvent(Boolean.TRUE);
// 向接收方推送
ResponseModel<ImMessageOnlineSend> responseModel = new ResponseModel<>();
responseModel.setCmd(ResponseModel.ONLINE_EVENT_MSG);
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(imMessageOnlineSend);
responseModel.setReqId(null);
writeDataService.write(responseModel, imApplication.getAppKey(), imClientReceiver.getClientId());
// 异步推送系统通知消息
pushTask.push(pushMap, imClientReceiver, imApplication);
}
if (b) {
return ApiResult.ok(); return ApiResult.ok();
} else { } else {
...@@ -81,7 +181,7 @@ public class ImMessageServiceImpl extends BaseServiceImpl<ImMessageMapper, ImMes ...@@ -81,7 +181,7 @@ public class ImMessageServiceImpl extends BaseServiceImpl<ImMessageMapper, ImMes
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public ApiResult<Boolean> updateMsgById(ImMsgUpdate imMsgUpdate) { public ApiResult<Boolean> updateMsgById(ImMsgUpdate imMsgUpdate) {
ImClient client = imClientService.getCurentClient(); // ImClient client = imClientService.getCurentClient();
/* // 判断该消息是否是该客户端发送 /* // 判断该消息是否是该客户端发送
ImMessage messageById = this.getById(imMsgUpdate.getId()); ImMessage messageById = this.getById(imMsgUpdate.getId());
......
...@@ -17,7 +17,7 @@ import java.lang.annotation.Target; ...@@ -17,7 +17,7 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)//RUNTIME运行时保留 @Retention(RetentionPolicy.RUNTIME)//RUNTIME运行时保留
@Target(ElementType.TYPE) //type描述类、接口 @Target(ElementType.TYPE) //type描述类、接口
@Documented @Documented
public @interface ReceiveTypeAnnotation { public @interface CmdTypeAnnotation {
WsRequestCmdEnum type(); WsRequestCmdEnum type();
} }
...@@ -15,7 +15,12 @@ public enum WsRequestCmdEnum { ...@@ -15,7 +15,12 @@ public enum WsRequestCmdEnum {
/** /**
* ping * ping
*/ */
PING(2); PING(2),
/**
* 单人WebRTC音视频通话
*/
SINGLE_RTC(3);
private final int cmdCode; private final int cmdCode;
......
...@@ -8,14 +8,25 @@ package com.wecloud.im.ws.enums; ...@@ -8,14 +8,25 @@ package com.wecloud.im.ws.enums;
public enum WsResponseCmdEnum { public enum WsResponseCmdEnum {
/** /**
* 离线消息下发完成指令 * 下发在线RTC事件
*/ */
OFFLINE_MSG_SUC(100), SINGLE_RTC_MSG(4),
/** /**
* 主动下发消息指令 * 下发在线事件消息
*/ */
WRITE_MSG(101); ONLINE_EVENT_MSG(3),
/**
* 下发在线基本类型消息
*/
ONLINE_MSG(2),
/**
* 响应数据类型
*/
RES(1);
private final int cmdCode; private final int cmdCode;
...@@ -23,20 +34,6 @@ public enum WsResponseCmdEnum { ...@@ -23,20 +34,6 @@ public enum WsResponseCmdEnum {
this.cmdCode = uriCode; this.cmdCode = uriCode;
} }
/**
* 根据uriCode获取
*
* @param uriCode
* @return
*/
public static WsResponseCmdEnum getByCode(int uriCode) {
for (WsResponseCmdEnum wsResponsePathEnum : values()) {
if (wsResponsePathEnum.getCmdCode() == uriCode) {
return wsResponsePathEnum;
}
}
return null;
}
public int getCmdCode() { public int getCmdCode() {
return cmdCode; return cmdCode;
......
package com.wecloud.im.ws.enums;
/**
* @Description webrtc响应类型
* @Author hewei hwei1233@163.com
* @Date 2019-12-05
*/
public enum WsRtcResponseSubCmdEnum {
// --- 服务端响应
/**
* 接收到RTC邀请
*/
RTC_CALL(1),
/**
* 用户状态更新事件(用户加入频道)
*/
CLIENT_JOIN(2),
/**
* 用户状态更新事件(用户退出频道)
*/
CLIENT_LEAVE(3),
/**
* 用户状态更新事件(用户拒接邀请,不同意进入频道)
*/
CLIENT_REJECT(4),
/**
* SDP数据转发
*/
SDP_FORWARD(5),
/**
* candidate候选者数据转发
*/
CANDIDATE_FORWARD(6);
private final int cmdCode;
WsRtcResponseSubCmdEnum(int cmdCode) {
this.cmdCode = cmdCode;
}
public int getCmdCode() {
return cmdCode;
}
}
...@@ -12,26 +12,11 @@ import java.io.Serializable; ...@@ -12,26 +12,11 @@ import java.io.Serializable;
*/ */
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
public class ResponseModel<T> implements Serializable { public class WsResponseModel<T> implements Serializable {
/** /**
* 下发在线事件消息 * 枚举类WsResponseCmdEnum 请求uri的编码
*/ * 由于webSocket使用同一个通道发送数据,需要区分不同类型请求
public static final Integer ONLINE_EVENT_MSG = 3;
/**
* 下发在线基本类型消息
*/
public static final Integer ONLINE_MSG = 2;
/**
* 响应数据类型
*/
public static final Integer RES = 1;
/**
* 枚举类UriPathEnum 请求uri的编码
* 由于websocket使用同一个通道发送数据,需要区分不同类型请求
*/ */
private Integer cmd; private Integer cmd;
......
...@@ -4,8 +4,8 @@ import com.fasterxml.jackson.databind.json.JsonMapper; ...@@ -4,8 +4,8 @@ import com.fasterxml.jackson.databind.json.JsonMapper;
import com.wecloud.im.ws.enums.WsRequestCmdEnum; import com.wecloud.im.ws.enums.WsRequestCmdEnum;
import com.wecloud.im.ws.model.request.ReceiveModel; import com.wecloud.im.ws.model.request.ReceiveModel;
import com.wecloud.im.ws.service.WriteDataService; import com.wecloud.im.ws.service.WriteDataService;
import com.wecloud.im.ws.strategy.AbstractReceiveStrategy; import com.wecloud.im.ws.strategy.ImCmdAbstract;
import com.wecloud.im.ws.strategy.ReceiveStrategyContext; import com.wecloud.im.ws.strategy.ImCmdContext;
import io.geekidea.springbootplus.framework.common.exception.BusinessException; import io.geekidea.springbootplus.framework.common.exception.BusinessException;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -23,15 +23,12 @@ import javax.annotation.Resource; ...@@ -23,15 +23,12 @@ import javax.annotation.Resource;
public class ReadWsData { public class ReadWsData {
private static final Logger log = LoggerFactory.getLogger(ReadWsData.class); private static final Logger log = LoggerFactory.getLogger(ReadWsData.class);
// idea此处报红 属于正常
// @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Resource @Resource
private ReceiveStrategyContext receiveStrategyContext; private ImCmdContext receiveStrategyContext;
@Resource @Resource
private WriteDataService writeDataService; private WriteDataService writeDataService;
private static final String PING = "ping";
/** /**
* 在此开始进入业务流程子线程,将不占netty的io线程 * 在此开始进入业务流程子线程,将不占netty的io线程
...@@ -42,11 +39,8 @@ public class ReadWsData { ...@@ -42,11 +39,8 @@ public class ReadWsData {
public void convertModel(String data, ChannelHandlerContext ctx, String appKey, String clientId) throws Exception { public void convertModel(String data, ChannelHandlerContext ctx, String appKey, String clientId) throws Exception {
log.info("appWS收到data:" + data + "\nappKey+clientId:" + appKey + ":" + clientId + log.info("appWS收到data:" + data + "\nappKey+clientId:" + appKey + ":" + clientId +
",channelId:" + ctx.channel().id().asShortText()); ",channelId:" + ctx.channel().id().asLongText());
if (PING.equals(data)) {
log.info("收到心跳clientId:" + clientId);
return;
}
// 解析json // 解析json
JsonMapper jsonMapper = new JsonMapper(); JsonMapper jsonMapper = new JsonMapper();
...@@ -57,8 +51,8 @@ public class ReadWsData { ...@@ -57,8 +51,8 @@ public class ReadWsData {
WsRequestCmdEnum wsRequestUriPathEnum = WsRequestCmdEnum.getByCode(receiveModel.getCmd()); WsRequestCmdEnum wsRequestUriPathEnum = WsRequestCmdEnum.getByCode(receiveModel.getCmd());
// 使用策略模式, 根据不同类型请求调用不同实现类 // 使用策略模式, 根据不同类型请求调用不同实现类
AbstractReceiveStrategy receiveStrategy = receiveStrategyContext.getStrategy(wsRequestUriPathEnum); ImCmdAbstract cmdStrategy = receiveStrategyContext.getStrategy(wsRequestUriPathEnum);
receiveStrategy.process(receiveModel, ctx, data, appKey, clientId); cmdStrategy.process(receiveModel, ctx, data, appKey, clientId);
} }
......
...@@ -34,15 +34,17 @@ public interface MangerChannelService { ...@@ -34,15 +34,17 @@ public interface MangerChannelService {
/** /**
* LANGUAGE * LANGUAGE
*/ */
AttributeKey<String> LANGUAGE = AttributeKey.valueOf("lan"); AttributeKey<String> LANGUAGE = AttributeKey.valueOf("la");
/** /**
* APP_VERSION * APP_VERSION
*/ */
// AttributeKey<String> APP_VERSION = AttributeKey.valueOf("appVersion"); AttributeKey<String> APP_VERSION = AttributeKey.valueOf("av");
// AttributeKey<String> TOKEN = AttributeKey.valueOf("TOKEN"); AttributeKey<String> TOKEN = AttributeKey.valueOf("to");
// AttributeKey<String> DEVICEID = AttributeKey.valueOf("DEVICEID"); AttributeKey<String> DEVICEID = AttributeKey.valueOf("dc");
// AttributeKey<String> PLATFORM = AttributeKey.valueOf("PLATFORM"); AttributeKey<String> PLATFORM = AttributeKey.valueOf("pt");
AttributeKey<Integer> READ_IDLE_TIMES = AttributeKey.valueOf("readIdleTimes");
/** /**
* 根据userID获取channel * 根据userID获取channel
......
package com.wecloud.im.ws.service; package com.wecloud.im.ws.service;
import com.wecloud.im.ws.model.ResponseModel; import com.wecloud.im.ws.model.WsResponseModel;
import com.wecloud.im.ws.model.request.ReceiveModel; import com.wecloud.im.ws.model.request.ReceiveModel;
import io.geekidea.springbootplus.framework.common.api.ApiCode; import io.geekidea.springbootplus.framework.common.api.ApiCode;
...@@ -47,7 +47,7 @@ public interface WriteDataService { ...@@ -47,7 +47,7 @@ public interface WriteDataService {
* *
* @param responseModel * @param responseModel
*/ */
void write(ResponseModel responseModel, String toAppKey, String toClientId); void write(WsResponseModel responseModel, String toAppKey, String toClientId);
} }
...@@ -310,22 +310,22 @@ public class MangerChannelServiceImpl implements MangerChannelService { ...@@ -310,22 +310,22 @@ public class MangerChannelServiceImpl implements MangerChannelService {
NioSocketChannel nioSocketChannel = get(toAppKey, toClientId); NioSocketChannel nioSocketChannel = get(toAppKey, toClientId);
if (null == nioSocketChannel) { if (null == nioSocketChannel) {
// userCache.offline(toAppKey + toClientId); // userCache.offline(toAppKey + toClientId);
if (log.isDebugEnabled()) { // if (log.isDebugEnabled()) {
log.info("writeData连接为空:" + toAppKey + toClientId + "," + msg); log.info("writeData连接为空:" + toAppKey + toClientId + "," + msg);
} // }
return false; return false;
} }
// 判断连接是否断开 // 判断连接是否断开
if (nioSocketChannel.isShutdown()) { if (nioSocketChannel.isShutdown()) {
if (log.isDebugEnabled()) { // if (log.isDebugEnabled()) {
log.info("writeData连接断开:" + toAppKey + toClientId + "," + msg + ",\nchannelId:" + nioSocketChannel.id().asLongText()); log.info("writeData连接断开:" + toAppKey + toClientId + "," + msg + ",\nchannelId:" + nioSocketChannel.id().asLongText());
} // }
return false; return false;
} }
if (log.isDebugEnabled()) { // if (log.isDebugEnabled()) {
log.info("writeData:" + toAppKey + "," + toClientId + "," + msg + ",\nchannelId:" + nioSocketChannel.id().asLongText()); log.info("writeData:" + toAppKey + "," + toClientId + "," + msg + ",\nchannelId:" + nioSocketChannel.id().asLongText());
} // }
ChannelFuture channelFuture = nioSocketChannel.writeAndFlush(new TextWebSocketFrame(msg)); ChannelFuture channelFuture = nioSocketChannel.writeAndFlush(new TextWebSocketFrame(msg));
channelFuture.addListener( channelFuture.addListener(
......
...@@ -3,7 +3,7 @@ package com.wecloud.im.ws.service.impl; ...@@ -3,7 +3,7 @@ package com.wecloud.im.ws.service.impl;
import cn.hutool.core.thread.ThreadFactoryBuilder; import cn.hutool.core.thread.ThreadFactoryBuilder;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import com.wecloud.im.ws.model.ResponseModel; import com.wecloud.im.ws.model.WsResponseModel;
import com.wecloud.im.ws.model.WsConstants; import com.wecloud.im.ws.model.WsConstants;
import com.wecloud.im.ws.model.request.ReceiveModel; import com.wecloud.im.ws.model.request.ReceiveModel;
import com.wecloud.im.ws.service.MangerChannelService; import com.wecloud.im.ws.service.MangerChannelService;
...@@ -62,7 +62,7 @@ public class WriteDataServiceImpl implements WriteDataService { ...@@ -62,7 +62,7 @@ public class WriteDataServiceImpl implements WriteDataService {
@Override @Override
public void dataAndStatus(ReceiveModel receiveModel, ApiCode apiCode, Object data, String toAppKey, String toClientId) { public void dataAndStatus(ReceiveModel receiveModel, ApiCode apiCode, Object data, String toAppKey, String toClientId) {
ApiResult<Boolean> apiResult = ApiResult.result(apiCode); ApiResult<Boolean> apiResult = ApiResult.result(apiCode);
ResponseModel responseModel = new ResponseModel(); WsResponseModel responseModel = new WsResponseModel();
responseModel.setMsg(apiResult.getMessage()); responseModel.setMsg(apiResult.getMessage());
responseModel.setCmd(receiveModel.getCmd()); responseModel.setCmd(receiveModel.getCmd());
responseModel.setReqId(receiveModel.getReqId()); responseModel.setReqId(receiveModel.getReqId());
...@@ -72,7 +72,7 @@ public class WriteDataServiceImpl implements WriteDataService { ...@@ -72,7 +72,7 @@ public class WriteDataServiceImpl implements WriteDataService {
} }
@Override @Override
public void write(ResponseModel responseModel, String toAppKey, String toClientId) { public void write(WsResponseModel responseModel, String toAppKey, String toClientId) {
WRITE_TASK_THREAD_POOL_EXECUTOR.execute( WRITE_TASK_THREAD_POOL_EXECUTOR.execute(
() -> { () -> {
......
...@@ -6,12 +6,12 @@ import com.wecloud.im.ws.model.request.ReceiveModel; ...@@ -6,12 +6,12 @@ import com.wecloud.im.ws.model.request.ReceiveModel;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
/** /**
* @Description 接收netty不同类型请求 * @Description 处理Cmd请求
* 抽象类 策略设计模式 * 抽象类 策略设计模式
* @Author hewei hwei1233@163.com * @Author hewei hwei1233@163.com
* @Date 2020-01-02 * @Date 2020-01-02
*/ */
public abstract class AbstractReceiveStrategy { public abstract class ImCmdAbstract {
/** /**
* 处理业务流程 * 处理业务流程
......
...@@ -11,15 +11,15 @@ import java.util.Map; ...@@ -11,15 +11,15 @@ import java.util.Map;
* 维护指令码与策略实现的对应 * 维护指令码与策略实现的对应
* @Author hewei hwei1233@163.com * @Author hewei hwei1233@163.com
*/ */
public class ReceiveStrategyContext { public class ImCmdContext {
private final Map<WsRequestCmdEnum, Class> strategyMap; private final Map<WsRequestCmdEnum, Class> strategyMap;
public ReceiveStrategyContext(Map<WsRequestCmdEnum, Class> strategyMap) { public ImCmdContext(Map<WsRequestCmdEnum, Class> strategyMap) {
this.strategyMap = strategyMap; this.strategyMap = strategyMap;
} }
public AbstractReceiveStrategy getStrategy(WsRequestCmdEnum wsRequestPathEnum) { public ImCmdAbstract getStrategy(WsRequestCmdEnum wsRequestPathEnum) {
if (wsRequestPathEnum == null) { if (wsRequestPathEnum == null) {
throw new IllegalArgumentException("not fond enum"); throw new IllegalArgumentException("not fond enum");
...@@ -35,6 +35,6 @@ public class ReceiveStrategyContext { ...@@ -35,6 +35,6 @@ public class ReceiveStrategyContext {
throw new IllegalArgumentException("not fond strategy for type:" + wsRequestPathEnum.getCmdCode()); throw new IllegalArgumentException("not fond strategy for type:" + wsRequestPathEnum.getCmdCode());
} }
return (AbstractReceiveStrategy) SpringBeanUtils.getBean(aClass); return (ImCmdAbstract) SpringBeanUtils.getBean(aClass);
} }
} }
package com.wecloud.im.ws.strategy; package com.wecloud.im.ws.strategy;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.wecloud.im.ws.annotation.ReceiveTypeAnnotation; import com.wecloud.im.ws.annotation.CmdTypeAnnotation;
import com.wecloud.im.ws.enums.WsRequestCmdEnum; import com.wecloud.im.ws.enums.WsRequestCmdEnum;
import com.wecloud.im.ws.utils.ClassScanner; import com.wecloud.im.ws.utils.ClassScanner;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
...@@ -22,7 +22,7 @@ import java.util.Set; ...@@ -22,7 +22,7 @@ import java.util.Set;
* @Date 2020-01-02 * @Date 2020-01-02
*/ */
@Component @Component
public class ReceiveStrategyProcessor implements BeanFactoryPostProcessor { public class ImCmdStrategyProcessor implements BeanFactoryPostProcessor {
// 扫码注解的包路径 // 扫码注解的包路径
private static final String STRATEGY_PACK = "com.wecloud.im.ws.strategy.concrete"; private static final String STRATEGY_PACK = "com.wecloud.im.ws.strategy.concrete";
...@@ -32,20 +32,20 @@ public class ReceiveStrategyProcessor implements BeanFactoryPostProcessor { ...@@ -32,20 +32,20 @@ public class ReceiveStrategyProcessor implements BeanFactoryPostProcessor {
Map<WsRequestCmdEnum, Class> handlerMap = Maps.newHashMapWithExpectedSize(5); Map<WsRequestCmdEnum, Class> handlerMap = Maps.newHashMapWithExpectedSize(5);
// 扫码ReceiveTypeAnnotation注解的类 // 扫码ReceiveTypeAnnotation注解的类
Set<Class<?>> classSet = ClassScanner.scan(STRATEGY_PACK, ReceiveTypeAnnotation.class); Set<Class<?>> classSet = ClassScanner.scan(STRATEGY_PACK, CmdTypeAnnotation.class);
classSet.forEach(clazz -> { classSet.forEach(clazz -> {
// 获取注解中的类型值,与枚举类一一对应 // 获取注解中的类型值,与枚举类一一对应
WsRequestCmdEnum type = clazz.getAnnotation(ReceiveTypeAnnotation.class).type(); WsRequestCmdEnum type = clazz.getAnnotation(CmdTypeAnnotation.class).type();
handlerMap.put(type, clazz); handlerMap.put(type, clazz);
}); });
// 初始化Contenxt, 将其注册到spring容器当中 // 初始化Contenxt, 将其注册到spring容器当中
ReceiveStrategyContext context = new ReceiveStrategyContext(handlerMap); ImCmdContext context = new ImCmdContext(handlerMap);
try { try {
configurableListableBeanFactory.registerResolvableDependency(Class.forName(ReceiveStrategyContext.class.getName()), context); configurableListableBeanFactory.registerResolvableDependency(Class.forName(ImCmdContext.class.getName()), context);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
} }
......
...@@ -3,6 +3,7 @@ package com.wecloud.im.ws.strategy.concrete; ...@@ -3,6 +3,7 @@ package com.wecloud.im.ws.strategy.concrete;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import com.turo.pushy.apns.PushType;
import com.wecloud.im.entity.ImApplication; import com.wecloud.im.entity.ImApplication;
import com.wecloud.im.entity.ImClient; import com.wecloud.im.entity.ImClient;
import com.wecloud.im.entity.ImConversationMembers; import com.wecloud.im.entity.ImConversationMembers;
...@@ -15,13 +16,14 @@ import com.wecloud.im.service.ImClientService; ...@@ -15,13 +16,14 @@ import com.wecloud.im.service.ImClientService;
import com.wecloud.im.service.ImConversationMembersService; import com.wecloud.im.service.ImConversationMembersService;
import com.wecloud.im.service.ImInboxService; import com.wecloud.im.service.ImInboxService;
import com.wecloud.im.service.ImMessageService; import com.wecloud.im.service.ImMessageService;
import com.wecloud.im.ws.annotation.ReceiveTypeAnnotation; import com.wecloud.im.ws.annotation.CmdTypeAnnotation;
import com.wecloud.im.ws.enums.WsRequestCmdEnum; import com.wecloud.im.ws.enums.WsRequestCmdEnum;
import com.wecloud.im.ws.model.ResponseModel; import com.wecloud.im.ws.enums.WsResponseCmdEnum;
import com.wecloud.im.ws.model.WsResponseModel;
import com.wecloud.im.ws.model.request.ReceiveModel; import com.wecloud.im.ws.model.request.ReceiveModel;
import com.wecloud.im.ws.sender.PushTask; import com.wecloud.im.ws.sender.SystemPush;
import com.wecloud.im.ws.service.WriteDataService; import com.wecloud.im.ws.service.WriteDataService;
import com.wecloud.im.ws.strategy.AbstractReceiveStrategy; import com.wecloud.im.ws.strategy.ImCmdAbstract;
import io.geekidea.springbootplus.framework.common.api.ApiCode; import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult; import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.shiro.util.SnowflakeUtil; import io.geekidea.springbootplus.framework.shiro.util.SnowflakeUtil;
...@@ -38,14 +40,14 @@ import java.util.List; ...@@ -38,14 +40,14 @@ import java.util.List;
/** /**
* @Description 处理app数据消息 * @Description 处理app数据消息
*/ */
@ReceiveTypeAnnotation(type = WsRequestCmdEnum.DATA) @CmdTypeAnnotation(type = WsRequestCmdEnum.DATA)
@Service @Service
@Slf4j @Slf4j
public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy { public class ImChatConcrete extends ImCmdAbstract {
private static final String TO_CONVERSATION_KEY = "toConversation"; private static final String TO_CONVERSATION_KEY = "toConversation";
private static final String PUSH_KEY = "push"; public static final String PUSH_KEY = "push";
private static final String MSG_ID = "msgId"; public static final String MSG_ID = "msgId";
private static final JsonMapper JSON_MAPPER = new JsonMapper(); private static final JsonMapper JSON_MAPPER = new JsonMapper();
...@@ -71,12 +73,11 @@ public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy { ...@@ -71,12 +73,11 @@ public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy {
private ImClientService imClientService; private ImClientService imClientService;
@Autowired @Autowired
private PushTask pushTask; private SystemPush systemPush;
@Override @Override
public void process(ReceiveModel receiveModel, ChannelHandlerContext ctx, String data, String appKey, String clientId) throws JsonProcessingException { public void process(ReceiveModel receiveModel, ChannelHandlerContext ctx, String data, String appKey, String clientId) throws JsonProcessingException {
// String language = ctx.channel().attr(MangerChannelService.LANGUAGE).get();
// 查询imApplication // 查询imApplication
ImApplication imApplication = imApplicationService.getOneByAppKey(appKey); ImApplication imApplication = imApplicationService.getOneByAppKey(appKey);
...@@ -171,8 +172,8 @@ public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy { ...@@ -171,8 +172,8 @@ public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy {
} }
// 向接收方推送 // 向接收方推送
ResponseModel<ImMessageOnlineSend> responseModel = new ResponseModel<>(); WsResponseModel<ImMessageOnlineSend> responseModel = new WsResponseModel<>();
responseModel.setCmd(ResponseModel.ONLINE_MSG); responseModel.setCmd(WsResponseCmdEnum.ONLINE_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS); ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode()); responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage()); responseModel.setMsg(result.getMessage());
...@@ -181,13 +182,13 @@ public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy { ...@@ -181,13 +182,13 @@ public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy {
writeDataService.write(responseModel, appKey, imClientReceiver.getClientId()); writeDataService.write(responseModel, appKey, imClientReceiver.getClientId());
// 异步推送系统通知消息 // 异步推送系统通知消息
pushTask.push(pushMap, imClientReceiver, imApplication); systemPush.push(pushMap, imClientReceiver, imApplication, PushType.ALERT);
} }
// 响应发送方消息id等信息 // 响应发送方消息id等信息
ResponseModel<HashMap<String, Long>> responseModel = new ResponseModel<>(); WsResponseModel<HashMap<String, Long>> responseModel = new WsResponseModel<>();
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS); ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCmd(ResponseModel.RES); responseModel.setCmd(WsResponseCmdEnum.RES.getCmdCode());
responseModel.setCode(result.getCode()); responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage()); responseModel.setMsg(result.getMessage());
HashMap<String, Long> stringHashMap = new HashMap<>(3); HashMap<String, Long> stringHashMap = new HashMap<>(3);
...@@ -232,9 +233,9 @@ public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy { ...@@ -232,9 +233,9 @@ public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy {
log.info("被对方拉黑了"); log.info("被对方拉黑了");
// 响应发送方 // 响应发送方
ResponseModel<HashMap<String, Long>> responseModel = new ResponseModel<>(); WsResponseModel<HashMap<String, Long>> responseModel = new WsResponseModel<>();
ApiResult<Boolean> result = ApiResult.result(ApiCode.IS_BE_BLACK); ApiResult<Boolean> result = ApiResult.result(ApiCode.IS_BE_BLACK);
responseModel.setCmd(ResponseModel.RES); responseModel.setCmd(WsResponseCmdEnum.RES.getCmdCode());
responseModel.setCode(result.getCode()); responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage()); responseModel.setMsg(result.getMessage());
responseModel.setReqId(receiveModel.getReqId()); responseModel.setReqId(receiveModel.getReqId());
...@@ -247,9 +248,9 @@ public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy { ...@@ -247,9 +248,9 @@ public class ImConcreteReceiveStrategy extends AbstractReceiveStrategy {
if (black) { if (black) {
log.info("你把对方拉黑了"); log.info("你把对方拉黑了");
// 响应发送方 // 响应发送方
ResponseModel<HashMap<String, Long>> responseModel = new ResponseModel<>(); WsResponseModel<HashMap<String, Long>> responseModel = new WsResponseModel<>();
ApiResult<Boolean> result = ApiResult.result(ApiCode.IS_TO_BLACK); ApiResult<Boolean> result = ApiResult.result(ApiCode.IS_TO_BLACK);
responseModel.setCmd(ResponseModel.RES); responseModel.setCmd(WsResponseCmdEnum.RES.getCmdCode());
responseModel.setCode(result.getCode()); responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage()); responseModel.setMsg(result.getMessage());
responseModel.setReqId(receiveModel.getReqId()); responseModel.setReqId(receiveModel.getReqId());
......
package com.wecloud.im.ws.strategy.concrete;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.wecloud.im.service.ImApplicationService;
import com.wecloud.im.service.ImClientBlacklistService;
import com.wecloud.im.service.ImClientService;
import com.wecloud.im.service.ImConversationMembersService;
import com.wecloud.im.service.ImInboxService;
import com.wecloud.im.service.ImMessageService;
import com.wecloud.im.ws.annotation.CmdTypeAnnotation;
import com.wecloud.im.ws.enums.WsRequestCmdEnum;
import com.wecloud.im.ws.model.request.ReceiveModel;
import com.wecloud.im.ws.sender.SystemPush;
import com.wecloud.im.ws.service.WriteDataService;
import com.wecloud.im.ws.strategy.ImCmdAbstract;
import com.wecloud.rtc.entity.RtcSubCmd;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 处理RTC信令消息
*/
@CmdTypeAnnotation(type = WsRequestCmdEnum.SINGLE_RTC)
@Service
@Slf4j
public class SingleRtcConcrete extends ImCmdAbstract {
private static final JsonMapper JSON_MAPPER = new JsonMapper();
@Autowired
private ImClientBlacklistService imClientBlacklistService;
@Autowired
private WriteDataService writeDataService;
@Autowired
private ImMessageService imMessageService;
@Autowired
private ImInboxService imInboxService;
@Autowired
private ImApplicationService imApplicationService;
@Autowired
private ImConversationMembersService imConversationMembersService;
@Autowired
private ImClientService imClientService;
@Autowired
private SystemPush systemPush;
@Override
public void process(ReceiveModel receiveModel, ChannelHandlerContext ctx, String data, String appKey, String clientId) throws JsonProcessingException {
// 指令判空
if (receiveModel.getData().get(RtcSubCmd.SUB_CMD) == null) {
return;
}
String cmd = receiveModel.getData().get(RtcSubCmd.SUB_CMD).toString();
switch (cmd) {
// //创建频道
// case RtcSubCmd.CREATE:
// break;
//
// //加入频道
// case RtcSubCmd.JOIN:
// break;
//
// //拒绝加入频道
// case RtcSubCmd.REJECT:
// break;
//
// //SDP数据转发
// case RtcSubCmd.SDP:
// break;
//
// //主动挂断(离开频道)
// case RtcSubCmd.LEAVE:
// break;
}
}
}
...@@ -22,6 +22,52 @@ public class RedisUtils { ...@@ -22,6 +22,52 @@ public class RedisUtils {
@Autowired @Autowired
private StringRedisTemplate redisTemplate; private StringRedisTemplate redisTemplate;
public StringRedisTemplate redisTemplate() {
return redisTemplate;
}
/**
* 添加Key:value
*
* @param key
* @param value
*/
public void setKey(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 删除Key
*
* @param key 如果传入hash类型的key,则把整个hash中所有field删除
*/
public boolean delKey(String key) {
return redisTemplate.delete(key);
}
/**
* 获取Key
*
* @param key
*/
public String getKey(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 模糊查询
*
* @param key
* @return
*/
public Set<String> keys(String key) {
return redisTemplate.keys(key);
}
/** /**
* 获取hash中field对应的值 * 获取hash中field对应的值
* *
...@@ -29,7 +75,7 @@ public class RedisUtils { ...@@ -29,7 +75,7 @@ public class RedisUtils {
* @param field * @param field
* @return * @return
*/ */
public String hget(String key, String field) { public String hashget(String key, String field) {
Object val = redisTemplate.opsForHash().get(key, field); Object val = redisTemplate.opsForHash().get(key, field);
return val == null ? null : val.toString(); return val == null ? null : val.toString();
} }
...@@ -41,7 +87,7 @@ public class RedisUtils { ...@@ -41,7 +87,7 @@ public class RedisUtils {
* @param field * @param field
* @param value * @param value
*/ */
public void hset(String key, String field, String value) { public void hashset(String key, String field, String value) {
redisTemplate.opsForHash().put(key, field, value); redisTemplate.opsForHash().put(key, field, value);
} }
...@@ -52,16 +98,17 @@ public class RedisUtils { ...@@ -52,16 +98,17 @@ public class RedisUtils {
* @param key * @param key
* @param field * @param field
*/ */
public void hdel(String key, String field) { public void hashdel(String key, String field) {
redisTemplate.opsForHash().delete(key, field); redisTemplate.opsForHash().delete(key, field);
} }
/** /**
* 删除key * 删除key
* *
* @param key 如果传入hash类型的key,则把整个hash中所有field删除 * @param key 如果传入hash类型的key,则把整个hash中所有field删除
*/ */
public void kdel(String key) { public void keydel(String key) {
redisTemplate.delete(key); redisTemplate.delete(key);
} }
...@@ -71,7 +118,7 @@ public class RedisUtils { ...@@ -71,7 +118,7 @@ public class RedisUtils {
* @param key * @param key
* @return * @return
*/ */
public Map<String, String> hgetll(String key) { public Map<String, String> hashgetll(String key) {
return redisTemplate.execute((RedisCallback<Map<String, String>>) con -> { return redisTemplate.execute((RedisCallback<Map<String, String>>) con -> {
Map<byte[], byte[]> result = con.hGetAll(key.getBytes()); Map<byte[], byte[]> result = con.hGetAll(key.getBytes());
if (CollectionUtils.isEmpty(result)) { if (CollectionUtils.isEmpty(result)) {
...@@ -93,7 +140,7 @@ public class RedisUtils { ...@@ -93,7 +140,7 @@ public class RedisUtils {
* @param fields * @param fields
* @return * @return
*/ */
public Map<String, String> hmget(String key, List<String> fields) { public Map<String, String> hashmget(String key, List<String> fields) {
List<String> result = redisTemplate.<String, String>opsForHash().multiGet(key, fields); List<String> result = redisTemplate.<String, String>opsForHash().multiGet(key, fields);
Map<String, String> ans = new HashMap<>(fields.size()); Map<String, String> ans = new HashMap<>(fields.size());
int index = 0; int index = 0;
...@@ -126,7 +173,7 @@ public class RedisUtils { ...@@ -126,7 +173,7 @@ public class RedisUtils {
} }
/** /**
* 获取指定key中存放set<String>的集合 * 获取指定key中存放set<String>的集合
* *
* @param key * @param key
*/ */
...@@ -134,12 +181,5 @@ public class RedisUtils { ...@@ -134,12 +181,5 @@ public class RedisUtils {
return redisTemplate.opsForSet().members(key); return redisTemplate.opsForSet().members(key);
} }
/**
* 删除指定key缓存
*
* @param key
*/
public void deleteByKey(String key) {
redisTemplate.delete(key);
}
} }
package com.wecloud.rtc.entity;
import java.io.Serializable;
public class RtcSubCmd implements Serializable {
/**
* subCmd子类型指令码
*/
public static final String SUB_CMD = "subCmd";
}
package com.wecloud.rtc.entity.redis;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* rtc 频道信息
*/
@Data
public class RtcChannelInfo implements Serializable {
@ApiModelProperty("当前房主")
private String owner;
private String appKey;
// private String appId;
@ApiModelProperty("创建时间")
private Long createTimestamp;
}
package com.wecloud.rtc.entity.redis;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* rtc 频道内的用户
*/
@Data
public class RtcJoinUser implements Serializable {
@ApiModelProperty("客户端")
private String clientId;
@ApiModelProperty("加入时间")
private Long createTimestamp;
private String sdpData;
private String sdpType;
}
package com.wecloud.rtc.entity.redis;
import lombok.Data;
import java.io.Serializable;
/**
* rtc 频道内所有用户
*/
@Data
public class RtcJoinUsers implements Serializable {
}
package com.wecloud.rtc.entity.redis;
import java.io.Serializable;
public class RtcRedisKey implements Serializable {
/**
* 维护频道信息 (kv)
*/
public static final String RTC_CHANNEL_INFO = "r:ci:%s";
/**
* 维护用户当前在线的频道ID ( kv)
* user_join_channel = ujc
* rcu:clientA = 10001
* rcu:clientB = 10001
* rcu:clientC = 10002
* rcu:clientD = 10003
*/
public static final String USER_JOIN_CHANNEL = "r:ujc:%s";
/**
* 维护频道中存在的用户 (set 集合):
* rtc_channel_users = rcu
* rcu:10001 = clientA , clientB
* rcu:10002 = clientC
* rcu:10003 = clientD
*/
public static final String RTC_CHANNEL_USERS = "r:cu:%s";
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcCallResponse extends RtcSubDataBase implements Serializable {
private String type;
private Long conversationId;
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcCandidateForwardResponse extends RtcSubDataBase implements Serializable {
/**
* 转发的候选者数据
*/
private String candidateData;
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcClientJoinResponse extends RtcSubDataBase implements Serializable {
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcClientLeaveResponse extends RtcSubDataBase implements Serializable {
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcClientRejectResponse extends RtcSubDataBase implements Serializable {
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import java.io.Serializable;
/**
* webRtc websocket下发数据封装类
*
* @param <T>
*/
@Data
public class RtcResponseBase<T> implements Serializable {
/**
* 子指令
*/
private Integer subCmd;
/**
* 根据不同子指令 不同的实体
*/
private T subData;
/**
* 自定义拓展字段
*/
private String attrs;
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcSdpForwardResponse extends RtcSubDataBase implements Serializable {
/**
* channelId : 1234263457652
* clientId : 7657567
* sdpData : xxxxxxxxxxxxxxxx
* sdpType : Offer/Answer
*/
private String sdpData;
private String sdpType;
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import java.io.Serializable;
@Data
public class RtcSubDataBase implements Serializable {
private Long channelId;
private String clientId;
private Long timestamp;
}
package com.wecloud.rtc.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.wecloud.rtc.entity.redis.RtcChannelInfo;
import java.util.List;
/**
* 管理rtc频道
*/
public interface MangerRtcCacheService {
/**
* 移除频道信息
*
* @param rtcChannelId
* @return
*/
boolean delChannelInfo(Long rtcChannelId);
/**
* 频道中客户端是否为空
*
* @param rtcChannelId
* @return
*/
boolean channelIsEmpty(Long rtcChannelId);
/**
* 获取频道信息
*
* @param rtcChannelId
* @return
* @throws JsonProcessingException
*/
RtcChannelInfo getRtcChannelInfo(Long rtcChannelId) throws JsonProcessingException;
/**
* 创建一个频道
*
* @param appKey
* @param clientId
* @param rtcChannelId 雪花算法生成频道id
*/
void create(String appKey, String clientId, Long rtcChannelId) throws JsonProcessingException;
/**
* 加入频道
*/
void join(String appKey, String clientId, Long rtcChannelId);
/**
* 退出频道
*/
void leave(String appKey, String clientId, Long rtcChannelId);
/**
* 根据频道ID获取频道内所有client
*/
List<String> getClientListByRtcChannelId(Long rtcChannelId);
/**
* 根据客户端ID获取该客户端加入的频道ID
*/
Long getRtcChannelIdListByClientId(String appKey, String clientId);
/**
* 获取客户端忙线/空闲状态
*
* @param appKey
* @param clientId
* @return true:忙线,false空闲
*/
boolean getBusyStatus(String appKey, String clientId);
}
package com.wecloud.rtc.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.wecloud.im.param.rtc.CandidateForwardParam;
import com.wecloud.im.param.rtc.CreateRtcChannelParam;
import com.wecloud.im.param.rtc.CreateRtcChannelResult;
import com.wecloud.im.param.rtc.JoinRtcChannelParam;
import com.wecloud.im.param.rtc.LeaveRtcChannelParam;
import com.wecloud.im.param.rtc.RejectRtcChannelParam;
import com.wecloud.im.param.rtc.SdpForwardParam;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
/**
* 管理rtc频道
*/
public interface RtcService {
/**
* 客户端离线
*/
void clientOffline(String appKey, String clientId);
/**
* 创建一个频道,并向接收方发送系统推送
*/
ApiResult<CreateRtcChannelResult> createAndCall(CreateRtcChannelParam createRtcChannelParam) throws JsonProcessingException;
/**
* 加入频道
*/
ApiResult<Boolean> join(JoinRtcChannelParam joinRtcChannelParam);
/**
* 拒接加入频道
*/
ApiResult<Boolean> reject(RejectRtcChannelParam rejectRtcChannelParam);
/**
* 退出频道
*/
ApiResult<Boolean> leave(LeaveRtcChannelParam leaveRtcChannelParam);
/**
* SDP数据转发
*
* @param sdpForwardParam
* @return
*/
ApiResult<Boolean> sdpForward(SdpForwardParam sdpForwardParam);
/**
* candidate候选者数据转发
*
* @param candidateForwardParam
* @return
*/
ApiResult<Boolean> candidateForward(CandidateForwardParam candidateForwardParam);
}
package com.wecloud.rtc.service;
import com.wecloud.rtc.entity.response.RtcCallResponse;
import com.wecloud.rtc.entity.response.RtcCandidateForwardResponse;
import com.wecloud.rtc.entity.response.RtcClientJoinResponse;
import com.wecloud.rtc.entity.response.RtcClientLeaveResponse;
import com.wecloud.rtc.entity.response.RtcClientRejectResponse;
import com.wecloud.rtc.entity.response.RtcSdpForwardResponse;
/**
* WebRtc webSocket下发指令数据
*/
public interface WsRtcWrite {
/**
* 接收到RTC邀请
*/
void rtcCall(RtcCallResponse rtcCallResponse, String toAppKey, String toClientId);
/**
* 用户状态更新事件(用户加入频道)
*/
void clientJoin(RtcClientJoinResponse rtcClientJoinResponse, String toAppKey, String toClientId);
/**
* 用户状态更新事件(用户退出频道)
*/
void clientLeave(RtcClientLeaveResponse rtcClientLeaveResponse, String toAppKey, String toClientId);
/**
* 用户状态更新事件(用户拒接邀请;不同意进入频道)
*/
void clientReject(RtcClientRejectResponse rtcClientRejectResponse, String toAppKey, String toClientId);
/**
* SDP数据转发
*/
void sdpForward(RtcSdpForwardResponse rtcSdpForwardResponse, String toAppKey, String toClientId);
/**
* candidate候选者数据转发
*/
void candidateForward(RtcCandidateForwardResponse rtcCandidateForwardResponse, String toAppKey, String toClientId);
}
package com.wecloud.rtc.service.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.wecloud.im.ws.utils.RedisUtils;
import com.wecloud.rtc.entity.redis.RtcChannelInfo;
import com.wecloud.rtc.entity.redis.RtcRedisKey;
import com.wecloud.rtc.service.MangerRtcCacheService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
@Service
public class MangerRtcCacheServiceImpl implements MangerRtcCacheService {
@Autowired
private RedisUtils redisUtils;
@Override
public boolean delChannelInfo(Long rtcChannelId) {
String channelKey = String.format(RtcRedisKey.RTC_CHANNEL_INFO, rtcChannelId);
redisUtils.delKey(channelKey);
return true;
}
@Override
public boolean channelIsEmpty(Long rtcChannelId) {
List<String> clientListByRtcChannelId = getClientListByRtcChannelId(rtcChannelId);
// // 移除自己
// clientListByRtcChannelId.remove(appKey + clientId);
return clientListByRtcChannelId.isEmpty();
}
@Override
public RtcChannelInfo getRtcChannelInfo(Long rtcChannelId) throws JsonProcessingException {
// 频道信息
String channelKey = String.format(RtcRedisKey.RTC_CHANNEL_INFO, rtcChannelId);
String value = redisUtils.getKey(channelKey);
if (StringUtils.isBlank(value)) {
return null;
}
JsonMapper jsonMapper = new JsonMapper();
return jsonMapper.readValue(value, RtcChannelInfo.class);
}
@Override
public void create(String appKey, String clientId, Long rtcChannelId) throws JsonProcessingException {
// --- 频道信息
RtcChannelInfo rtcChannelInfo = new RtcChannelInfo();
rtcChannelInfo.setAppKey(appKey);
// rtcChannelInfo.setAppId("");
//当前房主
rtcChannelInfo.setOwner(clientId);
//创建时间
rtcChannelInfo.setCreateTimestamp(new Date().getTime());
String rtcChannelInfoJson = new JsonMapper().writeValueAsString(rtcChannelInfo);
// --- 保存频道信息
String channelKey = String.format(RtcRedisKey.RTC_CHANNEL_INFO, rtcChannelId);
redisUtils.setKey(channelKey, rtcChannelInfoJson);
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
redisUtils.setKey(userJoinChannelKey, rtcChannelId.toString());
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
redisUtils.addForSet(rtcChannelUsers, clientId);
}
@Override
public void join(String appKey, String clientId, Long rtcChannelId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
redisUtils.setKey(userJoinChannelKey, rtcChannelId.toString());
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
redisUtils.addForSet(rtcChannelUsers, clientId);
}
@Override
public void leave(String appKey, String clientId, Long rtcChannelId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
redisUtils.delKey(userJoinChannelKey);
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
redisUtils.removeForSet(rtcChannelUsers, clientId);
}
@Override
public List<String> getClientListByRtcChannelId(Long rtcChannelId) {
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
Set<String> forSetMembers = redisUtils.getForSetMembers(rtcChannelUsers);
return new ArrayList<>(forSetMembers);
}
@Override
public Long getRtcChannelIdListByClientId(String appKey, String clientId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
String key = redisUtils.getKey(userJoinChannelKey);
if (StringUtils.isBlank(key)) {
return null;
}
return Long.valueOf(key);
}
@Override
public boolean getBusyStatus(String appKey, String clientId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
String key = redisUtils.getKey(userJoinChannelKey);
return key != null && !key.isEmpty();
}
}
package com.wecloud.rtc.service.impl;
import com.wecloud.im.ws.enums.WsResponseCmdEnum;
import com.wecloud.im.ws.enums.WsRtcResponseSubCmdEnum;
import com.wecloud.im.ws.model.WsResponseModel;
import com.wecloud.im.ws.service.WriteDataService;
import com.wecloud.rtc.entity.response.RtcCallResponse;
import com.wecloud.rtc.entity.response.RtcCandidateForwardResponse;
import com.wecloud.rtc.entity.response.RtcClientJoinResponse;
import com.wecloud.rtc.entity.response.RtcClientLeaveResponse;
import com.wecloud.rtc.entity.response.RtcClientRejectResponse;
import com.wecloud.rtc.entity.response.RtcResponseBase;
import com.wecloud.rtc.entity.response.RtcSdpForwardResponse;
import com.wecloud.rtc.service.WsRtcWrite;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class WsRtcWriteImpl implements WsRtcWrite {
// private static final JsonMapper JSON_MAPPER = new JsonMapper();
@Autowired
private WriteDataService writeDataService;
@Override
public void rtcCall(RtcCallResponse rtcCallResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcCallResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.RTC_CALL.getCmdCode());
rtcResponseBase.setSubData(rtcCallResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcCallResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
@Override
public void clientJoin(RtcClientJoinResponse rtcClientJoinResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcClientJoinResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.CLIENT_JOIN.getCmdCode());
rtcResponseBase.setSubData(rtcClientJoinResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcClientJoinResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
@Override
public void clientLeave(RtcClientLeaveResponse rtcClientLeaveResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcClientLeaveResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.CLIENT_LEAVE.getCmdCode());
rtcResponseBase.setSubData(rtcClientLeaveResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcClientLeaveResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
@Override
public void clientReject(RtcClientRejectResponse rtcClientRejectResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcClientRejectResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.CLIENT_REJECT.getCmdCode());
rtcResponseBase.setSubData(rtcClientRejectResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcClientRejectResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
@Override
public void sdpForward(RtcSdpForwardResponse rtcSdpForwardResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcSdpForwardResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.SDP_FORWARD.getCmdCode());
rtcResponseBase.setSubData(rtcSdpForwardResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcSdpForwardResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
@Override
public void candidateForward(RtcCandidateForwardResponse rtcCandidateForwardResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcCandidateForwardResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.CANDIDATE_FORWARD.getCmdCode());
rtcResponseBase.setSubData(rtcCandidateForwardResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcCandidateForwardResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
}
...@@ -72,6 +72,25 @@ ...@@ -72,6 +72,25 @@
) AS r1 ) AS r1
WHERE members_count = 2 WHERE members_count = 2
</select> </select>
<select id="getRepetitionConversationAttributes" resultType="java.lang.Long">
SELECT *
FROM im_conversation
WHERE id IN (
SELECT im_conversation_members.fk_conversation_id
FROM im_conversation_members
INNER JOIN (SELECT *
FROM im_conversation_members
WHERE im_conversation_members.fk_client_id = #{clientId2}) AS im_conversation_members2
ON im_conversation_members.fk_conversation_id =
im_conversation_members2.fk_conversation_id
WHERE im_conversation_members.fk_client_id = #{clientId1}
)
AND attributes = CAST(#{attributes} AS json) LIMIT 1
</select>
<select id="getRepetitionConversationInfo" resultType="com.wecloud.im.entity.ImConversation"> <select id="getRepetitionConversationInfo" resultType="com.wecloud.im.entity.ImConversation">
SELECT im_conversation.* SELECT im_conversation.*
FROM im_conversation_members FROM im_conversation_members
......
...@@ -24,7 +24,8 @@ ...@@ -24,7 +24,8 @@
UPDATE im_inbox UPDATE im_inbox
SET `im_inbox`.`update_time` = NOW(), SET `im_inbox`.`update_time` = NOW(),
`im_inbox`.`read_msg_status` = 1, `im_inbox`.`read_msg_status` = 1,
`im_inbox`.`receiver_time` = NOW() `im_inbox`.`receiver_time` = NOW(),
`im_inbox`.`read_time` = NOW()
WHERE WHERE
im_inbox.receiver = #{clientId} im_inbox.receiver = #{clientId}
AND im_inbox.fk_msg_id IN AND im_inbox.fk_msg_id IN
...@@ -45,5 +46,11 @@ ...@@ -45,5 +46,11 @@
<include refid="Base_Column_List"/> <include refid="Base_Column_List"/>
from im_Inbox from im_Inbox
</select> </select>
<select id="countMyNotReadCount" resultType="java.lang.Integer">
SELECT COUNT(id)
FROM im_inbox
WHERE receiver = #{clientId}
AND receiver_msg_status = 0
</select>
</mapper> </mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wecloud.im.mapper.ImIosApnsMapper">
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id
, fk_app_id, apns_file_value, env, bundle_id, pwd
</sql>
<select id="getImIosApnsById" resultType="com.wecloud.im.param.ImIosApnsQueryVo">
select
<include refid="Base_Column_List"/>
from im_ios_apns where id = #{id}
</select>
<select id="getImIosApnsPageList" parameterType="com.wecloud.im.param.ImIosApnsPageParam"
resultType="com.wecloud.im.param.ImIosApnsQueryVo">
select
<include refid="Base_Column_List"/>
from im_ios_apns
</select>
</mapper>
...@@ -15,10 +15,15 @@ spring-boot-plus: ...@@ -15,10 +15,15 @@ spring-boot-plus:
spring: spring:
datasource: datasource:
url: jdbc:mysql://localhost:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true url: jdbc:mysql://localhost:3306/wecloud_im_v1_3?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root username: root
password: 123 password: 123
#//测试外网
# url: jdbc:mysql://18.136.207.16:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
# username: web
# password: axT8knPN5hAP
# Redis配置 # Redis配置
redis: redis:
database: 0 database: 0
......
...@@ -14,18 +14,43 @@ spring-boot-plus: ...@@ -14,18 +14,43 @@ spring-boot-plus:
request-log-format: false request-log-format: false
response-log-format: false response-log-format: false
spring: spring:
datasource:
url: jdbc:mysql://172.31.38.183:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: web
password: 74DXJwJE7wYehHr5VFJbc2w8
# Redis配置 # upay
# datasource:
# url: jdbc:mysql://172.31.38.183:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
# username: web
# password: 74DXJwJE7wYehHr5VFJbc2w8
# # Redis配置
# redis:
# database: 0
# host: 172.31.38.183
# password: qZ8yzTz8chSZE1ZbbRbK
# port: 6379
# 飞蛙
# datasource:
# url: jdbc:mysql://127.0.0.1:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
# username: web
# password: axT8knPN5hAP
# redis:
# database: 0
# host: 127.0.0.1
# password: JH86uc53r8Ca
# port: 6379
# 国内IM集成版
datasource:
url: jdbc:mysql://127.0.0.1:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: axT8knPN5hAP
redis: redis:
database: 0 database: 0
host: 172.31.38.183 host: 127.0.0.1
password: qZ8yzTz8chSZE1ZbbRbK password: JH86uc53r8Ca
port: 6379 port: 6379
cloud: cloud:
nacos: nacos:
discovery: discovery:
......
...@@ -14,22 +14,41 @@ spring-boot-plus: ...@@ -14,22 +14,41 @@ spring-boot-plus:
response-log-format: false response-log-format: false
#spring:
# datasource:
# url: jdbc:mysql://127.0.0.1:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
# username: web
# password: axT8knPN5hAP
#
# # Redis配置
# redis:
# database: 0
# host: 127.0.0.1
# password: JH86uc53r8Ca
# port: 6379
# cloud:
# nacos:
# discovery:
# server-addr: localhost:8848
# 国内IM测试外网
spring: spring:
datasource: datasource:
url: jdbc:mysql://127.0.0.1:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true url: jdbc:mysql://127.0.0.1:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: web username: root
password: axT8knPN5hAP password: temple123456
# Redis配置 # Redis配置
redis: redis:
database: 0 database: 0
host: 127.0.0.1 host: 127.0.0.1
password: JH86uc53r8Ca password: temple123456
port: 6379 port: 6379
cloud: cloud:
nacos: nacos:
discovery: discovery:
server-addr: localhost:8848 server-addr: localhost:8848
# 打印SQL语句和结果集,本地开发环境可开启,线上注释掉 # 打印SQL语句和结果集,本地开发环境可开启,线上注释掉
mybatis-plus: mybatis-plus:
configuration: configuration:
......
...@@ -198,6 +198,7 @@ spring-boot-plus: ...@@ -198,6 +198,7 @@ spring-boot-plus:
# 应用相关 # 应用相关
- /imApplication/** - /imApplication/**
- /signDemo/get - /signDemo/get
- /Wecloud-IM-Websocket-Docs.html
# 多行字符串权限配置 # 多行字符串权限配置
filter-chain-definitions: | filter-chain-definitions: |
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -8,4 +8,4 @@ ps aux|grep bootstrap-2.0|awk '{print $2}'|xargs kill -9 ...@@ -8,4 +8,4 @@ ps aux|grep bootstrap-2.0|awk '{print $2}'|xargs kill -9
# 启动新jar, # 启动新jar,
nohup java -jar bootstrap-2.0.jar &>/dev/null & nohup java -jar bootstrap-2.0.jar &>/dev/null &
echo "success" echo "run success"
#! /bin/shell
# 国内IM集成版测试环境
# sudo 超级权限启动, 否则无法创建log日志文件夹报错
# 停止服务
ps aux|grep bootstrap-2.0|awk '{print $2}'|xargs kill -9
#/data0/java_projects_jenkins/
# 启动新jar,
#nohup java -jar bootstrap-2.0.jar &>/dev/null &
nohup java -jar \
-Dspring.datasource.url="jdbc:mysql://127.0.0.1:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true" \
-Dspring.datasource.username=root \
-Dspring.datasource.password=temple123456 \
-Dspring.redis.database=0 \
-Dspring.redis.host=127.0.0.1 \
-Dspring.redis.password=temple123456 \
-Dspring.redis.port=6379 \
bootstrap-2.0-test.jar &>/dev/null &
echo "run success"
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name xxx.com;
ssl_certificate ssl/_.im199.com.crt;
ssl_certificate_key ssl/_.im199.com.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location /api {
proxy_pass http://localhost:8082/api/;
}
location /ws {
proxy_pass http://localhost:8899/ws; # ws结尾不带"/",实际请求服务器时不会去掉"/ws"
proxy_set_header Upgrade $http_upgrade; # 升级协议头
proxy_set_header Connection upgrade; # 升级长连接协议头
}
location / {
root html;
index index.html index.htm;
}
}
\ No newline at end of file
# wecloud-RTC音视频客户端信令对接文档
# wecloud-RTC音视频客户端信令对接文档
## 文档描述
此文档为单人RTC音视频通讯技术对接文档
由于RTC基于wecloud-im即时通讯服务,**对接RTC前,需要先对接wecloud-im服务**
## 核心概念说明
### 频道与会话
频道与会话的概念不一样
```
1. 引入"频道RtcChannel"概念, 可以不基于会话发起音视频通话
2. 目前频道只支持两个client,进行通话的两端必须先加入到同一个"频道",所有的指令都在频道内进行转发
3. 允许不在同个会话中的两个client加入到同个频道进行通话 (可配置是否两个client必须在同一个会话才能发起音视频通话)
4. 一个频道可以由通话发起者绑定到会话ID,"挂断","未接听"等状态会同步到会话, 未绑定将不同步到会话(可选)
5. 连接websocket时带上client类型, 如web,安卓,ios
client需要监听频道内 状态更新(房间断开,挂断)、用户状态更新(用户加入, 用户退出)、流状态更新(切换音频 切换视频)
6.如果接收方未在线时收到音视频通话邀请,在规定时间内发起方还在等待中,发起方重新连接可以收到音视频通话的离线信令
```
## 流程图
### 不绑定到会话 示例一
![image-20211018180059906](https://tva1.sinaimg.cn/large/008i3skNly1gvjmbmw0wcj60u016877z02.jpg)
## subCmd指令码说明
枚举类:
```
/**
* 接收到RTC邀请
*/
RTC_CALL(1),
/**
* 用户状态更新事件(用户加入频道)
*/
CLIENT_JOIN(2),
/**
* 用户状态更新事件(用户退出频道)
*/
CLIENT_LEAVE(3),
/**
* 用户状态更新事件(用户拒接邀请,不同意进入频道)
*/
CLIENT_REJECT(4),
/**
* SDP数据转发
*/
SDP_FORWARD(5),
/**
* candidate候选者数据转发
*/
CANDIDATE_FORWARD(6);
```
## 创建频道 发起RTC音视频通话 (http)
见http接口文档
## 接收方收到RTC音视频通话邀请(ws)
服务端向client接收方下发数据:
```json
{
"cmd":4,
"data":{
"subCmd":"1",
"subData":{
"type":"video",
"conversationId":null,
"channelId":1234263457652,
"timestamp":113123123,
"clientId":"client_1010"
},
"attrs":{
"a":"示例: 用户自定义的一些键值对",
"b":"存储用户自定义的一些键值对"
}
}
}
```
**参数描述**
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| conversationId | Long | 否 | 绑定的会话id |
| subCmd | String | 否 | 子类型指令 |
| clientId | String | 否 | 发起通话的客户端ID |
| channelId | Long | 否 | 由服务端创建的频道id |
| type | String | 否 | 类型: "video" 或 "voice" |
| timestamp | Timestamp | 否 | 频道创建时间戳 |
## 同意加入频道(http)
见http接口文档
## 有client加入频道(ws下发)
服务端向频道内其他client响应数据:
```json
{
"cmd":4,
"data":{
"subCmd":"2",
"subData":{
"channelId":1234263457652,
"clientId":7657567,
}
}
}
```
## 拒绝加入频道 http
见http接口文档
## 有Client拒绝加入频道(ws下发)
服务端向频道内其他client响应数据:
```json
{
"cmd":4,
"data":{
"subCmd":4,
"subData":{
"channelId":1234263457652,
"clientId":7657567,
}
}
}
```
## SDP转发
流媒体描述信息
(服务端仅负责转发)(candidate,anser,offer)
### client上传SDP (http接口)
见http接口文档
### client接收SDP (ws下发)
```json
{
"cmd":4,
"data":{
"subCmd":5,
"subData":{
"channelId":1234263457652,
"clientId":7657567,
"sdpData":"xxxxxxxxxxxxxxxx",
"sdpType":"Offer/Answer"
},
"attrs":{}
}
}
```
**参数描述**
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| channelId | Long | 否 | 频道id |
| diyParam自定义字段 | Object | 是 | 自定义拓展字段 |
| subCmd | String | 否 | 子指令 |
| sdpData | String | 否 | sdp转发的数据 |
| sdpType | String | 否 | sdp类型: Offer或Answer |
## Candidate转发
候选人信息
(服务端仅负责转发)(candidate,anser,offer)
### client上传Candidate (http接口)
见http接口文档
### client接收Candidate (ws下发)
```json
{
"cmd":4,
"data":{
"subCmd":6,
"subData":{
"channelId":1234263457652,
"clientId":7657567,
"candidateData":"xxxxxxxxxxxxxxxx",
},
"attrs":{}
}
}
```
**参数描述**
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| channelId | Long | 否 | 频道id |
| diyParam自定义字段 | Object | 是 | 自定义拓展字段 |
| subCmd | String | 否 | 子指令 |
| candidateData | String | 否 | 转发的候选者数据 |
## 主动挂断(离开频道) http
见http接口文档
## 有client离开频道(ws下发)
服务端向频道内其他client响应数据:
```json
{
"cmd":4,
"data":{
"subCmd":3,
"subData":{
"channelId":1234263457652,
"clientId":7657567
}
}
}
```
## 查询忙线状态(对方正在通话中) http
见http接口文档
## 断线重连
重新join进频道即可重连
## 对方是否还挂起
## 查询频道信息及在线人员 http
## 视频/音频切换
## 查询对方是否离开频道
# wecloud-im服务端REST API对接文档
# wecloud-im服务端REST API对接文档
## 本地API文档地址
http://192.168.1.110:8082/api/doc.html#/home
账号密码admin admin
以上只包含api接口文档 websocket对接说明在此文档中
## 测试外网
文档:
```
https://wstest.im199.com/api/doc.html#/home
```
___
测试外网请求示例:
```
https://wstest.im199.com/api/imApplication/add
```
___
ws连接示例
```
wss://wstest.im199.com/ws?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWIiLCJjbGllbnRJZCI6ImFiY2QxIiwiaXNzIjoid2VjbG91ZF9pbSIsImFwcEtleSI6IkpLdE5IZnJWVXdzaGF4ek4iLCJleHAiOjE2MjgzMjMxNDMsImlhdCI6MTYyMzEzOTE0MywianRpIjoiNWU3NzU5ZjM2ODQ3NDFiMzg4MGEyYjkwMjQ0OWZjZmYifQ.CC-iuGjNwQLH4VxFI2wZEPuP4AGabOUOiRh9snp3IB4
```
_______
## 生产环境
ws.im199.com
文档:
```
https://ws.im199.com/api/doc.html#/home
```
___
测试外网请求示例:
```
https://ws.im199.com/api/imApplication/add
```
___
ws连接示例
```
wss://ws.im199.com/ws?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWIiLCJjbGllbnRJZCI6ImFiY2QxIiwiaXNzIjoid2VjbG91ZF9pbSIsImFwcEtleSI6IkpLdE5IZnJWVXdzaGF4ek4iLCJleHAiOjE2MjgzMjMxNDMsImlhdCI6MTYyMzEzOTE0MywianRpIjoiNWU3NzU5ZjM2ODQ3NDFiMzg4MGEyYjkwMjQ0OWZjZmYifQ.CC-iuGjNwQLH4VxFI2wZEPuP4AGabOUOiRh9snp3IB4
```
_______
## 鉴权方式
### 方式一:
对于 POST 和 PUT 请求,请求的主体必须是 JSON 格式,而且 HTTP header 的 Content-Type 需要设置为 `application/json`
用户验证通过 HTTP header 来进行,**X-LC-Id** 标明正在运行的是哪个应用(应用的 App ID), **X-LC-Key** 用来授权鉴定 endpoint:
```json
curl -X PUT \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "Content-Type: application/json" \
-d '{"content": "更新一篇博客的内容"}' \
https://https://ws.im199.com/api/
```
**X-LC-Key** 通常情况下是应用的 App Key
### 方式二
**更安全的鉴权**
我们还支持一种新的 API 鉴权方式,即在 HTTP header 中使用 **X-LC-Sign** 来代替 **X-LC-Key**,以降低 App Key 的泄露风险。例如:
```json
curl -X PUT \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Sign: d5bcbb897e19b2f6633c716dfdfaf9be,1453014943466" \
-H "Content-Type: application/json" \
-d '{"content": "在 HTTP header 中使用 X-LC-Sign 来更新一篇博客的内容"}' \
https://https://ws.im199.com/api/
```
**X-LC-Sign** 的值是由 `sign,timestamp` 组成的字符串:
| 取值 | 约束 | 描述 |
| :-------- | :--- | :----------------------------------------------------------- |
| sign | 必须 | 将 timestamp 加上 App Key 组成的字符串,再对它做 MD5 签名后的结果。 |
| timestamp | 必须 | 客户端产生本次请求的 unix 时间戳(UTC),精确到**毫秒**。 |
举例来说,假设应用的信息如下:
| App Id | `FFnN2hso42Wego3pWq4X5qlu` |
| ---------- | --------------------------------- |
| App Key | `UtOCzqb67d3sN12Kts4URwy8` |
| 请求时间 | 2016-01-17 15:15:43.466 GMT+08:00 |
| timestamp | `1453014943466` |
**使用 App Key 来计算(MD5) sign**:
```sh
-H "X-LC-Sign: d5bcbb897e19b2f6633c716dfdfaf9be,1453014943466" \
```
## 响应格式
对于所有的请求,响应格式都是一个 JSON 对象。
一个请求是否成功是由 HTTP 状态码标明的。一个 2XX 的状态码表示成功,而一个 4XX 表示请求失败。当一个请求失败时响应的主体仍然是一个 JSON 对象,但是总是会包含 `code``error` 这两个字段,你可以用它们来进行调试。举个例子,如果尝试用非法的属性名来保存一个对象会得到如下信息:
## 单聊、群聊
### 创建对话
### 查询对话
### 更新对话
### 删除对话
### 增加成员
### 移除成员
### 查询成员
### 单聊、群聊-发消息
### 查询历史消息
### 单聊、群聊-修改消息
### 单聊、群聊-撤回消息
## 黑名单
### 增加对话黑名单
### 移除对话黑名单
### 查询对话黑名单
# wecloud-im 视频通话云对接文档
# wecloud-im 视频通话云对接文档
[TOC]
## 产品概述
集成视频 SDK,实现高清流畅视频通话。
视频通话 SDK 可实现一对一视频通话,同时具备纯语音通话和视频通话功能。
**即将推出多对多视频通话**
## 使用场景
##### 视频聊天
支持 1 对 1 视频通话,适用于视频聊天、视频客服、远程医疗、金融双录、远程定损等场景
##### 在线教育
视频面对面教学,真实还原线下教学场景,支持 1v1 教学
##### 视频客服
支持 1v1 专属 VIP 视频客服,助力用户服务升级,提供更优质的服务体验
**即将推出多对多视频通话**
## 文档描述
此文档为一对一音视频通话技术对接文档
由于视频通话基于wecloud-im聊天服务,**对接视频通话前,需要先对接wecloud-im聊天服务**
## 核心概念说明
### 频道
两个client加入到同一个"频道"进行音视频通话
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment