Documentation
¶
Overview ¶
config.go 站点配置模型与校验:SystemConfig/UserSettings 类型、默认值、 写库前校验(纯函数簇,自 admin 域原样迁入)。
presign_policy_migration.go — 旧直传开关 download.presign_anonymous_enabled 退役(2026-10-26 兼容层清理)。
背景:直传策略先后经历两代形态:
① 旧:download.presign_anonymous_enabled(bool,true=匿名可直传) ② 新:download.presign_policy(三档 everyone/authenticated/disabled)
② 引入时 ① 被保留为回退读路径(显式 ② 为空 ⇒ 读 ①)。本次退役 ① 时, 存量库里仍可能带着 ① 的值:**直接忽略会让「曾关闭匿名直传」的站点 静默变成 everyone**(= 桶沦为免费匿名网盘,见 presign handler 的动机注释)。
处置:**读取时并入 ② + 启动响亮提示**。
- ② 已显式设置 ⇒ 以 ② 为准,① 直接忽略(管理员已在管理台改过策略)。
- ② 未设置(空串)⇒ 由 ① 推导:false→authenticated,true→everyone。
- ① 从数据库消失:管理台整段保存即自然清掉(json 解码忽略未知键, 重新序列化时不带该键)。
Package config 全站配置中心域:system_configs 单行 JSON 的唯一写者。
2026-10-10 自 admin 域独立——此前三段职责挤在 admin:管理面 API、治理操作 (用户/文件/统计/维护)、以及被 user/storage/bootstrap/mcp 四面消费的 全站配置中心(~500 行)。配置中心的归属错位使 admin 成为事实上的上帝域, 且"管理端在线设置"的读写方横跨四个域。独立后:
- 本域持有:内存缓存+懒加载、写穿 DB、校验、全局 conf overlay 热应用、 组件热重建钩子(ReconfigureHooks)、多副本变更广播回调、运行时存储 配置持久化(storage.RuntimePersister 实现)、用户设置生效语义 (EffectiveUserSettings)与配置操作审计。
- admin 域保留管理面 API 门面(委派方法 + 类型别名,消费方零改动)与 治理/审计职责。
- bootstrap(装配层)直接指向本域单例接线。
单实例约束:system_configs 是"单行 JSON 读改写"语义,必须全站单实例—— 多实例各自缓存内存副本会互相覆盖(用户设置写入后公共配置读旧值的 2026-10-03 事故即由此而来)。Default() 是全站唯一实例;NewService() 仅供测试/嵌入式构造独立实例。
stale.go — 陈旧配置键检出(2026-10-22 Wave B':设备直传面退役)。
背景:Wave B' 从 SystemConfig.p2p 段删掉了 direct 子段(p2pc 直传参数)。 Go 的 json 解码**静默忽略**未知键 —— 存量库里若仍有 `{"p2p":{"direct":{"registry_url":"http://p2p:56788",...}}}`,该段会被 无声丢弃:用户以为「注册中心还配着」,实际联邦名录已失去在线对端 (名录的 registry 真相源只剩 p2p.federation.registry_url)。
处置:**忽略 + 启动响亮提示**。本文件只做「检出」,不修复、不删库 (改库是管理员的动作,且 admin 后台整段保存即自然清掉该键)。
Index ¶
- func InitDefault(gormDB *gorm.DB)
- type BaseConfig
- type P2PSection
- type ReconfigureHooks
- type Service
- func (s *Service) EffectiveUploadBatchCap(ctx context.Context) int64
- func (s *Service) GetConfig(ctx context.Context) (*SystemConfig, error)
- func (s *Service) InvalidateRuntimeConfig()
- func (s *Service) LoadRuntimeStorage(ctx context.Context) *conf.StorageConfig
- func (s *Service) RestoreAdminSettings()
- func (s *Service) SaveRuntimeStorage(ctx context.Context, cfg *conf.StorageConfig) error
- func (s *Service) SetConfig(config *SystemConfig)
- func (s *Service) SetOnConfigPersisted(fn func())
- func (s *Service) SetReconfigureHooks(h *ReconfigureHooks)
- func (s *Service) UpdateConfig(ctx context.Context, newConfig *SystemConfig) error
- func (s *Service) UpdateConfigJSON(ctx context.Context, raw []byte) error
- func (s *Service) UpdateUserSettings(ctx context.Context, u UserSettings) error
- func (s *Service) UpdateUserSettingsJSON(ctx context.Context, raw []byte) error
- type StorageSettings
- type SystemConfig
- type TransferSettings
- type UserSettings
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func InitDefault ¶ added in v0.16.0
InitDefault 注册全站配置中心单例(bootstrap 在 InitDatabase 后调用;幂等)。 单实例约束见包注释:system_configs 单行读改写不允许多实例并存。
Types ¶
type BaseConfig ¶ added in v0.18.0
type BaseConfig struct {
Name string `json:"name"`
Description string `json:"description"`
Port int `json:"port"`
// PublicURL 站点公网地址(2026-10-22 落地,可选):用户可访问本柜的对外
// 地址(如 https://box.example.com)。分享链接与「交付文本」按它拼;
// 为空 = 未配置 = 前端回退浏览器当前 origin。
//
// 为什么需要:分享链接原先按**请求 origin** 拼(frontend-core
// ShareResultDialog.vue),NAS/内网部署时管理员从内网访问 → 生成的链接
// 带内网 IP(如 http://10.10.10.3:56789)→ 发给外部用户根本打不开。
// 这是"多部署方式"下最隐蔽的易用性断点。
PublicURL string `json:"public_url"`
}
BaseConfig 站点基础信息段。指针语义与其余段一致:nil = 本次未提交, UpdateConfig 保留现状(值类型无法表达"未提交",局部保存曾把整段清零)。
type P2PSection ¶ added in v0.19.0
type P2PSection struct {
// Federation 联邦控制面(overlay → conf.Federation;nil = 保留现状)
Federation *conf.FederationConfig `json:"federation,omitempty"`
}
P2PSection P2P 能力配置段(spec 2026-10-14 §5)。
2026-10-22 Wave B':直传段(direct)随 Box 侧直传编排层整体退役 —— 本段 只剩联邦段。存量 DB 里若仍有 p2p.direct.*(尤其 registry_url),该段被 **忽略**并在启动日志响亮提示(见 stale.go);注册中心请改配 p2p.federation.registry_url。
type ReconfigureHooks ¶
type ReconfigureHooks struct {
// OnNotifyChanged Webhook+SMTP 热重建(mailer 按新 SMTP 段重建,host 空=卸载)
OnNotifyChanged func(n *conf.NotifyConfig)
// OnOIDCChanged OIDC service 热重建(按新段;Enabled=false 时 handler 走未启用响应)
OnOIDCChanged func(o *conf.OIDCConfig)
// OnP2PFederationChanged federation 运行时热切换(bootstrap 注册;P1 对称节点
// spec §6)。返回 error 时 UpdateConfig 透传给管理员——切换失败但配置已持久化
// 的分叉必须响亮(spec §12.4),禁止静默。
OnP2PFederationChanged func(f *conf.FederationConfig) error
// OnOverlayApplied SystemConfig 段 overlay 进全局 conf 之后的回调(bootstrap
// 注册 env 恒覆盖重施器:统一优先级 yaml < DB < env,2026-10-15)。持 configMu
// 同步调用,实现方只许操作全局 conf/env,不得回调 config service。
OnOverlayApplied func()
}
ReconfigureHooks 存储域之外的组件热重建钩子(bootstrap 注册;SMTP mailer / OIDC service)。 参数为合并后的新段;nil 段表示维持现状。
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service 配置中心服务。
func DefaultSafe ¶ added in v0.32.30
func DefaultSafe() *Service
DefaultSafe 未装配时返回 nil(**不** panic)——供「可选读取」场景使用。
与 Default 的区别:Default 是 fail-loud(业务路径拿不到配置中心 = 装配错误, 必须当场炸出来);DefaultSafe 供**确实可能没有配置中心**的读取点, 例如能力快照(bootstrap 早期阶段、单元测试未 InitDefault)。 ⚠️ 用它的地方必须自己处理 nil(缺配置 ≠ 崩溃,但也不能假装有值)。
func NewService ¶
NewService 构造独立实例(测试/嵌入式;全站运行时请用 Default()—— 单行读改写语义不允许多实例并存,见包注释)。
func (*Service) EffectiveUploadBatchCap ¶ added in v0.32.31
EffectiveUploadBatchCap 生效的站点默认「单次发送**总量**上限」(字节,0=不限)。
与 upload.max_file_size(**单文件**上限)是两个不同的闸:
· max_file_size:单个文件的体积上限 · 本值:一次发送(多文件合计)的体积上限,**只约束登录用户** (匿名走访客日配额 upload.anonymous_daily_bytes)
取值 = 管理后台持久化的 user 段(无记录回退 yaml),与 user 域 GetUploadSizeCap 的**系统默认层**同源;**不含** per-user 覆盖 (users.max_upload_size 是登录用户私有值,不上公开面——服务端 413 才是权威, 本值只用于前端**提示**)。
func (*Service) GetConfig ¶
func (s *Service) GetConfig(ctx context.Context) (*SystemConfig, error)
GetConfig 获取系统配置(首次调用会从 DB 加载管理后台保存的配置)
func (*Service) InvalidateRuntimeConfig ¶
func (s *Service) InvalidateRuntimeConfig()
InvalidateRuntimeConfig 丢弃内存配置缓存(仅影响缓存,不写库)。 多副本:public 副本收到管理端变更广播后调用,下次 GetConfig 重读 DB—— 单机 standalone 不调用(保持既有"进程内即真相"语义)。
func (*Service) LoadRuntimeStorage ¶
func (s *Service) LoadRuntimeStorage(ctx context.Context) *conf.StorageConfig
LoadRuntimeStorage 读取持久化的运行时存储配置(storage.RuntimePersister 实现)。 无记录/未设置返回 nil;DB 不可用返回 nil(与配置加载的降级策略一致)。
func (*Service) RestoreAdminSettings ¶
func (s *Service) RestoreAdminSettings()
RestoreAdminSettings 启动时把持久化的新段 overlay 回全局 conf(DB 先于服务启动就绪)。 由 bootstrap 在 restoreRuntimeStorage 同相位调用。
func (*Service) SaveRuntimeStorage ¶
SaveRuntimeStorage 持久化运行时存储配置(storage.RuntimePersister 实现)。 读改写单行 JSON,只更新 runtime_storage 段;DB 不可用时返回错误, 由调用方(存储域)决定提示语义——切换已生效,仅持久化失败。
func (*Service) SetConfig ¶
func (s *Service) SetConfig(config *SystemConfig)
SetConfig 设置配置(显式注入优先于 DB 持久化配置)
func (*Service) SetOnConfigPersisted ¶
func (s *Service) SetOnConfigPersisted(fn func())
SetOnConfigPersisted 注入配置持久化回调(bootstrap 调用)。多副本拆分时 admin 实例经它发布 Redis 变更广播,public 副本订阅后失效本地缓存并热重建 (详见 docs/specs/2026-10-06-multi-replica-deployment-modes.md §4)。
func (*Service) SetReconfigureHooks ¶
func (s *Service) SetReconfigureHooks(h *ReconfigureHooks)
SetReconfigureHooks 注册组件热重建钩子(bootstrap 调用)。
func (*Service) UpdateConfig ¶
func (s *Service) UpdateConfig(ctx context.Context, newConfig *SystemConfig) error
UpdateConfig 更新系统配置:写穿到 DB(单行),成功后才更新内存。 防御性合并:handler 会从请求重建 SystemConfig,请求不含 runtime_storage (存储域维护的运行时段),为 nil 时保留旧值,避免保存站点配置时被冲掉。
func (*Service) UpdateConfigJSON ¶ added in v0.31.3
UpdateConfigJSON 按**字段级**合并提交的 JSON 到已落库配置后保存。
此前 PUT /admin/config 的语义只到「段级」:nil 段保留现状,但**段内**未提及的 字段会被**整体清零**(JSON 反序列化进结构体 ⇒ 未提及字段取零值)。实测后果 (2026-10-23 真机):
PUT {"upload_ex":{"max_file_size":1024}}
⇒ upload_ex 其余字段全部归零 ⇒ open_upload=false(匿名上传被"关闭",
/share/file/ 立刻 10012)、enable_chunk=false、enable_magic_check=false、
text_max_bytes=0、local_import.roots 清空……
⇒ 看起来只改了一个字段,实际把整段配置弄没了。
新语义(**字段级合并**):
- 提交里**出现**的字段生效(显式 null 视为"清空该字段" ⇒ **切片/指针项 可清空**;⚠️ **标量字段的 null 是 no-op** —— Go 的 json 把 null 解进 非指针标量不改值(那些字段的 0 本身就是"不限/关闭"语义,清不清等价);
- 提交里**未出现**的字段保留现状(含嵌套对象:改 notify.smtp.host 不会把 同级的 smtp.port/smtp.username 清零);
- 未提交的段整体保留。
合并底座用**已落库**的配置(不是 overlay 后的运行态:overlay 含 env 种子, 把它当底座会把 env 值固化进库)。
func (*Service) UpdateUserSettings ¶
func (s *Service) UpdateUserSettings(ctx context.Context, u UserSettings) error
UpdateUserSettings 在线更新"用户配置"段:读改写当前配置,仅替换 User, 其余段保持不变;写穿 DB 并即时应用副作用(会话时长)。
func (*Service) UpdateUserSettingsJSON ¶ added in v0.31.4
UpdateUserSettingsJSON 按**字段级**合并提交的 JSON 到当前用户配置段后保存。
与 UpdateConfigJSON 同一原则:部分提交**不得**把未提及的字段清零 —— 例如只提交 {"allowuserregistration":true},此前会把 sessionexpiryhours / useruploadsize / userstoragequota 一并清零(0 = 走包默认/不限)⇒ 看着只改 一个开关,实际把会话时长等配置弄没了(2026-10-23 真机实测同类问题)。
合并底座 = 当前**生效**的用户配置(已在线配置的取持久化值,未配置的取 yaml 默认)⇒ 合并后未提及的字段保持当前生效值。
type StorageSettings ¶ added in v0.18.0
StorageSettings 存储概览段(nil 语义同 BaseConfig;运行时存储后端走 runtime_storage)。
type SystemConfig ¶
type SystemConfig struct {
Base *BaseConfig `json:"base,omitempty"`
Storage *StorageSettings `json:"storage,omitempty"`
Transfer *TransferSettings `json:"transfer,omitempty"`
// RuntimeStorage 运行时存储配置(存储域经 RuntimePersister 接口读写,
// 本域只负责持久化不解释其内容;nil 表示管理端从未在线改过存储后端)。
RuntimeStorage *conf.StorageConfig `json:"runtime_storage,omitempty"`
// User 用户设置(注册开关/上传限制/存储配额/会话时长)。
// nil 表示从未在线保存过 → 运行时回退 yaml 全局配置(EffectiveUserSettings)。
// JSON 键沿用前端 configForm.user 的既有键名,读写端点直传免转换。
User *UserSettings `json:"user,omitempty"`
// ===== v0.7.3 扩容的在线设置段(同 User 语义:nil = 未在线设置 → yaml 生效;
// 非 nil = 保存时整体 overlay 到全局 conf 并热应用,重启由 restoreAdminSettings 恢复)=====
// 外观(背景图/主题色/管理入口可见性)
UI *conf.UIConfig `json:"ui,omitempty"`
// 上传细项(文本上限/扩展名白黑名单/匿名日配额/整文件上限/全局过期上限/过期样式裁剪)
UploadEx *conf.UploadConfig `json:"upload_ex,omitempty"`
// 下载(S3 直下/超时/需登录)
Download *conf.DownloadConfig `json:"download,omitempty"`
// 通知(Webhook + SMTP;保存经 Reconfigurers 热重建 mailer)
Notify *conf.NotifyConfig `json:"notify,omitempty"`
// 本地导入(enabled/roots)
LocalImport *conf.LocalImportConfig `json:"local_import,omitempty"`
// OIDC 单点登录
OIDC *conf.OIDCConfig `json:"oidc,omitempty"`
// API Key 认证总开关与 per-Key 限流
APIToken *conf.APITokenConfig `json:"api_token,omitempty"`
// P2P P2P 能力段(2026-10-14 对称节点 spec §5):Federation 控制面在线开关;
// 直传数据面参数(enabled/send_roots/…)随 P2a 落地。nil = 未在线设置。
P2P *P2PSection `json:"p2p,omitempty"`
}
type TransferSettings ¶ added in v0.18.0
type TransferSettings struct {
MaxCount int `json:"max_count"`
ExpireDefault int `json:"expire_default"`
}
TransferSettings 分享传输默认值段(nil 语义同 BaseConfig)。
type UserSettings ¶
type UserSettings struct {
// AllowUserRegistration 是否开放注册(/user/register 与 /api/config 同源读取)
AllowUserRegistration bool `json:"allowuserregistration"`
// UserUploadSize 用户单次上传大小默认上限(字节,0 = 不限)
UserUploadSize int64 `json:"useruploadsize"`
// UserStorageQuota 用户存储配额默认值(字节,0 = 不限)
UserStorageQuota int64 `json:"userstoragequota"`
// SessionExpiryHours 会话时长(小时,0 = auth 包默认 7 天)
SessionExpiryHours int `json:"sessionexpiryhours"`
}
UserSettings 管理后台"用户配置"标签页的在线设置。 语义:作为对应运行时消费点的系统级默认值(用户级覆盖仍然优先)。
func EffectiveUserSettings ¶
func EffectiveUserSettings(ctx context.Context) UserSettings
EffectiveUserSettings 生效的用户设置:优先管理后台持久化值, 无记录时回退 yaml 全局配置。注册开关、配额默认值、会话时长的 运行时消费点统一走这里(修复管理端开关不生效)。