fix:修复多个单元测试发现的bug

This commit is contained in:
qian committed 2026-05-28 12:42:00 +08:00
1 parent c76991e874
commit 941ba902f6
15 files changed
+124 -46

No files matched your search

+1 -1
View File
@@ -49,7 +49,7 @@ type ChannelCreateReq struct {
BaseURL string `json:"base_url" dc:"API 基础地址(留空使用供应商默认地址)"`
ApiKey string `json:"api_key" v:"required#请输入 API Key" dc:"API Key"`
Priority int `json:"priority" d:"0" dc:"优先级"`
Weight int `json:"weight" d:"100" v:"between:1,100" dc:"权重"`
Weight int `json:"weight" d:"100" v:"between:0,100" dc:"权重"`
TestModel string `json:"test_model" dc:"测试模型名"`
Remark string `json:"remark" dc:"备注"`
IsVIP bool `json:"is_vip" d:"false" dc:"是否VIP专属渠道"`
+2 -2
View File
@@ -6,8 +6,8 @@ import "github.com/gogf/gf/v2/frame/g"
type UsageLogCleanupCreateReq struct {
g.Meta `path:"/usage-logs/cleanup" method:"post" mime:"json" tags:"管理后台-日志清理" summary:"创建清理任务"`
StartTime string `json:"start_time" v:"required|datetime" dc:"起始时间(RFC3339)"`
EndTime string `json:"end_time" v:"required|datetime" dc:"截止时间(RFC3339)"`
StartTime string `json:"start_time" v:"required" dc:"起始时间(RFC3339)"`
EndTime string `json:"end_time" v:"required" dc:"截止时间(RFC3339)"`
TenantID *int64 `json:"tenant_id,omitempty" dc:"指定租户(可选)"`
ModelName string `json:"model_name,omitempty" dc:"指定模型(可选)"`
Status string `json:"status,omitempty" dc:"指定状态(可选)"`
+4 -1
View File
@@ -90,7 +90,10 @@ type TenantProjectApiKeyCreateReq struct {
}
type TenantProjectApiKeyCreateRes struct {
Data map[string]any `json:"data"`
ID int64 `json:"id"`
Name string `json:"name"`
Key string `json:"key"`
KeyPrefix string `json:"key_prefix"`
}
type TenantProjectApiKeyDeleteReq struct {
+25 -23
View File
@@ -388,43 +388,45 @@ func (s *sAdmin) AdjustBalance(ctx context.Context, req *v1.AdminWalletAdjustReq
amount := req.Amount
description := req.Description
type walletRow struct {
// 原子更新余额,避免并发竞态
db := g.DB()
updateQuery := "UPDATE bil_wallets SET balance = balance + ?, updated_at = ? WHERE tenant_id = ?"
args := []interface{}{amount, gtime.Now(), tenantID}
if amount < 0 {
updateQuery += " AND balance >= ?"
args = append(args, -amount)
}
result, err := db.Exec(ctx, updateQuery, args...)
if err != nil {
return nil, err
}
affected, _ := result.RowsAffected()
if affected == 0 {
return nil, gerror.New("钱包不存在或余额不足")
}
// 查询更新后的余额,用于记录流水
var wallet struct {
ID int64 `json:"id"`
Balance float64 `json:"balance"`
FrozenBalance float64 `json:"frozen_balance"`
}
var w *walletRow
err := dao.BilWallets.Ctx(ctx).
err = dao.BilWallets.Ctx(ctx).
Where("tenant_id", tenantID).
Fields("id, balance, frozen_balance").
Scan(&w)
if err != nil || w == nil {
return nil, gerror.Newf("wallet not found for tenant %d", tenantID)
}
// 检查扣减后余额是否为负
if amount < 0 && w.Balance+amount < 0 {
return nil, gerror.Newf("余额不足,当前余额 %.2f,无法扣减 %.2f", w.Balance, -amount)
}
_, err = dao.BilWallets.Ctx(ctx).
Where("id", w.ID).
Data(do.BilWallets{
Balance: w.Balance + amount,
}).Update()
Scan(&wallet)
if err != nil {
return nil, err
}
// 记录流水(含变动后余额)
balanceAfter := w.Balance + amount
// 记录流水
dao.BilTransactions.Ctx(ctx).Insert(do.BilTransactions{
TenantId: tenantID,
WalletId: w.ID,
WalletId: wallet.ID,
Type: "adjust",
Amount: amount,
BalanceAfter: balanceAfter,
FrozenAfter: w.FrozenBalance,
BalanceAfter: wallet.Balance,
FrozenAfter: wallet.FrozenBalance,
Description: description,
})
+8 -1
View File
@@ -91,8 +91,15 @@ func (s *sAdmin) ErrorLogDetail(ctx context.Context, req *v1.ErrorLogDetailReq)
// ErrorLogResolve marks an error log as resolved.
func (s *sAdmin) ErrorLogResolve(ctx context.Context, req *v1.ErrorLogResolveReq) (*v1.ErrorLogResolveRes, error) {
count, err := dao.SysErrorLogs.Ctx(ctx).Where("id", req.Id).Count()
if err != nil {
return nil, err
}
if count == 0 {
return nil, common.NewNotFoundError("错误日志")
}
userID := common.GetCtxUserID(ctx)
_, err := dao.SysErrorLogs.Ctx(ctx).
_, err = dao.SysErrorLogs.Ctx(ctx).
Where("id", req.Id).
Data(g.Map{
"resolved": true,
+8 -1
View File
@@ -376,7 +376,14 @@ func (s *sAdmin) ListAnnouncements(ctx context.Context, req *v1.AnnouncementList
// PublishAnnouncement 发布公告
func (s *sAdmin) PublishAnnouncement(ctx context.Context, req *v1.AnnouncementPublishReq) (*v1.AnnouncementPublishRes, error) {
_, err := dao.NtfAnnouncements.Ctx(ctx).
count, err := dao.NtfAnnouncements.Ctx(ctx).Where("id", req.Id).Count()
if err != nil {
return nil, err
}
if count == 0 {
return nil, common.NewNotFoundError("公告")
}
_, err = dao.NtfAnnouncements.Ctx(ctx).
Where("id", req.Id).
Data(do.NtfAnnouncements{
Status: "published",
+5 -6
View File
@@ -45,17 +45,16 @@ func (s *sAdmin) ListOrders(ctx context.Context, req *v1.OrderListReq) (*v1.Orde
// GetOrder 获取订单详情
func (s *sAdmin) GetOrder(ctx context.Context, req *v1.OrderDetailReq) (*v1.OrderDetailRes, error) {
var order map[string]any
err := dao.OrdOrders.Ctx(ctx).
record, err := dao.OrdOrders.Ctx(ctx).
Where("id", req.Id).
Scan(&order)
if err = common.IgnoreScanNoRows(err); err != nil {
One()
if err != nil {
return nil, err
}
if order == nil {
if record == nil {
return nil, common.NewNotFoundError("订单")
}
return &v1.OrderDetailRes{Data: order}, nil
return &v1.OrderDetailRes{Data: record.Map()}, nil
}
// RefundOrder 发起退款
+8 -1
View File
@@ -45,7 +45,14 @@ func (s *sAdmin) CreatePromoCode(ctx context.Context, req *v1.PromoCodeCreateReq
// UpdatePromoCode 更新优惠码
func (s *sAdmin) UpdatePromoCode(ctx context.Context, req *v1.PromoCodeUpdateReq) (*v1.PromoCodeUpdateRes, error) {
_, err := dao.OrdPromoCodes.Ctx(ctx).
count, err := dao.OrdPromoCodes.Ctx(ctx).Where("id", req.Id).Count()
if err != nil {
return nil, err
}
if count == 0 {
return nil, common.NewNotFoundError("优惠码")
}
_, err = dao.OrdPromoCodes.Ctx(ctx).
Where("id", req.Id).
Data(req.Update).
Update()
+8 -1
View File
@@ -85,7 +85,14 @@ func (s *sAdmin) BatchCreateRedemptions(ctx context.Context, req *v1.RedemptionC
// DisableRedemption 禁用兑换码
func (s *sAdmin) DisableRedemption(ctx context.Context, req *v1.RedemptionDisableReq) (*v1.RedemptionDisableRes, error) {
_, err := dao.OrdRedemptions.Ctx(ctx).
count, err := dao.OrdRedemptions.Ctx(ctx).Where("id", req.Id).Count()
if err != nil {
return nil, err
}
if count == 0 {
return nil, common.NewNotFoundError("兑换码")
}
_, err = dao.OrdRedemptions.Ctx(ctx).
Where("id", req.Id).
Where("status", "active").
Data(do.OrdRedemptions{
+1 -1
View File
@@ -160,7 +160,7 @@ func (s *sAdmin) CreateTenant(ctx context.Context, req *v1.TenantCreateReq) (*v1
TenantId: tenantID,
Balance: 0,
FrozenBalance: 0,
Currency: "CNY",
Currency: "USD",
}).Insert()
if err != nil {
return gerror.Wrapf(err, "create wallet")
+12 -1
View File
@@ -10,6 +10,7 @@ import (
v1 "github.com/qianfree/team-api/api/admin/v1"
"github.com/qianfree/team-api/internal/dao"
"github.com/qianfree/team-api/internal/logic/common"
do "github.com/qianfree/team-api/internal/model/do"
)
@@ -22,6 +23,9 @@ func (s *sAdmin) UsageLogCleanupCreate(ctx context.Context, req *v1.UsageLogClea
if err != nil {
return nil, err
}
if startTime.After(endTime) {
return nil, common.NewBadRequestError("起始时间不能晚于截止时间")
}
batchSize := req.BatchSize
if batchSize <= 0 {
@@ -125,7 +129,14 @@ func (s *sAdmin) UsageLogCleanupList(ctx context.Context, req *v1.UsageLogCleanu
}
func (s *sAdmin) UsageLogCleanupCancel(ctx context.Context, req *v1.UsageLogCleanupCancelReq) (*v1.UsageLogCleanupCancelRes, error) {
_, err := dao.TskTasks.Ctx(ctx).
count, err := dao.TskTasks.Ctx(ctx).Where("id", req.ID).Where("handler", "usage_log_cleanup").Count()
if err != nil {
return nil, err
}
if count == 0 {
return nil, common.NewNotFoundError("清理任务")
}
_, err = dao.TskTasks.Ctx(ctx).
Where("id", req.ID).
Where("handler", "usage_log_cleanup").
Where("status", "pending").
+1 -1
View File
@@ -129,7 +129,7 @@ func (s *sTenant) Register(ctx context.Context, req *v1.TenantRegisterReq) (*v1.
TenantId: tenantID,
Balance: 0,
FrozenBalance: 0,
Currency: "CNY",
Currency: "USD",
}).Insert()
if err != nil {
return gerror.Wrapf(err, "create wallet")
+20
View File
@@ -84,6 +84,10 @@ func (s *sTenant) ListMembers(ctx context.Context, req *v1.TenantMemberListReq)
// InviteMember generates an invitation link.
func (s *sTenant) InviteMember(ctx context.Context, req *v1.TenantMemberInviteReq) (*v1.TenantMemberInviteRes, error) {
role := middleware.GetUserRole(ctx)
if role != "owner" && role != "admin" {
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
}
tenantID := middleware.GetTenantID(ctx)
creatorID := middleware.GetUserID(ctx)
@@ -359,6 +363,10 @@ func (s *sTenant) JoinByInvite(ctx context.Context, req *v1.TenantMemberJoinReq)
// CreateMember directly creates a member account within the tenant.
func (s *sTenant) CreateMember(ctx context.Context, req *v1.TenantMemberCreateReq) (*v1.TenantMemberCreateRes, error) {
role := middleware.GetUserRole(ctx)
if role != "owner" && role != "admin" {
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
}
tenantID := middleware.GetTenantID(ctx)
// Validate role
@@ -465,6 +473,10 @@ func (s *sTenant) CreateMember(ctx context.Context, req *v1.TenantMemberCreateRe
// RemoveMember removes a member from the tenant.
// Revokes all API keys, anonymizes personal data, releases member model scopes.
func (s *sTenant) RemoveMember(ctx context.Context, req *v1.TenantMemberRemoveReq) (*v1.TenantMemberRemoveRes, error) {
role := middleware.GetUserRole(ctx)
if role != "owner" && role != "admin" {
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
}
tenantID := middleware.GetTenantID(ctx)
currentUserID := middleware.GetUserID(ctx)
memberID := req.Id
@@ -597,6 +609,10 @@ func (s *sTenant) EnableMember(ctx context.Context, tenantID, userID int64) erro
// UpdateMemberRole updates a member's role.
func (s *sTenant) UpdateMemberRole(ctx context.Context, req *v1.TenantMemberUpdateRoleReq) (*v1.TenantMemberUpdateRoleRes, error) {
role := middleware.GetUserRole(ctx)
if role != "owner" && role != "admin" {
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
}
tenantID := middleware.GetTenantID(ctx)
currentUserID := middleware.GetUserID(ctx)
memberID := req.Id
@@ -844,6 +860,10 @@ func (s *sTenant) ListMemberApiKeys(ctx context.Context, req *v1.TenantMemberApi
// ExportMembers exports the tenant member list as CSV or Excel.
func (s *sTenant) ExportMembers(ctx context.Context, req *v1.TenantMemberExportReq) (*v1.TenantMemberExportRes, error) {
role := middleware.GetUserRole(ctx)
if role != "owner" && role != "admin" {
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
}
tenantID := middleware.GetTenantID(ctx)
columns := []export.Column{
+5
View File
@@ -10,6 +10,7 @@ import (
v1 "github.com/qianfree/team-api/api/tenant/v1"
"github.com/qianfree/team-api/internal/dao"
"github.com/qianfree/team-api/internal/logic/common"
"github.com/qianfree/team-api/internal/middleware"
)
@@ -50,6 +51,10 @@ func (s *sTenant) MemberQuota(ctx context.Context, req *v1.TenantMemberQuotaReq)
}
func (s *sTenant) MemberQuotaSet(ctx context.Context, req *v1.TenantMemberQuotaSetReq) (*v1.TenantMemberQuotaSetRes, error) {
role := middleware.GetUserRole(ctx)
if role != "owner" && role != "admin" {
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
}
tenantID := middleware.GetTenantID(ctx)
if req.QuotaType == "periodic" && req.Period == "" {
+16 -6
View File
@@ -79,6 +79,10 @@ func (s *sTenant) ProjectList(ctx context.Context, req *v1.TenantProjectListReq)
// ProjectCreate creates a new project for a tenant.
func (s *sTenant) ProjectCreate(ctx context.Context, req *v1.TenantProjectCreateReq) (*v1.TenantProjectCreateRes, error) {
role := middleware.GetUserRole(ctx)
if role != "owner" && role != "admin" {
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
}
tenantID := middleware.GetTenantID(ctx)
userID := middleware.GetUserID(ctx)
@@ -113,6 +117,10 @@ func (s *sTenant) ProjectCreate(ctx context.Context, req *v1.TenantProjectCreate
// ProjectUpdate updates a project.
func (s *sTenant) ProjectUpdate(ctx context.Context, req *v1.TenantProjectUpdateReq) (*v1.TenantProjectUpdateRes, error) {
role := middleware.GetUserRole(ctx)
if role != "owner" && role != "admin" {
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
}
tenantID := middleware.GetTenantID(ctx)
var project *struct {
@@ -162,6 +170,10 @@ func (s *sTenant) ProjectUpdate(ctx context.Context, req *v1.TenantProjectUpdate
// ProjectArchive archives a project and revokes all its keys.
func (s *sTenant) ProjectArchive(ctx context.Context, req *v1.TenantProjectArchiveReq) (*v1.TenantProjectArchiveRes, error) {
role := middleware.GetUserRole(ctx)
if role != "owner" && role != "admin" {
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
}
tenantID := middleware.GetTenantID(ctx)
var project *struct {
@@ -447,12 +459,10 @@ func (s *sTenant) ProjectApiKeyCreate(ctx context.Context, req *v1.TenantProject
}
return &v1.TenantProjectApiKeyCreateRes{
Data: map[string]any{
"id": id,
"name": req.Name,
"key": rawKey,
"key_prefix": prefix,
},
ID: id,
Name: req.Name,
Key: rawKey,
KeyPrefix: prefix,
}, nil
}