Delphi 7 在现在生产环境中的开发基本已经绝迹,所剩不多的人员也主要用于旧系统的维护与小功能的升级,还有很少的一部分人沿用旧有的技能线升级到 XE 等后续版本继续完成日常的开发工作。更多的系统要么升级到了 .NET 体系 ,要么就是用了 Java 体系。但不可否认的是在 Windows 桌面软件的开发的某些场合中,其实用它还是蛮快的。
一个项目最初的需求跟数据库毫无关系,在考虑需求的基础上选择了 Delphi 7 ,这样发布一个独立的可执行程序,依赖很少,使用比较方便,但需求总是变化的,有了使用数据库的需求,把单机的应用变成了一个网络的应用,以往在 Windows 系统上一般用微软自家的 SQL Server 多一些,但现在机器上没有,MySQL 倒是现成的,于是决定用它了。
Delphi 7 开发的快速性在于其基于组件的丰富性,在早期好像用过 MySQL,用的是基于 ODBC 的 MySQL 驱动,但相当不好用,具体不好用的细节倒是忘记了,想着这么些年过去了,有没有与时俱进的组件呢,搜了一下还真发现了一个,名称是 ZeosLib
,看介绍挺强大,几乎支持所有的数据库,用了一下除了有一点通常都会出现的编码的坑之外还不错,把基本的使用做个记录归档以便以后使用。
具体的操作系统是 Windows 10 家庭中文版,版本号是 20H2,操作系统内部版本是 19042.928,用的数据库是 MySQL Community Server 8.0.17 for Win64 on x86_64 。
我用的是 ZeosLib 是 7.2.10,可通过网址 https://sourceforge.net/projects/zeoslib/ 下载获得。
1. 组件的安装
这里只说 Delphi 7 的安装,下载组件压缩文件后解压缩 zeosdbo-7.2.10-stable.zip
,解压缩后的目录下有三个文件夹和两个文件,在其中的找到 packages
文件夹,进入该文件夹后找到 delphi7
文件夹进入,鼠标左键双击 ZeosDbo.bpg
文件,在 Delphi 7 打开后选择菜单栏 Project
中的 Compile All Projects
菜单项,编译完成后选择 OK 按钮。在 Library Path 中增加该组件的路径,路径指向 delphi7
文件夹下的 build
文件夹。在 delphi7
文件夹中找到 ZComponentDesign.dpk
文件,鼠标双击打开,选择 install
,这样组件就会安装好了。
2. 数据库的访问与操作
MySQL 数据库的安装就不再详述了,在 Windows 系统上安装也比较简单,通常一路「下一步」即可安装完毕。我们这里写一个简单的登录示例,成功登录后记录一条登录信息,大概梳理一下流程。
1. 用户打开程序;
1.1. 如果发现没有数据库配置信息,启动数据库配置界面;
1.2 如果有数据库配置信息,启动用户登录界面;
2. 用户完成数据库连接配置
……
3. 用户完成登录
……
4. 记录登录信息
数据库的准备工作
# 数据库脚本
# 创建 examples 数据库
CREATE DATABASE IF NOT EXISTS `examples`
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
# 创建用户数据表
CREATE TABLE `users` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`loginname` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
`pwd` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`isadmin` tinyint(1) NOT NULL DEFAuLT '0',
PRIMARY KEY(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
# 创建用户登录信息表
CREATE TABLE `user_logins` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`login_desc` varchar(150) COLLATE utf8mb4_general_ci NOT NULL,
`login_time` datetime NOT NULL,
PRIMARY KEY(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
在 Delphi 7 中新建一个 Application ,再添加一个 Data Module 和两个 Form,将 Data Module 命名为 DataBox,将 Application 自带的 Form 和添加的两个 Form 分别命名为 frm_main 、 frm_login 、frm_dbset,保存工程文件为 LoginExample ,代码如下。
LoginExample.dpr 工程文件
program LoginExample;
uses
Forms,
u_main in 'u_main.pas' {frm_main},
u_databox in 'u_databox.pas' {DataBox : TDataModule},
u_login in 'u_login.pas' {frm_login},
u_dbset in 'u_dbset.pas' {frm_dbset};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TDataBox, DataBox);
if show_FormLogin then
begin
Application.CreateForm(Tfrm_main, frm_main);
end;
Application.Run;
end.
u_databox.pas 单元文件
unit u_databox;
interface
uses
Windows,SysUtils, Classes, ZAbstractConnection, ZConnection, DB,
ZAbstractRODataset, ZAbstractDataset, ZDataset,Registry,
Forms, DCPcrypt2, DCPsha256,Dialogs;
type
TDataBox = class(TDataModule)
ZQOper: TZQuery;
ZC: TZConnection;
ZQLogs: TZQuery;
DCP_sha2561: TDCP_sha256;
procedure DataModuleCreate(Sender: TObject);
private
{ Private declarations }
FDBHost : String;
FDBPort : Integer;
FDBName : String;
FDBConUser : String;
FDBConPwd : String;
public
{ Public declarations }
procedure GetDBConStr;
function GetSha256(s:String):String;
end;
var
DataBox: TDataBox;
OperID: Integer;
Operator: string;
LogsID:string;
CurDir:string;
isAdmin:Boolean;
procedure WriteLogs(sOper:Integer;funid:Integer;funName:string);
implementation
uses u_dbset, DateUtils;
{$R *.dfm}
procedure WriteLogs(sOper:Integer;login_desc:string);
begin
with DataBox.ZQLogs do
begin
Close;
SQL.Clear;
SQL.Add('INSERT INTO user_logins(user_id,login_desc,log_time) VALUES(:uid,:desc,:logtime)');
ParamByName('uid').Value:=sOper;
ParamByName('desc').Value:=funName;
ParamByName('logtime').Value:=Now;
ExecSQL;
end;
end;
procedure TDataBox.GetDBConStr;
var
myReg : TRegistry;
begin
myReg := TRegistry.Create;
with myReg do
try
RootKey := HKEY_CURRENT_USER;
if OpenKey('SOFTWARE\ExampleProg\DBConParam\', False) then
begin
FDBHost := ReadString('DBIP');
FDBPort := StrToInt(ReadString('DBPort'));
FDBName := ReadString('DBName');
FDBConUser := ReadString('DBUser');
FDBConPwd := ReadString('DBPwd');
end;
finally
myReg.CloseKey;
myReg.Free;
end;
end;
procedure TDataBox.DataModuleCreate(Sender: TObject);
begin
GetDBConStr;
if FDBHost = '' then
begin
Application.CreateForm(TfrmDbSet, frmDbSet);
frmDbSet.ShowModal;
end;
GetDBConStr;
with ZC do
begin
Disconnect;
Protocol := 'mysql';
LibraryLocation := ExtractFilePath(Application.ExeName)+'libmysql.dll';
HostName := FDBHost;
Port := FDBPort;
User := FDBConUser;
Password := FDBConPwd;
Database := FDBName;
Connect;
end;
with ZQOper do
begin
Close;
SQL.Text := 'SELECT Count(*) as OperCount FROM users';
Open;
if FieldByName('OperCount').Value = 0 then
begin
Close;
SQL.Text := 'INSERT INTO users(loginname,name,pwd,isAdmin) VALUES(:loginname,:name,:pwd,:isAdmin)';
ParamByName('loginname').Value := 'admin';
ParamByName('name').Value := 'admin';
ParamByName('pwd').Value := GetSha256('admin'); //sha256 admin
ParamByName('isAdmin').Value := 1;
try
ExecSQL;
finally
Close;
end;
end;
end;
end;
function TDataBox.GetSha256(s: String): String;
var
Hash : TDCP_sha256;
Digest : array[0..31] of byte;
Source : String;
i : Integer;
str1 : String;
begin
Source := s; //get s string sha256
if Source <> '' then
begin
Hash := TDCP_sha256.Create(nil); //create the hash
Hash.Init;
Hash.UpdateStr(Source);
Hash.Final(Digest);
str1 := '';
for i:=0 to 31 do
str1 := str1 + IntToHex(Digest[i],2);
end;
Result := str1;
end;
end.
u_dbset.pas 单元文件
unit u_dbset;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Mask, Registry,ZAbstractConnection, ZConnection;
type
TfrmDbSet = class(TForm)
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Bevel1: TBevel;
Label6: TLabel;
lblStatus: TLabel;
edt_ServerName: TEdit;
edt_DBName: TEdit;
edt_ConnUser: TEdit;
edt_Pwd: TEdit;
BitBtn1: TBitBtn;
BitBtn2: TBitBtn;
BitBtn3: TBitBtn;
Panel1: TPanel;
Label1: TLabel;
Image1: TImage;
Panel2: TPanel;
Label1: TLabel;
edt_Port: TEdit;
ZC: TZConnection;
procedure FormKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure BitBtn3Click(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure BitBtn1Click(Sender: TObject);
procedure ZCAfterConnect(Sender: TObject);
procedure ZCAfterDisconnect(Sender: TObject);
procedure RzBitBtn2Click(Sender: TObject);
private
{ Private declarations }
DBConStr: string;
FMacineName: string;
FDBName: string;
FPwd: string;
FConnUser: string;
procedure SetDBConStr;
public
{ Public declarations }
end;
var
frmDbSet: TfrmDbSet;
implementation
uses u_databox,comobj;
{$R *.dfm}
{ TfrmDbSet }
procedure TfrmDbSet.SetDBConStr;
var
myReg:TRegistry;
begin
myReg:=TRegistry.Create;
with myReg do
try
RootKey:=HKEY_CURRENT_USER;
if OpenKey('SOFTWARE\ExampleProg\DBConParam\',True) then
begin
WriteString('DBIP',edt_ServerName.Text);
WriteString('DBPort',edt_Port.Text);
WriteString('DBName',edt_DBName.Text);
WriteString('DBUser',edt_ConnUser.Text);
WriteString('DBPwd', edt_Pwd.Text);
end;
finally
myReg.CloseKey;
myReg.Free;
end;
end;
procedure TfrmDbSet.FormKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_F8) and (ssCtrl in Shift) then
begin
edt_ConnUser.Color := clWhite;
edt_ConnUser.Enabled := True;
edt_ConnUser.SetFocus;
end;
end;
procedure TfrmDbSet.BitBtn3Click(Sender: TObject);
begin
Close;
end;
procedure TfrmDbSet.FormShow(Sender: TObject);
begin
edt_ServerName.SetFocus;
end;
procedure TfrmDbSet.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ZC.Disconnect;
Action:=caFree;
frmDbSet:=nil;
end;
procedure TfrmDbSet.BitBtn1Click(Sender: TObject);
begin
with ZC do
begin
Disconnect;
Protocol := 'mysql';
LibraryLocation := ExtractFilePath(Application.ExeName)+'libmysql.dll';
HostName := edt_ServerName.Text;
Port := StrToInt(edt_Port.Text);
User := edt_ConnUser.Text;
Password := edt_Pwd.Text;
Database := edt_DBName.Text;
Connect;
RzBitBtn2.Enabled := True;
end;
lblStatus.Caption := '连接状态:测试连接成功,已连接!';
end;
procedure TfrmDbSet.ZCAfterConnect(Sender: TObject);
begin
lblStatus.Caption := '连接状态:已连接!';
end;
procedure TfrmDbSet.ZCAfterDisconnect(Sender: TObject);
begin
lblStatus.Caption := '连接状态:未连接!';
end;
procedure TfrmDbSet.BitBtn2Click(Sender: TObject);
begin
SetDBConStr;
Close;
end;
end.
u_login.pas 单元文件
unit u_login;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, DB, ZAbstractRODataset, ZAbstractDataset, ZDataset;
type
Tfrm_login = class(TForm)
Label1: TLabel;
edt_pwd: TEdit;
cbo_username: TComboBox;
Label2: TLabel;
btnLogin: TButton;
ZQUser: TZQuery;
procedure FormCreate(Sender: TObject);
procedure btnLoginClick(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure cbo_usernameKeyPress(Sender: TObject; var Key: Char);
procedure edt_pwdKeyPress(Sender: TObject; var Key: Char);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ZQUserAfterOpen(DataSet: TDataSet);
procedure edt_pwdEnter(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
function Show_FormLogin:Boolean;
implementation
uses u_databox;
var
PasswordOK : Boolean;
{$R *.dfm}
function Show_FormLogin:Boolean;
var
frm_login : Tfrm_login;
begin
PasswordOK := False;
frm_login := Tfrm_login.Create(Application);
try
frm_login.ShowModal;
finally
frm_login.Free;
end;
Result := PasswordOK;
end;
procedure Tfrm_login.FormCreate(Sender: TObject);
begin
ZQUser.Close;
ZQUser.Open;
end;
procedure Tfrm_login.btnLoginClick(Sender: TObject);
begin
if ZQUser.Locate('loginname;pwd',
VarArrayOf([cbo_username.Text, DataBox.GetSha256(edt_pwd.Text)]), [loCaseInsensitive]) then
begin
OperID := ZQUser.FieldByName('id').AsInteger;
Operator := cbo_username.Text;
isAdmin := (ZQUser.FieldByName('isAdmin').AsInteger=1);
Application.MessageBox(PChar(Operator + '登录成功'), '登录提示', MB_OK+MB_ICONINFORMATION);
PasswordOK := True;
WriteLogs(OperID,'登录');
if PasswordOK then
Close;
end
else
Application.MessageBox('用户名或密码不正确,登录失败,如忘记密码,请联系管理员!', '错误提示', MB_OK+MB_ICONWARNING);
end;
procedure Tfrm_login.FormCloseQuery(Sender: TObject;
var CanClose: Boolean);
begin
if not PasswordOK then
begin
CanClose := Application.MessageBox('你真的要退出该软件吗?', '信息提示', MB_YESNO+MB_ICONQUESTION)=IDYES;
WriteLogs(OperID, '退出系统');
end;
end;
procedure Tfrm_login.cbo_usernameKeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then
begin
edt_pwd.Text := '';
edt_pwd.SetFocus;
end;
end;
procedure Tfrm_login.edt_pwdKeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then
begin
btnLoginClick(Self);
end;
end;
procedure Tfrm_login.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
procedure Tfrm_login.ZQUserAfterOpen(DataSet: TDataSet);
begin
cbo_username.Items.Clear;
with ZQUser do
begin
First;
while not Eof do
begin
cbo_username.Items.Add(FieldByName('loginname').AsString);
Next;
end;
end;
end;
procedure Tfrm_login.edt_pwdEnter(Sender: TObject);
begin
edt_pwd.Text := '';
end;
end.
完成上述的步骤,这个小的 Demo 就算完成了,有三个地方需要注意一下:
第一,我看我的 IED 中安装了 DCPcrypt2
加密解密组件,随手引用了对密码做哈希处理,这部分如果用于练习的时候可以去掉。
第二,访问 MySQL 数据库需要动态链接库 libmysql.dll
,这个需要注意一下,不管你用 32 位的操作系统还是 64 位的操作系统,同时它也跟你安装的 MySQL 是 32 位的还是 64 位的也没有关系,在 Delphi 7 中使用 MySQL 的时候只能使用这个动态链接库的 32 位版本。
第三,编码问题,Delphi 7 会碰到编码问题,比如向 MySQL 数据库中写入中文会显示乱码,此时在 TZConnection
的 Properties
中添加 codepage=gbk
。另一种处理方式是在使用 TZQuery
等时,在运行 SQL 插入数据语句前,要先运行 set names gbk
。
begin
{这里的 zq1 是一个 TZQuery 控件}
with zq1 do
begin
Close;
SQL.Text := 'set names gbk';
ExecSQL;
SQL.Text := 'INSERT INTO Test(UName) VALUES("张三")';
ExecSQL;
end;
end;
因为是直接在项目中使用后,手工直接在 Notion 中码出来的,并没有写这个 Demo ,所以无法附上 Demo 的源码,对于现在还使用 Delphi 的人来说应该是比较简单的,我其实更多的是给自己做个笔记。但如果有任何问题,可直接通过「关于我」中提供的联系方式与我联系。
– EOF –