feat:设备日志模块
feat:定时任务 feat:设置页面用路由重构
This commit is contained in:
1 parent
1033f403b1
commit
2efa7a3c19
60 files changed
+6560
-643
No files matched your search
@@ -0,0 +1,97 @@
|
||||
import 'package:floor/floor.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'file_upload_record_entity.g.dart';
|
||||
|
||||
/// 本机文件上传记录。
|
||||
///
|
||||
/// 目前 [bizType] 使用 `terminal_log`,保留业务类型后可复用到数据库备份等文件任务。
|
||||
@JsonSerializable()
|
||||
@Entity(
|
||||
tableName: 'file_upload_record',
|
||||
indices: [
|
||||
Index(value: ['hardId', 'bizType', 'relativePath'], unique: true),
|
||||
Index(value: ['bizType', 'status', 'updateTime']),
|
||||
],
|
||||
)
|
||||
class FileUploadRecordEntity {
|
||||
/// SQLite 自增主键;首次保存前为空。
|
||||
@PrimaryKey(autoGenerate: true)
|
||||
final int? id;
|
||||
|
||||
/// 终端绑定的设备 ID,用于隔离设备重新绑定前后的文件状态。
|
||||
final String hardId;
|
||||
|
||||
/// 文件业务类型;终端日志固定使用 `terminal_log`。
|
||||
final String bizType;
|
||||
|
||||
/// 文件在业务根目录下使用正斜杠的相对路径。
|
||||
final String relativePath;
|
||||
|
||||
/// 原始文件名,不包含目录。
|
||||
final String originalName;
|
||||
|
||||
/// 生成脱敏副本时读取的原文件快照大小,单位为字节。
|
||||
final int sourceSize;
|
||||
|
||||
/// 生成脱敏副本时原文件的最后修改时间。
|
||||
final DateTime sourceModifiedTime;
|
||||
|
||||
/// 脱敏后文件内容的 MD5 哈希;首次尝试尚未完成脱敏时可为空字符串。
|
||||
final String sanitizedHash;
|
||||
|
||||
/// 脱敏后文件大小,单位为字节;尚未完成脱敏时为零。
|
||||
final int sanitizedSize;
|
||||
|
||||
/// 最近一次上传尝试状态,保存 [FileUploadStatus.name]。
|
||||
final String status;
|
||||
|
||||
/// 后端 hard_terminal_log_file 记录 ID;预检判定无需上传且本机无旧记录时可为空。
|
||||
final String? serverFileId;
|
||||
|
||||
/// 对该路径发起过的上传尝试次数,预检前即递增。
|
||||
final int attemptCount;
|
||||
|
||||
/// 最近一次开始上传尝试的本地时间;从未尝试时为空。
|
||||
final DateTime? lastAttemptAt;
|
||||
|
||||
/// 最近一次被服务端确认成功或无需上传的本地时间。
|
||||
final DateTime? uploadedAt;
|
||||
|
||||
/// 最近一次失败的截断错误摘要;成功记录为空。
|
||||
final String? lastError;
|
||||
|
||||
/// 当前设备与业务类型下首次创建该路径记录的本地时间。
|
||||
final DateTime createTime;
|
||||
|
||||
/// 状态、快照或服务端 ID 最近一次更新的本地时间。
|
||||
final DateTime updateTime;
|
||||
|
||||
/// 创建一条文件上传状态记录。
|
||||
const FileUploadRecordEntity({
|
||||
this.id,
|
||||
required this.hardId,
|
||||
required this.bizType,
|
||||
required this.relativePath,
|
||||
required this.originalName,
|
||||
required this.sourceSize,
|
||||
required this.sourceModifiedTime,
|
||||
required this.sanitizedHash,
|
||||
required this.sanitizedSize,
|
||||
required this.status,
|
||||
this.serverFileId,
|
||||
this.attemptCount = 0,
|
||||
this.lastAttemptAt,
|
||||
this.uploadedAt,
|
||||
this.lastError,
|
||||
required this.createTime,
|
||||
required this.updateTime,
|
||||
});
|
||||
|
||||
/// 从 JSON 数据恢复上传记录,仅供调试序列化使用。
|
||||
factory FileUploadRecordEntity.fromJson(Map<String, dynamic> json) =>
|
||||
_$FileUploadRecordEntityFromJson(json);
|
||||
|
||||
/// 将上传记录转换为 JSON,仅供调试序列化使用。
|
||||
Map<String, dynamic> toJson() => _$FileUploadRecordEntityToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'file_upload_record_entity.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
FileUploadRecordEntity _$FileUploadRecordEntityFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
FileUploadRecordEntity(
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
hardId: json['hardId'] as String,
|
||||
bizType: json['bizType'] as String,
|
||||
relativePath: json['relativePath'] as String,
|
||||
originalName: json['originalName'] as String,
|
||||
sourceSize: (json['sourceSize'] as num).toInt(),
|
||||
sourceModifiedTime: DateTime.parse(json['sourceModifiedTime'] as String),
|
||||
sanitizedHash: json['sanitizedHash'] as String,
|
||||
sanitizedSize: (json['sanitizedSize'] as num).toInt(),
|
||||
status: json['status'] as String,
|
||||
serverFileId: json['serverFileId'] as String?,
|
||||
attemptCount: (json['attemptCount'] as num?)?.toInt() ?? 0,
|
||||
lastAttemptAt: json['lastAttemptAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['lastAttemptAt'] as String),
|
||||
uploadedAt: json['uploadedAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['uploadedAt'] as String),
|
||||
lastError: json['lastError'] as String?,
|
||||
createTime: DateTime.parse(json['createTime'] as String),
|
||||
updateTime: DateTime.parse(json['updateTime'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$FileUploadRecordEntityToJson(
|
||||
FileUploadRecordEntity instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'hardId': instance.hardId,
|
||||
'bizType': instance.bizType,
|
||||
'relativePath': instance.relativePath,
|
||||
'originalName': instance.originalName,
|
||||
'sourceSize': instance.sourceSize,
|
||||
'sourceModifiedTime': instance.sourceModifiedTime.toIso8601String(),
|
||||
'sanitizedHash': instance.sanitizedHash,
|
||||
'sanitizedSize': instance.sanitizedSize,
|
||||
'status': instance.status,
|
||||
'serverFileId': instance.serverFileId,
|
||||
'attemptCount': instance.attemptCount,
|
||||
'lastAttemptAt': instance.lastAttemptAt?.toIso8601String(),
|
||||
'uploadedAt': instance.uploadedAt?.toIso8601String(),
|
||||
'lastError': instance.lastError,
|
||||
'createTime': instance.createTime.toIso8601String(),
|
||||
'updateTime': instance.updateTime.toIso8601String(),
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'package:floor/floor.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'scheduled_task_entity.g.dart';
|
||||
|
||||
/// 终端定时任务配置及最近一次执行快照。
|
||||
///
|
||||
/// [taskCode] 使用代码枚举名作为稳定主键,界面文案调整时不会产生重复任务。
|
||||
@JsonSerializable()
|
||||
@Entity(tableName: 'scheduled_task')
|
||||
class ScheduledTaskEntity {
|
||||
/// 稳定任务编码,取 [ScheduledTaskType.name],作为表主键且不能随展示文案变化。
|
||||
@PrimaryKey()
|
||||
final String taskCode;
|
||||
|
||||
/// 是否启用 Cron 自动调度;关闭时仍可通过设置页手动触发。
|
||||
final bool enabled;
|
||||
|
||||
/// cron 0.6.2 使用的六段表达式,字段顺序为秒、分、时、日、月、周。
|
||||
final String cronExpression;
|
||||
|
||||
/// 自动任务是否必须等待连续 60 秒无人操作的完整空闲窗口。
|
||||
final bool idleOnly;
|
||||
|
||||
/// 最近一次任务开始执行的本地时间;从未执行时为空。
|
||||
final DateTime? lastRunAt;
|
||||
|
||||
/// 最近一次成功完成的本地时间;尚无成功记录时为空。
|
||||
final DateTime? lastSuccessAt;
|
||||
|
||||
/// 最近一次运行状态,保存 [ScheduledTaskRunStatus.name]。
|
||||
final String lastStatus;
|
||||
|
||||
/// 最近一次运行结果的简短中文摘要。
|
||||
final String lastMessage;
|
||||
|
||||
/// 任务配置首次写入 SQLite 的本地时间。
|
||||
final DateTime createTime;
|
||||
|
||||
/// 配置或执行快照最近一次更新的本地时间。
|
||||
final DateTime updateTime;
|
||||
|
||||
/// 创建一条定时任务配置与最近执行快照。
|
||||
const ScheduledTaskEntity({
|
||||
required this.taskCode,
|
||||
required this.enabled,
|
||||
required this.cronExpression,
|
||||
required this.idleOnly,
|
||||
this.lastRunAt,
|
||||
this.lastSuccessAt,
|
||||
required this.lastStatus,
|
||||
required this.lastMessage,
|
||||
required this.createTime,
|
||||
required this.updateTime,
|
||||
});
|
||||
|
||||
/// 从 JSON 数据恢复实体,仅供兼容迁移和调试序列化使用。
|
||||
factory ScheduledTaskEntity.fromJson(Map<String, dynamic> json) =>
|
||||
_$ScheduledTaskEntityFromJson(json);
|
||||
|
||||
/// 将实体转换为 JSON,仅供兼容迁移和调试序列化使用。
|
||||
Map<String, dynamic> toJson() => _$ScheduledTaskEntityToJson(this);
|
||||
|
||||
/// 保留主键与创建时间,生成包含配置或最近执行结果变更的新实体。
|
||||
ScheduledTaskEntity copyWith({
|
||||
bool? enabled,
|
||||
String? cronExpression,
|
||||
bool? idleOnly,
|
||||
DateTime? lastRunAt,
|
||||
DateTime? lastSuccessAt,
|
||||
String? lastStatus,
|
||||
String? lastMessage,
|
||||
DateTime? updateTime,
|
||||
}) =>
|
||||
ScheduledTaskEntity(
|
||||
taskCode: taskCode,
|
||||
enabled: enabled ?? this.enabled,
|
||||
cronExpression: cronExpression ?? this.cronExpression,
|
||||
idleOnly: idleOnly ?? this.idleOnly,
|
||||
lastRunAt: lastRunAt ?? this.lastRunAt,
|
||||
lastSuccessAt: lastSuccessAt ?? this.lastSuccessAt,
|
||||
lastStatus: lastStatus ?? this.lastStatus,
|
||||
lastMessage: lastMessage ?? this.lastMessage,
|
||||
createTime: createTime,
|
||||
updateTime: updateTime ?? this.updateTime,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'scheduled_task_entity.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ScheduledTaskEntity _$ScheduledTaskEntityFromJson(Map<String, dynamic> json) =>
|
||||
ScheduledTaskEntity(
|
||||
taskCode: json['taskCode'] as String,
|
||||
enabled: json['enabled'] as bool,
|
||||
cronExpression: json['cronExpression'] as String,
|
||||
idleOnly: json['idleOnly'] as bool,
|
||||
lastRunAt: json['lastRunAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['lastRunAt'] as String),
|
||||
lastSuccessAt: json['lastSuccessAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['lastSuccessAt'] as String),
|
||||
lastStatus: json['lastStatus'] as String,
|
||||
lastMessage: json['lastMessage'] as String,
|
||||
createTime: DateTime.parse(json['createTime'] as String),
|
||||
updateTime: DateTime.parse(json['updateTime'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ScheduledTaskEntityToJson(
|
||||
ScheduledTaskEntity instance) =>
|
||||
<String, dynamic>{
|
||||
'taskCode': instance.taskCode,
|
||||
'enabled': instance.enabled,
|
||||
'cronExpression': instance.cronExpression,
|
||||
'idleOnly': instance.idleOnly,
|
||||
'lastRunAt': instance.lastRunAt?.toIso8601String(),
|
||||
'lastSuccessAt': instance.lastSuccessAt?.toIso8601String(),
|
||||
'lastStatus': instance.lastStatus,
|
||||
'lastMessage': instance.lastMessage,
|
||||
'createTime': instance.createTime.toIso8601String(),
|
||||
'updateTime': instance.updateTime.toIso8601String(),
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
import 'package:floor/floor.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'scheduled_task_log_entity.g.dart';
|
||||
|
||||
/// 定时任务的结构化执行日志。
|
||||
///
|
||||
/// 来源、触发方式、状态和统计值使用独立列,日志页面无需解析拼接文本即可筛选。
|
||||
@JsonSerializable()
|
||||
@Entity(
|
||||
tableName: 'scheduled_task_log',
|
||||
indices: [
|
||||
Index(value: ['taskCode', 'startedAt']),
|
||||
Index(value: ['sourceType', 'status', 'startedAt']),
|
||||
],
|
||||
)
|
||||
class ScheduledTaskLogEntity {
|
||||
/// SQLite 自增主键;日志尚未插入数据库时为空。
|
||||
@PrimaryKey(autoGenerate: true)
|
||||
final int? id;
|
||||
|
||||
/// 任务稳定编码,关联 scheduled_task.taskCode,但不建立外键以兼容任务版本演进。
|
||||
final String taskCode;
|
||||
|
||||
/// 执行发生时的任务展示名称快照。
|
||||
final String taskName;
|
||||
|
||||
/// 触发来源,保存 [ScheduledTaskTriggerType.name]。
|
||||
final String triggerType;
|
||||
|
||||
/// 主要依赖类型,保存 [ScheduledTaskSourceType.name]。
|
||||
final String sourceType;
|
||||
|
||||
/// 执行发生时的依赖名称快照,例如 MQTT 消息服务或 HTTP 商品接口。
|
||||
final String sourceName;
|
||||
|
||||
/// 本次执行状态,保存 [ScheduledTaskRunStatus.name]。
|
||||
final String status;
|
||||
|
||||
/// 本次执行结果的简短中文摘要。
|
||||
final String message;
|
||||
|
||||
/// 异常运行时的 Dart 异常类型;成功或跳过时为空。
|
||||
final String? errorType;
|
||||
|
||||
/// 截断并脱敏后的异常摘要;成功或跳过时为空。
|
||||
final String? errorMessage;
|
||||
|
||||
/// 截断后的异常堆栈,最多保存调度器允许的诊断长度。
|
||||
final String? errorStack;
|
||||
|
||||
/// 任务开始执行的本地时间。
|
||||
final DateTime startedAt;
|
||||
|
||||
/// 任务结束的本地时间;执行中日志为空。
|
||||
final DateTime? finishedAt;
|
||||
|
||||
/// 从开始到结束的耗时,单位为毫秒;执行中默认为零。
|
||||
final int durationMs;
|
||||
|
||||
/// 文件类任务扫描到的文件数量;不适用的任务为空。
|
||||
final int? scannedCount;
|
||||
|
||||
/// 文件类任务因未变化或服务端已存在而跳过的数量;不适用时为空。
|
||||
final int? skippedCount;
|
||||
|
||||
/// 文件类任务实际上传成功的数量;不适用时为空。
|
||||
final int? successCount;
|
||||
|
||||
/// 文件类任务处理失败的数量;不适用时为空。
|
||||
final int? failureCount;
|
||||
|
||||
/// 创建一条任务执行日志;开始记录尚未结束时不填写结束信息。
|
||||
const ScheduledTaskLogEntity({
|
||||
this.id,
|
||||
required this.taskCode,
|
||||
required this.taskName,
|
||||
required this.triggerType,
|
||||
required this.sourceType,
|
||||
required this.sourceName,
|
||||
required this.status,
|
||||
required this.message,
|
||||
this.errorType,
|
||||
this.errorMessage,
|
||||
this.errorStack,
|
||||
required this.startedAt,
|
||||
this.finishedAt,
|
||||
this.durationMs = 0,
|
||||
this.scannedCount,
|
||||
this.skippedCount,
|
||||
this.successCount,
|
||||
this.failureCount,
|
||||
});
|
||||
|
||||
/// 从 JSON 数据恢复任务日志,仅供调试序列化使用。
|
||||
factory ScheduledTaskLogEntity.fromJson(Map<String, dynamic> json) =>
|
||||
_$ScheduledTaskLogEntityFromJson(json);
|
||||
|
||||
/// 将任务日志转换为 JSON,仅供调试序列化使用。
|
||||
Map<String, dynamic> toJson() => _$ScheduledTaskLogEntityToJson(this);
|
||||
|
||||
/// 使用最终状态、错误和统计值生成同一条日志的完成快照。
|
||||
ScheduledTaskLogEntity finish({
|
||||
required String nextStatus,
|
||||
required String nextMessage,
|
||||
required DateTime at,
|
||||
String? nextErrorType,
|
||||
String? nextErrorMessage,
|
||||
String? nextErrorStack,
|
||||
int? scanned,
|
||||
int? skipped,
|
||||
int? succeeded,
|
||||
int? failed,
|
||||
}) =>
|
||||
ScheduledTaskLogEntity(
|
||||
id: id,
|
||||
taskCode: taskCode,
|
||||
taskName: taskName,
|
||||
triggerType: triggerType,
|
||||
sourceType: sourceType,
|
||||
sourceName: sourceName,
|
||||
status: nextStatus,
|
||||
message: nextMessage,
|
||||
errorType: nextErrorType,
|
||||
errorMessage: nextErrorMessage,
|
||||
errorStack: nextErrorStack,
|
||||
startedAt: startedAt,
|
||||
finishedAt: at,
|
||||
durationMs: at.difference(startedAt).inMilliseconds,
|
||||
scannedCount: scanned,
|
||||
skippedCount: skipped,
|
||||
successCount: succeeded,
|
||||
failureCount: failed,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'scheduled_task_log_entity.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ScheduledTaskLogEntity _$ScheduledTaskLogEntityFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
ScheduledTaskLogEntity(
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
taskCode: json['taskCode'] as String,
|
||||
taskName: json['taskName'] as String,
|
||||
triggerType: json['triggerType'] as String,
|
||||
sourceType: json['sourceType'] as String,
|
||||
sourceName: json['sourceName'] as String,
|
||||
status: json['status'] as String,
|
||||
message: json['message'] as String,
|
||||
errorType: json['errorType'] as String?,
|
||||
errorMessage: json['errorMessage'] as String?,
|
||||
errorStack: json['errorStack'] as String?,
|
||||
startedAt: DateTime.parse(json['startedAt'] as String),
|
||||
finishedAt: json['finishedAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['finishedAt'] as String),
|
||||
durationMs: (json['durationMs'] as num?)?.toInt() ?? 0,
|
||||
scannedCount: (json['scannedCount'] as num?)?.toInt(),
|
||||
skippedCount: (json['skippedCount'] as num?)?.toInt(),
|
||||
successCount: (json['successCount'] as num?)?.toInt(),
|
||||
failureCount: (json['failureCount'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ScheduledTaskLogEntityToJson(
|
||||
ScheduledTaskLogEntity instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'taskCode': instance.taskCode,
|
||||
'taskName': instance.taskName,
|
||||
'triggerType': instance.triggerType,
|
||||
'sourceType': instance.sourceType,
|
||||
'sourceName': instance.sourceName,
|
||||
'status': instance.status,
|
||||
'message': instance.message,
|
||||
'errorType': instance.errorType,
|
||||
'errorMessage': instance.errorMessage,
|
||||
'errorStack': instance.errorStack,
|
||||
'startedAt': instance.startedAt.toIso8601String(),
|
||||
'finishedAt': instance.finishedAt?.toIso8601String(),
|
||||
'durationMs': instance.durationMs,
|
||||
'scannedCount': instance.scannedCount,
|
||||
'skippedCount': instance.skippedCount,
|
||||
'successCount': instance.successCount,
|
||||
'failureCount': instance.failureCount,
|
||||
};
|
||||
Reference in new issue
Block a user