feat:设备日志模块

feat:定时任务
feat:设置页面用路由重构
This commit is contained in:
dzc committed 2026-07-30 13:16:14 +08:00
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,
};