Java大作业——聊天室

源码

功能概述

用户上线下线,登录和注册功能,群聊和私聊功能,统计聊天室在线人数功能。

文件说明

LoginController:登陆界面的控制器
LoginLauch:整个程序的启动器
PrivateChatController:私聊控制器
PrivateChatListener:私聊监听器
RegisterController:注册控制器
UserListController:用户列表控制器
Listener:群聊监听器

软件设计

客户端

用户登录和注册

UI设计

开始采用的是swing设计GUI,后来发觉javafx的功能比较强大,然后改用javafx设计。
javafx设计
在这里插入图片描述
swing设计
在这里插入图片描述
注册按键监听器设置:当点击时加载注册界面与数据库交互。
登录按键监听器设置:检查用户名和密码是否正确,如果正确则打开聊天界面(这个界面是群聊)和用户列表,并开启一个群聊监听器Listener。

代码片段
package com.jchat.Controller;


import com.Users.User;
import com.Utils.UserDao;

import com.jchat.demo.Listener;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

import javax.swing.*;

public class LoginController {

    @FXML
    private TextField usernameField;

    @FXML
    private Button registerButton;

    @FXML
    private Button loginButton;

    @FXML
    private PasswordField passwordField;

    @FXML
    private Label usernameLabel;

    @FXML
    private Label pleaseLogin;

    @FXML
    private AnchorPane mainPane;
    @FXML
    private Label passwordLabel;
    private PrivateChatController privateChatController;
    private UserListController userListController;

    private static LoginController instance;
    @FXML
    void Login(ActionEvent event) {
        User user=new User();
        user.setUserName(usernameField.getText());
        user.setPassword(passwordField.getText());
        UserDao dao=new UserDao();
        User user1=dao.login(user);

        if(user1==null){
            JOptionPane.showMessageDialog(null,"该用户不存在");
            passwordField.setText("");
        }
        else if(user.getUserName().equals(user1.getUserName())&&user.getPassword().equals(user1.getPassword())) {
            JOptionPane.showMessageDialog(null, "登录成功");
            try {

                FXMLLoader fxmlLoader=new FXMLLoader(getClass().getResource("UserListView.fxml"));
                Parent window=(AnchorPane)fxmlLoader.load();
                userListController=fxmlLoader.<UserListController>getController();

                FXMLLoader fxmlLoader1=new FXMLLoader(getClass().getResource("PrivateChatView.fxml"));
                Parent window1=(AnchorPane)fxmlLoader1.load();
                privateChatController=fxmlLoader1.<PrivateChatController>getController();
                privateChatController.setUsernameLabel(user1.getUserName());
                Listener listener = new Listener("localhost", 1234, user1.getUserName(),privateChatController ,userListController);
                Thread x = new Thread(listener);
                x.start();

                Scene scene=new Scene(window);
                Stage stage1=new Stage();
                Stage stage2=new Stage();
                stage1.setScene(scene);

                Scene scene1=new Scene(window1);
                stage2.setScene(scene1);
                stage1.show();
                stage2.show();

                LoginLaunch.getPrimaryStage().setIconified(true);
                LoginLaunch.getPrimaryStage().close();

            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    @FXML
    void Register(ActionEvent event) {
        try {
            //一定需要使用try-catch,不然编译器不会让你过的,Trust me!
            Parent anotherRoot = FXMLLoader.load(getClass().getResource("RegisterController.fxml"));
            System.out.println(getClass().getResource("RegisterController.fxml"));
            Stage anotherStage = new Stage();
            anotherStage.setTitle("Another Window Triggered by Clicking");
            anotherStage.setScene(new Scene(anotherRoot, 304, 427));
            anotherStage.show();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
    public LoginController() {
        instance = this;
    }
    public static LoginController getInstance() {
        return instance;
    }
}

数据库设计
表设计

在这里插入图片描述

在这里插入图片描述
昵称、用户名、密码三个字段。
与数据库交互主要用到了druid数据库连接池技术

代码片段
UserDao.java
package com.Utils;

import com.Users.User;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.swing.*;

//操作数据库中的user表的对象
public class UserDao {
    //创建jdbcTemplate对象,用来操作数据库
    JdbcTemplate template=new JdbcTemplate(JDBCUtils.getSourse());
    public User login(User loginUser){
        try{
            String sql="select * from users where username = ?";
            User user =template.queryForObject(
                    sql,
                    new BeanPropertyRowMapper<User>(User.class),
                    loginUser.getUserName()
            );
            if(user.getPassword().equals(loginUser.getPassword()))
                return user;
            else {
                JOptionPane.showMessageDialog(null,"密码错误,请重新输入");
            }
        }catch (DataAccessException e){
            e.printStackTrace();
            return null;
        }
        return  null;
    }
}
JDBCUtils.java
package com.Utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCUtils {
    private static DataSource ds;
    static {
        //加载配置文件
        Properties pro=new Properties();
        try{
            pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            //初始化连接池对象
            ds= DruidDataSourceFactory.createDataSource(pro);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    //获取连接池对象
    public static DataSource getSourse(){
        return ds;
    }
    //获取连接
    public static Connection getConnection() throws SQLException{
        return ds.getConnection();
    }
}
配置文件druid.properties(必须是这个名字)
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jchat?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false
username=root
password=root
maxActive=5
maxWait=3000

聊天界面

一个用户列表,一个对话框。
在这里插入图片描述

消息机制

登录成功后进入聊天界面。
群聊监听器Listenner会调用connect方法给服务器端发送一个消息,该消息的类型为CONNECTED,表示连接成功。服务器端收到该消息后,将该用户的用户名和输出流放入一个Map中,然后发送一个NOTIFACAITION类型的消息广播给所有成员,提示所有人有用户加入,并发送一个名为SERVER的消息,参数中包含用户列表和用户数量,广播给所有成员,让所有成员更新自己的客户端数据。

send按键监听发送事件。如果是群聊,本人的用户名和文本框中的消息构造成一个消息类型为GROUPCHAT的消息类发送给服务器,如果是私聊,则消息类型为PRIVATECHAT,还有一个目的地(发送给谁)成员变量。服务器收到该消息后判断该消息的类型,如果是私聊,则根据该消息的目的地从私聊的Map中找对应的输出流并将消息对象中的消息内容从该输出流发送出去,同时给自己发送一份该消息(显示在自己的聊天框中)。如果是群聊,则从群聊Map中的每一个输出流发送出去。

服务器

消息类

package com.Users;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;

public class Message implements Serializable {
    private String destination;
    private String name;
    private MessageType type;
    private String msg;
    private int count;
    private ArrayList<User> list;
    private ArrayList<User> users;
    private Status status;


    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name=name;
    }
    public String getMsg(){
        return  msg;
    }
    public void setMsg(String msg){
        this.msg=msg;
    }

    public ArrayList<User> getUserList() {
        return list;
    }

    public void setUserList(ArrayList<User> list) {
        this.list = list;
    }

    public ArrayList<User> getUsers() {
        return users;
    }

    public void setUsers(ArrayList<User> users) {
        this.users = users;
    }

    public Status getStatus() {
        return status;
    }

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

    public MessageType getType() {
        return type;
    }

    public void setType(MessageType type) {
        this.type = type;
    }
    public void setUserList(HashMap<String,User> userList){
        this.list=new ArrayList<>(userList.values());
    }

    public void setOnlineCount(int count){
        this.count=count;
    }

    public int getOnlineCount() {
        return count;
    }



    public String getDestination() {
        return destination;
    }

    public void setDestination(String destination) {
        this.destination = destination;
    }

    @Override
    public String toString() {
        return "Message{" +
                "destination='" + destination + '\'' +
                ", name='" + name + '\'' +
                ", type=" + type +
                ", msg='" + msg + '\'' +
                ", count=" + count +
                '}';
    }
}

ServerSocket建立连接
Handler处理异步消息

消息类型

通过再服务器和客户端之间传递这些类型的消息进行实时的通讯。

public enum MessageType {
    DISCONNECTED,   //断开连接
    CONNECTED,      //建立连接
    STATUS,         //用户状态
    SERVER,         //服务器
    NOTIFICATION,   //通知
    HASCONNECTED,   //连接建立完成
    GROUPCHAT,      //群聊
    PRIVATECHAT,    //私聊
    PRIVATECONNECT  //私聊的连接建立
}

代码片段

package com.jchat.Server;

import com.Users.Message;
import com.Users.MessageType;
import com.Users.Status;
import com.Users.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;


public class Server {
    private static final int PORT=1234;
    private static final HashMap<String,User> names=new HashMap<>();
    private static HashMap<String,ObjectOutputStream> writers=new HashMap<>();

    private static HashMap<String,ObjectOutputStream> privateChatWriters=new HashMap<>();
    private static ArrayList<User> users=new ArrayList<>();     //在线用户列表
    static  Logger logger= LoggerFactory.getLogger(Server.class);


    public static void main(String[] args) throws IOException {
        logger.info("The chat server is running");
        ServerSocket serverSocket=new ServerSocket(PORT);
        try{
            while(true){
                new Handler(serverSocket.accept()).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            serverSocket.close();
        }
    }

    private static class Handler extends Thread{
        private String name;
        private Socket socket;
        private Logger logger=LoggerFactory.getLogger(Handler.class);
        private User user;
        private ObjectInputStream  objectInputStream;
        private OutputStream outputStream;
        private ObjectOutputStream objectOutputStream;
        private InputStream inputStream;
        private MessageType messageType;

        public Handler(Socket socket) throws IOException{
            this.socket=socket;
        }
        public void  run(){
            logger.info("Attempting to connect a user...");
            try{
                inputStream=socket.getInputStream();
                objectInputStream=new ObjectInputStream(inputStream);
                outputStream=socket.getOutputStream();
                objectOutputStream=new ObjectOutputStream(outputStream);

                Message firstMessage=(Message) objectInputStream.readObject();
                System.out.println("firstMessage: "+firstMessage);
                if(firstMessage.getType()==MessageType.PRIVATECONNECT){
                    privateChatWriters.put(firstMessage.getName(),objectOutputStream);
                    messageType=MessageType.PRIVATECHAT;
                }
                else if(firstMessage.getType()!=MessageType.PRIVATECHAT){
                    checkDuplicateUsername(firstMessage);
                    messageType=MessageType.GROUPCHAT;
                    writers.put(firstMessage.getName(),objectOutputStream);
                    sendNotification(firstMessage);
                    addToList();
                }

                while(socket.isConnected()){
                    Message inputmsg=(Message)objectInputStream.readObject();
                    System.out.println("inputmsg:"+inputmsg);
                    logger.info(inputmsg.getType() + " - " + inputmsg.getName() + ": " + inputmsg.getMsg());
                    switch (inputmsg.getType()) {
                        case CONNECTED:
                            addToList();
                            break;
                        case STATUS:
                            changeStatus(inputmsg);
                            break;
                        case GROUPCHAT:
                            sendToGroup(inputmsg);
                            break;
                        case PRIVATECHAT:
                            sendToPerson(inputmsg, inputmsg.getDestination());
                            sendToPerson(inputmsg, inputmsg.getName());
                            break;
                        case PRIVATECONNECT:
                            privateConnected();
                            break;
                    }
                }

            } catch (SocketException socketException) {
                logger.error("Socket Exception for user " + name);
            } catch (DuplicateUsernameException duplicateException){
                logger.error("Duplicate Username : " + name);
            } catch (Exception e){
                logger.error("Exception in run() method for user: " + name, e);
            } finally {
                closedConnections();
            }
        }

        private void sendToGroup(Message msg) throws IOException {
            for(Map.Entry<String,ObjectOutputStream> mem:writers.entrySet()){
                mem.getValue().writeObject(msg);
                mem.getValue().reset();
            }
        }
        private void sendToPerson(Message msg,String destination) throws IOException {
            if(privateChatWriters.get(destination)==null){
                writers.get(destination).writeObject(msg);
                writers.get(destination).reset();
            }
            else{
                privateChatWriters.get(destination).writeObject(msg);
                privateChatWriters.get(destination).reset();
            }

        }

        //改变用户状态消息
        private Message changeStatus(Message inputmsg) throws IOException{
            logger.debug(inputmsg.getName() + " has changed status to  " + inputmsg.getStatus());
            Message msg = new Message();
            msg.setName(user.getUserName());
            msg.setType(MessageType.STATUS);
            msg.setMsg("");
            User userObj = names.get(name);
            userObj.setStatus(inputmsg.getStatus());
            sendToGroup(msg);
            return msg;
        }

        //用户名重复消息
        private synchronized void checkDuplicateUsername(Message firstMessage)throws DuplicateUsernameException{
            logger.info(firstMessage.getName()+"is trying to connect");
            if(!names.containsKey(firstMessage.getName())){
                this.name=firstMessage.getName();
                User user=new User();
                user.setUserName(firstMessage.getName());
                user.setStatus(Status.ONLINE);
                users.add(user);
                names.put(name,user);
                logger.info(name+" has been added to the list");
            }
            else {
                logger.error(firstMessage.getName()+ " is already connected");
                throw new DuplicateUsernameException(firstMessage.getName()+" is already connected");
            }
        }

        //通知消息
        private Message sendNotification(Message firstMessage) throws IOException{
            Message msg=new Message();
            msg.setMsg(" has joined the chat");
            msg.setType(MessageType.NOTIFICATION);
            msg.setName(firstMessage.getName());
            sendToGroup(msg);
            return msg;
        }

        //用户离开消息
        private Message removeFromList() throws IOException{
            logger.debug("removeFromList() method Enter");
            Message msg=new Message();
            msg.setMsg(" has left the chat.");
            msg.setType(MessageType.DISCONNECTED);
            msg.setName("SERVER");
            msg.setUserList(names);
            msg.setUsers(users);
            msg.setOnlineCount(names.size());
            sendToGroup(msg);
            logger.debug("removeFromList() method Exit");
            return msg;
        }

        private Message privateConnected() throws IOException {
            Message msg=new Message();
            msg.setType(MessageType.PRIVATECHAT);
            msg.setMsg("you have connected each other");
            sendToPerson(msg,msg.getName());
            return msg;
        }
        //用户加入列表消息
        private Message addToList() throws IOException{
            Message msg=new Message();
            msg.setMsg("Welcome, You have now joined the server!Enjoy chatting!");
            msg.setType(MessageType.CONNECTED);
            msg.setName("SERVER");
            msg.setUsers(users);
            msg.setUserList(names);
            msg.setOnlineCount(names.size());
            sendToGroup(msg);
            return msg;
        }
        //关闭客户端与服务器的连接
        private synchronized void closedConnections(){
            logger.debug("closeConnections() method Enter");
            logger.info("HashMap names:"+names.size()+"writers:"+writers.size()+" userList size:"+users.size());
            if(name!=null){
                names.remove(name);
                logger.info("User: "+name+" has been removed");
            }
            if (user != null){
                users.remove(user);
                logger.info("User object: " + user + " has been removed!");
            }
            if(objectOutputStream!=null){
                writers.remove(objectOutputStream);
                logger.info("Writer object: " + user + " has been removed!");
            }
            if(inputStream!=null){
                try{
                    inputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if(outputStream!=null){
                try{
                    outputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if(objectOutputStream!=null){
                try{
                    objectOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if(objectInputStream!=null){
                try{
                    objectInputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if(writers.get(name)==null&&privateChatWriters.get(name)==null){
                try{
                    removeFromList();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            logger.info("HashMap names: " + names.size() + " writers:" + writers.size() + " usersList size:" + users.size());
            logger.debug("closeConnections() method Exit");
        }
    }
}


测试

启动server
在这里插入图片描述

注册和登录

开启一个客户端,点击注册
在这里插入图片描述
在这里插入图片描述
可以看到后台数据库中多了一个记录,同理注册小红和小刚。
然后登录小明、小红、小刚的账号
在这里插入图片描述

群聊

在这里插入图片描述

私聊

左上角的聊天框是群聊的
1.小名打开和小红的聊天框发送小红你好,此时,小红并没有建立和小明私聊的对话框,所以在群聊框中收到一条私聊的消息,这个消息只有小明和小红能看到。
2.小红看到后打开和小明的聊天框,然后回复小明。这是消息会显示在二者的私聊对话框中(这里有一个bug就是会弹出两个私聊的聊天框,只有最上层的聊天框能显示消息,下层的聊天框虽然不能显示,但是也能发送消息。由于时间紧迫,先交作业,后期再修bug)
在这里插入图片描述

总结

有很多bug还没处理,比如用户名重复、重复登录等问题,
还有很多设计不好的地方,比如聊天框中没有显示对方的信息等。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

能饮一杯吴

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

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

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

打赏作者

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

抵扣说明:

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

余额充值