feat(kodbox): 使用用户名和密码替代 API Token(#4)
- 移除 KodBox API Token 相关代码 - 添加 KodBox 用户名和密码输入字段 - 实现 KodBox 有效 token 获取逻辑 - 更新 KodBox 日志获取和展示逻辑 - 添加文件路径点击打开功能 - 更新相关测试和依赖
This commit is contained in:
parent
85d741e8d1
commit
f8aaf86cf8
@ -1,5 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
|
||||
<application
|
||||
android:label="dashboard"
|
||||
android:name="${applicationName}"
|
||||
|
||||
@ -15,7 +15,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
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<SettingsScreen> {
|
||||
_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<SettingsScreen> {
|
||||
_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<SettingsScreen> {
|
||||
_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<SettingsScreen> {
|
||||
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,
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
65
lib/services/kodbox_service.dart
Normal file
65
lib/services/kodbox_service.dart
Normal file
@ -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<String> 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<bool> 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<String> 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;
|
||||
}
|
||||
}
|
||||
@ -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<KodBoxCard> {
|
||||
List<dynamic> _logs = [];
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
final _kodboxService = KodBoxService();
|
||||
|
||||
Future<void> _fetchLogs() async {
|
||||
setState(() {
|
||||
@ -25,11 +30,7 @@ class _KodBoxCardState extends State<KodBoxCard> {
|
||||
|
||||
try {
|
||||
final authService = Provider.of<AuthService>(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<KodBoxCard> {
|
||||
|
||||
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<KodBoxCard> {
|
||||
return '未知路径';
|
||||
}
|
||||
|
||||
int? _getSourceID(Map<String, dynamic> desc) {
|
||||
if (desc['sourceInfo'] == false) {
|
||||
// 处理删除文件的情况
|
||||
return desc['parentInfo']?['sourceID'];
|
||||
} else if (desc['sourceInfo'] != null) {
|
||||
return desc['sourceInfo']?['sourceID'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _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<String, dynamic> 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -7,9 +7,13 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_secure_storage_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@ -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"))
|
||||
}
|
||||
|
||||
64
pubspec.lock
64
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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -7,8 +7,11 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_secure_storage_windows
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user