前言
大家看我CSFramework博文哗啦啦一蹴而就的写下来,但并不是那么顺利写下来的,我们知道越是底层的的东西就一定要进行单元测试,测试这部分功能是否符合要求,其实在写CSFramework的时候我就拿聊天室作为单元测试测试每一部分的功能性是否可行。应该说CSFramework框架是和聊天室一起磕磕绊绊,修修补补写出来的,边测试聊天室边修改框架内容,相辅相成才出来的CSFramework框架的。
这篇博文旨在熟练掌握我们的CSFramework框架。
聊天室Project
首先来看聊天室工程整体做下来其内容是怎么分配的吧,主要是看包之间的名字和对应的关系。其中用到了MVC
模式(高内聚,低耦合)和以前写好的工具类。
聊天室服务器工程大致
聊天室客户端工程大致
聊天室服务器view模块
服务器的界面是一个很简单的控制台。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;
import com.mec.csframework.core.IListener;
import com.mec.csframework.core.Server;
import com.mec.util.FrameIsNull;
import com.mec.util.IMecView;
import com.mec.util.ViewTool;
public class ServerView implements IMecView, IListener {
private Server server;
private JFrame jfrmMain;
private Container container;
private JTextField jtxfCommand;
private JTextArea jtxaMessage;
public ServerView() {
server = new Server();
server.addListener(this);
initView();
}
public void setServerConfig(String path) {
server.initConfig(path);
}
public void setServerConfigAbsolutePath(String path) {
server.initConfigAbsolutePath(path);
}
@Override
public void init() {
jfrmMain = new JFrame("服务器测试");
jfrmMain.setSize(800, 700);
jfrmMain.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
jfrmMain.setLocationRelativeTo(null);
container = jfrmMain.getContentPane();
container.setLayout(new BorderLayout());
JLabel jlblTopic = new JLabel("一个用于测试的服务器监控界面", JLabel.CENTER);
jlblTopic.setFont(topicFont);
jlblTopic.setForeground(topicColor);
container.add(jlblTopic, BorderLayout.NORTH);
JPanel jpnlFooter = new JPanel(new FlowLayout());
container.add(jpnlFooter, BorderLayout.SOUTH);
JLabel jlblCommand = new JLabel("命令");
jlblCommand.setFont(normalFont);
jpnlFooter.add(jlblCommand);
jtxfCommand = new JTextField(40);
jtxfCommand.setFont(normalFont);
jpnlFooter.add(jtxfCommand);
JPanel jpnlLeft = new JPanel();
JLabel jlblLeft = new JLabel(" ");
jlblLeft.setFont(normalFont);
jpnlLeft.add(jlblLeft);
container.add(jpnlLeft, BorderLayout.WEST);
JPanel jpnlRight = new JPanel();
JLabel jlblRight = new JLabel(" ");
jlblRight.setFont(normalFont);
jpnlRight.add(jlblRight);
container.add(jpnlRight, BorderLayout.EAST);
jtxaMessage = new JTextArea();
jtxaMessage.setEditable(false);
jtxaMessage.setFocusable(false);
jtxaMessage.setFont(normalFont);
JScrollPane jscpMessage = new JScrollPane(jtxaMessage);
TitledBorder ttbdMessage = new TitledBorder("系统消息");
ttbdMessage.setTitleFont(normalFont);
ttbdMessage.setTitleColor(Color.red);
ttbdMessage.setTitlePosition(TitledBorder.ABOVE_TOP);
ttbdMessage.setTitleJustification(TitledBorder.CENTER);
jscpMessage.setBorder(ttbdMessage);
container.add(jscpMessage, BorderLayout.CENTER);
}
@Override
public void reinit() {
}
@Override
public void dealEvent() {
jfrmMain.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
closeView();
}
});
jtxfCommand.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String command = jtxfCommand.getText().trim();
if (command.length() <= 0) {
return;
}
dealCommand(command);
jtxfCommand.setText("");
}
});
}
@Override
public JFrame getJFrame() {
return jfrmMain;
}
@Override
public void processMessage(String message) {
jtxaMessage.append(message);
jtxaMessage.append("\n");
jtxaMessage.setCaretPosition(jtxaMessage.getText().length());
}
private void closeView() {
if (server.isStartUp()) {
ViewTool.showWarnning(jfrmMain, "服务器尚未宕机,请先宕机再关闭");
return;
}
try {
exitView();
} catch (FrameIsNull e) {
e.printStackTrace();
}
}
private void dealCommand(String command) {
if (command.equalsIgnoreCase("st")) {
try {
server.startUp();
} catch (IOException e) {
ViewTool.showError(jfrmMain, e.getMessage());
}
} else if (command.equalsIgnoreCase("sd")) {
server.shutDown();
} else if (command.equalsIgnoreCase("fd")) {
server.forceDown();
} else if (command.equalsIgnoreCase("x")) {
closeView();
}
}
}
聊天室服务器model模块
就是聊天室用户的model,描述一个用户的所有信息
public class UserInfo {
private String userId;
private String password;
private String nick;
private String netId;
public UserInfo() {
}
public String getUserId() {
return userId;
}
public UserInfo setUserId(String userId) {
this.userId = userId;
return this;
}
public String getPassword() {
return password;
}
public UserInfo setPassword(String password) {
this.password = password;
return this;
}
public String getNick() {
return nick;
}
public UserInfo setNick(String nick) {
this.nick = nick;
return this;
}
public String getNetId() {
return netId;
}
public UserInfo setNetId(String netId) {
this.netId = netId;
return this;
}
@Override
public String toString() {
return nick;
}
}
聊天室服务器controller模块
DAO模块:对于数据bai库中的数据做增删改查等操作的代码
import com.mec.chatroom.server.model.UserInfo;
import com.mec.orm.core.MecDataBase;
public class UserDao {
public UserDao() {
}
public UserInfo getUserById(String userId) {
MecDataBase mecDataBase = MecDataBase.newInstance();
UserInfo user = mecDataBase.getRecordByKey(UserInfo.class, userId);
return user;
}
}
service模块:服务层,将几种操作封装起来,提供不同的数据库的服务。
import com.mec.chatroom.server.model.UserInfo;
import com.mec.chatroom.server.userDao.UserDao;
public class UserService {
private UserDao userDao;
public UserService() {
userDao = new UserDao();
}
public UserInfo checkUser(String userId, String password) {
UserInfo user = new UserInfo();
if (userId.equals("") || password.equals("")) {
return null;
}
user = userDao.getUserById(userId);
if (user == null) {
return null;
}
if (user.getPassword() == null) {
return null;
}
String hPassword = String.valueOf(user.getPassword().hashCode());
if (!hPassword.equals(password)) {
return null;
}
return user;
}
}
对与用户的操作响应类
import com.mec.chatroom.server.model.UserInfo;
import com.mec.chatroom.server.userService.UserService;
public class UserAction {
private UserService userService;
public UserAction() {
userService = new UserService();
}
public UserInfo userLogin(String userId, String password) {
UserInfo user = userService.checkUser(userId, password);
return user;
}
}
聊天室服务器需要的配置文件
serverConfig.properties基本设置
port=54188
maxClientCount=200
connection.properties数据库连接相关配置
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mec_chat_room?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
user=xxxx
password=123456
ClassTableMapping.xml数据库表和类对应映射关系
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<mapping className = "com.mec.chatroom.server.model.UserInfo" tableName = "sys_user_info" key = "userId">
<column propertyName = "userId" columnName = "user_id"> </column>
<column propertyName = "netId" columnName = "net_id"> </column>
</mapping>
</mappings>
actionMapping.xml响应动作配置
<?xml version="1.0" encoding="UTF-8"?>
<actions>
<action actionName= "userLogin" className= "com.mec.chatroom.server.userAction.UserAction" methodName= "userLogin">
<parameter name= "userId" type= "string"></parameter>
<parameter name= "password" type= "string"></parameter>
</action>
</actions>
需要的jar包
聊天室客户端view模块
客户端的界面有三个分别是连接服务器界面(ConnectToServer),登陆界面(UserLoginView),和聊天室界面(ChatRoomView)。
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.UnknownHostException;
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.mec.csframework.core.ClientActionNotSetException;
import com.mec.util.FrameIsNull;
import com.mec.util.IMecView;
import com.mec.util.ViewTool;
public class ConnectToServer implements IMecView {
private Client client;
private JFrame jfrmMain;
private Container container;
private JLabel jlblMessage;
private int count;
public ConnectToServer() throws UnknownHostException {
count = 1;
client = new Client();
client.setClientAction(new ConnectToServerAction());
initView();
}
public void setClientConfig(String path) {
client.initConfig(path);
}
public void setClientConfigAbsolutePath(String path) {
client.initConfigAbsolutePath(path);
}
@Override
public void init() {
jfrmMain = new JFrame("连接服务器");
jfrmMain.setSize(400, 200);
jfrmMain.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
jfrmMain.setLocationRelativeTo(null);
jfrmMain.setResizable(false);
container = new Container();
container = jfrmMain.getContentPane();
container.setLayout(new BorderLayout());
JLabel jlblTopic = new JLabel("连接服务器", JLabel.CENTER);
jlblTopic.setFont(topicFont);
jlblTopic.setForeground(topicColor);
container.add(jlblTopic, BorderLayout.NORTH);
jlblMessage = new JLabel("正在进行第"+ count++ +"次连接....", JLabel.CENTER);
jlblMessage.setFont(normalFont);
container.add(jlblMessage, BorderLayout.CENTER);
}
@Override
public void reinit() {
boolean connectOk = false;
while (!connectOk) {
try {
connectOk = client.connectToServer();
if (connectOk == false) {
int choice = ViewTool.getChoice(jfrmMain, "连接失败,是否继续连接", JOptionPane.YES_NO_OPTION);
if (choice == JOptionPane.YES_OPTION) {
jlblMessage.setText("正在进行第"+ count++ +"次连接....");
continue;
} else {
closeView();
break;
}
}
} catch (ClientActionNotSetException e) {
ViewTool.showError(jfrmMain, e.getMessage());
closeView();
break;
}
}
}
@Override
public void dealEvent() {
jfrmMain.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
closeView();
}
});
}
@Override
public JFrame getJFrame() {
return jfrmMain;
}
private void closeView() {
try {
exitView();
} catch (FrameIsNull e) {
e.printStackTrace();
}
}
class ConnectToServerAction extends ClientActionAdapter {
@Override
public void serverFull() {
ViewTool.showMessage(jfrmMain, "服务器已经爆满,请稍后尝试");
closeView();
}
@Override
public void afterOnline() {
try {
new UserLoginView(client).showView();
} catch (FrameIsNull e) {
e.printStackTrace();
} finally {
closeView();
}
}
}
}
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
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.action.ActionBeanFactory;
import com.mec.csframework.core.Client;
import com.mec.csframework.core.ClientActionAdapter;
import com.mec.util.ArgumentMaker;
import com.mec.util.FrameIsNull;
import com.mec.util.IMecView;
import com.mec.util.ViewTool;
public class UserLoginView implements IMecView {
private JFrame jfrmUserLogin;
private Client client;
private JTextField jtxtUserName;
private JPasswordField jpswPassword;
private JLabel jlblRegistry;
private JButton jbtnLogin;
public UserLoginView(Client client) {
this.client = client;
this.client.setClientAction(new UserLoginAction());
initView();
}
@Override
public void init() {
jfrmUserLogin = new JFrame("用户登录");
jfrmUserLogin.setLayout(new BorderLayout());
jfrmUserLogin.setSize(300, 200);
jfrmUserLogin.setLocationRelativeTo(null);
jfrmUserLogin.setResizable(false);
jfrmUserLogin.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
JLabel jlblTopic = new JLabel("用户登录", JLabel.CENTER);
jlblTopic.setFont(topicFont);
jlblTopic.setForeground(topicColor);
jfrmUserLogin.add(jlblTopic, BorderLayout.NORTH);
JPanel jpnlContxt = new JPanel();
jpnlContxt.setLayout(null);
jfrmUserLogin.add(jpnlContxt, BorderLayout.CENTER);
int normalSize = normalFont.getSize();
int left = 8*PADDING;
int top = 5*PADDING;
int lblWidth = 2 * normalSize;
int lblHeight = normalSize + 2;
JLabel jlblUserName = new JLabel("账号");
jlblUserName.setFont(normalFont);
jlblUserName.setBounds(left, top, lblWidth, lblHeight);
jpnlContxt.add(jlblUserName);
jtxtUserName = new JTextField();
jtxtUserName.setFont(normalFont);
jtxtUserName.setBounds(left + lblWidth + PADDING, top, 11*normalSize, lblHeight+4);
jpnlContxt.add(jtxtUserName);
top += lblHeight + 3*PADDING;
JLabel jlblPassword = new JLabel("密码");
jlblPassword.setFont(normalFont);
jlblPassword.setBounds(left, top, lblWidth, lblHeight);
jpnlContxt.add(jlblPassword);
jpswPassword = new JPasswordField();
jpswPassword.setFont(normalFont);
jpswPassword.setBounds(left + lblWidth + PADDING, top, 11*normalSize, lblHeight + 4);
jpnlContxt.add(jpswPassword);
JPanel jpnlButton = new JPanel();
jpnlButton.setLayout(new FlowLayout());
jfrmUserLogin.add(jpnlButton, BorderLayout.SOUTH);
jlblRegistry = new JLabel("注册");
jlblRegistry.setFont(new Font("微软雅黑", Font.BOLD, 16));
jlblRegistry.setForeground(new Color(5,98,44));
jlblRegistry.setCursor(handCursor);
jpnlButton.add(jlblRegistry);
JLabel jlblBlank = new JLabel(" ");
jlblBlank.setFont(normalFont);
jpnlButton.add(jlblBlank);
jbtnLogin = new JButton("登陆");
jbtnLogin.setFont(smallFont);
jpnlButton.add(jbtnLogin);
}
@Override
public void reinit() {
}
@Override
public void dealEvent() {
jbtnLogin.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String action = "userLogin";
ActionBeanFactory.setObject(action, UserLoginView.this);
String userId = jtxtUserName.getText();
String mPassword = new String(jpswPassword.getPassword());
String password = String.valueOf(mPassword.hashCode());
client.sendRequest(action, new ArgumentMaker().add("userId", userId)
.add("password", password).toString());
}
});
jfrmUserLogin.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
client.offline();
}
});
jtxtUserName.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
jtxtUserName.selectAll();
}
});
jtxtUserName.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
jpswPassword.requestFocus();
}
});
jpswPassword.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
jpswPassword.setText("");
}
});
}
@Override
public JFrame getJFrame() {
return jfrmUserLogin;
}
private void closeView() {
try {
exitView();
} catch (FrameIsNull e) {
e.printStackTrace();
}
}
public void afterClickedLogin(UserInfo user) {
if (user == null) {
ViewTool.showError(jfrmUserLogin, "用户账号或密码错误");
jpswPassword.setText("");
jtxtUserName.selectAll();
jtxtUserName.requestFocus();
return;
}
user.setNetId(client.getId());
try {
new ChatRoomView(user, client).showView();
} catch (FrameIsNull e) {
e.printStackTrace();
}
closeView();
}
class UserLoginAction extends ClientActionAdapter {
@Override
public void serverForcedown() {
ViewTool.showError(jfrmUserLogin, "服务器强制宕机,服务停止");
closeView();
}
@Override
public void serverAbnormalDrop() {
ViewTool.showError(jfrmUserLogin, "服务器异常,服务停止");
closeView();
}
@Override
public boolean confirmOffline() {
int choice = ViewTool.getChoice(jfrmUserLogin, "是否确定下线?", JOptionPane.YES_NO_OPTION);
return choice == JOptionPane.YES_OPTION;
}
@Override
public void afterOffline() {
closeView();
}
}
}
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import com.mec.chatroom.client.model.UserInfo;
import com.mec.chatroom.core.AppMessage;
import com.mec.chatroom.core.EAppCommand;
import com.mec.chatroom.core.FriendPool;
import com.mec.csframework.core.Client;
import com.mec.csframework.core.ClientActionAdapter;
import com.mec.util.ArgumentMaker;
import com.mec.util.FrameIsNull;
import com.mec.util.IMecView;
import com.mec.util.ViewTool;
public class ChatRoomView implements IMecView {
private JFrame jfrmChatRoomView;
private UserInfo me;
private Client client;
private UserInfo allFriend;
private final String ALL = "00000000";
private FriendPool friendPool;
private JTextArea jtatSystemMessage;
private JTextArea jtatChat;
private DefaultListModel<UserInfo> dlmFriendList;
private JList<UserInfo> jlstFriendList;
private JLabel jlblCurrentFriend;
private JTextField jtxtSpeakContext;
private JButton jbtnSend;
public ChatRoomView(UserInfo userInfo, Client client) {
this.me = userInfo;
this.client = client;
this.client.setClientAction(new ChatRoomAction());
friendPool = new FriendPool();
this.allFriend = new UserInfo();
allFriend.setUserId(ALL);
allFriend.setNick("所有人");
initView();
}
@Override
public void init() {
jfrmChatRoomView = new JFrame("聊天室");
jfrmChatRoomView.setDefaultCloseOperation(
JFrame.DO_NOTHING_ON_CLOSE);
jfrmChatRoomView.setSize(800, 600);
jfrmChatRoomView.setLocationRelativeTo(null);
Container container = jfrmChatRoomView.getContentPane();
container.setLayout(new BorderLayout());
JLabel jlblTopic = new JLabel("聊天室", JLabel.CENTER);
jlblTopic.setFont(topicFont);
jlblTopic.setForeground(topicColor);
container.add(jlblTopic, BorderLayout.NORTH);
jtatSystemMessage = new JTextArea(0, 15);
jtatSystemMessage.setFont(normalFont);
JScrollPane jscpSystemMessage =
new JScrollPane(jtatSystemMessage);
jscpSystemMessage.setPreferredSize(new Dimension(200, 400));
TitledBorder ttdbSystemMessage = new TitledBorder(" 系统信息 ");
ttdbSystemMessage.setTitleFont(normalFont);
ttdbSystemMessage.setTitlePosition(TitledBorder.ABOVE_TOP);
ttdbSystemMessage.setTitleJustification(TitledBorder.CENTER);
jscpSystemMessage.setBorder(ttdbSystemMessage);
container.add(jscpSystemMessage, BorderLayout.EAST);
jtatChat = new JTextArea();
jtatChat.setFont(normalFont);
JScrollPane jscpChat = new JScrollPane(jtatChat);
TitledBorder ttdbChat = new TitledBorder(" 聊天信息 ");
ttdbChat.setTitleFont(normalFont);
ttdbChat.setTitleColor(Color.RED);
ttdbChat.setTitlePosition(TitledBorder.ABOVE_TOP);
ttdbChat.setTitleJustification(TitledBorder.CENTER);
jscpChat.setBorder(ttdbChat);
container.add(jscpChat, BorderLayout.CENTER);
JPanel jpnlFriends = new JPanel(new BorderLayout());
container.add(jpnlFriends, BorderLayout.WEST);
JPanel jpnlWelcome = new JPanel(new GridLayout(0, 1));
jpnlFriends.add(jpnlWelcome, BorderLayout.NORTH);
JLabel jlblWelcome = new JLabel(" 欢迎 ", JLabel.CENTER);
jlblWelcome.setFont(normalFont);
jlblWelcome.setForeground(Color.RED);
jpnlWelcome.add(jlblWelcome);
JLabel jlblUserSelf = new JLabel(
me.getNick(), JLabel.CENTER);
jlblUserSelf.setFont(normalFont);
jlblUserSelf.setForeground(topicColor);
jpnlWelcome.add(jlblUserSelf);
dlmFriendList = new DefaultListModel<>();
jlstFriendList = new JList<>(dlmFriendList);
jlstFriendList.setFont(normalFont);
JScrollPane jscpFriendList =
new JScrollPane(jlstFriendList);
jscpFriendList.setPreferredSize(new Dimension(200, 300));
TitledBorder ttdbFriendList =
new TitledBorder(" 好友列表 ");
ttdbFriendList.setTitleFont(normalFont);
ttdbFriendList.setTitlePosition(TitledBorder.TOP);
ttdbFriendList.setTitleJustification(
TitledBorder.CENTER);
jscpFriendList.setBorder(ttdbFriendList);
jpnlFriends.add(jscpFriendList, BorderLayout.CENTER);
JPanel jpnlCurFriend =
new JPanel(new GridLayout(0, 1));
jpnlFriends.add(jpnlCurFriend, BorderLayout.SOUTH);
JLabel jlblCurrentFriendCaption =
new JLabel("当前好友", 0);
jlblCurrentFriendCaption.setFont(normalFont);
jpnlCurFriend.add(jlblCurrentFriendCaption);
jlblCurrentFriend = new JLabel("", JLabel.CENTER);
jlblCurrentFriend.setFont(normalFont);
jlblCurrentFriend.setForeground(Color.RED);
jpnlCurFriend.add(jlblCurrentFriend);
JPanel jpnlFooter = new JPanel();
container.add(jpnlFooter, BorderLayout.SOUTH);
JLabel jlblYousay = new JLabel("你说 ");
jlblYousay.setFont(normalFont);
jpnlFooter.add(jlblYousay);
jtxtSpeakContext = new JTextField(40);
jtxtSpeakContext.setFont(normalFont);
jpnlFooter.add(jtxtSpeakContext);
jbtnSend = new JButton("发送");
jbtnSend.setFont(smallFont);
jpnlFooter.add(jbtnSend);
}
@Override
public void reinit() {
dlmFriendList.addElement(allFriend);
jlstFriendList.setSelectedIndex(0);
AppMessage appMessage = new AppMessage();
appMessage.setCommand(EAppCommand.I_AM_COMING)
.setParameter(ArgumentMaker.gson.toJson(this.me));
client.toAll(ArgumentMaker.gson.toJson(appMessage));
jtatChat.setEditable(false);
jtatChat.setFocusable(false);
jtatSystemMessage.setEditable(false);
jtatSystemMessage.setFocusable(false);
jtxtSpeakContext.requestFocus();
}
@Override
public void dealEvent() {
jfrmChatRoomView.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
client.offline();
}
});
jlstFriendList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() == false) {
UserInfo user = jlstFriendList.getSelectedValue();
jlblCurrentFriend.setText(user.getNick());
}
}
});
jbtnSend.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
UserInfo sendUser = jlstFriendList.getSelectedValue();
String message = jtxtSpeakContext.getText();
if (message.length() <=0) {
ViewTool.showError(jfrmChatRoomView, "不能发送空的消息");
return;
}
jtxtSpeakContext.setText("");
if (sendUser.getUserId().equals(ALL)) {
groupSend(message);
} else {
singleSend(message, sendUser);
}
}
});
}
private void singleSend(String message, UserInfo targetUser) {
showChatMessage("你对" + targetUser.getNick() + "悄悄地说:" + "[" + message + "]");
AppMessage appMessage = new AppMessage();
ArgumentMaker argumentMaker = new ArgumentMaker();
appMessage.setCommand(EAppCommand.TO_ONE)
.setParameter(argumentMaker.add("from", me).add("content", message).toString());
client.toOne(targetUser.getNetId(), ArgumentMaker.gson.toJson(appMessage));
}
private void groupSend(String message) {
showChatMessage("你向大家说:[" + message + "]");
AppMessage appMessage = new AppMessage();
ArgumentMaker argumentMaker = new ArgumentMaker();
appMessage.setCommand(EAppCommand.TO_ALL).setParameter(argumentMaker.add("from", me)
.add("content", message).toString());
client.toAll(ArgumentMaker.gson.toJson(appMessage));
}
@Override
public JFrame getJFrame() {
return jfrmChatRoomView;
}
private void showChatMessage(String message) {
jtatChat.append(message + "\n");
jtatChat.setCaretPosition(jtatChat.getText().length());
}
private void showSystemMessage(String message) {
jtatSystemMessage.append(message + "\n");
jtatSystemMessage.setCaretPosition(jtatSystemMessage.getText().length());
}
private void closeView() {
try {
exitView();
} catch (FrameIsNull e) {
e.printStackTrace();
}
}
class ChatRoomAction extends ClientActionAdapter {
@Override
public void serverForcedown() {
ViewTool.showError(jfrmChatRoomView, "服务强制宕机,服务停止");
closeView();
}
@Override
public void serverAbnormalDrop() {
ViewTool.showError(jfrmChatRoomView, "服务器异常,服务停止");
closeView();
}
@Override
public boolean confirmOffline() {
int choice = ViewTool.getChoice(jfrmChatRoomView, "是否确认下线?", JOptionPane.YES_NO_OPTION);
return choice == JOptionPane.YES_OPTION;
}
@Override
public void beforeOffline() {
AppMessage appMessage = new AppMessage();
appMessage.setCommand(EAppCommand.I_AM_GONE).setParameter(ArgumentMaker.gson.toJson(me));
client.toAll(ArgumentMaker.gson.toJson(appMessage));
}
@Override
public void afterOffline() {
closeView();
}
@Override
public void otherClientDrop(String id) {
UserInfo user = null;
for (int i = 1; i < dlmFriendList.size(); i++) {
user = dlmFriendList.get(i);
if (user.getNetId().equals(id)) {
break;
}
}
showSystemMessage("你的好友[" + user.getNick() + "]异常离开聊天室");
if (jlstFriendList.getSelectedValue().equals(user)) {
ViewTool.showError(jfrmChatRoomView, "你的聊天对象已经下线");
jlstFriendList.setSelectedIndex(0);
jlblCurrentFriend.setText(allFriend.toString());
}
dlmFriendList.removeElement(user);
}
@Override
public void dealToOne(String sourceId, String message) {
AppMessage appMessage = ArgumentMaker.gson.fromJson(message, AppMessage.class);
EAppCommand command = appMessage.getCommand();
String parameter = appMessage.getParameter();
switch (command) {
case I_AM_HERE:
UserInfo user = ArgumentMaker.gson.fromJson(parameter, UserInfo.class);
dlmFriendList.addElement(user);
friendPool.addFriend(user);
break;
case TO_ONE:
ArgumentMaker argumentMaker = new ArgumentMaker(parameter);
String spokenMessage = (String) argumentMaker.getArgumentByName("content", String.class);
UserInfo friend = (UserInfo) argumentMaker.getArgumentByName("from", UserInfo.class);
showChatMessage(friend.getNick() + "悄悄地对你说:" + "[" + spokenMessage +"]");
break;
default:
break;
}
}
@Override
public void dealToAll(String sourceId, String message) {
AppMessage appMessage = ArgumentMaker.gson.fromJson(message, AppMessage.class);
EAppCommand command = appMessage.getCommand();
String parameter = appMessage.getParameter();
switch (command) {
case I_AM_COMING:
UserInfo newUser = ArgumentMaker.gson.fromJson(parameter, UserInfo.class);
showSystemMessage("你的好友[" + newUser.getNick() + "]已经进入聊天室");
friendPool.addFriend(newUser);
dlmFriendList.addElement(newUser);
AppMessage toNewUser = new AppMessage();
toNewUser.setCommand(EAppCommand.I_AM_HERE).setParameter(ArgumentMaker.gson.toJson(me));
client.toOne(newUser.getNetId(), ArgumentMaker.gson.toJson(toNewUser));
break;
case I_AM_GONE:
UserInfo exitUser = ArgumentMaker.gson.fromJson(parameter, UserInfo.class);
showSystemMessage("你的好友[" + exitUser.getNick() + "]已经离开聊天室");
if (jlstFriendList.getSelectedValue().equals(friendPool.getUserByUserId(exitUser.getUserId()))) {
ViewTool.showError(jfrmChatRoomView, "你的聊天对象已经下线");
jlstFriendList.setSelectedIndex(0);
jlblCurrentFriend.setText(allFriend.toString());
}
dlmFriendList.removeElement(friendPool.getUserByUserId(exitUser.getUserId()));
break;
case TO_ALL:
ArgumentMaker argumentMaker = new ArgumentMaker(parameter);
String spokenMessage = (String) argumentMaker.getArgumentByName("content", String.class);
UserInfo friend = (UserInfo) argumentMaker.getArgumentByName("from", UserInfo.class);
showChatMessage(friend.getNick() + "对大家说:" + spokenMessage);
break;
default:
break;
}
}
}
}
聊天室客户端model模块
和服务器一样,描述一个用户所拥有的信息
public class UserInfo {
private String userId;
private String password;
private String nick;
private String netId;
public UserInfo() {
}
public String getUserId() {
return userId;
}
public UserInfo setUserId(String userId) {
this.userId = userId;
return this;
}
public String getPassword() {
return password;
}
public UserInfo setPassword(String password) {
this.password = password;
return this;
}
public String getNick() {
return nick;
}
public UserInfo setNick(String nick) {
this.nick = nick;
return this;
}
public String getNetId() {
return netId;
}
public UserInfo setNetId(String netId) {
this.netId = netId;
return this;
}
@Override
public String toString() {
return nick;
}
}
聊天室客户端controller模块
是聊天室发送消息的规范和好友列表的表示。聊天消息当然不是底层我们CSFramework的消息规范了,它在底层,已经成为我们的工具了,被屏蔽了,看不见,因此我们需要APP层的消息规范。
public class AppMessage {
private EAppCommand command;
private String parameter;
public AppMessage() {
}
public EAppCommand getCommand() {
return command;
}
public AppMessage setCommand(EAppCommand command) {
this.command = command;
return this;
}
public String getParameter() {
return parameter;
}
public AppMessage setParameter(String parameter) {
this.parameter = parameter;
return this;
}
}
public enum EAppCommand {
I_AM_COMING,
I_AM_HERE,
I_AM_GONE,
TO_ONE,
TO_ALL,
}
import java.util.HashMap;
import java.util.Map;
import com.mec.chatroom.client.model.UserInfo;
public class FriendPool {
private Map<String, UserInfo> friendPool;
public FriendPool() {
friendPool = new HashMap<>();
}
public void addFriend(UserInfo user) {
friendPool.put(user.getUserId(), user);
}
public boolean removeFriend(UserInfo user) {
return friendPool.remove(user.getUserId()) != null;
}
public UserInfo getUserByUserId(String userId) {
return friendPool.get(userId);
}
}
聊天室客户端需要的配置文件
clientConfig.properties基本配置
serverIp=127.0.0.1
port=54188
clientAction.xml用户动作响应需要和服务器那边保持一致同步,是对等的,以后扩展开发增加新响应两边都要改
<?xml version="1.0" encoding="UTF-8"?>
<actions>
<action actionName= "userLogin" className= "com.mec.chatroom.client.view.UserLoginView" methodName= "afterClickedLogin">
<parameter name= "user" type= "com.mec.chatroom.client.model.UserInfo"></parameter>
</action>
</actions>
需要的jar包
测试
import com.mec.chatroom.server.view.ServerView;
import com.mec.csframework.action.ActionBeanFactory;
public class ServerMain {
public static void main(String[] args) {
try {
ActionBeanFactory.scanActionMappingXMLPath();
ServerView sv = new ServerView();
sv.setServerConfig("/serverConfig.properties");
sv.showView();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
import java.net.UnknownHostException;
import com.mec.chatroom.client.view.ConnectToServer;
import com.mec.csframework.action.ActionBeanFactory;
import com.mec.util.FrameIsNull;
public class ClientMain {
public static void main(String[] args) {
try {
ActionBeanFactory.scanActionMappingXMLPath("/clientAction.xml");
ConnectToServer cts = new ConnectToServer();
cts.setClientConfig("/clientConfig.properties");
cts.showView();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (FrameIsNull e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结
一个聊天室就做好了,虽然很简陋,但基本群聊、私聊和异常掉线等功能都能得到满足。将聊天室服务器工程和聊天室客户端工程分别打成jar包,就不像CSFramework是个框架了,已经是个聊天APP了。
需要注意/学习的两点:
- 聊天室APP可以再次进行二次开发,如果你觉得界面不好看了,或者需要更多按钮的响应了,就可以继续开发,但要协商好两边,就相当于版本升级过程。
- CSFramework框架不是只能开发出聊天室,它还可以开发出棋牌室等网络APP。我们可以拿着 CSFramework给这个公司开发他们的聊天室,可以给另外一个公司开发他们的聊天室,还可以给其他公司开发棋牌室。对于开发来说这就是代码复用,这就是省钱。从这理解了框架重要性,就是为了避免写重复代码,加快开发效率。