roomList = groupService.getRoomList(reqBody.getName(), reqBody.getRoomId());
44 |
45 | respBody.setRoomList(roomList);
46 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_SEARCH_ROOM_RESP, respBody), Im.CHARSET);
47 |
48 | Im.send(channelContext,response);
49 | return null;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/commond/handler/room/SetPublicRoomReqHandler.java:
--------------------------------------------------------------------------------
1 | package org.example.commond.handler.room;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.serializer.SerializerFeature;
5 | import org.example.commond.AbstractCmdHandler;
6 | import org.example.commond.CommandManager;
7 | import org.example.config.Im;
8 | import org.example.enums.CommandEnum;
9 | import org.example.enums.GroupOutTypeEnum;
10 | import org.example.packets.bean.Group;
11 | import org.example.packets.bean.User;
12 | import org.example.packets.handler.message.ChatReqBody;
13 | import org.example.packets.handler.room.GroupAdminReqBody;
14 | import org.example.packets.handler.room.SetPublicRoomReqBody;
15 | import org.example.packets.handler.system.RespBody;
16 | import org.tio.core.ChannelContext;
17 | import org.tio.core.intf.Packet;
18 | import org.tio.websocket.common.WsRequest;
19 | import org.tio.websocket.common.WsResponse;
20 |
21 | public class SetPublicRoomReqHandler extends AbstractCmdHandler {
22 |
23 | @Override
24 | public CommandEnum command() {
25 | return CommandEnum.COMMAND_SET_PUBLIC_ROOM_REQ;
26 | }
27 |
28 | @Override
29 | public WsResponse handler(Packet packet, ChannelContext channelContext) {
30 | WsRequest request = (WsRequest) packet;
31 |
32 | SetPublicRoomReqBody body = JSON.parseObject(request.getBody(), SetPublicRoomReqBody.class);
33 |
34 | Group groupInfo = groupService.getGroupInfo(body.getRoomId());
35 | groupInfo.setPublicRoom(body.getPublicRoom());
36 | groupService.updateById(groupInfo);
37 |
38 |
39 | WsResponse wsResponse = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_SET_PUBLIC_ROOM_RESP), Im.CHARSET);
40 | Im.send(channelContext, wsResponse);
41 | return null;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/commond/handler/room/SetRoomAdminReqHandler.java:
--------------------------------------------------------------------------------
1 | package org.example.commond.handler.room;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.serializer.SerializerFeature;
5 | import org.example.commond.AbstractCmdHandler;
6 | import org.example.commond.CommandManager;
7 | import org.example.config.Chat;
8 | import org.example.config.Im;
9 | import org.example.enums.CommandEnum;
10 | import org.example.enums.GroupAdminTypeEnum;
11 | import org.example.enums.RoomRoleEnum;
12 | import org.example.packets.bean.User;
13 | import org.example.packets.bean.UserGroup;
14 | import org.example.packets.handler.message.ChatReqBody;
15 | import org.example.packets.handler.user.UserStatusBody;
16 | import org.example.packets.handler.room.GroupAdminReqBody;
17 | import org.tio.core.ChannelContext;
18 | import org.tio.core.intf.Packet;
19 | import org.tio.websocket.common.WsRequest;
20 | import org.tio.websocket.common.WsResponse;
21 |
22 | public class SetRoomAdminReqHandler extends AbstractCmdHandler {
23 |
24 | @Override
25 | public CommandEnum command() {
26 | return CommandEnum.COMMAND_SET_ROOM_ADMIN_REQ;
27 | }
28 |
29 | @Override
30 | public WsResponse handler(Packet packet, ChannelContext channelContext) {
31 |
32 | WsRequest request = (WsRequest) packet;
33 |
34 | GroupAdminReqBody body = JSON.parseObject(request.getBody(), GroupAdminReqBody.class);
35 |
36 | User user = Im.getUser(channelContext);
37 |
38 | UserGroup userGroup = userGroupService.getUserGroup(body.getRoomId(), body.getUserId());
39 | if (userGroup == null) {
40 | return null;
41 | }
42 |
43 | userGroup.setRole(body.getType().equals(GroupAdminTypeEnum.SET) ? RoomRoleEnum.SUB_ADMIN : RoomRoleEnum.GENERAL);
44 | userGroupService.update(userGroup);
45 |
46 | User userInfo = userService.getUserInfo(body.getUserId());
47 | userInfo.setRole(userGroup.getRole());
48 |
49 | // 用户状态变化消息
50 | UserStatusBody userStatusBody = new UserStatusBody();
51 | userStatusBody.setGroup(groupService.getGroupInfo(body.getRoomId()));
52 | userStatusBody.setUser(userInfo);
53 |
54 | Chat.sendToGroup(userStatusBody, channelContext, true);
55 |
56 | // 移交群主消息
57 | AbstractCmdHandler command = CommandManager.getCommand(CommandEnum.COMMAND_CHAT_REQ);
58 | String content;
59 | if (body.getType().equals(GroupAdminTypeEnum.SET)) {
60 | // 发送退出群聊消息
61 | content = user.getUsername() + "已设置\"" + userService.getUserInfo(body.getUserId()).getUsername() + "\"为管理员";
62 | } else {
63 | content = user.getUsername() + "已解除\"" + userService.getUserInfo(body.getUserId()).getUsername() + "\"管理员身份";
64 | }
65 |
66 | ChatReqBody chatReqBody = ChatReqBody.buildSystem(body.getRoomId(), user.getId(), content);
67 |
68 | WsRequest wsRequest = WsRequest.fromText(JSON.toJSONString(chatReqBody, SerializerFeature.DisableCircularReferenceDetect), Im.CHARSET);
69 |
70 | command.handler(wsRequest, channelContext);
71 | return null;
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/commond/handler/room/UserGroupConfigReqHandler.java:
--------------------------------------------------------------------------------
1 | package org.example.commond.handler.room;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import org.example.commond.AbstractCmdHandler;
5 | import org.example.config.Im;
6 | import org.example.enums.CommandEnum;
7 | import org.example.packets.bean.Group;
8 | import org.example.packets.bean.User;
9 | import org.example.packets.bean.UserGroup;
10 | import org.example.packets.handler.system.RespBody;
11 | import org.example.packets.handler.room.UserGroupConfigReqBody;
12 | import org.tio.core.ChannelContext;
13 | import org.tio.core.intf.Packet;
14 | import org.tio.websocket.common.WsRequest;
15 | import org.tio.websocket.common.WsResponse;
16 |
17 | /**
18 | * 群组用户修改自己的配置
19 | *
20 | * 通知 / 群组备注 / 成员备注 等
21 | *
22 | *
23 | * @author smart
24 | * @since 1.0.0
25 | */
26 | public class UserGroupConfigReqHandler extends AbstractCmdHandler {
27 |
28 | @Override
29 | public CommandEnum command() {
30 | return CommandEnum.COMMAND_USER_GROUP_CONFIG_REQ;
31 | }
32 |
33 | @Override
34 | public WsResponse handler(Packet packet, ChannelContext channelContext) {
35 |
36 | WsRequest request = (WsRequest) packet;
37 |
38 | UserGroupConfigReqBody body = JSON.parseObject(request.getBody(), UserGroupConfigReqBody.class);
39 |
40 | switch (body.getType()) {
41 | case NOTICE:
42 | setNotice(body, channelContext);
43 | break;
44 | case GROUP_REMARK:
45 | setGroupRemark(body, channelContext);
46 | break;
47 | case MOVE_TOP:
48 | setMoveTop(body, channelContext);
49 | break;
50 | default:
51 | break;
52 | }
53 |
54 | return null;
55 | }
56 |
57 | private void setMoveTop(UserGroupConfigReqBody body, ChannelContext channelContext) {
58 | User user = Im.getUser(channelContext);
59 | UserGroup userGroup = userGroupService.getUserGroup(body.getRoomId(), user.getId());
60 | userGroup.setTop(body.getMoveTop());
61 | userGroupService.update(userGroup);
62 |
63 | if (Boolean.TRUE.equals(body.getMoveTop())) {
64 | body.setIndex(9999999999999L);
65 | } else {
66 | Group group = groupService.getGroupInfo(body.getRoomId());
67 | body.setIndex(group.getIndex());
68 | }
69 |
70 | String success = RespBody.success(CommandEnum.COMMAND_USER_GROUP_CONFIG_RESP, body);
71 | WsResponse response = WsResponse.fromText(success, Im.CHARSET);
72 | Im.send(channelContext, response);
73 |
74 | }
75 |
76 | /**
77 | * 设置通知
78 | *
79 | * @param config 配置信息
80 | * @param channelContext 上下文信息
81 | */
82 | private void setNotice(UserGroupConfigReqBody config, ChannelContext channelContext) {
83 |
84 | User user = Im.getUser(channelContext);
85 | UserGroup userGroup = userGroupService.getUserGroup(config.getRoomId(), user.getId());
86 | userGroup.setNotice(config.getNotice());
87 | userGroupService.update(userGroup);
88 |
89 | WsResponse wsResponse = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_USER_GROUP_CONFIG_RESP, config), Im.CHARSET);
90 | Im.send(channelContext, wsResponse);
91 | }
92 |
93 | /**
94 | * 设置群组备注
95 | *
96 | * @param config 配置信息
97 | * @param channelContext 上下文信息
98 | */
99 | private void setGroupRemark(UserGroupConfigReqBody config, ChannelContext channelContext) {
100 |
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/commond/handler/system/EditProfileHandler.java:
--------------------------------------------------------------------------------
1 | package org.example.commond.handler.system;
2 |
3 | import cn.hutool.core.util.StrUtil;
4 | import com.alibaba.fastjson.JSON;
5 | import org.example.commond.AbstractCmdHandler;
6 | import org.example.config.Chat;
7 | import org.example.config.Im;
8 | import org.example.enums.CommandEnum;
9 | import org.example.packets.bean.Group;
10 | import org.example.packets.bean.User;
11 | import org.example.packets.handler.user.EditProfileReqBody;
12 | import org.example.packets.handler.system.RespBody;
13 | import org.example.packets.handler.user.UserStatusBody;
14 | import org.tio.core.ChannelContext;
15 | import org.tio.core.intf.Packet;
16 | import org.tio.websocket.common.WsRequest;
17 | import org.tio.websocket.common.WsResponse;
18 |
19 | import java.util.List;
20 |
21 | public class EditProfileHandler extends AbstractCmdHandler {
22 |
23 | @Override
24 | public CommandEnum command() {
25 | return CommandEnum.COMMAND_EDIT_PROFILE_REQ;
26 | }
27 |
28 | @Override
29 | public WsResponse handler(Packet packet, ChannelContext channelContext) {
30 |
31 | WsRequest request = (WsRequest) packet;
32 |
33 | EditProfileReqBody editProfileReqBody = JSON.parseObject(request.getBody(), EditProfileReqBody.class);
34 |
35 | User userInfo = userService.getUserInfo(editProfileReqBody.getUserId());
36 | if (StrUtil.isNotBlank(editProfileReqBody.getAvatar())) {
37 | userInfo.setAvatar(editProfileReqBody.getAvatar());
38 | }
39 |
40 | if (StrUtil.isNotBlank(editProfileReqBody.getName())) {
41 | userInfo.setUsername(editProfileReqBody.getName());
42 | }
43 | userService.updateById(userInfo);
44 | Im.getUser(channelContext).setUsername(userInfo.getUsername());
45 | Im.getUser(channelContext).setAvatar(userInfo.getAvatar());
46 |
47 | // 发送修改响应消息
48 | UserStatusBody userStatusBody = new UserStatusBody();
49 | userStatusBody.setUser(userInfo);
50 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_EDIT_PROFILE_RESP, userStatusBody), Im.CHARSET);
51 |
52 | Im.send(channelContext, response);
53 |
54 | // 给用户所在的群组发送消息
55 | List userGroups = userGroupService.getUserGroups(userInfo.getId());
56 | for (Group userGroup : userGroups) {
57 | /* List groupUsers = messageHelper.getGroupUsers(userGroup.getRoomId());
58 | userGroup.setUsers(groupUsers);*/
59 | userStatusBody.setGroup(userGroup);
60 | Chat.sendToGroup(userStatusBody, channelContext, true);
61 | }
62 |
63 | return null;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/commond/handler/system/SetNewPasswordReqHandler.java:
--------------------------------------------------------------------------------
1 | package org.example.commond.handler.system;
2 |
3 | import cn.hutool.crypto.SecureUtil;
4 | import com.alibaba.fastjson.JSON;
5 | import org.example.commond.AbstractCmdHandler;
6 | import org.example.config.Im;
7 | import org.example.enums.CommandEnum;
8 | import org.example.packets.bean.Auth;
9 | import org.example.packets.bean.User;
10 | import org.example.packets.handler.system.RespBody;
11 | import org.example.packets.handler.system.SetNewPasswordReqBody;
12 | import org.example.packets.handler.user.EditProfileReqBody;
13 | import org.tio.core.ChannelContext;
14 | import org.tio.core.intf.Packet;
15 | import org.tio.websocket.common.WsRequest;
16 | import org.tio.websocket.common.WsResponse;
17 |
18 | public class SetNewPasswordReqHandler extends AbstractCmdHandler {
19 | @Override
20 | public CommandEnum command() {
21 | return CommandEnum.COMMAND_SET_NEW_PASSWORD_REQ;
22 | }
23 |
24 | @Override
25 | public WsResponse handler(Packet packet, ChannelContext channelContext) {
26 | WsRequest request = (WsRequest) packet;
27 |
28 | SetNewPasswordReqBody newPasswordReqBody = JSON.parseObject(request.getBody(), SetNewPasswordReqBody.class);
29 |
30 | if(!newPasswordReqBody.getPassword().equals(newPasswordReqBody.getRepeatPassword())){
31 | // 发送修改响应消息
32 | WsResponse response = WsResponse.fromText(RespBody.fail(CommandEnum.COMMAND_SET_NEW_PASSWORD_RESP, "密码与重复密码不相同"), Im.CHARSET);
33 | Im.send(channelContext, response);
34 | return null;
35 | }
36 | User user = Im.getUser(channelContext);
37 | Auth auth = authService.getByUserId(user.getId());
38 | if(!auth.getPassword().equals(SecureUtil.md5(newPasswordReqBody.getOldPassword()))){
39 | // 发送修改响应消息
40 | WsResponse response = WsResponse.fromText(RespBody.fail(CommandEnum.COMMAND_SET_NEW_PASSWORD_RESP, "旧密码不正确"), Im.CHARSET);
41 | Im.send(channelContext, response);
42 | return null;
43 | }
44 | auth.setPassword(SecureUtil.md5(newPasswordReqBody.getPassword()));
45 | authService.update(auth);
46 |
47 | // 发送修改响应消息
48 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_SET_NEW_PASSWORD_RESP), Im.CHARSET);
49 | Im.send(channelContext, response);
50 | return null;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/config/ImServerHttpStart.java:
--------------------------------------------------------------------------------
1 | package org.example.config;
2 |
3 | import org.example.ImServer;
4 | import org.tio.http.common.HttpConfig;
5 | import org.tio.http.common.handler.HttpRequestHandler;
6 | import org.tio.http.server.HttpServerStarter;
7 | import org.tio.http.server.handler.DefaultHttpRequestHandler;
8 | import org.tio.server.TioServerConfig;
9 |
10 | public class ImServerHttpStart {
11 |
12 | public static HttpConfig httpConfig;
13 |
14 | public static HttpRequestHandler requestHandler;
15 |
16 | public static HttpServerStarter httpServerStarter;
17 |
18 | public static TioServerConfig serverTioConfig;
19 |
20 | public static void start() throws Exception {
21 |
22 | httpConfig = new HttpConfig(CourierConfig.httpPort, null, null, null);
23 | httpConfig.setPageRoot("classpath:page");
24 | httpConfig.setMaxLiveTimeOfStaticRes(CourierConfig.httpMaxLiveTime);
25 | httpConfig.setPage404("404.html");
26 | httpConfig.setPage500("500.html");
27 | httpConfig.setUseSession(CourierConfig.httpUseSession);
28 | httpConfig.setCheckHost(CourierConfig.httpCheckHost);
29 |
30 | requestHandler = new DefaultHttpRequestHandler(httpConfig, ImServer.class);//第二个参数也可以是数组
31 |
32 | httpServerStarter = new HttpServerStarter(httpConfig, requestHandler);
33 | serverTioConfig = httpServerStarter.getTioServerConfig();
34 | httpServerStarter.start(); //启动http服务器
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/config/ImServerWebSocketStart.java:
--------------------------------------------------------------------------------
1 | package org.example.config;
2 |
3 | import org.example.listener.*;
4 | import org.example.protocol.ws.WsMsgHandler;
5 | import org.tio.server.TioServerConfig;
6 | import org.tio.websocket.server.WsServerStarter;
7 |
8 | import java.io.IOException;
9 |
10 | public class ImServerWebSocketStart {
11 |
12 | private final WsServerStarter wsServerStarter;
13 | private final TioServerConfig serverTioConfig;
14 |
15 |
16 | public ImServerWebSocketStart(int port, WsMsgHandler wsMsgHandler) throws IOException {
17 | wsServerStarter = new WsServerStarter(port, wsMsgHandler);
18 |
19 | serverTioConfig = wsServerStarter.getTioServerConfig();
20 | serverTioConfig.setGroupListener(new ImGroupListenerAdapter(new ImServerGroupListener()));
21 | serverTioConfig.setName(ImConfig.PROTOCOL_NAME);
22 | serverTioConfig.setTioServerListener(ImTioServerListener.me);
23 | serverTioConfig.ipStats.addDurations(ImConfig.IpStatDuration.IPSTAT_DURATIONS);
24 | serverTioConfig.setHeartbeatTimeout(CourierConfig.socketHeartbeat);
25 | }
26 |
27 | public static void start() throws Exception {
28 | ImServerWebSocketStart appStarter = new ImServerWebSocketStart(CourierConfig.socketPort, WsMsgHandler.me);
29 |
30 | ImConfig imServerConfig = new ImConfig();
31 | // imServerConfig.setMessageHelper(new MongoMessageHelper());
32 | imServerConfig.setImUserListener(new ImUserListenerAdapter(new ImServerUserListener()));
33 | imServerConfig.setTioConfig(appStarter.serverTioConfig);
34 |
35 | appStarter.wsServerStarter.start();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/listener/ImServerGroupListener.java:
--------------------------------------------------------------------------------
1 | package org.example.listener;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.example.config.Im;
5 | import org.example.packets.bean.Group;
6 | import org.example.packets.bean.User;
7 | import org.example.service.GroupService;
8 | import org.example.service.UserGroupService;
9 | import org.tio.core.ChannelContext;
10 |
11 | @Slf4j
12 | public class ImServerGroupListener extends AbstractImGroupListener {
13 |
14 | public final UserGroupService userGroupService = new UserGroupService();
15 | public final GroupService groupService = new GroupService();
16 |
17 | @Override
18 | public void doAfterBind(ChannelContext channelContext, Group group) {
19 | log.info("群组绑定监听");
20 | // 将绑定信息持久化到Redis
21 | // ImConfig.get().messageHelper.onAfterGroupBind(channelContext, group);
22 |
23 | String roomId = group.getRoomId();
24 | User user = Im.getUser(channelContext);
25 | // 添加用户群组信息 顺手就完成了群组用户 用户群组的绑定
26 | // userGroupService.addGroupUser(roomId, user.getId());
27 |
28 | // for (Group userGroup : user.getGroups()) {
29 | // if (!userGroup.getRoomId().equals(roomId)) {
30 | // continue;
31 | // }
32 | // groupService.updateById(userGroup);
33 | // }
34 | }
35 |
36 | @Override
37 | public void doAfterUnbind(ChannelContext channelContext, Group group) {
38 | log.info("群组绑监听");
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/listener/ImServerUserListener.java:
--------------------------------------------------------------------------------
1 | package org.example.listener;
2 |
3 | import org.example.packets.bean.User;
4 | import org.example.service.UserService;
5 | import org.tio.core.ChannelContext;
6 |
7 | public class ImServerUserListener extends AbstractImUserListener{
8 |
9 | private final UserService userService ;
10 |
11 | public ImServerUserListener() {
12 | userService = new UserService();
13 | }
14 |
15 | @Override
16 | public void doAfterBind(ChannelContext channelContext, User user) {
17 | userService.saveOrUpdate(user);
18 | }
19 |
20 | @Override
21 | public void doAfterUnbind(ChannelContext channelContext, User user) {
22 |
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/listener/ImTioServerListener.java:
--------------------------------------------------------------------------------
1 | package org.example.listener;
2 |
3 | import org.example.config.Chat;
4 | import org.example.config.Im;
5 | import org.example.config.ImConfig;
6 | import org.example.config.ImSessionContext;
7 | import org.example.enums.KeyEnum;
8 | import org.example.packets.ImClientNode;
9 | import org.example.packets.bean.Group;
10 | import org.example.packets.bean.User;
11 | import org.example.packets.bean.UserGroup;
12 | import org.example.packets.handler.user.UserStatusBody;
13 | import org.example.service.UserGroupService;
14 | import org.example.service.UserService;
15 | import org.tio.core.ChannelContext;
16 | import org.tio.core.Node;
17 | import org.tio.core.Tio;
18 | import org.tio.core.intf.Packet;
19 | import org.tio.utils.lock.SetWithLock;
20 | import org.tio.websocket.server.WsTioServerListener;
21 |
22 |
23 | import java.util.List;
24 |
25 | public class ImTioServerListener extends WsTioServerListener {
26 |
27 | private final UserService userService = new UserService();
28 |
29 | private final UserGroupService userGroupService = new UserGroupService();
30 |
31 | public static final ImTioServerListener me = new ImTioServerListener();
32 |
33 | @Override
34 | public boolean onHeartbeatTimeout(ChannelContext channelContext, Long interval, int heartbeatTimeoutCount) {
35 | return false;
36 | }
37 |
38 | @Override
39 | public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception {
40 | ImSessionContext sessionContext = new ImSessionContext();
41 | Node clientNode = channelContext.getClientNode();
42 | ImClientNode build = ImClientNode.builder().ip(clientNode.getIp()).port(clientNode.getPort()).build();
43 | build.setId(channelContext.getId());
44 | sessionContext.setImClientNode(build);
45 | channelContext.set(KeyEnum.IM_CHANNEL_SESSION_CONTEXT_KEY.getKey(), sessionContext);
46 | super.onAfterConnected(channelContext, isConnected, isReconnect);
47 | }
48 |
49 | @Override
50 | public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize) throws Exception {
51 | super.onAfterDecoded(channelContext, packet, packetSize);
52 | }
53 |
54 | @Override
55 | public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes) throws Exception {
56 | super.onAfterReceivedBytes(channelContext, receivedBytes);
57 | }
58 |
59 | @Override
60 | public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess) throws Exception {
61 | super.onAfterSent(channelContext, packet, isSentSuccess);
62 | }
63 |
64 | @Override
65 | public void onAfterHandled(ChannelContext channelContext, Packet packet, long cost) throws Exception {
66 | super.onAfterHandled(channelContext, packet, cost);
67 | }
68 |
69 | @Override
70 | public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception {
71 |
72 |
73 | User user = Im.getUser(channelContext);
74 | // 首先判断能不能拿到用户
75 | if (user != null) {
76 | // 然后判断是不是多机器登录, 当仅在当前登录时, 更新用户状态为离线
77 | SetWithLock userChannelContexts = Tio.getByUserid(ImConfig.get().getTioConfig(), user.getId());
78 | if(userChannelContexts.size() == 1){
79 | // 更新用户为离线状态
80 | userService.userOffline(user.getId());
81 |
82 | UserStatusBody build = UserStatusBody.builder().user(userService.getUserInfo(user.getId())).build();
83 |
84 | for (Group group : user.getGroups()) {
85 | UserGroup userGroup = userGroupService.getUserGroup(group.getRoomId(), user.getId());
86 | build.getUser().setRole(userGroup.getRole());
87 | // 给所在群组发送离线消息 用户状态更新
88 | List groupUsers = userGroupService.getGroupUsers(group.getRoomId());
89 | group.setUsers(groupUsers);
90 | build.setGroup(group);
91 | Chat.sendToGroup(build, channelContext);
92 | }
93 | }
94 |
95 | }
96 |
97 | super.onBeforeClose(channelContext, throwable, remark, isRemove);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/protocol/http/LoginController.java:
--------------------------------------------------------------------------------
1 | package org.example.protocol.http;
2 |
3 |
4 | import cn.hutool.core.util.IdUtil;
5 | import cn.hutool.core.util.StrUtil;
6 | import cn.hutool.crypto.SecureUtil;
7 | import cn.hutool.jwt.JWTUtil;
8 | import com.alibaba.fastjson.JSON;
9 | import lombok.extern.slf4j.Slf4j;
10 | import org.example.enums.CommandEnum;
11 | import org.example.packets.bean.Auth;
12 | import org.example.packets.handler.system.LoginReqBody;
13 | import org.example.packets.handler.system.RespBody;
14 | import org.example.service.AuthService;
15 | import org.tio.http.common.HttpRequest;
16 | import org.tio.http.common.HttpResponse;
17 | import org.tio.http.server.annotation.RequestPath;
18 | import org.tio.http.server.util.Resps;
19 |
20 | import java.nio.charset.StandardCharsets;
21 | import java.util.HashMap;
22 |
23 | @Slf4j
24 | @RequestPath("/user")
25 | public class LoginController {
26 |
27 | private final AuthService authService;
28 |
29 | public LoginController() {
30 | authService = new AuthService();
31 | }
32 |
33 | @RequestPath("/login")
34 | public HttpResponse login(HttpRequest httpRequest) {
35 | LoginReqBody loginReq = JSON.parseObject(httpRequest.getBodyString(), LoginReqBody.class);
36 |
37 | log.info(String.valueOf(loginReq));
38 |
39 | if (StrUtil.isBlank(loginReq.getAccount())) {
40 | return Resps.txt(httpRequest, RespBody.fail(CommandEnum.COMMAND_LOGIN_RESP, "用户名不能为空"));
41 | }
42 |
43 | if (StrUtil.isBlank(loginReq.getPassword())) {
44 | return Resps.txt(httpRequest, RespBody.fail(CommandEnum.COMMAND_LOGIN_RESP, "密码不能为空"));
45 | }
46 |
47 | Auth auth = authService.getByAccount(loginReq.getAccount());
48 | if (auth == null) {
49 | return Resps.txt(httpRequest, RespBody.fail(CommandEnum.COMMAND_LOGIN_RESP, "账号不存在"));
50 | }
51 |
52 | if (!auth.getPassword().equals(SecureUtil.md5(loginReq.getPassword()))) {
53 | return Resps.txt(httpRequest, RespBody.fail(CommandEnum.COMMAND_LOGIN_RESP, "密码错误"));
54 | }
55 |
56 | HashMap hashMap = new HashMap<>() {
57 | private static final long serialVersionUID = 1L;
58 |
59 | {
60 | put("uid", auth.getUserId());
61 | put("expire_time", System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 60);
62 | }
63 | };
64 |
65 | String token = JWTUtil.createToken(hashMap, "123456".getBytes(StandardCharsets.UTF_8));
66 |
67 | return Resps.txt(httpRequest, RespBody.success(CommandEnum.COMMAND_LOGIN_RESP, token));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/protocol/http/RegisterController.java:
--------------------------------------------------------------------------------
1 | package org.example.protocol.http;
2 |
3 | import cn.hutool.core.util.IdUtil;
4 | import cn.hutool.core.util.StrUtil;
5 | import cn.hutool.crypto.SecureUtil;
6 | import com.alibaba.fastjson.JSONObject;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.example.enums.CommandEnum;
9 | import org.example.enums.DefaultEnum;
10 | import org.example.packets.Status;
11 | import org.example.packets.bean.Auth;
12 | import org.example.packets.bean.User;
13 | import org.example.packets.handler.system.RegisterReqBody;
14 | import org.example.packets.handler.system.RespBody;
15 | import org.example.protocol.http.service.UploadService;
16 | import org.example.service.AuthService;
17 | import org.example.service.UserService;
18 | import org.tio.http.common.HttpRequest;
19 | import org.tio.http.common.HttpResponse;
20 | import org.tio.http.server.annotation.RequestPath;
21 | import org.tio.http.server.util.Resps;
22 |
23 | @Slf4j
24 | @RequestPath("/account")
25 | public class RegisterController {
26 |
27 | private final AuthService authService;
28 |
29 | private final UserService userService;
30 |
31 | public RegisterController() {
32 | authService = new AuthService();
33 | userService = new UserService();
34 | }
35 |
36 | @RequestPath("/register")
37 | public HttpResponse register(HttpRequest httpRequest) {
38 | RegisterReqBody reqBody = JSONObject.parseObject(httpRequest.getBodyString(), RegisterReqBody.class);
39 |
40 | log.info(String.valueOf(reqBody));
41 |
42 | Auth authData = authService.getByAccount(reqBody.getAccount());
43 | if (authData != null) {
44 | return Resps.txt(httpRequest, RespBody.fail(CommandEnum.COMMAND_REGISTER_RESP, "该登录账户已存在"));
45 | }
46 |
47 | String url = UploadService.uploadDefault(DefaultEnum.ACCOUNT);
48 | log.info("未查询到用户信息,创建用户");
49 | User user = User.builder().id(IdUtil.getSnowflake().nextIdStr()).username(reqBody.getUsername()).status(Status.offline())
50 | .avatar(url).build();
51 |
52 | userService.saveOrUpdate(user);
53 |
54 | Auth auth = authService.createAccount(reqBody, user.getId());
55 |
56 | return Resps.txt(httpRequest, RespBody.success(CommandEnum.COMMAND_REGISTER_RESP));
57 | }
58 |
59 | @RequestPath(value = "/check")
60 | public HttpResponse checkAccount(String account, HttpRequest request) {
61 | Auth auth = authService.getByAccount(account);
62 | if (auth != null) {
63 | return Resps.txt(request, RespBody.fail(CommandEnum.COMMAND_REGISTER_RESP, "该登录账户已存在"));
64 | }
65 | return Resps.txt(request, RespBody.success(CommandEnum.COMMAND_REGISTER_RESP));
66 | }
67 |
68 | @RequestPath(value = "/check/question")
69 | public HttpResponse checkAccountQuestion(String account, String question, String answer, HttpRequest request) {
70 | Auth auth = authService.getByAccount(account);
71 | if (auth != null) {
72 | if (StrUtil.isBlank(auth.getQuestion())){
73 | auth.setQuestion(question);
74 | auth.setAnswer(answer);
75 | authService.update(auth);
76 | return Resps.txt(request, RespBody.success(CommandEnum.COMMAND_REGISTER_RESP, "验证通过"));
77 | }
78 | if (auth.getQuestion().equals(question) && auth.getAnswer().equals(answer)) {
79 | return Resps.txt(request, RespBody.success(CommandEnum.COMMAND_REGISTER_RESP, "验证通过"));
80 | }
81 | }
82 | return Resps.txt(request, RespBody.fail(CommandEnum.COMMAND_REGISTER_RESP, "问题或答案不正确"));
83 | }
84 |
85 | @RequestPath(value = "/reset")
86 | public HttpResponse reset(HttpRequest request) {
87 | RegisterReqBody reqBody = JSONObject.parseObject(request.getBodyString(), RegisterReqBody.class);
88 |
89 | Auth auth = authService.getByAccount(reqBody.getAccount());
90 | if (auth != null) {
91 | if (auth.getQuestion().equals(reqBody.getQuestion()) && auth.getAnswer().equals(reqBody.getAnswer())
92 | && reqBody.getPassword().equals(reqBody.getRepeatPassword())) {
93 | auth.setPassword(SecureUtil.md5(reqBody.getPassword()));
94 | authService.update(auth);
95 | return Resps.txt(request, RespBody.success(CommandEnum.COMMAND_REGISTER_RESP, "修改成功"));
96 | }
97 | }
98 | return Resps.txt(request, RespBody.fail(CommandEnum.COMMAND_REGISTER_RESP, "非法请求"));
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/protocol/http/service/UploadService.java:
--------------------------------------------------------------------------------
1 | package org.example.protocol.http.service;
2 |
3 | import cn.hutool.core.io.file.FileNameUtil;
4 | import cn.hutool.core.io.resource.ResourceUtil;
5 | import cn.hutool.core.util.IdUtil;
6 | import cn.hutool.core.util.StrUtil;
7 | import com.google.common.collect.ImmutableList;
8 | import org.example.config.CourierConfig;
9 | import org.example.enums.DefaultEnum;
10 | import org.example.service.FileService;
11 | import org.example.util.MinIoUtils;
12 |
13 | import java.io.InputStream;
14 | import java.util.HashMap;
15 | import java.util.Map;
16 |
17 | /**
18 | * 文件上传服务
19 | */
20 | public class UploadService {
21 | private static final FileService fileService = new FileService();
22 |
23 | public static Map initMultiPartUpload(String filename, Integer partCount, String contentType) {
24 |
25 | String filePath = IdUtil.getSnowflake().nextIdStr() + '.' + FileNameUtil.extName(filename);
26 |
27 | Map result = new HashMap<>();
28 | if (partCount == 1) {
29 | String uploadObjectUrl = MinIoUtils.getUploadObjectUrl(filePath,contentType);
30 | if(uploadObjectUrl != null) {
31 | result.put("uploadUrls", ImmutableList.of(uploadObjectUrl));
32 | }
33 | } else {
34 | result = MinIoUtils.initMultiPartUpload(filePath, partCount, contentType);
35 | }
36 | result.put("objectName", filePath);
37 | return result;
38 | }
39 |
40 | public static boolean mergeMultipartUpload(String objectName, String uploadId) {
41 | return MinIoUtils.mergeMultipartUpload(objectName, uploadId);
42 | }
43 |
44 | public static InputStream downloadGetStream(String filePath) {
45 | return MinIoUtils.download(filePath);
46 | }
47 |
48 | public static boolean uploadFile(byte[] bytes, String name) {
49 | return MinIoUtils.uploadByte(bytes, name);
50 | }
51 |
52 | public static String uploadDefault(DefaultEnum defaultEnum) {
53 | String url = fileService.getFileUrl(defaultEnum.getKey());
54 | if (StrUtil.isBlank(url)) {
55 | byte[] bytes = ResourceUtil.readBytes("img/" + defaultEnum.getValue());
56 | boolean b = uploadFile(bytes, defaultEnum.getValue());
57 | if (b) {
58 | fileService.setFileUrl(defaultEnum.getKey(), defaultEnum.getValue(),defaultEnum.getValue(), (long) bytes.length);
59 | }
60 | }
61 | return CourierConfig.fileUrl + defaultEnum.getValue();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/protocol/ws/WsMsgHandler.java:
--------------------------------------------------------------------------------
1 | package org.example.protocol.ws;
2 |
3 | import cn.hutool.core.util.ObjectUtil;
4 | import com.alibaba.fastjson.JSON;
5 | import org.example.commond.AbstractCmdHandler;
6 | import org.example.commond.CommandManager;
7 | import org.example.commond.handler.LoginReqHandler;
8 | import org.example.config.Im;
9 | import org.example.config.ImConfig;
10 | import org.example.enums.CommandEnum;
11 | import org.example.packets.bean.User;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 | import org.tio.core.ChannelContext;
15 | import org.tio.http.common.HttpRequest;
16 | import org.tio.http.common.HttpResponse;
17 | import org.tio.websocket.common.WsRequest;
18 | import org.tio.websocket.common.WsResponse;
19 | import org.tio.websocket.server.handler.IWsMsgHandler;
20 |
21 | public class WsMsgHandler implements IWsMsgHandler {
22 | private static final Logger log = LoggerFactory.getLogger(WsMsgHandler.class);
23 |
24 | public static final WsMsgHandler me = new WsMsgHandler();
25 |
26 |
27 | @Override
28 | public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) {
29 | String clientIp = httpRequest.getClientIp();
30 | String username = httpRequest.getParam("username");
31 | log.info("收到来自{}的ws握手包{}", clientIp, username);
32 | return httpResponse;
33 | }
34 |
35 | @Override
36 | public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) {
37 | LoginReqHandler loginHandler = (LoginReqHandler) CommandManager.getCommand(CommandEnum.COMMAND_LOGIN_REQ);
38 |
39 | String token = httpRequest.getParam("token");
40 | WsRequest wsRequest = WsRequest.fromText(token, ImConfig.CHARSET);
41 | loginHandler.handler(wsRequest, channelContext);
42 | log.info("握手完毕{},{}", "66", "666");
43 | }
44 |
45 | @Override
46 | public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) {
47 | return null;
48 | }
49 |
50 | @Override
51 | public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) {
52 | AbstractCmdHandler command = CommandManager.getCommand(CommandEnum.COMMAND_CLOSE_REQ);
53 | User user = Im.getUser(channelContext);
54 | if(user != null){
55 | WsRequest request = WsRequest.fromText(user.getId(), Im.CHARSET);
56 | command.handler(request, channelContext);
57 | System.out.println("关闭连接");
58 | }
59 | return null;
60 | }
61 |
62 | @Override
63 | public Object onText(WsRequest wsRequest, String text, ChannelContext channelContext) {
64 | Integer cmd = JSON.parseObject(text).getInteger("cmd");
65 | if(cmd != 13){
66 | log.info("socket消息:{}", text);
67 | }
68 | CommandEnum commandEnum = CommandEnum.forNumber(cmd);
69 | AbstractCmdHandler command = CommandManager.getCommand(commandEnum);
70 | WsResponse wsResponse = command.handler(wsRequest, channelContext);
71 | if (ObjectUtil.isNotNull(wsResponse)) {
72 | Im.send(channelContext, wsResponse);
73 | }
74 | return null;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/util/TestUtil2.java:
--------------------------------------------------------------------------------
1 | package org.example.util;
2 |
3 | public class TestUtil2 {
4 |
5 | // public static void main(String[] args) {
6 | // String uploadObjectUrl = MinIoUtils.getUploadObjectUrl("a.png");
7 | // }
8 |
9 | // public static void main(String[] args) {
10 | // MinIoUtils minIoUtils = new MinIoUtils();
11 | // try {
12 | // minIoUtils.init();
13 | // System.out.println("连接完成");
14 | // } catch (Exception e) {
15 | // e.printStackTrace();
16 | // }
17 | // }
18 |
19 | public static void main(String[] args) {
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/im-server/src/main/java/org/example/util/ThreadPoolUtil.java:
--------------------------------------------------------------------------------
1 | package org.example.util;
2 |
3 | import java.util.List;
4 | import java.util.concurrent.LinkedBlockingQueue;
5 | import java.util.concurrent.ScheduledThreadPoolExecutor;
6 | import java.util.concurrent.ThreadPoolExecutor;
7 | import java.util.concurrent.TimeUnit;
8 |
9 | public final class ThreadPoolUtil {
10 |
11 | private void ThreadPoolUtil() {
12 |
13 | }
14 |
15 | static volatile int theadCount = 0;
16 |
17 | /**
18 | * 通用的线程池。
19 | */
20 | public static ThreadPoolExecutor commonPool = new ThreadPoolExecutor(5,
21 | 500, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5000)
22 | , r -> new Thread(r, "common thread " + theadCount++)
23 | );
24 |
25 | /**
26 | * 定时任务线程池, 注意定时任务要设置合理的定时时间, 要根据任务的耗时来合理设置。 建议定时任务还是使用spring的定时任务功能
27 | */
28 | public static ScheduledThreadPoolExecutor schedulePool = new ScheduledThreadPoolExecutor(1,
29 | r -> new Thread(r, "schedulePool thread " + theadCount++)
30 | );
31 |
32 | /**
33 | * 短时间批量任务的执行
34 | *
35 | * @param poolSize
36 | * @param runnables
37 | */
38 | public static void createPool(int poolSize, List runnables) {
39 | if (poolSize < 1) {
40 | throw new RuntimeException("请设置合理的线程池数量");
41 | }
42 | ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(poolSize,
43 | poolSize, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(runnables.size())
44 | , r -> new Thread(r, "create new Pool thread " + theadCount++));
45 |
46 | runnables.forEach(threadPoolExecutor::execute);
47 | //这里只是通知线程池不在接收新任务了,并且所有任务结束后,线程池要关闭
48 | threadPoolExecutor.shutdown();
49 | }
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/im-server/src/main/resources/application.setting:
--------------------------------------------------------------------------------
1 | applicationName=IM
2 |
3 | socketPort=9326
4 | socketHeartbeat=60000
5 |
6 | httpPort=8088
7 | httpMaxLiveTime=2000
8 | httpUseSession=false
9 | httpCheckHost=false
10 |
11 | checkFileMd5=true
12 |
13 | [dev]
14 | mongoHost=192.168.3.206
15 | mongoUserName=admin
16 | mongoPassword=QuniMade!
17 | fileUrl=https://192.168.3.206:9999/courier/
18 | minioHost=192.168.3.206
19 | minioPort=9000
20 | minioUseSSL=truebv
21 | minioAccessKey=AKIAIOSFODNN7EXAMPLE
22 | minioSecretKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
23 |
24 | [prod]
25 | mongoHost=127.0.0.1
26 | mongoUserName=admin
27 | mongoPassword=QuniMade!
28 | fileUrl=https://miniodev.o0o0oo.com/courier/
29 | minioHost=miniodev.o0o0oo.com
30 | minioPort=443
31 | minioUseSSL=true
32 | minioAccessKey=AKIAIOSFODNN7EXAMPLE
33 | minioSecretKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
--------------------------------------------------------------------------------
/im-server/src/main/resources/img/account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alisonispig/im/2117923905091aae516058e216505375265802ce/im-server/src/main/resources/img/account.png
--------------------------------------------------------------------------------
/im-server/src/main/resources/img/accountGroup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alisonispig/im/2117923905091aae516058e216505375265802ce/im-server/src/main/resources/img/accountGroup.png
--------------------------------------------------------------------------------
/im-server/src/main/resources/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alisonispig/im/2117923905091aae516058e216505375265802ce/im-server/src/main/resources/img/logo.png
--------------------------------------------------------------------------------
/im-server/src/main/resources/logback.properties:
--------------------------------------------------------------------------------
1 | #http://logback.qos.ch/manual/configuration.html
2 | # resource, file, url (??????????????)
3 |
4 | context.name=im
5 |
6 | log.dir=../logs/im
7 |
8 | rolling.policy.file.name.pattern=yyyy-MM-dd HH
9 | max.file.size=100MB
10 | max.history=50
11 |
12 | conversion.pattern=%d %-5level %logger{30}[%line]: %m%n
13 | root.level=info
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/im-server/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | ${context.name}
6 |
7 |
8 |
10 |
11 | ${conversion.pattern}
12 |
13 |
14 |
15 |
16 |
18 | ${log.dir}/error.log
19 |
21 | ${log.dir}/error.%d{${rolling.policy.file.name.pattern}}%d{mmss}.%i.log.zip
22 |
23 |
25 | ${max.file.size}
26 |
27 | ${max.history}
28 |
29 |
30 | ${conversion.pattern}
31 |
32 |
33 | ERROR
34 | ACCEPT
35 | DENY
36 |
37 |
38 |
40 | ${log.dir}/warn.log
41 |
43 | ${log.dir}/warn.%d{${rolling.policy.file.name.pattern}}%d{mmss}.%i.log.zip
44 |
45 |
47 | ${max.file.size}
48 |
49 | ${max.history}
50 |
51 |
52 | ${conversion.pattern}
53 |
54 |
55 | warn
56 | ACCEPT
57 | DENY
58 |
59 |
60 |
62 | ${log.dir}/info.log
63 |
65 | ${log.dir}/info.%d{${rolling.policy.file.name.pattern}}%d{mmss}.%i.log.zip
66 |
67 |
69 | ${max.file.size}
70 |
71 | ${max.history}
72 |
73 |
74 | ${conversion.pattern}
75 |
76 |
77 | INFO
78 | ACCEPT
79 | DENY
80 |
81 |
82 |
84 | ${log.dir}/debug.log
85 |
87 | ${log.dir}/debug.%d{${rolling.policy.file.name.pattern}}%d{mmss}.%i.log.zip
88 |
89 |
91 | ${max.file.size}
92 |
93 | ${max.history}
94 |
95 |
96 | ${conversion.pattern}
97 |
98 |
99 | debug
100 | ACCEPT
101 | DENY
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.example
8 | im
9 | pom
10 | 1.0-SNAPSHOT
11 |
12 | im-core
13 | im-server
14 |
15 |
16 |
17 | UTF-8
18 | 13
19 | 13
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------