security: stop tracking env files; admin gate via VITE_SITE_ADMIN_GATE; Redis/cache/docs

Remove InfoGenie-frontend and Go .env from version control; add .env.example templates; ignore .claude local settings. Admin UI reads site gate from env only. Note: rotate secrets if repo history was ever public.

Made-with: Cursor
This commit is contained in:
2026-04-03 16:10:12 +08:00
parent 284b5a5260
commit 6b3fcc1791
25 changed files with 1078 additions and 972 deletions

View File

@@ -5,6 +5,7 @@ import (
"os"
"strconv"
"strings"
"time"
"github.com/joho/godotenv"
)
@@ -17,10 +18,21 @@ type AppConfig struct {
Mail MailConfig
AI AIConfig
AuthCenter AuthCenterConfig
Redis RedisConfig
// SiteAdminToken 与前端管理员口令一致,用于更新站点展示配置(如 60s 功能开关);为空则禁止写入
SiteAdminToken string
}
// RedisConfig 可选缓存Enabled 为 false 时不连接 Redis行为与未接入缓存时一致
type RedisConfig struct {
Enabled bool
Addr string
Password string
DB int
KeyPrefix string
SiteTTL time.Duration
}
type DBConfig struct {
Host string
Port string
@@ -67,6 +79,10 @@ const (
defaultDevDBName = "infogenie-test"
defaultDevDBUser = "infogenie-test"
defaultDevDBPassword = "infogenie-test"
defaultDevRedisAddr = "10.1.1.100:6379"
defaultRedisDB = 10
defaultRedisKeyPrefix = "infogenie:go:v1:"
)
func Load() (*AppConfig, error) {
@@ -129,6 +145,12 @@ func Load() (*AppConfig, error) {
return nil, err
}
redisCfg, err := loadRedisConfig(env)
if err != nil {
return nil, err
}
cfg.Redis = redisCfg
// AI配置现在完全从数据库读取不再加载ai_config.json文件
cfg.AI = AIConfig{Providers: make(map[string]AIProviderConfig)}
@@ -136,6 +158,53 @@ func Load() (*AppConfig, error) {
return cfg, nil
}
func loadRedisConfig(env string) (RedisConfig, error) {
if !parseBoolEnv(os.Getenv("REDIS_ENABLED")) {
return RedisConfig{}, nil
}
var addr string
var err error
if env == envProduction {
addr, err = getEnvByEnvironment(env, "REDIS_ADDR", "")
if err != nil {
return RedisConfig{}, err
}
} else {
addr = getEnv("REDIS_ADDR", defaultDevRedisAddr)
}
if strings.TrimSpace(addr) == "" {
return RedisConfig{}, fmt.Errorf("已启用 REDIS_ENABLED 但 REDIS_ADDR 为空")
}
dbIdx, err := strconv.Atoi(strings.TrimSpace(getEnv("REDIS_DB", strconv.Itoa(defaultRedisDB))))
if err != nil || dbIdx < 0 {
return RedisConfig{}, fmt.Errorf("无效的 REDIS_DB: %s", getEnv("REDIS_DB", ""))
}
ttlSec, err := strconv.Atoi(strings.TrimSpace(getEnv("REDIS_SITE_TTL", "60")))
if err != nil || ttlSec < 1 {
return RedisConfig{}, fmt.Errorf("无效的 REDIS_SITE_TTL: %s", getEnv("REDIS_SITE_TTL", ""))
}
prefix := strings.TrimSpace(getEnv("REDIS_KEY_PREFIX", defaultRedisKeyPrefix))
if prefix == "" {
prefix = defaultRedisKeyPrefix
}
if !strings.HasSuffix(prefix, ":") {
prefix += ":"
}
return RedisConfig{
Enabled: true,
Addr: addr,
Password: getEnv("REDIS_PASSWORD", ""),
DB: dbIdx,
KeyPrefix: prefix,
SiteTTL: time.Duration(ttlSec) * time.Second,
}, nil
}
func parseBoolEnv(raw string) bool {
s := strings.ToLower(strings.TrimSpace(raw))
return s == "1" || s == "true" || s == "yes" || s == "on"
}
func loadEnvFile(env string) error {
envFile := fmt.Sprintf(".env.%s", env)
if _, err := os.Stat(envFile); err == nil {