zoukankan      html  css  js  c++  java
  • Java课程设计(单人)——聊天应用

    1.项目简介,涉及技术

    用户打开应用,进行注册,然后登录后进入主界面,主要有聊天、联系人(群聊)和添加联系人(群聊)三个分页,
    可以通过添加联系人(群聊)发起聊天会话,还有删除联系人(群聊)等一些其他功能。
    

    涉及技术:

    netty用于实现通信,protobuf配合netty对信息进行结构化,spring boot主要使用到ioc,至于mybatis、mysql就是数据库相关。
    

    2.项目git地址

    服务端
    客户端

    3.项目git提交记录截图


    4.项目功能架构图、主要包关系图



    5.项目运行截图

    6.项目关键代码

    通信部分

    服务端通信的接收流程是:NettyServerHandler拦截到客户端发送的信息,调用userOrderDispatch根据信息类型分发到各个Handler(已经实现的对某种特定信息的处理方法),然后Handler根据需求调用server。
    服务端通信的发送流程是:将需要发送的信息包装到OrderMessage中,从指定channel发送出去。
    (客户端类似)
    

    protobuf的message

    1.代码中的消息结构化使用protobuf,使用OrderMessage管理多个message,使用枚举来确定信息类型,oneof orderBody是信息体,且每个OrderMessage中最多出现其中的一个,节省空间

    message OrderMessage {
    
        //定义一个枚举类型,message可以是枚举中的一个
        enum OrderType{
            UserLoginType=0;
            UserRegisterType=1;
            LoginSucceedType=2;
            AddConversationType=3;
            AddConversationSucceedType=4;
            SendPersonalChatMessageType=5;
            SendGroupChatMessageType=6;
            RemoveConversationType=7;
            LoginFailureType=8;
            RegisterSucceedType=9;
            RegisterFailureType=10;
            SearchLinkmanType=11;
            SearchLinkmanSucceedType=12;
            SearchGroupType=13;
            SearchGroupSucceedType=14;
            JoinGroupType=15;
            AddLinkmanType=16;
            JoinGroupSucceedType=17;
            AddLinkmanSucceedType=18;
            CreateGroupType=19;
            RemoveGroupType=20;
            RemoveLinkmanType=21;
            CreateGroupSucceedType=22;
            CreateGroupFailureType=23;
            LogOutType=24;
        }
    
        //用来标识是哪一个指令
        OrderType orderType=1;
    
        //表示每次枚举类型最多出现其中的一个,节省空间
        oneof orderBody{
            UserLogin userLogin=2;
            UserRegister userRegister=3;
            LoginSucceed loginSucceed=4;
            AddConversation addConversation=5;
            AddConversationSucceed addConversationSucceed=6;
            SendPersonalChatMessage sendPersonalChatMessage=7;
            SendGroupChatMessage sendGroupChatMessage=8;
            RemoveConversation removeConversation=9;
            LoginFailure loginFailure=10;
            RegisterSucceed registerSucceed=11;
            RegisterFailure registerFailure=12;
            SearchLinkman searchLinkman=13;
            SearchLinkmanSucceed searchLinkmanSucceed=14;
            SearchGroup searchGroup=15;
            SearchGroupSucceed searchGroupSucceed=16;
            JoinGroup joinGroup=17;
            AddLinkman addLinkman=18;
            JoinGroupSucceed joinGroupSucceed=19;
            AddLinkmanSucceed addLinkmanSucceed=20;
            CreateGroup createGroup=21;
            RemoveGroup removeGroup=22;
            RemoveLinkman removeLinkman=23;
            CreateGroupSucceed createGroupSucceed=24;
            CreateGroupFailure createGroupFailure=25;
            LogOut logOut=26;
        }
    
    }
    

    2.客户端发送注册请求的message

    message UserRegister{
        string account=1;
        string password=2;
        string nickname=3;
    }
    

    服务端的一个接收信息的流程

    1.服务端使用NettyServerHandler对与客户端建立的连接channel进行UserOrder.OrderMessage类型的信息拦截,然后调用userOrderDispatch进行派发

    public class NettyServerHandler extends SimpleChannelInboundHandler<UserOrder.OrderMessage> {
    
    
    
        private UserOrderController userOrderController= (UserOrderController) SpringContextUtil.getBean("userOrderController");
    
        private NettyOnlineServer nettyOnlineServer= (NettyOnlineServer) SpringContextUtil.getBean("nettyOnlineServer");
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, UserOrder.OrderMessage msg) throws Exception {
    
            System.out.println("消息到达");
            userOrderController.userOrderDispatch(ctx,msg);
    
        }
    
        /**
         *  数据读取完毕
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    
        }
    
        /**
         *  处理异常, 一般是需要关闭通道
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            nettyOnlineServer.removeOnlineUser(ctx.channel());
            super.channelInactive(ctx);
        }
    }
    
    

    2.服务端将消息派发到各个Handler

     public void userOrderDispatch(ChannelHandlerContext ctx, UserOrder.OrderMessage msg){
            UserOrder.OrderMessage.OrderType orderType=msg.getOrderType();
            switch (orderType){
                case UserLoginType:
                    userLoginTypeHandler(ctx,msg);
                    break;
                case UserRegisterType:
                    userRegisterTypeHandler(ctx,msg);
                    break;
                case AddConversationType:
                    addConversationHandler(ctx,msg);
                    break;
                case SendPersonalChatMessageType:
                    sendPersonalChatMessageHandler(ctx,msg);
                    break;
                case SendGroupChatMessageType:
                    sendSendGroupChatMessageHandler(ctx,msg);
                    break;
                case RemoveConversationType:
                    removeConversationHandler(ctx,msg);
                    break;
                case SearchLinkmanType:
                    searchLinkmanHandler(ctx,msg);
                    break;
                case SearchGroupType:
                    searchGroupHandler(ctx,msg);
                    break;
                case JoinGroupType:
                    joinGroupHandler(ctx,msg);
                    break;
                case AddLinkmanType:
                    addLinkmanHandler(ctx,msg);
                    break;
                case CreateGroupType:
                    createGroupHandler(ctx,msg);
                    break;
                case RemoveGroupType:
                    removeGroupHandler(ctx,msg);
                    break;
                case RemoveLinkmanType:
                    removeLinkmanHandler(ctx,msg);
                    break;
                case LogOutType:
                    logOutHandler(ctx,msg);
                default:
            }
    
        }
    

    3.处理注册的Handler,调用了server

     public void userRegisterTypeHandler(ChannelHandlerContext ctx, UserOrder.OrderMessage msg){
            userOrderServer.userRegister(ctx,msg);
        }
    
    

    4.进行注册的server,先对要进行注册的账号进行验证,是否已经注册,验证方式为,通过账号查询数据库得到user,如果user为空,说明未注册,那么就进行注册,并返回注册成功给客户端。
    如果不为空,说明改账号已经注册,那么返回账号已注册给客户端。

     public void userRegister(ChannelHandlerContext ctx, UserOrder.OrderMessage msg){
            UserOrder.UserRegister userRegister = msg.getUserRegister();
            String account = userRegister.getAccount();
            String password = userRegister.getPassword();
            String nickname = userRegister.getNickname();
    
            User user = userServer.getUserByAccount(account);
            if(user==null){
                User user1 = new User();
                user1.setAccount(account);
                user1.setPassword(password);
                user1.setNickname(nickname);
                userServer.insertUser(user1);
    
                //注册成功
                UserOrder.RegisterSucceed.Builder registerSucceed=UserOrder.RegisterSucceed.newBuilder();
                UserOrder.OrderMessage.Builder orderMessageBuilder = UserOrder.OrderMessage.newBuilder();
                orderMessageBuilder.setOrderType(UserOrder.OrderMessage.OrderType.RegisterSucceedType)
                        .setRegisterSucceed(registerSucceed);
                //使用channel发送,是从第一个handler开始
                ctx.channel().writeAndFlush(orderMessageBuilder.build());
    
            }else{
                //1账号已存在
                int type=1;
    
                //注册失败
                UserOrder.RegisterFailure.Builder registerFailure=UserOrder.RegisterFailure.newBuilder()
                        .setType(type);
                UserOrder.OrderMessage.Builder orderMessageBuilder = UserOrder.OrderMessage.newBuilder();
                orderMessageBuilder.setOrderType(UserOrder.OrderMessage.OrderType.RegisterFailureType)
                        .setRegisterFailure(registerFailure);
                //使用channel发送,是从第一个handler开始
                ctx.channel().writeAndFlush(orderMessageBuilder.build());
    
            }
        }
    

    客户端登录功能

    1.点击登录按钮后触发loginButtonOnAction,获取用户输入的账号、密码,调用loginOrder

        void loginButtonOnAction(ActionEvent event) {
            String account = accountTextField.getText();
            String password = passwordTextField.getText();
            if("".equals(account)==true){
                warningLabel.setText("请输入账号");
                return;
            }
            if("".equals(password)==true){
                warningLabel.setText("请输入密码");
                return;
            }
           userOrderServer.loginOrder(account,password);
        }
    

    2.loginOrder将账号、密码发送到服务端

     public void loginOrder(String account, String password) {
            UserOrder.OrderMessage orderMessage = UserOrder.OrderMessage.newBuilder()
                    .setOrderType(UserOrder.OrderMessage.OrderType.UserLoginType)
                    .setUserLogin(UserOrder.UserLogin.newBuilder()
                            .setAccount(account)
                            .setPassword(password)
                            .build())
                    .build();
            nettyContextUtil.getCurrentChannel().writeAndFlush(orderMessage);
        }
    

    3.服务端验证成功后返回loginSucceedType,客户端分发到loginSucceedTypeHandler,loginSucceedTypeHandler对结构化的信息转化为javabean
    然后向界面注入数据进行初始化。

     public void loginSucceedTypeHandler(ChannelHandlerContext ctx, UserOrder.OrderMessage msg){
            User user = transformController.parseTransformToUser(msg);
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    //更新JavaFX的主线程的代码放在此处
                    stageController.setStage("HomePageView","LoginView");
    
                    LoginController loginView = (LoginController) stageController.getController("LoginView");
                    loginView.getPasswordTextField().setText("");
                    loginView.getAccountTextField().setText("");
                    loginView.getWarningLabel().setText("");
    
                    ObservableList linkmanObservableList = (ObservableList) SpringContextUtil.getBean("linkmanObservableList");
                    ObservableList chatGroupObservableList = (ObservableList) SpringContextUtil.getBean("chatGroupObservableList");
                    ObservableList conversationObservableList = (ObservableList) SpringContextUtil.getBean("conversationObservableList");
    
    
                    linkmanObservableList.setAll(user.getLinkmanList());
                    chatGroupObservableList.setAll(user.getChatGroupList());
                    conversationObservableList.setAll(user.getConversationList());
    
                }
            });
        }
    

    javaFX

    界面布局使用fxml,不需要多介绍,所以主要介绍controller部分

    conversationJFXListView是显示会话的一个listview组件,给conversationJFXListView设置一个可观察数组conversationObservableList(通过改变可观察数组中的数据,listview会动态更新),然后是对conversationJFXListView中的cell进行自定义;

      ObservableList conversationObservableList = (ObservableList) SpringContextUtil.getBean("conversationObservableList");
            conversationJFXListView.setItems(conversationObservableList);
            conversationJFXListView.setCellFactory(new Callback<ListView<Conversation>, ListCell<Conversation>>() {
                @Override
                public ListCell<Conversation> call(ListView<Conversation> param) {
    
                    return getConversationCell(param);
                }
            });
    

    conversationJFXListView中cell的自定义,通过添加一些ImageView、label就可实现,利用VBOx、HBox进行布局

     private ListCell getConversationCell(ListView<Conversation> param) {
            ListCell<Conversation> conversationListCell = new ListCell<Conversation>() {
                @Override
                protected void updateItem(Conversation item, boolean empty) {
                    super.updateItem(item, empty);
                    if (empty == false) {
                        if (item.getType() == 1) {
                            GroupConversation groupConversation = item.getGroupConversation();
                            HBox hBox = new HBox(10);
                            ImageView headImage = new ImageView("image/group.png");
                            headImage.setPreserveRatio(true);
                            headImage.setFitHeight(48);
                            VBox vBox = new VBox(5);
    
                            Label nicknameLabel = new Label(groupConversation.getCurrentChatGroup().getGroupName());
                            
                            vBox.getChildren().addAll(nicknameLabel);
                            hBox.getChildren().addAll(headImage, vBox);
                            this.setGraphic(hBox);
                        } else {
                            PersonalConversation personalConversation = item.getPersonalConversation();
                            HBox hBox = new HBox(10);
                            ImageView headImage = new ImageView("image/personal.png");
                            headImage.setPreserveRatio(true);
                            headImage.setFitHeight(48);
    
                            Label nicknameLabel = new Label(personalConversation.getCurrentLinkman().getNickname());
                            Label remarkLabel = new Label(personalConversation.getCurrentLinkman().getRemark());
    
                            if (personalConversation.getCurrentLinkman().getRemark().equals("") == false) {
                                hBox.getChildren().addAll(headImage, remarkLabel);
                            } else {
                                hBox.getChildren().addAll(headImage, nicknameLabel);
                            }
    
                            this.setGraphic(hBox);
                        }
                    } else {
                        this.setGraphic(null);
                    }
                }
    
            };
    

    7.项目代码扫描结果及改正

    大部分警告是命名不规范和if没有大括号,注解没有使用正确
    


    8.尚待改进

    未读消息,表情等功能未实现
  • 相关阅读:
    Mybatis Plus 代码生成器
    Vue中 scss不支持/deep/写法问题
    学习过程中看到的网站收藏
    mysql数据库的安装配置
    element表格样式修改
    vue甘特图gantt
    一个CSS动画生成工具,可自由配置各种动画特效,并自动生成代码
    《TypeScript 入门教程》全面介绍了 TypeScript 强大的类型系统,完整而简洁,示例丰富,比官方文档更易读,非常适合作为初学者学习 TypeScript 的第一本书 —— 阮一峰
    程序员文档编辑器 Markdown
    Svn(小乌龟)的基本操作使用
  • 原文地址:https://www.cnblogs.com/codedawn/p/12174116.html
Copyright © 2011-2022 走看看