Files
go_blog/models/db.go
T
kevin f307781f58 docs: 全部 Go 代码注释汉化
- 48 个 Go 文件所有注释(行注释/块注释/行尾注释,含 _test.go)翻译为中文
- 保留技术标识符:SECURITY_TODO(n)、unsafe-inline、sqlite/mysql、路由参数等
- 代码、字符串字面量、日志消息保持英文原文,零逻辑改动
- go build/vet 通过,go test -count=1 ./... 全绿
2026-08-27 19:03:03 +08:00

114 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}