This commit is contained in:
oneao committed 2026-04-02 22:07:21 +08:00
1 parent ae0d245605
commit 4725ee75ca
26 files changed
+1089 -67

No files matched your search

@@ -0,0 +1,74 @@
package bootstrap
import (
"allapp-go/internal/bootstrap/boots"
"allapp-go/internal/config"
"allapp-go/pkg/logger"
"os"
"os/signal"
"syscall"
"github.com/jackc/pgx/v5/pgxpool"
)
type App struct {
Config *config.Config
PgSql *pgxpool.Pool
}
// NewApp 初始化(不阻塞)
func NewApp() (*App, error) {
cfg, err := config.Load()
if err != nil {
return nil, err
}
// 日志
boots.InitLogger(cfg)
pgsql, err := boots.InitPgsql(cfg)
if err != nil {
return nil, err
}
// 其他组件
boots.InitUniqueId(cfg)
boots.InitJwt(cfg)
return &App{
Config: cfg,
PgSql: pgsql,
}, nil
}
// Run 启动服务(阻塞)
func (a *App) Run() error {
app, err := boots.RunServer(a.Config)
if err != nil {
return err
}
// 监听退出信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
logger.Log.Info("shutting down server...")
if err := app.Shutdown(); err != nil {
logger.Log.Error("shutdown failed")
return err
}
return nil
}
// Close 释放资源
func (a *App) Close() {
if a.PgSql != nil {
a.PgSql.Close()
}
logger.Sync()
}
@@ -0,0 +1,18 @@
package boots
import (
"allapp-go/internal/config"
"allapp-go/pkg/logger"
)
func InitLogger(cfg *config.Config) {
logger.Init(logger.Config{
Dir: "logs",
Level: "info",
MaxSize: 100, // MB
MaxBackups: 10,
MaxAge: 7, // 天
Compress: true,
})
defer logger.Sync()
}
@@ -0,0 +1,11 @@
package boots
import (
"allapp-go/internal/config"
"allapp-go/pkg/jwtx"
)
// InitJwt 初始化Jwt
func InitJwt(cfg *config.Config) {
jwtx.Init(cfg.JWT.Secret, cfg.JWT.AccessExpiry)
}
@@ -0,0 +1,84 @@
package boots
import (
"allapp-go/internal/config"
"context"
"fmt"
"net/url"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
func InitPgsql(cfg *config.Config) (*pgxpool.Pool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
pg := cfg.Postgres
// ssl
sslMode := "disable"
if pg.SslMode {
sslMode = "require"
}
// 密码转义
password := url.QueryEscape(pg.Password)
// DSN
dsn := fmt.Sprintf(
"postgres://%s:%s@%s:%d/%s?sslmode=%s",
pg.User,
password,
pg.Host,
pg.Port,
pg.Dbname,
sslMode,
)
// 解析配置
conf, err := pgxpool.ParseConfig(dsn)
if err != nil {
return nil, err
}
// ======================
// 连接池配置
// ======================
// 最大连接数
if pg.MaxOpenConns > 0 {
conf.MaxConns = int32(pg.MaxOpenConns)
}
// 最小连接数(近似 idle)
if pg.MaxIdleConns > 0 {
conf.MinConns = int32(pg.MaxIdleConns)
}
// 连接最大生命周期
if pg.ConnMaxLifetime > 0 {
conf.MaxConnLifetime = pg.ConnMaxLifetime
}
// ======================
// 时区设置(正确方式 ⭐)
// ======================
if pg.TimeZone != "" {
conf.ConnConfig.RuntimeParams["TimeZone"] = pg.TimeZone
}
// 创建连接池
db, err := pgxpool.NewWithConfig(ctx, conf)
if err != nil {
return nil, err
}
// 测试连接
if err := db.Ping(ctx); err != nil {
db.Close()
return nil, err
}
return db, nil
}
@@ -0,0 +1,32 @@
package boots
import (
"allapp-go/internal/config"
"allapp-go/internal/router"
"fmt"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/requestid"
)
func RunServer(cfg *config.Config) (*fiber.App, error) {
app := fiber.New()
// ===== middleware =====
app.Use(requestid.New())
// TODO: 你可以加 logger middleware / recover
// ===== router =====
router.Setup(app)
addr := fmt.Sprintf(":%d", cfg.App.Port)
go func() {
if err := app.Listen(addr); err != nil {
panic(err)
}
}()
return app, nil
}
@@ -0,0 +1,12 @@
package boots
import (
"allapp-go/internal/config"
"allapp-go/pkg/uniqueid"
)
// InitUniqueId 初始化雪花id
func InitUniqueId(cfg *config.Config) {
options := uniqueid.NewIdGeneratorOptions(cfg.UniqueID.WorkerID)
uniqueid.SetIdGenerator(options)
}
@@ -1,24 +0,0 @@
package bootstrap
import (
"allapp-go/internal/config"
"log"
)
type App struct {
Config *config.Config
}
func BootService() *App {
// 1️⃣ 加载配置
cfg, err := config.Load()
if err != nil {
log.Fatalf("❌ load config failed: %v", err)
}
RunServer(cfg)
return &App{
Config: cfg,
}
}
@@ -1,24 +0,0 @@
package bootstrap
import (
"allapp-go/internal/config"
"allapp-go/internal/router"
"fmt"
"log"
"github.com/gofiber/fiber/v3"
)
func RunServer(cfg *config.Config) {
app := fiber.New()
router.Setup(app)
addr := fmt.Sprintf(":%d", cfg.App.Port)
log.Printf("🚀 Server running on %s", addr)
if err := app.Listen(addr); err != nil {
log.Fatalf("❌ start server failed: %v", err)
}
}
@@ -1,7 +1,11 @@
package config
import "time"
type Config struct {
App AppConfig `mapstructure:"app"`
JWT JwtConfig `mapstructure:"jwt"`
UniqueID UniqueIDConfig `mapstructure:"unique_id"`
Postgres PostgresConfig `mapstructure:"postgres"`
}
@@ -11,11 +15,24 @@ type AppConfig struct {
}
type PostgresConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Dbname string `mapstructure:"dbname"`
SslMode bool `mapstructure:"ssl_mode"`
TimeZone string `mapstructure:"timezone"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Dbname string `mapstructure:"dbname"`
SslMode bool `mapstructure:"ssl_mode"`
TimeZone string `mapstructure:"timezone"`
MaxOpenConns int32 `mapstructure:"max_open_conns"`
MaxIdleConns int32 `mapstructure:"max_idle_conns"`
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
}
type JwtConfig struct {
Secret string `mapstructure:"secret"`
AccessExpiry time.Duration `mapstructure:"access_expiry"`
}
type UniqueIDConfig struct {
DataCenterID uint16 `mapstructure:"datacenter_id"`
WorkerID uint16 `mapstructure:"worker_id"`
}
@@ -14,12 +14,19 @@ func Load() (*Config, error) {
v := viper.New()
v.SetConfigType("yaml")
// ✅ 默认值
setDefaults(v)
// ✅ 支持环境变量(如 APP_PORT → app.port)
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// 基础配置
v.AddConfigPath("configs")
v.SetConfigName("config")
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("read base config failed: %w", err)
return nil, fmt.Errorf("读取基础配置失败: %w", err)
}
// 环境配置(覆盖)
@@ -27,20 +34,30 @@ func Load() (*Config, error) {
v.SetConfigName(envConfigName)
if err := v.MergeInConfig(); err != nil {
// dev 允许没有文件,prod 建议必须存在
// dev 可以没有,prod 必须有
if env == "prod" {
return nil, fmt.Errorf("read env config failed: %w", err)
return nil, fmt.Errorf("读取环境配置失败(%s): %w", env, err)
}
}
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("unmarshal config failed: %w", err)
return nil, fmt.Errorf("解析配置失败: %w", err)
}
return &cfg, nil
}
// ✅ 默认值统一在这里管理
func setDefaults(v *viper.Viper) {
v.SetDefault("app.name", "allapp-go")
v.SetDefault("app.port", 8080)
// 可以继续扩展
v.SetDefault("unique_id.datacenter_id", 1)
v.SetDefault("unique_id.worker_id", 1)
}
func getEnv(key, defaultVal string) string {
val := os.Getenv(key)
if val == "" {