2020-06-10 17:41:34
在 Golang 中确保虚拟币充值用户余额更新的安全性和准确性,需从令牌管理、任务系统设计、数据库操作三个核心层面构建防护机制,具体实现如下:
一、令牌生成与验证的安全机制JWT 令牌的加密生成
使用 github.com/dgrijalva/jwt-go 库生成包含用户ID、过期时间(如 time.Now().Add(24 * time.Hour))的令牌,通过 HS256 或 RS256 算法签名。
示例代码:token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": 123, "exp": time.Now().Add(24 * time.Hour).Unix(),})tokenString, _ := token.SignedString(secretKey)
令牌验证的严格检查
解密时验证签名、过期时间(claims["exp"] > time.Now().Unix())及用户ID匹配性。
示例验证逻辑:token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return secretKey, nil})if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { if claims["user_id"] != requestUserID { return errors.New("用户ID不匹配") }}
单步任务:直接更新余额
适用于简单场景(如直接充值),通过条件检查(如支付成功状态)触发余额更新。
示例逻辑:if payment.Status == "success" { err := db.Exec("UPDATE balances SET amount = amount + ? WHERE user_id = ?", amount, userID)}
多步任务:状态加密与异步更新
Redis 存储任务状态:使用 github.com/go-redis/redis 记录任务进度(如视频播放时长),键为 task:{userID},值为加密后的进度数据。
进度加密:通过 AES-GCM 对任务状态加密,防止篡改。ciphertext, _ := encrypt(taskProgress, encryptionKey)err := redisClient.Set(ctx, fmt.Sprintf("task:%d", userID), ciphertext, 24*time.Hour).Err()
异步更新:当任务完成(如播放时长≥30秒),通过消息队列(如 RabbitMQ)触发余额更新,避免同步阻塞。
事务处理
使用数据库事务确保余额更新的原子性,失败时回滚。
MySQL 示例:tx, _ := db.Begin()_, err := tx.Exec("UPDATE balances SET amount = amount + ? WHERE user_id = ?", amount, userID)if err != nil { tx.Rollback()} else { tx.Commit()}
乐观锁控制并发
在余额表中添加 version 字段,更新时检查版本号是否匹配。result, err := tx.Exec("UPDATE balances SET amount = amount + ?, version = version + 1 WHERE user_id = ? AND version = ?", amount, userID, currentVersion)if affectedRows, _ := result.RowsAffected(); affectedRows == 0 { return errors.New("并发更新冲突")}
令牌安全
设置短过期时间(如 15 分钟),结合 Refresh Token 机制延长会话。
定期轮换加密密钥(secretKey),避免密钥泄露。
任务防篡改
对任务状态进行哈希校验(如 SHA-256),更新时验证哈希值是否一致。
限制任务完成频率(如每分钟最多 1 次),防止刷量。
日志与监控
记录所有余额变更操作(用户ID、金额、时间、IP),通过 ELK 堆栈分析异常行为。
设置告警规则(如单用户 5 分钟内更新超过 10 次),及时阻断可疑请求。
通过上述机制,可实现从令牌授权到数据库更新的全链路安全防护,确保虚拟币充值场景下余额更新的准确性与不可篡改性。