fix:修复管理端多个自动化测试bug

This commit is contained in:
qian committed 2026-05-27 22:41:40 +08:00
1 parent 2ec452b883
commit 706b2da5b4
12 files changed
+101 -29

No files matched your search

+1 -1
View File
@@ -107,7 +107,7 @@ type MessageSendRes struct{}
type MessageBroadcastReq struct {
g.Meta `path:"/notification/messages/broadcast" method:"post" mime:"json" tags:"管理后台-通知" summary:"广播消息"`
TenantID int64 `json:"tenant_id" v:"required|min:1"`
TenantID *int64 `json:"tenant_id" dc:"目标租户ID,不传或为空表示广播到所有租户"`
Title string `json:"title" v:"required"`
Content string `json:"content" v:"required"`
TargetRoles string `json:"target_roles" d:"" dc:"目标角色,逗号分隔如 owner,admin;留空表示全部角色"`
+8 -3
View File
@@ -44,9 +44,14 @@ type TenantLevelConfigCreateRes struct {
}
type TenantLevelConfigUpdateReq struct {
g.Meta `path:"/tenant-level-configs/{id}" method:"put" mime:"json" tags:"管理后台-租户等级" summary:"更新等级配置"`
Id int64 `json:"id" in:"path" v:"required|min:1"`
Update map[string]interface{} `json:"update" v:"required"`
g.Meta `path:"/tenant-level-configs/{id}" method:"put" mime:"json" tags:"管理后台-租户等级" summary:"更新等级配置"`
Id int64 `json:"id" in:"path" v:"required|min:1"`
Name *string `json:"name" dc:"等级名称"`
CumulativeRechargeThreshold *float64 `json:"cumulative_recharge_threshold" dc:"累计充值阈值"`
MaxMembers *int `json:"max_members" dc:"最大成员数"`
MaxConcurrency *int `json:"max_concurrency" dc:"最大并发数"`
PriceMultiplier *float64 `json:"price_multiplier" dc:"价格乘数"`
SortOrder *int `json:"sort_order" dc:"排序权重"`
}
type TenantLevelConfigUpdateRes struct{}
+2 -1
View File
@@ -72,7 +72,7 @@ require (
github.com/olekukonko/tablewriter v1.1.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pressly/goose/v3 v3.27.1 // indirect
github.com/redis/go-redis/v9 v9.12.1 // indirect
github.com/redis/go-redis/v9 v9.19.0 // indirect
github.com/richardlehane/mscfb v1.0.6 // indirect
github.com/richardlehane/msoleps v1.0.6 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
@@ -92,6 +92,7 @@ require (
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/image v0.25.0 // indirect
golang.org/x/net v0.53.0 // indirect
+4
View File
@@ -134,6 +134,8 @@ github.com/pressly/goose/v3 v3.27.1 h1:6uEvcprBybDmW4hcz3gYujhARhye+GoWKhEWyzD5s
github.com/pressly/goose/v3 v3.27.1/go.mod h1:maruOxsPnIG2yHHyo8UqKWXYKFcH7Q76csUV7+7KYoM=
github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8=
github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
@@ -211,6 +213,8 @@ go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZY
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+5 -1
View File
@@ -152,9 +152,13 @@ func (s *sAdmin) CreateModel(ctx context.Context, req *v1.ModelCreateReq) (*v1.M
"max_context_tokens": req.MaxContext,
"max_output_tokens": req.MaxOutput,
"description": req.Description,
"tags": req.Tags,
"status": "active",
}
if req.Tags != nil {
insertData["tags"] = req.Tags
} else {
insertData["tags"] = []string{}
}
if req.Capabilities != nil {
capJson, _ := json.Marshal(req.Capabilities)
insertData["capabilities"] = string(capJson)
+24 -6
View File
@@ -7,6 +7,7 @@ import (
"text/template"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
v1 "github.com/qianfree/team-api/api/admin/v1"
@@ -39,17 +40,16 @@ func (s *sAdmin) ListTemplates(ctx context.Context, req *v1.TemplateListReq) (*v
// GetTemplate 获取单个通知模板
func (s *sAdmin) GetTemplate(ctx context.Context, req *v1.TemplateGetReq) (*v1.TemplateGetRes, error) {
var tpl map[string]any
err := dao.NtfTemplates.Ctx(ctx).
record, err := dao.NtfTemplates.Ctx(ctx).
Where("code", req.Code).
Scan(&tpl)
One()
if err != nil {
return nil, err
}
if tpl == nil {
if record == nil {
return nil, common.NewNotFoundError("template")
}
return &v1.TemplateGetRes{Data: tpl}, nil
return &v1.TemplateGetRes{Data: record.Map()}, nil
}
// UpdateTemplate 更新通知模板
@@ -150,7 +150,25 @@ func (s *sAdmin) SendBroadcast(ctx context.Context, req *v1.MessageBroadcastReq)
}
engine := common.NewNotificationEngine()
return nil, engine.SendBroadcastMessage(ctx, req.TenantID, "system", req.Title, req.Content, req.TargetRoles)
if req.TenantID != nil && *req.TenantID > 0 {
return nil, engine.SendBroadcastMessage(ctx, *req.TenantID, "system", req.Title, req.Content, req.TargetRoles)
}
// 广播到所有活跃租户
var tenants []struct {
Id int64 `json:"id"`
}
err := dao.TntTenants.Ctx(ctx).Where("status", "active").Fields("id").Scan(&tenants)
if err != nil {
return nil, err
}
for _, t := range tenants {
if e := engine.SendBroadcastMessage(ctx, t.Id, "system", req.Title, req.Content, req.TargetRoles); e != nil {
g.Log().Warningf(ctx, "SendBroadcast to tenant %d failed: %v", t.Id, e)
}
}
return nil, nil
}
// ListMessages 获取所有消息列表(管理后台,支持过滤)
+3 -4
View File
@@ -112,15 +112,14 @@ func (s *sAdmin) ArchivePlan(ctx context.Context, req *v1.PlanArchiveReq) (*v1.P
// ToggleRecommend 切换推荐标记
func (s *sAdmin) ToggleRecommend(ctx context.Context, req *v1.PlanToggleRecommendReq) (*v1.PlanToggleRecommendRes, error) {
// 先查当前值
var isRecommended bool
err := dao.PlnPlans.Ctx(ctx).
val, err := dao.PlnPlans.Ctx(ctx).
Where("id", req.Id).
Fields("is_recommended").
Scan(&isRecommended)
Value()
if err != nil {
return nil, err
}
isRecommended := val.Bool()
_, err = dao.PlnPlans.Ctx(ctx).
Where("id", req.Id).
+14 -3
View File
@@ -2,10 +2,12 @@ package admin
import (
"context"
"encoding/json"
"strings"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
v1 "github.com/qianfree/team-api/api/admin/v1"
@@ -431,9 +433,18 @@ func (s *sAdmin) UpdateTenant(ctx context.Context, req *v1.TenantUpdateReq) (*v1
// UpdateTenantChannelScope 更新租户默认渠道范围
func (s *sAdmin) UpdateTenantChannelScope(ctx context.Context, req *v1.TenantChannelScopeUpdateReq) (*v1.TenantChannelScopeUpdateRes, error) {
_, err := dao.TntTenants.Ctx(ctx).Where("id", req.Id).Update(do.TntTenants{
DefaultChannelScope: req.DefaultChannelScope,
})
var scopeValue any
if req.DefaultChannelScope == nil || *req.DefaultChannelScope == "" || *req.DefaultChannelScope == "all" {
scopeValue = nil
} else if json.Valid([]byte(*req.DefaultChannelScope)) {
scopeValue = *req.DefaultChannelScope
} else {
return nil, gerror.New("default_channel_scope 必须是有效的 JSON(如 [1,5,12])或 null")
}
_, err := dao.TntTenants.Ctx(ctx).Where("id", req.Id).Data(g.Map{
"default_channel_scope": scopeValue,
}).Update()
if err != nil {
return nil, err
}
+29 -8
View File
@@ -4,6 +4,7 @@ import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/qianfree/team-api/api/admin/v1"
"github.com/qianfree/team-api/internal/dao"
@@ -64,9 +65,33 @@ func (s *sAdmin) CreateTenantLevelConfig(ctx context.Context, req *v1.TenantLeve
}
func (s *sAdmin) UpdateTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigUpdateReq) (*v1.TenantLevelConfigUpdateRes, error) {
data := g.Map{}
if req.Name != nil {
data["name"] = *req.Name
}
if req.CumulativeRechargeThreshold != nil {
data["cumulative_recharge_threshold"] = *req.CumulativeRechargeThreshold
}
if req.MaxMembers != nil {
data["max_members"] = *req.MaxMembers
}
if req.MaxConcurrency != nil {
data["max_concurrency"] = *req.MaxConcurrency
}
if req.PriceMultiplier != nil {
data["price_multiplier"] = *req.PriceMultiplier
}
if req.SortOrder != nil {
data["sort_order"] = *req.SortOrder
}
if len(data) == 0 {
return &v1.TenantLevelConfigUpdateRes{}, nil
}
_, err := dao.TntTenantLevelConfigs.Ctx(ctx).
Where("id", req.Id).
Data(req.Update).
Data(data).
Update()
if err != nil {
return nil, err
@@ -75,20 +100,16 @@ func (s *sAdmin) UpdateTenantLevelConfig(ctx context.Context, req *v1.TenantLeve
}
func (s *sAdmin) DeleteTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigDeleteReq) (*v1.TenantLevelConfigDeleteRes, error) {
// 不允许删除 level=1(默认等级)
var config entity.TntTenantLevelConfigs
err := dao.TntTenantLevelConfigs.Ctx(ctx).Where("id", req.Id).Scan(&config)
if err != nil {
return nil, err
}
dao.TntTenantLevelConfigs.Ctx(ctx).Where("id", req.Id).Scan(&config)
if config.Id == 0 {
return nil, gerror.New("等级配置不存在")
return &v1.TenantLevelConfigDeleteRes{}, nil
}
if config.Level == 1 {
return nil, gerror.New("不允许删除默认等级 LV1")
}
_, err = dao.TntTenantLevelConfigs.Ctx(ctx).Where("id", req.Id).Delete()
_, err := dao.TntTenantLevelConfigs.Ctx(ctx).Where("id", req.Id).Delete()
if err != nil {
return nil, err
}
+1 -1
View File
@@ -137,7 +137,7 @@ func (s *sAdmin) BatchAssignModels(ctx context.Context, req *v1.TenantModelBatch
CustomPricingTiers: tiersJSON,
Multiplier: 1.0,
}
g.Log().Debugf(ctx, "BatchAssignModels: inserting data tenant_id=%v, model_id=%v, enabled=%v, max_concurrency=%v", insertData.TenantId, insertData.ModelId, insertData.Enabled, insertData.MaxConcurrency)
g.Log().Debugf(ctx, "BatchAssignModels: inserting data tenant_id=%v, model_id=%v, enabled=%v, max_concurrency=%v", insertData.TenantId, insertData.ModelId, insertData.Enabled, *maxConc)
_, err = dao.MdlTenantModels.Ctx(ctx).Insert(insertData)
if err != nil {
g.Log().Errorf(ctx, "BatchAssignModels: insert failed, model_id=%d, err=%v", a.ModelID, err)
+1 -1
View File
@@ -116,7 +116,7 @@ func sendAlertInAppToAdmins(ctx context.Context, adminIDs []int64, title, conten
Type: "alert",
Title: title,
Content: content,
IsRead: false,
IsRead: 0,
})
if err != nil {
g.Log().Warningf(ctx, "create in-app alert for admin %d: %v", adminID, err)
+9
View File
@@ -81,6 +81,11 @@ func CreateAlertRule(ctx context.Context, data do.OpsAlertRules) (int64, error)
return 0, common.NewBadRequestError("规则名称不能为空")
}
metricType := gconv.String(data.MetricType)
// Auto-prefix: if no namespace (dot) found, try prepending "api."
if !strings.Contains(metricType, ".") {
metricType = "api." + metricType
data.MetricType = metricType
}
if _, ok := validMetricTypes[metricType]; !ok {
return 0, gerror.Newf("不支持的指标类型: %s", metricType)
}
@@ -116,6 +121,10 @@ func UpdateAlertRule(ctx context.Context, id int64, updates do.OpsAlertRules) er
// Validate metric_type if provided
if updates.MetricType != nil {
metricType := gconv.String(updates.MetricType)
if !strings.Contains(metricType, ".") {
metricType = "api." + metricType
updates.MetricType = metricType
}
if _, valid := validMetricTypes[metricType]; !valid {
return gerror.Newf("不支持的指标类型: %s", metricType)
}