Files
jiang13-bbs/backend/config/config.go
freefire 0c2b0d2d6a 首次提交:姜十三论坛
后端 Go+Gin:认证/CSRF/限流、板块、帖子、评论、点赞、通知、用户资料、置顶推荐;前端 Next.js 16:发帖/编辑/删除、搜索、分页、点赞、通知中心、设置;基础设施 docker-compose 与配置模板;添加 .gitignore 与专有许可证(保留所有权利)
2026-09-12 01:11:05 +08:00

113 lines
2.6 KiB
Go

package config
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"gopkg.in/ini.v1"
)
// Config 应用全局配置
type Config struct {
WorkPath string
Port int
DataDir string
JWTSecret string
DBDSN string
DevMode bool
}
// Parse 解析配置:环境变量 > app.ini > 默认值
func Parse() (*Config, error) {
workPath, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("获取工作目录失败: %w", err)
}
// 默认值
cfg := &Config{
WorkPath: workPath,
Port: 3001,
DataDir: filepath.Join(workPath, "data"),
DBDSN: "postgres://postgres:postgres@localhost:5432/jiang13?sslmode=disable",
DevMode: true,
}
// 尝试加载 app.ini
iniPath := filepath.Join(workPath, "app.ini")
if _, err := os.Stat(iniPath); err == nil {
f, err := ini.Load(iniPath)
if err != nil {
return nil, fmt.Errorf("加载 app.ini 失败: %w", err)
}
if s := f.Section("server"); s.HasKey("HTTP_PORT") {
if v, err := s.Key("HTTP_PORT").Int(); err == nil {
cfg.Port = v
}
}
if s := f.Section("database"); s.HasKey("DSN") {
cfg.DBDSN = s.Key("DSN").String()
}
if s := f.Section("security"); s.HasKey("JWT_SECRET") {
cfg.JWTSecret = s.Key("JWT_SECRET").String()
}
if s := f.Section("paths"); s.HasKey("DATA") {
dataRel := s.Key("DATA").String()
if !filepath.IsAbs(dataRel) {
cfg.DataDir = filepath.Join(workPath, dataRel)
} else {
cfg.DataDir = dataRel
}
}
if s := f.Section("app"); s.HasKey("DEV_MODE") {
cfg.DevMode = s.Key("DEV_MODE").MustBool(true)
}
}
// 环境变量覆盖
if v := os.Getenv("HTTP_PORT"); v != "" {
if p, err := strconv.Atoi(v); err == nil {
cfg.Port = p
}
}
if v := os.Getenv("DB_DSN"); v != "" {
cfg.DBDSN = v
}
if v := os.Getenv("JWT_SECRET"); v != "" {
cfg.JWTSecret = v
}
if v := os.Getenv("DEV_MODE"); v != "" {
cfg.DevMode = strings.EqualFold(v, "true") || v == "1"
}
// 确保数据目录存在
if err := os.MkdirAll(cfg.DataDir, 0755); err != nil {
return nil, fmt.Errorf("创建数据目录失败: %w", err)
}
// JWT 密钥:留空则自动生成并持久化
if strings.TrimSpace(cfg.JWTSecret) == "" {
secretFile := filepath.Join(cfg.DataDir, ".jwt_secret")
if data, err := os.ReadFile(secretFile); err == nil && len(data) > 0 {
cfg.JWTSecret = string(data)
} else {
cfg.JWTSecret = generateSecret(32)
_ = os.WriteFile(secretFile, []byte(cfg.JWTSecret), 0600)
}
}
return cfg, nil
}
func generateSecret(n int) string {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
for i := range b {
b[i] = chars[i%len(chars)]
}
return string(b)
}