项目实训(5) - 在线聊天1


前言

使用springboot整合netty。

一、配置文件

<dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
        </dependency>

        <dependency>
            <groupId>com.meeting</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

    </dependencies>
server:
  port: 8005

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/meeting?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8&
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mybatis-mapper/*.xml
  type-aliases-package: com.meeting.entity

netty:
  server:
    port: 8006

二、实体类

@Component
public class ChatChannelGroup {

    private final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();

    private final Map<Long, Channel> ID_CHANNEL_REF = new HashMap<>();

    private final Map<Channel, Long> CHANNEL_ID_REF = new HashMap<>();

    public void addChannel(Long id, Channel channel) {
        LOCK.writeLock().lock();
        try {
            ID_CHANNEL_REF.put(id, channel);
            CHANNEL_ID_REF.put(channel, id);
        } finally {
            LOCK.writeLock().unlock();
        }
    }

    public void removeChannel(Long id, Channel channel) {
        LOCK.writeLock().lock();
        try {
            ID_CHANNEL_REF.remove(id);
            CHANNEL_ID_REF.remove(channel);
        } finally {
            LOCK.writeLock().unlock();
        }
    }

    public void print() {
        LOCK.readLock().lock();
        try {
            ID_CHANNEL_REF.forEach((key, value) -> {
                System.out.println(key + ": " + value);
            });
            System.out.println("print over");
        } finally {
            LOCK.readLock().unlock();
        }
    }

    public Channel getChannelById(Long id) {
        LOCK.readLock().lock();
        try {
            return ID_CHANNEL_REF.get(id);
        } finally {
            LOCK.readLock().unlock();
        }
    }

    public Long getIdByChannel(Channel channel) {
        LOCK.readLock().lock();
        try {
            return CHANNEL_ID_REF.get(channel);
        } finally {
            LOCK.readLock().unlock();
        }
    }

}
public class MessageVO {

    /**
     * message的id
     */
    private Long id;

    /**
     * 接收方的id
     */
    private Long toId;

    /**
     * 消息信息
     */
    private String message;

    /**
     * 回复好友请求,布尔
     */
    private boolean agree;

    /**
     * 获取历史记录,与num一起使用,表示从倒数第start条开始,一直读nun条
     */
    private Integer start;

    /**
     * 获取历史记录,与start一起使用,表示从倒数第start条开始,一直读nun条
     */
    private Integer num;

    /**
     * 消息类型
     * 从客户端发来的消息类型,参照MessageType
     * 返回给客户端的消息类型
     * type = 1,聊天消息
     * type = 2,好友请求
     * type = 3,回复请求
     */
    private Integer type;

    /**
     * 日期
     */
    private long date;

    public MessageVO() {}

    /**
     *  todo type应该可以不要了
     */
    public MessageVO(MessageDO message, int type) {
        this.id = message.getId();
        this.toId = message.getToId();
        this.message = message.getMessage();
        this.type = type;
        this.date = message.getDate();
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getToId() {
        return toId;
    }

    public void setToId(Long toId) {
        this.toId = toId;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public boolean isAgree() {
        return agree;
    }

    public void setAgree(boolean agree) {
        this.agree = agree;
    }

    public Integer getStart() {
        return start;
    }

    public void setStart(Integer start) {
        this.start = start;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public long getDate() {
        return date;
    }

    public void setDate(long date) {
        this.date = date;
    }

    @Override
    public String toString() {
        return "MessageVO{" +
                "id=" + id +
                ", toId=" + toId +
                ", message='" + message + '\'' +
                ", agree=" + agree +
                ", start=" + start +
                ", num=" + num +
                ", type=" + type +
                ", date=" + date +
                '}';
    }

}
public class MessageDO {

    /**
     * 消息id
     */
    private long id;

    /**
     * 发送方id
     */
    private long fromId;

    /**
     * 接收方Id
     */
    private long toId;

    /**
     * 消息
     */
    private String message;

    /**
     * 时间
     */
    private long date;

    /**
     * status=0,消息未签收
     * status=1,消息已签收
     * status=2,好友请求,且没有被回复
     * status=3,好友请求,已经被回复
     */
    private int status;

    public MessageDO() {}

    public MessageDO(long id, long fromId, long toId, String message, long date, int status) {
        this.id = id;
        this.fromId = fromId;
        this.toId = toId;
        this.message = message;
        this.date = date;
        this.status = status;
    }

    /**
     * 根据MessageVo对象构造MessageDo
     */
    public MessageDO(MessageVO messageVO, long fromId) {
        this.id = messageVO.getId() == null ? 0 : messageVO.getId();
        this.fromId = fromId;
        this.toId = messageVO.getToId();
        this.message = messageVO.getMessage();
        // 当前时间
        this.date = System.currentTimeMillis();
        // 未签收的消息
        this.status = 0;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public long getFromId() {
        return fromId;
    }

    public void setFromId(long fromId) {
        this.fromId = fromId;
    }

    public long getToId() {
        return toId;
    }

    public void setToId(long toId) {
        this.toId = toId;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public long getDate() {
        return date;
    }

    public void setDate(long date) {
        this.date = date;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("id", this.id);
        map.put("message", this.message);
        map.put("date", this.date);
        map.put("toId", this.toId);
        map.put("fromId", this.fromId);
        return map;
    }

    @Override
    public String toString() {
        return "MessageDO{" +
                "id=" + id +
                ", fromId=" + fromId +
                ", toId=" + toId +
                ", message='" + message + '\'' +
                ", date=" + date +
                ", status=" + status +
                '}';
    }

}
public enum MessageType {

    /**
     * 聊天消息
     */
    CHAT(1, "聊天消息"),

    /**
     * 消息签收
     */
    SIGNED(2, "消息签收"),

    /**
     * 请求添加好友
     */
    REQUEST(3, "请求添加好友"),

    /**
     * 回复好友请求
     */
    REPLY(4, "回复好友请求"),

    /**
     * 获取好友信息
     */
    PULL_FRIENDS(5, "获取好友信息"),

    /**
     * 获取好友请求
     */
    PULL_REQUESTS(6, "获取好友请求"),

    /**
     * 获取未签收消息
     */
    PULL_UNSIGNED_MESSAGE(7, "获取未签收消息"),

    /**
     * 获取历史消息记录
     */
    PULL_HISTORY_MESSAGE(8, " 获取历史消息记录");

    private final int type;
    private final String text;

    MessageType(int type, String text) {
        this.type = type;
        this.text = text;
    }

    public int getType() {
        return type;
    }

    public String getText() {
        return text;
    }

}

public class Friend {

    private Long uid;
    private Long friendId;

    public Friend() {}

    public Friend(Long uid, Long friendId) {
        this.uid = uid;
        this.friendId = friendId;
    }

    public Long getUid() {
        return uid;
    }

    public void setUid(Long uid) {
        this.uid = uid;
    }

    public Long getFriendId() {
        return friendId;
    }

    public void setFriendId(Long friendId) {
        this.friendId = friendId;
    }

    @Override
    public String toString() {
        return "Friend{" +
                "uid=" + uid +
                ", friendId=" + friendId +
                '}';
    }

}
public class ResponseData {

    public final static ResponseData ID_NOT_FOUND = new ResponseData(false, "MISSING_MESSAGE_ID");

    public final static ResponseData MESSAGE_TOO_LONG = new ResponseData(false, "MESSAGE_TOO_LONG");

    public final static ResponseData USER_ID_NOT_FOUND = new ResponseData(false, "MISSING_USER_ID");

    public final static ResponseData MESSAGE_NOT_EXIST = new ResponseData(false, "MESSAGE_NOT_EXIST");

    public final static ResponseData HAVE_ALREADY_REQUESTED = new ResponseData(true, "HAVE_ALREADY_REQUESTED");

    public final static ResponseData ILLEGAL_MESSAGE_FORMAT = new ResponseData(false, "ILLEGAL_MESSAGE_FORMAT");

    public final static ResponseData TYPE_NOT_ALLOWED = new ResponseData(false, "TYPE_NOT_ALLOWED");

    public final static ResponseData IS_ALREADY_FRIEND = new ResponseData(true, "IS_ALREADY_FRIEND");

    public final static ResponseData SERVER_PROBLEM = new ResponseData(false, "SERVER_PROBLEM");

    public final static ResponseData UNAUTHORIZED = new ResponseData(false, "UNAUTHORIZED");

    public final static ResponseData BAD_REQUEST = new ResponseData(false, "BAD_REQUEST");

    private boolean success;
    private String type;
    private final Map<String, Object> data = new HashMap<>();

    public ResponseData() {}

    public ResponseData(boolean success, String type) {
        this.success = success;
        this.type = type;
    }

    public static ResponseData ok(String message) {
        return new ResponseData(true, message);
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Map<String, Object> getData() {
        return data;
    }

    @Override
    public String toString() {
        return "ResponseData{" +
                "success=" + success +
                ", type='" + type + '\'' +
                ", data=" + data +
                '}';
    }

}
/**
 * 容纳ResponseData的容器
 * toSender 回复给sender的响应
 * toReceiver 回复给receiver的响应
 */
public class ResponseDataContainer {

    /**
     * 回复给sender的响应
     */
    private ResponseData toSender;

    /**
     * 回复给receiver的响应
     */
    private ResponseData toReceiver;

    public ResponseDataContainer() {}

    public ResponseDataContainer(ResponseData toSender, ResponseData toReceiver) {
        this.toSender = toSender;
        this.toReceiver = toReceiver;
    }

    public ResponseData getToSender() {
        return toSender;
    }

    public void setToSender(ResponseData toSender) {
        this.toSender = toSender;
    }

    public ResponseData getToReceiver() {
        return toReceiver;
    }

    public void setToReceiver(ResponseData toReceiver) {
        this.toReceiver = toReceiver;
    }

    @Override
    public String toString() {
        return "ResponseDataContainer{" +
                "toSender=" + toSender +
                ", toReceiver=" + toReceiver +
                '}';
    }

}
public enum ResponseType {

    /**
     * 给消息发送方的成功响应
     */
    MESSAGE_SENDER_OK("MESSAGE_SENDER_OK"),

    /**
     * 给消息接受方的成功响应
     */
    MESSAGE_RECEIVER_OK("MESSAGE_RECEIVER_OK"),

    /**
     * 消息签收成功的响应
     */
    SIGN_OK("SIGN_OK"),

    /**
     * 请求未签收消息的响应
     */
    UNSIGNED_MESSAGE("UNSIGNED_MESSAGE_OK"),

    /**
     * 请求历史消息的响应
     */
    HISTORY_MESSAGE("HISTORY_MESSAGE"),

    /**
     * 请求好友列表的响应
     */
    FRIEND("FRIEND"),

    /**
     * 发送好友请求成功的响应
     */
    REQUEST_SENDER_OK("REQUEST_SENDER_OK"),

    /**
     * 收到好友请求的响应
     */
    REQUEST_RECEIVER_OK("REQUEST_RECEIVER_OK"),

    /**
     * 收到好友回复的消息
     */
    REPLY_SENDER_OK("REPLY_SENDER_OK"),

    /**
     * 回复好友请求成功的消息
     */
    REPLY_RECEIVER_OK("REPLY_RECEIVER_OK"),

    /**
     * 请求未回复的好友请求
     */
    REQUESTS_TO_BE_REPLIED("REQUESTS_TO_BE_REPLIED");

    private String type;

    private ResponseType(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

}

三、NettyServer.class

@Component
public class NettyServer {

    @Value("${netty.server.port}")
    private int port;

    private void startServer() {
        //服务端需要2个线程组  boss处理客户端连接  work进行客服端连接之后的处理
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup work = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            //服务器 配置
            bootstrap.group(boss,work).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline()
                                    // HttpServerCodec:将请求和应答消息解码为HTTP消息
                                    .addLast("http-codec",new HttpServerCodec())
                                    // HttpObjectAggregator:将HTTP消息的多个部分合成一条完整的HTTP消息
                                    .addLast("aggregator",new HttpObjectAggregator(65536))
                                    // ChunkedWriteHandler:向客户端发送HTML5文件
                                    .addLast("http-chunked",new ChunkedWriteHandler())
                                    .addLast("websocket", new WebSocketServerProtocolHandler("/ws", "WebSocket", true, 65536 * 10))
                                    // 进行设置心跳检测
                                    // .addLast("idle", new IdleStateHandler(60,30,60*30, TimeUnit.SECONDS))
                                    // 配置通道处理来进行业务处理
                                    .addLast("handler", new WebSocketHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childOption(ChannelOption.SO_KEEPALIVE,true);
            //绑定端口  开启事件驱动
            Channel channel = bootstrap.bind(port).sync().channel();
            channel.closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭资源
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }
    }

    @PostConstruct
    public void init() {
        // 需要开启一个新的线程来启动netty server服务器
        new Thread(this::startServer).start();
    }

}

其它部分放在下一篇博客中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

东羚

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

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

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

打赏作者

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

抵扣说明:

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

余额充值