数据存储结构
1.数据库设计
数据库是该即时通讯系统的中枢结构,进行信息处理的各个部分都是在数据库的基础上的,从而能够达成对于各类数据安全完整的存储,并进行合理的运用。有必要时可以设置数据字典,数据字典对于开发非常的有效,它的存在避免了程序员之前互相作繁杂的沟通,大家按照统一的标准来进行,即保证了效率也提高了整体的可维护性,下面是必须要注意的一些关键点:
(1)性能:数据库必须具有足够能力进行数据管控;
(2)兼容性:数据库是否支持开发所需的其他环境;
(3)封闭性:数据库对于数据的存储与处理是否足够保密;
(4)可靠性:数据库能不能承担对于数据的管理与传递;
(5)并行性:数据库是否可以并行运行在多节点上;
(6)可操作性:数据库是否在使用前需要大量时间学习。
根据该即时通讯系统开发时的各类现状需求,我们最终选择了MySQL作为信息处理的载体。
1.1概念结构设计
我们将系统中用到的各类数据抽象成各种数据体系,这些数据类型所组建成的体系正是概念结构的构建过程,进而组建全局的概念结构。
图13 用户信息实体属性图
图14 好友实体属性图
1.2逻辑结构设计
逻辑结构设计则是把上一步设计好的E-R图结构转换为系统中相应的各类数据对应的逻辑结构。而E-R图向关系结构的过度,正是将各类实体与实体的对应关系进一步具体化,这是必要的,也是一个难点所在。
实体之间的关系是需要分为下述几种角度分析的:
如果系统中实体间联系是1:1,则关系模式是自由的,可以与其他任何的关系模式结合。
如果系统中实体间联系是1:n,则应将单独那一端的关系码列入其他端构建的各类结构中。
要是系统中实体间联系是n:m,则应将各端与各端的实体对应关系表清,实体的码与联系的属性正是关系的属性。
基于上述原则,进而完成模型的转换。
图15 实体关系图
1.3物理结构设计
下述其遵守规则:
(1)避免各类数据的交叉而导致内存的浪费,最好建立数据字典。
(2)开发与设定相匹配,避免导致混乱。
(3)数据之间的关系必须是合理的,是可以在后续功能的拓展后不会产生影响的。
基于以上的考量,对本软件的信息结构如下:
表 1 消息表
解释 | 列名 | 类型 | 长度 | 说明 |
消息ID | msg_id | int | 11 | 主键 |
文字消息内容 | msg_content | varchar | 2000 | / |
非文字消息内容 | msg_ifcontent | longblob | / | / |
消息发送者 | msg_sendfrom | int | 11 | / |
消息接收者 | msg_sendto | int | 11 | / |
消息发送时间 | msg_sandtime | datetime | / | / |
消息备注 | msg_remark | varchar | 3000 | / |
消息类型 | msg_type | varchar | 20 | / |
表2 说说表
解释 | 列名 | 类型 | 长度 | 说明 |
空间说说ID | p_id | int | 11 | 主键 |
发布说说用户名 | p_name | varchar | 50 | / |
发布时间 | p_time | datetime | / | / |
发布内容 | p_content | varchar | 3000 | / |
发布备注 | p_remarke | varchar | 3000 | / |
表3 用户信息表
解释 | 列名 | 类型 | 长度 | 说明 |
用户ID | u_id | int | 11 | 主键 |
用户名 | u_name | varchar | 50 | / |
密码 | u_pwd | varchar | 50 | / |
IP | u_ip | varchar | 50 | / |
用户状态 | u_state | varchar | 50 | / |
性别 | u_gender | varchar | 50 | / |
邮箱 | u_email | varchar | 50 | / |
最后一次上线时间 | u_last_login | datetime | / | / |
最后一次离线时间 | u_last_exit | datetime | / | / |
备注 | u_remarke | varchar | 3000 | / |
个性签名 | u_signture | varchar | 100 | / |
用户头像序号 | u_head_img | varchar | 100 | / |
用户类型 | u_type | varchar | 50 | / |
生日 | u_birthday | date | / |
2.接口设计
2.1Socket接口
MYQQ聊天系统分服务端和客户端两个模块,服务端与客户端之间通过套接口Socket(TCP)连接。在 java 中使用套接口,Java API 为处理套接口的通信提供了一个类 java.net.Socket,服务器采用多线程以满足多用户的请求,并通过创建一个 AppSocket来监听来自客户的连接请求,默认 IP 地址为:172.20.10.2 默认端口为 8093。
当客户程序需要与服务器程序通讯的时候,客户程序在客户机创建一个 socket 对象。创建了一个 Socket 对象后,那么它可以通过调用 Socket 的 getInputStream0方法从服务程序获得输入流读传送来的信息,也可以通过调用 Socket 的 getOutputStream0方法获得输出流来发送消息。
由于 Client 使用了流套接字,所以服务程序也要使用流套接字。这就要创建一个ServerSocket 对象,接下来服务程序进入无限循环之中,无限循环从调用 ServerSocket 的accept0方法开始,在调用开始后 accept0)方法将导致调用线程阻塞直到连接建立。在建立连接后accept()返回一个最近创建的 Socket 对象,该 Socket 对象绑定了客户程序的IP 地址或端口号。由于存在单个服务程序与多个客户程序通讯的可能,服务程序循环检测是否有客户连接到服务器上,如果有,则创建一个线程来服务这个客户,以此完成对客户端的监听。
2.2IMsg、IPost、IUser接口
为msg、posts、user数据表创建三个接口,在接口里定义对数据表进行增删查改的抽象方法,再定义三个类Msgdao、Postdao、Userdao来分别实现三个接口,在这三个类里重载接口的抽象方法。
表4 Msg接口方法
方法名 | 解释 | 返回参数类型 | 传入参数类型 |
insertMsg() | 插入数据 | boolean | Msg、InputStream |
insertMsg() | 插入数据 | boolean | Msg |
deleteMsg() | 删除数据 | boolean | int |
updateMsg() | 更新数据 | int | Msg |
selectAMsg() | 查询数据 | Msg | int |
selectAMsg() | 查询数据 | List<Msg> | String |
queryById() | 查询数据 | Msg | int |
表5 IPost接口方法
方法名 | 解释 | 返回参数类型 | 传入参数类型 |
add () | 插入数据 | boolean | Posts |
delete() | 删除数据 | boolean | int |
update() | 更新数据 | boolean | Posts |
queryAll() | 查询数据 | List<Posts> | / |
queryByName() | 查询数据 | Posts | String |
queryById() | 查询数据 | Posts | int |
mana() | 查询数据 | boolean | Posts |
表6 IUser接口方法
方法名 | 解释 | 返回参数类型 | 传入参数类型 |
add () | 插入数据 | boolean | Users |
delete() | 删除数据 | boolean | int |
update() | 更新数据 | boolean | Users |
updateUserState() | 更新数据 | Boolean | String,String |
queryAll() | 查询数据 | List<Posts> | / |
queryByName() | 查询数据 | Users | String |
queryById() | 查询数据 | Users | int |
mana() | 查询数据 | boolean | Users |
checkUserIsExit() | 查询数据 | boolean | String |
checkNameAndPwd() | 查询数据 | boolean | Strin |
3.服务端模块
3.1服务端设计
端口服务:显示监听的端口,默认为 8093。
启动服务:启动服务器,并开始在设置的端口中监听,客户端用户可以登录并聊天。
停止服务:关闭服务器,监听结束,并返回服务器运行时间,客户端用户不能再聊天。
发布公告:服务器端给所有人发布公告。
用户信息管理:可阅览且按用户ID或用户名查找和删除已注册用户的信息,在线状态以及最后一次上下线的时间。
消息管理:可以显示所有用户的聊天记录,发送时间以及消息类型。
退出服务器:退出程序,并停止服务。
一个聊天系统的运行,首先是要有一个完整稳定的服务器端。服务端要具有实现用户注册和用户登陆,在登陆成功后,显示出好友列表,选中好友后可以实现聊天功能并且能够发送表情发送文件和保存聊天内容和显示聊天记录等功能,在新用户上线和用户下线时要更新每个客户端在线用户列表名,并且可以和在线的用户实现一对一的私聊功能。
在服务器端可以实现不断的监听客服的连接,并且根据客户端发送过来的不同信息如注册信息,登录信息,群聊信息,私聊信息,退出信息等,服务器端要做出不同的响应,并且要实现数据库的操作,如根据用户注册的信息保存数据库中,根据登录时发送来的用户名和密码检测是否合法用户等。服务器每接受一条信息,就要调用一次信息发送中心的方法,并将这条信息发送到所有客户端(或者特定的某个/某几个客户端)。电脑每做的一次动作,一个步骤,都是按照以经用计算机语言编好的程序来执行的,程序是计算机要执行的指令的集合,而程序全部都是用我们所掌握的语言来编写的。
3.1.1服务端功能
用户注册与登录模块
- 数据库表Users中没有用户信息时,用户可以通过Register类中的各种功能来注册多个账号。
- 当用户注册过账号但是忘记密码时,用户可以通过Findpwd类中的方法来通过曾注册过的账号和邮箱来查询密码。
- 当用户拥有账号以后就可以通过Login类中的方法来登录账号,其中Login类中的login方法先判断用户的账号用户名或者密码是否正确,如果用户账号、密码错误,或者该账号已经登录过,那就不能登录,系统会弹出窗口提示。
服务管理模块
- 服务端可以在服务管理界面开启或结束服务器,只有开启服务器后,用户可以进行注册、登录等所有功能。
- 主要通过ServerMana类中的startOrCloseServer方法中的btnStart按钮来监听服务器是否已经启动。
- 管理员通过QQserver类中的sendPublicMessage方法和sendShowWindow方法来向聊天室内的在线用户发送聊天室公告和弹窗公告并将公告消息存到数据库内。
用户管理模块
- 管理员可以在用户管理模块界面通过UserActionListen类中的各种方法来查看所有用户的账号、密码、IP、登录状态和邮箱等私人信息。
- 管理员还可以通过用户ID和用户名,利用UserDao类中的查询方法来查询存储在数据库中的Users表的用户信息
消息管理模块
- 管理员可以在消息管理模块界面通过MsgActionListener类中的各种方法,和利用MsgDao类中的各种数据查询方法来查询存储在数据库中的Msg表中的所有聊天和公告消息
图16 服务端模块图
4.客户端模块
4.1客户端设计
用户注册:用户需要通过注册新账号,设置个人信息才能进行登录。
用户登录:在聊天室服务端已开启的情况下,用户才能登录,之后才能开始聊天。
用户退出:关闭所有聊天并退出客户端。
查看好友列表:用户可以查找同一服务器下的所有用户的在线情况。
修改个人资料功能:用户有权限可以修改以往设置的所有基本信息和头像。
单聊功能:用户之间有权限与拥有账号的用户进行即时的聊天。
群聊功能:用户可以与所有在线用户进行即时的群聊。
查看聊天记录:用户有权限查看与好友过往的聊天记录。
发布QQ空间:用户可以发布个人空间说说。
查看QQ空间:用户有权限查看所有用户发的QQ空间说说。
用户单聊模块可以让不同的用户发送在线和离线消息,其中在线消息可以互相发送文字、表情、图片、截图、文件;在线的接收方也可以向离线的用户发送离线消息。用户还可以查询聊天记录。
客户端是指与服务器相对应,为客户提供本地服务的程序。除了一些只在本地运行的应用程序之外,一般安装在普通的客户机上,需要与服务端互相配合运行。因特网发展以后,较常用的用户端包括了如万维网使用的网页浏览器,收寄电子邮件时的电子邮件客户端,以及即时通讯的客户端软件等。对于这一类应用程序,需要网络中有相应的服务器和服务程序来提供相应的服务,如数据库服务,电子邮件服务等等,这样在客户机和服务器端,需要建立特定的通信连接 来保证应用程序的正常运行。
不过客户端及服务端的关系不见得一定建立在两台分开的机器上,同一台机器中也有这种主从关系的存在。提供服务的服务端及接受服务的客户端也有可能都在同一台机器上。
4.2客户端功能
用户单聊模块
- 实现用户在线单聊的类主要是CC_TCP类、CS_TCP类和Chat类,其中CC_TCP类、CS_TCP类继承自TCP类。当在线用户进行各种文字和表情消息的发送与接收时,发送方利用Chat类的sendMessage方法将消息发送给服务端,服务端会利用TCP协议通过 CC_TCP类的线程getMessageNewThread方法接收来自客户端的消息,然后通过dealWithMessage方法处理消息,最后发送给接收方。
- 实现用户离单私聊的类主要是Chat类的sendMessage方法发送给服务端,服务端将离线消息存储到数据表,当离线用户登录上线时,就会接收到服务端转发给自己的离线消息。
- 表情发送主要是通过Chat类的sendMessage方法发送给服务端,类似于发送文字。
- 图片发送主要是通过Chat类的sendImg方法,先获取发送方的图片路径,然后通过IO流将图片进行传输,最后发送给接收方并显示在双方的聊天框上面。
- 文件传输主要是通过Chat类的sendFile方法,先获取发送方的文件路径。由接收方选择是否同意接收,当接收方同意后,再通过IO流将文件进行传输。
- 聊天记录查询主要是通过发送方向服务端发送请求,服务端读取曾存储到数据库的消息并显示在查询记录界面。
用户群聊模块
- 用户群聊模块功能与用户单聊模块相似,有发送文字与聊天记录的功能,不过用户还可以在聊天室内接收到服务端发来的聊天室公告和弹窗公告
- 用户互动模块
- 用户可以进入“我的QQ空间”发布自己的说说动态,此时服务端将动态信息存储到数据库表Posts中。
- 用户还可以在“QQ空间”中通过服务端查读取数据库表Posts,查看到所有用户发布的动态。
图17 客户端模块图
5.运行与测试
5.1服务端模块
- 服务管理界面
- 用户管理界面
- 用户管理功能:查找用户信息
按用户ID查找
按用户名查找
消息管理界面
5.2客户端模块
- 登录界面
- 注册界面
- 找回密码界面
根据用户的注册时填写的邮箱,进行身份验证,账号与E-mail对应正确即可找回密码。
- 用户登陆后界面
- 所有用户当前状态列表(好友列表)
- 个人资料修改界面
点击在线界面左下角个人管理图标即可进入。
- 与好友交互功能
选中指定用户,即可选择发送消息,发送文件,发送图片,查看好友资料,删除好友等功能。
- 查看好友资料功能
- 单人聊天功能
- 发送在线消息
用户1(伙子哥)向用户2(菜菜)发送在线消息,用户1聊天界面
用户2右下角接收到的弹窗界面如下
用户2打开聊天界面如下
- 发送离线消息
用户1向用户3(小马)发送离线消息,用户1界面如下
用户3登录后弹窗界面如下:
- 发送文件功能
用户2向用户1发送文件界面
用户1接收文件界面
用户1与用户2的文件传输进度条(由于界面一模一样,这里只放用户1)
- 发送表情功能
用户1向用户2 发送表情
用户2接收表情
发送图片功能
- 群组聊天功能
点击群组聊天选项,再点击进入官方聊天室按钮,即可与所有在线用户聊天。
其他在线用户弹窗界面
其他用户聊天界面
- 查找聊天记录功能
单人聊天记录
群组聊天记录
- QQ空间功能
点击“我的QQ空间”即可发送个人说说。
点击刷新即可查看自己的所有说说
点击进入QQ空间即可查看所有用户发的说说
介绍:与TCP连接,处理客户端登录事件
//部分代码展示
public void doLogin(String message,TCP tcp){System.out.println("客户端"+tcp.getClientIP()+"尝试登录……");String[] temp=message.split(MyTools.SPLIT1);String name=temp[0];//用户名String password=temp[1];//用户密码int port=Integer.parseInt(temp[2]);//用户端口号int userState=Integer.parseInt(temp[3]);//用户状态System.out.println("用户你好!");if(checkNameAndPwd(name, password))//如果用户名和密码都正确{if(checkIsLoginAgain(name))//如果重复登录tcp.sendMessage(Flag.LOGIN+MyTools.FLAGEND+Flag.FAILED+MyTools.SPLIT1+"您不能重复登录!");else {tcp.setClientName(name);//设置登录用户的名字到TCP中保存tcp.setClientServerPort(port);//保存当前登录用户的端口tcp.setUserState(userState);//保存当前登录用户的状态userDao.updateUserState(name, userState+"");//将用户的状态保存到数据库中去userDao.setLastLogin(name);userDao.setIP(tcp.getClientIP(), name);//将用户的IP存往数据库tcp.sendMessage(Flag.LOGIN+MyTools.FLAGEND+Flag.SUCCESS+MyTools.SPLIT1+getCurrentUserInfo(tcp));//发送一个消息给用户提示登录成功try{Thread.sleep(50);}catch (InterruptedException e){e.printStackTrace();}refreshAllUserFriendList();//刷新所有用户的好友列表,但不包括当前登录用户try{Thread.sleep(50);}catch (InterruptedException e){e.printStackTrace();}//sendUnreadMessage(tcp);//发送用户未读消息showOnlineNumber();//显示当前在线人数System.out.println("***************************************************");Msg msgg=msgDao.queryById(userDao.queryByName(name).getId());//System.out.println(msgg);if(msgg!=null)sendUnreadMessage(tcp);//发送用户未读消息}}else//如果登录失败{tcp.sendMessage(Flag.LOGIN+MyTools.FLAGEND+Flag.FAILED+MyTools.SPLIT1+"用户名或密码错误!");}}
连接数据库,打开及关闭
private Connection getConnection(String url,String user,String password){try{Class.forName("com.mysql.cj.jdbc.Driver");System.out.println("开始尝试连接数据库!");con = DriverManager.getConnection(url, user, password);System.out.println("连接成功!");}catch (Exception e){System.out.println("执行数据库操作报错!");e.printStackTrace();}return con;}/*** 使用默认的参数获取数据库的连接* @return*/private Connection getConnection(){return getConnection(MyTools.JDBC_URL, MyTools.JDBC_USER, MyTools.JDBC_PWD);}/*** 关闭数据库的连接*/public void closeConnection(){try{if(result!=null)result.close();if(preSta!=null)preSta.close();if (con != null)con.close();System.out.println("数据库连接已关闭!");}catch (Exception e){e.printStackTrace();}}