feat: 管理员后台配置 + PostgreSQL 支持
管理员后台: - User 新增 IsAdmin,第一个注册的用户自动成为管理员; 老库升级时自动把最早注册的用户提升为管理员 - 新增 AdminMiddleware 与 /api/v1/admin/config 读写接口 - 可改开放注册、检查间隔、规则重试时长、日志保留条数,保存即生效 - 检查间隔改动后自动重排定时任务,为此把 cron 抽成 handler/scheduler - 监听地址、数据库、会话密钥等启动项不开放,接口只返回只读展示 - 设置页新增"系统配置"区块,仅管理员可见 PostgreSQL: - 新增 database_driver / database_dsn 配置,默认仍为 sqlite - postgres 用独立连接池(20/5/1h),sqlite 保持单连接避免写锁 - 启动时先 Ping,配置写错在启动阶段就报错而不是等到首次查询 - 支持 GOODBABY_DB_DRIVER / GOODBABY_DB_DSN 环境变量, 只给 DSN 时自动按 postgres 处理 文档: 新增 docs/database.md(含 compose 示例与切换说明),README 补充管理员与数据库章节 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+26
-1
@@ -25,8 +25,13 @@ type Config struct {
|
||||
TimeoutDurationHours int `json:"timeout_duration_hours"`
|
||||
// 检查 timer 的间隔(分钟)
|
||||
CheckIntervalMinutes int `json:"check_interval_minutes"`
|
||||
// sqlite 数据库文件路径
|
||||
// 数据库驱动,"sqlite"(默认) 或 "postgres"
|
||||
DatabaseDriver string `json:"database_driver"`
|
||||
// sqlite 数据库文件路径,DatabaseDriver 为 sqlite 时使用
|
||||
DatabasePath string `json:"database_path"`
|
||||
// postgres 连接串,DatabaseDriver 为 postgres 时使用
|
||||
// 例如 postgres://user:pass@localhost:5432/goodbaby?sslmode=disable
|
||||
DatabaseDSN string `json:"database_dsn"`
|
||||
// session 加密密钥,为空时自动生成并写回配置文件
|
||||
SessionSecret string `json:"session_secret"`
|
||||
// session 有效期(小时)
|
||||
@@ -37,12 +42,19 @@ type Config struct {
|
||||
LogRetainCount int `json:"log_retain_count"`
|
||||
}
|
||||
|
||||
// 支持的数据库驱动
|
||||
const (
|
||||
DriverSQLite = "sqlite"
|
||||
DriverPostgres = "postgres"
|
||||
)
|
||||
|
||||
func defaultConfig() Config {
|
||||
return Config{
|
||||
ListenAddr: ":8088",
|
||||
EnableRegistry: true,
|
||||
TimeoutDurationHours: 6,
|
||||
CheckIntervalMinutes: 10,
|
||||
DatabaseDriver: DriverSQLite,
|
||||
DatabasePath: "data.db",
|
||||
SessionMaxAgeHours: 24 * 7,
|
||||
AllowedOrigins: []string{},
|
||||
@@ -135,6 +147,9 @@ func normalize(c *Config) bool {
|
||||
if c.DatabasePath == "" {
|
||||
c.DatabasePath, changed = def.DatabasePath, true
|
||||
}
|
||||
if c.DatabaseDriver == "" {
|
||||
c.DatabaseDriver, changed = def.DatabaseDriver, true
|
||||
}
|
||||
if c.SessionMaxAgeHours <= 0 {
|
||||
c.SessionMaxAgeHours, changed = def.SessionMaxAgeHours, true
|
||||
}
|
||||
@@ -155,6 +170,16 @@ func applyEnvOverrides(c *Config) {
|
||||
if v := os.Getenv("GOODBABY_DB_PATH"); v != "" {
|
||||
c.DatabasePath = v
|
||||
}
|
||||
if v := os.Getenv("GOODBABY_DB_DRIVER"); v != "" {
|
||||
c.DatabaseDriver = v
|
||||
}
|
||||
if v := os.Getenv("GOODBABY_DB_DSN"); v != "" {
|
||||
c.DatabaseDSN = v
|
||||
// 给了连接串却没显式指定驱动时,按 postgres 处理
|
||||
if os.Getenv("GOODBABY_DB_DRIVER") == "" {
|
||||
c.DatabaseDriver = DriverPostgres
|
||||
}
|
||||
}
|
||||
if v := os.Getenv("GOODBABY_SESSION_SECRET"); v != "" {
|
||||
c.SessionSecret = v
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package configs
|
||||
|
||||
import "fmt"
|
||||
|
||||
// EditableConfig 是管理员可以在 WebUI 里修改的配置子集。
|
||||
//
|
||||
// 只收录改完立即生效的项。监听地址、数据库、session 密钥这类需要重启
|
||||
// 或改错会导致服务起不来的配置,只能改配置文件。
|
||||
type EditableConfig struct {
|
||||
EnableRegistry bool `json:"enable_registry"`
|
||||
TimeoutDurationHours int `json:"timeout_duration_hours"`
|
||||
CheckIntervalMinutes int `json:"check_interval_minutes"`
|
||||
LogRetainCount int `json:"log_retain_count"`
|
||||
}
|
||||
|
||||
// Editable 从完整配置中取出可编辑的部分
|
||||
func (c Config) Editable() EditableConfig {
|
||||
return EditableConfig{
|
||||
EnableRegistry: c.EnableRegistry,
|
||||
TimeoutDurationHours: c.TimeoutDurationHours,
|
||||
CheckIntervalMinutes: c.CheckIntervalMinutes,
|
||||
LogRetainCount: c.LogRetainCount,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate 校验管理员提交的配置
|
||||
func (e EditableConfig) Validate() error {
|
||||
if e.TimeoutDurationHours < 1 || e.TimeoutDurationHours > 72 {
|
||||
return fmt.Errorf("规则重试时长必须在 1~72 小时之间")
|
||||
}
|
||||
if e.CheckIntervalMinutes < 1 || e.CheckIntervalMinutes > 1440 {
|
||||
return fmt.Errorf("检查间隔必须在 1~1440 分钟之间")
|
||||
}
|
||||
if e.LogRetainCount < 0 || e.LogRetainCount > 100000 {
|
||||
return fmt.Errorf("日志保留条数必须在 0~100000 之间,0 表示不限制")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyEditable 把可编辑部分合并进当前配置并持久化,返回更新后的完整配置
|
||||
func ApplyEditable(e EditableConfig) (Config, error) {
|
||||
if err := e.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
current, err := GetConfig()
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
current.EnableRegistry = e.EnableRegistry
|
||||
current.TimeoutDurationHours = e.TimeoutDurationHours
|
||||
current.CheckIntervalMinutes = e.CheckIntervalMinutes
|
||||
current.LogRetainCount = e.LogRetainCount
|
||||
|
||||
if err := Save(current); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
return current, nil
|
||||
}
|
||||
Reference in New Issue
Block a user