我有个需要,获取电脑的cpu,主板号,硬盘号,获取注册码,然后验证我的云端是否对设备授权了。这需要一个云端验证程序,我用mormot2开发。orm模式。
mysql数据库,自动创建表,用unidac,mormot2可以调用第三方的数据库组件,如unidac,zeoslib
mormot2,国内用的好少,资料不多,ai也是一知半解,调教很久才出来。
它太强大了,如果会用了,什么互联网后端,一把梭,啥都能搞出来
你要的所有的东西他都已经有了。
DataModel.pas
unit DataModel;
interface
uses
mormot.core.base,
mormot.orm.core;
type
// Define the Device Entity
[OrmEntity]
TDevice = class(TOrm)
private
fHotelID: Integer;
fHotelName: RawUTF8;
fDeviceCode: RawUTF8;
fIsRegistered: Boolean;
fValidity: TDateTime;
fRegistrationTime: TDateTime;
fDevicePoints: Integer;
published
property HotelID: Integer read fHotelID write fHotelID;
property HotelName: RawUTF8 read fHotelName write fHotelName;
[OrmField(unique)]
property DeviceCode: RawUTF8 read fDeviceCode write fDeviceCode;
property IsRegistered: Boolean read fIsRegistered write fIsRegistered;
property Validity: TDateTime read fValidity write fValidity;
property RegistrationTime: TDateTime read fRegistrationTime write fRegistrationTime;
property DevicePoints: Integer read fDevicePoints write fDevicePoints;
end;
function CreateDataModel: TOrmModel;
implementation
function CreateDataModel: TOrmModel;
begin
Result := TOrmModel.Create([TDevice]);
end;
end.
AuthService.pas
unit AuthService;
interface
uses
mormot.core.base,
mormot.core.data,
mormot.core.json,
mormot.orm.core,
mormot.rest.server,
mormot.core.datetime,
mormot.db.core,
mormot.db.sql,
mormot.core.log,
System.SysUtils,
DataModel;
type
TDeviceValidationResult = record
Success: Boolean;
Message: RawUTF8;
HotelName: RawUTF8;
IsRegistered: Boolean;
Validity: TDateTime;
DaysRemaining: Integer;
DevicePoints: Integer;
end;
TDeviceRegistrationResult = record
Success: Boolean;
Message: RawUTF8;
HotelName: RawUTF8;
Validity: TDateTime;
end;
IDeviceService = interface(IInvokable)
['{9A60C33F-21B2-4B6A-8E56-8A3D2D6D9E9C}']
function ValidateDevice(const DeviceCode: RawUTF8): TDeviceValidationResult;
function RegisterDevice(const HotelID: Integer; const HotelName, DeviceCode: RawUTF8): TDeviceRegistrationResult;
function HealthCheck: Boolean;
end;
TDeviceService = class(TInterfacedObject, IDeviceService)
private
fDatabase: TSqlDBConnectionProperties;
public
procedure SetDatabaseConnection(aDatabase: TSqlDBConnectionProperties);
function ValidateDevice(const DeviceCode: RawUTF8): TDeviceValidationResult;
function RegisterDevice(const HotelID: Integer; const HotelName, DeviceCode: RawUTF8): TDeviceRegistrationResult;
function HealthCheck: Boolean;
function GetDeviceCount: Integer; // 添加一个辅助方法
end;
implementation
{ TDeviceService }
procedure TDeviceService.SetDatabaseConnection(aDatabase: TSqlDBConnectionProperties);
begin
fDatabase := aDatabase;
end;
function TDeviceService.ValidateDevice(const DeviceCode: RawUTF8): TDeviceValidationResult;
var
SQL: RawUTF8;
Rows: ISqlDBRows;
begin
// 初始化返回结果
Result.Success := False;
Result.Message := '设备验证失败';
Result.HotelName := '';
Result.IsRegistered := False;
Result.Validity := 0;
Result.DaysRemaining := 0;
Result.DevicePoints := 0;
if not Assigned(fDatabase) then
begin
Result.Message := '数据库连接未初始化';
Exit;
end;
try
// 直接执行SQL查询
SQL := 'SELECT HotelName, IsRegistered, Validity, DevicePoints ' +
'FROM devices WHERE DeviceCode = ? LIMIT 1';
// 使用正确的 Execute 参数
Rows := fDatabase.Execute(SQL, [DeviceCode]);
if Rows.Step then // 找到设备
begin
Result.HotelName := Rows.ColumnString('HotelName');
Result.IsRegistered := Rows.ColumnInt('IsRegistered') <> 0;
Result.Validity := Rows.ColumnDateTime('Validity');
Result.DevicePoints := Rows.ColumnInt('DevicePoints');
Result.DaysRemaining := Trunc(Result.Validity - Now);
// 验证逻辑
if not Result.IsRegistered then
begin
Result.Message := '设备未注册';
end
else if Now > Result.Validity then
begin
Result.Message := '设备已过期';
end
else
begin
Result.Success := True;
Result.Message := '设备验证成功';
end;
end
else
begin
Result.Message := '设备不存在';
end;
except
on E: Exception do
begin
Result.Message := '服务器错误: ' + E.Message;
TSynLog.Add.Log(sllError, 'ValidateDevice 错误 (设备号: %): %',
[DeviceCode, E.Message]);
end;
end;
end;
function TDeviceService.RegisterDevice(const HotelID: Integer;
const HotelName, DeviceCode: RawUTF8): TDeviceRegistrationResult;
var
SQL: RawUTF8;
Rows: ISqlDBRows;
begin
Result.Success := False;
Result.Message := '设备注册失败';
Result.HotelName := HotelName;
Result.Validity := 0;
if not Assigned(fDatabase) then
begin
Result.Message := '数据库连接未初始化';
Exit;
end;
try
// 1. 检查设备是否已存在
SQL := 'SELECT COUNT(*) as cnt FROM devices WHERE DeviceCode = ?';
Rows := fDatabase.Execute(SQL, [DeviceCode]);
if Rows.Step and (Rows.ColumnInt('cnt') > 0) then
begin
Result.Message := '设备已存在';
Exit;
end;
// 2. 插入新设备
SQL := 'INSERT INTO devices (' +
' HotelID, HotelName, DeviceCode, IsRegistered, ' +
' Validity, RegistrationTime, DevicePoints' +
') VALUES (' +
' :?, :?, :?, 1, ' +
' :?, NOW(), :?' +
')';
var Validity := Now + 365; // 1年有效期
var DevicePoints := 0;
// 注意:Execute 用于 INSERT 可能返回 nil,需要检查受影响行数
var AffectedRows: Integer;
fDatabase.Execute(SQL, [HotelID, HotelName, DeviceCode, Validity, DevicePoints], @AffectedRows);
if AffectedRows > 0 then
begin
Result.Success := True;
Result.Message := '设备注册成功';
Result.Validity := Validity;
TSynLog.Add.Log(sllInfo, '设备注册成功: % (%s)',
[DeviceCode, HotelName]);
end
else
begin
Result.Message := '注册失败,未插入任何记录';
end;
except
on E: Exception do
begin
Result.Message := '注册错误: ' + E.Message;
TSynLog.Add.Log(sllError, 'RegisterDevice 错误: %', [E.Message]);
end;
end;
end;
function TDeviceService.HealthCheck: Boolean;
begin
Result := False;
if not Assigned(fDatabase) then
Exit;
try
// 方法1:使用正确的参数(推荐)
var Rows := fDatabase.Execute('SELECT 1 as health', []);
Result := Rows.Step and (Rows.ColumnInt('health') = 1);
// 方法2:更简单的写法
// var Rows := fDatabase.Execute('SELECT 1', []);
// Result := Rows.Step;
if not Result then
TSynLog.Add.Log(sllWarning, '数据库健康检查失败');
except
on E: Exception do
begin
TSynLog.Add.Log(sllError, '健康检查异常: %', [E.Message]);
end;
end;
end;
function TDeviceService.GetDeviceCount: Integer;
begin
Result := 0;
if not Assigned(fDatabase) then
Exit;
try
var Rows := fDatabase.Execute('SELECT COUNT(*) as cnt FROM devices', []);
if Rows.Step then
Result := Rows.ColumnInt('cnt');
except
on E: Exception do
begin
TSynLog.Add.Log(sllError, '获取设备数量失败: %', [E.Message]);
end;
end;
end;
end.
DeviceAuthServer.dpr
program DeviceAuthServer;
{$APPTYPE CONSOLE}
uses
SysUtils,
mormot.core.base,
mormot.db.raw.sqlite3.static,
mormot.core.os,
mormot.core.log,
mormot.core.data,
mormot.db.core,
mormot.db.sql,
mormot.db.rad.unidac,
MySQLUniProvider,
Uni,
mormot.orm.core,
mormot.orm.sql,
mormot.rest.server,
mormot.rest.memserver,
mormot.rest.http.server,
mormot.core.interfaces,
mormot.rest.sqlite3,
DataModel in 'DataModel.pas',
AuthService in 'AuthService.pas';
// 全局变量
var
Server: TRestHttpServer;
Model: TOrmModel;
MySQLDB: TSqlDBConnectionProperties; // MySQL连接
RestServer: TRestServerDB;
DeviceService: TDeviceService;
procedure RegisterInterfaces;
begin
TInterfaceFactory.RegisterInterfaces([TypeInfo(IDeviceService)]);
end;
begin
try
// 1. 设置日志
with TSynLog.Family do
begin
Level := LOG_VERBOSE;
PerThreadLog := ptIdentifiedInOnFile;
HighResolutionTimestamp := True;
AutoFlushTimeOut := 10;
EchoToConsole := LOG_VERBOSE;
end;
TSynLog.Add.Log(sllInfo, '启动设备认证服务(MySQL直接连接)...');
// 2. 注册接口
RegisterInterfaces;
// 3. 定义数据模型
Model := CreateDataModel;
try
// 4. 使用 UniDAC 连接 MySQL 数据库(彻底摆脱 libmysql.dll)
TSynLog.Add.Log(sllInfo, '使用 UniDAC 连接 MySQL 数据库 device_db...');
MySQLDB := TSqlDBUniDACConnectionProperties.Create(
'MySQL?Server=localhost;Port=3306', // 这里指定服务器和端口
'device_db', // 数据库名
'root', // 用户名
'w123456'); // 密码
with TSqlDBUniDACConnectionProperties(MySQLDB) do
begin
SpecificOptions.Values['UseUnicode'] := 'True'; // 防止乱码
end;
try
// 5. 🔴 关键修改:不注册虚拟表,直接使用MySQL连接
// 6. 创建一个空的SQLite内存数据库(仅用于ORM框架)
// 我们不需要存储任何数据,只需要框架结构
RestServer := TRestServerDB.Create(Model, ':memory:', False, '', 0);
try
// 7. 创建设备服务并传入MySQL连接
DeviceService := TDeviceService.Create;
try
// 传入MySQL连接,而不是RestServer
DeviceService.SetDatabaseConnection(MySQLDB);
// 8. 注册服务
RestServer.ServiceDefine(DeviceService, [IDeviceService]);
// 9. 启动HTTP服务器
TSynLog.Add.Log(sllInfo, '启动HTTP服务器...');
Server := TRestHttpServer.Create('1897', [RestServer], '+', useHttpAsync, 32, secNone);
try
// 配置服务器
Server.AccessControlAllowOrigin := '*';
TSynLog.Add.Log(sllInfo, '═════════════════════════════════════════');
TSynLog.Add.Log(sllInfo, '设备认证服务启动成功!');
TSynLog.Add.Log(sllInfo, '数据库: MySQL - device_db');
TSynLog.Add.Log(sllInfo, '服务端口: 1897');
TSynLog.Add.Log(sllInfo, 'API地址: http://localhost:1897/root');
TSynLog.Add.Log(sllInfo, '按 [Enter] 退出...');
TSynLog.Add.Log(sllInfo, '═════════════════════════════════════════');
Readln;
finally
Server.Free;
TSynLog.Add.Log(sllInfo, 'HTTP服务器已停止');
end;
finally
// DeviceService由接口管理
end;
finally
RestServer.Free;
TSynLog.Add.Log(sllInfo, 'REST服务器已停止');
end;
finally
MySQLDB.Free;
TSynLog.Add.Log(sllInfo, 'MySQL连接已释放');
end;
finally
Model.Free;
end;
TSynLog.Add.Log(sllInfo, '设备认证服务已正常退出');
except
on E: Exception do
begin
TSynLog.Add.Log(sllError, '服务启动失败: %', [E.Message]);
Writeln('错误: ', E.ClassName, ': ', E.Message);
Writeln('按 [Enter] 退出...');
Readln;
end;
end;
end.