Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.7.1
1.8.2
16 changes: 4 additions & 12 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
base:
name: FileCodeBox
description: 开箱即用的文件快传系统
keywords: ""
keywords: FileCodeBox, 文件快递柜, 口令传送箱, 匿名口令分享文本, 文件
port: 12345
host: 0.0.0.0
datapath: /Users/zhangyi/zy/FileCodeBox/data
Expand Down Expand Up @@ -44,8 +44,6 @@ mcp:
enablemcpserver: 0
mcpport: ""
mcphost: ""
notifytitle: ""
notifycontent: ""
ui:
themes_select: themes/2025
background: ""
Expand All @@ -54,15 +52,9 @@ ui:
User-agent: *
Disallow: /
show_admin_addr: 0
opacity: 0
themes_select: themes/2025
robots_text: |-
User-agent: *
Disallow: /
page_explain: 请勿上传或分享违法内容。根据《中华人民共和国网络安全法》、《中华人民共和国刑法》、《中华人民共和国治安管理处罚法》等相关规定。 传播或存储违法、违规内容,会受到相关处罚,严重者将承担刑事责任。本站坚决配合相关部门,确保网络内容的安全,和谐,打造绿色网络环境。
show_admin_addr: 0
opacity: 0
background: ""
opacity: 1
notify_title: ""
notify_content: ""
sys_start: ""
upload_minute: 0
upload_count: 0
Expand Down
4 changes: 2 additions & 2 deletions docs/swagger-enhanced.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ swagger: "2.0"
info:
title: "FileCodeBox API"
description: "FileCodeBox 是一个用于文件分享和代码片段管理的 Web 应用程序"
version: "1.7.1"
version: "1.8.2"
termsOfService: "http://swagger.io/terms/"
contact:
name: "API Support"
Expand Down Expand Up @@ -69,7 +69,7 @@ paths:
example: "2025-09-11T10:00:00Z"
version:
type: "string"
example: "1.7.1"
example: "1.8.2"
uptime:
type: "string"
example: "2h30m15s"
Expand Down
4 changes: 2 additions & 2 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"name": "MIT",
"url": "https://github.com/zy84338719/filecodebox/blob/main/LICENSE"
},
"version": "1.7.1"
"version": "1.8.2"
},
"host": "localhost:12345",
"basePath": "/",
Expand Down Expand Up @@ -553,7 +553,7 @@
},
"version": {
"type": "string",
"example": "1.7.1"
"example": "1.8.2"
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ definitions:
example: 2h30m15s
type: string
version:
example: 1.7.1
example: 1.8.2
type: string
type: object
handlers.SystemConfig:
Expand Down Expand Up @@ -57,7 +57,7 @@ info:
url: https://github.com/zy84338719/filecodebox/blob/main/LICENSE
termsOfService: http://swagger.io/terms/
title: FileCodeBox API
version: "1.7.1"
version: "1.8.2"
paths:
/api/config:
get:
Expand Down
69 changes: 24 additions & 45 deletions internal/config/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package config
import (
"errors"
"os"
"strconv"
"strings"

"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -56,15 +55,17 @@ func NewConfigManager() *ConfigManager {
func InitManager() *ConfigManager {
cm := NewConfigManager()

// 尝试加载 YAML 配置文件
var sources []ConfigSource

if configPath := os.Getenv("CONFIG_PATH"); configPath != "" {
_ = cm.LoadFromYAML(configPath)
sources = append(sources, YAMLFileSource{Path: configPath})
} else if _, err := os.Stat("./config.yaml"); err == nil {
_ = cm.LoadFromYAML("./config.yaml")
sources = append(sources, YAMLFileSource{Path: "./config.yaml"})
}

// 应用环境变量覆盖
cm.applyEnvironmentOverrides()
sources = append(sources, NewDefaultEnvSource())

_ = cm.ApplySources(sources...)
return cm
}

Expand Down Expand Up @@ -135,24 +136,24 @@ func (cm *ConfigManager) mergeSimpleFields(fileCfg *ConfigManager) {
}
}

// LoadFromYAML 从 YAML 文件加载配置
func (cm *ConfigManager) LoadFromYAML(path string) error {
b, err := os.ReadFile(path)
if err != nil {
return err
}

var fileCfg ConfigManager
if err := yaml.Unmarshal(b, &fileCfg); err != nil {
return err
// ApplySources processes a group of configuration sources and collects errors.
func (cm *ConfigManager) ApplySources(sources ...ConfigSource) error {
var errs []error
for _, source := range sources {
if source == nil {
continue
}
if err := source.Apply(cm); err != nil {
errs = append(errs, err)
}
}

// 按模块合并配置
cm.mergeConfigModules(&fileCfg)
cm.mergeUserConfig(fileCfg.User)
cm.mergeSimpleFields(&fileCfg)
return errors.Join(errs...)
}

return nil
// LoadFromYAML 从 YAML 文件加载配置
func (cm *ConfigManager) LoadFromYAML(path string) error {
return cm.ApplySources(YAMLFileSource{Path: path})
}

// ReloadConfig 重新加载配置(仅支持环境变量,保持端口不变)
Expand Down Expand Up @@ -190,30 +191,8 @@ func (cm *ConfigManager) PersistYAML() error {

// applyEnvironmentOverrides 应用环境变量覆盖配置
func (cm *ConfigManager) applyEnvironmentOverrides() {
// 基础配置环境变量
if port := os.Getenv("PORT"); port != "" {
if n, err := strconv.Atoi(port); err == nil {
cm.Base.Port = n
}
}
if dataPath := os.Getenv("DATA_PATH"); dataPath != "" {
cm.Base.DataPath = dataPath
}

// MCP 配置环境变量
if enableMCP := os.Getenv("ENABLE_MCP_SERVER"); enableMCP != "" {
if enableMCP == "true" || enableMCP == "1" {
cm.MCP.EnableMCPServer = 1
} else {
cm.MCP.EnableMCPServer = 0
}
}
if mcpPort := os.Getenv("MCP_PORT"); mcpPort != "" {
cm.MCP.MCPPort = mcpPort
}
if mcpHost := os.Getenv("MCP_HOST"); mcpHost != "" {
cm.MCP.MCPHost = mcpHost
}
// 收集错误以便在调用者中统一处理,保持现有签名
_ = NewDefaultEnvSource().Apply(cm)
}

// Save 保存配置(已废弃,请使用 config.yaml 和环境变量)
Expand Down
15 changes: 15 additions & 0 deletions internal/config/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,18 @@ func TestEnvOverride(t *testing.T) {
t.Fatalf("expected PORT env to override to 9090, got %d", cm.Base.Port)
}
}

func TestApplySourcesAggregatesErrors(t *testing.T) {
cm := NewConfigManager()
src := NewDefaultEnvSource()
src.lookup = func(key string) string {
if key == "ENABLE_MCP_SERVER" {
return "definitely-not-bool"
}
return ""
}

if err := cm.ApplySources(src); err == nil {
t.Fatalf("expected aggregated error when environment value invalid")
}
}
143 changes: 143 additions & 0 deletions internal/config/source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package config

import (
"errors"
"fmt"
"os"
"strconv"
"strings"

"gopkg.in/yaml.v3"
)

// ConfigSource represents a configuration input that can mutate the manager state.
type ConfigSource interface {
Apply(*ConfigManager) error
}

// ConfigSourceFunc allows plain functions to be used as ConfigSource.
type ConfigSourceFunc func(*ConfigManager) error

// Apply executes the underlying function.
func (f ConfigSourceFunc) Apply(cm *ConfigManager) error { return f(cm) }

// YAMLFileSource loads configuration values from a YAML file.
type YAMLFileSource struct {
Path string
}

// Apply reads and merges YAML content into the manager.
func (s YAMLFileSource) Apply(cm *ConfigManager) error {
if strings.TrimSpace(s.Path) == "" {
return errors.New("config: YAML path is empty")
}

data, err := os.ReadFile(s.Path)
if err != nil {
return err
}

var fileCfg ConfigManager
if err := yaml.Unmarshal(data, &fileCfg); err != nil {
return fmt.Errorf("config: unmarshal %s: %w", s.Path, err)
}

cm.mergeConfigModules(&fileCfg)
cm.mergeUserConfig(fileCfg.User)
cm.mergeSimpleFields(&fileCfg)
return nil
}

type envOverride struct {
key string
apply func(string, *ConfigManager) error
}

// EnvSource mutates configuration using environment variables.
type EnvSource struct {
overrides []envOverride
lookup func(string) string
}

// NewDefaultEnvSource returns the built-in environment overrides.
func NewDefaultEnvSource() EnvSource {
return EnvSource{
overrides: []envOverride{
{key: "PORT", apply: applyPortOverride},
{key: "DATA_PATH", apply: applyDataPathOverride},
{key: "ENABLE_MCP_SERVER", apply: applyMCPEnabledOverride},
{key: "MCP_PORT", apply: applyMCPPortOverride},
{key: "MCP_HOST", apply: applyMCPHostOverride},
},
}
}

// Apply applies every configured override.
func (s EnvSource) Apply(cm *ConfigManager) error {
lookup := s.lookup
if lookup == nil {
lookup = os.Getenv
}

var errs []error
for _, override := range s.overrides {
if value := lookup(override.key); value != "" {
if err := override.apply(value, cm); err != nil {
errs = append(errs, fmt.Errorf("%s: %w", override.key, err))
}
}
}

return errors.Join(errs...)
}

func applyPortOverride(val string, cm *ConfigManager) error {
port, err := strconv.Atoi(val)
if err != nil {
return fmt.Errorf("invalid port %q", val)
}
cm.Base.Port = port
return nil
}

func applyDataPathOverride(val string, cm *ConfigManager) error {
if strings.TrimSpace(val) == "" {
return errors.New("data path cannot be blank")
}
cm.Base.DataPath = val
return nil
}

func applyMCPEnabledOverride(val string, cm *ConfigManager) error {
enabled, err := parseBool(val)
if err != nil {
return err
}
if enabled {
cm.MCP.EnableMCPServer = 1
} else {
cm.MCP.EnableMCPServer = 0
}
return nil
}

func applyMCPPortOverride(val string, cm *ConfigManager) error {
cm.MCP.MCPPort = val
return nil
}

func applyMCPHostOverride(val string, cm *ConfigManager) error {
cm.MCP.MCPHost = val
return nil
}

func parseBool(val string) (bool, error) {
switch strings.ToLower(strings.TrimSpace(val)) {
case "1", "true", "t", "yes", "y":
return true, nil
case "0", "false", "f", "no", "n":
return false, nil
default:
return false, fmt.Errorf("invalid boolean value %q", val)
}
}
Comment on lines +134 to +143
Copy link

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This custom boolean parsing duplicates functionality available in Go's standard library. Consider using strconv.ParseBool() which is more standard and well-tested, or clearly document why this custom implementation with additional accepted values ('y', 'n', 't', 'f') is necessary.

Copilot uses AI. Check for mistakes.
3 changes: 3 additions & 0 deletions internal/handlers/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ func (h *AdminHandler) GetConfig(c *gin.Context) {
SysStart: &cfg.SysStart,
},
}

resp.NotifyTitle = &cfg.NotifyTitle
resp.NotifyContent = &cfg.NotifyContent
common.SuccessResponse(c, resp)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/handlers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func NewAPIHandler(manager *config.ConfigManager) *APIHandler {
type HealthResponse struct {
Status string `json:"status" example:"ok"`
Timestamp string `json:"timestamp" example:"2025-09-11T10:00:00Z"`
Version string `json:"version" example:"1.7.1"`
Version string `json:"version" example:"1.8.2"`
Uptime string `json:"uptime" example:"2h30m15s"`
}

Expand Down
2 changes: 1 addition & 1 deletion internal/models/service/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var (
GitBranch = "unknown"

// Version 应用版本号
Version = "1.7.1"
Version = "1.8.2"
)

// BuildInfo 构建信息结构体
Expand Down
4 changes: 4 additions & 0 deletions internal/models/web/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ type AdminConfigRequest struct {

// 系统运行时特有字段(不属于配置模块的字段)
SysStart *string `json:"sys_start,omitempty"`

// 顶层通知字段保留与历史配置结构兼容
NotifyTitle *string `json:"notify_title,omitempty"`
NotifyContent *string `json:"notify_content,omitempty"`
}

// CountResponse 通用计数响应
Expand Down
Loading