首次提交:姜十三论坛
后端 Go+Gin:认证/CSRF/限流、板块、帖子、评论、点赞、通知、用户资料、置顶推荐;前端 Next.js 16:发帖/编辑/删除、搜索、分页、点赞、通知中心、设置;基础设施 docker-compose 与配置模板;添加 .gitignore 与专有许可证(保留所有权利)
This commit is contained in:
112
backend/config/config.go
Normal file
112
backend/config/config.go
Normal file
@@ -0,0 +1,112 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user