使用delphi12+mysql+nginx+php+indy+dev+ehlib 作为开发环境 开发一个C/S架构的局域网即时通讯系统
使用delphi12+mysql+nginx+php+indy+dev+ehlib 作为开发环境 开发一个C/S架构的局域网即时通讯系统;
主要架构,A客户端>服务器>B客户端,可以互发文件,文字,图片,表情等;
服务端可以进行聊天记录查询,组织架构管理,数据库备份,文件查找等;
客户端可以采用exe+ini+access/sqlite 将聊天记录留存本地数据库,将文件留存软件根目录file/对方用户名/下;
我不太熟悉delphi我需要详细的数据库建表文件,delphi项目新建、窗体新建、控件拖拽的详细说明
一、系统架构与技术栈
二、数据库设计(MySQL)
1. 部门表(tb_department
):存储组织架构
CREATE TABLE `tb_department` ( `department_id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID', `department_name` VARCHAR(100) NOT NULL COMMENT '部门名称', `parent_id` INT DEFAULT 0 COMMENT '父部门ID(0为根部门)', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) COMMENT '部门表';
2. 用户表(tb_user
):存储用户信息
CREATE TABLE `tb_user` ( `user_id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', `username` VARCHAR(50) UNIQUE NOT NULL COMMENT '登录用户名', `password` VARCHAR(100) NOT NULL COMMENT '哈希后的密码(如SHA256)', `nickname` VARCHAR(50) NOT NULL COMMENT '昵称', `department_id` INT COMMENT '所属部门ID', `avatar` VARCHAR(200) DEFAULT 'default_avatar.png' COMMENT '头像路径', `status` TINYINT DEFAULT 0 COMMENT '在线状态(0:离线,1:在线)', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', FOREIGN KEY (`department_id`) REFERENCES `tb_department`(`department_id`) ) COMMENT '用户表';
3. 聊天记录表(tb_chat_log
):存储全局聊天记录
CREATE TABLE `tb_chat_log` ( `msg_id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '消息ID', `sender_id` INT NOT NULL COMMENT '发送者ID', `receiver_id` INT NOT NULL COMMENT '接收者ID', `msg_type` TINYINT NOT NULL COMMENT '消息类型(1:文字,2:图片,3:文件,4:表情)', `msg_content` VARCHAR(2000) NOT NULL COMMENT '消息内容(文字/文件路径)', `send_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间', `is_read` TINYINT DEFAULT 0 COMMENT '是否已读(0:未读,1:已读)', FOREIGN KEY (`sender_id`) REFERENCES `tb_user`(`user_id`), FOREIGN KEY (`receiver_id`) REFERENCES `tb_user`(`user_id`) ) COMMENT '聊天记录表';
4. 文件传输表(tb_file_transfer
):存储文件传输记录
CREATE TABLE `tb_file_transfer` ( `file_id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '文件ID', `sender_id` INT NOT NULL COMMENT '发送者ID', `receiver_id` INT NOT NULL COMMENT '接收者ID', `file_name` VARCHAR(200) NOT NULL COMMENT '文件名', `file_path` VARCHAR(500) NOT NULL COMMENT '服务器存储路径', `file_size` BIGINT NOT NULL COMMENT '文件大小(字节)', `transfer_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '传输时间', `transfer_status` TINYINT DEFAULT 0 COMMENT '传输状态(0:未完成,1:完成,2:失败)', FOREIGN KEY (`sender_id`) REFERENCES `tb_user`(`user_id`), FOREIGN KEY (`receiver_id`) REFERENCES `tb_user`(`user_id`) ) COMMENT '文件传输表';
5. 备份记录表(tb_backup_log
):存储数据库备份记录
CREATE TABLE `tb_backup_log` ( `backup_id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '备份ID', `backup_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '备份时间', `backup_path` VARCHAR(500) NOT NULL COMMENT '备份文件路径', `backup_size` BIGINT NOT NULL COMMENT '备份文件大小(字节)', `backup_status` TINYINT DEFAULT 1 COMMENT '备份状态(1:成功,0:失败)' ) COMMENT '备份记录表';
三、服务器端开发(Delphi 12)
1. 新建服务器端项目
2. 设计服务器端界面
3. 核心代码实现
(1)启动 / 停止服务器(BtnStartStop
点击事件)
procedure TMainForm.BtnStartStopClick(Sender: TObject); begin if not IdTCPServer1.Active then begin try IdTCPServer1.Active := True; BtnStartStop.Caption := '停止服务器'; LabelStatus.Caption := '状态:运行中'; MemoLog.Lines.Add('服务器启动成功,端口:' + IntToStr(IdTCPServer1.DefaultPort)); except on E: Exception do MemoLog.Lines.Add('服务器启动失败:' + E.Message); end; end else begin IdTCPServer1.Active := False; BtnStartStop.Caption := '启动服务器'; LabelStatus.Caption := '状态:未启动'; MemoLog.Lines.Add('服务器停止成功'); end; end;
(2)处理客户端连接(IdTCPServer1
的OnExecute
事件)
procedure TMainForm.IdTCPServer1Execute(AContext: TIdContext); var Msg: string; MsgObj: TJSONObject; begin try if AContext.Connection.IOHandler.InputBuffer.Size > 0 then begin Msg := AContext.Connection.IOHandler.ReadLn; // 读取客户端消息(UTF-8编码) MemoLog.Lines.Add('收到消息:' + Msg); // 解析JSON消息(需引用System.JSON单元) MsgObj := TJSONObject.ParseJSONValue(Msg) as TJSONObject; try case StrToInt(MsgObj.GetValue('type').Value) of 1: ProcessLogin(AContext, MsgObj); // 处理登录请求(type=1) 2: ProcessChatMsg(AContext, MsgObj); // 处理聊天消息(type=2) 3: ProcessFileTransfer(AContext, MsgObj); // 处理文件传输(type=3) end; finally MsgObj.Free; end; end; except on E: Exception do begin MemoLog.Lines.Add('客户端处理错误:' + E.Message); AContext.Connection.Disconnect; // 断开异常连接 end; end; end;
(3)处理登录请求(ProcessLogin
函数)
procedure TMainForm.ProcessLogin(AContext: TIdContext; MsgObj: TJSONObject); var Username, Password: string; UserID: Integer; ResponseObj: TJSONObject; begin Username := MsgObj.GetValue('username').Value; Password := MsgObj.GetValue('password').Value; // 实际应传输哈希后的密码(如SHA256) // 验证用户(从MySQL查询) FDQuery1.SQL.Text := 'SELECT user_id FROM tb_user WHERE username=:username AND password=:password'; FDQuery1.ParamByName('username').Value := Username; FDQuery1.ParamByName('password').Value := Password; FDQuery1.Open; try if FDQuery1.IsEmpty then begin // 登录失败:用户不存在或密码错误 ResponseObj := TJSONObject.Create; ResponseObj.AddPair('code', TJSONNumber.Create(401)); ResponseObj.AddPair('msg', '用户名或密码错误'); AContext.Connection.IOHandler.WriteLn(ResponseObj.ToString); ResponseObj.Free; MemoLog.Lines.Add('登录失败:' + Username); end else begin // 登录成功:记录客户端连接(需用临界区保证线程安全) UserID := FDQuery1.FieldByName('user_id').AsInteger; CS.Enter; // CS是全局TCriticalSection变量,用于保护ClientList try if ClientList.ContainsKey(UserID) then ClientList.Remove(UserID); ClientList.Add(UserID, AContext); // ClientList是TDictionary<Integer, TIdContext>,存储用户ID与连接的映射 finally CS.Leave; end; // 发送成功响应 ResponseObj := TJSONObject.Create; ResponseObj.AddPair('code', TJSONNumber.Create(200)); ResponseObj.AddPair('msg', '登录成功'); ResponseObj.AddPair('user_id', TJSONNumber.Create(UserID)); AContext.Connection.IOHandler.WriteLn(ResponseObj.ToString); ResponseObj.Free; MemoLog.Lines.Add('登录成功:' + Username + '(ID:' + IntToStr(UserID) + ')'); // 更新用户在线状态 FDQuery1.SQL.Text := 'UPDATE tb_user SET status=1 WHERE user_id=:user_id'; FDQuery1.ParamByName('user_id').Value := UserID; FDQuery1.ExecSQL; end; finally FDQuery1.Close; end; end;
四、客户端开发(Delphi 12)
1. 新建客户端项目
2. 设计登录窗体(LoginForm
)
3. 设计主聊天窗体(MainForm
)
4. 核心代码实现
(1)登录逻辑(LoginForm
的BtnLogin
点击事件)
procedure TLoginForm.BtnLoginClick(Sender: TObject); var LoginMsg: TJSONObject; Response: string; ResponseObj: TJSONObject; begin // 验证输入 if EditUsername.Text = '' then begin ShowMessage('请输入用户名'); EditUsername.SetFocus; Exit; end; if EditPassword.Text = '' then begin ShowMessage('请输入密码'); EditPassword.SetFocus; Exit; end; // 连接服务器 try IdTCPClient1.Host := EditServerIP.Text; IdTCPClient1.Port := StrToInt(EditServerPort.Text); IdTCPClient1.Connect; // 阻塞调用,需用TIdAntiFreeze防止界面冻结 except on E: Exception do begin ShowMessage('连接服务器失败:' + E.Message); Exit; end; end; // 发送登录消息(JSON格式) LoginMsg := TJSONObject.Create; try LoginMsg.AddPair('type', TJSONNumber.Create(1)); // 消息类型:登录 LoginMsg.AddPair('username', EditUsername.Text); LoginMsg.AddPair('password', EditPassword.Text); // 实际应哈希(如SHA256) IdTCPClient1.IOHandler.WriteLn(LoginMsg.ToString); // 发送消息 // 接收服务器响应 Response := IdTCPClient1.IOHandler.ReadLn; ResponseObj := TJSONObject.ParseJSONValue(Response) as TJSONObject; try if ResponseObj.GetValue('code').Value = '200' then begin // 登录成功:打开主窗体 MainForm := TMainForm.Create(Self); MainForm.CurrentUserID := StrToInt(ResponseObj.GetValue('user_id').Value); MainForm.CurrentUsername := EditUsername.Text; MainForm.Show; Self.Hide; end else begin ShowMessage('登录失败:' + ResponseObj.GetValue('msg').Value); IdTCPClient1.Disconnect; end; finally ResponseObj.Free; end; finally LoginMsg.Free; end; end;
(2)发送文字消息(MainForm
的BtnSend
点击事件)
procedure TMainForm.BtnSendClick(Sender: TObject); var ChatMsg: TJSONObject; ReceiverID: Integer; begin // 获取接收者ID(从联系人列表选择) if ListContacts.ItemIndex = -1 then begin ShowMessage('请选择联系人'); Exit; end; ReceiverID := StrToInt(ListContacts.Items.Objects[ListContacts.ItemIndex].ToString); // 假设联系人列表存储了用户ID // 构造聊天消息(JSON格式) ChatMsg := TJSONObject.Create; try ChatMsg.AddPair('type', TJSONNumber.Create(2)); // 消息类型:聊天 ChatMsg.AddPair('sender_id', TJSONNumber.Create(CurrentUserID)); ChatMsg.AddPair('receiver_id', TJSONNumber.Create(ReceiverID)); ChatMsg.AddPair('msg_type', TJSONNumber.Create(1)); // 消息子类型:文字 ChatMsg.AddPair('msg_content', EditMsg.Text); IdTCPClient1.IOHandler.WriteLn(ChatMsg.ToString); // 发送消息 // 保存到本地数据库(SQLite) FDQuery1.SQL.Text := 'INSERT INTO local_chat_log (sender_id, sender_username, receiver_id, receiver_username, msg_type, msg_content, send_time) VALUES (:sender_id, :sender_username, :receiver_id, :receiver_username, :msg_type, :msg_content, :send_time)'; FDQuery1.ParamByName('sender_id').Value := CurrentUserID; FDQuery1.ParamByName('sender_username').Value := CurrentUsername; FDQuery1.ParamByName('receiver_id').Value := ReceiverID; FDQuery1.ParamByName('receiver_username').Value := ListContacts.Items[ListContacts.ItemIndex]; FDQuery1.ParamByName('msg_type').Value := 1; FDQuery1.ParamByName('msg_content').Value := EditMsg.Text; FDQuery1.ParamByName('send_time').Value := Now; FDQuery1.ExecSQL; // 清空输入框 EditMsg.Clear; finally ChatMsg.Free; end; end;
(3)接收消息(客户端需异步线程)
type TReceiveThread = class(TThread) private FClient: TIdTCPClient; FMainForm: TMainForm; protected procedure Execute; override; public constructor Create(AClient: TIdTCPClient; AMainForm: TMainForm); end; constructor TReceiveThread.Create(AClient: TIdTCPClient; AMainForm: TMainForm); begin inherited Create(False); // 立即启动线程 FClient := AClient; FMainForm := AMainForm; FreeOnTerminate := True; // 线程结束后自动释放 end; procedure TReceiveThread.Execute; var Msg: string; MsgObj: TJSONObject; begin while not Terminated and FClient.Connected do begin try Msg := FClient.IOHandler.ReadLn; // 读取消息 if Msg = '' then Continue; // 解析消息 MsgObj := TJSONObject.ParseJSONValue(Msg) as TJSONObject; try case StrToInt(MsgObj.GetValue('type').Value) of 2: FMainForm.ProcessChatMsg(MsgObj); // 处理聊天消息 3: FMainForm.ProcessFileMsg(MsgObj); // 处理文件消息 end; finally MsgObj.Free; end; except on E: Exception do begin FMainForm.MemoChatLog.Lines.Add('接收消息错误:' + E.Message); FClient.Disconnect; end; end; end; end;
在主窗体创建时启动线程:
procedure TMainForm.FormCreate(Sender: TObject); begin // 启动接收消息线程 FReceiveThread := TReceiveThread.Create(IdTCPClient1, Self); end;
五、关键功能补充
1. 文件传输(客户端)
2. 本地存储(SQLite)
3. INI 配置文件
var Ini: TIniFile; begin Ini := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'config.ini'); try // 读取配置 EditServerIP.Text := Ini.ReadString('Server', 'IP', '127.0.0.1'); EditServerPort.Text := Ini.ReadInteger('Server', 'Port', 8080).ToString; // 写入配置 Ini.WriteString('User', 'Username', EditUsername.Text); Ini.WriteString('User', 'Password', SHA256Hash(EditPassword.Text)); // 哈希后存储 finally Ini.Free; end; end;
六、测试与调试
七、注意事项
procedure TMainForm.BtnBackupClick(Sender: TObject); var BackupPath: string; Cmd: string; begin BackupPath := ExtractFilePath(Application.ExeName) + 'backup_' + FormatDateTime('yyyyMMddHHmmss', Now) + '.sql'; Cmd := Format('mysqldump -h localhost -u root -p123456 im_db > "%s"', [BackupPath]); WinExec(PChar(Cmd), SW_HIDE); // 执行备份命令 // 记录备份到tb_backup_log end;
总结
本文由
Doubao-Seed-1.6-thinking 生成