Repository navigation
feat: 添加VIP用户升级系统升级系统 #1572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 添加VIP用户升级系统升级系统 #1572
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| # 🎯 功能: 添加VIP用户升级系统 | ||
|
|
||
| ## 📋 功能概述 | ||
|
|
||
| 为New API添加了完整的VIP用户升级系统,允许用户通过消费额度升级为VIP会员,享受VIP专属权益。 | ||
|
|
||
| ## ✨ 新增功能 | ||
|
|
||
| ### 🎮 用户功能 | ||
| - **一键VIP升级**: 在钱包页面提供VIP升级按钮,用户可直接升级 | ||
| - **智能余额检查**: 自动检查用户余额是否足够(30额度) | ||
| - **VIP状态显示**: 在钱包页面和个人设置显示VIP身份标识 | ||
| - **实时状态更新**: 升级后立即显示VIP状态 | ||
|
|
||
| ### 🔧 管理功能 | ||
| - **功能开关**: 通过`ENABLE_VIP_UPGRADE`环境变量控制功能启用 | ||
| - **配置灵活**: 支持自定义VIP服务URL和升级路径 | ||
| - **数据兼容**: 基于现有user表的`group`字段,无需数据迁移 | ||
|
|
||
| ### 🛡️ 技术特性 | ||
| - **事务安全**: 使用数据库事务确保余额扣除和状态更新的原子性 | ||
| - **权限验证**: API需要用户认证,防止未授权访问 | ||
| - **重复检查**: 防止已是VIP用户重复升级 | ||
| - **向后兼容**: 不影响现有功能,完全向后兼容 | ||
|
|
||
| ## 🎨 用户界面 | ||
|
|
||
| ### 钱包页面 (`/console/topup`) | ||
| - 新增VIP升级卡片,显示VIP特权和升级按钮 | ||
| - 改进用户角色显示,优先显示用户分组(VIP) | ||
| - 升级成功后实时更新界面状态 | ||
|
|
||
| ### 个人设置页面 | ||
| - 自动显示VIP标签(基于现有的renderGroup函数) | ||
| - 与现有角色标签协同显示 | ||
|
|
||
| ## 🔧 技术实现 | ||
|
|
||
| ### 后端 API | ||
| - **新增端点**: `POST /api/user/vip_upgrade` | ||
| - **响应数据**: 返回升级结果和用户新的分组状态 | ||
| - **错误处理**: 完善的错误信息和状态码 | ||
|
|
||
| ### 前端组件 | ||
| - **新增组件**: `VipUpgrade.js` - 独立的VIP升级组件 | ||
| - **功能检测**: 自动检测后端是否启用VIP功能 | ||
| - **状态管理**: 与现有用户状态管理系统集成 | ||
|
|
||
| ### 配置选项 | ||
| ```bash | ||
| # 启用VIP升级功能 | ||
| ENABLE_VIP_UPGRADE=true | ||
|
|
||
| # 可选配置 | ||
| VIP_SERVICE_URL=https://your-service.com | ||
| VIP_UPGRADE_PATH=/console/topup | ||
| ``` | ||
|
|
||
| ## 📁 文件变更 | ||
|
|
||
| ### 后端核心文件 | ||
| - `controller/user.go` - 新增VIP升级API实现 | ||
| - `router/api-router.go` - 添加VIP升级路由 | ||
| - `common/constants.go` - 新增VIP相关常量定义 | ||
| - `common/init.go` - 新增VIP环境变量初始化 | ||
| - `controller/misc.go` - 状态API返回VIP配置信息 | ||
| - `model/option.go` - 扩展系统选项管理 | ||
|
|
||
| ### 前端核心文件 | ||
| - `web/src/components/common/VipUpgrade.js` - **新增** VIP升级组件 | ||
| - `web/src/pages/TopUp/index.js` - 集成VIP升级组件,改进角色显示 | ||
|
|
||
| ### 文档 | ||
| - `docs/VIP_UPGRADE_FEATURE.md` - **新增** 完整功能文档 | ||
|
|
||
| ## 🧪 测试验证 | ||
|
|
||
| - ✅ 功能测试: VIP升级流程完整测试 | ||
| - ✅ 边界测试: 余额不足、重复升级等场景 | ||
| - ✅ 兼容性测试: 与现有功能无冲突 | ||
| - ✅ 多环境测试: 开发、测试环境验证通过 | ||
| - ✅ 数据安全: 事务性保证数据一致性 | ||
|
|
||
| ## 🔒 安全考虑 | ||
|
|
||
| - **权限控制**: API需要用户认证 | ||
| - **重复检查**: 防止已VIP用户重复升级 | ||
| - **余额验证**: 严格检查用户余额 | ||
| - **事务安全**: 原子性操作保证数据一致性 | ||
| - **输入验证**: 完善的参数验证和错误处理 | ||
|
|
||
| ## 🚀 部署说明 | ||
|
|
||
| 1. **零依赖**: 基于现有数据结构,无需额外数据库迁移 | ||
| 2. **渐进式启用**: 通过环境变量控制,可逐步启用 | ||
| 3. **向后兼容**: 不影响现有用户和功能 | ||
| 4. **即插即用**: 部署后立即可用 | ||
|
|
||
| ## 🔮 后续扩展 | ||
|
|
||
| 此功能为VIP系统奠定了基础,后续可扩展: | ||
| - VIP有效期管理 | ||
| - 多级VIP会员制度 | ||
| - VIP专属功能权限 | ||
| - VIP续费和管理功能 | ||
|
|
||
| ## 🎯 影响范围 | ||
|
|
||
| - **用户体验**: 增加VIP升级选项,提升用户粘性 | ||
| - **商业价值**: 提供额度消费转化渠道 | ||
| - **系统架构**: 基于现有架构扩展,无破坏性变更 | ||
| - **维护成本**: 最小化代码变更,降低维护复杂度 | ||
|
|
||
| --- | ||
|
|
||
| **这个PR引入了一个完整的、生产就绪的VIP升级系统,为New API的商业化和用户分层管理提供了强大的基础功能。** |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,7 @@ import ( | |||||
|
|
||||||
| "github.com/gin-contrib/sessions" | ||||||
| "github.com/gin-gonic/gin" | ||||||
| "gorm.io/gorm" | ||||||
| ) | ||||||
|
|
||||||
| type LoginRequest struct { | ||||||
|
|
@@ -407,10 +408,37 @@ func GetSelf(c *gin.Context) { | |||||
| // Hide admin remarks: set to empty to trigger omitempty tag, ensuring the remark field is not included in JSON returned to regular users | ||||||
| user.Remark = "" | ||||||
|
|
||||||
| // 添加VIP状态信息 | ||||||
| setting := user.GetSetting() | ||||||
| userInfo := gin.H{ | ||||||
| "id": user.Id, | ||||||
| "username": user.Username, | ||||||
| "display_name": user.DisplayName, | ||||||
| "role": user.Role, | ||||||
| "status": user.Status, | ||||||
| "email": user.Email, | ||||||
| "github_id": user.GitHubId, | ||||||
| "oidc_id": user.OidcId, | ||||||
| "wechat_id": user.WeChatId, | ||||||
| "telegram_id": user.TelegramId, | ||||||
| "quota": user.Quota, | ||||||
| "used_quota": user.UsedQuota, | ||||||
| "request_count": user.RequestCount, | ||||||
| "group": user.Group, | ||||||
| "aff_code": user.AffCode, | ||||||
| "aff_count": user.AffCount, | ||||||
| "aff_quota": user.AffQuota, | ||||||
| "aff_history_quota": user.AffHistoryQuota, | ||||||
| "inviter_id": user.InviterId, | ||||||
| "linux_do_id": user.LinuxDOId, | ||||||
| "is_vip": user.IsVipUser(), | ||||||
| "vip_expire_time": setting.VipExpireTime, | ||||||
| } | ||||||
|
|
||||||
| c.JSON(http.StatusOK, gin.H{ | ||||||
| "success": true, | ||||||
| "message": "", | ||||||
| "data": user, | ||||||
| "data": userInfo, | ||||||
| }) | ||||||
| return | ||||||
| } | ||||||
|
|
@@ -954,3 +982,71 @@ func UpdateUserSetting(c *gin.Context) { | |||||
| "message": "设置已更新", | ||||||
| }) | ||||||
| } | ||||||
|
|
||||||
| // VipUpgrade VIP升级 | ||||||
| func VipUpgrade(c *gin.Context) { | ||||||
| const VIP_PRICE = 30 * 500000 // VIP升级费用30额度 (30美元 * 500000微额度/美元) | ||||||
| const VIP_DURATION = 30 * 24 * 60 * 60 // VIP有效期30天(秒) | ||||||
|
Comment on lines
+988
to
+989
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Unused constant VIP_DURATION The constant const VIP_PRICE = 30 * 500000 // VIP升级费用30额度 (30美元 * 500000微额度/美元)
- const VIP_DURATION = 30 * 24 * 60 * 60 // VIP有效期30天(秒)If you intend to use it for setting expiration time, you should update the user's VIP expiration time in the transaction: // 设置VIP过期时间
expireTime := time.Now().Unix() + VIP_DURATION
if err := tx.Model(&model.User{}).Where("id = ?", userId).Update("vip_expire_time", expireTime).Error; err != nil {
return err
}🤖 Prompt for AI Agents |
||||||
|
|
||||||
| userId := c.GetInt("id") | ||||||
| user, err := model.GetUserById(userId, true) | ||||||
| if err != nil { | ||||||
| c.JSON(http.StatusOK, gin.H{ | ||||||
| "success": false, | ||||||
| "message": "获取用户信息失败", | ||||||
| }) | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
| // 检查用户余额是否足够 | ||||||
| if user.Quota < VIP_PRICE { | ||||||
| c.JSON(http.StatusOK, gin.H{ | ||||||
| "success": false, | ||||||
| "message": "余额不足,需要30额度才能升级VIP", | ||||||
| }) | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
| // 检查用户是否已经是VIP | ||||||
| if user.Group == "vip" { | ||||||
| c.JSON(http.StatusOK, gin.H{ | ||||||
| "success": false, | ||||||
| "message": "您已经是VIP用户", | ||||||
| }) | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
| // 使用事务确保余额扣除和VIP状态更新的原子性 | ||||||
| err = model.DB.Transaction(func(tx *gorm.DB) error { | ||||||
| // 扣除余额 | ||||||
| if err := tx.Model(&model.User{}).Where("id = ?", userId).Update("quota", gorm.Expr("quota - ?", VIP_PRICE)).Error; err != nil { | ||||||
| return err | ||||||
| } | ||||||
|
|
||||||
| // 设置VIP状态 | ||||||
| if err := tx.Model(&model.User{}).Where("id = ?", userId).Update("group", "vip").Error; err != nil { | ||||||
| return err | ||||||
| } | ||||||
|
|
||||||
| return nil | ||||||
| }) | ||||||
|
|
||||||
| if err != nil { | ||||||
| c.JSON(http.StatusOK, gin.H{ | ||||||
| "success": false, | ||||||
| "message": "升级VIP失败: " + err.Error(), | ||||||
| }) | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
| // 记录日志 | ||||||
| model.RecordLog(userId, model.LogTypeTopup, fmt.Sprintf("升级VIP成功,扣除额度: %d", VIP_PRICE)) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Incorrect log type used Using - model.RecordLog(userId, model.LogTypeTopup, fmt.Sprintf("升级VIP成功,扣除额度: %d", VIP_PRICE))
+ model.RecordLog(userId, model.LogTypeSystem, fmt.Sprintf("升级VIP成功,扣除额度: %d", VIP_PRICE/500000))Note: The logged quota value should be divided by 500000 to show the actual dollar amount (30) rather than the internal representation (15000000). 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| c.JSON(http.StatusOK, gin.H{ | ||||||
| "success": true, | ||||||
| "message": "恭喜您!VIP升级成功", | ||||||
| "data": gin.H{ | ||||||
| "group": "vip", | ||||||
| }, | ||||||
| }) | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| # VIP升级功能 | ||
|
|
||
| ## 功能概述 | ||
|
|
||
| VIP升级功能为New API添加了用户VIP会员系统,允许用户通过消费额度升级为VIP用户,享受VIP专属权益。 | ||
|
|
||
| ## 功能特性 | ||
|
|
||
| ### 🎯 核心功能 | ||
| - **一键VIP升级** - 用户可通过钱包页面直接升级VIP | ||
| - **智能余额检查** - 自动检查用户余额是否足够升级 | ||
| - **原子事务处理** - 确保余额扣除和状态更新的数据一致性 | ||
| - **VIP状态显示** - 在个人设置和钱包页面显示VIP状态 | ||
|
|
||
| ### 💰 费用设置 | ||
| - **升级费用**: 30额度(可配置) | ||
| - **计费方式**: 基于new-api原生额度系统(微额度) | ||
| - **扣费机制**: 立即扣除,事务性保证 | ||
|
|
||
| ### 👑 VIP权益系统 | ||
| - **用户分组**: 基于new-api原生的`group`字段('vip') | ||
| - **权限继承**: 兼容现有的角色权限系统 | ||
| - **显示标识**: 前端显示专属VIP标签 | ||
|
|
||
| ## 技术架构 | ||
|
|
||
| ### 后端实现 | ||
| - **API端点**: `POST /api/user/vip_upgrade` | ||
| - **权限控制**: 需要用户认证 | ||
| - **数据库**: 基于原生user表的`group`字段 | ||
| - **事务安全**: 使用GORM事务确保数据一致性 | ||
|
|
||
| ### 前端实现 | ||
| - **VIP组件**: `web/src/components/common/VipUpgrade.js` | ||
| - **集成页面**: 钱包页面和个人设置页面 | ||
| - **状态检测**: 自动检测用户VIP状态 | ||
| - **UI反馈**: 升级成功/失败的用户反馈 | ||
|
|
||
| ### 配置选项 | ||
| - `ENABLE_VIP_UPGRADE`: 启用/禁用VIP升级功能 | ||
| - `VIP_SERVICE_URL`: VIP服务相关URL(可选) | ||
| - `VIP_UPGRADE_PATH`: VIP升级页面路径(可选) | ||
|
|
||
| ## 使用方式 | ||
|
|
||
| ### 管理员配置 | ||
| 1. 设置环境变量 `ENABLE_VIP_UPGRADE=true` 启用功能 | ||
| 2. 可选配置VIP服务相关URL和路径 | ||
|
|
||
| ### 用户使用 | ||
| 1. 访问钱包页面(`/console/topup`) | ||
| 2. 点击"升级VIP"按钮 | ||
| 3. 确认升级(自动扣除30额度) | ||
| 4. 升级成功后享受VIP权益 | ||
|
|
||
| ### VIP状态查看 | ||
| - **钱包页面**: 显示当前用户分组状态 | ||
| - **个人设置**: 显示VIP标签 | ||
| - **API接口**: `/api/user/self` 返回用户分组信息 | ||
|
|
||
| ## 兼容性 | ||
|
|
||
| - ✅ **向后兼容**: 不影响现有功能 | ||
| - ✅ **版本兼容**: 支持new-api最新版本 | ||
| - ✅ **数据兼容**: 基于原生数据结构 | ||
| - ✅ **权限兼容**: 与现有角色系统协同工作 | ||
|
|
||
| ## 文件修改列表 | ||
|
|
||
| ### 后端核心文件 | ||
| - `controller/user.go` - VIP升级API实现 | ||
| - `router/api-router.go` - 路由配置 | ||
| - `common/constants.go` - VIP相关常量 | ||
| - `common/init.go` - 环境变量初始化 | ||
| - `controller/misc.go` - 状态API扩展 | ||
| - `model/option.go` - 选项管理扩展 | ||
|
|
||
| ### 前端核心文件 | ||
| - `web/src/components/common/VipUpgrade.js` - VIP升级组件 | ||
| - `web/src/pages/TopUp/index.js` - 钱包页面集成 | ||
| - `web/src/components/settings/PersonalSetting.js` - 个人设置显示 | ||
|
|
||
| ### 配置文件 | ||
| - `dto/user_settings.go` - 用户设置数据结构扩展 | ||
|
|
||
| ## 安全考虑 | ||
|
|
||
| - **权限验证**: API需要用户认证 | ||
| - **余额检查**: 防止余额不足的恶意请求 | ||
| - **事务安全**: 确保扣费和状态更新的原子性 | ||
| - **重复检查**: 防止重复升级VIP | ||
|
|
||
| ## 部署说明 | ||
|
|
||
| 1. **环境变量设置**: | ||
| ```bash | ||
| ENABLE_VIP_UPGRADE=true | ||
| ``` | ||
|
|
||
| 2. **重启服务**: 修改后需要重启new-api服务 | ||
|
|
||
| 3. **数据库**: 自动使用现有user表,无需额外迁移 | ||
|
|
||
| ## 后续扩展 | ||
|
|
||
| - 支持VIP有效期设置 | ||
| - 支持多级VIP会员 | ||
| - 支持VIP专属功能 | ||
| - 支持VIP续费机制 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing nil check for user settings
The
GetSetting()method could potentially return nil or panic if the user object is malformed. Add defensive checks before accessing the setting.📝 Committable suggestion
🤖 Prompt for AI Agents