From f8aaf86cf8fdd309ff0c4f1893e36ce62074aab8 Mon Sep 17 00:00:00 2001 From: jdysya <1912377458@qq.com> Date: Tue, 10 Jun 2025 15:23:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(kodbox):=20=E4=BD=BF=E7=94=A8=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=90=8D=E5=92=8C=E5=AF=86=E7=A0=81=E6=9B=BF=E4=BB=A3?= =?UTF-8?q?=20API=20Token(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 KodBox API Token 相关代码 - 添加 KodBox 用户名和密码输入字段 - 实现 KodBox 有效 token 获取逻辑 - 更新 KodBox 日志获取和展示逻辑 - 添加文件路径点击打开功能 - 更新相关测试和依赖 --- android/app/src/main/AndroidManifest.xml | 1 + lib/screens/settings_screen.dart | 39 ++++++-- lib/services/auth_service.dart | 8 ++ lib/services/kodbox_service.dart | 65 ++++++++++++ lib/widgets/kodbox_card.dart | 98 ++++++++++++------- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 64 ++++++++++++ pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 12 files changed, 245 insertions(+), 42 deletions(-) create mode 100644 lib/services/kodbox_service.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index dddc87a..2911a44 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + { final _leetcodeUserSlugController = TextEditingController(); final _giteaController = TextEditingController(); final _giteaUsernameController = TextEditingController(); - final _kodboxController = TextEditingController(); + final _kodboxUsernameController = TextEditingController(); + final _kodboxPasswordController = TextEditingController(); final _githubUsernameController = TextEditingController(); final _githubTokenController = TextEditingController(); final _wakatimeTokenController = TextEditingController(); @@ -32,7 +33,8 @@ class _SettingsScreenState extends State { _leetcodeUserSlugController.dispose(); _giteaController.dispose(); _giteaUsernameController.dispose(); - _kodboxController.dispose(); + _kodboxUsernameController.dispose(); + _kodboxPasswordController.dispose(); _githubUsernameController.dispose(); _githubTokenController.dispose(); _wakatimeTokenController.dispose(); @@ -51,7 +53,10 @@ class _SettingsScreenState extends State { _giteaController.text = authService.credentials['gitea_token'] ?? ''; _giteaUsernameController.text = authService.credentials['gitea_username'] ?? ''; - _kodboxController.text = authService.credentials['kodbox_token'] ?? ''; + _kodboxUsernameController.text = + authService.credentials['kodbox_username'] ?? ''; + _kodboxPasswordController.text = + authService.credentials['kodbox_password'] ?? ''; _githubUsernameController.text = authService.credentials['github_username'] ?? ''; _githubTokenController.text = @@ -86,8 +91,17 @@ class _SettingsScreenState extends State { _giteaUsernameController.text, ); } - if (_kodboxController.text.isNotEmpty) { - await authService.saveConfigs('kodbox_token', _kodboxController.text); + if (_kodboxUsernameController.text.isNotEmpty) { + await authService.saveConfigs( + 'kodbox_username', + _kodboxUsernameController.text, + ); + } + if (_kodboxPasswordController.text.isNotEmpty) { + await authService.saveConfigs( + 'kodbox_password', + _kodboxPasswordController.text, + ); } if (_githubUsernameController.text.isNotEmpty) { await authService.saveConfigs( @@ -181,10 +195,19 @@ class _SettingsScreenState extends State { icon: Icons.folder, children: [ TextFormField( - controller: _kodboxController, + controller: _kodboxUsernameController, decoration: const InputDecoration( - labelText: 'API Token', - hintText: '请输入 KodBox API Token', + labelText: '用户名', + hintText: '请输入 KodBox 用户名', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 16), + TextFormField( + controller: _kodboxPasswordController, + decoration: const InputDecoration( + labelText: '密码', + hintText: '请输入 KodBox 密码', border: OutlineInputBorder(), ), obscureText: true, diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 9d48207..feef9f3 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -25,6 +25,8 @@ class AuthService extends ChangeNotifier { final githubUserName = await _storage.read(key: 'github_username'); final githubToken = await _storage.read(key: 'github_token'); final wakatimeToken = await _storage.read(key: 'wakatime_token'); + final kodboxUname = await _storage.read(key: 'kodbox_username'); + final kodboxPwd = await _storage.read(key: 'kodbox_password'); if (leetcodeCookie != null) { _credentials['leetcode_cookie'] = leetcodeCookie; @@ -50,6 +52,12 @@ class AuthService extends ChangeNotifier { if (wakatimeToken != null) { _credentials['wakatime_token'] = wakatimeToken; } + if (kodboxUname != null) { + _credentials['kodbox_username'] = kodboxUname; + } + if (kodboxPwd != null) { + _credentials['kodbox_password'] = kodboxPwd; + } _isAuthenticated = true; notifyListeners(); } diff --git a/lib/services/kodbox_service.dart b/lib/services/kodbox_service.dart new file mode 100644 index 0000000..0ef1579 --- /dev/null +++ b/lib/services/kodbox_service.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:provider/provider.dart'; +import 'auth_service.dart'; + +class KodBoxService { + static const String baseUrl = 'https://cloud.jdysya.top'; + + Future getToken(String username, String password) async { + final response = await http.get( + Uri.parse( + '$baseUrl/?user/index/loginSubmit&name=$username&password=$password'), + ); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (data['code'] == true) { + return data['data']['accessToken']; + } else { + throw Exception('获取token失败: ${data['info'] ?? '未知错误'}'); + } + } else { + throw Exception('请求失败: ${response.statusCode}'); + } + } + + Future validateToken(String token) async { + try { + final response = await http.get( + Uri.parse('$baseUrl/?admin/log/get&accessToken=$token'), + ); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + return data['code'] != '10001'; + } + return false; + } catch (e) { + return false; + } + } + + Future getValidToken(AuthService authService) async { + final username = authService.credentials['kodbox_username']; + final password = authService.credentials['kodbox_password']; + final currentToken = authService.credentials['kodbox_token']; + + if (username == null || password == null) { + throw Exception('请先在设置中配置 KodBox 用户名和密码'); + } + + // 如果当前有token,先验证是否有效 + if (currentToken != null) { + final isValid = await validateToken(currentToken); + if (isValid) { + return currentToken; + } + } + + // 获取新token + final newToken = await getToken(username, password); + await authService.saveConfigs('kodbox_token', newToken); + return newToken; + } +} diff --git a/lib/widgets/kodbox_card.dart b/lib/widgets/kodbox_card.dart index 3eeea08..477bcd5 100644 --- a/lib/widgets/kodbox_card.dart +++ b/lib/widgets/kodbox_card.dart @@ -1,9 +1,13 @@ +import 'dart:ffi'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import '../services/auth_service.dart'; +import '../services/kodbox_service.dart'; import 'package:timeago/timeago.dart' as timeago; +import 'package:url_launcher/url_launcher.dart'; class KodBoxCard extends StatefulWidget { const KodBoxCard({super.key}); @@ -16,6 +20,7 @@ class _KodBoxCardState extends State { List _logs = []; bool _isLoading = false; String? _error; + final _kodboxService = KodBoxService(); Future _fetchLogs() async { setState(() { @@ -25,11 +30,7 @@ class _KodBoxCardState extends State { try { final authService = Provider.of(context, listen: false); - final token = authService.credentials['kodbox_token']; - - if (token == null || token.isEmpty) { - throw Exception('未配置 KodBox Token'); - } + final token = await _kodboxService.getValidToken(authService); // 计算时间范围(最近7天) final now = DateTime.now(); @@ -39,7 +40,7 @@ class _KodBoxCardState extends State { final request = http.MultipartRequest( 'POST', - Uri.parse('https://cloud.jdysya.top/?admin/log/get&accessToken=$token'), + Uri.parse('${KodBoxService.baseUrl}/?admin/log/get&accessToken=$token'), ); request.fields.addAll({ @@ -87,47 +88,76 @@ class _KodBoxCardState extends State { return '未知路径'; } + int? _getSourceID(Map desc) { + if (desc['sourceInfo'] == false) { + // 处理删除文件的情况 + return desc['parentInfo']?['sourceID']; + } else if (desc['sourceInfo'] != null) { + return desc['sourceInfo']?['sourceID']; + } + return null; + } + + Future _launchFileUrl(int sourceID) async { + final url = Uri.parse('${KodBoxService.baseUrl}/#explorer&sidf=$sourceID'); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('无法打开文件链接')), + ); + } + } + } + Widget _buildLogItem(Map log) { final desc = log['desc']; - final isFileOperation = - desc != null && + final isFileOperation = desc != null && (desc['sourceInfo'] != null || desc['parentInfo'] != null); + final sourceID = isFileOperation ? _getSourceID(desc) : null; + return Card( margin: const EdgeInsets.symmetric(vertical: 4.0), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - CircleAvatar( - radius: 16, - child: Text(log['nickName']?[0].toUpperCase() ?? '?'), - ), - const SizedBox(width: 8), + child: InkWell( + onTap: sourceID != null ? () => _launchFileUrl(sourceID) : null, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CircleAvatar( + radius: 16, + child: Text(log['nickName']?[0].toUpperCase() ?? '?'), + ), + const SizedBox(width: 8), + Text( + log['nickName'] ?? log['name'] ?? '未知用户', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(width: 8), + Text(log['title'] ?? '未知操作'), + ], + ), + if (isFileOperation) ...[ + const SizedBox(height: 8), Text( - log['nickName'] ?? log['name'] ?? '未知用户', - style: const TextStyle(fontWeight: FontWeight.bold), + '文件路径: ${_getFilePath(desc)}', + style: TextStyle( + color: sourceID != null ? Colors.blue : Colors.black, + ), ), - const SizedBox(width: 8), - Text(log['title'] ?? '未知操作'), ], - ), - if (isFileOperation) ...[ const SizedBox(height: 8), Text( - '文件路径: ${_getFilePath(desc)}', - style: const TextStyle(color: Colors.blue), + '时间: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(int.parse(log['createTime']) * 1000), locale: 'zh_CN')}', + style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], - const SizedBox(height: 8), - Text( - '时间: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(int.parse(log['createTime']) * 1000), locale: 'zh_CN')}', - style: const TextStyle(fontSize: 12, color: Colors.grey), - ), - ], + ), ), ), ); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d0e7f79..38dd0bc 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b29e9ba..65240e9 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index bbb5747..0ff2353 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,10 +9,12 @@ import flutter_secure_storage_macos import path_provider_foundation import shared_preferences_foundation import sqflite_darwin +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 619ee5f..e0b8c8a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -581,6 +581,70 @@ packages: url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "6.3.16" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "3.1.4" uuid: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 477f584..4a11e72 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: cached_network_image: ^3.3.1 timeago: ^3.7.1 fl_chart: ^0.65.0 + url_launcher: ^6.2.4 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 0c50753..2048c45 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4fc759c..de626cc 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST