Commit d9ac2d80 by giaogiao

支持多端登陆,保存多端channel

parent c2c00664
/*
* Copyright 2019-2029 geekidea(https://github.com/geekidea)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.geekidea.springbootplus.test;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author geekidea
* @date 2020/3/16
**/
public class MapTest {
public static void main(String[] args) {
Map<String, Set<String>> hashMap = new ConcurrentHashMap<>();
Set<String> sets = new HashSet<>();
sets.add("hi");
hashMap.put("a1", sets);
sets.add("hi2");
}
}
package com.wecloud.im.ws.model; package com.wecloud.im.ws.model;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.Data; import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
/**
* 连接的client信息
*/
@Data @Data
public class UserSession implements Serializable { public class ClientInfo implements Serializable {
private NioSocketChannel nioSocketChannel;
/** /**
* 通道id * 设备ID
*/ */
private String channelId; private String deviceId;
/**
* 设备类型
*/
private String deviceType; private String deviceType;
/**
* 设备推送token
*/
private String deviceToken; private String deviceToken;
/**
* 登陆token
*/
private String token; private String token;
} }
package com.wecloud.im.ws.service; package com.wecloud.im.ws.service;
import com.wecloud.im.ws.model.UserSession; import com.wecloud.im.ws.model.ClientInfo;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
...@@ -23,11 +23,15 @@ public interface MangerChannelService { ...@@ -23,11 +23,15 @@ public interface MangerChannelService {
*/ */
Map<String, NioSocketChannel> CHANNEL_MAP = new ConcurrentHashMap<>(); Map<String, NioSocketChannel> CHANNEL_MAP = new ConcurrentHashMap<>();
/** /**
* 本地维护 uid 对应 channel_shortID * 本地维护 uid 对应 channel_shortID
*/ */
Map<String, List<UserSession>> USER_SESSION_MAP = new ConcurrentHashMap<>(); Map<String, Set<String>> CLIENTS_MAP = new ConcurrentHashMap<>();
/**
* channel_shortID对应client端数据
*/
Map<String, ClientInfo> SESSION_INFO_MAP = new ConcurrentHashMap<>();
/** /**
* CLIENT_ID,是客户端的字符串id * CLIENT_ID,是客户端的字符串id
......
package com.wecloud.im.ws.service.impl; package com.wecloud.im.ws.service.impl;
import com.wecloud.im.ws.cache.UserCache; import com.wecloud.im.ws.cache.UserCache;
import com.wecloud.im.ws.model.ClientInfo;
import com.wecloud.im.ws.service.MangerChannelService; import com.wecloud.im.ws.service.MangerChannelService;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
...@@ -12,6 +13,9 @@ import lombok.extern.slf4j.Slf4j; ...@@ -12,6 +13,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/** /**
* @Description 维护netty用户channel对象 * @Description 维护netty用户channel对象
* @Author hewei hwei1233@163.com * @Author hewei hwei1233@163.com
...@@ -21,6 +25,7 @@ import org.springframework.stereotype.Component; ...@@ -21,6 +25,7 @@ import org.springframework.stereotype.Component;
@Slf4j @Slf4j
public class MangerChannelServiceImpl implements MangerChannelService { public class MangerChannelServiceImpl implements MangerChannelService {
// private final static ThreadFactory NAMED_THREAD_FACTORY = new ThreadFactoryBuilder() // private final static ThreadFactory NAMED_THREAD_FACTORY = new ThreadFactoryBuilder()
// .setNamePrefix("rpcWrite-").build(); // .setNamePrefix("rpcWrite-").build();
/** /**
...@@ -53,7 +58,7 @@ public class MangerChannelServiceImpl implements MangerChannelService { ...@@ -53,7 +58,7 @@ public class MangerChannelServiceImpl implements MangerChannelService {
@Override @Override
public NioSocketChannel get(String appKey, String clientId) { public NioSocketChannel get(String appKey, String clientId) {
return MangerChannelService.CHANNEL_MAP.get(appKey + clientId); return this.CHANNEL_MAP.get(appKey + clientId);
} }
@Override @Override
...@@ -66,9 +71,33 @@ public class MangerChannelServiceImpl implements MangerChannelService { ...@@ -66,9 +71,33 @@ public class MangerChannelServiceImpl implements MangerChannelService {
nioSocketChannel.close(); nioSocketChannel.close();
} }
MangerChannelService.CHANNEL_MAP.put(appKey + clientId, channel); this.CHANNEL_MAP.put(appKey + clientId, channel);
String ShortChannelId = channel.id().asShortText();
this.putClientsMap(appKey, clientId, ShortChannelId);
this.putSessionInfoMap(ShortChannelId, channel);
}
void putSessionInfoMap(String ShortChannelId, NioSocketChannel channel) {
ClientInfo clientInfo = new ClientInfo();
clientInfo.setDeviceId("");
clientInfo.setDeviceType("");
clientInfo.setDeviceToken("");
clientInfo.setNioSocketChannel(channel);
clientInfo.setToken("");
this.SESSION_INFO_MAP.put(ShortChannelId, clientInfo);
}
void putClientsMap(String appKey, String clientId, String ShortChannelId) {
Set<String> set = this.CLIENTS_MAP.get(appKey + ":" + clientId);
if (set.isEmpty()) {
HashSet<String> shortChannelId = new HashSet<>();
shortChannelId.add(ShortChannelId);
this.CLIENTS_MAP.put(appKey + ":" + clientId, shortChannelId);
} else {
set.add(ShortChannelId);
}
} }
@Override @Override
...@@ -85,7 +114,7 @@ public class MangerChannelServiceImpl implements MangerChannelService { ...@@ -85,7 +114,7 @@ public class MangerChannelServiceImpl implements MangerChannelService {
String userId = String.valueOf(this.getInfoByChannel(channelHandlerContext)); String userId = String.valueOf(this.getInfoByChannel(channelHandlerContext));
userCache.offline(userId); userCache.offline(userId);
log.info("不活跃remove,uid:" + userId); log.info("不活跃remove,uid:" + userId);
MangerChannelService.CHANNEL_MAP.remove(userId); this.CHANNEL_MAP.remove(userId);
channelHandlerContext.channel().close(); channelHandlerContext.channel().close();
} }
} }
......
# wecloud-im服务端REST API对接文档 # wecloud-im服务端REST API对接文档
...@@ -168,4 +168,3 @@ curl -X PUT \ ...@@ -168,4 +168,3 @@ curl -X PUT \
### 移除对话黑名单 ### 移除对话黑名单
### 查询对话黑名单 ### 查询对话黑名单
# wecloud-im 即时通讯云对接文档 # wecloud-im 即时通讯云对接文档
...@@ -95,7 +95,7 @@ appKey为应用在蔚可云的唯一标识, appSecret为安全密钥, 均由蔚 ...@@ -95,7 +95,7 @@ appKey为应用在蔚可云的唯一标识, appSecret为安全密钥, 均由蔚
### 生成sign签名 ### 生成sign签名
sign 由接入方后端生成, 进行加签的参数: MD5{timestamp + clientId + appKey + appSecret} sign 由接入方后端生成, 进行加签的参数: MD5{timestamp + clientId + appKey + appSecret}
timestamp:毫秒时间戳 timestamp:毫秒时间戳
clientId:由后端生成 clientId:由后端生成
...@@ -151,7 +151,7 @@ clientId:由后端生成 ...@@ -151,7 +151,7 @@ clientId:由后端生成
```java ```java
import org.springframework.util.DigestUtils; import org.springframework.util.DigestUtils;
private void getSign(String timestamp, String clientId, String appKey, String appSecret) { private void getSign(String timestamp, String clientId, String appKey, String appSecret) {
String data = timestamp + clientId + appKey + appSecret; String data = timestamp + clientId + appKey + appSecret;
String sign = DigestUtils.md5DigestAsHex(data.getBytes()); String sign = DigestUtils.md5DigestAsHex(data.getBytes());
} }
...@@ -197,10 +197,43 @@ public static void main(String[] args) { ...@@ -197,10 +197,43 @@ public static void main(String[] args) {
wecloud为提供**HTTPS**,以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性,。HTTPS 在HTTP 的基础下加入**SSL**,HTTPS 的安全基础是 SSL,。保障终端设备到服务器传输时的数据安全,不被窃取. wecloud为提供**HTTPS**,以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性,。HTTPS 在HTTP 的基础下加入**SSL**,HTTPS 的安全基础是 SSL,。保障终端设备到服务器传输时的数据安全,不被窃取.
## IOS推送指南
客户方将应用APNS证书上传至wecloud控制台
## Android 混合推送开发指南
### 混合推送概述
自 Android 8.0 之后,系统权限控制越来越严,第三方推送通道的生命周期受到较大限制;同时,国内主流厂商也开始推出自己独立的推送服务,而厂商间千差万别的繁杂接口徒增了开发和代码维护的难度。为此,我们推出了混合推送的方案。我们逐一对接国内主流厂商,将它们不同的接口隐藏起来,让开发者通过统一的 API 完成推送任务。这不仅大幅降低了开发复杂度,还保障了主流 Android 系统上的推送到达率。
在混合推送方案里,消息下发时使用的通道不再是我们自己维持的 WebSocket 长连接,而是借用厂商和 OS 层的系统通道进行通信。一条推送消息下发的步骤如下:
1. 开发者调用云服务 Push API 请求对全部或特定设备进行推送;
2. 云推送服务端将请求转发给厂商的推送接口;
3. 厂商通过手机端的系统通道下发推送消息,同时手机端系统消息接收器将推送消息展示到通知栏;
4. 终端用户点击消息之后唤起目标应用或者页面。
整个流程与苹果的 APNs 推送类似,SDK 在客户端基本不会得到调用,消息的下发和展示都依赖厂商客户端的行为。所以如果部分厂商在某些推送中夹带了其他非开发者提交的消息,或者在服务启用的时候,有额外营销性质的弹窗,这都是厂商自己的行为,与我们完全无关,还请大家了解。另外,如果开发者碰到厂商 SDK 的问题,我们也无法深入调查,还请大家自行到厂商的论坛或技术支持渠道咨询解决。
Android 混合推送功能仅对商用版应用开放,如果希望使用该功能,请进入 **云服务控制台 > 推送 > 设置 > 混合推送**,打开混合推送的开关。
注意,混合推送可以随时按需开关。当该选项关闭后,下一次 Android 推送会与普通推送一样自动选择自有通道送达客户端,除了会再次遇到上面提到的自有通道在部分 ROM 上会受到限制的问题之外,不会有别的影响。而当该选项再次开启后,Android 推送又会去选择厂商推送渠道。
开启了混合推送之后,Installation 表中每一个设备对应的记录,会增加一个 vendor 字段(如果没有这一字段,则说明客户端集成有问题),其值分别为:+
| vendor | 厂商 |
| :----- | :--------------------- |
| `HMS` | 华为 HMS 推送 |
| `mi` | 小米推送 |
| `mz` | 魅族推送 |
| `oppo` | Oppo 推送 |
| `vivo` | VIVO 推送 |
| `fcm` | FCM 推送(仅限国际版) |
注意,混合推送对接的是厂商各自的推送服务,需要单独配置,不支持混用。 通常情况下,需要提交不同的版本(分别对接厂商的推送服务)到相应厂商的应用商店。 如果希望使用统一版本,那么需要自行判断手机型号,在手机上开启对应的推送。
## 富媒体消息 ## 富媒体消息
为了方便开发者的使用,我们提供了几种封装好的基于 JSON 格式的富媒体消息类型(`TypedMessage`),譬如: 为了方便开发者的使用,我们提供了几种封装好的基于 JSON 格式的富媒体消息类型(`TypedMessage`),譬如:
...@@ -212,8 +245,7 @@ wecloud为提供**HTTPS**,以安全为目标的 HTTP 通道,在HTTP的基础 ...@@ -212,8 +245,7 @@ wecloud为提供**HTTPS**,以安全为目标的 HTTP 通道,在HTTP的基础
- 位置(`LocationMessage` - 位置(`LocationMessage`
### 自定义类型消息
## 自定义类型消息
如发送红包 转账通知 好友验证等等 如发送红包 转账通知 好友验证等等
......
## 本地redis ## 本地redis
...@@ -8,4 +8,7 @@ docker run -p 3306:3306 --name mysql57 -v $PWD/dockerData/mysql57/data:/var/lib/ ...@@ -8,4 +8,7 @@ docker run -p 3306:3306 --name mysql57 -v $PWD/dockerData/mysql57/data:/var/lib/
## 本地建库语句 ## 本地建库语句
CREATE DATABASE IF NOT EXISTS wecloud_im DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE IF NOT EXISTS wecloud_im DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;
## nacos ## nacos
\ No newline at end of file
RabbitMQ
\ No newline at end of file
...@@ -301,6 +301,9 @@ com.xteng.Hibro 123456 ...@@ -301,6 +301,9 @@ com.xteng.Hibro 123456
飞蛙测试外网
https://testws.frogsell.com/api/doc.html#/home
国内IM集成版 国内IM集成版
正式环境 正式环境
121.37.208.9 121.37.208.9
...@@ -318,33 +321,33 @@ web示例: ...@@ -318,33 +321,33 @@ web示例:
imwebtest.wecloud.cn imwebtest.wecloud.cn
2021年11月05日 <<
## 多端在线 + 服务器集群 ## 多端在线 + 服务器集群
本地维护 uid 对应 channel_shortID 本地维护 uid 对应 channel_shortID
map<String id , set<channelId>>>
1123=[channelId_ababab1,channelId_ababab2,channelId_ababab3]
1123:{ channel_shortID对应client端数据
channelId = ababab1 , map<String channelId,ChannelInfo>
channelId_ababab1:{
deviceTypeId =23123,
deviceType = ios, deviceType = ios,
deviceToken = abdsbsn, deviceToken = abdsbsn,
token = sjn321sdjfsdf token = sjn321sdjfsdf
} }
1123:{ channelId_ababab1:{
channelId = ababab2 , deviceType = android,
deviceType = android, deviceToken = abdsbsn,
deviceToken = abdsbsn, token = sjn321sdjfsdf
token = sjnsdj123fsdf
} }
channelId_ababab1:{
1123:{ deviceType = web,
channelId = ababab3 , deviceToken = abdsbsn,
deviceType = web, token = null
deviceToken = abdsb123sn,
token = null
} }
2021年11月05日 >>
\ No newline at end of file
飞蛙测试外网
https://testws.frogsell.com/api/doc.html#/home
\ No newline at end of file
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