refactor: 重构Gemini适配器以支持多模态输入 fix: 修复React Hooks依赖警告 style: 清理未使用的导入和代码 docs: 更新用户界面文本和提示 perf: 优化图像和视频URL处理性能 test: 添加数据迁移工具和测试 build: 更新依赖项和.gitignore chore: 同步Zenmux模型和价格比例
131 lines
3.1 KiB
Go
131 lines
3.1 KiB
Go
// migrate/main.go — SQLite → MySQL one-api data migration tool
|
|
// Usage: go run tools/migrate/main.go
|
|
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
const (
|
|
sqlitePath = "one-api.db"
|
|
mysqlDSN = "root:123456@tcp(localhost:3306)/oneapi?charset=utf8mb4&parseTime=True&loc=Local"
|
|
)
|
|
|
|
func main() {
|
|
if _, err := os.Stat(sqlitePath); os.IsNotExist(err) {
|
|
log.Fatalf("SQLite file not found: %s", sqlitePath)
|
|
}
|
|
|
|
sqlite, err := sql.Open("sqlite3", sqlitePath)
|
|
if err != nil {
|
|
log.Fatalf("open sqlite: %v", err)
|
|
}
|
|
defer sqlite.Close()
|
|
|
|
mysql, err := sql.Open("mysql", mysqlDSN)
|
|
if err != nil {
|
|
log.Fatalf("open mysql: %v", err)
|
|
}
|
|
defer mysql.Close()
|
|
|
|
if err = mysql.Ping(); err != nil {
|
|
log.Fatalf("mysql ping failed: %v\nCheck if MySQL is running and DSN is correct.", err)
|
|
}
|
|
|
|
fmt.Println("Connected to both databases. Starting migration...")
|
|
|
|
tables := []string{"users", "channels", "tokens", "options", "redemptions", "logs", "abilities"}
|
|
for _, table := range tables {
|
|
if err := migrateTable(sqlite, mysql, table); err != nil {
|
|
fmt.Printf("[WARN] table %s: %v\n", table, err)
|
|
}
|
|
}
|
|
|
|
fmt.Println("\nMigration complete!")
|
|
}
|
|
|
|
func migrateTable(src, dst *sql.DB, table string) error {
|
|
// Check table exists in SQLite
|
|
var count int
|
|
err := src.QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='%s'", table)).Scan(&count)
|
|
if err != nil || count == 0 {
|
|
fmt.Printf("[SKIP] table %s not found in SQLite\n", table)
|
|
return nil
|
|
}
|
|
|
|
// Get row count
|
|
var total int
|
|
_ = src.QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM `%s`", table)).Scan(&total)
|
|
if total == 0 {
|
|
fmt.Printf("[SKIP] table %s is empty\n", table)
|
|
return nil
|
|
}
|
|
|
|
// Read all rows
|
|
rows, err := src.Query(fmt.Sprintf("SELECT * FROM `%s`", table))
|
|
if err != nil {
|
|
return fmt.Errorf("select from sqlite: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
cols, err := rows.Columns()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Build INSERT statement with placeholders
|
|
placeholders := ""
|
|
colNames := ""
|
|
for i, col := range cols {
|
|
if i > 0 {
|
|
placeholders += ","
|
|
colNames += ","
|
|
}
|
|
placeholders += "?"
|
|
colNames += fmt.Sprintf("`%s`", col)
|
|
}
|
|
insertSQL := fmt.Sprintf("INSERT IGNORE INTO `%s` (%s) VALUES (%s)", table, colNames, placeholders)
|
|
|
|
stmt, err := dst.Prepare(insertSQL)
|
|
if err != nil {
|
|
return fmt.Errorf("prepare insert for %s: %w", table, err)
|
|
}
|
|
defer stmt.Close()
|
|
|
|
inserted := 0
|
|
vals := make([]interface{}, len(cols))
|
|
valPtrs := make([]interface{}, len(cols))
|
|
for i := range vals {
|
|
valPtrs[i] = &vals[i]
|
|
}
|
|
|
|
for rows.Next() {
|
|
if err := rows.Scan(valPtrs...); err != nil {
|
|
return fmt.Errorf("scan: %w", err)
|
|
}
|
|
// Convert []byte to string for MySQL compatibility
|
|
args := make([]interface{}, len(vals))
|
|
for i, v := range vals {
|
|
if b, ok := v.([]byte); ok {
|
|
args[i] = string(b)
|
|
} else {
|
|
args[i] = v
|
|
}
|
|
}
|
|
if _, err := stmt.Exec(args...); err != nil {
|
|
fmt.Printf("[WARN] insert row in %s: %v\n", table, err)
|
|
continue
|
|
}
|
|
inserted++
|
|
}
|
|
|
|
fmt.Printf("[OK] %s: %d/%d rows migrated\n", table, inserted, total)
|
|
return nil
|
|
}
|