package main import ( "embed" "fmt" "io/fs" "log" "mime" "net/url" "os" "strings" "verification-platform-backend/internal/config" "verification-platform-backend/internal/database" "verification-platform-backend/internal/middleware" "verification-platform-backend/internal/router" "verification-platform-backend/internal/service" "verification-platform-backend/pkg/logger" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" ) //go:embed all:embedded/dist var embeddedFiles embed.FS func main() { if len(os.Args) > 1 { switch os.Args[1] { case "list-admin": listAdmins() case "reset-password": resetPassword() case "create-admin": createAdmin() case "help", "-h", "--help": printHelp() default: fmt.Printf("未知命令: %s\n", os.Args[1]) printHelp() os.Exit(1) } return } startServer() } func printHelp() { fmt.Println("用法:") fmt.Println(" verify 启动服务器") fmt.Println(" verify list-admin 列出所有管理员账号") fmt.Println(" verify reset-password <用户名> [新密码] 重置管理员密码") fmt.Println(" verify create-admin <用户名> <密码> [邮箱] 创建新管理员") fmt.Println(" verify help 显示帮助信息") fmt.Println() fmt.Println("示例:") fmt.Println(" verify list-admin") fmt.Println(" verify reset-password admin") fmt.Println(" verify reset-password admin newpass") fmt.Println(" verify create-admin newadmin password123 admin@example.com") } func listAdmins() { config.Init() database.Init() var users []struct { ID uint Username string Email *string Role string Status string CreatedAt string } if err := database.DB.Table("users").Where("role = ?", "admin").Find(&users).Error; err != nil { fmt.Printf("错误: 查询失败 - %v\n", err) os.Exit(1) } if len(users) == 0 { fmt.Println("没有找到管理员账号") return } fmt.Println("管理员账号列表:") fmt.Println("----------------------------------------") for _, u := range users { email := "" if u.Email != nil { email = *u.Email } fmt.Printf("ID: %d 用户名: %s 邮箱: %s 状态: %s\n", u.ID, u.Username, email, u.Status) } fmt.Println("----------------------------------------") } func createAdmin() { config.Init() database.Init() if len(os.Args) < 4 { fmt.Println("用法: verify create-admin <用户名> <密码> [邮箱]") os.Exit(1) } username := os.Args[2] password := os.Args[3] email := "" if len(os.Args) >= 5 { email = os.Args[4] } if len(password) < 6 { fmt.Println("错误: 密码长度至少6位") os.Exit(1) } var count int64 database.DB.Table("users").Where("username = ?", username).Count(&count) if count > 0 { fmt.Printf("错误: 用户名 '%s' 已存在\n", username) os.Exit(1) } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { fmt.Printf("错误: 密码加密失败 - %v\n", err) os.Exit(1) } emailPtr := (*string)(nil) if email != "" { emailPtr = &email } if err := database.DB.Table("users").Create(map[string]interface{}{ "username": username, "password": string(hashedPassword), "email": emailPtr, "role": "admin", "status": "active", }).Error; err != nil { fmt.Printf("错误: 创建失败 - %v\n", err) os.Exit(1) } fmt.Printf("成功: 管理员账号 '%s' 已创建\n", username) } func resetPassword() { config.Init() database.Init() if len(os.Args) < 3 { fmt.Println("用法: verify reset-password <用户名> [新密码]") os.Exit(1) } username := os.Args[2] newPassword := "" if len(os.Args) >= 4 { newPassword = os.Args[3] } var user struct { ID uint Username string Role string } if err := database.DB.Table("users").Where("username = ? AND role = ?", username, "admin").First(&user).Error; err != nil { fmt.Printf("错误: 未找到管理员用户 '%s'\n", username) os.Exit(1) } if newPassword == "" { newPassword = generateRandomPassword(12) } if len(newPassword) < 6 { fmt.Println("错误: 密码长度至少6位") os.Exit(1) } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) if err != nil { fmt.Printf("错误: 密码加密失败 - %v\n", err) os.Exit(1) } if err := database.DB.Table("users").Where("id = ?", user.ID).Update("password", string(hashedPassword)).Error; err != nil { fmt.Printf("错误: 更新密码失败 - %v\n", err) os.Exit(1) } fmt.Printf("成功: 用户 '%s' 的密码已重置\n", username) fmt.Printf("新密码: %s\n", newPassword) } func generateRandomPassword(length int) string { const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%" result := make([]byte, length) for i := range result { result[i] = chars[i%len(chars)] } for i := len(result) - 1; i > 0; i-- { j := i % len(chars) result[i], result[j] = result[j], result[i] } return string(result) } func startServer() { config.Init() logger.Init() database.Init() os.MkdirAll("uploads", 0755) if config.GetString("app.env") == "production" { gin.SetMode(gin.ReleaseMode) } r := gin.Default() r.MaxMultipartMemory = 32 << 20 r.Use(middleware.Logger()) r.Use(middleware.Recovery()) r.Use(middleware.Cors()) r.Static("/uploads", "uploads") router.SetupRoutes(r) setupEmbeddedFrontend(r) port := config.GetString("app.port") if port == "" { port = "8080" } log.Printf("Server starting on port %s", port) if err := r.Run(":" + port); err != nil { log.Fatal("Failed to start server: ", err) } } func setupEmbeddedFrontend(r *gin.Engine) { distFS, err := fs.Sub(embeddedFiles, "embedded/dist") if err != nil { log.Printf("Warning: embedded dist not found, frontend will not be served") return } indexHTML, err := fs.ReadFile(distFS, "index.html") if err != nil { log.Printf("Warning: index.html not found in embedded dist") return } r.GET("/assets/*filepath", func(c *gin.Context) { filepath := c.Param("filepath") filepath = strings.TrimPrefix(filepath, "/") decodedPath, err := url.PathUnescape(filepath) if err != nil { decodedPath = filepath } fullPath := "assets/" + decodedPath data, err := fs.ReadFile(distFS, fullPath) if err != nil { c.Status(404) return } contentType := mime.TypeByExtension(decodedPath) if contentType == "" { contentType = "application/octet-stream" } c.Header("Cache-Control", "public, max-age=31536000, immutable") c.Data(200, contentType, data) }) r.NoRoute(func(c *gin.Context) { if c.Request.Method != "GET" || strings.HasPrefix(c.Request.URL.Path, "/api/") { c.JSON(404, gin.H{"error": "not found"}) return } path := strings.TrimPrefix(c.Request.URL.Path, "/") decodedPath, err := url.PathUnescape(path) if err != nil { decodedPath = path } if data, err := fs.ReadFile(distFS, decodedPath); err == nil { contentType := mime.TypeByExtension(decodedPath) if contentType == "" { contentType = "application/octet-stream" } c.Data(200, contentType, data) return } c.Header("Cache-Control", "no-cache, no-store, must-revalidate") serveIndexHTML(c, string(indexHTML)) }) } func serveIndexHTML(c *gin.Context, indexHTML string) { settingService := service.NewSettingService() settings, err := settingService.GetSettingsByCategory("basic") if err != nil { settings = make(map[string]interface{}) } siteName := getSettingString(settings, "siteName", "微授权") siteDescription := getSettingString(settings, "siteDescription", "专业的应用验证平台") siteKeywords := getSettingString(settings, "siteKeywords", "验证平台,软件授权,卡密验证") logoLight := getSettingString(settings, "logoLight", "") logoDark := getSettingString(settings, "logoDark", "") favicon := getSettingString(settings, "favicon", "") html := indexHTML html = strings.ReplaceAll(html, "{{.SiteName}}", siteName) html = strings.ReplaceAll(html, "{{.SiteDescription}}", siteDescription) html = strings.ReplaceAll(html, "{{.SiteKeywords}}", siteKeywords) html = strings.ReplaceAll(html, "{{.LogoLight}}", logoLight) html = strings.ReplaceAll(html, "{{.LogoDark}}", logoDark) html = strings.ReplaceAll(html, "{{.Favicon}}", favicon) c.Data(200, "text/html; charset=utf-8", []byte(html)) } func getSettingString(settings map[string]interface{}, key, defaultValue string) string { if val, ok := settings[key]; ok { if str, ok := val.(string); ok && str != "" { return str } } return defaultValue }