fix: 修复数据库配置和事务一致性问题
数据库配置: - SQLite 开启 WAL 模式,读写可并发 - 设置 busy_timeout=5000ms 避免锁超时 - 设置 synchronous=NORMAL 平衡性能和安全 - SQLite MaxOpenConns 设为 1 避免并发写入冲突 事务修复: - auth.go: 注册流程(用户+设备+会话)加入事务 - auth.go: 密码重置(修改密码+标记验证码)加入事务 - cards.go: 卡密使用加入事务,补充 UsedAt 字段 - cards.go: 代理商批量生成(扣款+生成卡密)加入事务 - extension.go: 充值(修改余额+记录)加入事务 - extension.go: 扣费(修改余额+记录)加入事务 - storage_config.go: 设置默认存储加入事务
This commit is contained in:
@@ -59,7 +59,7 @@ func initSQLite() {
|
|||||||
dbPath := filepath.Join(dataDir, "verification_platform.db")
|
dbPath := filepath.Join(dataDir, "verification_platform.db")
|
||||||
|
|
||||||
var errOpen error
|
var errOpen error
|
||||||
DB, errOpen = gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
DB, errOpen = gorm.Open(sqlite.Open(dbPath+"?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL"), &gorm.Config{
|
||||||
Logger: logger.Default.LogMode(logger.Silent),
|
Logger: logger.Default.LogMode(logger.Silent),
|
||||||
DisableForeignKeyConstraintWhenMigrating: true,
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
})
|
})
|
||||||
@@ -70,7 +70,7 @@ func initSQLite() {
|
|||||||
|
|
||||||
if sqlDB, err := DB.DB(); err == nil {
|
if sqlDB, err := DB.DB(); err == nil {
|
||||||
sqlDB.SetMaxIdleConns(10)
|
sqlDB.SetMaxIdleConns(10)
|
||||||
sqlDB.SetMaxOpenConns(100)
|
sqlDB.SetMaxOpenConns(1)
|
||||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ func initAutoDetect() {
|
|||||||
dataDir := config.GetDataDir()
|
dataDir := config.GetDataDir()
|
||||||
dbPath := filepath.Join(dataDir, "verification_platform.db")
|
dbPath := filepath.Join(dataDir, "verification_platform.db")
|
||||||
|
|
||||||
DB, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
DB, err = gorm.Open(sqlite.Open(dbPath+"?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL"), &gorm.Config{
|
||||||
Logger: logger.Default.LogMode(logger.Silent),
|
Logger: logger.Default.LogMode(logger.Silent),
|
||||||
DisableForeignKeyConstraintWhenMigrating: true,
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
})
|
})
|
||||||
@@ -143,8 +143,13 @@ func initAutoDetect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sqlDB, err := DB.DB(); err == nil {
|
if sqlDB, err := DB.DB(); err == nil {
|
||||||
sqlDB.SetMaxIdleConns(config.GetInt("database.max_idle_conns"))
|
if config.GetString("database.type") == "sqlite" || config.GetString("database.type") == "" {
|
||||||
sqlDB.SetMaxOpenConns(config.GetInt("database.max_open_conns"))
|
sqlDB.SetMaxIdleConns(10)
|
||||||
|
sqlDB.SetMaxOpenConns(1)
|
||||||
|
} else {
|
||||||
|
sqlDB.SetMaxIdleConns(config.GetInt("database.max_idle_conns"))
|
||||||
|
sqlDB.SetMaxOpenConns(config.GetInt("database.max_open_conns"))
|
||||||
|
}
|
||||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -618,19 +618,36 @@ func handleUseCard(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx := database.DB.Begin()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var card model.Card
|
var card model.Card
|
||||||
if err := database.DB.Where("card_key = ?", req.CardKey).First(&card).Error; err != nil {
|
if err := tx.Where("card_key = ?", req.CardKey).First(&card).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
response.Error(c, 404, "卡密不存在")
|
response.Error(c, 404, "卡密不存在")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if card.Status != "unused" {
|
if card.Status != "unused" {
|
||||||
|
tx.Rollback()
|
||||||
response.Error(c, 400, "卡密已被使用或已禁用")
|
response.Error(c, 400, "卡密已被使用或已禁用")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
card.Status = "used"
|
card.Status = "used"
|
||||||
database.DB.Save(&card)
|
now := time.Now()
|
||||||
|
card.UsedAt = &now
|
||||||
|
if err := tx.Save(&card).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
response.Error(c, 500, "卡密使用失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
response.Success(c, gin.H{
|
response.Success(c, gin.H{
|
||||||
"message": "卡密使用成功",
|
"message": "卡密使用成功",
|
||||||
@@ -733,13 +750,50 @@ func handleBatchGenerateCards(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.DB.Model(&agentUser).Update("balance", agentUser.Balance-totalCost).Error; err != nil {
|
tx := database.DB.Begin()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := tx.Model(&agentUser).Update("balance", agentUser.Balance-totalCost).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
fmt.Printf("[DEBUG] Update balance error: %v\n", err)
|
fmt.Printf("[DEBUG] Update balance error: %v\n", err)
|
||||||
response.Error(c, 500, "扣款失败")
|
response.Error(c, 500, "扣款失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("[DEBUG] Balance updated successfully\n")
|
cards := make([]model.Card, 0, req.Count)
|
||||||
|
for i := 0; i < req.Count; i++ {
|
||||||
|
cardKey := req.Prefix + utils.GenerateRandomString(req.Length)
|
||||||
|
card := model.Card{
|
||||||
|
ApplicationID: req.ApplicationID,
|
||||||
|
CardTypeID: req.CardTypeID,
|
||||||
|
CardKey: cardKey,
|
||||||
|
CreatorID: effectiveUserID,
|
||||||
|
Status: "unused",
|
||||||
|
}
|
||||||
|
cards = append(cards, card)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[DEBUG] Generated %d cards, saving to database...\n", len(cards))
|
||||||
|
|
||||||
|
if err := tx.Create(&cards).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
fmt.Printf("[DEBUG] Database create error: %v\n", err)
|
||||||
|
response.Error(c, 500, "生成卡密失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
fmt.Printf("[DEBUG] Successfully created %d cards\n", len(cards))
|
||||||
|
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"message": "批量生成成功",
|
||||||
|
"count": req.Count,
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cards := make([]model.Card, 0, req.Count)
|
cards := make([]model.Card, 0, req.Count)
|
||||||
|
|||||||
@@ -60,30 +60,54 @@ func handleCreateStorageConfig(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var config model.StorageConfig
|
||||||
|
|
||||||
if req.IsDefault {
|
if req.IsDefault {
|
||||||
database.DB.Model(&model.StorageConfig{}).Where("is_default = ?", true).Update("is_default", false)
|
tx := database.DB.Begin()
|
||||||
}
|
tx.Model(&model.StorageConfig{}).Where("is_default = ?", true).Update("is_default", false)
|
||||||
|
config = model.StorageConfig{
|
||||||
config := model.StorageConfig{
|
Name: req.Name,
|
||||||
Name: req.Name,
|
Type: req.Type,
|
||||||
Type: req.Type,
|
Endpoint: req.Endpoint,
|
||||||
Endpoint: req.Endpoint,
|
Bucket: req.Bucket,
|
||||||
Bucket: req.Bucket,
|
AccessKey: req.AccessKey,
|
||||||
AccessKey: req.AccessKey,
|
SecretKey: req.SecretKey,
|
||||||
SecretKey: req.SecretKey,
|
Region: req.Region,
|
||||||
Region: req.Region,
|
PathPrefix: req.PathPrefix,
|
||||||
PathPrefix: req.PathPrefix,
|
IsDefault: req.IsDefault,
|
||||||
IsDefault: req.IsDefault,
|
Status: "active",
|
||||||
Status: "active",
|
Remark: req.Remark,
|
||||||
Remark: req.Remark,
|
}
|
||||||
}
|
if err := tx.Create(&config).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
if err := database.DB.Create(&config).Error; err != nil {
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
"code": 500,
|
||||||
"code": 500,
|
"message": "创建失败",
|
||||||
"message": "创建失败",
|
})
|
||||||
})
|
return
|
||||||
return
|
}
|
||||||
|
tx.Commit()
|
||||||
|
} else {
|
||||||
|
config = model.StorageConfig{
|
||||||
|
Name: req.Name,
|
||||||
|
Type: req.Type,
|
||||||
|
Endpoint: req.Endpoint,
|
||||||
|
Bucket: req.Bucket,
|
||||||
|
AccessKey: req.AccessKey,
|
||||||
|
SecretKey: req.SecretKey,
|
||||||
|
Region: req.Region,
|
||||||
|
PathPrefix: req.PathPrefix,
|
||||||
|
IsDefault: req.IsDefault,
|
||||||
|
Status: "active",
|
||||||
|
Remark: req.Remark,
|
||||||
|
}
|
||||||
|
if err := database.DB.Create(&config).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "创建失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|||||||
@@ -313,7 +313,15 @@ func handleAppRegister(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.DB.Create(&user).Error; err != nil {
|
tx := database.DB.Begin()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := tx.Create(&user).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
log.Printf("[DEBUG] Failed to create user: %v", err)
|
log.Printf("[DEBUG] Failed to create user: %v", err)
|
||||||
response.Error(c, 500, "注册失败")
|
response.Error(c, 500, "注册失败")
|
||||||
return
|
return
|
||||||
@@ -330,7 +338,7 @@ func handleAppRegister(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var device model.UserDevice
|
var device model.UserDevice
|
||||||
if err := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", user.ID, appModel.ID, req.DeviceID).First(&device).Error; err != nil {
|
if err := tx.Where("user_id = ? AND application_id = ? AND device_id = ?", user.ID, appModel.ID, req.DeviceID).First(&device).Error; err != nil {
|
||||||
device = model.UserDevice{
|
device = model.UserDevice{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
ApplicationID: appModel.ID,
|
ApplicationID: appModel.ID,
|
||||||
@@ -339,7 +347,7 @@ func handleAppRegister(c *gin.Context) {
|
|||||||
DeviceType: deviceType,
|
DeviceType: deviceType,
|
||||||
Status: "active",
|
Status: "active",
|
||||||
}
|
}
|
||||||
if err := database.DB.Create(&device).Error; err != nil {
|
if err := tx.Create(&device).Error; err != nil {
|
||||||
log.Printf("[DEBUG] Failed to create device: %v", err)
|
log.Printf("[DEBUG] Failed to create device: %v", err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[DEBUG] Successfully created device with ID: %d, type: %s", device.ID, deviceType)
|
log.Printf("[DEBUG] Successfully created device with ID: %d, type: %s", device.ID, deviceType)
|
||||||
@@ -353,7 +361,7 @@ func handleAppRegister(c *gin.Context) {
|
|||||||
|
|
||||||
if device.ID > 0 {
|
if device.ID > 0 {
|
||||||
var session model.DeviceSession
|
var session model.DeviceSession
|
||||||
sessionErr := database.DB.Where("device_id = ? AND instance_id = ?", device.ID, instanceID).First(&session).Error
|
sessionErr := tx.Where("device_id = ? AND instance_id = ?", device.ID, instanceID).First(&session).Error
|
||||||
|
|
||||||
if sessionErr != nil {
|
if sessionErr != nil {
|
||||||
session = model.DeviceSession{
|
session = model.DeviceSession{
|
||||||
@@ -363,18 +371,20 @@ func handleAppRegister(c *gin.Context) {
|
|||||||
InstanceID: instanceID,
|
InstanceID: instanceID,
|
||||||
LastHeartbeat: &now,
|
LastHeartbeat: &now,
|
||||||
}
|
}
|
||||||
if err := database.DB.Create(&session).Error; err != nil {
|
if err := tx.Create(&session).Error; err != nil {
|
||||||
log.Printf("[DEBUG] Failed to create session: %v", err)
|
log.Printf("[DEBUG] Failed to create session: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
session.LastHeartbeat = &now
|
session.LastHeartbeat = &now
|
||||||
if err := database.DB.Save(&session).Error; err != nil {
|
if err := tx.Save(&session).Error; err != nil {
|
||||||
log.Printf("[DEBUG] Failed to update session: %v", err)
|
log.Printf("[DEBUG] Failed to update session: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
service.LogVerification(c, &appModel.ID, &user.ID, "register", "用户注册: "+req.Username, req.DeviceID, nil)
|
service.LogVerification(c, &appModel.ID, &user.ID, "register", "用户注册: "+req.Username, req.DeviceID, nil)
|
||||||
|
|
||||||
response.SuccessWithMessage(c, "注册成功", gin.H{
|
response.SuccessWithMessage(c, "注册成功", gin.H{
|
||||||
@@ -903,14 +913,24 @@ func handleAppResetPassword(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx := database.DB.Begin()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
user.Password = string(hashedPassword)
|
user.Password = string(hashedPassword)
|
||||||
if err := database.DB.Save(&user).Error; err != nil {
|
if err := tx.Save(&user).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
response.Error(c, 500, "密码重置失败")
|
response.Error(c, 500, "密码重置失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyCode.Used = true
|
verifyCode.Used = true
|
||||||
database.DB.Save(&verifyCode)
|
tx.Save(&verifyCode)
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
response.Success(c, gin.H{
|
response.Success(c, gin.H{
|
||||||
"message": "密码重置成功",
|
"message": "密码重置成功",
|
||||||
|
|||||||
@@ -272,7 +272,15 @@ func handleRechargeUser(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.DB.Save(&user).Error; err != nil {
|
tx := database.DB.Begin()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := tx.Save(&user).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
service.LogVerification(c, &app.ID, &user.ID, "extension_recharge_failed", fmt.Sprintf("扩展API充值失败: 保存失败 - %s", user.Username), "", err)
|
service.LogVerification(c, &app.ID, &user.ID, "extension_recharge_failed", fmt.Sprintf("扩展API充值失败: 保存失败 - %s", user.Username), "", err)
|
||||||
response.Error(c, 500, "充值失败")
|
response.Error(c, 500, "充值失败")
|
||||||
return
|
return
|
||||||
@@ -286,7 +294,13 @@ func handleRechargeUser(c *gin.Context) {
|
|||||||
PaymentType: "extension_api",
|
PaymentType: "extension_api",
|
||||||
Remark: req.Description,
|
Remark: req.Description,
|
||||||
}
|
}
|
||||||
database.DB.Create(&record)
|
if err := tx.Create(&record).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
response.Error(c, 500, "充值失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
service.LogVerification(c, &app.ID, &user.ID, "extension_recharge", fmt.Sprintf("扩展API充值: 用户%s, 类型:%s, 数量:%d", user.Username, req.Type, req.Amount), "", nil)
|
service.LogVerification(c, &app.ID, &user.ID, "extension_recharge", fmt.Sprintf("扩展API充值: 用户%s, 类型:%s, 数量:%d", user.Username, req.Type, req.Amount), "", nil)
|
||||||
|
|
||||||
@@ -355,7 +369,15 @@ func handleDeductUser(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.DB.Save(&user).Error; err != nil {
|
tx := database.DB.Begin()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := tx.Save(&user).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct_failed", fmt.Sprintf("扩展API扣费失败: 保存失败 - %s", user.Username), "", err)
|
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct_failed", fmt.Sprintf("扩展API扣费失败: 保存失败 - %s", user.Username), "", err)
|
||||||
response.Error(c, 500, "扣除失败")
|
response.Error(c, 500, "扣除失败")
|
||||||
return
|
return
|
||||||
@@ -370,7 +392,13 @@ func handleDeductUser(c *gin.Context) {
|
|||||||
PaymentType: "extension_api",
|
PaymentType: "extension_api",
|
||||||
Remark: req.Description,
|
Remark: req.Description,
|
||||||
}
|
}
|
||||||
database.DB.Create(&record)
|
if err := tx.Create(&record).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
response.Error(c, 500, "扣除失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct", fmt.Sprintf("扩展API扣费: 用户%s, 类型:%s, 数量:%d", user.Username, req.Type, req.Amount), "", nil)
|
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct", fmt.Sprintf("扩展API扣费: 用户%s, 类型:%s, 数量:%d", user.Username, req.Type, req.Amount), "", nil)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user