Files
dyp/pas/uExceptionHandler.pas
T
2026-05-07 20:25:34 +08:00

342 lines
9.9 KiB
ObjectPascal

unit uExceptionHandler;
interface
uses
SysUtils, Classes, System.JSON, System.Threading, FMX.Forms;
type
EBaseException = class(Exception)
private
FErrorCode: Integer;
FErrorType: string;
FCause: string;
FContext: TStrings;
FTimestamp: TDateTime;
public
constructor Create(const AMessage: string; AErrorCode: Integer = 0; AErrorType: string = ''); overload;
constructor Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer = 0; AErrorType: string = ''); overload;
destructor Destroy; override;
function ToJSON: TJSONObject;
function ToDetailString: string;
property ErrorCode: Integer read FErrorCode write FErrorCode;
property ErrorType: string read FErrorType write FErrorType;
property Cause: string read FCause write FCause;
property Context: TStrings read FContext;
property Timestamp: TDateTime read FTimestamp;
end;
ENetworkException = class(EBaseException)
public
constructor Create(const AMessage: string; AErrorCode: Integer = 0); overload;
constructor Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer = 0); overload;
end;
EDataException = class(EBaseException)
public
constructor Create(const AMessage: string; AErrorCode: Integer = 0); overload;
constructor Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer = 0); overload;
end;
EBusinessException = class(EBaseException)
public
constructor Create(const AMessage: string; AErrorCode: Integer = 0); overload;
constructor Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer = 0); overload;
end;
EDeviceException = class(EBaseException)
public
constructor Create(const AMessage: string; AErrorCode: Integer = 0); overload;
constructor Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer = 0); overload;
end;
EValidationException = class(EBaseException)
private
FFieldName: string;
FFieldValue: string;
public
constructor Create(const AFieldName, AFieldValue, AMessage: string);
property FieldName: string read FFieldName write FFieldName;
property FieldValue: string read FFieldValue write FFieldValue;
end;
TExceptionHandler = class
private
class var
FInstance: TExceptionHandler;
var
FOnException: TProc<Exception, TObject>;
FLogEnabled: Boolean;
FShowDialogEnabled: Boolean;
FErrorPrefix: string;
class function GetInstance: TExceptionHandler; static;
public
constructor Create;
destructor Destroy; override;
procedure HandleException(E: Exception; Context: TObject = nil);
procedure HandleAndRethrow(E: Exception; const AdditionalInfo: string = '');
function SafeCall<T>(Func: TFunc<T>; const DefaultValue: T; const ErrorMsg: string = ''): T; overload;
procedure SafeCall(Func: TProc; const ErrorMsg: string = ''); overload;
property OnException: TProc<Exception, TObject> read FOnException write FOnException;
property LogEnabled: Boolean read FLogEnabled write FLogEnabled;
property ShowDialogEnabled: Boolean read FShowDialogEnabled write FShowDialogEnabled;
property ErrorPrefix: string read FErrorPrefix write FErrorPrefix;
class property Instance: TExceptionHandler read GetInstance;
class procedure SetAsDefaultInstance;
class procedure LogException(E: Exception; const Context: string = '');
class function GetExceptionDetail(E: Exception): string;
end;
procedure RaiseNetworkException(const AMsg: string; AErrorCode: Integer = 0);
procedure RaiseDataException(const AMsg: string; AErrorCode: Integer = 0);
procedure RaiseBusinessException(const AMsg: string; AErrorCode: Integer = 0);
procedure RaiseDeviceException(const AMsg: string; AErrorCode: Integer = 0);
procedure RaiseValidationException(const AFieldName, AFieldValue, AMsg: string);
implementation
uses
uSafeLog;
{ EBaseException }
constructor EBaseException.Create(const AMessage: string; AErrorCode: Integer; AErrorType: string);
begin
inherited Create(AMessage);
FErrorCode := AErrorCode;
FErrorType := AErrorType;
FCause := '';
FContext := TStringList.Create;
FTimestamp := Now;
end;
constructor EBaseException.Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer; AErrorType: string);
begin
Create(Format(AMessage, AArgs), AErrorCode, AErrorType);
end;
destructor EBaseException.Destroy;
begin
FContext.Free;
inherited;
end;
function EBaseException.ToJSON: TJSONObject;
begin
Result := TJSONObject.Create;
Result.AddPair('message', Message);
Result.AddPair('errorCode', TJSONNumber.Create(FErrorCode));
Result.AddPair('errorType', FErrorType);
Result.AddPair('cause', FCause);
Result.AddPair('timestamp', FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz', FTimestamp));
end;
function EBaseException.ToDetailString: string;
var
I: Integer;
begin
Result := Format('[%s] %s (Code: %d, Type: %s)',
[FormatDateTime('yyyy-mm-dd hh:nn:ss', FTimestamp), Message, FErrorCode, FErrorType]);
if FCause <> '' then
Result := Result + sLineBreak + 'Cause: ' + FCause;
if FContext.Count > 0 then
begin
Result := Result + sLineBreak + 'Context:';
for I := 0 to FContext.Count - 1 do
Result := Result + sLineBreak + ' ' + FContext.Names[I] + ' = ' + FContext.ValueFromIndex[I];
end;
end;
{ ENetworkException }
constructor ENetworkException.Create(const AMessage: string; AErrorCode: Integer);
begin
inherited Create(AMessage, AErrorCode, 'Network');
end;
constructor ENetworkException.Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer);
begin
inherited Create(Format(AMessage, AArgs), AErrorCode, 'Network');
end;
{ EDataException }
constructor EDataException.Create(const AMessage: string; AErrorCode: Integer);
begin
inherited Create(AMessage, AErrorCode, 'Data');
end;
constructor EDataException.Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer);
begin
inherited Create(Format(AMessage, AArgs), AErrorCode, 'Data');
end;
{ EBusinessException }
constructor EBusinessException.Create(const AMessage: string; AErrorCode: Integer);
begin
inherited Create(AMessage, AErrorCode, 'Business');
end;
constructor EBusinessException.Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer);
begin
inherited Create(Format(AMessage, AArgs), AErrorCode, 'Business');
end;
{ EDeviceException }
constructor EDeviceException.Create(const AMessage: string; AErrorCode: Integer);
begin
inherited Create(AMessage, AErrorCode, 'Device');
end;
constructor EDeviceException.Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer);
begin
inherited Create(Format(AMessage, AArgs), AErrorCode, 'Device');
end;
{ EValidationException }
constructor EValidationException.Create(const AFieldName, AFieldValue, AMessage: string);
begin
inherited Create(AMessage, -1, 'Validation');
FFieldName := AFieldName;
FFieldValue := AFieldValue;
end;
{ TExceptionHandler }
constructor TExceptionHandler.Create;
begin
inherited;
FLogEnabled := True;
FShowDialogEnabled := True;
FErrorPrefix := '[Error]';
end;
destructor TExceptionHandler.Destroy;
begin
inherited;
end;
class function TExceptionHandler.GetInstance: TExceptionHandler;
begin
if FInstance = nil then
FInstance := TExceptionHandler.Create;
Result := FInstance;
end;
procedure TExceptionHandler.HandleException(E: Exception; Context: TObject);
begin
if FLogEnabled then
LogException(E);
if FShowDialogEnabled and Assigned(FOnException) then
FOnException(E, Context);
end;
procedure TExceptionHandler.HandleAndRethrow(E: Exception; const AdditionalInfo: string);
var
Ex: EBaseException;
begin
if E is EBaseException then
begin
Ex := E as EBaseException;
if AdditionalInfo <> '' then
Ex.Cause := AdditionalInfo;
raise Ex;
end
else
begin
Ex := EBusinessException.Create(E.Message);
if AdditionalInfo <> '' then
Ex.Cause := AdditionalInfo;
raise Ex;
end;
end;
function TExceptionHandler.SafeCall<T>(Func: TFunc<T>; const DefaultValue: T; const ErrorMsg: string): T;
begin
try
Result := Func;
except
on E: Exception do
begin
if ErrorMsg <> '' then
LogException(E, ErrorMsg)
else
LogException(E);
Result := DefaultValue;
end;
end;
end;
procedure TExceptionHandler.SafeCall(Func: TProc; const ErrorMsg: string);
begin
try
Func;
except
on E: Exception do
begin
if ErrorMsg <> '' then
LogException(E, ErrorMsg)
else
LogException(E);
end;
end;
end;
class procedure TExceptionHandler.SetAsDefaultInstance;
begin
if FInstance = nil then
FInstance := TExceptionHandler.Create;
end;
class procedure TExceptionHandler.LogException(E: Exception; const Context: string);
begin
if not Assigned(FInstance) then
Exit;
try
if Context <> '' then
WorkLog.Error('[Context: %s] %s: %s', [Context, E.ClassName, E.Message])
else
WorkLog.Error('%s: %s', [E.ClassName, E.Message]);
except
end;
end;
class function TExceptionHandler.GetExceptionDetail(E: Exception): string;
begin
if E is EBaseException then
Result := (E as EBaseException).ToDetailString
else
Result := Format('[%s] %s', [E.ClassName, E.Message]);
end;
procedure RaiseNetworkException(const AMsg: string; AErrorCode: Integer);
begin
raise ENetworkException.Create(AMsg, AErrorCode);
end;
procedure RaiseDataException(const AMsg: string; AErrorCode: Integer);
begin
raise EDataException.Create(AMsg, AErrorCode);
end;
procedure RaiseBusinessException(const AMsg: string; AErrorCode: Integer);
begin
raise EBusinessException.Create(AMsg, AErrorCode);
end;
procedure RaiseDeviceException(const AMsg: string; AErrorCode: Integer);
begin
raise EDeviceException.Create(AMsg, AErrorCode);
end;
procedure RaiseValidationException(const AFieldName, AFieldValue, AMsg: string);
begin
raise EValidationException.Create(AFieldName, AFieldValue, AMsg);
end;
end.