使用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 生成
