ci(backend): 添加 github actions (#10)

## 变更内容

### CI/CD
- 添加 GitHub Actions 工作流(test + golangci-lint)
- 添加 golangci-lint 配置,启用 errcheck/govet/staticcheck/unused/depguard
- 通过 depguard 强制 service 层不能直接导入 repository

### 错误处理修复
- 修复 CSV 写入、SSE 流式输出、随机数生成等未处理的错误
- GenerateRedeemCode() 现在返回 error

### 资源泄露修复
- 统一使用 defer func() { _ = xxx.Close() }() 模式

### 代码清理
- 移除未使用的常量
- 简化 nil map 检查
- 统一代码格式
This commit is contained in:
NepetaLemon
2025-12-20 15:29:52 +08:00
committed by GitHub
parent f1325e9ae6
commit c6b3de1199
33 changed files with 316 additions and 147 deletions

View File

@@ -352,4 +352,3 @@ func install(c *gin.Context) {
"restart": true,
})
}

View File

@@ -14,9 +14,9 @@ import (
"github.com/redis/go-redis/v9"
"golang.org/x/crypto/bcrypt"
"gopkg.in/yaml.v3"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gopkg.in/yaml.v3"
)
// Config paths
@@ -101,7 +101,14 @@ func TestDatabaseConnection(cfg *DatabaseConfig) error {
if err != nil {
return fmt.Errorf("failed to get db instance: %w", err)
}
defer sqlDB.Close()
defer func() {
if sqlDB == nil {
return
}
if err := sqlDB.Close(); err != nil {
log.Printf("failed to close postgres connection: %v", err)
}
}()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
@@ -129,7 +136,10 @@ func TestDatabaseConnection(cfg *DatabaseConfig) error {
}
// Now connect to the target database to verify
sqlDB.Close()
if err := sqlDB.Close(); err != nil {
log.Printf("failed to close postgres connection: %v", err)
}
sqlDB = nil
targetDSN := fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
@@ -145,7 +155,11 @@ func TestDatabaseConnection(cfg *DatabaseConfig) error {
if err != nil {
return fmt.Errorf("failed to get target db instance: %w", err)
}
defer targetSqlDB.Close()
defer func() {
if err := targetSqlDB.Close(); err != nil {
log.Printf("failed to close postgres connection: %v", err)
}
}()
ctx2, cancel2 := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel2()
@@ -164,7 +178,11 @@ func TestRedisConnection(cfg *RedisConfig) error {
Password: cfg.Password,
DB: cfg.DB,
})
defer rdb.Close()
defer func() {
if err := rdb.Close(); err != nil {
log.Printf("failed to close redis client: %v", err)
}
}()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
@@ -185,7 +203,11 @@ func Install(cfg *SetupConfig) error {
// Generate JWT secret if not provided
if cfg.JWT.Secret == "" {
cfg.JWT.Secret = generateSecret(32)
secret, err := generateSecret(32)
if err != nil {
return fmt.Errorf("failed to generate jwt secret: %w", err)
}
cfg.JWT.Secret = secret
}
// Test connections
@@ -243,7 +265,11 @@ func initializeDatabase(cfg *SetupConfig) error {
if err != nil {
return err
}
defer sqlDB.Close()
defer func() {
if err := sqlDB.Close(); err != nil {
log.Printf("failed to close postgres connection: %v", err)
}
}()
// 使用 model 包的 AutoMigrate确保模型定义统一
return model.AutoMigrate(db)
@@ -265,7 +291,11 @@ func createAdminUser(cfg *SetupConfig) error {
if err != nil {
return err
}
defer sqlDB.Close()
defer func() {
if err := sqlDB.Close(); err != nil {
log.Printf("failed to close postgres connection: %v", err)
}
}()
// Check if admin already exists
var count int64
@@ -352,10 +382,12 @@ func writeConfigFile(cfg *SetupConfig) error {
return os.WriteFile(ConfigFile, data, 0600)
}
func generateSecret(length int) string {
func generateSecret(length int) (string, error) {
bytes := make([]byte, length)
rand.Read(bytes)
return hex.EncodeToString(bytes)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
// =============================================================================
@@ -431,13 +463,21 @@ func AutoSetupFromEnv() error {
// Generate JWT secret if not provided
if cfg.JWT.Secret == "" {
cfg.JWT.Secret = generateSecret(32)
secret, err := generateSecret(32)
if err != nil {
return fmt.Errorf("failed to generate jwt secret: %w", err)
}
cfg.JWT.Secret = secret
log.Println("Generated JWT secret automatically")
}
// Generate admin password if not provided
if cfg.Admin.Password == "" {
cfg.Admin.Password = generateSecret(16)
password, err := generateSecret(16)
if err != nil {
return fmt.Errorf("failed to generate admin password: %w", err)
}
cfg.Admin.Password = password
log.Printf("Generated admin password: %s", cfg.Admin.Password)
log.Println("IMPORTANT: Save this password! It will not be shown again.")
}