feat: 实现依赖注入系统、插件架构和窗口管理

- 引入ExamAware的DI系统并适配SecScore项目
- 添加服务注册表和窗口管理器
- 实现基于Koishi的Disposable设计的插件系统
- 集成Winston日志服务
- 分离设置窗口为独立页面
- 将非核心功能改为内置插件
- 更新README文件
- 添加相关hooks和工具函数
This commit is contained in:
Yukino_fox
2026-04-26 19:39:56 +08:00
parent a39355d62a
commit 18eada8ff3
47 changed files with 5347 additions and 180 deletions
+150
View File
@@ -0,0 +1,150 @@
import { invoke } from "@tauri-apps/api/core"
export interface ConfigChangeEvent {
key: string
value: any
timestamp: number
}
export type ConfigChangeListener = (event: ConfigChangeEvent) => void
export interface ConfigSpec {
// 应用设置
is_wizard_completed: boolean
log_level: "debug" | "info" | "warn" | "error"
window_zoom: number
search_keyboard_layout: "t9" | "qwerty26"
disable_search_keyboard: boolean
font_family: string
current_theme_id: string
themes_custom: any[]
dashboards_config: any[]
mobile_bottom_nav_items: string[]
// 自动评分
auto_score_enabled: boolean
auto_score_rules: any[]
auto_score_batches: any[]
// 数据库
pg_connection_string: string
pg_connection_status: {
connected: boolean
type: "sqlite" | "postgresql"
error?: string
}
}
class ConfigServiceClass {
private cache: Map<string, any> = new Map()
private listeners: Set<ConfigChangeListener> = new Set()
private initialized = false
private initPromise: Promise<void> | null = null
async initialize(): Promise<void> {
if (this.initialized) return
if (this.initPromise) return this.initPromise
this.initPromise = (async () => {
try {
const result = await this.invokeApi("getAllSettings")
if (result.success && result.data) {
Object.entries(result.data).forEach(([key, value]) => {
this.cache.set(key, value)
})
}
this.setupListener()
this.initialized = true
} catch (error) {
console.error("Failed to initialize config service:", error)
throw error
}
})()
return this.initPromise
}
async get<K extends keyof ConfigSpec>(key: K): Promise<ConfigSpec[K]> {
await this.initialize()
return this.cache.get(key)
}
async set<K extends keyof ConfigSpec>(key: K, value: ConfigSpec[K]): Promise<void> {
await this.initialize()
const result = await this.invokeApi("setSetting", [key, value])
if (!result.success) {
throw new Error(result.message || "Failed to set config")
}
this.cache.set(key, value)
this.notifyListeners({ key, value, timestamp: Date.now() })
}
async patch(partial: Partial<ConfigSpec>): Promise<void> {
await this.initialize()
for (const [key, value] of Object.entries(partial)) {
await this.set(key as keyof ConfigSpec, value)
}
}
getAll(): ConfigSpec {
const entries = Array.from(this.cache.entries())
return Object.fromEntries(entries) as ConfigSpec
}
has(key: string): boolean {
return this.cache.has(key)
}
onChange(listener: ConfigChangeListener): () => void {
this.listeners.add(listener)
return () => this.listeners.delete(listener)
}
private notifyListeners(event: ConfigChangeEvent): void {
this.listeners.forEach((listener) => {
try {
listener(event)
} catch (error) {
console.error("Config listener error:", error)
}
})
}
private setupListener(): void {
const api = (window as any).api
if (!api?.onSettingChanged) return
api
.onSettingChanged((change: any) => {
if (change?.key && change?.value) {
this.cache.set(change.key, change.value)
this.notifyListeners({
key: change.key,
value: change.value,
timestamp: Date.now(),
})
}
})
.catch(() => void 0)
}
private async invokeApi<T = any>(
command: string,
args?: any[]
): Promise<{ success: boolean; data?: T; message?: string }> {
const api = (window as any).api
if (api?.[command]) {
return await api[command](...(args || []))
}
// Fallback to invoke if API not available
try {
const result = await invoke(command, args ? { args } : {})
return { success: true, data: result as T }
} catch (error: any) {
return { success: false, message: error.message || "Unknown error" }
}
}
}
export const ConfigService = new ConfigServiceClass()
+230
View File
@@ -0,0 +1,230 @@
/**
* SecScore 日志服务
* 支持分级日志、多色打印、文件存储
*/
export type LogLevel = "debug" | "info" | "warn" | "error"
export interface LogEntry {
level: LogLevel
message: string
timestamp: number
meta?: any
source?: string
}
export interface LogWriter {
write(entry: LogEntry): void
}
/**
* 控制台日志写入器
* 支持多色打印
*/
class ConsoleLogWriter implements LogWriter {
private colors: Record<LogLevel, string> = {
debug: "\x1b[36m", // Cyan
info: "\x1b[32m", // Green
warn: "\x1b[33m", // Yellow
error: "\x1b[31m", // Red
}
private reset = "\x1b[0m"
write(entry: LogEntry): void {
const { level, message, timestamp, meta, source } = entry
const color = this.colors[level]
const time = new Date(timestamp).toISOString()
const prefix = source ? `[${source}]` : "[SecScore]"
const logMessage = `${color}${time} ${prefix} [${level.toUpperCase()}]${this.reset} ${message}`
switch (level) {
case "debug":
console.debug(logMessage, meta || "")
break
case "info":
console.info(logMessage, meta || "")
break
case "warn":
console.warn(logMessage, meta || "")
break
case "error":
console.error(logMessage, meta || "")
break
}
}
}
/**
* 远程日志写入器
* 将日志发送到 Rust 后端
*/
class RemoteLogWriter implements LogWriter {
write(entry: LogEntry): void {
const api = (window as any).api
if (!api?.writeLog) return
const payload = {
level: entry.level,
message: entry.message,
meta: entry.meta,
}
Promise.resolve(api.writeLog(payload)).catch(() => void 0)
}
}
/**
* 日志服务类
*/
class LoggerServiceClass {
private writers: LogWriter[] = []
private minLevel: LogLevel = "info"
private levelPriority: Record<LogLevel, number> = {
debug: 0,
info: 1,
warn: 2,
error: 3,
}
private source: string = "App"
private buffer: LogEntry[] = []
private bufferSize = 1000
constructor() {
// 默认添加控制台和远程写入器
this.addWriter(new ConsoleLogWriter())
this.addWriter(new RemoteLogWriter())
}
/**
* 设置日志源
*/
setSource(source: string): void {
this.source = source
}
/**
* 添加日志写入器
*/
addWriter(writer: LogWriter): void {
this.writers.push(writer)
}
/**
* 设置最低日志级别
*/
setLevel(level: LogLevel): void {
this.minLevel = level
}
/**
* 获取当前日志级别
*/
getLevel(): LogLevel {
return this.minLevel
}
/**
* 写入日志
*/
private log(level: LogLevel, message: string, meta?: any): void {
if (this.levelPriority[level] < this.levelPriority[this.minLevel]) {
return
}
const entry: LogEntry = {
level,
message,
timestamp: Date.now(),
meta,
source: this.source,
}
// 添加到缓冲区
this.buffer.push(entry)
if (this.buffer.length > this.bufferSize) {
this.buffer.shift()
}
// 写入所有写入器
this.writers.forEach((writer) => {
try {
writer.write(entry)
} catch (error) {
console.error("Log writer error:", error)
}
})
}
/**
* Debug 日志
*/
debug(message: string, meta?: any): void {
this.log("debug", message, meta)
}
/**
* Info 日志
*/
info(message: string, meta?: any): void {
this.log("info", message, meta)
}
/**
* Warning 日志
*/
warn(message: string, meta?: any): void {
this.log("warn", message, meta)
}
/**
* Error 日志
*/
error(message: string, meta?: any): void {
this.log("error", message, meta)
}
/**
* 创建带指定源的日志记录器
*/
createChild(source: string): LoggerServiceClass {
const child = new LoggerServiceClass()
child.writers = [...this.writers]
child.minLevel = this.minLevel
child.source = source
return child
}
/**
* 获取最近的日志
*/
getRecentLogs(count: number = 100): LogEntry[] {
return this.buffer.slice(-count)
}
/**
* 清空日志缓冲
*/
clear(): void {
this.buffer = []
}
}
// 导出全局日志服务实例
export const LoggerService = new LoggerServiceClass()
/**
* 便捷日志函数
*/
export const logger = {
debug: (message: string, meta?: any) => LoggerService.debug(message, meta),
info: (message: string, meta?: any) => LoggerService.info(message, meta),
warn: (message: string, meta?: any) => LoggerService.warn(message, meta),
error: (message: string, meta?: any) => LoggerService.error(message, meta),
createChild: (source: string) => LoggerService.createChild(source),
setLevel: (level: LogLevel) => LoggerService.setLevel(level),
getLevel: () => LoggerService.getLevel(),
getRecentLogs: (count?: number) => LoggerService.getRecentLogs(count),
clear: () => LoggerService.clear(),
}