Commit 6cab69bf by zhangjw

1:完善ws鉴权逻辑

2:会话列表接口新增 latestRecord recordId字段逻辑
3:全局status修改为code
parent 448d645d
......@@ -41,13 +41,6 @@
<dependencies>
<!-- <dependency>-->
<!-- <groupId>com.ym</groupId>-->
<!-- <artifactId>pathfinder_common</artifactId>-->
<!-- <version>1.0-SNAPSHOT</version>-->
<!-- <scope>compile</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
......
......@@ -48,7 +48,7 @@ public class ExceptionConfig {
final String errornum = UUID.randomUUID().toString();
log.error("Error number: " + errornum, e);
final MsgBody msgBody = new MsgBody<>()
.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value())
.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value())
.setMessage(MessageUtils.getMsg("error.system", request.getLocale(), errornum));
return new ResponseEntity<>(msgBody, HttpStatus.INTERNAL_SERVER_ERROR);
}
......@@ -61,7 +61,7 @@ public class ExceptionConfig {
@ExceptionHandler(value = HttpMessageNotReadableException.class)
protected ResponseEntity<MsgBody> httpMessageNotReadableExceptionHandle(HttpServletRequest request) {
final MsgBody msgBody = new MsgBody<>()
.setStatus(HttpStatus.BAD_REQUEST.value())
.setCode(HttpStatus.BAD_REQUEST.value())
.setMessage(MessageUtils.getMsg("error.missing_body", request.getLocale()));
return new ResponseEntity<>(msgBody, HttpStatus.BAD_REQUEST);
}
......@@ -74,7 +74,7 @@ public class ExceptionConfig {
*/
@ExceptionHandler(value = IMessageException.class)
protected ResponseEntity<MsgBody> iMessageExceptionHandle(IMessageException e) {
final MsgBody msgBody = new MsgBody<>().setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()).setMessage("error.top_level_error");
final MsgBody msgBody = new MsgBody<>().setCode(HttpStatus.INTERNAL_SERVER_ERROR.value()).setMessage("error.top_level_error");
return new ResponseEntity<>(msgBody, HttpStatus.INTERNAL_SERVER_ERROR);
}
......@@ -101,7 +101,7 @@ public class ExceptionConfig {
}
}
final MsgBody<List<String>> msgBody = new MsgBody<>();
msgBody.setStatus(HttpStatus.BAD_REQUEST.value());
msgBody.setCode(HttpStatus.BAD_REQUEST.value());
msgBody.setMessage(MessageUtils.getMsg("error.bad_parameter", request.getLocale()) + errorMsg.toString());
return new ResponseEntity<>(msgBody, HttpStatus.BAD_REQUEST);
}
......
......@@ -17,26 +17,6 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
**/
@Configuration
public class RedisConfig {
/**
* 序列化、反序列化为Json的RedisTemplate
*
* @param redisConnectionFactory 连接工厂
* @param objectMapper Jackson.ObjectMapper
* @return redisTemplate
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) {
final RedisSerializer<String> stringSerializer = new StringRedisSerializer();
final GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
final RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setKeySerializer(stringSerializer);
template.setValueSerializer(jsonRedisSerializer);
template.setHashKeySerializer(stringSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
template.setDefaultSerializer(jsonRedisSerializer);
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
public RedisTemplate<Object, Object> myRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
......@@ -53,4 +33,5 @@ public class RedisConfig {
return template;
}
}
......@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ym.im.entity.MsgBody;
import com.ym.im.entity.Session;
import com.ym.im.entity.enums.ResultStatus;
import com.ym.im.entity.model.SessionInfo;
import com.ym.im.service.SessionListService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
......@@ -27,20 +28,19 @@ public class SessionController {
@Autowired
private SessionListService sessionListService;
@GetMapping("/list")
@ApiOperation(value = "获取用户会话列表")
public MsgBody<List<Session>> getSessionList(Long userId) {
return new MsgBody<List<Session>>()
.setStatus(ResultStatus.SUCCESS.getCode())
.setData(sessionListService.list(new QueryWrapper<Session>().lambda().eq(Session::getUserId, userId)));
public MsgBody<List<SessionInfo>> getSessionList(Long userId) {
return sessionListService.getSessionList(userId);
}
@DeleteMapping("/list")
@ApiOperation(value = "删除商户会话")
public MsgBody deleteSessionList(Long merchantId) {
sessionListService.remove(new QueryWrapper<Session>().lambda().eq(Session::getMerchantId, merchantId));
return new MsgBody<>().setStatus(ResultStatus.SUCCESS.getCode());
public MsgBody deleteSessionList(Long userId, Long merchantId) {
sessionListService.remove(new QueryWrapper<Session>().lambda().eq(Session::getUserId, userId).eq(Session::getMerchantId, merchantId));
return new MsgBody<>().setCode(ResultStatus.SUCCESS.getCode());
}
}
......@@ -34,12 +34,12 @@ public class MessageDecoder extends MessageToMessageDecoder<TextWebSocketFrame>
protected void decode(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame, List out) throws Exception {
final String jsonStr = textWebSocketFrame.text();
MsgBody<ChatRecord> msgBody = new MsgBody<ChatRecord>().setStatus(MsgBody.ERROR);
MsgBody<ChatRecord> msgBody = new MsgBody<ChatRecord>().setCode(MsgBody.ERROR);
try {
msgBody = JsonUtils.json2Obj(jsonStr, MsgBody.class, ChatRecord.class);
} catch (IOException e) {
log.error("Json转{}异常:\r\n" + "Json原串:{}\r\n" + "===异常栈信息===", "MsgBody.class", jsonStr, e);
msgBody.setStatus(MsgBody.ERROR);
msgBody.setCode(MsgBody.ERROR);
msgBody.setMessage("错误的Json格式:" + jsonStr);
ctx.channel().writeAndFlush(msgBody);
return;
......
/*
* 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 com.ym.im.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* JwtToken Redis缓存对象
*
* @author geekidea
* @date 2019-09-30
**/
@Data
@Accessors(chain = true)
public class JwtTokenRedisVo implements Serializable {
private static final long serialVersionUID = 1831633309466775223L;
/**
* 客户端类型
*/
private String type;
/**
* mcId
*/
private Long mcId;
/**
* 登录ip
*/
private String host;
/**
* 登录用户ID
*/
private Long userId;
/**
* 登录用户名称
*/
private String username;
/**
* 登录盐值
*/
private String salt;
/**
* 登录token
*/
private String token;
/**
* 创建时间
*/
private Date createDate;
/**
* 多长时间过期,默认一小时
*/
private long expireSecond;
/**
* 过期日期
*/
private Date expireDate;
}
......@@ -43,7 +43,7 @@ public class MsgBody<T> implements Serializable {
" * 6、分配客服" +
" * 7、绑定失败"
)
private Integer status;
private Integer code;
@Valid
@NotNull(message = "{error.msg_body_data_empty}", groups = MsgBodyGroup.class)
......
......@@ -39,10 +39,10 @@ public class Session implements Serializable {
private Long merchantId;
@NotNull(message = "{error.chat_create_time_empty}", groups = {Default.class, ChatRecordSaveGroup.class})
@ApiModelProperty(value = "创建时间(服务端设置)")
@ApiModelProperty(value = "创建时间")
private Date createTime;
@NotNull(message = "{error.chat_modify_time_empty}", groups = {Default.class})
@ApiModelProperty(value = "修改时间(服务端设置)")
@ApiModelProperty(value = "修改时间)")
private Date modifyTime;
}
package com.ym.im.entity.model;
import com.ym.im.entity.Session;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author: JJww
* @Date:2020/10/22
*/
@Data
@Accessors(chain = true)
public class SessionInfo extends Session {
private Long recordId;
private String latestRecord;
}
......@@ -26,7 +26,7 @@ public class IMessageException extends RuntimeException {
super(MessageUtils.getMsg(i18nError, Locale.CHINA));
this.i18nError = i18nError;
this.msgBody = new MsgBody()
.setStatus(MsgBody.ERROR)
.setCode(MsgBody.ERROR)
.setMessage(MessageUtils.getMsg(i18nError));
}
}
......@@ -25,7 +25,6 @@ public class SingleChatFactory {
* @return
*/
public ChatService getService(String type) {
switch (RoleEnum.get(type)) {
case APP:
return userSingleChatServiceImpl;
......
//package com.ym.im.handler;
//
//
//import com.ym.im.entity.BaseSocketInfo;
//import com.ym.im.entity.LanguageEnum;
//import com.ym.im.entity.MsgBody;
//import com.ym.im.entity.NettyConstant;
//import com.ym.im.entity.enums.PlatformEnum;
//import com.ym.im.factory.SingleChatFactory;
//import com.ym.im.service.ChannelGroupService;
//import io.netty.buffer.ByteBuf;
//import io.netty.buffer.Unpooled;
//import io.netty.channel.ChannelFuture;
//import io.netty.channel.ChannelFutureListener;
//import io.netty.channel.ChannelHandler;
//import io.netty.channel.ChannelHandlerContext;
//import io.netty.handler.codec.http.*;
//import io.netty.util.CharsetUtil;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.data.redis.core.RedisTemplate;
//import org.springframework.stereotype.Component;
//
//import javax.annotation.Resource;
//import java.util.List;
//import java.util.Map;
//
//import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED;
//import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
//
///**
// * @author: JJww
// * ws 校验/初始化相关处理
// * @Date:2019-05-17
// */
//@Slf4j
//@Component
//@ChannelHandler.Sharable
//public class IndexHandler extends BaseHandler<FullHttpRequest> {
//
// @Resource(name = "myRedisTemplate")
// private RedisTemplate redisTemplate;
//
// @Autowired
// private SingleChatFactory singleChatFactory;
//
// @Override
// protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
//
// // 获取uri参数信息
// final Map<String, List<String>> parameters = new QueryStringDecoder(request.uri()).parameters();
// // 获取token信息
// final List<String> tokenStr = parameters.get(NettyConstant.TOKEN);
// if (tokenStr == null) {
// log.error("token值为空!");
// sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED));
// return;
// }
// // 解析token
//// final String[] tokenInfo = String.valueOf(JwtUtil.checkToken(tokenStr.get(0)).get("user_id")).split("_");
// final String[] tokenInfo = null;
// if (tokenInfo.length < 2) {
// log.error("userId格式不正确:" + tokenInfo[1]);
// sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED));
// return;
// }
// final Integer type = NettyConstant.CONNECT_TYPE.get(tokenInfo[0]);
// final Long userId = Long.valueOf(tokenInfo[1]);
// if (type == null) {
// log.error("用户类型值不正确:" + tokenInfo[0]);
// sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED));
// return;
// }
//// if (NettyConstant.CONNECT_TYPE_USER == type.intValue() && Constants.FORBIDDEN.equals(redisTemplate.opsForHash().get(Constants.USER, userId))) {
//// log.info("用户被禁用:" + userId);
//// sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HTTP_1_1, PROXY_AUTHENTICATION_REQUIRED));
//// return;
//// }
// //保存当前token
// String token = "Bearer " + tokenStr.get(0);
// ctx.channel().attr(NettyConstant.TOKEN_INFO).set(token);
// ctx.channel().attr(NettyConstant.ID).set(userId);
// ctx.channel().attr(NettyConstant.COL_INFO).set(parameters.get(NettyConstant.COL) == null ? LanguageEnum.zh.getKey() : parameters.get(NettyConstant.COL).get(0));
// this.close(token, userId, tokenInfo[0]);
// singleChatFactory.getService(type).init(ctx);
// request.setUri(NettyConstant.WS);
// ctx.fireChannelRead(request.retain());
// }
//
//
// private void close(String token, Long userId, String type) {
// final BaseSocketInfo baseSocketInfo = PlatformEnum.app.getKey().equals(type) ? ChannelGroupService.USER_GROUP.get(userId) : ChannelGroupService.STAFF_GROUP.get(userId);
// if (baseSocketInfo != null && !token.equals(baseSocketInfo.getToken())) {
// baseSocketInfo.getChannel().write(new MsgBody(MsgBody.FORCEDOFFLINE));
// baseSocketInfo.getChannel().close();
// ChannelGroupService.USER_GROUP.remove(userId);
// log.info("用户: " + userId + " 被迫下线");
// }
// }
//
//
// private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
//
// if (response.status().code() != HttpResponseStatus.OK.code()) {
// ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
// response.content().writeBytes(buf);
// buf.release();
// response.headers();
// HttpUtil.setContentLength(response, response.content().readableBytes());
// }
//
// ChannelFuture f = ctx.channel().writeAndFlush(response);
// if (!HttpUtil.isKeepAlive(request) || response.status().code() != HttpResponseStatus.OK.code()) {
// f.addListener(ChannelFutureListener.CLOSE);
// }
// }
//
//
//}
\ No newline at end of file
......@@ -42,11 +42,11 @@ public class SingleChatHandler extends BaseHandler<MsgBody<ChatRecord>> {
next = constraintViolation;
errorMsg.add(next.getMessage() + " " + ((PathImpl) next.getPropertyPath()).getLeafNode().asString() + " = " + next.getInvalidValue());
}
msgBody.setStatus(MsgBody.ERROR);
msgBody.setCode(MsgBody.ERROR);
msgBody.setMessage(errorMsg.toString());
ctx.channel().writeAndFlush(msgBody);
} catch (IMessageException e) {
msgBody.setStatus(MsgBody.ERROR);
msgBody.setCode(MsgBody.ERROR);
msgBody.setMessage(MessageUtils.getMsg(e.getI18nError()));
ctx.channel().writeAndFlush(msgBody);
} catch (Exception e) {
......
package com.ym.im.handler;
import com.ym.im.entity.JwtTokenRedisVo;
import com.ym.im.entity.MsgBody;
import com.ym.im.entity.base.BaseSocketInfo;
import com.ym.im.entity.base.ChannelAttributeKey;
......@@ -12,9 +13,13 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.Optional;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
......@@ -37,25 +42,31 @@ public class WebSocketHandshakerHandler extends BaseHandler<FullHttpRequest> {
@Autowired
private SingleChatFactory singleChatFactory;
public static final String ROLE_TYPE = "RoleType";
public static final String MERCHANT_ID = "MerchantId";
@Resource(name = "myRedisTemplate")
private RedisTemplate redisTemplate;
public static final String AUTHORIZATION = "Authorization";
/**
* 登录用户token信息key
* login:token:tokenMd5
*/
public static final String LOGIN_TOKEN = "login:token:%s";
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
final String token = fullHttpRequest.headers().get(AUTHORIZATION);
Optional.ofNullable(token).orElseThrow(() -> new HttpException(ctx, fullHttpRequest, new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED)));
final String roleType = fullHttpRequest.headers().get(ROLE_TYPE, null);
Optional.ofNullable(roleType).orElseThrow(() -> new HttpException(ctx, fullHttpRequest, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST)));
final JwtTokenRedisVo tokenInfoForRedis = this.getTokenInfoForRedis(token);
Optional.ofNullable(tokenInfoForRedis).orElseThrow(() -> new HttpException(ctx, fullHttpRequest, new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED)));
final String roleType = tokenInfoForRedis.getType();
if (RoleEnum.merchant.getType().equals(roleType)) {
final Object merchantId = fullHttpRequest.headers().get(MERCHANT_ID);
final Long merchantId = tokenInfoForRedis.getMcId();
Optional.ofNullable(merchantId).orElseThrow(() -> new HttpException(ctx, fullHttpRequest, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST)));
ctx.channel().attr(ChannelAttributeKey.MERCHANT_ID).set(Long.valueOf(merchantId.toString()));
ctx.channel().attr(ChannelAttributeKey.MERCHANT_ID).set(merchantId);
}
final Long userId = Long.valueOf(fullHttpRequest.headers().get(MERCHANT_ID)); //待完善鉴权逻辑
final Long userId = tokenInfoForRedis.getUserId();
ctx.channel().attr(ChannelAttributeKey.ROLE_ID).set(userId);
ctx.channel().attr(ChannelAttributeKey.TOKEN_INFO).set(token);
ctx.channel().attr(ChannelAttributeKey.ROLE_TYPE).set(roleType);
......@@ -80,9 +91,23 @@ public class WebSocketHandshakerHandler extends BaseHandler<FullHttpRequest> {
default:
}
if (baseSocketInfo != null && !token.equals(baseSocketInfo.getToken())) {
baseSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.FORCEDOFFLINE));
baseSocketInfo.writeAndFlush(new MsgBody<>().setCode(MsgBody.FORCEDOFFLINE));
baseSocketInfo.close();
log.info("用户: " + roleId + " 被迫下线");
}
}
public JwtTokenRedisVo getTokenInfoForRedis(String token) {
final LinkedHashMap jwtTokenInfo = (LinkedHashMap) redisTemplate.opsForValue().get(String.format(LOGIN_TOKEN, DigestUtils.md5Hex(token)));
if (jwtTokenInfo == null) {
return null;
}
//@class 对象路径不一致 重新set
final String type = jwtTokenInfo.get("type").toString();
return new JwtTokenRedisVo()
.setUserId(Long.valueOf(jwtTokenInfo.get("userId").toString()))
.setType(type).setMcId(RoleEnum.merchant.getType().equals(type) ? Long.valueOf(jwtTokenInfo.get("mcId").toString()) : null);
}
}
package com.ym.im.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ym.im.entity.ChatRecord;
import com.ym.im.entity.PullChatRecord;
import com.ym.im.entity.Session;
import org.apache.ibatis.annotations.Param;
import java.util.List;
......@@ -14,7 +16,7 @@ import java.util.List;
* @author 陈俊雄
* @since 2019-05-28
*/
public interface ChatRecordMapper {
public interface ChatRecordMapper extends BaseMapper<ChatRecord> {
int insert(@Param("chat") ChatRecord chatRecord, @Param("index") int index);
int insertSelective(@Param("chat") ChatRecord chatRecord, @Param("index") int index);
......@@ -30,4 +32,7 @@ public interface ChatRecordMapper {
ChatRecord selectById(@Param("id") Long id, @Param("index") int index);
List<ChatRecord> getChatRecord(@Param("pull") PullChatRecord pull, @Param("index") int index);
ChatRecord getLatestRecord(@Param("userId") Long userId, @Param("merchantId") Long merchantId, @Param("index") int index);
}
......@@ -75,7 +75,7 @@ public class Receiver {
if (channelGroup.USER_GROUP.get(userId) != null) {
final StaffSocketInfo idleStaff = staffService.getIdleStaff(staffSocketInfo.getMerchantId(), userId);
if (idleStaff != null) {
idleStaff.writeAndFlush(new MsgBody<>().setStatus(MsgBody.DISTRIBUTION_STAFF).setData(new IdModel().setStaffId(staffId).setUserId(userId)));
idleStaff.writeAndFlush(new MsgBody<>().setCode(MsgBody.DISTRIBUTION_STAFF).setData(new IdModel().setStaffId(staffId).setUserId(userId)));
}
}
});
......@@ -94,7 +94,7 @@ public class Receiver {
final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(Long.valueOf(userId));
if (userSocketInfo != null) {
userSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.LOGOUT));
userSocketInfo.writeAndFlush(new MsgBody<>().setCode(MsgBody.LOGOUT));
userSocketInfo.close();
log.info("用户: " + userId + "被禁用");
}
......@@ -115,7 +115,7 @@ public class Receiver {
return;
}
StaffSocketInfo staffSocketInfo = channelGroup.getMerchantStaff(userSocketInfo.getStaffId(orderModel.getMerchantId()));
final MsgBody<OrderModel> orderInfo = new MsgBody<OrderModel>().setStatus(MsgBody.ORDER).setData(orderModel);
final MsgBody<OrderModel> orderInfo = new MsgBody<OrderModel>().setCode(MsgBody.ORDER).setData(orderModel);
/**
* 绑定客服在线,发送订单信息
*/
......@@ -159,7 +159,7 @@ public class Receiver {
}
MsgBody<ChatRecord> msg = JsonUtils.json2Obj(String.valueOf(redisTemplate.opsForHash().get(NettyConstant.MSG_KEY + userId, recordId)), JsonUtils.getJavaType(MsgBody.class, ChatRecord.class));
if (msg != null && msg.getStatus().equals(MsgBody.HAVE_READ)) {
if (msg != null && msg.getCode().equals(MsgBody.HAVE_READ)) {
redisTemplate.opsForHash().delete(NettyConstant.MSG_KEY + userId, recordId);
return;
}
......
......@@ -30,7 +30,7 @@ public interface ChatRecordService {
@Validated({ChatRecordSaveGroup.class})
int insertSelective(@Valid ChatRecord chatRecord);
int updateById(ChatRecord chatRecord);
int updateByRecordId(ChatRecord chatRecord);
int updateByIdSelective(ChatRecord chatRecord);
......@@ -42,5 +42,6 @@ public interface ChatRecordService {
MsgBody<List<ChatRecord>> updateReceiveTime(List<ChatRecord> chats);
// MsgBody
ChatRecord getLatestMsg(Long userId, Long merchantId);
}
package com.ym.im.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ym.im.entity.MsgBody;
import com.ym.im.entity.Session;
import com.ym.im.entity.model.SessionInfo;
import java.util.List;
/**
* @author: JJww
......@@ -10,6 +14,12 @@ import com.ym.im.entity.Session;
public interface SessionListService extends IService<Session> {
/**
* 获取会话信息列表
*
* @param userId
* @return
*/
MsgBody<List<SessionInfo>> getSessionList(Long userId);
}
package com.ym.im.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ym.im.entity.ChatRecord;
......@@ -22,7 +23,7 @@ import java.util.List;
* @since 2019-05-28
*/
@Service
public class ChatRecordServiceImpl implements ChatRecordService {
public class ChatRecordServiceImpl extends ServiceImpl<ChatRecordMapper, ChatRecord> implements ChatRecordService {
@Autowired
private ChatRecordMapper chatRecordMapper;
......@@ -38,7 +39,7 @@ public class ChatRecordServiceImpl implements ChatRecordService {
}
@Override
public int updateById(ChatRecord chatRecord) {
public int updateByRecordId(ChatRecord chatRecord) {
return chatRecordMapper.updateById(chatRecord, remainder(chatRecord.getUserId()));
}
......@@ -65,16 +66,21 @@ public class ChatRecordServiceImpl implements ChatRecordService {
pull.setChatRecords(chatRecords);
pull.setPages(pageInfo.getPages());
pull.setTotal(pageInfo.getTotal());
return new MsgBody<PullChatRecord>().setStatus(ResultStatus.SUCCESS.getCode()).setData(pull);
return new MsgBody<PullChatRecord>().setCode(ResultStatus.SUCCESS.getCode()).setData(pull);
}
@Override
public MsgBody<List<ChatRecord>> updateReceiveTime(List<ChatRecord> chats) {
chatRecordMapper.updateBatchReceiveTimeById(chats, remainder(chats.get(0).getUserId()));
return new MsgBody<List<ChatRecord>>().setStatus(ResultStatus.SUCCESS.getCode()).setData(chats);
return new MsgBody<List<ChatRecord>>().setCode(ResultStatus.SUCCESS.getCode()).setData(chats);
}
@Override
public ChatRecord getLatestMsg(Long userId, Long merchantId) {
return chatRecordMapper.getLatestRecord(userId, merchantId, remainder(userId));
}
/**
* 使用userId取聊天记录表数量余数
*
......
......@@ -51,7 +51,7 @@ public class MsgBodyServiceImpl implements MsgBodyService {
final ChatService chatService = singleChatFactory.getService(ctx.channel().attr(ChannelAttributeKey.ROLE_TYPE).get());
switch (msgBody.getStatus()) {
switch (msgBody.getCode()) {
case SEND_MSG:
// 获取用户、客服Id
......@@ -59,18 +59,18 @@ public class MsgBodyServiceImpl implements MsgBodyService {
// 先保存聊天消息
chatService.save(id, msgBody);
// 再发送回执
ctx.channel().writeAndFlush(msgBody.setStatus(CHECK_MSG)).addListener((ChannelFutureListener) future -> {
ctx.channel().writeAndFlush(msgBody.setCode(CHECK_MSG)).addListener((ChannelFutureListener) future -> {
// 获取对应的channel
final NioSocketChannel channel = chatService.distribution(id, msgBody);
if (channel != null) {
// 最后发送聊天消息
chatService.send(channel, msgBody.setStatus(SEND_MSG));
chatService.send(channel, msgBody.setCode(SEND_MSG));
}
});
break;
case CHECK_MSG:
msgBody.setStatus(MsgBody.HAVE_READ);//回执
msgBody.setCode(MsgBody.HAVE_READ);//回执
chatService.ack(msgBody);
break;
......@@ -82,7 +82,7 @@ public class MsgBodyServiceImpl implements MsgBodyService {
@Override
public void sendAndAck(NioSocketChannel channel, MsgBody<ChatRecord> msgBody) throws JsonProcessingException {
msgBody.setStatus(SEND_MSG);
msgBody.setCode(SEND_MSG);
// 先保存消息至Redis
redisTemplate.opsForHash().put(NettyConstant.MSG_KEY + msgBody.getData().getUserId(), msgBody.getData().getId(), JsonUtils.obj2Json(msgBody));
// 再默认以用户没有收到消息为前提,做循环、延迟通知
......
package com.ym.im.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ym.im.entity.ChatRecord;
import com.ym.im.entity.MsgBody;
import com.ym.im.entity.Session;
import com.ym.im.entity.enums.ResultStatus;
import com.ym.im.entity.model.SessionInfo;
import com.ym.im.mapper.SessionListMapper;
import com.ym.im.service.ChatRecordService;
import com.ym.im.service.SessionListService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author: JJww
* @Date:2020/10/21
......@@ -15,5 +26,18 @@ import org.springframework.stereotype.Service;
@Service
public class SessionListServiceImpl extends ServiceImpl<SessionListMapper, Session> implements SessionListService {
@Autowired
private ChatRecordService chatRecordService;
@Override
public MsgBody<List<SessionInfo>> getSessionList(Long userId) {
final List<SessionInfo> sessions = new ArrayList<>();
baseMapper.selectList(new QueryWrapper<Session>().lambda().eq(Session::getUserId, userId)).forEach(session -> {
final ChatRecord latestMsg = chatRecordService.getLatestMsg(userId, session.getMerchantId());
final SessionInfo sessionModel = new SessionInfo().setLatestRecord(latestMsg.getMsgInfo()).setRecordId(latestMsg.getId());
BeanUtils.copyProperties(session, sessionModel);
sessions.add(sessionModel);
});
return new MsgBody<List<SessionInfo>>().setCode(ResultStatus.SUCCESS.getCode()).setData(sessions);
}
}
......@@ -54,7 +54,7 @@ public class StaffServiceImpl implements StaffService {
final Long staffId = staffSocketInfo.getStaffId();
userSocketInfo.setStaff(merchantId, staffId);
//通知用户 新的客服
userSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.DISTRIBUTION_STAFF).setData(new IdModel().setStaffId(staffId).setUserId(userId)));
userSocketInfo.writeAndFlush(new MsgBody<>().setCode(MsgBody.DISTRIBUTION_STAFF).setData(new IdModel().setStaffId(staffId).setUserId(userId)));
}
return staffSocketInfo;
}
......@@ -69,14 +69,14 @@ public class StaffServiceImpl implements StaffService {
final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(userId);
final StaffSocketInfo staffSocketInfo = channelGroup.getMerchantStaff(staffId);
if (staffSocketInfo == null || userSocketInfo == null) {
return new MsgBody<>().setStatus(MsgBody.BINDINGFAILURE).setMessage(ResultStatus.FORWARD_FAILURE.getMessage());
return new MsgBody<>().setCode(MsgBody.BINDINGFAILURE).setMessage(ResultStatus.FORWARD_FAILURE.getMessage());
}
//移除原客服绑定
channelGroup.getMerchantStaff(userSocketInfo.getStaffId(merchantId)).getUserIds().remove(userId);
//设置新的客服
staffSocketInfo.getUserIds().add(userId);
userSocketInfo.setStaff(merchantId, staffId);
final MsgBody<IdModel> msgBody = new MsgBody<IdModel>().setStatus(MsgBody.DISTRIBUTION_STAFF).setData(idModel);
final MsgBody<IdModel> msgBody = new MsgBody<IdModel>().setCode(MsgBody.DISTRIBUTION_STAFF).setData(idModel);
//通知用户 新客服ID
userSocketInfo.writeAndFlush(msgBody);
//通知新客服
......@@ -91,7 +91,7 @@ public class StaffServiceImpl implements StaffService {
channelGroup.STAFF_GROUP.get(merchantId).forEach((k, v) -> {
staffs.add(v);
});
return new MsgBody<>().setStatus(ResultStatus.SUCCESS.getCode()).setData(staffs);
return new MsgBody<>().setCode(ResultStatus.SUCCESS.getCode()).setData(staffs);
}
......
......@@ -113,12 +113,12 @@ public class StaffSingleChatServiceImpl implements ChatService {
if (currentStaffId == null) {
//通知用户 新的客服
userSocketInfo.setStaff(merchantId, id);
userSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.DISTRIBUTION_STAFF).setData(new IdModel().setStaffId(id).setUserId(userId)));
userSocketInfo.writeAndFlush(new MsgBody<>().setCode(MsgBody.DISTRIBUTION_STAFF).setData(new IdModel().setStaffId(id).setUserId(userId)));
channelGroup.getMerchantStaff(id).getUserIds().add(userId);
}
if (currentStaffId != null && !currentStaffId.equals(id)) {
//通知客服 绑定失败 当前用户已绑定客服
channelGroup.getMerchantStaff(id).writeAndFlush(new MsgBody<>().setStatus(MsgBody.BINDINGFAILURE).setData(new IdModel().setStaffId(currentStaffId).setUserId(userId).setMerchantId(merchantId)));
channelGroup.getMerchantStaff(id).writeAndFlush(new MsgBody<>().setCode(MsgBody.BINDINGFAILURE).setData(new IdModel().setStaffId(currentStaffId).setUserId(userId).setMerchantId(merchantId)));
return null;
}
return userSocketInfo.getChannel();
......
......@@ -37,7 +37,7 @@ public class UserServiceImpl implements UserService {
if (usersId.size() > 0) {
redisTemplate.delete(NettyConstant.STAFF_USERIDS_KEY + staffId);
}
return new MsgBody<>().setStatus(ResultStatus.SUCCESS.getCode()).setData(usersId);
return new MsgBody<>().setCode(ResultStatus.SUCCESS.getCode()).setData(usersId);
}
@Override
......@@ -45,14 +45,14 @@ public class UserServiceImpl implements UserService {
final StaffSocketInfo staffSocketInfo = channelGroup.getMerchantStaff(idModel.getStaffId());
if (staffSocketInfo == null) {
return new MsgBody<>().setStatus(ResultStatus.REQUEST_ERROR.getCode()).setMessage(ResultStatus.REQUEST_ERROR.getMessage());
return new MsgBody<>().setCode(ResultStatus.REQUEST_ERROR.getCode()).setMessage(ResultStatus.REQUEST_ERROR.getMessage());
}
staffSocketInfo.getUserIds().remove(idModel.getUserId());
final UserSocketInfo userSocketInfo = channelGroup.USER_GROUP.get(idModel.getUserId());
if (userSocketInfo != null && idModel.getStaffId().equals(userSocketInfo.getStaffId(idModel.getMerchantId()))) {
userSocketInfo.getStaffIds().remove(idModel.getMerchantId());
}
return new MsgBody<>().setStatus(ResultStatus.SUCCESS.getCode());
return new MsgBody<>().setCode(ResultStatus.SUCCESS.getCode());
}
@Override
......@@ -65,9 +65,9 @@ public class UserServiceImpl implements UserService {
* 用户在线未绑定客服
*/
if (userSocketInfo == null || (userSocketInfo != null && idModel.getStaffId().equals(userSocketInfo.getStaffId(merchantId)) || userSocketInfo.getStaffId(merchantId) == null)) {
return new MsgBody<>().setStatus(ResultStatus.SUCCESS.getCode());
return new MsgBody<>().setCode(ResultStatus.SUCCESS.getCode());
}
return new MsgBody<>().setStatus(ResultStatus.CHECK_FAILURE.getCode()).setData(userSocketInfo.getStaffId(merchantId));
return new MsgBody<>().setCode(ResultStatus.CHECK_FAILURE.getCode()).setData(userSocketInfo.getStaffId(merchantId));
}
......
......@@ -217,7 +217,7 @@ public class UserSingleChatServiceImpl implements ChatService {
channelGroup.USER_GROUP.get(userId).getStaffIds().forEach((merchantId, staffId) -> {
channelGroup.STAFF_GROUP.get(merchantId).values().forEach(staffSocketInfo -> {
staffSocketInfo.getUserIds().remove(userId);
staffSocketInfo.writeAndFlush(new MsgBody<>().setStatus(MsgBody.USERS_ONLINE).setData(new IdModel().setStaffId(staffId).setUserId(userId)));
staffSocketInfo.writeAndFlush(new MsgBody<>().setCode(MsgBody.USERS_ONLINE).setData(new IdModel().setStaffId(staffId).setUserId(userId)));
});
});
}
......
//package com.ym.im.util;
//
//import cn.hutool.extra.template.TemplateConfig;
//import com.baomidou.mybatisplus.core.config.GlobalConfig;
//import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
//import com.baomidou.mybatisplus.core.toolkit.StringPool;
//import com.baomidou.mybatisplus.generator.AutoGenerator;
//import com.baomidou.mybatisplus.generator.InjectionConfig;
//import com.baomidou.mybatisplus.generator.config.*;
//import com.baomidou.mybatisplus.generator.config.po.TableInfo;
//import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
//import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
......
......@@ -34,9 +34,9 @@ spring:
simple:
default-requeue-rejected: false
redis:
database: 5
host: 127.0.0.1
password:
database: 0
host: 47.99.47.225
password: temple123456
port: 6379
# (重要!)设置mvc默认语言为zh_CN,默认语言必须为static.i18n目录下有的语言配置文件,否则跟随服务器语言
mvc:
......
......@@ -200,5 +200,16 @@
</trim>
</select>
<select id="getLatestRecord" resultType="com.ym.im.entity.ChatRecord">
SELECT
<include refid="Base_Column_List"/>
FROM chat_record_${index}
where
user_id = #{userId}
and merchant_id = #{merchantId}
order by send_time desc
limit 1
</select>
</mapper>
......@@ -72,7 +72,6 @@
<module>api-system</module>
<module>api-app</module>
<module>api-merchant</module>
<module>customer-service</module>
</modules>
<dependencyManagement>
......
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