JAVA SE 实战篇 C7 基于CSFramework的聊天室 (下) 客户端APP

P1 聊天室用户端APP功能分析

对于客户端的聊天室APP,其功能不像服务器端APP那么简单,聊天室客户端APP需要有连接,登录,聊天这三个主要模块

对于连接,如果当前聊天室已经满员,那么客户端不能连接到服务器进入聊天室,且应该有相应的对话框提示

对于登录,理应提供登录和注册两个按键,点击登录则应该在数据库查找当前用户是否存在且密码是否正确,均通过才能进入聊天室,如果是新用户那么就需要点击注册按键,注册好自己的账号,昵称,密码,将其插入到数据库表中,再登录进入聊天室

对于聊天室,需要有友好的窗口布局,显示好友列表,系统信息,聊天信息等,还需要支持私聊,群聊等功能,且对于下线等操作都需要一系列的妥善处理

在这里插入图片描述
实际效果:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

P2 进入聊天室前的准备

1 连接 ChatRoomConnectToServer

进入聊天室之前,需要连接到服务器和输入账号密码,这里先处理连接到服务器的问题

package com.mec.chatroom.client.view;

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

import com.mec.csframework.core.Client;
import com.mec.csframework.core.ClientActionAdapter;
import com.my.util.IMyView;
import com.my.util.ViewIsNullException;
import com.my.util.ViewTool;

public class ChatRoomConnectToServer implements IMyView{
    
    
	
	private static final int Width = 500;
	private static final int Height = 300;
	
	private JFrame jfrmView;
	private JLabel jlblConnect;
	private int times;
	
	private Client client;
	
	/**
	 * Client类将IClientAction作为成员,并有相应的set,get方法
	 * set用来替换接口,get可以用来取得里面的具体方法
	 * 目的就是为了将一些只有APP开发者才能做的决定留给上层,如怎样处理对端异常掉线
	 * 这里有继承了ClientActionAdapter的内部类,APP开发者用它实现需要实现的方法
	 * 再通过setClientAction方法,使得底层能从上层获取如何处理问题
	 * 如在会话层处理对端异常掉线:this.client.getClientAction().serverAbnormalDrop
	 */
	public ChatRoomConnectToServer() {
    
    
		this.times = 0;
		this.client = new Client();
		this.client.setClientAction(new ConnectToServerAction());
	}
	
	/**
	 * 提供给用户的设置IP的方法
	 */
	public void setIp(String ip) {
    
    
		this.client.setIp(ip);
	}
	
	/**
	 * 提供给用户的设置端口的方法
	 */
	public void setPort(int port) {
    
    
		this.client.setPort(port);
	}
	
	/**
	 * 初始化连接界面
	 */
	@Override
	public void init() {
    
    
		this.jfrmView = new JFrame("雫-聊天室-连接服务器");
		this.jfrmView.setLayout(new BorderLayout());
		this.jfrmView.setSize(Width, Height);
		this.jfrmView.setLocationRelativeTo(null);
		this.jfrmView.setResizable(false);
		this.jfrmView.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		
		this.jlblConnect = new JLabel("", JLabel.CENTER);
		this.jlblConnect.setFont(topicFont);
		this.jlblConnect.setForeground(topicColor);
		this.jfrmView.add(jlblConnect, BorderLayout.CENTER);
	}

	/**
	 * 显示完连接窗口后尝试连接到服务器
	 */
	@Override
	public void afterShowView() {
    
    
		boolean keepLink = true;
		boolean linkOk = false;
		
		while(!linkOk && keepLink) {
    
    
			//每次连接刷新times的次数
			this.jlblConnect.setText("第" + ++this.times + "次连接...");
			//connectToServer方法连接成功返回true,失败false
			linkOk = this.client.connectToServer();
			
			//如果连接失败,弹出对话框询问用户是否继续尝试连接
			if(!linkOk) {
    
    
				int choice = ViewTool.getUserChoice(jfrmView, "是否继续连接");
				keepLink = choice == JOptionPane.YES_OPTION;
			}
		}
	}
	
	/**
	 * 关闭连接窗口的内部方法
	 */
	private void closeConnectView() {
    
    
		try {
    
    
			this.closeView();
		} catch (ViewIsNullException e) {
    
    
			e.printStackTrace();
		}
	}
	
	@Override
	public void dealAction() {
    
    
	}
	
	@Override
	public JFrame getFrame() {
    
    
		return this.jfrmView;
	}
	
	/**
	 * 构建一个内部类继承ClientActionAdapter
	 * 用来选择性的完成连接时需要APP层实现的方法
	 */
	class ConnectToServerAction extends ClientActionAdapter {
    
    
		
		public ConnectToServerAction() {
    
    
		}
		
		//超出最大连接的处理,显示警告框并关闭连接窗口
		@Override
		public void serverOutOfRoom() {
    
    
			ViewTool.showWarnning(jfrmView, "聊天室已满,请稍后尝试进入");
			closeConnectView();
		}

		//连接成功后,应该进入登录窗口,并关闭连接窗口
		@Override
		public void afterConnectToServer() {
    
    
			ChatRoomLoginView loginView = new ChatRoomLoginView(client);
			loginView.initView();
			try {
    
    
				loginView.showView();
			} catch (ViewIsNullException e) {
    
    
				e.printStackTrace();
			}
			closeConnectView();
		}	
	}
	
}

2 登录 ChatRoomLoginView

在连接成功后,进入登录窗口,这里没有做关于数据库表的访问和插入,只是用来演示:

package com.mec.chatroom.client.view;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;

import com.mec.chatroom.client.model.UserInfo;
import com.mec.csframework.core.Client;
import com.mec.csframework.core.ClientActionAdapter;
import com.my.util.IMyView;
import com.my.util.ViewIsNullException;
import com.my.util.ViewTool;

public class ChatRoomLoginView implements IMyView{
    
    
	
	private static final int Width = 400;
	private static final int Height = 200;
	
	private JFrame jfrmLogin;
	private JTextField jtxtName;
	private JPasswordField jpswPassWord;
	private JButton jbtnLogin;
	private JLabel jlblRegistry;
	
	private Client client;
	
	public ChatRoomLoginView(Client client) {
    
    
		this.client = client;
		this.client.setClientAction(new ChatRoomLoginAction());
	}
	
	/**
	 * 连接成功,进入到登录窗口的初始化
	 */
	@Override
	public void init() {
    
    
		this.jfrmLogin = new JFrame("雫-聊天室-登录");
		this.jfrmLogin.setSize(Width, Height);
		this.jfrmLogin.setResizable(false);
		this.jfrmLogin.setLocationRelativeTo(null);
		this.jfrmLogin.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		this.jfrmLogin.setLayout(new BorderLayout());
		
		JLabel jlblTopic = new JLabel("雫-聊天室-登录");
		jlblTopic.setFont(topicFont);
		jlblTopic.setForeground(topicColor);
		this.jfrmLogin.add(jlblTopic, BorderLayout.NORTH);
		
		JPanel jpnlBody = new JPanel(null);
		this.jfrmLogin.add(jpnlBody, BorderLayout.CENTER);

		int left = 10 * MARGIN;
		int top = 4 * MARGIN;
		
		JLabel jlblName = new JLabel("账号");
		jlblName.setFont(normalFont);
		jlblName.setBounds(left, top, normalFontSize * 2, normalFontSize + 4);
		jpnlBody.add(jlblName);
		
		this.jtxtName = new JTextField();
		this.jtxtName.setFont(normalFont);
		this.jtxtName.setBounds(left + jlblName.getWidth() + MARGIN, top, 
				normalFontSize * 12, normalFontSize + 6);
		jpnlBody.add(jtxtName);
		
		top += this.jtxtName.getHeight() + 2 * MARGIN;
		
		JLabel jlblPassWord = new JLabel("密码");
		jlblPassWord.setFont(normalFont);
		jlblPassWord.setBounds(left, top, normalFontSize * 2, normalFontSize + 4);
		jpnlBody.add(jlblPassWord);
		
		this.jpswPassWord = new JPasswordField();
		this.jpswPassWord.setFont(normalFont);
		this.jpswPassWord.setBounds(left + jlblName.getWidth() + MARGIN, top, 
				normalFontSize * 12, normalFontSize + 6);
		jpnlBody.add(jpswPassWord);
		
		JPanel jpnlFooter = new JPanel(new FlowLayout());
		this.jfrmLogin.add(jpnlFooter, BorderLayout.SOUTH);
		
		this.jbtnLogin = new JButton("登录");
		this.jbtnLogin.setFont(normalFont);
		jpnlFooter.add(this.jbtnLogin);
		
		JLabel jlblBlank = new JLabel("   ");
		jlblBlank.setFont(normalFont);
		jpnlFooter.add(jlblBlank);
		
		this.jlblRegistry = new JLabel("注册");
		this.jlblRegistry.setFont(normalFont);
		this.jlblRegistry.setForeground(topicColor);
		this.jlblRegistry.setCursor(csHand);
		jpnlFooter.add(this.jlblRegistry);
	}
	
	@Override
	public void afterShowView() {
    
    	
	}

	/**
	 * 注册窗口应该响应的窗口事件
	 */
	@Override
	public void dealAction() {
    
    
		
		//账号文本框获得焦点时,全选中
		this.jtxtName.addFocusListener(new FocusAdapter() {
    
    
			@Override
			public void focusGained(FocusEvent e) {
    
    
				jtxtName.selectAll();
			}
		});
		
		//在账号文本框敲击回车时,密码框获得焦点
		this.jtxtName.addActionListener(new ActionListener() {
    
    
			@Override
			public void actionPerformed(ActionEvent e) {
    
    
				jpswPassWord.requestFocus();
			}
		});
		
		//密码框获得焦点时,清空密码框
		this.jpswPassWord.addFocusListener(new FocusAdapter() {
    
    
			@Override
			public void focusGained(FocusEvent e) {
    
    
				jpswPassWord.setText("");
			}
		});
		
		//在密码框敲击回车时,登录按键获得焦点
		this.jpswPassWord.addActionListener(new ActionListener() {
    
    
			@Override
			public void actionPerformed(ActionEvent e) {
    
    
				jbtnLogin.requestFocus();
			}
		});
		
		//点击右上x关闭窗口时,下线该客户端,断开连接
		this.jfrmLogin.addWindowListener(new WindowAdapter() {
    
    
			@Override
			public void windowClosing(WindowEvent e) {
    
    
				client.offLine();
			}
		});	
		
		//点击登录按钮,进入聊天室界面
		this.jbtnLogin.addActionListener(new ActionListener() {
    
    
			@Override
			public void actionPerformed(ActionEvent e) {
    
    
				String nick = jtxtName.getText().trim();
				if(nick == null || nick.length() <= 0) {
    
    
					ViewTool.showError(jfrmLogin, "账号不能为空");
					jtxtName.requestFocus();
					return;
				}
				
				//账号文本框有文本后,可以进入聊天室窗口
				ChatRoomView chatRoomView = 
						new ChatRoomView(client, new UserInfo(client.getId(), nick));
				chatRoomView.initView();
				try {
    
    
					chatRoomView.showView();
				} catch (ViewIsNullException e1) {
    
    
					e1.printStackTrace();
				}
				//关闭当前登录窗口
				closeLoginView();
			}
		});
		
	}
		
	@Override
	public JFrame getFrame() {
    
    
		return this.jfrmLogin;
	}
	
	/**
	 * 关闭登录窗口的内部方法
	 */
	private void closeLoginView() {
    
    
		
		try {
    
    
			closeView();
		} catch (ViewIsNullException e) {
    
    
			e.printStackTrace();
		}
	}
	
	/**
	 * 构建一个内部类继承ClientActionAdapter
	 * 用来选择性的完成登录时需要APP层实现的方法
	 */
	class ChatRoomLoginAction extends ClientActionAdapter {
    
    
		
		public ChatRoomLoginAction() {
    
    
		}
		
		//处理服务器异常掉线,弹出错误,关闭登录窗口并下线
		@Override
		public void serverAbnormalDrop() {
    
    
			ViewTool.showError(jfrmLogin, "服务器异常,暂时停止服务");
			closeLoginView();
		}

		//下线前弹出确认是否下线
		@Override
		public boolean confirmOffLine() {
    
    
			int choice = ViewTool.getUserChoice(jfrmLogin, "请确认,是否下线");
			return choice == JOptionPane.YES_OPTION;
		}

		//下线后,关闭登录窗口
		@Override
		public void afterOffLine() {
    
    
			closeLoginView();
		}

		//服务器强制下线的提示
		@Override
		public void serverForceDown() {
    
    
			ViewTool.showError(jfrmLogin, "服务器被强制宕机,停止服务");
		}

		//某客户端被服务器下线
		@Override
		public void killByServer(String reason) {
    
    
			ViewTool.showError(jfrmLogin, "本机被强制下线,原因" + reason);
			closeLoginView();
		}
		
	}

}

3 用户信息 UserInfo

对于每个进入聊天室的用户,都需要为其生成一个独有的对象,包含它的id和昵称,以便在发送信息时找到对应的id,且昵称需要显示在好友列表中,发送的信息也应该显示“昵称 + 信息”显示在聊天画板中

package com.mec.chatroom.client.model;

/**
 * 
 * @author coisini1999
 *
 */
public class UserInfo {
    
    
	
	private String netId;
	private String nick;
	
	public UserInfo(String netId, String nick) {
    
    
		this.netId = netId;
		this.nick = nick;
	}

	public String getNetId() {
    
    
		return netId;
	}

	public void setNetId(String netId) {
    
    
		this.netId = netId;
	}

	public String getNick() {
    
    
		return nick;
	}

	public void setNick(String nick) {
    
    
		this.nick = nick;
	}

	@Override
	public int hashCode() {
    
    
		final int prime = 31;
		int result = 1;
		result = prime * result + ((netId == null) ? 0 : netId.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
    
    
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		UserInfo other = (UserInfo) obj;
		if (netId == null) {
    
    
			if (other.netId != null)
				return false;
		} else if (!netId.equals(other.netId))
			return false;
		return true;
	}

	@Override
	public String toString() {
    
    
		return nick;
	}
	
}

P3 聊天窗口的实现 ChatRoomView

当有了UserInfo,成功的连接,登录后,就可以进入聊天室,但聊天室窗口有一系列操作需要完善,如私聊,群聊上线告知所有人,下线将自己在其它好友的好友列表中移除等操作需要完善

1 窗口界面的绘制

	/**
	 * 取得当前窗口,方便交给关闭方法
	 */
	@Override
	public JFrame getFrame() {
    
    
		return this.jfrmChatView;
	}
	
	/**
	 * 绘制画板标题的方法
	 */
	private TitledBorder newTitledBorder(String topic, int position, int adjust) {
    
    
		TitledBorder border = new TitledBorder(topic);
		border.setTitleFont(normalFont);
		border.setTitlePosition(position);
		border.setTitleJustification(adjust);
		return border;
	}
	
	/**
	 * 初始化客户端聊天室界面
	 */
	@Override
	public void init() {
    
    
		
		this.jfrmChatView = new JFrame("雫 - 聊天室");
		this.jfrmChatView.setMinimumSize(new Dimension(MinWidth, MinHeight));
		this.jfrmChatView.setExtendedState(JFrame.MAXIMIZED_BOTH);
		this.jfrmChatView.setLocationRelativeTo(null);
		this.jfrmChatView.setLayout(new BorderLayout());
		this.jfrmChatView.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

		JLabel jlblTopic = new JLabel("雫", 0);
		jlblTopic.setFont(topicFont);
		jlblTopic.setForeground(topicColor);
		this.jfrmChatView.add(jlblTopic, BorderLayout.NORTH);

		this.jpnlBody = new JPanel(null);
		this.jfrmChatView.add(this.jpnlBody, BorderLayout.CENTER);

		this.jpnlFriend = new JPanel(null);
		jpnlBody.add(this.jpnlFriend);

		this.jlblWelcome = new JLabel("欢迎");
		this.jlblWelcome.setFont(topicFont);
		this.jlblWelcome.setForeground(topicColor);
		this.jpnlFriend.add(this.jlblWelcome);

		this.jlblUserMe = new JLabel(this.userMe.getNick());
		this.jlblUserMe.setFont(normalFont);
		this.jpnlFriend.add(this.jlblUserMe);

		this.dlmFriendList = new DefaultListModel<>();
		this.jlstFriendList = new JList<>(this.dlmFriendList);
		this.jlstFriendList.setFont(normalFont);
		this.jscpFriendList = new JScrollPane(this.jlstFriendList);
		this.jscpFriendList.setBorder(newTitledBorder("好友列表", TitledBorder.TOP, TitledBorder.CENTER));
		this.jpnlFriend.add(this.jscpFriendList);

		this.jpnlChatContext = new JPanel(null);
		jpnlBody.add(this.jpnlChatContext);

		this.jtatChatContext = new JTextArea();
		this.jtatChatContext.setFont(normalFont);
		this.jscpChatContext = new JScrollPane(this.jtatChatContext);
		this.jscpChatContext.setBorder(newTitledBorder("聊天记录", TitledBorder.TOP, TitledBorder.CENTER));
		this.jpnlChatContext.add(this.jscpChatContext);

		this.jpnlSystemMessage = new JPanel(null);
		jpnlBody.add(this.jpnlSystemMessage);

		this.jtatSystemMessage = new JTextArea();
		this.jtatSystemMessage.setFont(normalFont);

		this.jscpSystemMessage = new JScrollPane(this.jtatSystemMessage);

		this.jscpSystemMessage.setBorder(newTitledBorder("系统消息", TitledBorder.TOP, TitledBorder.CENTER));
		this.jpnlSystemMessage.add(this.jscpSystemMessage);

		JPanel jpnlFooter = new JPanel(new FlowLayout());
		this.jfrmChatView.add(jpnlFooter, BorderLayout.SOUTH);

		this.jlblCurrentFriend = new JLabel("所有人");
		this.jlblCurrentFriend.setFont(normalFont);
		jpnlFooter.add(this.jlblCurrentFriend);

		this.jtxtChatContent = new JTextField(30);
		this.jtxtChatContent.setFont(normalFont);
		jpnlFooter.add(this.jtxtChatContent);	
	}

2 控件采用绝对定位

对于窗口内的画板和标签都采用了绝对定位的方式,但当用户操作窗口大小时,每次都需要重新计算控件的位置

	/**
	 * 部分控件需要采用绝对定位的方法
	 */
	private void redrawView() {
    
    
		int bodyWidth = this.jpnlBody.getWidth();
		int bodyHeigth = this.jpnlBody.getHeight();

		int sideWidth = (int) ((bodyWidth - 4 * MARGIN) * SideScale);
		int contextWidth = (int) ((bodyWidth - 4 * MARGIN) * ContentScale);

		int top = MARGIN;
		int left = MARGIN;
		this.jpnlFriend.setBounds(left, top, sideWidth, bodyHeigth);
		left += MARGIN + sideWidth;
		this.jpnlChatContext.setBounds(left, top, contextWidth, bodyHeigth);
		left += MARGIN + contextWidth;
		this.jpnlSystemMessage.setBounds(left, top, sideWidth, bodyHeigth);

		top = MARGIN;
		int welcomeWidth = this.jlblWelcome.getText().length() * topicFontSize;
		this.jlblWelcome.setBounds((sideWidth - welcomeWidth) / 2, top, welcomeWidth, topicFontSize);

		top += this.jlblWelcome.getHeight() + MARGIN;
		int userMeWidth = this.jlblUserMe.getText().length() * normalFontSize;
		this.jlblUserMe.setBounds((sideWidth - userMeWidth) / 2, top, userMeWidth, normalFontSize);

		int friendListHeight = bodyHeigth - 5 * MARGIN - this.jlblWelcome.getHeight() - this.jlblUserMe.getHeight();
		top += this.jlblUserMe.getHeight() + 2 * MARGIN;
		this.jscpFriendList.setBounds(0, top, sideWidth, friendListHeight);

		this.jscpSystemMessage.setBounds(0, 0, sideWidth, bodyHeigth - MARGIN);
		this.jscpChatContext.setBounds(0, 0, contextWidth, bodyHeigth - MARGIN);
	}

且窗口显示后后还需要一系列操作,如向当前在线用户发送我上线了的信息

3 APP层的信息描述

在聊天室,最基础的功能就是发送信息,所以仍然需要采用“信令” + “信息”来描述,虽然CSFramework也有一套“信令” + “信息”,但那是处理框架内的信息的,主要是确定信息的目的和接收者,而这里的“信令” + “信息”才是为具体的聊天信息服务

(1) APP层的信令 EChatCommand

在这里插入图片描述

(2) APP层的信息描述 ChatMessage

package com.mec.chatroom.core;

public class ChatMessage {
    
    
	
	private EChatCommand command;
	private String from;
	private String to;
	private String message;
	
	public ChatMessage() {
    
    
	}

	public EChatCommand getCommand() {
    
    
		return command;
	}

	public ChatMessage setCommand(EChatCommand command) {
    
    
		this.command = command;
		return this;
	}

	public String getFrom() {
    
    
		return from;
	}

	public ChatMessage setFrom(String from) {
    
    
		this.from = from;
		return this;
	}

	public String getTo() {
    
    
		return to;
	}

	public ChatMessage setTo(String to) {
    
    
		this.to = to;
		return this;
	}

	public String getMessage() {
    
    
		return message;
	}

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

	@Override
	public String toString() {
    
    
		return "ChatMessage [command=" + command + ", from=" + from + ", to=" + to + ", message=" + message + "]";
	}
	
}

4 刚上线需要的操作

	/**
	 * 显示完聊天窗口后的善后处理
	 * 和必要操作,即上线后给所有人发送一条我上线了的信息
	 */
	@Override
	public void afterShowView() {
    
    
		this.jtatChatContext.setEditable(false);
		this.jtatChatContext.setFocusable(false);
		this.jtatSystemMessage.setEditable(false);
		this.jtatSystemMessage.setFocusable(false);
		
	
		this.dlmFriendList.addElement(friendAll);
		this.jlstFriendList.setSelectedIndex(0);
		this.jlblCurrentFriend.setText(friendAll.getNick());
		this.jlblCurrentFriend.setName(friendAll.getNetId());
		
		this.jtxtChatContent.requestFocus();
		
		//下面的代码意为向所有在线的人发送一条我上线了的信息
		//信息载体是ChatMessage,但是底层工具采用的send方法内部是writeUTF(String xxx)
		//所以要将ChatMessage类的对象转换成String类的对象,以便能够通过网络传输
		//对于这里的setMessage内的内容,是将发送者UserInfo类型的userMe转换成字符串发送
		//因为需要获取到发送者的nick
		//所以对于该信息的处理,要分两步,1,将String类的chatMessage转换成ChatMessage
		//2,将chatMessage内的message由String类型转换成UserInfo类型
		ChatMessage chatMessage = new ChatMessage()
				.setCommand(EChatCommand.HELLO)
				.setFrom(userMe.getNetId())
				.setTo(friendAll.getNetId())
				.setMessage(Client.gson.toJson(userMe));
		this.client.toOther(Client.gson.toJson(chatMessage));
	}

5 完成框架抛给APP层需要实现的方法

	/**
	 * 内部类,用来实现ClientActionAdapter内的方法
	 * 将下层无权处理的信息交给上层APP开发者完成
	 */
	class ChatAction extends ClientActionAdapter {
    
    
		
		public ChatAction() {
    
    
		}

		//接收到私聊传信息的具体处理
		@Override
		public void toOne(String source, String message) {
    
    
		
			ChatMessage chatMessage = Client.gson.fromJson(message, ChatMessage.class);
			EChatCommand command = chatMessage.getCommand();
					
			String mess = chatMessage.getMessage();
			
			switch(command) {
    
    
			//向刚上线的客户端发送本客户端在线的信息
			case HELLO:
				//解析chatMessage内的Message,取得向本客户端发送在线信息的发送者的nick
				String cMessage = chatMessage.getMessage();
				UserInfo friendOnline = Client.gson.fromJson(cMessage, UserInfo.class);
				
				dlmFriendList.addElement(friendOnline);
				break;
			case SPEACH:
				//解析发送到本客户端的私聊信息,获取发送人的详细信息
				UserInfo user = getUserById(chatMessage.getFrom());
				showChatContent(user + "对你悄悄说:" + mess);	
				break;
			default:
				break;
			}
			
		}
			
		//接收到来自群发信息的具体处理
		@Override
		public void toOther(String sourceId, String message) {
    
    
			
			//将接收到的String类的chatMessage转换成ChatMessage类的对象,并取出command分类处理
			ChatMessage chatMessage = Client.gson.fromJson(message, ChatMessage.class);
			EChatCommand command = chatMessage.getCommand();
			
			String mess = chatMessage.getMessage();
			
			switch(command) {
    
    
			//一个刚上线的客户端向所有客户端发送上线信息
			case HELLO:
				UserInfo newFriend = Client.gson.fromJson(mess, UserInfo.class);
				dlmFriendList.addElement(newFriend);
				showSystemMessage("好友" + newFriend + "上线");
				
				//告知刚上线的客户端自己在线
				String targetId = newFriend.getNetId();
				ChatMessage cMessage = new ChatMessage()
						.setCommand(EChatCommand.HELLO)
						.setFrom(userMe.getNetId())
						.setTo(targetId)
						.setMessage(Client.gson.toJson(userMe));
				client.toOne(targetId, Client.gson.toJson(cMessage));
				break;
			//一个下线了的客户端向所有客户端发送下线信息
			case BYE_BYE:
				UserInfo byebyeFriend = Client.gson.fromJson(mess, UserInfo.class);
				dlmFriendList.removeElement(byebyeFriend);
				showSystemMessage("好友" + byebyeFriend + "下线");
				break;
			case SPEACH:
				//解析发送到本客户端的私聊信息,获取发送人的详细信息
				UserInfo user = getUserById(chatMessage.getFrom());
				showChatContent(user + "对所有人说:" + mess);	
				break;
			default:
				break;
			}
			
		}
		
		//下线前先确认是否真的要下线了
		@Override
		public boolean confirmOffLine() {
    
    
			int choice = ViewTool.getUserChoice(jfrmChatView, "是否确认离开?");
			return choice == JOptionPane.YES_OPTION;
		}
		
		//确认下线后,向所有人发送我离开了的信息 
		@Override
		public void beforeOffLine() {
    
    
			ChatMessage chatMessage = new ChatMessage()
					.setCommand(EChatCommand.BYE_BYE)
					.setFrom(userMe.getNetId())
					.setTo(FRIEND_ALL_NET_ID)
					.setMessage(Client.gson.toJson(userMe));
			client.toOther(Client.gson.toJson(chatMessage));
		}
		
		//发送完离开信息后,关闭聊天室
		@Override
		public void afterOffLine() {
    
    
			closeChatRoomView();
			
			//这里提供了一种关闭的操作,强制退出,结束JVM的执行
			//但是有些系统资源可能无法释放,因此这种方式应该慎用
//			System.exit(0);
		}
				
		// 服务器强制下线处理
		@Override
		public void serverForceDown() {
    
    
			ViewTool.showError(jfrmChatView, "聊天室服务器异常宕机,服务终止");
			closeChatRoomView();
		}
		
		 //服务器异常宕机处理  
		@Override
		public void serverAbnormalDrop() {
    
    
			ViewTool.showError(jfrmChatView, "聊天室服务器被强制宕机,服务终止");
			closeChatRoomView();
		}
		
		// 服务器强制下线某客户端的处理
		@Override
		public void killByServer(String reason) {
    
    
			ViewTool.showError(jfrmChatView, "您被强制退出聊天室,服务停止 \n"
					+ "原因:" + reason );
			closeChatRoomView();
		}	
		
	}

6 响应用户的操作

	/**
	 * 对用户操作的响应
	 */
	@Override
	public void dealAction() {
    
    
		//点击关闭窗口
		this.jfrmChatView.addWindowListener(new WindowAdapter() {
    
    
			@Override
			public void windowClosing(WindowEvent e) {
    
    
				client.offLine();
			}
		});
		
		//用户每次调整窗口时,都会重新计算控件的位置
		this.jfrmChatView.addComponentListener(new ComponentAdapter() {
    
    
			@Override
			public void componentResized(ComponentEvent e) {
    
    
				redrawView();
			}
		});
		
		//用户选择好友列表时,在聊天文本框前显示好友
		this.jlstFriendList.addListSelectionListener(new ListSelectionListener() {
    
    
			@Override
			public void valueChanged(ListSelectionEvent e) {
    
    
				if(e.getValueIsAdjusting() == true) {
    
    
					setSelectedFriend();
				}
			}
		});
		
		//用户在聊天框内输入内容,点击回车发送
		this.jtxtChatContent.addActionListener(new ActionListener() {
    
    
			@Override
			public void actionPerformed(ActionEvent e) {
    
    
				String chatContent = jtxtChatContent.getText().trim();
				if(chatContent.length() <= 0) {
    
    
					ViewTool.showWarnning(jfrmChatView, "聊天内容不能为空");
					jtxtChatContent.setText("");
					return;
				}
				
				//获取当前好友的netId,区分私聊和群聊
				String targetId = jlblCurrentFriend.getName();
				
				if(targetId.equals(FRIEND_ALL_NET_ID)) {
    
    
					//群聊过程
					ChatMessage chatMessage = new ChatMessage()
							.setCommand(EChatCommand.SPEACH)
							.setFrom(userMe.getNetId())
							.setTo(FRIEND_ALL_NET_ID)
							.setMessage(chatContent);
					client.toOther(Client.gson.toJson(chatMessage));
					showChatContent("你对所有人说:" + chatContent);
				} else {
    
    
					//私聊过程,先判断私聊的好友是否在线
					if(isUserExit(targetId)) {
    
    
						ChatMessage chatMessage = new ChatMessage()
								.setCommand(EChatCommand.SPEACH)
								.setFrom(userMe.getNetId())
								.setTo(targetId)
								.setMessage(chatContent);
						client.toOne(targetId, Client.gson.toJson(chatMessage));
						showChatContent("你对" + jlblCurrentFriend.getText() + "悄悄说:" +chatContent);
					} else {
    
    
						ViewTool.showWarnning(jfrmChatView, "当前好友" + jlblCurrentFriend.getText() + "已下线");
						jlstFriendList.setSelectedIndex(0);
						setSelectedFriend();
					}
				}
				jtxtChatContent.setText("");
			}
		});
		
	}

猜你喜欢

转载自blog.csdn.net/weixin_43541094/article/details/111185507