- 48 个 Go 文件所有注释(行注释/块注释/行尾注释,含 _test.go)翻译为中文 - 保留技术标识符:SECURITY_TODO(n)、unsafe-inline、sqlite/mysql、路由参数等 - 代码、字符串字面量、日志消息保持英文原文,零逻辑改动 - go build/vet 通过,go test -count=1 ./... 全绿
114 lines
3.5 KiB
Go
114 lines
3.5 KiB
Go
package models
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"log"
|
||
"os"
|
||
"path/filepath"
|
||
|
||
"go_blog/config"
|
||
|
||
"gorm.io/driver/mysql"
|
||
"gorm.io/gorm"
|
||
"gorm.io/gorm/logger"
|
||
|
||
"github.com/glebarez/sqlite"
|
||
)
|
||
|
||
// DB 是全局数据库连接,由 InitDB 初始化。
|
||
var DB *gorm.DB
|
||
|
||
// adminPasswordAlphabet 避免了视觉上易混淆的字符(不含 l、I、O、0、1),
|
||
// 用于生成首次运行的管理员密码。
|
||
const adminPasswordAlphabet = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789" + "^$*+?%"
|
||
|
||
// randomAdminPassword 返回密码学随机的 16 位首次运行管理员密码
|
||
// (SECURITY_TODO #12:不再硬编码 admin/admin)。
|
||
func randomAdminPassword() string {
|
||
b := make([]byte, 16)
|
||
if _, err := rand.Read(b); err != nil {
|
||
log.Fatalf("Failed to generate admin password: %v", err)
|
||
}
|
||
out := make([]byte, len(b))
|
||
for i, v := range b {
|
||
out[i] = adminPasswordAlphabet[int(v)%len(adminPasswordAlphabet)]
|
||
}
|
||
return string(out)
|
||
}
|
||
|
||
// InitDB 打开数据库连接、执行迁移并初始化管理员用户。
|
||
func InitDB(cfg *config.Config) *gorm.DB {
|
||
// 确保存储目录存在。
|
||
if err := os.MkdirAll(cfg.Path, 0755); err != nil {
|
||
log.Fatalf("Failed to create storage directory %s: %v", cfg.Path, err)
|
||
}
|
||
|
||
var dialector gorm.Dialector
|
||
|
||
switch cfg.Database.Type {
|
||
case "mysql":
|
||
if cfg.Database.DSN == "" {
|
||
log.Fatalf("Database DSN is required when type is 'mysql'. Please set it in your config file.")
|
||
}
|
||
dialector = mysql.Open(cfg.Database.DSN)
|
||
default:
|
||
dbPath := filepath.Join(cfg.Path, "blog.db")
|
||
dialector = sqlite.Open(dbPath)
|
||
}
|
||
|
||
db, err := gorm.Open(dialector, &gorm.Config{
|
||
Logger: logger.Default.LogMode(logger.Warn),
|
||
})
|
||
if err != nil {
|
||
log.Fatalf("Failed to connect to database: %v", err)
|
||
}
|
||
|
||
// 自动迁移数据表(幂等操作)。
|
||
if err := db.AutoMigrate(&User{}, &Article{}, &SiteSetting{}, &UploadConfig{}, &UploadFileType{}, &DownloadBaseURL{}, &Attachment{}, &Comment{}, &CommentConfig{}, &ArticleView{}, &NavLink{}, &Tag{}, &ArticleTag{}); err != nil {
|
||
log.Fatalf("Failed to auto-migrate database: %v", err)
|
||
}
|
||
|
||
// 首次运行时初始化站点平台配置。
|
||
seedSiteSettings(db)
|
||
seedUploadConfig(db)
|
||
seedUploadFileTypes(db)
|
||
seedCommentConfig(db)
|
||
|
||
// 首次运行初始化:若不存在任何用户则创建管理员用户。
|
||
var count int64
|
||
db.Model(&User{}).Count(&count)
|
||
if count == 0 {
|
||
admin := &User{
|
||
Username: "admin",
|
||
DisplayName: "Administrator",
|
||
Gender: "other",
|
||
Status: StatusNormal,
|
||
Role: RoleAdmin,
|
||
}
|
||
adminPassword := randomAdminPassword()
|
||
if err := admin.SetPassword(adminPassword); err != nil {
|
||
log.Fatalf("Failed to hash admin password: %v", err)
|
||
}
|
||
if err := db.Create(admin).Error; err != nil {
|
||
log.Fatalf("Failed to create admin user: %v", err)
|
||
}
|
||
// SECURITY_TODO #12:首次运行密码为密码学随机生成,且只打印一次——
|
||
// 请立即抄写;之后将无法找回。
|
||
log.Println("==============================================")
|
||
log.Println(" First run: created default admin user.")
|
||
log.Println(" Username: admin")
|
||
log.Println(" Password: " + adminPassword)
|
||
log.Println(" This password is shown ONCE. Change it after login!")
|
||
log.Println("==============================================")
|
||
}
|
||
|
||
// 迁移修复:始终为 admin 用户设置管理员角色。
|
||
result := db.Model(&User{}).Where("username = ?", "admin").Update("role", RoleAdmin)
|
||
if result.RowsAffected > 0 {
|
||
log.Printf("Migration: set admin role for existing admin user (rows affected: %d)", result.RowsAffected)
|
||
}
|
||
|
||
DB = db
|
||
return db
|
||
}
|