用mormot2 orm模式搭建一个http服务验证设备的注册信息

我有个需要,获取电脑的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.
相关推荐
wearegogog1233 小时前
基于C#的FTP客户端实现方案
java·网络·c#
小白电脑技术3 小时前
网络进阶教程:仅部署一个中心节点,即可访问局域网内所有设备
网络
代码游侠3 小时前
学习笔记——写时复制(Copy-on-Write)
linux·网络·笔记·学习·写时复制
卓码软件测评3 小时前
第三方CMA/CNAS软件测评机构:【Apifox在Dubbo接口调试和RPC服务测试中的测试应用】
网络·测试工具·性能优化·测试用例
盛世宏博北京3 小时前
分布式库房集中管!云端 “八防” 监控平台,多站点统一可视化运维
大数据·网络·数据库·档案温湿度
北京耐用通信3 小时前
工程师实战:如何以最小成本,耐达讯自动化无缝连接Profinet转DeviceNet网关
人工智能·物联网·网络协议·自动化·信息与通信
老蒋新思维4 小时前
创客匠人:小团队的知识变现革命 —— 超级个体 + 多智能体如何重构组织价值
服务器·网络·人工智能·重构·创始人ip·创客匠人·知识变现
古城小栈4 小时前
Go mod 忽略指令:解决依赖冲突的核心技巧
网络·golang
福尔摩斯张4 小时前
基于TCP的FTP文件传输系统设计与实现(超详细)
linux·开发语言·网络·网络协议·tcp/ip·udp