Compare commits
8 Commits
efce651809
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a0c9b06ee | ||
|
|
5557ee0d3e | ||
|
|
50477ccf5d | ||
|
|
8778141154 | ||
|
|
e9c4582e0d | ||
|
|
194b062bb9 | ||
|
|
7f86327a96 | ||
|
|
8142887644 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,6 +10,8 @@
|
|||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
.swiftpm/
|
.swiftpm/
|
||||||
|
linux_app/
|
||||||
|
windows_app/
|
||||||
migrate_working_dir/
|
migrate_working_dir/
|
||||||
|
|
||||||
# IntelliJ related
|
# IntelliJ related
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -1,4 +1,4 @@
|
|||||||
# 云算力平台客户端 - Windows 桌面应用
|
# 云算力平台客户端 - Windows / Linux 桌面应用
|
||||||
|
|
||||||
基于 Flutter 开发的 Windows 桌面客户端应用,实现与云算力平台的通信、挖矿管理等功能。
|
基于 Flutter 开发的 Windows 桌面客户端应用,实现与云算力平台的通信、挖矿管理等功能。
|
||||||
|
|
||||||
@@ -11,12 +11,13 @@
|
|||||||
- **版本号**:从 `bin/version` 文件读取
|
- **版本号**:从 `bin/version` 文件读取
|
||||||
- **身份信息**:从 `bin/auth` 文件读取
|
- **身份信息**:从 `bin/auth` 文件读取
|
||||||
- **GPU 信息**:通过 `nvidia-smi` 命令自动检测(启动时获取一次)
|
- **GPU 信息**:通过 `nvidia-smi` 命令自动检测(启动时获取一次)
|
||||||
- 显示 GPU 索引、品牌、型号、显存大小
|
- 显示 GPU索引、品牌、型号、显存大小
|
||||||
- **硬盘身份码**:通过 `wmic diskdrive get serialnumber` 命令获取
|
- **硬盘身份码**:通过 `wmic diskdrive get serialnumber` 命令获取
|
||||||
- **当前状态**:实时显示客户端连接状态
|
- **当前状态**:实时显示客户端连接状态:
|
||||||
- 🔴 **离线**:心跳异常,红色指示
|
- 🔴 **离线**:未连接服务器,红色指示
|
||||||
- 🟢 **在线**:心跳正常,绿色指示
|
- 🟢 **在线**:已连接服务器,绿色指示
|
||||||
- 🟡 **挖矿中**:挖矿程序运行中,黄色指示
|
- 🟡 **挖矿中**:租约挖矿进行中,黄色指示
|
||||||
|
- 🔵 **持续挖矿中**:持续挖矿任务进行中,蓝色指示
|
||||||
|
|
||||||
### 2. 版本更新功能
|
### 2. 版本更新功能
|
||||||
|
|
||||||
@@ -79,15 +80,15 @@
|
|||||||
- **SystemInfoService**:系统信息获取(GPU、硬盘序列号等)
|
- **SystemInfoService**:系统信息获取(GPU、硬盘序列号等)
|
||||||
- **ConfigService**:配置文件管理(INI 格式)
|
- **ConfigService**:配置文件管理(INI 格式)
|
||||||
- **UpdateService**:版本检查和更新管理
|
- **UpdateService**:版本检查和更新管理
|
||||||
- **DatabaseService**:SQLite 数据库,存储挖矿任务历史
|
- **DatabaseService**:挖矿任务日志管理(基于 `bin/mining_tasks.log` 的 JSON 行记录,用于保存 / 恢复当前或最近一次挖矿任务)
|
||||||
- **LogService**:日志文件管理
|
- **LogService**:客户端日志文件管理
|
||||||
|
|
||||||
### 数据存储
|
### 数据存储
|
||||||
|
|
||||||
- **配置文件**:`bin/mining.windows.conf`(INI 格式)
|
- **配置文件**:`bin/mining.windows.conf`(INI 格式)
|
||||||
- **身份文件**:`bin/auth`
|
- **身份文件**:`bin/auth`
|
||||||
- **版本文件**:`bin/version`
|
- **版本文件**:`bin/version`
|
||||||
- **数据库**:`bin/mining_task.db`(SQLite)
|
- **挖矿任务日志**:`bin/mining_tasks.log`(JSON 行格式,仅记录当前 / 最近任务,用于断线恢复)
|
||||||
- **日志文件**:`bin/logs/client.log`
|
- **日志文件**:`bin/logs/client.log`
|
||||||
|
|
||||||
### 通信协议
|
### 通信协议
|
||||||
|
|||||||
19
UPDATE.md
19
UPDATE.md
@@ -4,6 +4,25 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 2026-01-29
|
||||||
|
|
||||||
|
- **挖矿任务持久化重构(替换 SQLite 为本地日志文件)**
|
||||||
|
- 移除 `sqflite_common_ffi` 及 SQLite 依赖,避免在 Windows / Linux 环境下对系统 `libsqlite3` 的安装要求。
|
||||||
|
- `DatabaseService` 改为基于 `bin/mining_tasks.log` 的 JSON 行存储:
|
||||||
|
- 新挖矿任务创建时追加写入 `.log`;
|
||||||
|
- 挖矿任务完成后,从 `.log` 中删除对应记录;
|
||||||
|
- 客户端启动时读取 `.log`,仅保留未过期任务,并自动恢复最新一条未完成的挖矿任务。
|
||||||
|
|
||||||
|
- **退出流程优化**
|
||||||
|
- 新增 `ClientProvider.shutdown()`,在点击“退出程序”时:
|
||||||
|
- 停止与服务器的连接和心跳;
|
||||||
|
- 停止当前挖矿进程和持续挖矿任务;
|
||||||
|
- 关闭自动刷新定时器,确保退出后不会残留后台矿工进程。
|
||||||
|
|
||||||
|
- **文档与多平台说明**
|
||||||
|
- README 中补充了“持续挖矿中”状态标识(蓝色指示灯)及 `bin/mining_tasks.log` 的作用说明。
|
||||||
|
- 增加 Linux 构建脚本(`build_linux.sh`)和运行脚本(`start_linux_app.sh`)的使用说明,支持在 Linux 环境下一键安装依赖并运行客户端。
|
||||||
|
|
||||||
### 2026-01-23
|
### 2026-01-23
|
||||||
|
|
||||||
- **网络与构建相关**
|
- **网络与构建相关**
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#请确认您的主机上安装了下列挖矿软件,确认后可以打开注释,并修改其路径,如果没有安装,请勿打开注释
|
#请确认您的主机上安装了下列挖矿软件,确认后可以打开注释,并修改其路径,如果没有安装,请勿打开注释
|
||||||
#请使用双\\,否则可能无法解析出准确的路径
|
#请使用双\\,否则可能无法解析出准确的路径
|
||||||
|
|
||||||
[client]
|
# [client]
|
||||||
server_url=18.183.240.108:2345
|
# server_url=18.183.240.108:2345
|
||||||
update_url=https://test.m2pool.com/api/lease
|
# update_url=https://test.m2pool.com/api/lease
|
||||||
|
|
||||||
#请确认您的主机上安装了下列挖矿软件,确认后可以打开注释,并修改其路径,如果没有安装,请勿打开注释
|
#请确认您的主机上安装了下列挖矿软件,确认后可以打开注释,并修改其路径,如果没有安装,请勿打开注释
|
||||||
#请使用双\\,否则可能无法解析出准确的路径
|
#请使用双\\,否则可能无法解析出准确的路径
|
||||||
|
|||||||
193
build_linux.sh
Normal file
193
build_linux.sh
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# 配置:修改为你的项目路径
|
||||||
|
#############################################
|
||||||
|
# 方式1:手动指定路径(如果脚本不在项目根目录)
|
||||||
|
# PROJECT_DIR="/home/lizixuan/linux_client/windows-application"
|
||||||
|
|
||||||
|
# 方式2:自动检测(如果脚本放在项目根目录,推荐)
|
||||||
|
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# 0. 打印环境信息
|
||||||
|
#############################################
|
||||||
|
echo "==== Linux Flutter 桌面环境初始化 & 构建脚本 ===="
|
||||||
|
echo "项目路径: $PROJECT_DIR"
|
||||||
|
echo "当前用户: $(whoami)"
|
||||||
|
echo "当前目录: $(pwd)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# 1. 安装系统依赖(Ubuntu / Debian)
|
||||||
|
#############################################
|
||||||
|
echo "==== 1. 安装系统依赖(需要 sudo) ===="
|
||||||
|
|
||||||
|
sudo apt update
|
||||||
|
|
||||||
|
# 基本编译工具
|
||||||
|
sudo apt install -y build-essential
|
||||||
|
|
||||||
|
# CMake / Ninja(Flutter 构建需要)
|
||||||
|
sudo apt install -y cmake ninja-build
|
||||||
|
|
||||||
|
# GTK3 及相关开发包(Flutter Linux GUI 需要)
|
||||||
|
sudo apt install -y libgtk-3-dev libblkid-dev liblzma-dev
|
||||||
|
|
||||||
|
# SQLite 库(sqflite_common_ffi 需要)
|
||||||
|
sudo apt install -y libsqlite3-dev
|
||||||
|
|
||||||
|
# 常用工具
|
||||||
|
sudo apt install -y git curl unzip
|
||||||
|
|
||||||
|
echo "系统依赖安装完成"
|
||||||
|
echo
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# 2. 安装 Flutter SDK(如果还没有)
|
||||||
|
#############################################
|
||||||
|
echo "==== 2. 检查 Flutter 是否已安装 ===="
|
||||||
|
|
||||||
|
if ! command -v flutter >/dev/null 2>&1; then
|
||||||
|
echo "未检测到 flutter 命令,开始安装 Flutter SDK..."
|
||||||
|
|
||||||
|
FLUTTER_VERSION="3.16.0" # 可按需要调整版本
|
||||||
|
FLUTTER_TAR="flutter_linux_${FLUTTER_VERSION}-stable.tar.xz"
|
||||||
|
FLUTTER_URL="https://storage.flutter-io.cn/flutter_infra_release/releases/stable/linux/${FLUTTER_TAR}"
|
||||||
|
|
||||||
|
cd "$HOME"
|
||||||
|
echo "从中国镜像下载 Flutter: $FLUTTER_URL"
|
||||||
|
curl -O "$FLUTTER_URL"
|
||||||
|
tar xf "$FLUTTER_TAR"
|
||||||
|
|
||||||
|
# 写入 PATH(如果已经写入过,不会有问题)
|
||||||
|
if ! grep -q "flutter/bin" "$HOME/.bashrc"; then
|
||||||
|
echo 'export PATH="$PATH:$HOME/flutter/bin"' >> "$HOME/.bashrc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 立刻生效当前 shell
|
||||||
|
export PATH="$PATH:$HOME/flutter/bin"
|
||||||
|
|
||||||
|
echo "Flutter 安装完成,版本:$(flutter --version)"
|
||||||
|
else
|
||||||
|
echo "已检测到 Flutter:$(flutter --version)"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# 3. 配置 Flutter 国内镜像
|
||||||
|
#############################################
|
||||||
|
echo "==== 3. 配置 Flutter 国内镜像环境变量 ===="
|
||||||
|
|
||||||
|
export PUB_HOSTED_URL="https://pub.flutter-io.cn"
|
||||||
|
export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
|
||||||
|
|
||||||
|
# 写入 ~/.bashrc 方便下次登录继续使用
|
||||||
|
if ! grep -q "PUB_HOSTED_URL" "$HOME/.bashrc"; then
|
||||||
|
cat >> "$HOME/.bashrc" << 'EOF'
|
||||||
|
|
||||||
|
# Flutter China mirrors
|
||||||
|
export PUB_HOSTED_URL="https://pub.flutter-io.cn"
|
||||||
|
export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "国内镜像已配置(当前 shell 已生效)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# 4. 启用 Linux 桌面支持
|
||||||
|
#############################################
|
||||||
|
echo "==== 4. 启用 Linux 桌面支持 ===="
|
||||||
|
|
||||||
|
flutter config --enable-linux-desktop
|
||||||
|
|
||||||
|
echo "flutter doctor 检查环境:"
|
||||||
|
flutter doctor -v || true
|
||||||
|
echo
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# 5. 进入项目,生成 linux 工程(如未生成)
|
||||||
|
#############################################
|
||||||
|
echo "==== 5. 切换到项目目录,并生成 linux 工程(如需要) ===="
|
||||||
|
|
||||||
|
if [ ! -d "$PROJECT_DIR" ]; then
|
||||||
|
echo "错误:项目目录不存在:$PROJECT_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
# 如果没有 linux 目录,则创建
|
||||||
|
if [ ! -d "linux" ]; then
|
||||||
|
echo "未检测到 linux 目录,执行 flutter create --platforms=linux ."
|
||||||
|
|
||||||
|
# 从 pubspec.yaml 读取项目名(如果存在)
|
||||||
|
PROJECT_NAME="cloud_client_gui"
|
||||||
|
if [ -f "pubspec.yaml" ]; then
|
||||||
|
# 尝试从 pubspec.yaml 提取 name 字段
|
||||||
|
EXTRACTED_NAME=$(grep -E "^name:" pubspec.yaml | head -1 | sed 's/name:[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
||||||
|
if [ -n "$EXTRACTED_NAME" ]; then
|
||||||
|
PROJECT_NAME="$EXTRACTED_NAME"
|
||||||
|
echo "从 pubspec.yaml 读取到项目名: $PROJECT_NAME"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 使用 --project-name 参数明确指定项目名,避免目录名问题
|
||||||
|
echo "使用项目名: $PROJECT_NAME 创建 linux 平台..."
|
||||||
|
if flutter create --platforms=linux . --project-name "$PROJECT_NAME"; then
|
||||||
|
echo "linux 平台创建成功"
|
||||||
|
else
|
||||||
|
echo "警告: flutter create 执行失败"
|
||||||
|
echo "尝试不带 --project-name 参数重新执行..."
|
||||||
|
flutter create --platforms=linux . || {
|
||||||
|
echo "错误: 无法创建 linux 平台,请检查错误信息"
|
||||||
|
echo "可以尝试手动执行: flutter create --platforms=linux . --project-name cloud_client_gui"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "已检测到 linux 目录,跳过 flutter create。"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# 6. 获取依赖
|
||||||
|
#############################################
|
||||||
|
echo "==== 6. 获取 Dart / Flutter 依赖 ===="
|
||||||
|
|
||||||
|
flutter pub get
|
||||||
|
echo
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# 7. 构建 Linux Release 版本
|
||||||
|
#############################################
|
||||||
|
echo "==== 7. 构建 Linux Release 版本 ===="
|
||||||
|
|
||||||
|
flutter build linux --release
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==== 构建完成 ===="
|
||||||
|
echo "可执行文件及运行目录位于:"
|
||||||
|
echo " $PROJECT_DIR/build/linux/x64/release/bundle/"
|
||||||
|
echo
|
||||||
|
echo "请确保将项目中的 bin/ 目录复制到该 bundle 目录同级,例如:"
|
||||||
|
echo " build/linux/x64/release/bundle/"
|
||||||
|
echo " ├─ cloud_client_gui"
|
||||||
|
echo " ├─ bin/"
|
||||||
|
echo " │ ├─ version"
|
||||||
|
echo " │ ├─ auth"
|
||||||
|
echo " │ ├─ mining.linux.conf"
|
||||||
|
echo " │ ├─ logs/"
|
||||||
|
echo " │ └─ mining_task.db"
|
||||||
|
echo
|
||||||
|
echo "然后在该目录下运行:"
|
||||||
|
echo " ./cloud_client_gui"
|
||||||
|
echo
|
||||||
|
echo "==== 运行时依赖说明 ===="
|
||||||
|
echo "如果运行时提示找不到 libsqlite3.so,请确保已安装:"
|
||||||
|
echo " sudo apt install -y libsqlite3-dev"
|
||||||
|
echo "或者确保系统已安装 libsqlite3.so(通常在 /usr/lib/x86_64-linux-gnu/)"
|
||||||
|
echo
|
||||||
|
echo "全部步骤完成。"
|
||||||
@@ -1,74 +1,55 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
// import 'dart:io';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
|
||||||
import 'mining_task_info.dart';
|
import 'mining_task_info.dart';
|
||||||
import '../utils/path_utils.dart';
|
import '../utils/path_utils.dart';
|
||||||
|
|
||||||
/// 数据库管理服务
|
/// 挖矿任务持久化服务(基于 .log 文件,而非 SQLite)
|
||||||
|
///
|
||||||
|
/// 设计约定:
|
||||||
|
/// - 使用 `bin/mining_tasks.log` 记录当前(或最近)挖矿任务,一行一条 JSON 记录。
|
||||||
|
/// - 每次收到新的挖矿任务时,追加一条记录。
|
||||||
|
/// - 挖矿任务完成后,从 .log 中删除对应记录。
|
||||||
|
/// - 客户端启动时读取 .log:
|
||||||
|
/// - 如果存在任务且 `endTimestamp` 尚未过期,则恢复该任务;
|
||||||
|
/// - 如果已过期,则删除该记录,并不恢复。
|
||||||
class DatabaseService {
|
class DatabaseService {
|
||||||
static final DatabaseService _instance = DatabaseService._internal();
|
static final DatabaseService _instance = DatabaseService._internal();
|
||||||
factory DatabaseService() => _instance;
|
factory DatabaseService() => _instance;
|
||||||
DatabaseService._internal();
|
DatabaseService._internal();
|
||||||
|
|
||||||
final Logger _logger = Logger('DatabaseService');
|
final Logger _logger = Logger('DatabaseService');
|
||||||
Database? _database;
|
|
||||||
|
|
||||||
/// 初始化数据库
|
/// 日志文件路径:bin/mining_tasks.log
|
||||||
|
File get _logFile => File(PathUtils.binFile('mining_tasks.log'));
|
||||||
|
|
||||||
|
/// 初始化(确保 bin 目录存在)
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
try {
|
try {
|
||||||
// Windows/Desktop: 使用 sqflite_common_ffi
|
// 确保 bin 目录存在
|
||||||
sqfliteFfiInit();
|
final binDir = Directory(PathUtils.binDir);
|
||||||
databaseFactory = databaseFactoryFfi;
|
if (!await binDir.exists()) {
|
||||||
|
await binDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
// 与 Go 版一致,尽量落到 ./bin/mining_task.db
|
// 日志文件可懒创建,不强制在这里创建
|
||||||
final dbPath = PathUtils.binFile('mining_task.db');
|
_logger.info('任务日志初始化完成(使用文件存储,不再使用 SQLite)');
|
||||||
|
|
||||||
_database = await databaseFactory.openDatabase(
|
|
||||||
dbPath,
|
|
||||||
options: OpenDatabaseOptions(
|
|
||||||
version: 1,
|
|
||||||
onCreate: (db, version) async {
|
|
||||||
await db.execute('''
|
|
||||||
CREATE TABLE mining_tasks (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
coin TEXT NOT NULL,
|
|
||||||
algo TEXT NOT NULL,
|
|
||||||
pool TEXT NOT NULL,
|
|
||||||
pool_url TEXT NOT NULL,
|
|
||||||
wallet_address TEXT NOT NULL,
|
|
||||||
worker_id TEXT NOT NULL,
|
|
||||||
pool_user TEXT,
|
|
||||||
wallet_mining INTEGER NOT NULL,
|
|
||||||
end_timestamp INTEGER NOT NULL,
|
|
||||||
miner TEXT NOT NULL,
|
|
||||||
status TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
''');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
_logger.info('数据库初始化成功');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logger.severe('数据库初始化失败: $e');
|
_logger.severe('任务日志初始化失败: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 插入挖矿任务
|
/// 插入挖矿任务(在 .log 文件中追加一条记录)
|
||||||
Future<int> insertMiningTask(MiningTaskInfo task) async {
|
Future<int> insertMiningTask(MiningTaskInfo task) async {
|
||||||
if (_database == null) {
|
|
||||||
await initialize();
|
await initialize();
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
return await _database!.insert(
|
final record = <String, dynamic>{
|
||||||
'mining_tasks',
|
|
||||||
{
|
|
||||||
'coin': task.coin,
|
'coin': task.coin,
|
||||||
'algo': task.algo,
|
'algo': task.algo,
|
||||||
'pool': task.pool,
|
'pool': task.pool,
|
||||||
@@ -76,110 +57,184 @@ class DatabaseService {
|
|||||||
'wallet_address': task.walletAddress,
|
'wallet_address': task.walletAddress,
|
||||||
'worker_id': task.workerId,
|
'worker_id': task.workerId,
|
||||||
'pool_user': task.poolUser,
|
'pool_user': task.poolUser,
|
||||||
'wallet_mining': task.walletMining ? 1 : 0,
|
'wallet_mining': task.walletMining,
|
||||||
'end_timestamp': task.endTimestamp,
|
'end_timestamp': task.endTimestamp,
|
||||||
'miner': task.miner,
|
'miner': task.miner,
|
||||||
'status': 'running',
|
|
||||||
'created_at': now,
|
'created_at': now,
|
||||||
'updated_at': now,
|
};
|
||||||
},
|
|
||||||
);
|
final jsonLine = jsonEncode(record);
|
||||||
|
await _logFile.writeAsString('$jsonLine\n', mode: FileMode.append, flush: true);
|
||||||
|
|
||||||
|
return 1; // 返回值目前未被使用,保持兼容即可
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logger.severe('插入挖矿任务失败: $e');
|
_logger.severe('插入挖矿任务失败: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 完成挖矿任务
|
/// 挖矿任务完成后,从 .log 文件中删除该任务
|
||||||
Future<void> finishMiningTask(int taskId) async {
|
Future<void> finishMiningTask(MiningTaskInfo task) async {
|
||||||
if (_database == null) {
|
|
||||||
await initialize();
|
await initialize();
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
if (!await _logFile.exists()) {
|
||||||
await _database!.update(
|
return;
|
||||||
'mining_tasks',
|
}
|
||||||
{
|
|
||||||
'status': 'finished',
|
final lines = await _logFile.readAsLines();
|
||||||
'updated_at': now,
|
if (lines.isEmpty) return;
|
||||||
},
|
|
||||||
where: 'id = ?',
|
final List<String> keptLines = [];
|
||||||
whereArgs: [taskId],
|
|
||||||
);
|
for (final line in lines) {
|
||||||
|
if (line.trim().isEmpty) continue;
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> data = jsonDecode(line) as Map<String, dynamic>;
|
||||||
|
final existing = _taskFromJson(data);
|
||||||
|
|
||||||
|
// 如果与当前任务匹配,则跳过(即删除)
|
||||||
|
if (_isSameTask(existing, task)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
keptLines.add(line);
|
||||||
|
} catch (_) {
|
||||||
|
// 解析失败的行保留,避免误删
|
||||||
|
keptLines.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _logFile.writeAsString(keptLines.join('\n') + (keptLines.isEmpty ? '' : '\n'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logger.severe('完成挖矿任务失败: $e');
|
_logger.severe('完成挖矿任务(从日志中删除)失败: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 加载未完成的挖矿任务
|
/// 加载未完成的挖矿任务
|
||||||
|
///
|
||||||
|
/// - 读取 .log 中所有任务;
|
||||||
|
/// - 删除已过期(endTimestamp <= now)的任务;
|
||||||
|
/// - 返回最新的、尚未过期的任务(如果有)。
|
||||||
Future<MiningTaskInfo?> loadUnfinishedTask() async {
|
Future<MiningTaskInfo?> loadUnfinishedTask() async {
|
||||||
if (_database == null) {
|
|
||||||
await initialize();
|
await initialize();
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final results = await _database!.query(
|
if (!await _logFile.exists()) {
|
||||||
'mining_tasks',
|
|
||||||
where: 'status = ?',
|
|
||||||
whereArgs: ['running'],
|
|
||||||
orderBy: 'created_at DESC',
|
|
||||||
limit: 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (results.isEmpty) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final row = results.first;
|
final lines = await _logFile.readAsLines();
|
||||||
final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
if (lines.isEmpty) {
|
||||||
final endTimestamp = row['end_timestamp'] as int;
|
|
||||||
|
|
||||||
if (currentTime >= endTimestamp) {
|
|
||||||
// 任务已过期,标记为完成
|
|
||||||
await finishMiningTask(row['id'] as int);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MiningTaskInfo(
|
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
coin: row['coin'] as String,
|
final List<_StoredTask> validTasks = [];
|
||||||
algo: row['algo'] as String,
|
|
||||||
pool: row['pool'] as String,
|
for (final line in lines) {
|
||||||
poolUrl: row['pool_url'] as String,
|
if (line.trim().isEmpty) continue;
|
||||||
walletAddress: row['wallet_address'] as String,
|
try {
|
||||||
workerId: row['worker_id'] as String,
|
final Map<String, dynamic> data = jsonDecode(line) as Map<String, dynamic>;
|
||||||
poolUser: row['pool_user'] as String?,
|
final task = _taskFromJson(data);
|
||||||
walletMining: (row['wallet_mining'] as int) == 1,
|
final createdAt = (data['created_at'] as int?) ?? task.endTimestamp;
|
||||||
endTimestamp: endTimestamp,
|
|
||||||
miner: row['miner'] as String,
|
// 过滤掉已过期任务
|
||||||
);
|
if (now >= task.endTimestamp) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
validTasks.add(_StoredTask(task: task, createdAt: createdAt));
|
||||||
|
} catch (e) {
|
||||||
|
_logger.warning('解析任务日志行失败,已跳过: $e, 原始行: $line');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新写回仅包含未过期的任务
|
||||||
|
if (validTasks.isEmpty) {
|
||||||
|
await _logFile.writeAsString('');
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
// 按创建时间排序,取最新一条作为恢复任务
|
||||||
|
validTasks.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||||
|
final toKeep = validTasks;
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
for (final t in toKeep) {
|
||||||
|
final record = _taskToJson(t.task, createdAt: t.createdAt);
|
||||||
|
buffer.writeln(jsonEncode(record));
|
||||||
|
}
|
||||||
|
await _logFile.writeAsString(buffer.toString());
|
||||||
|
|
||||||
|
return validTasks.first.task;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logger.severe('加载挖矿任务失败: $e');
|
_logger.severe('加载挖矿任务失败: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取任务历史
|
/// 获取任务历史(目前基于 .log 仅保存“当前/最近”任务,这里返回空列表以保持接口兼容)
|
||||||
Future<List<Map<String, dynamic>>> getTaskHistory({int limit = 100}) async {
|
Future<List<Map<String, dynamic>>> getTaskHistory({int limit = 100}) async {
|
||||||
if (_database == null) {
|
// 如有需要,可以在未来扩展为持久化历史记录
|
||||||
await initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await _database!.query(
|
|
||||||
'mining_tasks',
|
|
||||||
orderBy: 'created_at DESC',
|
|
||||||
limit: limit,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
_logger.severe('获取任务历史失败: $e');
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 关闭(对文件存储无实际操作,保留接口以兼容旧代码)
|
||||||
|
Future<void> close() async {
|
||||||
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 关闭数据库
|
// === 内部工具方法 ===
|
||||||
Future<void> close() async {
|
|
||||||
await _database?.close();
|
MiningTaskInfo _taskFromJson(Map<String, dynamic> data) {
|
||||||
_database = null;
|
return MiningTaskInfo(
|
||||||
|
coin: data['coin'] as String,
|
||||||
|
algo: data['algo'] as String,
|
||||||
|
pool: (data['pool'] as String?) ?? '',
|
||||||
|
poolUrl: data['pool_url'] as String,
|
||||||
|
walletAddress: data['wallet_address'] as String,
|
||||||
|
workerId: data['worker_id'] as String,
|
||||||
|
poolUser: data['pool_user'] as String?,
|
||||||
|
walletMining: (data['wallet_mining'] as bool?) ??
|
||||||
|
((data['wallet_mining'] is int) ? (data['wallet_mining'] as int) == 1 : false),
|
||||||
|
endTimestamp: data['end_timestamp'] as int,
|
||||||
|
miner: data['miner'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _taskToJson(MiningTaskInfo task, {required int createdAt}) {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'coin': task.coin,
|
||||||
|
'algo': task.algo,
|
||||||
|
'pool': task.pool,
|
||||||
|
'pool_url': task.poolUrl,
|
||||||
|
'wallet_address': task.walletAddress,
|
||||||
|
'worker_id': task.workerId,
|
||||||
|
'pool_user': task.poolUser,
|
||||||
|
'wallet_mining': task.walletMining,
|
||||||
|
'end_timestamp': task.endTimestamp,
|
||||||
|
'miner': task.miner,
|
||||||
|
'created_at': createdAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isSameTask(MiningTaskInfo a, MiningTaskInfo b) {
|
||||||
|
return a.coin == b.coin &&
|
||||||
|
a.algo == b.algo &&
|
||||||
|
a.pool == b.pool &&
|
||||||
|
a.poolUrl == b.poolUrl &&
|
||||||
|
a.walletAddress == b.walletAddress &&
|
||||||
|
a.workerId == b.workerId &&
|
||||||
|
(a.poolUser ?? '') == (b.poolUser ?? '') &&
|
||||||
|
a.walletMining == b.walletMining &&
|
||||||
|
a.endTimestamp == b.endTimestamp &&
|
||||||
|
a.miner == b.miner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _StoredTask {
|
||||||
|
final MiningTaskInfo task;
|
||||||
|
final int createdAt;
|
||||||
|
|
||||||
|
_StoredTask({required this.task, required this.createdAt});
|
||||||
|
}
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ class ClientProvider with ChangeNotifier {
|
|||||||
|
|
||||||
/// 挖矿任务变化回调
|
/// 挖矿任务变化回调
|
||||||
void _onMiningTaskChanged(MiningTaskInfo? task) async {
|
void _onMiningTaskChanged(MiningTaskInfo? task) async {
|
||||||
|
final previousTask = _currentMiningTask;
|
||||||
_currentMiningTask = task;
|
_currentMiningTask = task;
|
||||||
|
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
@@ -246,6 +247,10 @@ class ClientProvider with ChangeNotifier {
|
|||||||
} else {
|
} else {
|
||||||
// 停止挖矿
|
// 停止挖矿
|
||||||
await _miningManager.stopMining();
|
await _miningManager.stopMining();
|
||||||
|
// 挖矿任务完成后,从日志中删除该任务
|
||||||
|
if (previousTask != null) {
|
||||||
|
await _database.finishMiningTask(previousTask);
|
||||||
|
}
|
||||||
|
|
||||||
// 恢复持续挖矿
|
// 恢复持续挖矿
|
||||||
await _sustainMiner.resume();
|
await _sustainMiner.resume();
|
||||||
|
|||||||
66
pubspec.lock
66
pubspec.lock
@@ -49,14 +49,6 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
code_assets:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: code_assets
|
|
||||||
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -105,14 +97,6 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.5"
|
||||||
file:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: file
|
|
||||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "7.0.1"
|
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -144,22 +128,6 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
glob:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: glob
|
|
||||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.3"
|
|
||||||
hooks:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: hooks
|
|
||||||
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -256,14 +224,6 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.17.0"
|
||||||
native_toolchain_c:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: native_toolchain_c
|
|
||||||
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "0.17.4"
|
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -357,30 +317,6 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.1"
|
version: "1.10.1"
|
||||||
sqflite_common:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: sqflite_common
|
|
||||||
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "2.5.6"
|
|
||||||
sqflite_common_ffi:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: sqflite_common_ffi
|
|
||||||
sha256: c59fcdc143839a77581f7a7c4de018e53682408903a0a0800b95ef2dc4033eff
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.0+2"
|
|
||||||
sqlite3:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: sqlite3
|
|
||||||
sha256: "00e5e65f8e9b556ed3d999ad310881c956ffb656ed96bea487a4c50ffdff6d14"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.3"
|
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -494,5 +430,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.10.0-0 <4.0.0"
|
dart: ">=3.8.0 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
|
|||||||
@@ -19,12 +19,8 @@ dependencies:
|
|||||||
# 时间格式化
|
# 时间格式化
|
||||||
intl: ^0.18.1
|
intl: ^0.18.1
|
||||||
|
|
||||||
# 数据库
|
|
||||||
# Windows 桌面端请使用 sqflite_common_ffi
|
|
||||||
sqflite_common_ffi: ^2.3.3
|
|
||||||
|
|
||||||
# 路径工具(database.dart 使用 join)
|
# 路径工具(database.dart 使用 join)
|
||||||
path: ^1.9.0
|
path: ^1.8.3
|
||||||
|
|
||||||
# 进程管理
|
# 进程管理
|
||||||
process_run: ^0.12.5+2
|
process_run: ^0.12.5+2
|
||||||
|
|||||||
Reference in New Issue
Block a user