Files
SKYMirror 6f1a925833
Release 镜像 / 测试(推送前置门禁) (push) Failing after 12s
Release 镜像 / 多架构构建并推送 ACR (push) Skipped
26.9:品牌统一(fileshare)+ 版本号改为日期式
- 数据库默认文件 filecodebox.db → fileshare.db(config.go 默认值与全部文档/编排同步)
- Go module filecodebox → fileshare(全部 import 同步,build/vet/test 全绿)
- 应用版本 APP_VERSION 2.5.6 → 26.9(health 接口已验证返回 26.9)
- deploy 编排统一:compose 项目名、Postgres 默认凭据、minio 桶名、env 注释
- JWT issuer、存储临时目录前缀、web 包名同步 fileshare
- CI:镜像 tag 以 APP_VERSION 为唯一版本源,main/tag 推送即发布
  ${VER} + latest;tag 触发时校验 tag 名与 APP_VERSION 一致,防错版
- 本地开发库文件已改名 fileshare.db(含 -shm/-wal 清理)
2026-09-05 06:32:18 +08:00

173 lines
5.1 KiB
Go
Raw Permalink 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 settings 提供数据库 settings KV 的运行时读写:
// envFCB_*)提供基线,DB KV 覆盖可变项;管理端修改后立即生效。
package settings
import (
"context"
"encoding/json"
"errors"
"log"
"sync"
"time"
"gorm.io/gorm"
"fileshare/internal/config"
"fileshare/internal/model"
)
// settingsKey 数据库中的配置键(对齐参考实现)。
const settingsKey = "settings"
// Manager 设置管理器:线程安全,缓存 KV 覆盖到内存。
type Manager struct {
db *gorm.DB
cfg *config.Config
mu sync.RWMutex
secret string // jwt_secret(频繁使用,单独缓存)
initPwd string // admin_token 哈希(频繁使用,单独缓存)
}
// NewManager 构造设置管理器并加载 DB KV。
// ensure 默认配置行(首次启动时写入 settings 键)。
func NewManager(ctx context.Context, db *gorm.DB, cfg *config.Config) (*Manager, error) {
m := &Manager{db: db, cfg: cfg}
if err := m.ensureSettingsRow(ctx); err != nil {
return nil, err
}
if err := m.Reload(ctx); err != nil {
return nil, err
}
return m, nil
}
// ensureSettingsRow 首次启动时把默认安全配置写入 KV(对齐 ensure_settings_row)。
func (m *Manager) ensureSettingsRow(ctx context.Context) error {
var row model.KeyValue
err := m.db.WithContext(ctx).Where(model.KeyValue{Key: settingsKey}).First(&row).Error
if err == nil {
return nil
}
// 双方言:必须用 errors.Is 判定(sqlite 驱动错误链与字符串消息与 postgres 不同)
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
// 不存在:写入初始配置(不含 admin_token/jwt_secret,保持未初始化状态)
initial := map[string]any{}
raw, _ := json.Marshal(initial)
row = model.KeyValue{Key: settingsKey, Value: strPtr(string(raw))}
if err := m.db.WithContext(ctx).Create(&row).Error; err != nil {
return err
}
log.Println("[settings] 系统尚未初始化,请在浏览器中打开站点并完成管理员密码设置")
return nil
}
// Reload 从数据库加载 settings KV 并覆盖到运行时配置。
func (m *Manager) Reload(ctx context.Context) error {
var row model.KeyValue
err := m.db.WithContext(ctx).Where(model.KeyValue{Key: settingsKey}).First(&row).Error
if err != nil {
// 行不存在时保持现有覆盖(双方言:errors.Is 判定)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return err
}
kv := map[string]any{}
if row.Value != nil && *row.Value != "" {
if err := json.Unmarshal([]byte(*row.Value), &kv); err != nil {
log.Printf("[settings] settings KV 解析失败: %v", err)
}
}
// 内部键不允许通过 KV 覆盖(_ 开头)
safe := map[string]any{}
for k, v := range kv {
if len(k) > 0 && k[0] == '_' {
continue
}
safe[k] = v
}
m.mu.Lock()
m.cfg.ApplyKV(safe)
m.secret, _ = safe["jwt_secret"].(string)
m.initPwd, _ = safe["admin_token"].(string)
m.mu.Unlock()
return nil
}
// Get 返回当前配置(只读使用;不要修改返回值)。
func (m *Manager) Get() *config.Config { return m.cfg }
// SecretProvider 返回 jwt_secret 读取函数(JWT 中间件用)。
func (m *Manager) SecretProvider() func() string {
return func() string {
m.mu.RLock()
defer m.mu.RUnlock()
return m.secret
}
}
// IsInitialized 系统是否已完成初始化(管理员密码已设置且非默认密码)。
func (m *Manager) IsInitialized() bool {
m.mu.RLock()
defer m.mu.RUnlock()
if m.initPwd == "" {
return false
}
// 旧版默认密码视为未初始化(对齐 LEGACY_DEFAULT_ADMIN_TOKEN 检查)
return !verifyLegacyDefault(m.initPwd)
}
// legacyDefaultToken 参考实现的旧默认管理员密码。
const legacyDefaultToken = "FileCodeBox2023"
// verifyLegacyDefault 检查哈希是否对应旧默认密码。
func verifyLegacyDefault(hashed string) bool {
if hashed == "" {
return false
}
return VerifyPassword(legacyDefaultToken, hashed)
}
// UpdateKV 合并更新 settings KV(管理端保存配置)。
func (m *Manager) UpdateKV(ctx context.Context, patch map[string]any) error {
m.mu.Lock()
defer m.mu.Unlock()
// 读现有值
var row model.KeyValue
err := m.db.WithContext(ctx).Where(model.KeyValue{Key: settingsKey}).First(&row).Error
kv := map[string]any{}
if err == nil && row.Value != nil {
_ = json.Unmarshal([]byte(*row.Value), &kv)
}
for k, v := range patch {
if len(k) > 0 && k[0] == '_' {
continue
}
kv[k] = v
}
raw, err := json.Marshal(kv)
if err != nil {
return err
}
if err == nil && row.ID > 0 {
row.Value = strPtr(string(raw))
return m.db.WithContext(ctx).Model(&row).Update("value", row.Value).Error
}
row = model.KeyValue{Key: settingsKey, Value: strPtr(string(raw))}
return m.db.WithContext(ctx).Create(&row).Error
}
// SystemStart 记录系统启动时间(对齐 sys_start 键)。
func (m *Manager) SystemStart(ctx context.Context) {
now := time.Now().UnixMilli()
raw, _ := json.Marshal(now)
_ = m.db.WithContext(ctx).Where(model.KeyValue{Key: "sys_start"}).
Assign(model.KeyValue{Value: strPtr(string(raw))}).
FirstOrCreate(&model.KeyValue{Key: "sys_start"}).Error
}
func strPtr(s string) *string { return &s }