feat: 添加VIP用户升级系统升级系统 - #1572
Conversation
- 新增VIP升级API端点 (/api/user/vip_upgrade) - 实现基于额度的VIP升级机制 (30额度) - 添加VIP升级前端组件和界面集成 - 支持环境变量配置功能开关 - 改进用户角色和分组显示逻辑 - 使用数据库事务确保升级操作原子性 - 完全向后兼容,基于现有user.group字段 核心功能: - 用户可在钱包页面一键升级VIP - 自动检查余额并扣除30额度 - 升级后立即显示VIP状态 - 防重复升级和余额不足保护 技术特性: - 零数据库迁移依赖 - 事务性保证数据一致性 - 完善的错误处理和用户反馈 - 响应式UI设计
WalkthroughAdds a production-ready VIP upgrade system: env-configurable feature flag and URLs, backend POST /api/user/self/vip_upgrade with transactional quota deduction and group promotion, status endpoints expose VIP flags, option persistence, frontend VipUpgrade component integrated into TopUp, and supporting docs/scripts. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant FE as Frontend (VipUpgrade)
participant API as New API
participant DB as Database
U->>FE: Click "Upgrade VIP"
FE->>API: POST /api/user/self/vip_upgrade
API->>DB: Begin transaction
API->>DB: Check balance & deduct VIP_PRICE
API->>DB: Update user.group = "vip"
DB-->>API: Commit
API-->>FE: { "group":"vip" }
FE-->>U: Update UI (VIP badge, quota)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
- 将默认VIP服务URL改为通用域名示例 - 移除前端代码中硬编码的服务器IP地址 - 使用相对路径替代绝对URL地址 - 确保PR中不包含生产环境敏感信息
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (6)
docs/VIP_UPGRADE_FEATURE.md (2)
16-17: 使用规范的量词表达根据中文语言规范,数词与名词之间应使用适当的量词。
-- **升级费用**: 30额度(可配置) +- **升级费用**: 30个额度(可配置)
53-53: 使用规范的量词表达保持文档用语的规范性。
-3. 确认升级(自动扣除30额度) +3. 确认升级(自动扣除30个额度)common/constants.go (1)
149-154: VIP配置常量定义合理VIP升级功能的常量定义清晰,默认值设置合理。建议考虑将默认服务URL改为相对路径或配置为空字符串,避免硬编码外部域名。
// VIP升级功能配置 var ( EnableVipUpgrade = false - VipServiceUrl = "https://tiantianai.pro" + VipServiceUrl = "" // 默认为空,通过环境变量配置 VipUpgradePath = "/console/topup" )PR_DESCRIPTION.md (1)
11-11: 使用规范的量词表达保持PR描述的语言规范性。
-- **智能余额检查**: 自动检查用户余额是否足够(30额度) +- **智能余额检查**: 自动检查用户余额是否足够(30个额度)controller/user.go (1)
1019-1032: Consider adding rollback loggingWhile the transaction ensures atomicity, if the transaction fails, there's no specific log entry for the failure. Consider adding a failure log for audit purposes.
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 { + model.RecordLog(userId, model.LogTypeSystem, fmt.Sprintf("升级VIP失败: %s", err.Error())) c.JSON(http.StatusOK, gin.H{ "success": false, "message": "升级VIP失败: " + err.Error(), }) return }web/src/components/common/VipUpgrade.js (1)
71-71: Remove console.log statements in productionDebug console.log statements should be removed or replaced with proper logging for production code.
- console.log('当前用户数据:', userState?.user); + // console.log('当前用户数据:', userState?.user); - console.log('用户setting字段:', setting); + // console.log('用户setting字段:', setting); - console.warn('Failed to parse user setting for VIP status:', error); + // console.warn('Failed to parse user setting for VIP status:', error);Also applies to: 93-93, 106-106
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
PR_DESCRIPTION.md(1 hunks)common/constants.go(1 hunks)common/init.go(1 hunks)controller/misc.go(1 hunks)controller/user.go(3 hunks)docs/VIP_UPGRADE_FEATURE.md(1 hunks)model/option.go(3 hunks)router/api-router.go(1 hunks)web/src/components/common/VipUpgrade.js(1 hunks)web/src/pages/TopUp/index.js(3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
router/api-router.go (2)
web/src/components/common/VipUpgrade.js (1)
VipUpgrade(20-418)controller/user.go (1)
VipUpgrade(987-1052)
web/src/components/common/VipUpgrade.js (2)
web/src/pages/TopUp/index.js (4)
Typography(41-41)useTranslation(44-44)userState(45-45)statusState(46-46)controller/user.go (1)
VipUpgrade(987-1052)
controller/misc.go (1)
common/constants.go (3)
EnableVipUpgrade(151-151)VipServiceUrl(152-152)VipUpgradePath(153-153)
model/option.go (1)
common/constants.go (4)
OptionMap(36-36)EnableVipUpgrade(151-151)VipServiceUrl(152-152)VipUpgradePath(153-153)
controller/user.go (4)
web/src/components/common/VipUpgrade.js (1)
VipUpgrade(20-418)model/user.go (2)
GetUserById(224-236)User(18-47)model/main.go (1)
DB(63-63)model/log.go (1)
RecordLog(76-92)
common/init.go (2)
common/constants.go (3)
EnableVipUpgrade(151-151)VipServiceUrl(152-152)VipUpgradePath(153-153)common/env.go (2)
GetEnvOrDefaultBool(28-38)GetEnvOrDefaultString(21-26)
web/src/pages/TopUp/index.js (2)
web/src/components/common/VipUpgrade.js (2)
userState(22-22)VipUpgrade(20-418)controller/user.go (1)
VipUpgrade(987-1052)
🪛 LanguageTool
docs/VIP_UPGRADE_FEATURE.md
[uncategorized] ~16-~16: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...设置和钱包页面显示VIP状态 ### 💰 费用设置 - 升级费用: 30额度(可配置) - 计费方式: 基于new-api原生额度系统(微额度) -...
(wa5)
[uncategorized] ~53-~53: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...sole/topup`) 2. 点击"升级VIP"按钮 3. 确认升级(自动扣除30额度) 4. 升级成功后享受VIP权益 ### VIP状态查看 - *钱包页面...
(wa5)
PR_DESCRIPTION.md
[uncategorized] ~11-~11: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...升级按钮,用户可直接升级 - 智能余额检查: 自动检查用户余额是否足够(30额度) - VIP状态显示: 在钱包页面和个人设置显示VIP身份标识 - *...
(wa5)
🔇 Additional comments (12)
router/api-router.go (1)
69-69: VIP升级路由设计合理新增的VIP升级路由放置在用户认证路由组下,符合安全设计原则,确保只有已认证用户才能访问该端点。
controller/misc.go (1)
96-99: VIP配置信息暴露合理在状态API中添加VIP升级相关配置信息,使前端能够根据配置动态显示功能,实现了良好的前后端解耦。
common/init.go (1)
91-94: 环境变量初始化实现正确VIP升级相关环境变量的初始化逻辑正确,使用了统一的辅助函数处理默认值。
model/option.go (3)
138-141: VIP配置选项管理完善正确地将VIP升级配置添加到选项映射中,实现了配置的持久化管理。
284-285: 布尔配置更新处理正确在布尔值配置更新处理中正确添加了EnableVipUpgrade的case分支。
422-425: 字符串配置更新处理正确在字符串配置更新处理中正确添加了VipServiceUrl和VipUpgradePath的case分支。
web/src/pages/TopUp/index.js (3)
39-39: VIP升级组件导入正确正确导入了VipUpgrade组件,符合模块化设计原则。
109-132: 用户角色显示逻辑改进合理getUserRole函数的改进很好地处理了VIP用户分组的显示逻辑,优先显示用户分组(如VIP),这为VIP用户提供了更清晰的身份标识。实现逻辑清晰,向后兼容性良好。
1087-1091: 右侧布局调整合理在右侧区域添加VIP升级卡片,并使用space-y-6保持组件间距一致,布局设计合理,用户体验良好。
controller/user.go (2)
20-20: LGTM!The addition of the
gorm.io/gormimport is necessary for the transaction handling in the VipUpgrade function.
1002-1008: Quota precision check passed—no action needed
All quota values, including user.Quota and VIP_PRICE, use the same “micro-unit” basis (500 000 micro-credits per unit). VIP_PRICE is defined as30 * 500000, and quota assignments, deductions, and sample values throughout the code consistently operate in these micro-units.web/src/components/common/VipUpgrade.js (1)
36-60: Potential infinite loop in useEffectThe useEffect dependency array includes
userState?.user?.settingwhich is being updated inside the effect itself viauserDispatch. This could cause an infinite loop.- }, [userState?.user?.id, userInfoLoaded, userDispatch, userState?.user?.setting]); + }, [userState?.user?.id, userInfoLoaded]);Likely an incorrect or invalid review comment.
| // 添加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, | ||
| } |
There was a problem hiding this comment.
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.
// 添加VIP状态信息
- setting := user.GetSetting()
+ setting := &dto.UserSetting{}
+ if user != nil {
+ setting = user.GetSetting()
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 添加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, | |
| } | |
| // 添加VIP状态信息 | |
| setting := &dto.UserSetting{} | |
| if user != nil { | |
| 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, | |
| } |
🤖 Prompt for AI Agents
In controller/user.go around lines 411 to 436, the code calls setting :=
user.GetSetting() and immediately accesses setting.VipExpireTime without
checking for nil; add a defensive nil check after GetSetting() and use a safe
default (e.g., nil, empty string, or zero time) when setting is nil. Concretely:
call GetSetting(), if it returns nil set a local variable vipExpireTime to the
safe default, otherwise set vipExpireTime = setting.VipExpireTime, and then use
vipExpireTime in the userInfo map instead of directly referencing
setting.VipExpireTime.
| const VIP_PRICE = 30 * 500000 // VIP升级费用30额度 (30美元 * 500000微额度/美元) | ||
| const VIP_DURATION = 30 * 24 * 60 * 60 // VIP有效期30天(秒) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Unused constant VIP_DURATION
The constant VIP_DURATION is defined but never used in the function. Either use it to set the VIP expiration time or remove it.
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
In controller/user.go around lines 988-989, the constant VIP_DURATION is
declared but unused; either remove the constant or use it to set the user's VIP
expiration inside the existing transaction — compute expireTime as
time.Now().Unix() + VIP_DURATION and perform a tx.Model(&model.User{}).Where("id
= ?", userId).Update("vip_expire_time", expireTime) (handling and returning any
error), ensuring this update is done within the same transaction that grants VIP
to keep atomicity.
| } | ||
|
|
||
| // 记录日志 | ||
| model.RecordLog(userId, model.LogTypeTopup, fmt.Sprintf("升级VIP成功,扣除额度: %d", VIP_PRICE)) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Incorrect log type used
Using LogTypeTopup for VIP upgrade might be misleading. Consider creating a dedicated log type for VIP operations or use a more appropriate existing type.
- 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
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| model.RecordLog(userId, model.LogTypeTopup, fmt.Sprintf("升级VIP成功,扣除额度: %d", VIP_PRICE)) | |
| model.RecordLog(userId, model.LogTypeSystem, fmt.Sprintf("升级VIP成功,扣除额度: %d", VIP_PRICE/500000)) |
🤖 Prompt for AI Agents
In controller/user.go around line 1043, the code logs a VIP upgrade using
model.RecordLog with LogTypeTopup and logs the internal quota value; change the
log type to a dedicated VIP type (e.g., LogTypeVIP or an appropriate existing
enum) and update the logged amount to show the actual dollar value by dividing
the stored quota by 500000 before formatting the message (so it logs 30 instead
of 15000000); ensure you add the new log type to the model/log enum if it
doesn't exist and update any related references or switch statements
accordingly.
| const currentQuota = userState?.user?.quota || 0; | ||
|
|
||
| if (currentQuota < 30) { | ||
| Toast.error('余额不足,需要30额度才能升级VIP'); | ||
| setUpgrading(false); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Inconsistent quota calculation
The frontend checks if currentQuota < 30 but the backend expects user.Quota < VIP_PRICE where VIP_PRICE = 30 * 500000. This mismatch will cause the frontend check to pass incorrectly.
- if (currentQuota < 30) {
+ if (currentQuota < 30 * 500000) {
Toast.error('余额不足,需要30额度才能升级VIP');
setUpgrading(false);
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const currentQuota = userState?.user?.quota || 0; | |
| if (currentQuota < 30) { | |
| Toast.error('余额不足,需要30额度才能升级VIP'); | |
| setUpgrading(false); | |
| return; | |
| } | |
| const currentQuota = userState?.user?.quota || 0; | |
| if (currentQuota < 30 * 500000) { | |
| Toast.error('余额不足,需要30额度才能升级VIP'); | |
| setUpgrading(false); | |
| return; | |
| } |
🤖 Prompt for AI Agents
In web/src/components/common/VipUpgrade.js around lines 142 to 148, the frontend
compares currentQuota < 30 while the backend expects Quota < VIP_PRICE
(VIP_PRICE = 30 * 500000); update the check to use the same units as the backend
by either importing/using the shared VIP_PRICE constant or multiplying the
frontend quota threshold by 500000 (e.g., check currentQuota < 30 * 500000), and
ensure the error message and any displayed amounts reflect the same unit
conversion so both sides consistently compare the same numeric value.
| const response = await API.post('/api/user/vip_upgrade', { | ||
| username: userState?.user?.username |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid sending username in POST body
The username is already available from the session on the backend (via c.GetInt("id")). Sending it in the request body is redundant and could lead to security issues if manipulated.
- const response = await API.post('/api/user/vip_upgrade', {
- username: userState?.user?.username
- });
+ const response = await API.post('/api/user/vip_upgrade', {});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const response = await API.post('/api/user/vip_upgrade', { | |
| username: userState?.user?.username | |
| const response = await API.post('/api/user/vip_upgrade', {}); |
🤖 Prompt for AI Agents
In web/src/components/common/VipUpgrade.js around lines 153-154, the POST
payload is sending username in the request body which is redundant and
potentially unsafe; remove the username property from the object passed to
API.post so the request body is empty (or only contains necessary non-auth
data), and rely on the backend session/c.GetInt("id") to identify the user;
update any related frontend tests or callers accordingly and ensure the backend
already reads the user id from the session so this change won’t break
authentication.
| ...userState.user, | ||
| is_vip: true, | ||
| vip_expire_time: response.data.data.vip_expire_time, | ||
| quota: currentQuota - 30 |
There was a problem hiding this comment.
Fix incorrect quota deduction in frontend
The frontend deducts only 30 from quota, but the backend deducts 30 * 500000. This creates state inconsistency.
...userState.user,
is_vip: true,
vip_expire_time: response.data.data.vip_expire_time,
- quota: currentQuota - 30
+ quota: currentQuota - (30 * 500000)Apply the same fix to lines 201 and 225.
Also applies to: 201-201, 225-225
🤖 Prompt for AI Agents
In web/src/components/common/VipUpgrade.js around lines 165, 201, and 225, the
frontend is subtracting a flat 30 from quota (quota: currentQuota - 30) while
the backend deducts 30 * 500000; update the subtraction to match the backend by
deducting 30 * 500000 (i.e., quota: currentQuota - 30 * 500000) at each of those
three locations so the frontend state stays consistent with the backend; make
the same change on lines 201 and 225.
| const username = userState?.user?.username; | ||
| const upgradeResponse = await fetch(`http://151.242.26.120:3000/api/internal/vip_upgrade?username=${username}`, { | ||
| method: 'GET' | ||
| }); |
There was a problem hiding this comment.
Security: Hardcoded external URL
The code contains a hardcoded external URL (http://151.242.26.120:3000/api/internal/vip_upgrade) which poses security and maintainability risks. This should be configurable through environment variables.
- const upgradeResponse = await fetch(`http://151.242.26.120:3000/api/internal/vip_upgrade?username=${username}`, {
+ const vipServiceUrl = statusState?.status?.vip_service_url || '';
+ const vipUpgradePath = statusState?.status?.vip_upgrade_path || '/api/internal/vip_upgrade';
+ if (!vipServiceUrl) {
+ throw new Error('VIP service URL not configured');
+ }
+ const upgradeResponse = await fetch(`${vipServiceUrl}${vipUpgradePath}?username=${username}`, {
method: 'GET'
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const username = userState?.user?.username; | |
| const upgradeResponse = await fetch(`http://151.242.26.120:3000/api/internal/vip_upgrade?username=${username}`, { | |
| method: 'GET' | |
| }); | |
| const username = userState?.user?.username; | |
| const vipServiceUrl = statusState?.status?.vip_service_url || ''; | |
| const vipUpgradePath = statusState?.status?.vip_upgrade_path || '/api/internal/vip_upgrade'; | |
| if (!vipServiceUrl) { | |
| throw new Error('VIP service URL not configured'); | |
| } | |
| const upgradeResponse = await fetch(`${vipServiceUrl}${vipUpgradePath}?username=${username}`, { | |
| method: 'GET' | |
| }); |
🤖 Prompt for AI Agents
In web/src/components/common/VipUpgrade.js around lines 181 to 184, the code
uses a hardcoded external URL for the VIP upgrade endpoint; replace the literal
URL with a base URL read from an environment variable (e.g.
process.env.REACT_APP_API_BASE or import.meta.env.VITE_API_BASE depending on the
build system), build the full endpoint via string/template or URL constructor
(e.g. `${API_BASE}/api/internal/vip_upgrade?username=${username}`), and remove
the IP literal; also add a sensible fallback or throw a clear error if the env
var is missing and document/update the .env.example so the variable is set at
build time.
| // 备用方案:直接在数据库中设置VIP状态(仅用于演示) | ||
| const expireTime = Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60); | ||
|
|
||
| // 更新用户状态(本地) | ||
| if (userDispatch) { | ||
| userDispatch({ type: 'login', payload: { | ||
| ...userState.user, | ||
| setting: JSON.stringify({ | ||
| ...((typeof userState.user.setting === 'string' ? JSON.parse(userState.user.setting || '{}') : userState.user.setting) || {}), | ||
| is_vip: true, | ||
| vip_expire_time: expireTime | ||
| }), | ||
| quota: currentQuota - 30 | ||
| }}); | ||
| } | ||
|
|
||
| Toast.success('VIP升级成功!(演示模式)'); | ||
| setUpgradeModalVisible(false); |
There was a problem hiding this comment.
Remove demo/fallback VIP upgrade logic
The fallback logic that performs a "demo mode" VIP upgrade without actually updating the backend is dangerous. It creates inconsistency between frontend and backend state.
- // 备用方案:直接在数据库中设置VIP状态(仅用于演示)
- const expireTime = Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60);
-
- // 更新用户状态(本地)
- if (userDispatch) {
- userDispatch({ type: 'login', payload: {
- ...userState.user,
- setting: JSON.stringify({
- ...((typeof userState.user.setting === 'string' ? JSON.parse(userState.user.setting || '{}') : userState.user.setting) || {}),
- is_vip: true,
- vip_expire_time: expireTime
- }),
- quota: currentQuota - 30
- }});
- }
-
- Toast.success('VIP升级成功!(演示模式)');
- setUpgradeModalVisible(false);
+ // If all API calls fail, show error
+ Toast.error('VIP升级服务暂时不可用,请稍后重试');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 备用方案:直接在数据库中设置VIP状态(仅用于演示) | |
| const expireTime = Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60); | |
| // 更新用户状态(本地) | |
| if (userDispatch) { | |
| userDispatch({ type: 'login', payload: { | |
| ...userState.user, | |
| setting: JSON.stringify({ | |
| ...((typeof userState.user.setting === 'string' ? JSON.parse(userState.user.setting || '{}') : userState.user.setting) || {}), | |
| is_vip: true, | |
| vip_expire_time: expireTime | |
| }), | |
| quota: currentQuota - 30 | |
| }}); | |
| } | |
| Toast.success('VIP升级成功!(演示模式)'); | |
| setUpgradeModalVisible(false); | |
| // If all API calls fail, show error | |
| Toast.error('VIP升级服务暂时不可用,请稍后重试'); |
🤖 Prompt for AI Agents
In web/src/components/common/VipUpgrade.js around lines 213-230 the current
fallback/demo logic mutates userState locally to mark a user VIP and deduct
quota without updating the backend; remove this local-only VIP upgrade flow and
replace it with a proper server-backed flow: delete the demo block that computes
expireTime and dispatches a local 'login' with modified setting/quota; instead
call the existing backend upgrade API (or return an error/toast if none exists),
wait for the server response, then update userState only with the authoritative
data returned by the server; keep userDispatch usage limited to applying
server-returned state and show success/failure toasts based on the API result.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (13)
CHANGELOG_VIP.md (2)
6-7: 修正中文量词用法,提升可读性与专业度。“30额度/微额度”建议补充量词。
-用户可通过钱包页面消费30额度升级为VIP会员 +用户可通过钱包页面消费30单位额度升级为VIP会员 -自动验证用户是否有足够额度(30额度) +自动验证用户是否有足够额度(30单位额度) -扣除30额度(15,000,000微额度) +扣除30单位额度(15,000,000微额度)Also applies to: 23-24, 36-37
28-33: 更具体的“错误处理/输入校验”说明有助于对外对齐期望。建议在安全与可靠性一节列出典型错误码与示例响应,便于前端及第三方集成。
示例追加内容(在文档中扩展,非强制):
- 400: balance_not_enough / already_vip
- 401: unauthorized
- 409: race_conflict(事务重试失败时)
- 500: internal_error
PUSH_TO_GITHUB.md (3)
5-5: 修复 markdown 裸链接与代码块语言标记问题(MD034/MD040)。-1. 访问 https://github.com/Calcium-Ion/new-api +1. 访问 <https://github.com/Calcium-Ion/new-api> @@ -``` +```text feat: 添加VIP用户升级系统--- `26-32`: **推送命令重复与“首次推送”提示可合并简化。** 保留一次带 -u 的推送即可,避免用户困惑。 ```diff -# 推送VIP功能分支到您的fork -git push origin feature/vip-upgrade-system - -# 如果是第一次推送,可能需要设置upstream -git push -u origin feature/vip-upgrade-system +# 推送VIP功能分支到您的fork(首次推送推荐使用 -u) +git push -u origin feature/vip-upgrade-system
66-74: 数量统计需与实际变更保持同步,避免误导。“7个核心文件修改、2个新增文件、+797/-7”等统计可能很快失真。建议引导读者以 GitHub PR 中的 Files changed 为准,或移除此类易失信息。
HOW_TO_SUBMIT_PR.md (3)
45-47: 避免固定提交哈希示例导致误用。固定的 9736e4e 可能不存在或过时,建议说明“替换为实际提交哈希”,并提供从 feature 分支范围内选择提交的命令提示。
- git cherry-pick 9736e4e9 # 替换为实际的commit hash + # 将下方示例中的 <commit> 替换为实际提交哈希(可多次使用) + git cherry-pick <commit> + # 或从功能分支一次性合并(推荐在干净工作区操作): + # git merge --no-ff feature/vip-upgrade-system
85-89: 文件清单中的“PR专用,不提交到主仓库”与“新增文件(3个)”表述冲突。如果该文档不应进入主仓库,请从清单中移除;如需提交,请去掉括注,保持一致。
-- 文档文件(PR专用,不提交到主仓库) +- 文档文件(PR专用) // 或移除此行
1-21: 为清单各项补充“如何验证”的简要说明会更可操作。例如:错误处理可通过哪些接口返回码与单测覆盖来证明。
示例补充:
- “包含完善的错误处理” → 列出关键错误码及对应测试用例文件/名称
- “代码风格与项目一致” → 指定 lint 命令或格式化规则链接
PR_DESCRIPTION.md (3)
11-12: 统一量词表述,避免“30额度”歧义。建议改为“30单位额度”,与其它文档保持一致。
-- **智能余额检查**: 自动检查用户余额是否足够(30额度) +- **智能余额检查**: 自动检查用户余额是否足够(30单位额度) @@ -# 启用VIP升级功能 +# 启用VIP升级功能 ENABLE_VIP_UPGRADE=true @@ -# 可选配置 +# 可选配置 VIP_SERVICE_URL=https://your-domain.com VIP_UPGRADE_PATH=/console/topupAlso applies to: 49-57
41-43: 补充响应示例与错误码能显著提升集成效率。仅描述“返回升级结果”过于笼统,建议给出成功/失败 JSON 示例与典型状态码。
示例:
- 200: { success: true, group: "vip", balance: 123 }
- 400: { error: "balance_not_enough" }
- 409: { error: "already_vip" }
84-91: “输入验证/事务安全”的具体范围建议列点说明。例如:请求体是否为空、是否仅允许自我升级、事务重试策略等。
setup_and_push.sh (2)
6-6: 更健壮的 shell 选项。建议启用 nounset 与 pipefail,提升脚本鲁棒性。
-set -e +set -euo pipefail
35-43: 指引中引用 PR_DESCRIPTION.md 应增加存在性校验或降级提示。否则文件缺失会让用户困惑。
-echo "3. 使用 PR_DESCRIPTION.md 中的内容作为PR描述" +echo "3. 使用 PR_DESCRIPTION.md 中的内容作为PR描述(若不存在,请参考仓库README或CHANGELOG_VIP.md)"
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
CHANGELOG_VIP.md(1 hunks)HOW_TO_SUBMIT_PR.md(1 hunks)PR_DESCRIPTION.md(1 hunks)PUSH_TO_GITHUB.md(1 hunks)common/constants.go(1 hunks)common/init.go(1 hunks)setup_and_push.sh(1 hunks)web/src/components/common/VipUpgrade.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- common/constants.go
- common/init.go
- web/src/components/common/VipUpgrade.js
🧰 Additional context used
🪛 LanguageTool
PR_DESCRIPTION.md
[uncategorized] ~11-~11: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...升级按钮,用户可直接升级 - 智能余额检查: 自动检查用户余额是否足够(30额度) - VIP状态显示: 在钱包页面和个人设置显示VIP身份标识 - *...
(wa5)
CHANGELOG_VIP.md
[uncategorized] ~6-~6: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ... ### 🎯 功能亮点 - 用户VIP升级: 用户可通过钱包页面消费30额度升级为VIP会员 - 智能界面集成: VIP组件无缝集成到现有钱包和设置...
(wa5)
[uncategorized] ~23-~23: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...逻辑 #### 核心特性 - 余额检查: 自动验证用户是否有足够额度(30额度) - 重复保护: 防止已是VIP用户重复升级 - 实时反馈: ...
(wa5)
[uncategorized] ~36-~36: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...现有users.group字段,值为'vip' - 费用机制: 扣除30额度(15,000,000微额度) - 兼容性: 完全向后兼容,不影响现有功...
(wa5)
HOW_TO_SUBMIT_PR.md
[uncategorized] ~132-~132: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:重要"地"商业化
Context: ...持现有功能的完整性和稳定性 --- 这个PR为new-api项目带来了重要的商业化功能,同时保持了高质量的代码标准和完善的文档支持。
(wb4)
🪛 markdownlint-cli2 (0.17.2)
PUSH_TO_GITHUB.md
5-5: Bare URL used
(MD034, no-bare-urls)
43-43: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (3)
CHANGELOG_VIP.md (1)
12-16: Unify the documented API path with the actual router binding.This changelog documents POST /api/user/vip_upgrade. Other references in this PR (router summary) indicate POST /api/user/self/vip_upgrade. Please confirm the actual route and make all docs consistent to avoid client breakage.
Apply one of the diffs below after you confirm the source of truth:
-- `POST /api/user/vip_upgrade` - VIP升级API端点 +- `POST /api/user/self/vip_upgrade` - VIP升级API端点or
-- `POST /api/user/self/vip_upgrade` - VIP升级API端点 +- `POST /api/user/vip_upgrade` - VIP升级API端点HOW_TO_SUBMIT_PR.md (1)
61-68: 与其他文档统一 API 路径,消除接口歧义。此文档与 CHANGELOG/描述文档存在“/api/user/vip_upgrade” vs “/api/user/self/vip_upgrade”的差异。请确认后统一。
- feat: 添加VIP用户升级系统 + feat: 添加VIP用户升级系统 @@ - 使用 `PR_DESCRIPTION.md` 中的内容作为PR描述。 + 使用 `PR_DESCRIPTION.md` 中的内容作为PR描述(确保接口路径与路由一致)。PR_DESCRIPTION.md (1)
39-43: 接口路径需与路由实现保持一致。此处声明为 POST /api/user/vip_upgrade。另有说明显示路由为 /api/user/self/vip_upgrade。请确认最终路径并统一所有文档、前端调用与后端实现。
-- **新增端点**: `POST /api/user/vip_upgrade` +- **新增端点**: `POST /api/user/self/vip_upgrade`或反之,视实际实现而定。
| ```bash | ||
| # 添加您的fork作为origin(替换YOUR_USERNAME为您的GitHub用户名) | ||
| git remote add origin https://github.com/YOUR_USERNAME/new-api.git | ||
|
|
||
| # 验证远程仓库配置 | ||
| git remote -v | ||
| # 应该看到: | ||
| # origin https://github.com/YOUR_USERNAME/new-api.git (fetch) | ||
| # origin https://github.com/YOUR_USERNAME/new-api.git (push) | ||
| # upstream https://github.com/Calcium-Ion/new-api.git (fetch) | ||
| # upstream https://github.com/Calcium-Ion/new-api.git (push) | ||
| ``` |
There was a problem hiding this comment.
🛠️ Refactor suggestion
远程示例包含 upstream,但未给出添加 upstream 的命令,易造成困惑。
当前步骤只添加了 origin,却在示例中展示了 upstream。请补充添加 upstream 的命令或移除示例中的 upstream。
```bash
# 添加您的fork作为origin(替换YOUR_USERNAME为您的GitHub用户名)
git remote add origin https://github.com/YOUR_USERNAME/new-api.git
+
+# 可选:添加上游仓库(便于同步更新)
+git remote add upstream https://github.com/Calcium-Ion/new-api.git
@@
-# upstream https://github.com/Calcium-Ion/new-api.git (fetch)
-# upstream https://github.com/Calcium-Ion/new-api.git (push)
+# upstream https://github.com/Calcium-Ion/new-api.git (fetch)
+# upstream https://github.com/Calcium-Ion/new-api.git (push)
<details>
<summary>🤖 Prompt for AI Agents</summary>
In PUSH_TO_GITHUB.md around lines 11 to 22, the remote example shows an upstream
remote but the steps only add origin; add an explicit instruction to optionally
add the upstream remote (git remote add upstream
https://github.com/Calcium-Ion/new-api.git) and mention it is optional for
syncing, and update the example output so it matches (showing both origin and
upstream), or alternatively remove the upstream lines from the example if you
prefer not to document adding upstream.
</details>
<!-- fingerprinting:phantom:triton:chinchilla -->
<!-- This is an auto-generated comment by CodeRabbit -->
| # 配置origin为您的fork | ||
| echo "📤 添加您的fork作为origin..." | ||
| git remote add origin "https://github.com/$GITHUB_USERNAME/new-api.git" | ||
|
|
||
| # 验证配置 | ||
| echo "✅ 远程仓库配置:" | ||
| git remote -v | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
避免在已存在 origin 时直接 add 失败。
脚本在已有 origin 的仓库中会因 set -e 直接退出。建议检测后选择 set-url 或提示用户确认。
-echo "📤 添加您的fork作为origin..."
-git remote add origin "https://github.com/$GITHUB_USERNAME/new-api.git"
+echo "📤 配置您的fork为 origin..."
+if git remote get-url origin >/dev/null 2>&1; then
+ echo "ℹ️ 检测到已存在 origin。将更新其 URL。"
+ git remote set-url origin "https://github.com/$GITHUB_USERNAME/new-api.git"
+else
+ git remote add origin "https://github.com/$GITHUB_USERNAME/new-api.git"
+fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # 配置origin为您的fork | |
| echo "📤 添加您的fork作为origin..." | |
| git remote add origin "https://github.com/$GITHUB_USERNAME/new-api.git" | |
| # 验证配置 | |
| echo "✅ 远程仓库配置:" | |
| git remote -v | |
| # 配置origin为您的fork | |
| echo "📤 配置您的fork为 origin..." | |
| if git remote get-url origin >/dev/null 2>&1; then | |
| echo "ℹ️ 检测到已存在 origin。将更新其 URL。" | |
| git remote set-url origin "https://github.com/$GITHUB_USERNAME/new-api.git" | |
| else | |
| git remote add origin "https://github.com/$GITHUB_USERNAME/new-api.git" | |
| fi | |
| # 验证配置 | |
| echo "✅ 远程仓库配置:" | |
| git remote -v |
🤖 Prompt for AI Agents
In setup_and_push.sh around lines 19 to 26, the script unconditionally runs git
remote add origin which fails (and exits the script under set -e) if an origin
already exists; change this to first detect whether origin exists (e.g., git
remote get-url origin or git remote | grep -q origin), and if it exists either
run git remote set-url origin "https://github.com/$GITHUB_USERNAME/new-api.git"
or prompt the user to confirm replacing it; ensure the script branches
accordingly so it does not error out under set -e and provides a clear message
about which action was taken.
| # 推送代码 | ||
| echo "" | ||
| echo "🚀 推送VIP功能分支到您的fork..." | ||
| git push -u origin feature/vip-upgrade-system | ||
|
|
There was a problem hiding this comment.
确保分支存在或提供友好提示。
若本地未创建 feature/vip-upgrade-system 分支,push 将失败。建议在推送前校验并给出创建指引或推送当前 HEAD。
-echo "🚀 推送VIP功能分支到您的fork..."
-git push -u origin feature/vip-upgrade-system
+echo "🚀 推送VIP功能分支到您的fork..."
+if git rev-parse --verify feature/vip-upgrade-system >/dev/null 2>&1; then
+ git push -u origin feature/vip-upgrade-system
+else
+ echo "❌ 未找到本地分支 feature/vip-upgrade-system。"
+ echo "请先创建并提交:"
+ echo " git checkout -b feature/vip-upgrade-system"
+ echo " git commit --allow-empty -m 'init vip branch' # 如需"
+ echo "然后重新运行本脚本。"
+ exit 1
+fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # 推送代码 | |
| echo "" | |
| echo "🚀 推送VIP功能分支到您的fork..." | |
| git push -u origin feature/vip-upgrade-system | |
| # 推送代码 | |
| echo "" | |
| echo "🚀 推送VIP功能分支到您的fork..." | |
| if git rev-parse --verify feature/vip-upgrade-system >/dev/null 2>&1; then | |
| git push -u origin feature/vip-upgrade-system | |
| else | |
| echo "❌ 未找到本地分支 feature/vip-upgrade-system。" | |
| echo "请先创建并提交:" | |
| echo " git checkout -b feature/vip-upgrade-system" | |
| echo " git commit --allow-empty -m 'init vip branch' # 如需" | |
| echo "然后重新运行本脚本。" | |
| exit 1 | |
| fi |
🤖 Prompt for AI Agents
In setup_and_push.sh around lines 27 to 31, the script unconditionally tries to
push branch feature/vip-upgrade-system which will fail if that branch doesn't
exist locally; update the script to first check whether the local branch exists
and if it does push it, otherwise print a clear, user-friendly message
explaining the branch is missing and instruct the user how to either create the
branch locally or push the current HEAD as that branch (and optionally prompt to
continue or abort), so the script only attempts git push when the branch is
present or after explicit user confirmation.
|
建议请读个书,然后审核一下 vibe 的产物再把代码往上传 |
|
就是玩,希望大佬更新一下这个功能吧
发自我的iPhone
…------------------ 原始邮件 ------------------
发件人: KOMATA ***@***.***>
发送时间: 2025年8月12日 10:31
收件人: QuantumNous/new-api ***@***.***>
抄送: TianTian ***@***.***>, Author ***@***.***>
主题: Re: [QuantumNous/new-api] feat: 添加VIP用户升级系统升级系统 (PR #1572)
HynoR left a comment (QuantumNous/new-api#1572)
建议请读个书,然后审核一下 vibe 的产物再把代码往上传
full vide coding without review 是个坏习惯
408c1133d56fb576684ff21c3fdf6b32.jpeg (view on web)
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
…em-v2 feat(payment): add complete payment system with multi-provider support
🎯 功能: 添加VIP用户升级系统
📋 功能概述
为New API添加了完整的VIP用户升级系统,允许用户通过消费额度升级为VIP会员,享受VIP专属权益。
✨ 新增功能
🎮 用户功能
🔧 管理功能
ENABLE_VIP_UPGRADE环境变量控制功能启用group字段,无需数据迁移🛡️ 技术特性
🎨 用户界面
钱包页面 (
/console/topup)个人设置页面
🔧 技术实现
后端 API
POST /api/user/vip_upgrade前端组件
VipUpgrade.js- 独立的VIP升级组件配置选项
📁 文件变更
后端核心文件
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系统奠定了基础,后续可扩展:
🎯 影响范围
这个PR引入了一个完整的、生产就绪的VIP升级系统,为New API的商业化和用户分层管理提供了强大的基础功能。
Summary by CodeRabbit