客服功能
Published in:2022-11-05 | category: 项目

最近一段时间我们在完善项目,我们在写的项目是一个商城项目。上次考核的时候考虑到进度问题,客服功能只做了一半,这次项目我对它进行了完善。

客服功能因为要实现即时通信,我主要是基于websocket实现的,它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的。因为是第一次去写类似的功能,所以在实现过程中遇到了很多问题。

离线消息

因为客服和用户并不是一直在线的,但是在用户离线的时候别人向他发送的消息他也应该接收到。所以在发送信息的时候我加了一个判断,当用户离线的时候将接收到的信息存到redis里面,在用户连接websocket的查询redis判断用户是否有离线的信息,如果有,在登录的时候一起发送过来。

发送消息

1
2
3
4
5
6
7
8
Session userSession = map.get("user:" + userId);
if (userSession != null) {
userSession.getAsyncRemote().sendText(msgJson);
}
{
String userKey = "msg@" + message.getUserId() + ":offLine";
redisUtil.lLeftPush(userKey, msgJson);
}

登录时判断

1
2
3
4
5
String chatKey= "msg@" +userId + ":offLine";
while(redisUtil.hasKey(chatKey)&&redisUtil.lLen(chatKey)!=0){
String msg = redisUtil.lRightPop(chatKey);
session.getAsyncRemote().sendText(msg);
}

角色绑定:

因为店铺与客服是一对多的关系,一个店铺里面可以有多个客服在线,当用户与客服进行聊天时,要保证一个客服和一个用户进行聊天,否则不符合逻辑。所以写项目的时候我给聊天列表设置了一个状态,客服可以查看所有的聊天列表,然后选择性进行接取业务,当一个聊天列表被一个客服接取后,其他客服不能进入这个聊天列表与用户进行聊天。

因为店铺与客服的一对多的关系,所以在用户反馈的时候该店铺的所有客服都可以看到聊天记录。我在用户给店铺发送信息的时候,先查询店铺里面有哪些客服,再查询客服的session,遍历给客服发送消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@ServerEndpoint(value = "/websocket/{userId}")
@Component
public class WebSocketUtil {

private static CopyOnWriteArraySet<WebSocketUtil> webSocketSet = new CopyOnWriteArraySet<WebSocketUtil>();

private Session session;

private String key;

private static Map<String,Session> map = new HashMap<>();
// private static Map<Integer, List<Integer>> storeMap=new HashMap<>();

private static RedisUtil redisUtil;

private static ChatDao chatDao;

@Autowired
public void setRedisUtil(RedisUtil redisUtil){
WebSocketUtil.redisUtil = redisUtil;
}

@Autowired
public void setChatDao(ChatDao chatDao){
WebSocketUtil.chatDao = chatDao;
}

/**
* 连接建立成功调用的方法
*/

@OnOpen
public void onOpen(Session session,@PathParam("userId")Integer userId) {
this.session = session;
this.key="user:"+userId;
map.put(key, session);
String chatKey= "msg@" +userId + ":offLine";
while(redisUtil.hasKey(chatKey)&&redisUtil.lLen(chatKey)!=0){
String msg = redisUtil.lRightPop(chatKey);
session.getAsyncRemote().sendText(msg);
}
webSocketSet.add(this);
//加入set中
System.out.println("有新连接加入:"+userId+",当前在线人数为" + webSocketSet.size());
this.session.getAsyncRemote().sendText("恭喜"+userId+"成功连接上WebSocket(其频道号:"+userId+")-->当前在线人数为:"+webSocketSet.size());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
System.out.println("有一连接关闭!当前在线人数为" + webSocketSet.size());
}

/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message,@PathParam("id") String id) {
}
/**
* 发生错误时调用
*
*/
@OnError
public void onError( Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 群发自定义消息
* */
public void broadcast(String message){
for (WebSocketUtil item : webSocketSet) {
//同步异步说明参考:http://blog.csdn.net/who_is_xiaoming/article/details/53287691
//this.session.getBasicRemote().sendText(message);
item.session.getAsyncRemote().sendText(message);//异步发送消息.
}
}

public void sendMessage(Integer userId, Integer storeId, Message message) {
Session userSession = map.get("user:" + userId);
String msgJson = JSON.toJSONString(message);
String userListKey = "userList:" + userId + "@chat";
String storeListKey = "storeList:" + storeId + "@chat";
int userList = (int) Double.parseDouble(redisUtil.zScore(userListKey, storeId + "") + "");
int storeList = (int) Double.parseDouble(redisUtil.zScore(storeListKey, userId + "") + "");
//发送给用户.
if (userSession != null) {
userSession.getAsyncRemote().sendText(msgJson);
}
{
String userKey = "msg@" + message.getUserId() + ":offLine";
redisUtil.lLeftPush(userKey, msgJson);
}
// List<Integer> list = storeMap.get(storeId);
String userKey = "msg@" + userList + ":message";
redisUtil.lLeftPush(userKey, msgJson);
String storeKey = "msg@" + storeList + ":message";
redisUtil.lLeftPush(storeKey, msgJson);
Integer chatListId;
if (message.getMessageStatus() == 0) {
chatListId = storeId;
} else {
chatListId = userId;
}
String numKey = "chat@No:" + chatListId + "num";
if (redisUtil.hasKey(numKey)) {
redisUtil.incrBy(numKey, 1);
} else {
redisUtil.set(numKey, "1");
}
List<Integer> list=null;
if(redisUtil.hasKey("chatService:"+storeId)){
Set<String> strings = redisUtil.setMembers("chatService:" + storeId);
list = strings.stream().map(Integer::parseInt).collect(Collectors.toList());
}
if (list != null) {
for (Integer id :
list) {
Session chatSession = map.get("user:" + id);
String chatKey;
if (chatSession != null) {
chatSession.getAsyncRemote().sendText(msgJson);
} else {
chatKey = "msg@" + message.getUserId() + ":offLine";
redisUtil.lLeftPush(chatKey, msgJson);
}
}
}
}
Prev:
秒杀功能
Next:
mysql索引