项目实训(6) - 在线聊天2


前言

springboot整合netty实现一对一聊天室。

WebSocketHandler.java

核心功能由WebSocketHandler实现,发来的消息反序列化为MessageVO对象,通过type判断不同的功能。

package com.meeting.chatroom.handler;

import com.alibaba.fastjson.JSON;
import com.meeting.chatroom.entity.*;
import com.meeting.chatroom.service.ChatService;
import com.meeting.chatroom.service.FriendService;
import com.meeting.chatroom.util.SpringUtil;
import com.meeting.common.util.JwtTokenUtil;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.AttributeKey;

import java.util.HashMap;
import java.util.Map;

import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

public class WebSocketHandler extends SimpleChannelInboundHandler<Object> {

    private ChannelPromise handshakePromise;

    private Channel channel = null;

    private Long fromId = null;

    private final JwtTokenUtil jwtTokenUtil = SpringUtil.getBean(JwtTokenUtil.class);

    private final ChatService chatService = SpringUtil.getBean(ChatService.class);

    private final FriendService friendService = SpringUtil.getBean(FriendService.class);

    private final ChatChannelGroup chatChannelGroup = SpringUtil.getBean(ChatChannelGroup.class);

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        handshakePromise = ctx.newPromise();
    }

    /**
     * 活跃的通道  也可以当作用户连接上客户端进行使用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

    }

    /**
     * 出现异常
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // todo 异常处理
        System.out.println(cause.getClass());
        System.out.println(cause.getMessage());
        sendMessageToChannel(ctx.channel(), ResponseData.SERVER_PROBLEM);
    }

    /**
     * 不活跃的通道  就说明用户失去连接
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        // 从channelGroup通道组中移除
        chatChannelGroup.removeChannel(this.fromId, this.channel);
    }

    /**
     * 服务器接受客户端的数据信息
     * @param ctx
     * @param data
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object data) throws Exception {
        if (data instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) data);
        } else if (data instanceof TextWebSocketFrame) {
            handleWebSocketFrame((TextWebSocketFrame) data);
        }
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        if (!GET.equals(req.method())) {
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN, ctx.alloc().buffer(0)));
            return;
        }

        String token = req.headers().get("Sec-WebSocket-Protocol");
        if (token == null || !jwtTokenUtil.validateToken(token)
                || (this.fromId = jwtTokenUtil.getUserIdFromToken(token)) == null) {
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED, ctx.alloc().buffer(0)));
            return;
        }

        this.channel = ctx.channel();
        this.chatChannelGroup.addChannel(this.fromId, this.channel);

        final WebSocketServerHandshakerFactory wsFactory =
                new WebSocketServerHandshakerFactory(getWebSocketLocation(ctx.pipeline(), req, "/ws"),
                        "WebSocket", true, 65536 * 10);
        final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);
        final ChannelPromise localHandshakePromise = handshakePromise;
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            // setHandshaker(ctx.channel(), handshaker);
            HttpHeaders headers = new DefaultHttpHeaders();
            headers.set("Access-Control-Allow-Methods", "*");
            headers.set("Access-Control-Allow-Credentials", "true");
            headers.set("Access-Control-Allow-Origin", "*");
            headers.set("Access-Control-Allow-Headers", "*");
            headers.set("Access-Control-Expose-Headers", "*");
            headers.set("Sec-WebSocket-Protocol", token);
            final ChannelFuture handshakeFuture = handshaker.handshake(ctx.channel(), req, headers, ctx.newPromise());
            handshakeFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) {
                    if (!future.isSuccess()) {
                        localHandshakePromise.tryFailure(future.cause());
                        ctx.fireExceptionCaught(future.cause());
                    } else {
                        localHandshakePromise.trySuccess();
                    }
                }
            });
        }
    }

    private void handleWebSocketFrame(TextWebSocketFrame data) {
        // 获取客户端发送的消息
        String content = data.text();
        MessageVO messageVO;
        try {
            messageVO = JSON.parseObject(content, MessageVO.class);
        } catch (RuntimeException exception) {
            ResponseData toSender = ResponseData.ILLEGAL_MESSAGE_FORMAT;
            sendMessageToChannel(this.channel, toSender);
            return;
        }

        if (messageVO.getType() == null) {
            sendMessageToChannel(this.channel, ResponseData.ILLEGAL_MESSAGE_FORMAT);
        };

        int type = messageVO.getType();

        // 根据type处理不同业务
        if (type == MessageType.CHAT.getType()) {
            // 聊天类型的消息
            MessageDO messageDO = new MessageDO(messageVO, this.fromId);
            // 发送消息
            // 从全channelMap中获取接受方的channel
            Channel sender = this.channel;
            Channel receiver = chatChannelGroup.getChannelById(messageVO.getToId());
            if (receiver == null) {
                // 对方离线
                sendToOfflineUser(sender, messageDO);
            } else {
                sendToOnlineUser(sender, receiver, messageDO);
            }
        } else if (type == MessageType.SIGNED.getType()) {
            // 消息签收
            if (messageVO.getId() == null) {
                sendMessageToChannel(this.channel, ResponseData.ID_NOT_FOUND);
            }
            sign(channel, messageVO);
        } else if (type == MessageType.REQUEST.getType()) {
            // 好友添加请求
            if (messageVO.getToId() == null) {
                sendMessageToChannel(this.channel, ResponseData.ID_NOT_FOUND);
            } else {
                handleRequest(fromId, messageVO.getToId());
            }
        } else if (type == MessageType.REPLY.getType()) {
            // 回复好友请求
            Long id = messageVO.getId();
            if (id == null) {
                sendMessageToChannel(this.channel, ResponseData.ID_NOT_FOUND);
            } else {
                handleReply(id, messageVO.isAgree());
            }
        } else if (type == MessageType.PRIVATE_WEBRTC_OFFER.getType()) {
            // 用户发起会话请求
            Long sender = messageVO.getSender();
            Long receiver = messageVO.getReceiver();
            if (sender == null || receiver == null) {
                sendMessageToChannel(this.channel, ResponseData.ID_NOT_FOUND);
            } else {
                handleWebRtcOffer(messageVO, receiver);
            }
        } else if (type == MessageType.PRIVATE_WEBRTC_ANSWER.getType()) {
            // 响应会话请求
            Long sender = messageVO.getSender();
            Long receiver = messageVO.getReceiver();
            if (sender == null || receiver == null) {
                sendMessageToChannel(this.channel, ResponseData.ID_NOT_FOUND);
            } else {
                handleWebRtcAnswer(messageVO, sender);
            }
        } else if (type == MessageType.PRIVATE_WEBRTC_CANDIDATE.getType()) {
            // ICE候选者
            Long target = messageVO.getTarget();
            if (target == null) {
                sendMessageToChannel(this.channel, ResponseData.ID_NOT_FOUND);
            } else {
                handleWebRtcCandidate(messageVO, target);
            }
        } else if (type == MessageType.PRIVATE_WEBRTC_DISCONNECT.getType()) {
            // 挂断电话
            Long target = messageVO.getTarget();
            if (target == null) {
                sendMessageToChannel(this.channel, ResponseData.ID_NOT_FOUND);
            } else {
                handleWebRtcDisconnect(messageVO, target);
            }
        } else {
            handleDefault(this.channel);
        }
    }

    /**
     * 发送消息给在线用户
     * @param sender Channel对象 发送方
     * @param receiver Channel对象 接收方
     * @param message MessageDO对象 封装消息
     */
    private void sendToOnlineUser(Channel sender, Channel receiver, MessageDO message) {
        ResponseDataContainer container = chatService.sendToUser(message, true);
        ResponseData toSender = container.getToSender();
        ResponseData toReceiver = container.getToReceiver();
        // 发送响应给发送方
        sendMessageToChannel(sender, toSender);
        if (toSender.isSuccess()) {
            // 发送消息给接收方
            sendMessageToChannel(receiver, toReceiver);
        }
    }

    /**
     * 发送消息给离线用户
     * @param sender Channel对象 发送方
     * @param message MessageDO对象 封装消息
     */
    private void sendToOfflineUser(Channel sender, MessageDO message) {
        ResponseDataContainer container = chatService.sendToUser(message, false);
        ResponseData toSender = container.getToSender();
        sendMessageToChannel(sender, toSender);
    }

    /**
     * 接收方签收消息
     * @param channel Channel对象 接收方
     * @param message MessageVO对象 封装消息Id
     */
    private void sign(Channel channel, MessageVO message) {
        ResponseDataContainer container = chatService.sign(message.getId(), this.fromId);
        sendMessageToChannel(channel, container.getToReceiver());
    }

    /**
     * 处理添加好友
     * @param fromId 发送方id
     * @param toId 接受方id
     */
    private void handleRequest(Long fromId, Long toId) {
        ResponseDataContainer container = friendService.requestFriend(fromId, toId);
        ResponseData toSender = container.getToSender();
        ResponseData toReceiver = container.getToReceiver();
        if (toSender.isSuccess()) {
            // 请求成功
            if (toSender != ResponseData.HAVE_ALREADY_REQUESTED) {
                // 不是重复请求
                Channel receiver = chatChannelGroup.getChannelById(toId);
                if (receiver != null) {
                    // 对方在线
                    sendMessageToChannel(receiver, toReceiver);
                }
            }
            sendMessageToChannel(this.channel, toSender);
        } else {
            sendMessageToChannel(this.channel, toSender);
        }
    }

    /**
     * 处理回复好友请求
     * @param id 请求id
     * @param agree 是否同意
     */
    private void handleReply(Long id, boolean agree) {
        ResponseDataContainer container = friendService.replyFriend(id, agree, this.fromId);
        ResponseData toSender = container.getToSender();
        ResponseData toReceiver = container.getToReceiver();

        if (toSender.isSuccess()) {
            if (agree) {
                sendMessageToChannel(this.channel, toSender);
                if (toReceiver != null) {
                    Long receiverId = (Long) toSender.getData().get("id");
                    Channel receiver = chatChannelGroup.getChannelById(receiverId);
                    if (receiver != null) {
                        sendMessageToChannel(receiver, toReceiver);
                    }
                }
            } else {
                sendMessageToChannel(this.channel, toSender);
            }
        } else {
            sendMessageToChannel(this.channel, toSender);
        }
    }

    /**
     * 处理用户发起会话请求
     * @param messageVO messageVO对象,封装消息
     * @param receiver 接收方的id
     */
    private void handleWebRtcOffer(MessageVO messageVO, Long receiver) {
        // 因为是发起请求,因此服务器应将该消息从 sender 向 receiver 转发
        Channel receiverChannel = chatChannelGroup.getChannelById(receiver);
        Map<String, Object> map = new HashMap<>();
        if (receiverChannel != null) {
            map.put("type", messageVO.getType());
            map.put("sdp", messageVO.getSdp());
            map.put("sender", messageVO.getSender());
            map.put("receiver", messageVO.getReceiver());
            sendMessageToChannel(receiverChannel, map);
        } else {
            map.put("accept", false);
            sendMessageToChannel(receiverChannel, map);
        }
    }

    /**
     * 处理响应会话请求
     * @param messageVO messageVO对象,封装消息
     * @param sender 发送方用户id
     */
    private void handleWebRtcAnswer(MessageVO messageVO, Long sender) {
        // 因为是响应对方发起的请求,因此服务器应将该消息从 receiver 向 sender 转发。
        Channel senderChannel = chatChannelGroup.getChannelById(sender);
        if (senderChannel != null) {
            Map<String, Object> map = new HashMap<>();
            map.put("type", messageVO.getType());
            map.put("accept", messageVO.getAccept());
            map.put("sdp", messageVO.getSdp());
            map.put("sender", messageVO.getSender());
            map.put("receiver", messageVO.getReceiver());
            sendMessageToChannel(senderChannel, map);
        }
    }

    /**
     * 处理ICE候选者
     * @param messageVO messageVO对象,封装消息
     * @param target 目标用户id
     */
    private void handleWebRtcCandidate(MessageVO messageVO, Long target) {
        Channel targetChannel = chatChannelGroup.getChannelById(target);
        if (targetChannel != null) {
            Map<String, Object> map = new HashMap<>();
            map.put("type", messageVO.getType());
            map.put("candidate", messageVO.getCandidate());
            map.put("sdpMid", messageVO.getSdpMid());
            map.put("sdpMLineIndex", messageVO.getSdpMLineIndex());
            map.put("sender", messageVO.getSender());
            map.put("receiver", messageVO.getReceiver());
            map.put("target", messageVO.getTarget());
            sendMessageToChannel(targetChannel, map);
        }
    }

    /**
     * 处理挂断电话
     * @param messageVO messageVO对象,封装消息
     * @param target 目标用户id
     */
    private void handleWebRtcDisconnect(MessageVO messageVO, Long target) {
        Channel targetChannel = chatChannelGroup.getChannelById(target);
        if (targetChannel != null) {
            Map<String, Object> map = new HashMap<>();
            map.put("type", messageVO.getType());
            map.put("sender", messageVO.getSender());
            map.put("receiver", messageVO.getReceiver());
            map.put("target", messageVO.getTarget());
            sendMessageToChannel(targetChannel, map);
        }
    }

    /**
     * 默认处理位置消息类型
     * @param channel
     */
    private void handleDefault(Channel channel) {
        sendMessageToChannel(channel, ResponseData.TYPE_NOT_ALLOWED);
    }

    private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    private void sendMessageToChannel(Channel channel, ResponseData responseData) {
        channel.writeAndFlush(
                new TextWebSocketFrame(JSON.toJSONString(responseData))
        );
    }

    private void sendMessageToChannel(Channel channel, Map<String, Object> responseData) {
        channel.writeAndFlush(
                new TextWebSocketFrame(JSON.toJSONString(responseData))
        );
    }

    private String getWebSocketLocation(ChannelPipeline cp, HttpRequest req, String path) {
        String protocol = "ws";
        if (cp.get(SslHandler.class) != null) {
            // SSL in use so use Secure WebSockets
            protocol = "wss";
        }
        String host = req.headers().get(HttpHeaderNames.HOST);
        return protocol + "://" + host + path;
    }

    private void setHandshaker(Channel channel, WebSocketServerHandshaker handshaker) {
        channel.attr(AttributeKey.valueOf(WebSocketHandler.class, "HANDSHAKER")).set(handshaker);
    }

}

http请求

部分功能经过考虑,由http请求实现更合理。

package com.meeting.chatroom.controller;

import com.meeting.chatroom.entity.ChatChannelGroup;

import com.meeting.chatroom.service.OnOpenService;
import com.meeting.common.entity.ResponseData;
import com.meeting.common.util.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 负责处理websocket连接后的一些请求
 */
@Controller
public class OnOpenController {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private OnOpenService onOpenService;

    /**
     * 获取好友列表、好有请求、未签收的信息
     * @param token jwt token
     * @return ResponseData对象
     */
    @ResponseBody
    @GetMapping("/getFriendsAndMessages")
    public ResponseData getFriendsAndMessages(@RequestHeader(value = "Authorization", required = false) String token) {
        ResponseData responseData = null;
        Long uid = null;
        if (token == null || !jwtTokenUtil.validateToken(token)
                || (uid = jwtTokenUtil.getUserIdFromToken(token)) == null) {
            return new ResponseData(401, "未登录");
        }
        return onOpenService.getFriendsAndMessage(uid);
    }

}

package com.meeting.chatroom.controller;

import com.meeting.chatroom.service.ChatService;
import com.meeting.common.entity.ResponseData;
import com.meeting.common.exception.UnAuthorizedException;
import com.meeting.common.util.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@CrossOrigin(origins = ("*"))
public class HistoryController {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private ChatService chatService;

    @ResponseBody
    @GetMapping("/getHistoryMessage")
    public ResponseData getHistoryMessage(@RequestHeader(value = "Authorization", required = false) String token,
                                          @RequestParam("toId") long uid,
                                          @RequestParam(value = "page", required = false) Integer page) {
        Long id = null;
        if (token == null || !jwtTokenUtil.validateToken(token)
                || (id = jwtTokenUtil.getUserIdFromToken(token)) == null) {
            throw new UnAuthorizedException();
        }
        if (page == null || page < 1) {
            page = 1;
        }
        int num = 10;
        int start = num * (page - 1);
        return chatService.selectHistoryMessage(id, uid, start, num);
    }

}

心跳

关于心跳处理,等之后再做。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东羚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值