基于java的仿QQ聊天工具

Sunshine

发布日期: 2018-10-29 15:12:06 浏览量: 3298
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

一 需求分析

本系统是基于java开发的聊天室。有用户注册、用户登陆、修改密码、忘记密码、添加好友、用户聊天、群聊功能。如果服务器还没有启动,则客户端是不可以登陆、注册、忘记密码,如果在运行过程中,服务器断开则系统会有提示,聊天对象如果下线发送消息后会有弹窗提示,添加好友后有是否添加好友成功提示。

二 概要设计

在客户端:当用户登录后,生成唯一的socket, 存放在Client实体类中,在整个客户端就一个Client类和一个socket。有一个窗口控制器——ChatUIList,用来记录用户和好友聊天框是否打开,当收到消息后,首先在ChatUIList中查询是否有好友的窗口,如果没有则新建聊天框弹出并显示消息,如果存在与好友的窗口则将消息追加到原聊天框并重新着重显示该窗口。在客户端还拥有一个“命令控制中心”——ChatTread类,在ChatTread类中判断并处理来自服务器中的命令(消息),如果是“message”那么客户端收到是来自好友的消息,如果是“requeste_add_friend”则是好友申请命令,类似有“WorldChat”,“accept_add_friend”,“refuse_to_add”,“changepwd”等命令。

在服务端:有多个socket,用SockList管理连接成功的用户名及其socket。同样在服务端也有一个“命令控制中心”——ServerTread类,它负责处理来自客户端的命令(消息),判断命令的类型,并正确处理他们,给出处理结果和判断是否处理成功,将处理后的命令转发给正确的用户。

功能设计如下:

  • 注册功能

    设计一个注册UI(RegisterUI)类,在打开程序后,模仿QQ在左下方有一个注册按钮,点击注册按钮后弹出注册页面,用户填完必填信息后由客户端将命令发送给服务端(如果服务器在线),服务器收到“register”命令后,连接数据库判断,如果注册成功则返回注册成功消息并弹窗提示,如果失败则弹窗提示注册失败。

  • 登录功能

    打开客户端后,类似QQ有登录按钮,当用户填完用户账号和用户密码并提交后,客户端将登录请求发送给给服务端判断(如果服务端在线),如果密码正确则用户登录成功,显示朋友列表(FriendsUI),否则提示密码错误或账号不存在。

  • 忘记密码

    在客户端右下侧有忘记密码按钮,用户点击按钮后弹出忘记密码页面(ForgetUI),用户填写用户账号后客户端将消息发往服务器,服务器在数据库中检测该账号是否存在,如果存在则显示提示问题,如果不存在则提示账号不存在。用户填写完后续相关信息后,点击“重置密码”按钮后,如果找回密码答案正确则向服务器发送修改密码请求,如果失败则弹窗提示密码错误。最后服务器将处理结果(修改密码是否成功)返还给客户端。

  • 单独聊天(私聊)

    用户登录成功后,双击好友后,首先判断用户与该好友是否有聊天框存在,如果不存在则创建新的聊天框(ChatUI)并在ChatUIList中登记,如果存在则将改聊天框突出显示。用户可以再聊天页面(ChatUI)发送消息,如果好友不在线,服务器会返回好友不在线提示,客户端弹窗提示,如果好友在线收到消息则无提示(类似Linux,没有消息就是好消息)。好友收到消息时,在ChatUIList中查询是否有与该好友的聊天窗口,如果没有则新建窗口显示并在ChatUIList中注册,如果存在则直接将消息追加到聊天窗口上并突出显示。

  • 多人聊天(群聊)

    这里实现的多人聊天式世界喊话,即在线用户都能收到世界喊话的消息,没有好友限制,实现与单独聊天类似。不同的是,服务器收到“世界喊话”命令后,在SocketList中查询当前在线用户,并将世界喊话消息发送给这些用户。

  • 添加好友

    在好友列表页面左下角有添加好友按钮,点击该按钮后弹出添加好友框(AddFriendUI),在添加好友框中重复输入两次欲添加的好友name便可向服务器发送好友请求。当用户收到好友请求后,同意或拒绝都像添加方反馈,添加成功后重新登录便可刷新好友列表。

  • 修改密码

    在好友列表右下方有修改密码按钮,点击该按钮后弹出修改密码框(ChangePwdUI),在这里只需要重复输入两次新密码即可修改密码,是否修改成功服务器都会做出应答,客户端有弹窗提示。在服务端对数据库进行操作,由于可能数据库会出错,如果数据库未成功修改密码,那么要提醒客户。

三 详细设计与实现

3.1 开发环境

此项目运行在Windows 10上,使用Eclipse作为IDE,用MySQL作为数据库。以Java为主要设计语言。

3.2 系统总体结构设计

如下图所示:

3.3 系统流程逻辑设计

如下图所示:

3.4 数据库表设计

数据库名为myqquser, 此数据库中若干个表,一个用户表(tb_user),用来存储用户的信息,如用户名,用户密码,用户问题,用户答案;每一个用户有一个好友表,好友表里存着用户的好友名。

3.4.1 数据库构成

此时数据库中有三个用户分别为inforSec、 zzz、sdust,tb_user中存放着用户的信息,inforSec_friends中存放着inforSec的好友信息,zzz_friends存放着zzz的好友信息,sdust_friends中存放着sdust的好友列表。

3.4.2 用户表(tb_user)的结构及存放的数据

3.4.3 某一用户好友表结果及数据

3.5 客户端关键类与方法

UI类

MainFrame、FriendsUI、ChatUI、ChangeUI、AddFriendUI、RegeditUI、ForgetUI分别为主窗口页面、好友列表页面、聊天窗口、修改密码窗口、添加好友窗口、注册窗口、忘记密码窗口。这一部分主要是显示处理和逻辑处理。

消息处理、通信类

  • ChatUIList类主要记录客户端打开的聊天页面,处理与好友的消息弹窗
  • Client类中有socket负责和服务器通信
  • ChatTreat类是客户端的消息处理中心,处理来自服务器的各种消息并做出相应

3.6 服务端关键类与方法

UI类

服务器端只有一个UI页面——StartServerFrame,因为服务器没有太多消息要显示,所以一个启动窗口即可。

消息处理、通信类

Service类中有socket,负责与客户端建立通信,每个建立的通信都存储在SocketList中,供服务器查询哪些用户上线。ServerThread类是服务器端的控制中心,负责处理来自用户端的消息,并转发给正确的用户,有时还会对数据库进行操作。

数据库处理相关

DBHelper类负责和数据库建立连接,UserService类负责处理具体的和数据库交互的内容,如查询用户账号和密码是否匹配、修改密码、注册用户、忘记密码、添加好友等操作。

3.7 客户端关键代码

3.7.1 主页面(MainFrame)

  1. public class MainFrame extends JFrame implements ActionListener, FocusListener {
  2. private static final long serialVersionUID = 1L;
  3. private static final String _txt_account = "QQ密码/手机/邮箱";
  4. private static final String _txt_pwd = "密码";
  5. private static final String _txt_title = "QQ登录";
  6. private static final String _txt_registe = "注册";
  7. private static final String _txt_forget = "忘记密码";
  8. private static final String _txt_blank = "";
  9. private JTextField account;
  10. private JPasswordField pwd;
  11. private JLabel upper_frame;
  12. private JPanel lower_frame, center_frame;
  13. private JButton login, register, forget;
  14. MainFrame() {
  15. //部分的形成
  16. init();
  17. //整体形成
  18. add(upper_frame, BorderLayout.NORTH);
  19. add(center_frame, BorderLayout.CENTER);
  20. add(lower_frame, BorderLayout.SOUTH);
  21. ImageIcon logo = new ImageIcon("image/logo.jpg"); //左上方小图标
  22. setIconImage(logo.getImage());
  23. setBounds(500, 230, 430, 360); //设定大小及位置
  24. setResizable(false); //登录框大小固定,不允许通过拖、拉改变大小
  25. setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); //设置窗口右上角的叉号,点击叉号窗口关闭 注意不能EXIT_ON_CLOSE做参数的,用它时候使用的是System.exit方法退出应用程序。故会关闭所有窗口。
  26. setTitle(_txt_title);
  27. setVisible(true); //说明数据模型已经构造好了,允许JVM可以根据数据模型执行paint方法开始画图并显示到屏幕上,一般放在最后一句
  28. }
  29. public void init() {
  30. //账号输入块
  31. account = new JTextField(_txt_account);
  32. account.setName("account");
  33. account.setForeground(Color.gray);
  34. account.addFocusListener(this); //产生事件响应用户行为
  35. //密码输入块
  36. pwd = new JPasswordField(_txt_pwd);
  37. pwd.setName("pwd");
  38. pwd.setForeground(Color.gray);
  39. pwd.setEchoChar('\0'); //启动后密码框内一定为 “密码”
  40. pwd.addFocusListener(this);
  41. //注册模块
  42. register = new JButton(_txt_registe);
  43. register.setBorderPainted(false);
  44. register.setBorder(BorderFactory.createRaisedBevelBorder()); //斜面边框(凸)
  45. register.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
  46. register.addActionListener(this);
  47. //忘记密码模块
  48. forget = new JButton(_txt_forget);
  49. forget.setBorderPainted(false);
  50. forget.setBorder(BorderFactory.createRaisedBevelBorder());
  51. forget.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
  52. forget.addActionListener(this);
  53. //login按钮模块
  54. login = new JButton();
  55. ImageIcon login_image = new ImageIcon("image/login_image.png");
  56. login_image.setImage(login_image.getImage().getScaledInstance(430, 35, Image.SCALE_DEFAULT));
  57. login.setIcon(login_image);
  58. login.setBackground(Color.white);
  59. login.setBorderPainted(false); //如果进度条应该绘制边框,则为 true;否则为 false
  60. login.setBorder(null); //设置此组件的边框 无
  61. login.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); //将光标设为 “小手”形状
  62. login.addActionListener(this);
  63. //qq登录框架上半部分(无按钮之类的内容,只有一张图片)
  64. ImageIcon upper_image = new ImageIcon("image/upper_image.png");
  65. upper_image.setImage(upper_image.getImage().getScaledInstance(430, 160, Image.SCALE_DEFAULT));
  66. upper_frame = new JLabel(upper_image);
  67. //qq登录中间部分 (账号、密码、 注册、forget)
  68. center_frame = new JPanel();
  69. center_frame.setName("center_frame");
  70. center_frame.setLayout(null);
  71. center_frame.setLayout(new GridLayout(3, 3, 3, 15)); //这里用到3行3列的空间, 用空格填充
  72. center_frame.add(new JLabel(_txt_blank, JLabel.CENTER));
  73. center_frame.add(account);
  74. center_frame.add(new JLabel(_txt_blank, JLabel.CENTER));
  75. center_frame.add(new JLabel(_txt_blank, JLabel.CENTER));
  76. center_frame.add(pwd);
  77. center_frame.add(new JLabel(_txt_blank, JLabel.CENTER));
  78. center_frame.add(register);
  79. center_frame.add(new JLabel(_txt_blank, JLabel.CENTER));
  80. center_frame.add(forget);
  81. center_frame.setBackground(Color.white);
  82. //qq登录框架的下半部分login
  83. lower_frame = new JPanel();
  84. lower_frame.setName("lower_frame");
  85. lower_frame.setLayout(null);
  86. //lower_frame.setLayout(new GridLayout(1, 3, 3, 15));
  87. lower_frame.setLayout(new GridLayout(0, 1));
  88. lower_frame.add(login);
  89. }
  90. //按钮的点击事件用actionPerformed
  91. @Override
  92. public void actionPerformed(ActionEvent e){
  93. /*
  94. * 1:如果点击了登录按钮 首先判断帐号或者密码是否为空 然后封装为CommandTranser对象 向服务器发送数据 服务器通过与数据库的比对
  95. * 来验证帐号密码,
  96. * 2:如果点击了注册账号就弹出注册页面, 信息填写完整后连接服务器
  97. * 3:如果点击了忘记密码弹出找回密码页面
  98. */
  99. //处理登录(login)页面
  100. if(e.getSource() == login){
  101. String user_name = account.getText().trim();
  102. String user_pwd = new String(pwd.getPassword()).trim();
  103. if("".equals(user_name) || user_name == null || _txt_account.equals(user_name)) {
  104. JOptionPane.showMessageDialog(null, "请输入帐号!!");
  105. return;
  106. }
  107. if("".equals(user_pwd) || user_pwd == null || _txt_pwd.equals(user_pwd)) {
  108. JOptionPane.showMessageDialog(null, "请输入密码!!");
  109. return;
  110. }
  111. User user = new User(user_name, user_pwd);
  112. CommandTranser cmd = new CommandTranser();
  113. cmd.setCmd("login");
  114. cmd.setData(user);
  115. cmd.setReceiver(user_name);
  116. cmd.setSender(user_name);
  117. //实例化客户端 连接服务器 发送数据 密码是否正确?
  118. Client client = new Client(); //创建唯一的客户端(用于接受服务器发来的消息, socket接口),
  119. client.sendData(cmd); //发送数据
  120. cmd = client.getData(); //接受反馈的消息
  121. if(cmd != null) {
  122. if(cmd.isFlag()) {
  123. this.dispose(); //关闭MainFrame页面
  124. JOptionPane.showMessageDialog(null, "登陆成功");
  125. user = (User)cmd.getData();
  126. FriendsUI friendsUI = new FriendsUI(user, client); //将user的全部信息传到FriendsUI中,并将唯一与服务器交流的接口传到FriendUI中 这里传client仅为了发送消息
  127. ChatTread thread = new ChatTread(client, user, friendsUI); //这里传client为了收消息, 整个客户端用一个 ChatTread,一个client
  128. thread.start();
  129. }else {
  130. JOptionPane.showMessageDialog(this, cmd.getResult());
  131. }
  132. }
  133. }
  134. //处理注册(register)页面
  135. if(e.getSource() == register){
  136. RegisterUI registerUI = new RegisterUI(this);
  137. //
  138. }
  139. //处理找回密码(forget)页面
  140. if(e.getSource() == forget){
  141. ForgetUI forgetUI = new ForgetUI(this);
  142. }
  143. }
  144. //鼠标的点击或移动之类的用focuslistener
  145. @Override
  146. public void focusGained(FocusEvent e) {
  147. //处理账号输入框
  148. if(e.getSource() == account){
  149. if(_txt_account.equals(account.getText())){
  150. account.setText("");
  151. account.setForeground(Color.BLACK);
  152. }
  153. }
  154. //处理密码输入框
  155. if(e.getSource() == pwd){
  156. if(_txt_pwd.equals(pwd.getText())){
  157. pwd.setText("");
  158. pwd.setEchoChar('*');
  159. pwd.setForeground(Color.BLACK);
  160. }
  161. }
  162. }
  163. @Override
  164. public void focusLost(FocusEvent e) {
  165. //处理账号输入框
  166. if(e.getSource() == account){
  167. if("".equals(account.getText())){
  168. account.setForeground(Color.gray);
  169. account.setText(_txt_account);
  170. }
  171. }
  172. //处理密码输入框
  173. if(e.getSource() == pwd){
  174. if("".equals(pwd.getText())){
  175. pwd.setForeground(Color.gray);
  176. pwd.setText(_txt_pwd);
  177. pwd.setEchoChar('\0');
  178. }
  179. }
  180. }
  181. public static void main(String[] args) {
  182. // TODO Auto-generated method stub
  183. MainFrame mainframe = new MainFrame();
  184. }
  185. }

3.7.2 消息处理核心——ChatTread类

  1. public class ChatTread extends Thread{
  2. private Client client;
  3. private boolean isOnline = true;
  4. private User user; //如果同意好友请求, 则刷新好友列表
  5. private FriendsUI friendsUI; //刷新好友列表用
  6. private String username; //如果创建新的聊天窗口(chatUI)那么必须将username传进去 用来发送消息
  7. public ChatTread(Client client, User user, FriendsUI friendsUI) {
  8. this.client = client;
  9. this.user = user;
  10. this.friendsUI = friendsUI;
  11. this.username = user.getUsername();
  12. //this.chat_windows = chat_windows;
  13. }
  14. public boolean isOnline() {
  15. return isOnline;
  16. }
  17. public void setOnline(boolean isOnline) {
  18. this.isOnline = true;
  19. }
  20. //run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,
  21. //便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务
  22. @Override
  23. public void run() {
  24. if(!isOnline) {
  25. JOptionPane.showMessageDialog(null, "unbelievable !!!");
  26. return;
  27. }
  28. while(isOnline) {
  29. CommandTranser cmd = client.getData();
  30. //与服务器端相同处理接收到的消息(命令)
  31. //这里处理来自服务器的消息(命令)
  32. if(cmd != null) {
  33. execute(cmd);
  34. //System.out.println(cmd.getCmd());
  35. }
  36. }
  37. }
  38. //处理消息(命令)
  39. private void execute(CommandTranser cmd) {
  40. //登录、忘记密码、注册消息未在此处处理
  41. System.out.println(cmd.getCmd());
  42. //聊天消息请求
  43. if("message".equals(cmd.getCmd())) {
  44. if(cmd.isFlag() == false) {
  45. JOptionPane.showMessageDialog(null, cmd.getResult());
  46. return;
  47. }
  48. //查询是否有与该好友的窗口该窗口
  49. String friendname = cmd.getSender();
  50. ChatUI chatUI = ChatUIList.getChatUI(friendname);
  51. if(chatUI == null) {
  52. chatUI = new ChatUI(username, friendname, username, client);
  53. ChatUIEntity chatUIEntity = new ChatUIEntity();
  54. chatUIEntity.setName(friendname);
  55. chatUIEntity.setChatUI(chatUI);
  56. ChatUIList.addChatUI(chatUIEntity);
  57. } else {
  58. chatUI.show(); //如果以前创建过仅被别的窗口掩盖了 就重新显示
  59. }
  60. Date date = new Date();
  61. SimpleDateFormat sdf = new SimpleDateFormat(
  62. "yy-MM-dd hh:mm:ss a");
  63. String message = friendname + "说:"
  64. + (String) cmd.getData() + "\t" + sdf.format(date)
  65. + "\n";
  66. chatUI.getChatWin().append(message); //追加消息
  67. return;
  68. }
  69. if("WorldChat".equals(cmd.getCmd())) {
  70. //查询是否有与该好友的窗口该窗口
  71. String friendname = cmd.getSender();
  72. ChatUI chatUI = ChatUIList.getChatUI("WorldChat");
  73. if(chatUI == null) {
  74. chatUI = new ChatUI("WorldChat", "WorldChat", user.getUsername(), client);
  75. ChatUIEntity chatUIEntity = new ChatUIEntity();
  76. chatUIEntity.setName("WorldChat");
  77. chatUIEntity.setChatUI(chatUI);
  78. ChatUIList.addChatUI(chatUIEntity);
  79. } else {
  80. chatUI.show(); //如果以前创建过仅被别的窗口掩盖了 就重新显示
  81. }
  82. Date date = new Date();
  83. SimpleDateFormat sdf = new SimpleDateFormat(
  84. "yy-MM-dd hh:mm:ss a");
  85. String message = friendname + "说:"
  86. + (String) cmd.getData() + "\t" + sdf.format(date)
  87. + "\n";
  88. chatUI.getChatWin().append(message); //追加消息
  89. return;
  90. }
  91. if("requeste_add_friend".equals(cmd.getCmd())) {
  92. if(cmd.isFlag() == false) {
  93. JOptionPane.showMessageDialog(null, cmd.getResult());
  94. return;
  95. }
  96. String sendername = cmd.getSender();
  97. int flag = JOptionPane.showConfirmDialog(null, "是否同意" + sendername + "的好友请求", "好友请求", JOptionPane.YES_NO_OPTION);
  98. System.out.println(flag);
  99. if(flag == 0) {
  100. cmd.setCmd("accept_add_friend");
  101. } else {
  102. cmd.setCmd("refuse_add_friend");
  103. }
  104. cmd.setSender(username);
  105. cmd.setReceiver(sendername);
  106. client.sendData(cmd);
  107. return;
  108. }
  109. if("accept_add_friend".equals(cmd.getCmd())) {
  110. JOptionPane.showMessageDialog(null, cmd.getResult());
  111. return;
  112. }
  113. if("refuse_to_add".equals(cmd.getCmd())) {
  114. JOptionPane.showMessageDialog(null, cmd.getResult());
  115. return;
  116. }
  117. if("changepwd".equals(cmd.getCmd())) {
  118. JOptionPane.showMessageDialog(null, cmd.getResult());
  119. return;
  120. }
  121. return;
  122. }
  123. }

3.7.3 负责通信——Client 类

  1. public class Client {
  2. private int port = 2222;
  3. private String Sever_address = "127.0.0.1"; //服务器主机ip
  4. private Socket socket;
  5. //实例化, 建立连接
  6. public Client(){
  7. try {
  8. socket = new Socket(Sever_address, port);
  9. } catch(UnknownHostException e) {
  10. JOptionPane.showMessageDialog(null, "服务器端未开启");
  11. }catch(IOException e) {
  12. JOptionPane.showMessageDialog(null, "服务器端未开启");
  13. }
  14. }
  15. public Socket getSocket() {
  16. return socket;
  17. }
  18. public void setSocket(Socket socket) {
  19. this.socket = socket;
  20. }
  21. //向服务端发送数据
  22. public void sendData(CommandTranser cmd) {
  23. ObjectOutputStream oos = null; //主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了
  24. try {
  25. if(socket == null) {
  26. return;
  27. }
  28. oos = new ObjectOutputStream(socket.getOutputStream());
  29. oos.writeObject(cmd);
  30. } catch(UnknownHostException e) {
  31. JOptionPane.showMessageDialog(null, "服务器端未开启");
  32. }catch(IOException e) {
  33. JOptionPane.showMessageDialog(null, "服务器端未开启");
  34. }
  35. }
  36. //接受服务端发送的消息
  37. public CommandTranser getData() {
  38. ObjectInputStream ois = null;
  39. CommandTranser cmd = null;
  40. if(socket == null) {
  41. //System.out.println("weishenme");
  42. return null;
  43. }
  44. try {
  45. ois = new ObjectInputStream(socket.getInputStream());
  46. cmd = (CommandTranser) ois.readObject();
  47. } catch (IOException e) {
  48. return null;
  49. } catch (ClassNotFoundException e) {
  50. return null;
  51. }
  52. return cmd;
  53. }
  54. }

3.8 服务端关键代码

3.8.1 CommandTranser类——用于命令传递

  1. public class CommandTranser implements Serializable {
  2. private static final long serialVersionUID = 1L;
  3. private String sender = null;// 发送者
  4. private String receiver = null;// 接受者
  5. private Object data = null; // 传递的数据
  6. private boolean flag = false; // 指令的处理结果
  7. private String cmd = null; // 服务端要做的指令
  8. private String result = null; //处理结果
  9. public String getSender() {
  10. return sender;
  11. }
  12. public String setSender(String sender) {
  13. return this.sender = sender;
  14. }
  15. public String getReceiver() {
  16. return receiver;
  17. }
  18. public String setReceiver(String receiver) {
  19. return this.receiver = receiver;
  20. }
  21. public Object getData() {
  22. return data;
  23. }
  24. public Object setData(Object data) {
  25. return this.data = data;
  26. }
  27. public boolean isFlag() {
  28. return flag;
  29. }
  30. public boolean setFlag(boolean flag) {
  31. return this.flag = flag;
  32. }
  33. public String getResult() {
  34. return result;
  35. }
  36. public String setResult(String result) {
  37. return this.result = result;
  38. }
  39. public String getCmd() {
  40. return cmd;
  41. }
  42. public String setCmd(String cmd) {
  43. return this.cmd = cmd;
  44. }
  45. }

3.8.2 ServerThread类——服务器端的消息控制中心

  1. public class ServerThread extends Thread{
  2. private Socket socket;
  3. public ServerThread(Socket socket) {
  4. this.socket = socket;
  5. }
  6. @Override
  7. public void run() {
  8. ObjectInputStream ois = null;
  9. ObjectOutputStream oos1 = null;
  10. ObjectOutputStream oos2 = null;
  11. //ObjectOutputStream oos3 = null;
  12. while(socket != null) {
  13. try {
  14. ois = new ObjectInputStream(socket.getInputStream());
  15. CommandTranser cmd = (CommandTranser) ois.readObject();
  16. //执行命令来自客户端的请求
  17. cmd = execute(cmd);
  18. //消息对话请求,服务器将sender发来的消息发送给receiver
  19. if("message".equals(cmd.getCmd())) {
  20. //如果 msg.ifFlag即 服务器处理成功 可以向朋友发送信息 如果服务器处理信息失败 信息发送给发送者本人
  21. if(cmd.isFlag()) {
  22. //System.out.println("对方在线");
  23. oos1 = new ObjectOutputStream(SocketList.getSocket(cmd.getReceiver()).getOutputStream());
  24. } else {
  25. //System.out.println("对方未在线");
  26. oos2 = new ObjectOutputStream(socket.getOutputStream());
  27. }
  28. }
  29. if ("WorldChat".equals(cmd.getCmd())) {
  30. HashMap<String, Socket> map = SocketList.getMap();
  31. Iterator<Map.Entry<String, Socket>> it = map.entrySet().iterator();
  32. while(it.hasNext()) {
  33. Map.Entry<String, Socket> entry = it.next();
  34. if(!entry.getKey().equals(cmd.getSender())) {
  35. oos1 = new ObjectOutputStream(entry.getValue().getOutputStream());
  36. oos1.writeObject(cmd);
  37. }
  38. }
  39. continue;
  40. }
  41. //登录请求 将数据发送给sender
  42. if ("login".equals(cmd.getCmd())) {
  43. oos1 = new ObjectOutputStream(socket.getOutputStream());
  44. }
  45. //注册请求 将数据发送给sender
  46. if ("register".equals(cmd.getCmd())) {
  47. System.out.println("向客户端发送消息");
  48. oos1 = new ObjectOutputStream(socket.getOutputStream());
  49. }
  50. //添加好友请求将数据发送给 receiver
  51. if ("requeste_add_friend".equals(cmd.getCmd())) {
  52. //在线,将请求发给receiver
  53. if(cmd.isFlag()) {
  54. oos1 = new ObjectOutputStream(SocketList.getSocket(cmd.getReceiver()).getOutputStream());
  55. } else {
  56. //不管在不在线都要向发送方提示消息发送成功
  57. oos2 = new ObjectOutputStream(socket.getOutputStream());
  58. }
  59. }
  60. //同意添加好友请求将数据发送给 receiver和sender
  61. if ("accept_add_friend".equals(cmd.getCmd())) {
  62. //无论是否成功插入数据库都要将结果反馈,但有可能最初请求的客户下线了
  63. oos1 = new ObjectOutputStream(socket.getOutputStream());
  64. if(SocketList.getSocket(cmd.getReceiver()) != null) {
  65. oos2 = new ObjectOutputStream(SocketList.getSocket(cmd.getReceiver()).getOutputStream());
  66. }
  67. }
  68. //拒绝添加好友请求将数据发送给 receiver
  69. if ("refuse_to_add".equals(cmd.getCmd())) {
  70. //被拒绝方在线
  71. if(cmd.isFlag()) {
  72. oos1 = new ObjectOutputStream(SocketList.getSocket(cmd.getReceiver()).getOutputStream());
  73. }else { //被拒方不在线则向拒绝方发送消息
  74. oos2 = new ObjectOutputStream(socket.getOutputStream());
  75. }
  76. }
  77. //修改资料请求 发送给sender 功能暂未实现
  78. if ("changeinfo".equals(cmd.getCmd())) {
  79. oos1 = new ObjectOutputStream(socket.getOutputStream());
  80. }
  81. //修改密码请求 将数据发送给sender
  82. if ("changepwd".equals(cmd.getCmd())) {
  83. oos1 = new ObjectOutputStream(socket.getOutputStream());
  84. }
  85. //忘记密码 发送给sender
  86. if ("forgetpwd".equals(cmd.getCmd())) {
  87. oos1 = new ObjectOutputStream(socket.getOutputStream());
  88. }
  89. //用户下线
  90. if("logout".equals(cmd.getCmd())) {
  91. //
  92. }
  93. if(oos1 != null) {
  94. oos1.writeObject(cmd);
  95. }
  96. if(oos2 != null) {
  97. oos2.writeObject(cmd);
  98. }
  99. } catch(IOException e) {
  100. socket = null;
  101. } catch (ClassNotFoundException e) {
  102. // TODO Auto-generated catch block
  103. e.printStackTrace();
  104. }
  105. }
  106. }
  107. //处理客户端发来的命令
  108. private CommandTranser execute(CommandTranser cmd) {
  109. //登录请求
  110. if("login".equals(cmd.getCmd())) {
  111. UserService userservice = new UserService();
  112. User user = (User)cmd.getData();
  113. cmd.setFlag(userservice.checkUser(user));
  114. //如果登陆成功,将该客户端加入已经连接成功的map集合里面 并且开启此用户的接受线程
  115. if(cmd.isFlag()) {
  116. // 将该线程加入连接成功的map集合
  117. SocketEntity socketEntity = new SocketEntity();
  118. socketEntity.setName(cmd.getSender());
  119. socketEntity.setSocket(socket);
  120. SocketList.addSocket(socketEntity);
  121. //从数据库获取其好友列表并将其好友列表发送至客户端
  122. cmd.setData(userservice.getFriendsList(user));
  123. cmd.setResult("登陆成功");
  124. } else {
  125. cmd.setResult("密码错误");
  126. }
  127. }
  128. //注册请求
  129. if("register".equals(cmd.getCmd())) {
  130. UserService userservice = new UserService();
  131. User user = (User)cmd.getData();
  132. cmd.setFlag(userservice.registerUser(user));
  133. //如果注册成功
  134. if(cmd.isFlag()) {
  135. SocketEntity socketEntity = new SocketEntity();
  136. socketEntity.setName(cmd.getSender());
  137. socketEntity.setSocket(socket);
  138. SocketList.addSocket(socketEntity);
  139. cmd.setData(userservice.getFriendsList(user));
  140. //刚注册的肯定没有好友
  141. cmd.setResult("注册成功");
  142. } else {
  143. cmd.setResult("注册失败可能该用户已存在");
  144. }
  145. }
  146. //修改资料请求 功能暂未实现
  147. if("changeInfo".equals(cmd.getCmd())) {
  148. UserService userservice = new UserService();
  149. User user = (User)cmd.getData();
  150. cmd.setFlag(userservice.changeInfo(user));
  151. if(cmd.isFlag()) {
  152. cmd.setResult("修改信息成功");
  153. } else {
  154. cmd.setResult("修改信息失败");
  155. }
  156. }
  157. //添加好友
  158. if("requeste_add_friend".equals(cmd.getCmd())) {
  159. //检查用户是否在线
  160. if(SocketList.getSocket(cmd.getReceiver()) != null) {
  161. cmd.setFlag(true);
  162. cmd.setResult("对方收到了您的好友请求");
  163. } else {
  164. cmd.setFlag(false);
  165. cmd.setResult("当前用户不在线或者改用户不存在");
  166. }
  167. }
  168. //同意添加好友请求
  169. if("accept_add_friend".equals(cmd.getCmd())) {
  170. UserService userservice = new UserService();
  171. cmd.setFlag(userservice.addFriend(cmd.getReceiver(), cmd.getSender()));
  172. if(cmd.isFlag()) {
  173. cmd.setResult("好友添加成功请重新登陆刷新");
  174. } else {
  175. cmd.setResult("服务器故障导致添加好友失败或者您们已经为好友");
  176. }
  177. }
  178. //拒绝添加好友
  179. if("refuse_to_add".equals(cmd.getCmd())) {
  180. //检查是否在线
  181. if(SocketList.getSocket(cmd.getReceiver()) != null) {
  182. cmd.setFlag(true);
  183. cmd.setResult("您被 " + cmd.getSender() + " 拒绝了");
  184. } else {
  185. cmd.setFlag(false);
  186. cmd.setResult("对方不在线不知道你拒绝了他的好友请求");
  187. }
  188. }
  189. //发送消息指令
  190. if("message".equals(cmd.getCmd())) {
  191. //检查是否在线
  192. if(SocketList.getSocket(cmd.getReceiver()) != null) {
  193. //System.out.println("神奇");
  194. cmd.setFlag(true);
  195. //cmd.setResult("对方成功收到您的消息");
  196. } else {
  197. //System.out.println("神奇啊");
  198. cmd.setFlag(false);
  199. cmd.setResult("当前用户不在线");
  200. }
  201. }
  202. if("WordChat".equals(cmd.getCmd())) {
  203. cmd.setFlag(true);
  204. }
  205. //忘记密码指令 这里最后要讲用户的问题和答案返回
  206. if("forgetpwd".equals(cmd.getCmd())) {
  207. UserService userservice = new UserService();
  208. User user = (User)cmd.getData();
  209. user = userservice.getUser(user);
  210. //如果用户存在
  211. if(user != null ) {
  212. cmd.setResult("查询成功");
  213. cmd.setData(user);
  214. cmd.setFlag(true);
  215. } else {
  216. cmd.setResult("用户可能不存在");
  217. cmd.setFlag(false);
  218. }
  219. }
  220. if ("changepwd".equals(cmd.getCmd())) {
  221. UserService userservice = new UserService();
  222. User user = (User)cmd.getData();
  223. cmd.setFlag(userservice.changePassword(user));
  224. System.out.println("there 111 ");
  225. System.out.println(user.getUsername());
  226. if(cmd.isFlag()) {
  227. cmd.setResult("修改密码成功");
  228. }else {
  229. cmd.setResult("修改密码失败");
  230. }
  231. }
  232. if("logout".equals(cmd.getCmd())) {
  233. SocketList.getSocket(cmd.getSender());
  234. }
  235. return cmd;
  236. }
  237. }

3.8.3 DBHelper类——连接数据库

  1. public class DBHelper {
  2. private static final String driver = "com.mysql.cj.jdbc.Driver";
  3. private static final String url = "jdbc:mysql://localhost:3306/myqquser?&useSSL=false&serverTimezone=UTC";
  4. private static final String username = "root";
  5. private static final String password = "zzzz";
  6. private static Connection con = null;
  7. //静态代码负责加载驱动
  8. static {
  9. try {
  10. Class.forName(driver); //Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段
  11. } catch(ClassNotFoundException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. public static Connection getConnection() {
  16. if(con == null) {
  17. try {
  18. con = DriverManager.getConnection(url, username, password);
  19. } catch(SQLException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. return con;
  24. }
  25. }

3.8.4 UserService类——对数据库进行增删查改

  1. public class UserService {
  2. //login验证账号密码
  3. public boolean checkUser(User user) {
  4. PreparedStatement stmt = null; //PreparedStatement是用来执行SQL查询语句的API之一
  5. Connection conn = null; //与特定数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果
  6. ResultSet rs = null; //是数据中查询结果返回的一种对象,可以说结果集是一个存储查询结果的对象,但是结果集并不仅仅具有存储的功能,他同时还具有操纵数据的功能,可能完成对数据的更新等
  7. conn = DBHelper.getConnection();
  8. String sql = "select * from tb_user where user_name =? and user_pwd =?";
  9. try {
  10. stmt = conn.prepareStatement(sql);
  11. stmt.setString(1, user.getUsername());
  12. stmt.setString(2, user.getUserpwd());
  13. rs = stmt.executeQuery();
  14. if(rs.next()) {
  15. return true;
  16. }
  17. } catch(SQLException e) {
  18. e.printStackTrace();
  19. } finally{
  20. try {
  21. if(rs != null) {
  22. rs.close();
  23. }
  24. if(stmt != null) {
  25. stmt.close();
  26. }
  27. } catch(SQLException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. return false;
  32. }
  33. //登陆后向客户端发送其好友列表
  34. public User getFriendsList(User user) {
  35. PreparedStatement stmt = null;
  36. Connection conn = null;
  37. ResultSet rs = null;
  38. conn = DBHelper.getConnection();
  39. String sql = "select * from " + user.getUsername() + "_friends";
  40. ArrayList<String> friendslist = new ArrayList<String>(); //这里假设好友不超过20个
  41. try {
  42. stmt = conn.prepareStatement(sql);
  43. //stmt.setString(1, user.getUsername() + "_friends"); 这样的话会报错
  44. rs = stmt.executeQuery();
  45. int count = 0;
  46. while(rs.next()) {
  47. friendslist.add(rs.getString(2)); //获取好友name
  48. count++;
  49. }
  50. user.setFriendsNum(count);
  51. user.setFriendsList(friendslist);
  52. return user;
  53. } catch(SQLException e) {
  54. e.printStackTrace();
  55. } finally{
  56. try {
  57. if(rs != null) {
  58. rs.close();
  59. }
  60. if(stmt != null) {
  61. stmt.close();
  62. }
  63. } catch(SQLException e) {
  64. e.printStackTrace();
  65. }
  66. }
  67. return user;
  68. }
  69. //用户注册
  70. public boolean registerUser(User user) {
  71. PreparedStatement stmt1 = null; //PreparedStatement是用来执行SQL查询语句的API之一
  72. PreparedStatement stmt2 = null;
  73. PreparedStatement stmt3 = null;
  74. Connection conn = null;
  75. ResultSet rs = null;
  76. int insertFlag = 0;
  77. int creatFlag = 0;
  78. conn = DBHelper.getConnection();
  79. String sql = "select * from tb_user where user_name =?";
  80. String insertusersql = "insert into tb_user (user_name, user_pwd, user_question, user_ans) values(?, ?, ?, ?)";
  81. String creatfriendstabsql = "CREATE TABLE " + user.getUsername() + "_friends " + "(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(45) NOT NULL, PRIMARY KEY (id))";
  82. try {
  83. stmt1 = conn.prepareStatement(sql);
  84. stmt1.setString(1, user.getUsername());
  85. rs = stmt1.executeQuery();
  86. if(rs.next()) {
  87. System.out.println("该用户已存在" + user.getUsername() + "***");
  88. //用户已被注册
  89. return false;
  90. }
  91. else {
  92. System.out.println("该用户不存在" + user.getUsername() + "***");
  93. //向用户表插入数据
  94. stmt2 = conn.prepareStatement(insertusersql);
  95. stmt2.setString(1, user.getUsername());
  96. stmt2.setString(2, user.getUserpwd());
  97. stmt2.setString(3, user.getUserQuestion());
  98. stmt2.setString(4, user.getUserAnswer());
  99. insertFlag = stmt2.executeUpdate();
  100. System.out.println("向表中插入数据" + user.getUsername() + "***" + insertFlag);
  101. //创建好友表
  102. stmt3 = conn.prepareStatement(creatfriendstabsql);
  103. creatFlag = stmt3.executeUpdate();
  104. System.out.println("创建表" + user.getUsername() + "***" + creatFlag);
  105. if(insertFlag == 1) {
  106. return true;
  107. }
  108. System.out.println("不高兴" + user.getUsername() + "***");
  109. //return true;
  110. }
  111. } catch(SQLException e) {
  112. e.printStackTrace();
  113. } finally{
  114. try {
  115. if(rs != null) {
  116. rs.close();
  117. }
  118. if(stmt1 != null) {
  119. stmt1.close();
  120. }
  121. if(stmt2 != null) {
  122. stmt2.close();
  123. }
  124. if(stmt3 != null) {
  125. stmt3.close();
  126. }
  127. } catch(SQLException e) {
  128. e.printStackTrace();
  129. }
  130. }
  131. return false;
  132. }
  133. //添加好友
  134. public boolean addFriend(String sender, String receiver) {
  135. PreparedStatement stmt1 = null;
  136. PreparedStatement stmt2 = null;
  137. Connection conn = null;
  138. int updateResult1 = 0;
  139. int updateResult2 = 0;
  140. conn = DBHelper.getConnection();
  141. String sql1 = "insert into " + sender + "_friends (name) values(?)";
  142. //String sql1 = "insert into ? (name) values(?)";
  143. String sql2 = "insert into " + receiver + "_friends (name) values(?)";
  144. //String sql2 = "insert into ? (name) values(?)";
  145. try {
  146. stmt1 = conn.prepareStatement(sql1);
  147. stmt2 = conn.prepareStatement(sql2);
  148. stmt1.setString(1, receiver);
  149. stmt2.setString(1, sender);
  150. updateResult1 = stmt1.executeUpdate();
  151. updateResult2 = stmt2.executeUpdate();
  152. if(updateResult1 == 1 && updateResult2 == 1) {
  153. return true;
  154. }
  155. else {
  156. // 如果插入不成功的话,应该将插入成功的删除....这里不做处理了
  157. }
  158. } catch(SQLException e) {
  159. e.printStackTrace();
  160. } finally{
  161. try {
  162. if(stmt1 != null) {
  163. stmt1.close();
  164. }
  165. if(stmt2 != null) {
  166. stmt2.close();
  167. }
  168. } catch(SQLException e) {
  169. e.printStackTrace();
  170. }
  171. }
  172. return false;
  173. }
  174. //修改信息
  175. public boolean changeInfo(User user) {
  176. return false;
  177. }
  178. //修改密码 忘记密码
  179. public boolean changePassword(User user) {
  180. PreparedStatement stmt1 = null; //PreparedStatement是用来执行SQL查询语句的API之一
  181. PreparedStatement stmt2 = null; //PreparedStatement是用来执行SQL查询语句的API之一
  182. Connection conn = null; //与特定数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果
  183. ResultSet rs = null; //是数据中查询结果返回的一种对象,可以说结果集是一个存储查询结果的对象,但是结果集并不仅仅具有存储的功能,他同时还具有操纵数据的功能,可能完成对数据的更新等
  184. int updateFlag = 0;
  185. conn = DBHelper.getConnection();
  186. //String sql = "select * from tb_user where user_question =? and user_ans =?";
  187. String updatesql = "update tb_user set user_pwd =? where user_name = ?";
  188. try {
  189. stmt2 = conn.prepareStatement(updatesql);
  190. stmt2.setString(1, user.getUserpwd());
  191. stmt2.setString(2, user.getUsername());
  192. updateFlag = stmt2.executeUpdate();
  193. if(updateFlag == 1)
  194. return true;
  195. //}
  196. } catch(SQLException e) {
  197. e.printStackTrace();
  198. } finally{
  199. try {
  200. if(rs != null) {
  201. rs.close();
  202. }
  203. if(stmt1 != null) {
  204. stmt1.close();
  205. }
  206. if(stmt2 != null) {
  207. stmt2.close();
  208. }
  209. } catch(SQLException e) {
  210. e.printStackTrace();
  211. }
  212. }
  213. return false;
  214. }
  215. //获得用户的相关信息
  216. public User getUser(User user) {
  217. PreparedStatement stmt1 = null;
  218. PreparedStatement stmt2 = null;
  219. Connection conn = null;
  220. ResultSet rs = null;
  221. conn = DBHelper.getConnection();
  222. String sql = "select * from tb_user where user_name =?";
  223. try {
  224. stmt1 = conn.prepareStatement(sql);
  225. stmt1.setString(1, user.getUsername());
  226. rs = stmt1.executeQuery();
  227. if(rs.next()) {
  228. user.setUsername(rs.getString("user_name"));
  229. user.setUserAnswer(rs.getString("user_ans"));
  230. user.setUserQuestion(rs.getString("user_question"));
  231. return user;
  232. }
  233. } catch(SQLException e) {
  234. e.printStackTrace();
  235. } finally{
  236. try {
  237. if(rs != null) {
  238. rs.close();
  239. }
  240. if(stmt1 != null) {
  241. stmt1.close();
  242. }
  243. if(stmt2 != null) {
  244. stmt2.close();
  245. }
  246. } catch(SQLException e) {
  247. e.printStackTrace();
  248. }
  249. }
  250. return null;
  251. }
  252. }

四 运行测试

注册界面如下

登录界面如下

修改密码界面如下

私聊、群聊界面如下

上传的附件 cloud_download 基于java 的仿QQ聊天工具.7z ( 665.77kb, 342次下载 )
error_outline 下载需要6点积分

发送私信

这个世界上我只相信两个人,一个是我,另一个不是你

13
文章数
15
评论数
最近文章
eject