- new resp package (OK/Page/Fail/BadRequest/.../InternalErr): unified error
envelope {detail, code?}, sanitized 500s (full error logged server-side,
generic message to client - stops DB error leakage), pagination envelope
{items,total,page,page_size}; migrate 200+ inline c.JSON error sites
- refresh token: 7d rotation tokens (SHA-256 hashed at rest), public
POST /auth/refresh with single-use rotation, POST /auth/logout revokes
all user refresh tokens (endpoint previously missing - frontend 404)
- security: gh-ost command line password masked in task status payload;
Instance.MarshalJSON guarantees encrypted passwords never leave the API
(removes reliance on per-handler manual masking); production (DEBUG=false)
with default JWT secret now auto-generates and persists a random secret
- safe.Go recover wrapper for background goroutines (instance probe,
gh-ost async)
- delete dead service layer (zero callers)
- fix pre-existing go vet unkeyed bson.E literals
102 lines
2.6 KiB
Go
102 lines
2.6 KiB
Go
package config
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
|
|
"sql-platform/internal/model"
|
|
|
|
"github.com/joho/godotenv"
|
|
)
|
|
|
|
// DefaultJWTSecret 仅用于本地 DEBUG;生产模式(DEBUG=false)会被 EnsureJWTSecret 替换为随机密钥
|
|
const DefaultJWTSecret = "sql-platform-secret-key-change-in-production"
|
|
|
|
type Config struct {
|
|
Port string
|
|
DBPath string
|
|
JWTSecret string
|
|
JWTExpire int // hours
|
|
GhOstPath string
|
|
Debug bool
|
|
}
|
|
|
|
var C Config
|
|
|
|
func Init() {
|
|
_ = godotenv.Load()
|
|
|
|
C = Config{
|
|
Port: getEnv("PORT", "9000"),
|
|
DBPath: getEnv("DB_PATH", "../backend/sqlite3.db"),
|
|
JWTSecret: getEnv("JWT_SECRET", DefaultJWTSecret),
|
|
JWTExpire: getEnvInt("JWT_EXPIRE_HOURS", 24),
|
|
GhOstPath: getEnv("GH_OST_PATH", "gh-ost"),
|
|
Debug: getEnvBool("DEBUG", true),
|
|
}
|
|
}
|
|
|
|
// EnsureJWTSecret 治理默认 JWT 密钥:
|
|
// - 显式配置了 JWT_SECRET(非默认值)→ 直接使用
|
|
// - DEBUG 模式使用默认密钥 → 打印告警(开发便利优先)
|
|
// - 生产模式使用默认密钥 → 自动生成随机密钥并持久化到 system_settings(重启复用;
|
|
// 首次切换会使存量会话失效一次)
|
|
//
|
|
// 必须在 model.DB 初始化并完成 AutoMigrate 之后调用。
|
|
func EnsureJWTSecret() {
|
|
if C.JWTSecret != DefaultJWTSecret {
|
|
return
|
|
}
|
|
if C.Debug {
|
|
log.Println("[WARN] 正在使用默认 JWT 密钥(DEBUG 模式);生产环境请设置 JWT_SECRET 环境变量")
|
|
return
|
|
}
|
|
|
|
var s model.SystemSetting
|
|
if err := model.DB.Where("`key` = ?", "jwt_secret").Take(&s).Error; err == nil && s.Value != "" {
|
|
C.JWTSecret = s.Value
|
|
return
|
|
}
|
|
|
|
buf := make([]byte, 32)
|
|
if _, err := rand.Read(buf); err != nil {
|
|
log.Fatalf("生成随机 JWT 密钥失败: %v", err)
|
|
}
|
|
secret := hex.EncodeToString(buf)
|
|
if err := model.DB.Where(model.SystemSetting{Key: "jwt_secret"}).
|
|
Assign(model.SystemSetting{Value: secret, ValueType: "string", Group: "security", Label: "JWT Secret"}).
|
|
FirstOrCreate(&s).Error; err != nil {
|
|
log.Fatalf("持久化 JWT 密钥失败: %v", err)
|
|
}
|
|
C.JWTSecret = secret
|
|
log.Println("[INFO] 已自动生成随机 JWT 密钥并持久化(未显式配置 JWT_SECRET 的生产部署,存量会话将失效一次)")
|
|
}
|
|
|
|
func getEnv(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func getEnvInt(key string, fallback int) int {
|
|
if v := os.Getenv(key); v != "" {
|
|
if i, err := strconv.Atoi(v); err == nil {
|
|
return i
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func getEnvBool(key string, fallback bool) bool {
|
|
if v := os.Getenv(key); v != "" {
|
|
if b, err := strconv.ParseBool(v); err == nil {
|
|
return b
|
|
}
|
|
}
|
|
return fallback
|
|
}
|