Files
junhong_cmp_fiber/cmd/api/main.go
huang 9c6d4a3bd4 实现个人客户微信认证和短信验证功能
- 添加个人客户微信登录和手机验证码登录接口
- 实现个人客户设备、ICCID、手机号关联管理
- 添加短信发送服务(HTTP 客户端)
- 添加微信认证服务(含 mock 实现)
- 添加 JWT Token 生成和验证工具
- 创建数据库迁移脚本(personal_customer 关联表)
- 修复测试文件中的路由注册参数错误
- 重构 scripts 目录结构(分离独立脚本到子目录)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-10 11:42:38 +08:00

284 lines
7.8 KiB
Go
Raw 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 main
import (
"context"
"os"
"os/signal"
"strconv"
"syscall"
"github.com/bytedance/sonic"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/requestid"
"github.com/google/uuid"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
internalMiddleware "github.com/break/junhong_cmp_fiber/internal/middleware"
"github.com/break/junhong_cmp_fiber/internal/routes"
"github.com/break/junhong_cmp_fiber/pkg/config"
"github.com/break/junhong_cmp_fiber/pkg/database"
"github.com/break/junhong_cmp_fiber/pkg/logger"
"github.com/break/junhong_cmp_fiber/pkg/queue"
)
func main() {
// 1. 初始化配置
cfg := initConfig()
// 2. 初始化日志
appLogger := initLogger(cfg)
defer func() {
_ = logger.Sync()
}()
// 3. 初始化数据库
db := initDatabase(cfg, appLogger)
defer closeDatabase(db, appLogger)
// 4. 初始化 Redis
redisClient := initRedis(cfg, appLogger)
defer closeRedis(redisClient, appLogger)
// 5. 初始化队列客户端
queueClient := initQueue(redisClient, appLogger)
defer closeQueue(queueClient, appLogger)
// 6. 初始化所有业务组件(通过 Bootstrap
result, err := bootstrap.Bootstrap(&bootstrap.Dependencies{
DB: db,
Redis: redisClient,
Logger: appLogger,
})
if err != nil {
appLogger.Fatal("初始化业务组件失败", zap.Error(err))
}
// 7. 启动配置监听器
watchCtx, cancelWatch := context.WithCancel(context.Background())
defer cancelWatch()
go config.Watch(watchCtx, appLogger)
// 8. 创建 Fiber 应用
app := createFiberApp(cfg, appLogger)
// 9. 注册中间件
initMiddleware(app, cfg, appLogger)
// 10. 注册路由
initRoutes(app, cfg, result, queueClient, db, redisClient, appLogger)
// 11. 生成 OpenAPI 文档
generateOpenAPIDocs("./openapi.yaml", appLogger)
// 12. 启动服务器
startServer(app, cfg, appLogger, cancelWatch)
}
// initConfig 加载配置
func initConfig() *config.Config {
cfg, err := config.Load()
if err != nil {
panic("加载配置失败: " + err.Error())
}
return cfg
}
// initLogger 初始化日志
func initLogger(cfg *config.Config) *zap.Logger {
if err := logger.InitLoggers(
cfg.Logging.Level,
cfg.Logging.Development,
logger.LogRotationConfig{
Filename: cfg.Logging.AppLog.Filename,
MaxSize: cfg.Logging.AppLog.MaxSize,
MaxBackups: cfg.Logging.AppLog.MaxBackups,
MaxAge: cfg.Logging.AppLog.MaxAge,
Compress: cfg.Logging.AppLog.Compress,
},
logger.LogRotationConfig{
Filename: cfg.Logging.AccessLog.Filename,
MaxSize: cfg.Logging.AccessLog.MaxSize,
MaxBackups: cfg.Logging.AccessLog.MaxBackups,
MaxAge: cfg.Logging.AccessLog.MaxAge,
Compress: cfg.Logging.AccessLog.Compress,
},
); err != nil {
panic("初始化日志失败: " + err.Error())
}
appLogger := logger.GetAppLogger()
appLogger.Info("应用程序启动中...", zap.String("address", cfg.Server.Address))
return appLogger
}
// initDatabase 初始化数据库连接
func initDatabase(cfg *config.Config, appLogger *zap.Logger) *gorm.DB {
db, err := database.InitPostgreSQL(&cfg.Database, appLogger)
if err != nil {
appLogger.Fatal("初始化 PostgreSQL 失败", zap.Error(err))
}
return db
}
// closeDatabase 关闭数据库连接
func closeDatabase(db *gorm.DB, appLogger *zap.Logger) {
sqlDB, _ := db.DB()
if sqlDB != nil {
if err := sqlDB.Close(); err != nil {
appLogger.Error("关闭 PostgreSQL 连接失败", zap.Error(err))
}
}
}
// initRedis 初始化 Redis 连接
func initRedis(cfg *config.Config, appLogger *zap.Logger) *redis.Client {
redisAddr := cfg.Redis.Address + ":" + strconv.Itoa(cfg.Redis.Port)
redisClient, err := database.NewRedisClient(database.RedisConfig{
Address: redisAddr,
Password: cfg.Redis.Password,
DB: cfg.Redis.DB,
PoolSize: cfg.Redis.PoolSize,
MinIdleConns: cfg.Redis.MinIdleConns,
DialTimeout: cfg.Redis.DialTimeout,
ReadTimeout: cfg.Redis.ReadTimeout,
WriteTimeout: cfg.Redis.WriteTimeout,
}, appLogger)
if err != nil {
appLogger.Fatal("连接 Redis 失败", zap.Error(err))
}
return redisClient
}
// closeRedis 关闭 Redis 连接
func closeRedis(redisClient *redis.Client, appLogger *zap.Logger) {
if err := redisClient.Close(); err != nil {
appLogger.Error("关闭 Redis 客户端失败", zap.Error(err))
}
}
// initQueue 初始化队列客户端
func initQueue(redisClient *redis.Client, appLogger *zap.Logger) *queue.Client {
return queue.NewClient(redisClient, appLogger)
}
// closeQueue 关闭队列客户端
func closeQueue(queueClient *queue.Client, appLogger *zap.Logger) {
if err := queueClient.Close(); err != nil {
appLogger.Error("关闭 Asynq 客户端失败", zap.Error(err))
}
}
// createFiberApp 创建 Fiber 应用
func createFiberApp(cfg *config.Config, appLogger *zap.Logger) *fiber.App {
return fiber.New(fiber.Config{
AppName: "君鸿卡管系统 v1.0.0",
StrictRouting: true,
CaseSensitive: true,
JSONEncoder: sonic.Marshal,
JSONDecoder: sonic.Unmarshal,
Prefork: cfg.Server.Prefork,
ReadTimeout: cfg.Server.ReadTimeout,
WriteTimeout: cfg.Server.WriteTimeout,
ErrorHandler: internalMiddleware.ErrorHandler(appLogger),
})
}
// initMiddleware 注册中间件
func initMiddleware(app *fiber.App, cfg *config.Config, appLogger *zap.Logger) {
// 1. Recover - 必须第一个,捕获所有 panic
app.Use(internalMiddleware.Recover(appLogger))
// 2. RequestID - 为每个请求生成唯一 ID
app.Use(requestid.New(requestid.Config{
Generator: func() string {
return uuid.NewString()
},
}))
// 3. Logger - 记录所有请求
app.Use(logger.Middleware())
// 4. Compress - 响应压缩
app.Use(compress.New(compress.Config{
Level: compress.LevelDefault,
}))
}
// initRoutes 注册路由
func initRoutes(app *fiber.App, cfg *config.Config, result *bootstrap.BootstrapResult, queueClient *queue.Client, db *gorm.DB, redisClient *redis.Client, appLogger *zap.Logger) {
// 注册模块化路由
routes.RegisterRoutes(app, result.Handlers, result.Middlewares)
// API v1 路由组(用于受保护的端点)
v1 := app.Group("/api/v1")
// 可选:启用认证中间件
if cfg.Middleware.EnableAuth {
// TODO: 配置 TokenValidator
appLogger.Info("认证中间件已启用")
}
// 可选:启用限流器
if cfg.Middleware.EnableRateLimiter {
initRateLimiter(v1, cfg, appLogger)
}
}
// initRateLimiter 初始化限流器
func initRateLimiter(router fiber.Router, cfg *config.Config, appLogger *zap.Logger) {
var rateLimitStorage fiber.Storage
if cfg.Middleware.RateLimiter.Storage == "redis" {
rateLimitStorage = internalMiddleware.NewRedisStorage(
cfg.Redis.Address,
cfg.Redis.Password,
cfg.Redis.DB,
cfg.Redis.Port,
)
appLogger.Info("限流器使用 Redis 存储", zap.String("redis_address", cfg.Redis.Address))
} else {
rateLimitStorage = nil
appLogger.Info("限流器使用内存存储")
}
router.Use(internalMiddleware.RateLimiter(
cfg.Middleware.RateLimiter.Max,
cfg.Middleware.RateLimiter.Expiration,
rateLimitStorage,
))
}
// startServer 启动服务器
func startServer(app *fiber.App, cfg *config.Config, appLogger *zap.Logger, cancelWatch context.CancelFunc) {
// 优雅关闭
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
go func() {
if err := app.Listen(cfg.Server.Address); err != nil {
appLogger.Fatal("服务器启动失败", zap.Error(err))
}
}()
appLogger.Info("服务器已启动", zap.String("address", cfg.Server.Address))
// 等待关闭信号
<-quit
appLogger.Info("正在关闭服务器...")
// 取消配置监听器
cancelWatch()
// 关闭 HTTP 服务器
if err := app.ShutdownWithTimeout(cfg.Server.ShutdownTimeout); err != nil {
appLogger.Error("强制关闭服务器", zap.Error(err))
}
appLogger.Info("服务器已停止")
}