Commit c4f674bc by zhangjw

1:在原单商户的基础上 新增多商户客户逻辑 待完善和测试

parent fe2730a4
package com.ym.im.controller; package com.ym.im.controller;
import com.ym.im.entity.model.IdModel;
import com.ym.im.entity.MsgBody; import com.ym.im.entity.MsgBody;
import com.ym.im.entity.model.IdModel;
import com.ym.im.service.StaffService; import com.ym.im.service.StaffService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
...@@ -24,9 +24,9 @@ public class StaffController { ...@@ -24,9 +24,9 @@ public class StaffController {
private StaffService staffService; private StaffService staffService;
@GetMapping(value = "/getStaffList") @GetMapping(value = "/getStaffList")
@ApiOperation(value = "获取所有客服信息") @ApiOperation(value = "获取商户客服信息")
public MsgBody getStaffList() { public MsgBody getStaffList(Long merchantId) {
return staffService.getStaffList(); return staffService.getMerchantStaffGroup(merchantId);
} }
@PostMapping(value = "/forward") @PostMapping(value = "/forward")
......
...@@ -53,6 +53,11 @@ public class ChatRecord implements Serializable { ...@@ -53,6 +53,11 @@ public class ChatRecord implements Serializable {
@ApiModelProperty(value = "员工(客服)Id") @ApiModelProperty(value = "员工(客服)Id")
private Long staffId; private Long staffId;
@NotNull(message = "{error.merchant_id_empty}", groups = {Default.class})
@Positive(message = "{error.merchant_id_greater_than_zero}", groups = {Default.class})
@ApiModelProperty(value = "商户ID")
private Long merchantId;
@NotNull(message = "{error.chat_msg_type_empty}", groups = {Default.class, ChatRecordSaveGroup.class, ChatRecordSendGroup.class}) @NotNull(message = "{error.chat_msg_type_empty}", groups = {Default.class, ChatRecordSaveGroup.class, ChatRecordSendGroup.class})
@ApiModelProperty(value = "消息类型:1、聊天信息,2、PDF") @ApiModelProperty(value = "消息类型:1、聊天信息,2、PDF")
private Integer msgType; private Integer msgType;
......
//package com.ym.im.entity;
//
//import lombok.Data;
//
//import java.io.Serializable;
//import java.util.Set;
//
///**
// * @author: JJww
// * 商户对应的客服集合
// * @Date:2020/10/13
// */
//@Data
//public class Merchant implements Serializable {
//
// private Long merchantId;
//
// private Set<StaffSocketInfo> staffSocketInfoSet;
//}
...@@ -23,6 +23,8 @@ public class StaffSocketInfo extends BaseSocketInfo { ...@@ -23,6 +23,8 @@ public class StaffSocketInfo extends BaseSocketInfo {
private Set<Long> userIds; private Set<Long> userIds;
private Long merchantId;
public Set<Long> getUserIds() { public Set<Long> getUserIds() {
return userIds != null ? userIds : new HashSet<Long>(); return userIds != null ? userIds : new HashSet<Long>();
} }
......
...@@ -15,6 +15,11 @@ public class ChannelAttributeKey { ...@@ -15,6 +15,11 @@ public class ChannelAttributeKey {
/** /**
* 商户ID
*/
public static final AttributeKey<Long> MERCHANT_ID = AttributeKey.valueOf("merchant_Id");
/**
* 角色类型 * 角色类型
*/ */
public static final AttributeKey<String> ROLE_TYPE = AttributeKey.valueOf("role_type"); public static final AttributeKey<String> ROLE_TYPE = AttributeKey.valueOf("role_type");
......
package com.ym.im.handler;
import com.ym.im.entity.StaffSocketInfo;
import com.ym.im.entity.UserSocketInfo;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: JJww
* @Date:2020/10/14
*/
@Component
public class ChannelGroupHandler {
/**
* 在线用户Group
*/
public final Map<Long, UserSocketInfo> USER_GROUP = new ConcurrentHashMap<>();
/**
* 在线客服Group
*/
public final Map<Long, Map<Long, StaffSocketInfo>> STAFF_GROUP = new ConcurrentHashMap<>();
/**
* 新增商户 客服
*
* @param merchantId
* @param staffSocketInfo
*/
public void putMerchantStaff(Long merchantId, StaffSocketInfo staffSocketInfo) {
final Map<Long, StaffSocketInfo> staffSocketInfoMap = STAFF_GROUP.get(merchantId) != null ? STAFF_GROUP.get(merchantId) : new HashMap<Long, StaffSocketInfo>();
staffSocketInfoMap.put(staffSocketInfo.getStaffId(), staffSocketInfo);
STAFF_GROUP.put(merchantId, staffSocketInfoMap);
}
/**
* 移除商户 客服
*
* @param merchantId
* @param staffId
*/
public void removeMerchantStaff(Long merchantId, Long staffId) {
final Map<Long, StaffSocketInfo> staffSocketInfoMap = STAFF_GROUP.get(merchantId);
staffSocketInfoMap.remove(staffId);
if (staffSocketInfoMap.isEmpty()) {
STAFF_GROUP.remove(merchantId);
}
}
public StaffSocketInfo getMerchantStaff(Long merchantId, Long staffId) {
return STAFF_GROUP.get(merchantId).get(staffId);
}
public StaffSocketInfo getMerchantStaff(Long staffId) {
StaffSocketInfo staffSocketInfo = null;
for (Map<Long, StaffSocketInfo> staffGroup : STAFF_GROUP.values()) {
for (StaffSocketInfo staffInfo : staffGroup.values()) {
if (staffInfo.getStaffId().equals(staffId)) {
staffInfo = staffSocketInfo;
}
}
}
return staffSocketInfo;
}
}
\ No newline at end of file
...@@ -7,7 +7,6 @@ import com.ym.im.entity.base.NettyConstant; ...@@ -7,7 +7,6 @@ import com.ym.im.entity.base.NettyConstant;
import com.ym.im.entity.enums.RoleEnum; import com.ym.im.entity.enums.RoleEnum;
import com.ym.im.exception.HttpException; import com.ym.im.exception.HttpException;
import com.ym.im.factory.SingleChatFactory; import com.ym.im.factory.SingleChatFactory;
import com.ym.im.service.ChannelGroupService;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultFullHttpResponse;
...@@ -33,6 +32,9 @@ import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; ...@@ -33,6 +32,9 @@ import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
public class WebSocketHandshakerHandler extends BaseHandler<FullHttpRequest> { public class WebSocketHandshakerHandler extends BaseHandler<FullHttpRequest> {
@Autowired @Autowired
private ChannelGroupHandler channelGroup;
@Autowired
private SingleChatFactory singleChatFactory; private SingleChatFactory singleChatFactory;
public static final String ROLE_TYPE = "roleType"; public static final String ROLE_TYPE = "roleType";
...@@ -54,6 +56,7 @@ public class WebSocketHandshakerHandler extends BaseHandler<FullHttpRequest> { ...@@ -54,6 +56,7 @@ public class WebSocketHandshakerHandler extends BaseHandler<FullHttpRequest> {
ctx.channel().attr(ChannelAttributeKey.ROLE_ID).set(userId); ctx.channel().attr(ChannelAttributeKey.ROLE_ID).set(userId);
ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).set(token); ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).set(token);
ctx.channel().attr(ChannelAttributeKey.ROLE_TYPE).set(roleType); ctx.channel().attr(ChannelAttributeKey.ROLE_TYPE).set(roleType);
ctx.channel().attr(ChannelAttributeKey.MERCHANT_ID).set(merchantId);
this.sso(token, userId, roleType); this.sso(token, userId, roleType);
singleChatFactory.getService(roleType).init(ctx); singleChatFactory.getService(roleType).init(ctx);
fullHttpRequest.setUri(NettyConstant.CS); fullHttpRequest.setUri(NettyConstant.CS);
...@@ -65,16 +68,16 @@ public class WebSocketHandshakerHandler extends BaseHandler<FullHttpRequest> { ...@@ -65,16 +68,16 @@ public class WebSocketHandshakerHandler extends BaseHandler<FullHttpRequest> {
BaseSocketInfo baseSocketInfo = null; BaseSocketInfo baseSocketInfo = null;
switch (RoleEnum.get(type)) { switch (RoleEnum.get(type)) {
case APP: case APP:
baseSocketInfo = ChannelGroupService.USER_GROUP.get(roleId); baseSocketInfo = channelGroup.USER_GROUP.get(roleId);
break; break;
case merchant: case merchant:
baseSocketInfo = ChannelGroupService.STAFF_GROUP.get(roleId); baseSocketInfo = channelGroup.getMerchantStaff(roleId);
break; break;
} }
if (baseSocketInfo != null && !token.equals(baseSocketInfo.getToken())) { if (baseSocketInfo != null && !token.equals(baseSocketInfo.getToken())) {
baseSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.FORCEDOFFLINE)); baseSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.FORCEDOFFLINE));
baseSocketInfo.close(); baseSocketInfo.close();
ChannelGroupService.USER_GROUP.remove(roleId);//待完善 channelGroup.USER_GROUP.remove(roleId);//待完善
log.info("用户: " + roleId + " 被迫下线"); log.info("用户: " + roleId + " 被迫下线");
} }
} }
......
...@@ -9,7 +9,7 @@ import com.ym.im.entity.base.NettyConstant; ...@@ -9,7 +9,7 @@ import com.ym.im.entity.base.NettyConstant;
import com.ym.im.entity.enums.RoleEnum; import com.ym.im.entity.enums.RoleEnum;
import com.ym.im.entity.model.IdModel; import com.ym.im.entity.model.IdModel;
import com.ym.im.entity.model.OrderModel; import com.ym.im.entity.model.OrderModel;
import com.ym.im.service.ChannelGroupService; import com.ym.im.handler.ChannelGroupHandler;
import com.ym.im.service.StaffService; import com.ym.im.service.StaffService;
import com.ym.im.util.JsonUtils; import com.ym.im.util.JsonUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
...@@ -40,6 +40,9 @@ public class Receiver { ...@@ -40,6 +40,9 @@ public class Receiver {
@Autowired @Autowired
private StaffService staffService; private StaffService staffService;
@Autowired
private ChannelGroupHandler channelGroup;
/** /**
* 禁用用户 队列名称 * 禁用用户 队列名称
*/ */
...@@ -64,13 +67,13 @@ public class Receiver { ...@@ -64,13 +67,13 @@ public class Receiver {
//移除用户列表 //移除用户列表
redisTemplate.delete(NettyConstant.STAFF_USERIDS_KEY + staffId); redisTemplate.delete(NettyConstant.STAFF_USERIDS_KEY + staffId);
//客服真离线后 才转发 //客服真离线后 才转发
if (ChannelGroupService.STAFF_GROUP.get(staffId) == null) { if (channelGroup.getMerchantStaff(staffId) == null) {
final Set<Long> userIds = staffSocketInfo.getUserIds(); final Set<Long> userIds = staffSocketInfo.getUserIds();
log.info("客服离线队列: " + "ID: " + "UserIds:" + userIds); log.info("客服离线队列: " + "ID: " + "UserIds:" + userIds);
userIds.forEach((Long userId) -> { userIds.forEach((Long userId) -> {
//用户在线才重新分配和转发 //用户在线才重新分配和转发
if (ChannelGroupService.USER_GROUP.get(userId) != null) { if (channelGroup.USER_GROUP.get(userId) != null) {
final StaffSocketInfo idleStaff = staffService.getIdleStaff(userId); final StaffSocketInfo idleStaff = staffService.getIdleStaff(staffSocketInfo.getMerchantId(),userId);
if (idleStaff != null) { if (idleStaff != null) {
idleStaff.writeAndFlush(new MsgBody<>().setStatus(MsgBody.DISTRIBUTION_STAFF).setData(new IdModel().setStaffId(staffId).setUserId(userId))); idleStaff.writeAndFlush(new MsgBody<>().setStatus(MsgBody.DISTRIBUTION_STAFF).setData(new IdModel().setStaffId(staffId).setUserId(userId)));
} }
...@@ -89,7 +92,7 @@ public class Receiver { ...@@ -89,7 +92,7 @@ public class Receiver {
@RabbitListener(queues = USER_QUEUE_NAME) @RabbitListener(queues = USER_QUEUE_NAME)
public void disableUserHandler(String userId) { public void disableUserHandler(String userId) {
final UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(Long.valueOf(userId)); final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(Long.valueOf(userId));
if (userSocketInfo != null) { if (userSocketInfo != null) {
userSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.LOGOUT)); userSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.LOGOUT));
userSocketInfo.close(); userSocketInfo.close();
...@@ -107,11 +110,11 @@ public class Receiver { ...@@ -107,11 +110,11 @@ public class Receiver {
log.info("Constants.ORDER_QUEUE_NAME: " + JSON.toJSONString(orderModel)); log.info("Constants.ORDER_QUEUE_NAME: " + JSON.toJSONString(orderModel));
final UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(Long.valueOf(orderModel.getUserId())); final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(Long.valueOf(orderModel.getUserId()));
if (userSocketInfo == null) { if (userSocketInfo == null) {
return; return;
} }
StaffSocketInfo staffSocketInfo = ChannelGroupService.STAFF_GROUP.get(userSocketInfo.getStaffId()); StaffSocketInfo staffSocketInfo = channelGroup.getMerchantStaff(userSocketInfo.getStaffId());
final MsgBody<OrderModel> orderInfo = new MsgBody<OrderModel>().setStatus(MsgBody.ORDER).setData(orderModel); final MsgBody<OrderModel> orderInfo = new MsgBody<OrderModel>().setStatus(MsgBody.ORDER).setData(orderModel);
/** /**
* 绑定客服在线,发送订单信息 * 绑定客服在线,发送订单信息
...@@ -127,7 +130,7 @@ public class Receiver { ...@@ -127,7 +130,7 @@ public class Receiver {
final Long staffId = (Long) redisTemplate.opsForHash().get(NettyConstant.IM_USERS, orderModel.getUserId()); final Long staffId = (Long) redisTemplate.opsForHash().get(NettyConstant.IM_USERS, orderModel.getUserId());
if (staffId != null) { if (staffId != null) {
log.info("客服订单: " + "尝试给历史客服(" + staffId + ")发送订单:" + orderInfo.toString()); log.info("客服订单: " + "尝试给历史客服(" + staffId + ")发送订单:" + orderInfo.toString());
staffSocketInfo = ChannelGroupService.STAFF_GROUP.get(staffId); staffSocketInfo = channelGroup.getMerchantStaff(staffId);
if (staffSocketInfo != null) { if (staffSocketInfo != null) {
staffSocketInfo.writeAndFlush(orderInfo); staffSocketInfo.writeAndFlush(orderInfo);
log.info("客服订单: " + "给历史客服(" + staffId + ")发送订单:" + orderInfo.toString()); log.info("客服订单: " + "给历史客服(" + staffId + ")发送订单:" + orderInfo.toString());
...@@ -150,7 +153,7 @@ public class Receiver { ...@@ -150,7 +153,7 @@ public class Receiver {
if (msgBody != null && chatRecord.getRetryCount().intValue() < NettyConstant.RETRY_COUNT.intValue()) { if (msgBody != null && chatRecord.getRetryCount().intValue() < NettyConstant.RETRY_COUNT.intValue()) {
UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(userId); UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(userId);
if (userSocketInfo == null) { if (userSocketInfo == null) {
return; return;
} }
...@@ -165,7 +168,7 @@ public class Receiver { ...@@ -165,7 +168,7 @@ public class Receiver {
case APP: case APP:
Long staffId = userSocketInfo.getStaffId() == null ? chatRecord.getStaffId() : userSocketInfo.getStaffId(); Long staffId = userSocketInfo.getStaffId() == null ? chatRecord.getStaffId() : userSocketInfo.getStaffId();
StaffSocketInfo staffSocketInfo = ChannelGroupService.STAFF_GROUP.get(staffId); StaffSocketInfo staffSocketInfo = channelGroup.getMerchantStaff(staffId);
if (staffSocketInfo != null) { if (staffSocketInfo != null) {
staffSocketInfo.writeAndFlush(msgBody); staffSocketInfo.writeAndFlush(msgBody);
} }
......
package com.ym.im.service;
import com.ym.im.entity.StaffSocketInfo;
import com.ym.im.entity.UserSocketInfo;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: JJww
* Channe管理
* @Date:2019-05-21
*/
public interface ChannelGroupService {
/**
* 在线用户Group
*/
Map<Long, UserSocketInfo> USER_GROUP = new ConcurrentHashMap<>();
/**
* 在线客服Group
*/
Map<Long, StaffSocketInfo> STAFF_GROUP = new ConcurrentHashMap<>();
}
...@@ -17,7 +17,7 @@ public interface StaffService { ...@@ -17,7 +17,7 @@ public interface StaffService {
* @param userId * @param userId
* @return * @return
*/ */
StaffSocketInfo getIdleStaff(Long userId); StaffSocketInfo getIdleStaff(Long merchantId, Long userId);
/** /**
...@@ -29,10 +29,10 @@ public interface StaffService { ...@@ -29,10 +29,10 @@ public interface StaffService {
MsgBody forward(IdModel idModel); MsgBody forward(IdModel idModel);
/** /**
* 获取所有在线客服 * 获取商户所有在线客服
* *
* @return * @return
*/ */
MsgBody getStaffList(); MsgBody getMerchantStaffGroup(Long merchantId);
} }
package com.ym.im.service.impl; package com.ym.im.service.impl;
import com.ym.im.entity.model.IdModel;
import com.ym.im.entity.MsgBody; import com.ym.im.entity.MsgBody;
import com.ym.im.entity.StaffSocketInfo; import com.ym.im.entity.StaffSocketInfo;
import com.ym.im.entity.UserSocketInfo; import com.ym.im.entity.UserSocketInfo;
import com.ym.im.entity.enums.ResultStatus; import com.ym.im.entity.enums.ResultStatus;
import com.ym.im.service.ChannelGroupService; import com.ym.im.entity.model.IdModel;
import com.ym.im.handler.ChannelGroupHandler;
import com.ym.im.service.StaffService; import com.ym.im.service.StaffService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -24,10 +25,13 @@ import static java.util.stream.Collectors.toMap; ...@@ -24,10 +25,13 @@ import static java.util.stream.Collectors.toMap;
@Service @Service
public class StaffServiceImpl implements StaffService { public class StaffServiceImpl implements StaffService {
@Autowired
private ChannelGroupHandler channelGroup;
@Override @Override
public StaffSocketInfo getIdleStaff(Long userId) { public StaffSocketInfo getIdleStaff(Long merchantId,Long userId) {
final LinkedHashMap<Long, StaffSocketInfo> collect = ChannelGroupService.STAFF_GROUP final LinkedHashMap<Long, StaffSocketInfo> collect = channelGroup.STAFF_GROUP.get(merchantId)
.entrySet() .entrySet()
.stream() .stream()
.sorted(comparingByValue(new Comparator<StaffSocketInfo>() { .sorted(comparingByValue(new Comparator<StaffSocketInfo>() {
...@@ -38,7 +42,7 @@ public class StaffServiceImpl implements StaffService { ...@@ -38,7 +42,7 @@ public class StaffServiceImpl implements StaffService {
})).collect(toMap(e -> e.getKey(), e -> e.getValue(), (e1, e2) -> e2, LinkedHashMap::new)); })).collect(toMap(e -> e.getKey(), e -> e.getValue(), (e1, e2) -> e2, LinkedHashMap::new));
if (collect.size() == 0) { if (collect.size() == 0) {
ChannelGroupService.USER_GROUP.get(userId).setStaffId(null); channelGroup.USER_GROUP.get(userId).setStaffId(null);
return null; return null;
} }
//客服和用户绑定 //客服和用户绑定
...@@ -46,7 +50,7 @@ public class StaffServiceImpl implements StaffService { ...@@ -46,7 +50,7 @@ public class StaffServiceImpl implements StaffService {
staffSocketInfo.getUserIds().add(userId); staffSocketInfo.getUserIds().add(userId);
Long staffId = staffSocketInfo.getStaffId(); Long staffId = staffSocketInfo.getStaffId();
//用户和客服绑定 //用户和客服绑定
final UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(userId); final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(userId);
if (userSocketInfo != null) { if (userSocketInfo != null) {
userSocketInfo.setStaffId(staffId); userSocketInfo.setStaffId(staffId);
//通知用户 新的客服 //通知用户 新的客服
...@@ -59,13 +63,13 @@ public class StaffServiceImpl implements StaffService { ...@@ -59,13 +63,13 @@ public class StaffServiceImpl implements StaffService {
@Override @Override
public MsgBody forward(IdModel idModel) { public MsgBody forward(IdModel idModel) {
final UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(idModel.getUserId()); final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(idModel.getUserId());
final StaffSocketInfo staffSocketInfo = ChannelGroupService.STAFF_GROUP.get(idModel.getStaffId()); final StaffSocketInfo staffSocketInfo =channelGroup.getMerchantStaff(idModel.getStaffId());
if (staffSocketInfo == null || userSocketInfo == null) { if (staffSocketInfo == null || userSocketInfo == null) {
return new MsgBody<>().setStatus(MsgBody.BINDINGFAILURE).setMessage(ResultStatus.FORWARD_FAILURE.getMessage()); return new MsgBody<>().setStatus(MsgBody.BINDINGFAILURE).setMessage(ResultStatus.FORWARD_FAILURE.getMessage());
} }
//移除原客服绑定 //移除原客服绑定
ChannelGroupService.STAFF_GROUP.get(userSocketInfo.getStaffId()).getUserIds().remove(idModel.getUserId()); channelGroup.getMerchantStaff(userSocketInfo.getStaffId()).getUserIds().remove(idModel.getUserId());
//设置新的客服 //设置新的客服
staffSocketInfo.getUserIds().add(idModel.getUserId()); staffSocketInfo.getUserIds().add(idModel.getUserId());
userSocketInfo.setStaffId(idModel.getStaffId()); userSocketInfo.setStaffId(idModel.getStaffId());
...@@ -79,9 +83,9 @@ public class StaffServiceImpl implements StaffService { ...@@ -79,9 +83,9 @@ public class StaffServiceImpl implements StaffService {
} }
@Override @Override
public MsgBody getStaffList() { public MsgBody getMerchantStaffGroup(Long merchantId) {
List<StaffSocketInfo> staffs = new ArrayList<StaffSocketInfo>(); List<StaffSocketInfo> staffs = new ArrayList<StaffSocketInfo>();
ChannelGroupService.STAFF_GROUP.forEach((k, v) -> { channelGroup.STAFF_GROUP.get(merchantId).forEach((k, v) -> {
staffs.add(v); staffs.add(v);
}); });
return new MsgBody<>().setStatus(ResultStatus.SUCCESS.getCode()).setData(staffs); return new MsgBody<>().setStatus(ResultStatus.SUCCESS.getCode()).setData(staffs);
......
...@@ -9,11 +9,9 @@ import com.ym.im.entity.UserSocketInfo; ...@@ -9,11 +9,9 @@ import com.ym.im.entity.UserSocketInfo;
import com.ym.im.entity.base.ChannelAttributeKey; import com.ym.im.entity.base.ChannelAttributeKey;
import com.ym.im.entity.base.NettyConstant; import com.ym.im.entity.base.NettyConstant;
import com.ym.im.entity.model.IdModel; import com.ym.im.entity.model.IdModel;
import com.ym.im.handler.ChannelGroupHandler;
import com.ym.im.mq.Queue; import com.ym.im.mq.Queue;
import com.ym.im.service.ChannelGroupService; import com.ym.im.service.*;
import com.ym.im.service.ChatRecordService;
import com.ym.im.service.ChatService;
import com.ym.im.service.MsgBodyService;
import com.ym.im.util.JsonUtils; import com.ym.im.util.JsonUtils;
import com.ym.im.validation.group.*; import com.ym.im.validation.group.*;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
...@@ -52,6 +50,10 @@ public class StaffSingleChatServiceImpl implements ChatService { ...@@ -52,6 +50,10 @@ public class StaffSingleChatServiceImpl implements ChatService {
@Autowired @Autowired
private MsgBodyService msgBodyService; private MsgBodyService msgBodyService;
@Autowired
private ChannelGroupHandler channelGroup;
// @Autowired // @Autowired
// private PushGatherService pushService; // private PushGatherService pushService;
...@@ -60,12 +62,13 @@ public class StaffSingleChatServiceImpl implements ChatService { ...@@ -60,12 +62,13 @@ public class StaffSingleChatServiceImpl implements ChatService {
public void init(ChannelHandlerContext ctx) { public void init(ChannelHandlerContext ctx) {
final Long staffId = ctx.channel().attr(ChannelAttributeKey.ROLE_ID).get(); final Long staffId = ctx.channel().attr(ChannelAttributeKey.ROLE_ID).get();
final Long merchantId = ctx.channel().attr(ChannelAttributeKey.MERCHANT_ID).get();
StaffSocketInfo staffSocketInfo = new StaffSocketInfo(); StaffSocketInfo staffSocketInfo = new StaffSocketInfo();
staffSocketInfo.setStaffId(staffId); staffSocketInfo.setStaffId(staffId);
staffSocketInfo.setToken(ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).get()); staffSocketInfo.setToken(ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).get());
staffSocketInfo.setChannel((NioSocketChannel) ctx.channel()); staffSocketInfo.setChannel((NioSocketChannel) ctx.channel());
staffSocketInfo.setUserIds(redisTemplate.opsForSet().members(NettyConstant.STAFF_USERIDS_KEY + staffId)); staffSocketInfo.setUserIds(redisTemplate.opsForSet().members(NettyConstant.STAFF_USERIDS_KEY + staffId));
ChannelGroupService.STAFF_GROUP.put(staffId, staffSocketInfo); channelGroup.putMerchantStaff(merchantId, staffSocketInfo);
log.info("客服: " + staffId + " 上线:"); log.info("客服: " + staffId + " 上线:");
} }
...@@ -73,7 +76,8 @@ public class StaffSingleChatServiceImpl implements ChatService { ...@@ -73,7 +76,8 @@ public class StaffSingleChatServiceImpl implements ChatService {
public void offline(ChannelHandlerContext ctx) { public void offline(ChannelHandlerContext ctx) {
final Long staffId = ctx.channel().attr(ChannelAttributeKey.ROLE_ID).get(); final Long staffId = ctx.channel().attr(ChannelAttributeKey.ROLE_ID).get();
final StaffSocketInfo staffSocketInfo = ChannelGroupService.STAFF_GROUP.get(staffId); final Long merchantId = ctx.channel().attr(ChannelAttributeKey.MERCHANT_ID).get();
final StaffSocketInfo staffSocketInfo = channelGroup.getMerchantStaff(merchantId,staffId);
if (ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).get().equals(staffSocketInfo.getToken())) { if (ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).get().equals(staffSocketInfo.getToken())) {
final Set<Long> userIds = staffSocketInfo.getUserIds(); final Set<Long> userIds = staffSocketInfo.getUserIds();
if (userIds.size() != 0) { if (userIds.size() != 0) {
...@@ -82,7 +86,7 @@ public class StaffSingleChatServiceImpl implements ChatService { ...@@ -82,7 +86,7 @@ public class StaffSingleChatServiceImpl implements ChatService {
redisTemplate.opsForSet().add(userListKey, userIds.toArray(new Long[userIds.size()])); redisTemplate.opsForSet().add(userListKey, userIds.toArray(new Long[userIds.size()]));
queue.staffOfflineQueue(new StaffSocketInfo(staffId, userIds)); //NioSocketChannel无法序列化 所以new StaffSocketInfo queue.staffOfflineQueue(new StaffSocketInfo(staffId, userIds)); //NioSocketChannel无法序列化 所以new StaffSocketInfo
} }
ChannelGroupService.STAFF_GROUP.remove(staffId); channelGroup.removeMerchantStaff(merchantId,staffId);
ctx.close(); ctx.close();
log.info("客服: " + staffId + " 下线:"); log.info("客服: " + staffId + " 下线:");
} }
...@@ -93,7 +97,7 @@ public class StaffSingleChatServiceImpl implements ChatService { ...@@ -93,7 +97,7 @@ public class StaffSingleChatServiceImpl implements ChatService {
public NioSocketChannel distribution(Long id, @Valid MsgBody<ChatRecord> msgBody) { public NioSocketChannel distribution(Long id, @Valid MsgBody<ChatRecord> msgBody) {
final Long userId = msgBody.getData().getUserId(); final Long userId = msgBody.getData().getUserId();
final UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(userId); final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(userId);
if (userSocketInfo == null) { if (userSocketInfo == null) {
//用户不在线,保存最后发送消息的客服ID //用户不在线,保存最后发送消息的客服ID
redisTemplate.opsForHash().put(NettyConstant.IM_USERS, userId, id); redisTemplate.opsForHash().put(NettyConstant.IM_USERS, userId, id);
...@@ -106,11 +110,11 @@ public class StaffSingleChatServiceImpl implements ChatService { ...@@ -106,11 +110,11 @@ public class StaffSingleChatServiceImpl implements ChatService {
//通知用户 新的客服 //通知用户 新的客服
userSocketInfo.setStaffId(id); userSocketInfo.setStaffId(id);
userSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.DISTRIBUTION_STAFF).setData(new IdModel().setStaffId(id).setUserId(userId))); userSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.DISTRIBUTION_STAFF).setData(new IdModel().setStaffId(id).setUserId(userId)));
ChannelGroupService.STAFF_GROUP.get(id).getUserIds().add(userId); channelGroup.getMerchantStaff(id).getUserIds().add(userId);
} }
if (currentStaffId != null && !currentStaffId.equals(id)) { if (currentStaffId != null && !currentStaffId.equals(id)) {
//通知客服 绑定失败 当前用户已绑定客服 //通知客服 绑定失败 当前用户已绑定客服
ChannelGroupService.STAFF_GROUP.get(id).writeAndFlush(new MsgBody<>().setStatus(MsgBody.BINDINGFAILURE).setData(new IdModel().setStaffId(currentStaffId).setUserId(userId))); channelGroup.getMerchantStaff(id).writeAndFlush(new MsgBody<>().setStatus(MsgBody.BINDINGFAILURE).setData(new IdModel().setStaffId(currentStaffId).setUserId(userId)));
return null; return null;
} }
return userSocketInfo.getChannel(); return userSocketInfo.getChannel();
...@@ -157,7 +161,7 @@ public class StaffSingleChatServiceImpl implements ChatService { ...@@ -157,7 +161,7 @@ public class StaffSingleChatServiceImpl implements ChatService {
final ChatRecord record = msgBody.getData(); final ChatRecord record = msgBody.getData();
record.setModifyTime(new Date()); record.setModifyTime(new Date());
chatRecordService.updateReceiveTime(record); chatRecordService.updateReceiveTime(record);
UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(record.getUserId()); UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(record.getUserId());
if (userSocketInfo != null) { if (userSocketInfo != null) {
userSocketInfo.writeAndFlush(msgBody); userSocketInfo.writeAndFlush(msgBody);
redisTemplate.opsForHash().put(NettyConstant.MSG_KEY + record.getUserId(), record.getId(), JsonUtils.obj2Json(msgBody)); redisTemplate.opsForHash().put(NettyConstant.MSG_KEY + record.getUserId(), record.getId(), JsonUtils.obj2Json(msgBody));
......
package com.ym.im.service.impl; package com.ym.im.service.impl;
import com.ym.im.entity.*; import com.ym.im.entity.MsgBody;
import com.ym.im.entity.StaffSocketInfo;
import com.ym.im.entity.UserSocketInfo;
import com.ym.im.entity.base.NettyConstant; import com.ym.im.entity.base.NettyConstant;
import com.ym.im.entity.enums.ResultStatus; import com.ym.im.entity.enums.ResultStatus;
import com.ym.im.entity.model.IdModel; import com.ym.im.entity.model.IdModel;
import com.ym.im.service.ChannelGroupService; import com.ym.im.handler.ChannelGroupHandler;
import com.ym.im.service.UserService; import com.ym.im.service.UserService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -24,6 +27,9 @@ public class UserServiceImpl implements UserService { ...@@ -24,6 +27,9 @@ public class UserServiceImpl implements UserService {
@Resource(name = "myRedisTemplate") @Resource(name = "myRedisTemplate")
private RedisTemplate redisTemplate; private RedisTemplate redisTemplate;
@Autowired
private ChannelGroupHandler channelGroup;
@Override @Override
public MsgBody getUserList(Long staffId) { public MsgBody getUserList(Long staffId) {
...@@ -37,12 +43,12 @@ public class UserServiceImpl implements UserService { ...@@ -37,12 +43,12 @@ public class UserServiceImpl implements UserService {
@Override @Override
public MsgBody deleteUserFromList(IdModel idModel) { public MsgBody deleteUserFromList(IdModel idModel) {
final StaffSocketInfo staffSocketInfo = ChannelGroupService.STAFF_GROUP.get(idModel.getStaffId()); final StaffSocketInfo staffSocketInfo = channelGroup.getMerchantStaff(idModel.getStaffId());
if (staffSocketInfo == null) { if (staffSocketInfo == null) {
return new MsgBody<>().setStatus(ResultStatus.REQUEST_ERROR.getCode()).setMessage(ResultStatus.REQUEST_ERROR.getMessage()); return new MsgBody<>().setStatus(ResultStatus.REQUEST_ERROR.getCode()).setMessage(ResultStatus.REQUEST_ERROR.getMessage());
} }
staffSocketInfo.getUserIds().remove(idModel.getUserId()); staffSocketInfo.getUserIds().remove(idModel.getUserId());
final UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(idModel.getUserId()); final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(idModel.getUserId());
if (userSocketInfo != null && idModel.getStaffId().equals(userSocketInfo.getStaffId())) { if (userSocketInfo != null && idModel.getStaffId().equals(userSocketInfo.getStaffId())) {
userSocketInfo.setStaffId(null); userSocketInfo.setStaffId(null);
} }
...@@ -52,7 +58,7 @@ public class UserServiceImpl implements UserService { ...@@ -52,7 +58,7 @@ public class UserServiceImpl implements UserService {
@Override @Override
public MsgBody checkBinding(IdModel idModel) { public MsgBody checkBinding(IdModel idModel) {
final UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(idModel.getUserId()); final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(idModel.getUserId());
/** /**
* 用户不在线 不校验绑定关系 * 用户不在线 不校验绑定关系
* 用户在线,只有绑定的客服才能发送消息 * 用户在线,只有绑定的客服才能发送消息
......
...@@ -9,6 +9,7 @@ import com.ym.im.entity.UserSocketInfo; ...@@ -9,6 +9,7 @@ import com.ym.im.entity.UserSocketInfo;
import com.ym.im.entity.base.ChannelAttributeKey; import com.ym.im.entity.base.ChannelAttributeKey;
import com.ym.im.entity.base.NettyConstant; import com.ym.im.entity.base.NettyConstant;
import com.ym.im.entity.model.IdModel; import com.ym.im.entity.model.IdModel;
import com.ym.im.handler.ChannelGroupHandler;
import com.ym.im.service.*; import com.ym.im.service.*;
import com.ym.im.util.JsonUtils; import com.ym.im.util.JsonUtils;
import com.ym.im.validation.group.ChatRecordReceiveGroup; import com.ym.im.validation.group.ChatRecordReceiveGroup;
...@@ -51,6 +52,9 @@ public class UserSingleChatServiceImpl implements ChatService { ...@@ -51,6 +52,9 @@ public class UserSingleChatServiceImpl implements ChatService {
@Autowired @Autowired
private ChatRecordService chatRecordService; private ChatRecordService chatRecordService;
@Autowired
private ChannelGroupHandler channelGroup;
@Override @Override
public void init(ChannelHandlerContext ctx) { public void init(ChannelHandlerContext ctx) {
...@@ -61,7 +65,7 @@ public class UserSingleChatServiceImpl implements ChatService { ...@@ -61,7 +65,7 @@ public class UserSingleChatServiceImpl implements ChatService {
userSocketInfo.setCol(ctx.channel().attr(ChannelAttributeKey.COL_INFO).get()); userSocketInfo.setCol(ctx.channel().attr(ChannelAttributeKey.COL_INFO).get());
userSocketInfo.setToken(ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).get()); userSocketInfo.setToken(ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).get());
userSocketInfo.setPushToken(null); userSocketInfo.setPushToken(null);
ChannelGroupService.USER_GROUP.put(userId, userSocketInfo); channelGroup.USER_GROUP.put(userId, userSocketInfo);
//恢复历史绑定关系 //恢复历史绑定关系
restoreBindingRelationship(userId); restoreBindingRelationship(userId);
//通知客服 用户上线 //通知客服 用户上线
...@@ -75,13 +79,13 @@ public class UserSingleChatServiceImpl implements ChatService { ...@@ -75,13 +79,13 @@ public class UserSingleChatServiceImpl implements ChatService {
public void offline(ChannelHandlerContext ctx) { public void offline(ChannelHandlerContext ctx) {
final Long userId = ctx.channel().attr(ChannelAttributeKey.ROLE_ID).get(); final Long userId = ctx.channel().attr(ChannelAttributeKey.ROLE_ID).get();
final UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(userId); final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(userId);
if (userSocketInfo != null && ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).get().equals(userSocketInfo.getToken())) { if (userSocketInfo != null && ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).get().equals(userSocketInfo.getToken())) {
final Long staffId = userSocketInfo.getStaffId(); final Long staffId = userSocketInfo.getStaffId();
ChannelGroupService.USER_GROUP.remove(userId); channelGroup.USER_GROUP.remove(userId);
ctx.close(); ctx.close();
if (staffId != null && ChannelGroupService.STAFF_GROUP.get(staffId) != null) { if (staffId != null && channelGroup.getMerchantStaff(staffId) != null) {
// 保存最后的客服 // 保存最后的客服
redisTemplate.opsForHash().put(NettyConstant.IM_USERS, userId, staffId); redisTemplate.opsForHash().put(NettyConstant.IM_USERS, userId, staffId);
} }
...@@ -94,19 +98,20 @@ public class UserSingleChatServiceImpl implements ChatService { ...@@ -94,19 +98,20 @@ public class UserSingleChatServiceImpl implements ChatService {
@Validated({MsgBodyGroup.class, ChatRecordSendGroup.class}) @Validated({MsgBodyGroup.class, ChatRecordSendGroup.class})
public NioSocketChannel distribution(Long id, @Valid MsgBody<ChatRecord> msgBody) { public NioSocketChannel distribution(Long id, @Valid MsgBody<ChatRecord> msgBody) {
final Long merchantId = msgBody.getData().getMerchantId();
// 获取服务用户的客服Id // 获取服务用户的客服Id
Long staffId = ChannelGroupService.USER_GROUP.get(id).getStaffId(); Long staffId = channelGroup.USER_GROUP.get(id).getStaffId();
// 客服SocketInfo对象 // 客服SocketInfo对象
StaffSocketInfo staffSocketInfo; StaffSocketInfo staffSocketInfo;
// 若客服Id为空,分配客服,不为空则获取客服SocketInfo // 若客服Id为空,分配客服,不为空则获取客服SocketInfo
if (staffId == null) { if (staffId == null) {
staffSocketInfo = staffService.getIdleStaff(id); staffSocketInfo = staffService.getIdleStaff(merchantId,id);
if (staffSocketInfo == null) { if (staffSocketInfo == null) {
return null; return null;
} }
} else { } else {
// 根据客服id获取SocketInfo // 根据客服id获取SocketInfo
staffSocketInfo = ChannelGroupService.STAFF_GROUP.get(staffId); staffSocketInfo = channelGroup.getMerchantStaff(staffId);
// 服务用户的客服不在线 // 服务用户的客服不在线
if (staffSocketInfo == null) { if (staffSocketInfo == null) {
// Redis是否存在当前客服服务用户Set对象(TTL=60秒) // Redis是否存在当前客服服务用户Set对象(TTL=60秒)
...@@ -114,7 +119,7 @@ public class UserSingleChatServiceImpl implements ChatService { ...@@ -114,7 +119,7 @@ public class UserSingleChatServiceImpl implements ChatService {
// 客服下线超过60秒,当前客服服务用户Set对象已过期,重新分配客服 // 客服下线超过60秒,当前客服服务用户Set对象已过期,重新分配客服
// 过期后set对象不会为空而是大小为0 // 过期后set对象不会为空而是大小为0
if (members.size() == 0) { if (members.size() == 0) {
staffSocketInfo = staffService.getIdleStaff(id); staffSocketInfo = staffService.getIdleStaff(merchantId,id);
} }
return null; return null;
} else { } else {
...@@ -149,7 +154,7 @@ public class UserSingleChatServiceImpl implements ChatService { ...@@ -149,7 +154,7 @@ public class UserSingleChatServiceImpl implements ChatService {
* 情况4:当前用户有客服服务,且客服已下线并超过60秒,重新分配客服且有客服可分配,ChatRecord.staffId = StaffSocketInfo.id; * 情况4:当前用户有客服服务,且客服已下线并超过60秒,重新分配客服且有客服可分配,ChatRecord.staffId = StaffSocketInfo.id;
* 情况5(特殊:需求未定暂时这样处理):当前用户有客服服务,且客服已下线并超过60秒,重新分配客服且无客服可分配,ChatRecord.staffId = staffId; * 情况5(特殊:需求未定暂时这样处理):当前用户有客服服务,且客服已下线并超过60秒,重新分配客服且无客服可分配,ChatRecord.staffId = staffId;
*/ */
record.setStaffId(ChannelGroupService.USER_GROUP.get(id).getStaffId()); record.setStaffId(channelGroup.USER_GROUP.get(id).getStaffId());
// 先保存至数据库,再发送消息(若颠倒顺序可能导致数据未保存,更新已读操作先执行导致消息一直是未读状态) // 先保存至数据库,再发送消息(若颠倒顺序可能导致数据未保存,更新已读操作先执行导致消息一直是未读状态)
chatRecordService.insertSelective(record); chatRecordService.insertSelective(record);
log.info("用户 消息保存:" + record.getId()); log.info("用户 消息保存:" + record.getId());
...@@ -173,10 +178,10 @@ public class UserSingleChatServiceImpl implements ChatService { ...@@ -173,10 +178,10 @@ public class UserSingleChatServiceImpl implements ChatService {
final ChatRecord record = msgBody.getData(); final ChatRecord record = msgBody.getData();
record.setModifyTime(new Date()); record.setModifyTime(new Date());
chatRecordService.updateReceiveTime(record); chatRecordService.updateReceiveTime(record);
UserSocketInfo userSocketInfo = ChannelGroupService.USER_GROUP.get(record.getUserId()); UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(record.getUserId());
if (userSocketInfo != null) { if (userSocketInfo != null) {
userSocketInfo.setStaffId(userSocketInfo.getStaffId() == null ? record.getStaffId() : userSocketInfo.getStaffId()); userSocketInfo.setStaffId(userSocketInfo.getStaffId() == null ? record.getStaffId() : userSocketInfo.getStaffId());
StaffSocketInfo staffSocketInfo = ChannelGroupService.STAFF_GROUP.get(userSocketInfo.getStaffId()); StaffSocketInfo staffSocketInfo = channelGroup.getMerchantStaff(userSocketInfo.getStaffId());
if (staffSocketInfo != null) { if (staffSocketInfo != null) {
staffSocketInfo.writeAndFlush(msgBody); staffSocketInfo.writeAndFlush(msgBody);
} }
...@@ -193,9 +198,9 @@ public class UserSingleChatServiceImpl implements ChatService { ...@@ -193,9 +198,9 @@ public class UserSingleChatServiceImpl implements ChatService {
private void restoreBindingRelationship(Long userId) { private void restoreBindingRelationship(Long userId) {
if (redisTemplate.opsForHash().hasKey(NettyConstant.IM_USERS, userId)) { if (redisTemplate.opsForHash().hasKey(NettyConstant.IM_USERS, userId)) {
final Long staffId = (Long) redisTemplate.opsForHash().get(NettyConstant.IM_USERS, userId); final Long staffId = (Long) redisTemplate.opsForHash().get(NettyConstant.IM_USERS, userId);
final StaffSocketInfo staffSocketInfo = ChannelGroupService.STAFF_GROUP.get(staffId); final StaffSocketInfo staffSocketInfo = channelGroup.getMerchantStaff(staffId);
if (staffSocketInfo != null) { if (staffSocketInfo != null) {
ChannelGroupService.USER_GROUP.get(userId).setStaffId(staffId); channelGroup.USER_GROUP.get(userId).setStaffId(staffId);
staffSocketInfo.getUserIds().add(userId); staffSocketInfo.getUserIds().add(userId);
} }
} }
...@@ -207,10 +212,12 @@ public class UserSingleChatServiceImpl implements ChatService { ...@@ -207,10 +212,12 @@ public class UserSingleChatServiceImpl implements ChatService {
* @param userId * @param userId
*/ */
private void broadcastUserOnline(Long userId) { private void broadcastUserOnline(Long userId) {
final Long staffId = ChannelGroupService.USER_GROUP.get(userId).getStaffId(); final Long staffId = channelGroup.USER_GROUP.get(userId).getStaffId();
ChannelGroupService.STAFF_GROUP.forEach((key, staffSocketInfo) -> { channelGroup.STAFF_GROUP.forEach((key, staffGroup) -> {
staffSocketInfo.getUserIds().remove(userId); staffGroup.values().forEach(staffSocketInfo -> {
staffSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.USERS_ONLINE).setData(new IdModel().setStaffId(staffId).setUserId(userId))); staffSocketInfo.getUserIds().remove(userId);
staffSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.USERS_ONLINE).setData(new IdModel().setStaffId(staffId).setUserId(userId)));
});
}); });
} }
......
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