🚧 Refactor basic services

This commit is contained in:
2025-12-14 02:19:50 +08:00
parent d16905c0a3
commit cc4c2189dc
126 changed files with 18164 additions and 4247 deletions

View File

@@ -6,376 +6,100 @@ import (
"fmt"
"os"
"path/filepath"
"reflect"
"sync"
"voidraft/internal/models"
"time"
"voidraft/internal/models/ent"
"voidraft/internal/models/ent/migrate"
_ "voidraft/internal/models/ent/runtime"
"entgo.io/ent/dialect"
entsql "entgo.io/ent/dialect/sql"
_ "github.com/mattn/go-sqlite3"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/services/log"
_ "modernc.org/sqlite"
)
const (
dbName = "voidraft.db"
// SQLite performance optimization settings
sqlOptimizationSettings = `
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA cache_size = -64000;
PRAGMA temp_store = MEMORY;
PRAGMA foreign_keys = ON;`
// Documents table
sqlCreateDocumentsTable = `
CREATE TABLE IF NOT EXISTS documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT DEFAULT '∞∞∞text-a',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
is_deleted INTEGER DEFAULT 0,
is_locked INTEGER DEFAULT 0
)`
// Extensions table
sqlCreateExtensionsTable = `
CREATE TABLE IF NOT EXISTS extensions (
id TEXT PRIMARY KEY,
enabled INTEGER NOT NULL DEFAULT 1,
is_default INTEGER NOT NULL DEFAULT 0,
config TEXT DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)`
// Key bindings table
sqlCreateKeyBindingsTable = `
CREATE TABLE IF NOT EXISTS key_bindings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
command TEXT NOT NULL,
extension TEXT NOT NULL,
key TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
is_default INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)`
// Themes table
sqlCreateThemesTable = `
CREATE TABLE IF NOT EXISTS themes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
colors TEXT NOT NULL,
is_default INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)`
dbName = "voidraft.db"
maxIdleConns = 10
maxOpenConns = 100
connMaxLife = time.Hour
)
// ColumnInfo 存储列的信息
type ColumnInfo struct {
SQLType string
DefaultValue string
}
// TableModel 表示表与模型之间的映射关系
type TableModel struct {
TableName string
Model interface{}
}
// DatabaseService provides shared database functionality
// DatabaseService 数据库服务
type DatabaseService struct {
configService *ConfigService
logger *log.LogService
Client *ent.Client
db *sql.DB
mu sync.RWMutex
ctx context.Context
tableModels []TableModel // 注册的表模型
// 配置观察者取消函数
cancelObserver CancelFunc
}
// NewDatabaseService creates a new database service
// NewDatabaseService 创建数据库服务
func NewDatabaseService(configService *ConfigService, logger *log.LogService) *DatabaseService {
if logger == nil {
logger = log.New()
}
ds := &DatabaseService{
return &DatabaseService{
configService: configService,
logger: logger,
}
// 注册所有模型
ds.registerAllModels()
return ds
}
// registerAllModels 注册所有数据模型,集中管理表-模型映射
func (ds *DatabaseService) registerAllModels() {
// 文档表
ds.RegisterModel("documents", &models.Document{})
// 扩展表
ds.RegisterModel("extensions", &models.Extension{})
// 快捷键表
ds.RegisterModel("key_bindings", &models.KeyBinding{})
// 主题表
ds.RegisterModel("themes", &models.Theme{})
}
// ServiceStartup initializes the service when the application starts
func (ds *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
ds.ctx = ctx
return ds.initDatabase()
}
// initDatabase initializes the SQLite database
func (ds *DatabaseService) initDatabase() error {
dbPath, err := ds.getDatabasePath()
// ServiceStartup 服务启动
func (s *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
dbPath, err := s.getBasePath()
if err != nil {
return fmt.Errorf("failed to get database path: %w", err)
return fmt.Errorf("get database path error: %w", err)
}
// 确保数据库目录存在
dbDir := filepath.Dir(dbPath)
if err := os.MkdirAll(dbDir, 0755); err != nil {
return fmt.Errorf("failed to create database directory: %w", err)
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
return fmt.Errorf("create database directory error: %w", err)
}
// 打开数据库连接
ds.db, err = sql.Open("sqlite", dbPath)
// _fk=1 启用外键_journal_mode=WAL 启用 WAL 模式
dsn := fmt.Sprintf("file:%s?_fk=1&_journal_mode=WAL&_synchronous=NORMAL&_busy_timeout=5000", dbPath)
s.db, err = sql.Open("sqlite3", dsn)
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
return fmt.Errorf("open database error: %w", err)
}
// 测试连接
if err := ds.db.Ping(); err != nil {
return fmt.Errorf("failed to ping database: %w", err)
}
// 连接池配置
s.db.SetMaxIdleConns(maxIdleConns)
s.db.SetMaxOpenConns(maxOpenConns)
s.db.SetConnMaxLifetime(connMaxLife)
// 应用性能优化设置
if _, err := ds.db.Exec(sqlOptimizationSettings); err != nil {
return fmt.Errorf("failed to apply optimization settings: %w", err)
}
// 创建 ent 客户端
drv := entsql.OpenDB(dialect.SQLite, s.db)
s.Client = ent.NewClient(ent.Driver(drv))
// 创建表和索引
if err := ds.createTables(); err != nil {
return fmt.Errorf("failed to create tables: %w", err)
}
if err := ds.createIndexes(); err != nil {
return fmt.Errorf("failed to create indexes: %w", err)
}
// 执行模型与表结构同步
if err := ds.syncAllModelTables(); err != nil {
return fmt.Errorf("failed to sync model tables: %w", err)
// 自动迁移
if err := s.Client.Schema.Create(ctx,
migrate.WithDropColumn(true),
migrate.WithDropIndex(true),
); err != nil {
return fmt.Errorf("schema migration error: %w", err)
}
return nil
}
// getDatabasePath gets the database file path
func (ds *DatabaseService) getDatabasePath() (string, error) {
config, err := ds.configService.GetConfig()
func (s *DatabaseService) getBasePath() (string, error) {
config, err := s.configService.GetConfig()
if err != nil {
return "", err
}
return filepath.Join(config.General.DataPath, dbName), nil
}
// createTables creates all database tables
func (ds *DatabaseService) createTables() error {
tables := []string{
sqlCreateDocumentsTable,
sqlCreateExtensionsTable,
sqlCreateKeyBindingsTable,
sqlCreateThemesTable,
}
for _, table := range tables {
if _, err := ds.db.Exec(table); err != nil {
// ServiceShutdown 服务关闭
func (s *DatabaseService) ServiceShutdown() error {
if s.Client != nil {
if err := s.Client.Close(); err != nil {
return err
}
}
return nil
}
// createIndexes creates database indexes
func (ds *DatabaseService) createIndexes() error {
indexes := []string{
// Documents indexes
`CREATE INDEX IF NOT EXISTS idx_documents_updated_at ON documents(updated_at DESC)`,
`CREATE INDEX IF NOT EXISTS idx_documents_title ON documents(title)`,
`CREATE INDEX IF NOT EXISTS idx_documents_is_deleted ON documents(is_deleted)`,
// Extensions indexes
`CREATE INDEX IF NOT EXISTS idx_extensions_enabled ON extensions(enabled)`,
// Key bindings indexes
`CREATE INDEX IF NOT EXISTS idx_key_bindings_command ON key_bindings(command)`,
`CREATE INDEX IF NOT EXISTS idx_key_bindings_extension ON key_bindings(extension)`,
`CREATE INDEX IF NOT EXISTS idx_key_bindings_enabled ON key_bindings(enabled)`,
// Themes indexes
`CREATE INDEX IF NOT EXISTS idx_themes_type ON themes(type)`,
`CREATE INDEX IF NOT EXISTS idx_themes_is_default ON themes(is_default)`,
}
for _, index := range indexes {
if _, err := ds.db.Exec(index); err != nil {
return err
}
}
return nil
}
// RegisterModel 注册模型与表的映射关系
func (ds *DatabaseService) RegisterModel(tableName string, model interface{}) {
ds.mu.Lock()
defer ds.mu.Unlock()
ds.tableModels = append(ds.tableModels, TableModel{
TableName: tableName,
Model: model,
})
}
// syncAllModelTables 同步所有注册的模型与表结构
func (ds *DatabaseService) syncAllModelTables() error {
for _, tm := range ds.tableModels {
if err := ds.syncModelTable(tm.TableName, tm.Model); err != nil {
return fmt.Errorf("failed to sync table %s: %w", tm.TableName, err)
}
}
return nil
}
// syncModelTable 同步模型与表结构
func (ds *DatabaseService) syncModelTable(tableName string, model interface{}) error {
// 获取表结构元数据
columns, err := ds.getTableColumns(tableName)
if err != nil {
return fmt.Errorf("failed to get table columns: %w", err)
}
// 使用反射从模型中提取字段信息
expectedColumns, err := ds.getModelColumns(model)
if err != nil {
return fmt.Errorf("failed to get model columns: %w", err)
}
// 检查缺失的列并添加
for colName, colInfo := range expectedColumns {
if _, exists := columns[colName]; !exists {
// 执行添加列的SQL
alterSQL := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s DEFAULT %s",
tableName, colName, colInfo.SQLType, colInfo.DefaultValue)
if _, err := ds.db.Exec(alterSQL); err != nil {
return fmt.Errorf("failed to add column %s: %w", colName, err)
}
}
}
return nil
}
// getTableColumns 获取表的列信息
func (ds *DatabaseService) getTableColumns(table string) (map[string]string, error) {
query := fmt.Sprintf("PRAGMA table_info(%s)", table)
rows, err := ds.db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
columns := make(map[string]string)
for rows.Next() {
var cid int
var name, typeName string
var notNull, pk int
var dflt_value interface{}
if err := rows.Scan(&cid, &name, &typeName, &notNull, &dflt_value, &pk); err != nil {
return nil, err
}
columns[name] = typeName
}
if err := rows.Err(); err != nil {
return nil, err
}
return columns, nil
}
// getModelColumns 从模型结构体中提取数据库列信息
func (ds *DatabaseService) getModelColumns(model interface{}) (map[string]ColumnInfo, error) {
columns := make(map[string]ColumnInfo)
// 使用反射获取结构体的类型信息
t := reflect.TypeOf(model)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("model must be a struct or a pointer to struct")
}
// 遍历所有字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// 只处理有db标签的字段
dbTag := field.Tag.Get("db")
if dbTag == "" {
// 如果没有db标签跳过该字段
continue
}
// 获取字段类型对应的SQL类型和默认值
sqlType, defaultVal := getSQLTypeAndDefault(field.Type)
columns[dbTag] = ColumnInfo{
SQLType: sqlType,
DefaultValue: defaultVal,
}
}
return columns, nil
}
// getSQLTypeAndDefault 根据Go类型获取对应的SQL类型和默认值
func getSQLTypeAndDefault(t reflect.Type) (string, string) {
switch t.Kind() {
case reflect.Bool:
return "INTEGER", "0"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "INTEGER", "0"
case reflect.Float32, reflect.Float64:
return "REAL", "0.0"
case reflect.String:
return "TEXT", "''"
default:
return "TEXT", "NULL"
}
}
// ServiceShutdown shuts down the service when the application closes
func (ds *DatabaseService) ServiceShutdown() error {
// 取消配置观察者
if ds.cancelObserver != nil {
ds.cancelObserver()
}
if ds.db != nil {
return ds.db.Close()
if s.db != nil {
return s.db.Close()
}
return nil
}