Files
mce/internal/config/config.go
Victor_Jay 2449a27eb5
Some checks failed
CI / Lint + Build (push) Has been cancelled
refactor: CSP nonce替代unsafe-inline, HSTS始终启用, router/api拆分, 管理后台+用户设置页拆分, DB健康降级, 时区配置, UID统一解析, CI接入
审计修复:
- CSP: unsafe-inline移除, nonce替代; unsafe-eval保留供Vditor使用
- HSTS: 始终启用(不再依赖release模式)
- Controller Exp: common.GetGinExp包装, 不再直接读context
- UID解析: common.ParseUIDParam统一, follow/admin controller改用

重构:
- router/api.go(198行)拆为7个域文件: auth/settings/posts/comments/reactions/social/studio
- 管理后台站点设置: 单页拆为4个子页(brand/security/registration/content)+子菜单
- 前台用户设置页: 1201行拆为6个独立模板+6个独立JS文件

新增:
- DB健康检测中间件(middleware/db_health.go): 5s ping+降级页面
- 时区配置: config.yaml server.timezone→time.Local初始化
- .gitea/workflows/ci.yml: strict模式CI流水线
2026-06-22 03:06:24 +08:00

179 lines
5.4 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 config 管理应用配置的加载、解析和运行时访问。
package config
import (
"bufio"
"log"
"os"
"strings"
"github.com/spf13/viper"
)
// Config 应用总配置
type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
Session SessionConfig `mapstructure:"session"`
Redis RedisConfig `mapstructure:"redis"`
Bcrypt BcryptConfig `mapstructure:"bcrypt"`
Roles RolesConfig `mapstructure:"roles"`
Audit AuditConfig `mapstructure:"audit"`
}
// AuditConfig 审核系统配置config.yaml 提供默认值,站点设置可运行时覆盖)
type AuditConfig struct {
Enabled bool `mapstructure:"enabled"`
UsernameAudit bool `mapstructure:"username_audit"`
AvatarAudit bool `mapstructure:"avatar_audit"`
BioAudit bool `mapstructure:"bio_audit"`
}
type ServerConfig struct {
Port string `mapstructure:"port"`
Mode string `mapstructure:"mode"`
CookieSecure bool `mapstructure:"cookie_secure"`
Timezone string `mapstructure:"timezone"`
}
type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port string `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
DBName string `mapstructure:"dbname"`
SSLMode string `mapstructure:"sslmode"`
}
// SessionConfig 服务端会话配置
type SessionConfig struct {
IdleTimeout int `mapstructure:"idle_timeout"` // 不记住我:空闲超时(分钟),默认 144024小时
RememberTimeout int `mapstructure:"remember_timeout"` // 记住我:空闲超时(分钟),默认 4320030天
CleanupInterval int `mapstructure:"cleanup_interval"` // 后台清理间隔(秒),默认 300仅内存模式使用
}
// RedisConfig Redis 连接配置
type RedisConfig struct {
Enabled bool `mapstructure:"enabled"`
Host string `mapstructure:"host"`
Port string `mapstructure:"port"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
}
type BcryptConfig struct {
Cost int `mapstructure:"cost"`
}
// RolesConfig 角色配置(可扩展,新增角色只需改 config.yaml
type RolesConfig struct {
Levels map[string]int `mapstructure:"levels"`
Names map[string]string `mapstructure:"names"`
Permissions RolesPermissions `mapstructure:"permissions"`
}
// RolesPermissions 角色间操作权限
type RolesPermissions struct {
OperableRoles map[string][]string `mapstructure:"operable_roles"`
}
// App 全局配置实例(初始化后只读)。
var App *Config
// Load 加载配置config.yaml → .env 覆盖
func Load(configPath string) *Config {
// 1. 从 .env 加载环境变量(优先于 config.yaml
loadEnvFile(".env")
v := viper.New()
// 2. 读取 config.yaml
v.SetConfigFile(configPath)
v.SetConfigType("yaml")
if err := v.ReadInConfig(); err != nil {
log.Fatalf("读取配置文件失败: %v", err)
}
// 3. 环境变量覆盖DATABASE_PASSWORD → database.password
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
bindEnvOverride(v)
c := &Config{}
if err := v.Unmarshal(c); err != nil {
log.Fatalf("解析配置失败: %v", err)
}
log.Printf("[Config] Session.IdleTimeout=%d min Session.RememberTimeout=%d min Session.CleanupInterval=%d s | Server.Mode=%s",
c.Session.IdleTimeout, c.Session.RememberTimeout, c.Session.CleanupInterval, c.Server.Mode)
App = c
return c
}
// loadEnvFile 读取 .env 文件并设置环境变量(仅当环境变量未设置时)
func loadEnvFile(path string) {
f, err := os.Open(path) //nolint:gosec // path 为内部 .env 文件路径
if err != nil {
return // .env 不存在,跳过
}
defer func() { _ = f.Close() }()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
val := strings.TrimSpace(parts[1])
if os.Getenv(key) == "" {
_ = os.Setenv(key, val)
}
}
}
// bindEnvOverride 将环境变量映射到 config 的嵌套键(.env 覆盖 config.yaml 默认值)
func bindEnvOverride(v *viper.Viper) {
// 数据库
_ = v.BindEnv("database.host", "DATABASE_HOST")
_ = v.BindEnv("database.port", "DATABASE_PORT")
_ = v.BindEnv("database.user", "DATABASE_USER")
_ = v.BindEnv("database.password", "DATABASE_PASSWORD")
_ = v.BindEnv("database.dbname", "DATABASE_DBNAME")
_ = v.BindEnv("database.sslmode", "DATABASE_SSLMODE")
// Redis
_ = v.BindEnv("redis.enabled", "REDIS_ENABLED")
_ = v.BindEnv("redis.host", "REDIS_HOST")
_ = v.BindEnv("redis.port", "REDIS_PORT")
_ = v.BindEnv("redis.password", "REDIS_PASSWORD")
_ = v.BindEnv("redis.db", "REDIS_DB")
// 服务
_ = v.BindEnv("server.port", "SERVER_PORT")
_ = v.BindEnv("server.mode", "SERVER_MODE")
}
// GetIdleTimeout 返回临时会话空闲超时(分钟),实现 controller.sessionConfig 接口
func (c *Config) GetIdleTimeout() int {
return c.Session.IdleTimeout
}
// GetRememberTimeout 返回记住我会话空闲超时(分钟)
func (c *Config) GetRememberTimeout() int {
return c.Session.RememberTimeout
}
// DSN 返回 PostgreSQL 连接字符串
func (d DatabaseConfig) DSN() string {
return "host=" + d.Host +
" port=" + d.Port +
" user=" + d.User +
" password=" + d.Password +
" dbname=" + d.DBName +
" sslmode=" + d.SSLMode
}