diff --git a/.gitignore b/.gitignore index 93b8e6d..cef0a95 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ *.out /vendor/ +# 数据库文件(SQLite) +/data/ + # 系统文件 .DS_Store ._* diff --git a/config.go b/config.go new file mode 100644 index 0000000..479dcf4 --- /dev/null +++ b/config.go @@ -0,0 +1,220 @@ +package main + +import ( + "errors" + "fmt" + "io/fs" + "log/slog" + "os" + "strings" + "time" + + "github.com/goccy/go-yaml" +) + +type Config struct { + Server ServerConfig `yaml:"server"` + Log LogConfig `yaml:"log"` + Static StaticConfig `yaml:"static"` + API APIConfig `yaml:"api"` + Database DatabaseConfig `yaml:"database"` +} + +type ServerConfig struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + Mode string `yaml:"mode"` +} + +type LogConfig struct { + Level string `yaml:"level"` + AccessLog bool `yaml:"access_log"` +} + +type StaticConfig struct { + Dir string `yaml:"dir"` +} + +type APIConfig struct { + Prefix string `yaml:"prefix"` + CORS CORSConfig `yaml:"cors"` +} + +type CORSConfig struct { + Enabled bool `yaml:"enabled"` + AllowOrigins []string `yaml:"allow_origins"` + AllowMethods []string `yaml:"allow_methods"` + AllowHeaders []string `yaml:"allow_headers"` + AllowCredentials bool `yaml:"allow_credentials"` + MaxAge string `yaml:"max_age"` +} + +type DatabaseConfig struct { + Driver string `yaml:"driver"` // sqlite3 / mysql + ConnectTimeout string `yaml:"connect_timeout"` // 建立连接超时 + SQLite SQLiteConfig `yaml:"sqlite"` + MySQL MySQLConfig `yaml:"mysql"` +} + +type SQLiteConfig struct { + Path string `yaml:"path"` +} + +type MySQLConfig struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + User string `yaml:"user"` + Password string `yaml:"password"` + Database string `yaml:"database"` + Charset string `yaml:"charset"` + MaxOpenConns int `yaml:"max_open_conns"` + MaxIdleConns int `yaml:"max_idle_conns"` + ConnMaxLifetime string `yaml:"conn_max_lifetime"` +} + +func defaultConfig() *Config { + return &Config{ + Server: ServerConfig{ + Host: "0.0.0.0", + Port: 8080, + Mode: "debug", + }, + Log: LogConfig{ + Level: "info", + AccessLog: true, + }, + Static: StaticConfig{ + Dir: "./dist", + }, + API: APIConfig{ + Prefix: "/api", + CORS: CORSConfig{ + Enabled: true, + AllowOrigins: []string{"http://localhost:5173"}, + AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, + AllowCredentials: false, + MaxAge: "12h", + }, + }, + Database: DatabaseConfig{ + Driver: "sqlite3", + ConnectTimeout: "10s", + SQLite: SQLiteConfig{ + Path: "./data/rill.db", + }, + MySQL: MySQLConfig{ + Host: "127.0.0.1", + Port: 3306, + User: "root", + Password: "", + Database: "rill", + Charset: "utf8mb4", + MaxOpenConns: 10, + MaxIdleConns: 5, + ConnMaxLifetime: "1h", + }, + }, + } +} + +// LoadConfig 读取 YAML 配置文件,未指定的配置项使用默认值;文件不存在时回退到默认配置。 +func LoadConfig(path string) (*Config, error) { + cfg := defaultConfig() + + data, err := os.ReadFile(path) + switch { + case err == nil: + if err := yaml.Unmarshal(data, cfg); err != nil { + return nil, fmt.Errorf("解析配置文件 %s 失败: %w", path, err) + } + case errors.Is(err, fs.ErrNotExist): + slog.Warn("配置文件不存在,使用默认配置", "path", path) + default: + return nil, fmt.Errorf("读取配置文件 %s 失败: %w", path, err) + } + + if err := cfg.validate(); err != nil { + return nil, err + } + return cfg, nil +} + +func (c *Config) validate() error { + switch c.Server.Mode { + case "debug", "release", "test": + default: + return fmt.Errorf("server.mode 无效: %q(可选: debug/release/test)", c.Server.Mode) + } + if c.Server.Port <= 0 || c.Server.Port > 65535 { + return fmt.Errorf("server.port 无效: %d", c.Server.Port) + } + if _, err := parseLogLevel(c.Log.Level); err != nil { + return err + } + if _, err := time.ParseDuration(c.API.CORS.MaxAge); err != nil { + return fmt.Errorf("api.cors.max_age 无效: %w", err) + } + if err := c.validateDatabase(); err != nil { + return err + } + return nil +} + +func (c *Config) validateDatabase() error { + if _, err := time.ParseDuration(c.Database.ConnectTimeout); err != nil { + return fmt.Errorf("database.connect_timeout 无效: %w", err) + } + + switch c.Database.Driver { + case "sqlite3": + if strings.TrimSpace(c.Database.SQLite.Path) == "" { + return fmt.Errorf("database.sqlite.path 不能为空") + } + case "mysql": + db := c.Database.MySQL + if strings.TrimSpace(db.Host) == "" { + return fmt.Errorf("database.mysql.host 不能为空") + } + if db.Port <= 0 || db.Port > 65535 { + return fmt.Errorf("database.mysql.port 无效: %d", db.Port) + } + if strings.TrimSpace(db.Database) == "" { + return fmt.Errorf("database.mysql.database 不能为空") + } + if db.MaxOpenConns < 0 || db.MaxIdleConns < 0 { + return fmt.Errorf("database.mysql 连接池大小不能为负数") + } + if _, err := time.ParseDuration(db.ConnMaxLifetime); err != nil { + return fmt.Errorf("database.mysql.conn_max_lifetime 无效: %w", err) + } + default: + return fmt.Errorf("database.driver 无效: %q(可选: sqlite3/mysql)", c.Database.Driver) + } + return nil +} + +func (c *Config) LogLevel() slog.Level { + level, _ := parseLogLevel(c.Log.Level) + return level +} + +func (c *Config) CORSMaxAge() time.Duration { + maxAge, _ := time.ParseDuration(c.API.CORS.MaxAge) + return maxAge +} + +func parseLogLevel(level string) (slog.Level, error) { + switch level { + case "debug": + return slog.LevelDebug, nil + case "info": + return slog.LevelInfo, nil + case "warn": + return slog.LevelWarn, nil + case "error": + return slog.LevelError, nil + default: + return slog.LevelInfo, fmt.Errorf("log.level 无效: %q(可选: debug/info/warn/error)", level) + } +} diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..4eb3a48 --- /dev/null +++ b/config.yaml @@ -0,0 +1,40 @@ +# rill 服务端配置 +server: + host: "0.0.0.0" # 监听地址,0.0.0.0 表示所有网卡 + port: 8080 + mode: debug # gin 运行模式: debug / release / test + +log: + level: info # 日志级别: debug / info / warn / error + access_log: true # 是否输出 HTTP 访问日志 + +static: + dir: "./dist" # 前端构建产物目录 + +api: + prefix: "/api" # API 路由前缀 + cors: + enabled: true + allow_origins: + - "http://localhost:5173" # Vite 开发服务器地址 + allow_methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] + allow_headers: ["Origin", "Content-Type", "Accept", "Authorization"] + allow_credentials: false + max_age: "12h" + +# 数据库(当前仅配置,服务暂不建立连接) +database: + driver: sqlite3 # sqlite3 / mysql + connect_timeout: "10s" # 建立连接超时 + sqlite: + path: "./data/rill.db" + mysql: + host: "127.0.0.1" + port: 3306 + user: "root" + password: "" # 允许留空 + database: "rill" + charset: "utf8mb4" + max_open_conns: 10 + max_idle_conns: 5 + conn_max_lifetime: "1h" diff --git a/go.mod b/go.mod index 642ae86..7d28e9a 100644 --- a/go.mod +++ b/go.mod @@ -3,34 +3,38 @@ module rill go 1.26.4 require ( - github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.15.0 // indirect - github.com/bytedance/sonic/loader v0.5.0 // indirect - github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect - github.com/gin-contrib/sse v1.1.0 // indirect - github.com/gin-gonic/gin v1.12.0 // indirect + github.com/gin-contrib/cors v1.7.8 + github.com/gin-gonic/gin v1.12.0 + github.com/goccy/go-yaml v1.19.2 +) + +require ( + github.com/bytedance/gopkg v0.1.4 // indirect + github.com/bytedance/sonic v1.15.2 // indirect + github.com/bytedance/sonic/loader v0.5.1 // indirect + github.com/cloudwego/base64x v0.1.7 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect + github.com/gin-contrib/sse v1.1.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.1 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/goccy/go-yaml v1.19.2 // indirect + github.com/go-playground/validator/v10 v10.30.3 // indirect + github.com/goccy/go-json v0.10.6 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/klauspost/cpuid/v2 v2.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.23 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.59.0 // indirect + github.com/quic-go/quic-go v0.60.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect - go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect - golang.org/x/arch v0.22.0 // indirect - golang.org/x/crypto v0.48.0 // indirect - golang.org/x/net v0.51.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect + golang.org/x/arch v0.29.0 // indirect + golang.org/x/crypto v0.55.0 // indirect + golang.org/x/net v0.57.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/go.sum b/go.sum index 6a9c680..2241211 100644 --- a/go.sum +++ b/go.sum @@ -1,50 +1,60 @@ -github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= -github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= -github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= -github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= -github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= -github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= -github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM= +github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4= +github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo= +github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= +github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI= +github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI= +github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= -github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= -github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/cors v1.7.8 h1:U0jjyXlWXMEx27hFE6hmdg/TOZviYgsOGGT+kLmUZrc= +github.com/gin-contrib/cors v1.7.8/go.mod h1:u3nLI2pP1IlJn7tbvL7iiubDO9quBZ8FXP9LxmqpsPI= +github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko= +github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= -github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8= +github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= -github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= +github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= +github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0= +github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= -github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0= +github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -54,25 +64,29 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= -go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= -golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= -golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8= +go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.29.0 h1:8sSET5wB0+exBm0FGmOtdHMqjlRdV2DRD3/IV6OZgho= +golang.org/x/arch v0.29.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 639f5fa..f1d4eaa 100644 --- a/main.go +++ b/main.go @@ -1,21 +1,59 @@ package main import ( + "flag" + "log/slog" + "net" "net/http" + "os" + "strconv" "strings" + "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) func main() { + configPath := flag.String("c", "config.yaml", "配置文件路径") + flag.Parse() + + cfg, err := LoadConfig(*configPath) + if err != nil { + slog.Error("加载配置失败", "err", err) + os.Exit(1) + } + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: cfg.LogLevel()}))) + + //设置gin运行模式 + gin.SetMode(cfg.Server.Mode) + //启动gin服务 - r := gin.Default() + r := gin.New() + r.Use(gin.Recovery()) + if cfg.Log.AccessLog { + r.Use(gin.Logger()) + } + if cfg.API.CORS.Enabled { + r.Use(cors.New(cors.Config{ + AllowOrigins: cfg.API.CORS.AllowOrigins, + AllowMethods: cfg.API.CORS.AllowMethods, + AllowHeaders: cfg.API.CORS.AllowHeaders, + AllowCredentials: cfg.API.CORS.AllowCredentials, + MaxAge: cfg.CORSMaxAge(), + })) + } + + // API 路由 + api := r.Group(cfg.API.Prefix) + api.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + }) // 静态文件服务 - fs := http.FileServer(http.Dir("./dist")) + fs := http.FileServer(http.Dir(cfg.Static.Dir)) // 中间件处理路由 r.Use(func(c *gin.Context) { - if strings.HasPrefix(c.Request.URL.Path, "/api") { + if strings.HasPrefix(c.Request.URL.Path, cfg.API.Prefix) { c.Next() // 继续处理API请求 return } @@ -25,5 +63,10 @@ func main() { c.Abort() }) - r.Run() + addr := net.JoinHostPort(cfg.Server.Host, strconv.Itoa(cfg.Server.Port)) + slog.Info("服务启动", "addr", addr, "static", cfg.Static.Dir, "mode", cfg.Server.Mode) + if err := r.Run(addr); err != nil { + slog.Error("服务启动失败", "err", err) + os.Exit(1) + } }