diff --git a/api/admin/v1/audit.go b/api/admin/v1/audit.go index 0bf93bc..01da006 100644 --- a/api/admin/v1/audit.go +++ b/api/admin/v1/audit.go @@ -1,6 +1,10 @@ package v1 -import "github.com/gogf/gf/v2/frame/g" +import ( + "encoding/json" + + "github.com/gogf/gf/v2/frame/g" +) // === 审计管理 === @@ -81,7 +85,11 @@ type RequestAuditLogDetailReq struct { } type RequestAuditLogDetailRes struct { - Data map[string]any `json:"data"` + Data map[string]any `json:"-"` +} + +func (r *RequestAuditLogDetailRes) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Data) } // OperationLogExportReq 导出操作日志请求 diff --git a/api/admin/v1/data_governance.go b/api/admin/v1/data_governance.go index 6ea0f2a..4c9c96f 100644 --- a/api/admin/v1/data_governance.go +++ b/api/admin/v1/data_governance.go @@ -1,6 +1,10 @@ package v1 -import "github.com/gogf/gf/v2/frame/g" +import ( + "encoding/json" + + "github.com/gogf/gf/v2/frame/g" +) // === 数据治理设置 === @@ -9,7 +13,11 @@ type DataGovernanceSettingsGetReq struct { } type DataGovernanceSettingsGetRes struct { - Data map[string]any `json:"data"` + Data map[string]any `json:"-"` +} + +func (r *DataGovernanceSettingsGetRes) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Data) } type DataGovernanceSettingsUpdateReq struct { diff --git a/api/admin/v1/error_log.go b/api/admin/v1/error_log.go index cc95aae..574e72f 100644 --- a/api/admin/v1/error_log.go +++ b/api/admin/v1/error_log.go @@ -1,6 +1,10 @@ package v1 -import "github.com/gogf/gf/v2/frame/g" +import ( + "encoding/json" + + "github.com/gogf/gf/v2/frame/g" +) // === 系统错误日志 === @@ -29,7 +33,11 @@ type ErrorLogDetailReq struct { } type ErrorLogDetailRes struct { - Data map[string]any `json:"data"` + Data map[string]any `json:"-"` +} + +func (r *ErrorLogDetailRes) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Data) } type ErrorLogResolveReq struct { diff --git a/api/admin/v1/model_group.go b/api/admin/v1/model_group.go index bb5f9ae..5647b23 100644 --- a/api/admin/v1/model_group.go +++ b/api/admin/v1/model_group.go @@ -94,7 +94,7 @@ type GroupModelItem struct { type GroupModelsSetReq struct { g.Meta `path:"/model-groups/{id}/models" method:"put" mime:"json" tags:"管理后台-模型分组" summary:"设置分组内模型"` ID int64 `json:"id" in:"path" v:"required" dc:"分组ID"` - ModelIds []int64 `json:"model_ids" v:"required#请选择模型" dc:"模型ID列表"` + ModelIds []int64 `json:"model_ids" dc:"模型ID列表(空数组表示清空)"` } // GroupModelsSetRes 设置分组内模型响应 @@ -126,7 +126,7 @@ type TenantGroupItem struct { type TenantGroupsSetReq struct { g.Meta `path:"/tenants/{tenant_id}/groups" method:"put" mime:"json" tags:"管理后台-模型分组" summary:"设置租户关联分组"` TenantID int64 `json:"tenant_id" in:"path" v:"required" dc:"租户ID"` - GroupIds []int64 `json:"group_ids" v:"required#请选择分组" dc:"分组ID列表"` + GroupIds []int64 `json:"group_ids" dc:"分组ID列表(空数组表示清空)"` } // TenantGroupsSetRes 设置租户关联分组响应 diff --git a/api/admin/v1/monitor.go b/api/admin/v1/monitor.go index 1661bcc..3868139 100644 --- a/api/admin/v1/monitor.go +++ b/api/admin/v1/monitor.go @@ -45,7 +45,11 @@ type MonitorDBPoolReq struct { } type MonitorDBPoolRes struct { - Data map[string]any `json:"data"` + ActiveConnections int `json:"active_connections"` + IdleConnections int `json:"idle_connections"` + TotalConnections int `json:"total_connections"` + MaxConnections int `json:"max_connections"` + WaitingQueries int `json:"waiting_queries"` } type MonitorRedisPoolReq struct { @@ -53,7 +57,15 @@ type MonitorRedisPoolReq struct { } type MonitorRedisPoolRes struct { - Data map[string]any `json:"data"` + ConnectedClients int `json:"connected_clients"` + UsedMemoryMB float64 `json:"used_memory_mb"` + MaxMemoryMB float64 `json:"max_memory_mb"` + UsedMemoryPercent float64 `json:"used_memory_percent"` + TotalCommands int64 `json:"total_commands"` + InstantaneousOps int64 `json:"instantaneous_ops"` + KeyspaceHits int64 `json:"keyspace_hits"` + KeyspaceMisses int64 `json:"keyspace_misses"` + HitRate float64 `json:"hit_rate"` } // === 实时监控 === diff --git a/api/admin/v1/notification.go b/api/admin/v1/notification.go index 775e09c..8eaa857 100644 --- a/api/admin/v1/notification.go +++ b/api/admin/v1/notification.go @@ -38,7 +38,15 @@ type TemplateGetReq struct { } type TemplateGetRes struct { - Data map[string]any `json:"data"` + Id int64 `json:"id"` + Code string `json:"code"` + Channel string `json:"channel"` + Subject string `json:"subject"` + BodyTemplate string `json:"body_template"` + Variables string `json:"variables"` + Status string `json:"status"` + CreatedAt *gtime.Time `json:"created_at"` + UpdatedAt *gtime.Time `json:"updated_at"` } type TemplateUpdateReq struct { @@ -57,8 +65,16 @@ type TemplateTestReq struct { Variables map[string]any `json:"variables"` } +type TemplateRenderResult struct { + Original string `json:"original"` + Rendered string `json:"rendered,omitempty"` + Error string `json:"error,omitempty"` +} + type TemplateTestRes struct { - Data map[string]any `json:"data"` + Subject TemplateRenderResult `json:"subject"` + Body TemplateRenderResult `json:"body"` + Channel string `json:"channel"` } // === 站内消息管理 === diff --git a/api/admin/v1/order_admin.go b/api/admin/v1/order_admin.go index db93256..d8ddb2b 100644 --- a/api/admin/v1/order_admin.go +++ b/api/admin/v1/order_admin.go @@ -53,7 +53,7 @@ type OrderDetailReq struct { } type OrderDetailRes struct { - Data map[string]any `json:"data"` + *OrderItem } type OrderRefundReq struct { diff --git a/api/admin/v1/ticket.go b/api/admin/v1/ticket.go index 34f53ff..7c0c2f6 100644 --- a/api/admin/v1/ticket.go +++ b/api/admin/v1/ticket.go @@ -46,8 +46,44 @@ type TicketGetReq struct { Id int64 `json:"id" in:"path" v:"required|min:1"` } +type TicketReplyItem struct { + Id int64 `json:"id"` + TicketId int64 `json:"ticket_id"` + UserId int64 `json:"user_id"` + UserType string `json:"user_type"` + UserName string `json:"user_name"` + Content string `json:"content"` + CreatedAt *gtime.Time `json:"created_at"` +} + +type TicketAttachmentItem struct { + Id int64 `json:"id"` + TicketId int64 `json:"ticket_id"` + ReplyId int64 `json:"reply_id"` + FileName string `json:"file_name"` + FileUrl string `json:"file_url"` + FileSize int `json:"file_size"` + ContentType string `json:"content_type"` + CreatedAt *gtime.Time `json:"created_at"` +} + type TicketGetRes struct { - Data map[string]any `json:"data"` + Id int64 `json:"id"` + TenantId int64 `json:"tenant_id"` + UserId int64 `json:"user_id"` + Category string `json:"category"` + Title string `json:"title"` + Description string `json:"description"` + Urgency string `json:"urgency"` + Status string `json:"status"` + AssignedAdminId int64 `json:"assigned_admin_id"` + TenantName string `json:"tenant_name"` + UserDisplayName string `json:"user_display_name"` + AssignedAdminName string `json:"assigned_admin_name"` + CreatedAt *gtime.Time `json:"created_at"` + UpdatedAt *gtime.Time `json:"updated_at"` + Replies []*TicketReplyItem `json:"replies"` + Attachments []*TicketAttachmentItem `json:"attachments"` } type TicketAssignReq struct { diff --git a/api/tenant/v1/audit_tenant.go b/api/tenant/v1/audit_tenant.go index b2c7263..63e51ea 100644 --- a/api/tenant/v1/audit_tenant.go +++ b/api/tenant/v1/audit_tenant.go @@ -1,6 +1,10 @@ package v1 -import "github.com/gogf/gf/v2/frame/g" +import ( + "encoding/json" + + "github.com/gogf/gf/v2/frame/g" +) // === 租户审计 === @@ -60,5 +64,9 @@ type TenantRequestAuditLogDetailReq struct { } type TenantRequestAuditLogDetailRes struct { - Data map[string]any `json:"data"` + Data map[string]any `json:"-"` +} + +func (r *TenantRequestAuditLogDetailRes) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Data) } diff --git a/api/tenant/v1/member_import_tenant.go b/api/tenant/v1/member_import_tenant.go index 24749d2..0bbf810 100644 --- a/api/tenant/v1/member_import_tenant.go +++ b/api/tenant/v1/member_import_tenant.go @@ -23,7 +23,15 @@ type TenantImportRecordGetReq struct { } type TenantImportRecordGetRes struct { - Data map[string]any `json:"data"` + Id int64 `json:"id"` + Filename string `json:"filename"` + TotalCount int `json:"total_count"` + SuccessCount int `json:"success_count"` + FailCount int `json:"fail_count"` + SkipCount int `json:"skip_count"` + Status string `json:"status"` + ErrorMessage string `json:"error_message"` + Details any `json:"details,omitempty"` } type TenantMemberImportReq struct { @@ -31,5 +39,5 @@ type TenantMemberImportReq struct { } type TenantMemberImportRes struct { - Data map[string]any `json:"data"` + ID int64 `json:"id"` } diff --git a/api/tenant/v1/notification_tenant.go b/api/tenant/v1/notification_tenant.go index 38dddd0..6b458e6 100644 --- a/api/tenant/v1/notification_tenant.go +++ b/api/tenant/v1/notification_tenant.go @@ -67,7 +67,9 @@ type TenantNotificationPreferencesGetReq struct { } type TenantNotificationPreferencesGetRes struct { - Data map[string]any `json:"data"` + OrgPreferences map[string]any `json:"org_preferences"` + UserPreferences map[string]any `json:"user_preferences"` + Merged map[string]any `json:"merged"` } type TenantNotificationPreferencesUpdateReq struct { diff --git a/api/tenant/v1/order_tenant.go b/api/tenant/v1/order_tenant.go index fc2a805..97221bb 100644 --- a/api/tenant/v1/order_tenant.go +++ b/api/tenant/v1/order_tenant.go @@ -80,7 +80,7 @@ type TenantOrderDetailReq struct { } type TenantOrderDetailRes struct { - Data map[string]any `json:"data"` + *TenantOrderItem } type TenantOrderCancelReq struct { diff --git a/api/tenant/v1/project_tenant.go b/api/tenant/v1/project_tenant.go index 242dc19..062861f 100644 --- a/api/tenant/v1/project_tenant.go +++ b/api/tenant/v1/project_tenant.go @@ -61,7 +61,16 @@ type TenantProjectGetReq struct { } type TenantProjectGetRes struct { - Data map[string]any `json:"data"` + Id int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Status string `json:"status"` + Budget *string `json:"budget"` + CreatedBy int64 `json:"created_by"` + ActiveKeys int `json:"active_keys"` + TotalKeys int `json:"total_keys"` + MonthCost float64 `json:"month_cost"` + MonthRequests int64 `json:"month_requests"` } // === 项目子资源(owner/admin 权限) === diff --git a/api/tenant/v1/promo_tenant.go b/api/tenant/v1/promo_tenant.go index ae8263e..2e71a8a 100644 --- a/api/tenant/v1/promo_tenant.go +++ b/api/tenant/v1/promo_tenant.go @@ -13,7 +13,12 @@ type TenantRedeemCodeReq struct { } type TenantRedeemCodeRes struct { - Data map[string]any `json:"data"` + Code string `json:"code"` + Type string `json:"type"` + Credited float64 `json:"credited,omitempty"` + PlanId int64 `json:"plan_id,omitempty"` + Months int `json:"months,omitempty"` + ExtendedDays int `json:"extended_days,omitempty"` } type TenantValidatePromoCodeReq struct { @@ -23,7 +28,10 @@ type TenantValidatePromoCodeReq struct { } type TenantValidatePromoCodeRes struct { - Data map[string]any `json:"data"` + PromoCodeId int64 `json:"promo_code_id"` + Type string `json:"type"` + Discount float64 `json:"discount"` + FinalAmount float64 `json:"final_amount"` } type TenantRedemptionUsagesReq struct { diff --git a/api/tenant/v1/ticket_tenant.go b/api/tenant/v1/ticket_tenant.go index 09179da..24fc1eb 100644 --- a/api/tenant/v1/ticket_tenant.go +++ b/api/tenant/v1/ticket_tenant.go @@ -53,8 +53,41 @@ type TenantTicketGetReq struct { Id int64 `json:"id" in:"path" v:"required|min:1"` } +type TenantTicketReplyItem struct { + Id int64 `json:"id"` + TicketId int64 `json:"ticket_id"` + UserId int64 `json:"user_id"` + UserType string `json:"user_type"` + Content string `json:"content"` + CreatedAt *gtime.Time `json:"created_at"` +} + +type TenantTicketAttachmentItem struct { + Id int64 `json:"id"` + TicketId int64 `json:"ticket_id"` + ReplyId int64 `json:"reply_id"` + FileName string `json:"file_name"` + FileUrl string `json:"file_url"` + FileSize int `json:"file_size"` + ContentType string `json:"content_type"` + CreatedAt *gtime.Time `json:"created_at"` +} + type TenantTicketGetRes struct { - Data map[string]any `json:"data"` + Id int64 `json:"id"` + TenantId int64 `json:"tenant_id"` + UserId int64 `json:"user_id"` + Category string `json:"category"` + Title string `json:"title"` + Description string `json:"description"` + Urgency string `json:"urgency"` + Status string `json:"status"` + AssignedAdminId int64 `json:"assigned_admin_id"` + AssignedAdminName string `json:"assigned_admin_name"` + CreatedAt *gtime.Time `json:"created_at"` + UpdatedAt *gtime.Time `json:"updated_at"` + Replies []*TenantTicketReplyItem `json:"replies"` + Attachments []*TenantTicketAttachmentItem `json:"attachments"` } type TenantTicketReplyReq struct { diff --git a/internal/logic/admin/notification.go b/internal/logic/admin/notification.go index c332c9a..9148518 100644 --- a/internal/logic/admin/notification.go +++ b/internal/logic/admin/notification.go @@ -40,16 +40,17 @@ 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) { - record, err := dao.NtfTemplates.Ctx(ctx). + var tpl *v1.TemplateGetRes + err := dao.NtfTemplates.Ctx(ctx). Where("code", req.Code). - One() + Scan(&tpl) if err != nil { return nil, err } - if record == nil { + if tpl == nil { return nil, common.NewNotFoundError("template") } - return &v1.TemplateGetRes{Data: record.Map()}, nil + return tpl, nil } // UpdateTemplate 更新通知模板 @@ -92,41 +93,32 @@ func (s *sAdmin) TestTemplate(ctx context.Context, req *v1.TemplateTestReq) (*v1 return nil, common.NewNotFoundError("template") } - type renderResult struct { - Original string `json:"original"` - Rendered string `json:"rendered"` - Error string `json:"error,omitempty"` - } - subjectRendered, subjectErr := renderGoTemplate(tpl.Subject, req.Variables) bodyRendered, bodyErr := renderGoTemplate(tpl.BodyTemplate, req.Variables) - result := map[string]any{ - "subject": renderResult{ - Original: tpl.Subject, - Rendered: subjectRendered, - }, - "body": renderResult{ - Original: tpl.BodyTemplate, - Rendered: bodyRendered, - }, - "channel": tpl.Channel, + subjectResult := v1.TemplateRenderResult{ + Original: tpl.Subject, + } + if subjectErr != nil { + subjectResult.Error = subjectErr.Error() + } else { + subjectResult.Rendered = subjectRendered } - if subjectErr != nil { - result["subject"] = renderResult{ - Original: tpl.Subject, - Error: subjectErr.Error(), - } + bodyResult := v1.TemplateRenderResult{ + Original: tpl.BodyTemplate, } if bodyErr != nil { - result["body"] = renderResult{ - Original: tpl.BodyTemplate, - Error: bodyErr.Error(), - } + bodyResult.Error = bodyErr.Error() + } else { + bodyResult.Rendered = bodyRendered } - return &v1.TemplateTestRes{Data: result}, nil + return &v1.TemplateTestRes{ + Subject: subjectResult, + Body: bodyResult, + Channel: tpl.Channel, + }, nil } // SendMessage 创建手动站内消息并推送 WebSocket 通知 diff --git a/internal/logic/admin/order.go b/internal/logic/admin/order.go index 85fe350..359beab 100644 --- a/internal/logic/admin/order.go +++ b/internal/logic/admin/order.go @@ -45,16 +45,17 @@ 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) { - record, err := dao.OrdOrders.Ctx(ctx). + var order *v1.OrderItem + err := dao.OrdOrders.Ctx(ctx). Where("id", req.Id). - One() + Scan(&order) if err != nil { return nil, err } - if record == nil { + if order == nil { return nil, common.NewNotFoundError("订单") } - return &v1.OrderDetailRes{Data: record.Map()}, nil + return &v1.OrderDetailRes{OrderItem: order}, nil } // RefundOrder 发起退款 diff --git a/internal/logic/admin/ticket.go b/internal/logic/admin/ticket.go index 8c24a64..a2206da 100644 --- a/internal/logic/admin/ticket.go +++ b/internal/logic/admin/ticket.go @@ -4,7 +4,6 @@ import ( "context" "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" @@ -113,50 +112,76 @@ func (s *sAdmin) GetTicketAdmin(ctx context.Context, req *v1.TicketGetReq) (*v1. return nil, common.NewNotFoundError("工单") } - type replyItem struct { + type replyRow struct { entity.SptReplies - UserName string `json:"user_name"` + UserName string `json:"user_name" orm:"user_name"` } - var replies []*replyItem + var replyRows []*replyRow err = dao.SptReplies.Ctx(ctx). LeftJoin("sys_admin_users sa", "spt_replies.user_type = 'admin' AND spt_replies.user_id = sa.id"). LeftJoin("tnt_users tu", "spt_replies.user_type = 'tenant' AND spt_replies.user_id = tu.id"). Fields("spt_replies.*, CASE WHEN spt_replies.user_type = 'admin' THEN COALESCE(sa.username, '') ELSE COALESCE(tu.display_name, '') END as user_name"). Where("spt_replies.ticket_id", req.Id). OrderAsc("spt_replies.created_at"). - Scan(&replies) + Scan(&replyRows) if err != nil { return nil, err } - var attachments []*entity.SptAttachments + replies := make([]*v1.TicketReplyItem, 0, len(replyRows)) + for _, r := range replyRows { + replies = append(replies, &v1.TicketReplyItem{ + Id: r.Id, + TicketId: r.TicketId, + UserId: r.UserId, + UserType: r.UserType, + UserName: r.UserName, + Content: r.Content, + CreatedAt: r.CreatedAt, + }) + } + + var attachRows []*entity.SptAttachments err = dao.SptAttachments.Ctx(ctx). Where("ticket_id", req.Id). OrderAsc("created_at"). - Scan(&attachments) + Scan(&attachRows) if err != nil { return nil, err } - data := g.Map{ - "id": ticket.Id, - "tenant_id": ticket.TenantId, - "user_id": ticket.UserId, - "category": ticket.Category, - "title": ticket.Title, - "description": ticket.Description, - "urgency": ticket.Urgency, - "status": ticket.Status, - "assigned_admin_id": ticket.AssignedAdminId, - "tenant_name": ticket.TenantName, - "user_display_name": ticket.UserDisplayName, - "assigned_admin_name": ticket.AssignedAdminName, - "created_at": ticket.CreatedAt, - "updated_at": ticket.UpdatedAt, - "replies": replies, - "attachments": attachments, + attachments := make([]*v1.TicketAttachmentItem, 0, len(attachRows)) + for _, a := range attachRows { + attachments = append(attachments, &v1.TicketAttachmentItem{ + Id: a.Id, + TicketId: a.TicketId, + ReplyId: a.ReplyId, + FileName: a.FileName, + FileUrl: a.FileUrl, + FileSize: a.FileSize, + ContentType: a.ContentType, + CreatedAt: a.CreatedAt, + }) } - return &v1.TicketGetRes{Data: data}, nil + + return &v1.TicketGetRes{ + Id: ticket.Id, + TenantId: ticket.TenantId, + UserId: ticket.UserId, + Category: ticket.Category, + Title: ticket.Title, + Description: ticket.Description, + Urgency: ticket.Urgency, + Status: ticket.Status, + AssignedAdminId: ticket.AssignedAdminId, + TenantName: ticket.TenantName, + UserDisplayName: ticket.UserDisplayName, + AssignedAdminName: ticket.AssignedAdminName, + CreatedAt: ticket.CreatedAt, + UpdatedAt: ticket.UpdatedAt, + Replies: replies, + Attachments: attachments, + }, nil } // AssignTicket 分配工单给管理员 diff --git a/internal/logic/monitor/monitor.go b/internal/logic/monitor/monitor.go index d938cb6..814abda 100644 --- a/internal/logic/monitor/monitor.go +++ b/internal/logic/monitor/monitor.go @@ -71,13 +71,13 @@ func (s *sMonitor) DBPool(ctx context.Context, _ *v1.MonitorDBPoolReq) (*v1.Moni if err != nil { return nil, err } - return &v1.MonitorDBPoolRes{Data: g.Map{ - "active_connections": data.ActiveConnections, - "idle_connections": data.IdleConnections, - "total_connections": data.TotalConnections, - "max_connections": data.MaxConnections, - "waiting_queries": data.WaitingQueries, - }}, nil + return &v1.MonitorDBPoolRes{ + ActiveConnections: data.ActiveConnections, + IdleConnections: data.IdleConnections, + TotalConnections: data.TotalConnections, + MaxConnections: data.MaxConnections, + WaitingQueries: data.WaitingQueries, + }, nil } func (s *sMonitor) RedisPool(ctx context.Context, _ *v1.MonitorRedisPoolReq) (*v1.MonitorRedisPoolRes, error) { @@ -85,17 +85,17 @@ func (s *sMonitor) RedisPool(ctx context.Context, _ *v1.MonitorRedisPoolReq) (*v if err != nil { return nil, err } - return &v1.MonitorRedisPoolRes{Data: g.Map{ - "connected_clients": data.ConnectedClients, - "used_memory_mb": data.UsedMemoryMB, - "max_memory_mb": data.MaxMemoryMB, - "used_memory_percent": data.UsedMemoryPercent, - "total_commands": data.TotalCommands, - "instantaneous_ops": data.InstantaneousOps, - "keyspace_hits": data.KeyspaceHits, - "keyspace_misses": data.KeyspaceMisses, - "hit_rate": data.HitRate, - }}, nil + return &v1.MonitorRedisPoolRes{ + ConnectedClients: data.ConnectedClients, + UsedMemoryMB: data.UsedMemoryMB, + MaxMemoryMB: data.MaxMemoryMB, + UsedMemoryPercent: data.UsedMemoryPercent, + TotalCommands: data.TotalCommands, + InstantaneousOps: int64(data.InstantaneousOps), + KeyspaceHits: data.KeyspaceHits, + KeyspaceMisses: data.KeyspaceMisses, + HitRate: data.HitRate, + }, nil } func (s *sMonitor) Realtime(ctx context.Context, _ *v1.MonitorRealtimeReq) (*v1.MonitorRealtimeRes, error) { diff --git a/internal/logic/tenant/member.go b/internal/logic/tenant/member.go index 5253266..9ba1ca4 100644 --- a/internal/logic/tenant/member.go +++ b/internal/logic/tenant/member.go @@ -474,7 +474,9 @@ func (s *sTenant) RemoveMember(ctx context.Context, req *v1.TenantMemberRemoveRe // Check if target is owner var user struct { - Role string `json:"role"` + Role string `json:"role"` + DisplayName string `json:"display_name"` + Username string `json:"username"` } err := dao.TntUsers.Ctx(ctx). Where("id", memberID). @@ -487,6 +489,16 @@ func (s *sTenant) RemoveMember(ctx context.Context, req *v1.TenantMemberRemoveRe return nil, common.NewBadRequestError("不能移除组织所有者") } + removedDisplayName := user.DisplayName + if removedDisplayName == "" { + removedDisplayName = user.Username + } + if removedDisplayName != "" { + removedDisplayName = fmt.Sprintf("[已移除] %s", removedDisplayName) + } else { + removedDisplayName = "[已移除成员]" + } + // Revoke all sessions common.RevokeAllSessions(ctx, "tenant", memberID) @@ -511,7 +523,7 @@ func (s *sTenant) RemoveMember(ctx context.Context, req *v1.TenantMemberRemoveRe Data(do.TntUsers{ Status: "removed", Email: fmt.Sprintf("deleted_%d@removed.local", memberID), - DisplayName: "[已移除成员]", + DisplayName: removedDisplayName, Username: fmt.Sprintf("deleted_%d", memberID), }).Update() if err != nil { diff --git a/internal/logic/tenant/member_import.go b/internal/logic/tenant/member_import.go index 14ae86b..cc04327 100644 --- a/internal/logic/tenant/member_import.go +++ b/internal/logic/tenant/member_import.go @@ -55,9 +55,7 @@ func (s *sTenant) MemberImport(ctx context.Context, req *v1.TenantMemberImportRe return nil, err } - return &v1.TenantMemberImportRes{Data: map[string]any{ - "id": importID, - }}, nil + return &v1.TenantMemberImportRes{ID: importID}, nil } // ImportRecords returns a paginated list of import records. @@ -144,24 +142,24 @@ func (s *sTenant) ImportRecordGet(ctx context.Context, req *v1.TenantImportRecor return nil, common.NewNotFoundError("导入记录") } - result := map[string]any{ - "id": record.ID, - "filename": record.Filename, - "total_count": record.TotalCount, - "success_count": record.SuccessCount, - "fail_count": record.FailCount, - "skip_count": record.SkipCount, - "status": record.Status, - "error_message": record.ErrorMessage, + res := &v1.TenantImportRecordGetRes{ + Id: record.ID, + Filename: record.Filename, + TotalCount: record.TotalCount, + SuccessCount: record.SuccessCount, + FailCount: record.FailCount, + SkipCount: record.SkipCount, + Status: record.Status, + ErrorMessage: record.ErrorMessage, } if record.ResultJSON != "" { var details []ImportResult json.Unmarshal([]byte(record.ResultJSON), &details) - result["details"] = details + res.Details = details } - return &v1.TenantImportRecordGetRes{Data: result}, nil + return res, nil } // -- internal helpers -- diff --git a/internal/logic/tenant/notification.go b/internal/logic/tenant/notification.go index 64b0fc5..118f215 100644 --- a/internal/logic/tenant/notification.go +++ b/internal/logic/tenant/notification.go @@ -282,12 +282,11 @@ func (s *sTenant) NotificationPreferencesGet(ctx context.Context, req *v1.Tenant orgPrefs := loadPreferences(ctx, tenantID, 0, "org") userPrefs := loadPreferences(ctx, tenantID, userID, "user") - result := map[string]any{ - "org_preferences": orgPrefs, - "user_preferences": userPrefs, - "merged": mergePreferences(orgPrefs, userPrefs), - } - return &v1.TenantNotificationPreferencesGetRes{Data: result}, nil + return &v1.TenantNotificationPreferencesGetRes{ + OrgPreferences: orgPrefs, + UserPreferences: userPrefs, + Merged: mergePreferences(orgPrefs, userPrefs), + }, nil } // NotificationPreferencesUpdate 更新通知偏好 diff --git a/internal/logic/tenant/order.go b/internal/logic/tenant/order.go index 4ed705d..025112f 100644 --- a/internal/logic/tenant/order.go +++ b/internal/logic/tenant/order.go @@ -57,7 +57,7 @@ func (s *sTenant) OrderDetail(ctx context.Context, req *v1.TenantOrderDetailReq) return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限") } tenantID := middleware.GetTenantID(ctx) - var order map[string]any + var order *v1.TenantOrderItem err := dao.OrdOrders.Ctx(ctx). Where("id", req.Id). Where("tenant_id", tenantID). @@ -68,7 +68,7 @@ func (s *sTenant) OrderDetail(ctx context.Context, req *v1.TenantOrderDetailReq) if order == nil { return nil, lcommon.NewNotFoundError("订单") } - return &v1.TenantOrderDetailRes{Data: order}, nil + return &v1.TenantOrderDetailRes{TenantOrderItem: order}, nil } // OrderCreate 创建订单 diff --git a/internal/logic/tenant/project.go b/internal/logic/tenant/project.go index c10e206..1631e1c 100644 --- a/internal/logic/tenant/project.go +++ b/internal/logic/tenant/project.go @@ -346,18 +346,16 @@ func (s *sTenant) ProjectGet(ctx context.Context, req *v1.TenantProjectGetReq) ( Scan(&monthUsage) return &v1.TenantProjectGetRes{ - Data: map[string]any{ - "id": p.Id, - "name": p.Name, - "description": p.Description, - "status": p.Status, - "budget": p.Budget, - "created_by": p.CreatedBy, - "active_keys": keyStats.Active, - "total_keys": keyStats.Total, - "month_cost": monthUsage.TotalCost, - "month_requests": monthUsage.RequestCount, - }, + Id: p.Id, + Name: p.Name, + Description: p.Description, + Status: p.Status, + Budget: p.Budget, + CreatedBy: p.CreatedBy, + ActiveKeys: keyStats.Active, + TotalKeys: keyStats.Total, + MonthCost: monthUsage.TotalCost, + MonthRequests: int64(monthUsage.RequestCount), }, nil } diff --git a/internal/logic/tenant/promo_code.go b/internal/logic/tenant/promo_code.go index 8096adc..4b0bf83 100644 --- a/internal/logic/tenant/promo_code.go +++ b/internal/logic/tenant/promo_code.go @@ -86,12 +86,12 @@ func (s *sTenant) ValidatePromoCode(ctx context.Context, req *v1.TenantValidateP return nil, lcommon.NewBusinessError(500, fmt.Sprintf("未知的优惠码类型: %s", promo.Type)) } - return &v1.TenantValidatePromoCodeRes{Data: map[string]any{ - "promo_code_id": promo.ID, - "type": promo.Type, - "discount": discount, - "final_amount": req.Amount - discount, - }}, nil + return &v1.TenantValidatePromoCodeRes{ + PromoCodeId: promo.ID, + Type: promo.Type, + Discount: discount, + FinalAmount: req.Amount - discount, + }, nil } // applyPromoCode 应用优惠码到订单 diff --git a/internal/logic/tenant/redemption.go b/internal/logic/tenant/redemption.go index 856d71d..00635e1 100644 --- a/internal/logic/tenant/redemption.go +++ b/internal/logic/tenant/redemption.go @@ -65,7 +65,7 @@ func (s *sTenant) RedeemCode(ctx context.Context, req *v1.TenantRedeemCodeReq) ( return nil, lcommon.NewBusinessError(422, "兑换码已全部使用") } - result := g.Map{"code": req.Code, "type": redemption.Type} + res := &v1.TenantRedeemCodeRes{Code: req.Code, Type: redemption.Type} var txID int64 usageValue := float64(0) @@ -73,7 +73,7 @@ func (s *sTenant) RedeemCode(ctx context.Context, req *v1.TenantRedeemCodeReq) ( case "quota": txID = creditWalletForRedemption(ctx, tenantID, redemption.Value, redemption.ID) usageValue = redemption.Value - result["credited"] = redemption.Value + res.Credited = redemption.Value case "plan": if redemption.PlanID == 0 { @@ -90,15 +90,15 @@ func (s *sTenant) RedeemCode(ctx context.Context, req *v1.TenantRedeemCodeReq) ( if err != nil { return nil, gerror.Wrapf(err, "激活套餐失败") } - result["plan_id"] = redemption.PlanID - result["months"] = months + res.PlanId = redemption.PlanID + res.Months = months case "duration": if redemption.DurationDays <= 0 { return nil, lcommon.NewBusinessError(422, "时长兑换码缺少duration_days") } extendPlanDuration(ctx, tenantID, redemption.DurationDays) - result["extended_days"] = redemption.DurationDays + res.ExtendedDays = redemption.DurationDays default: return nil, gerror.Newf("未知的兑换类型: %s", redemption.Type) @@ -119,7 +119,7 @@ func (s *sTenant) RedeemCode(ctx context.Context, req *v1.TenantRedeemCodeReq) ( "UPDATE ord_redemptions SET used_count = used_count + 1, redeemed_by = ?, redeemed_at = ?, updated_at = ? WHERE id = ?", tenantID, time.Now(), time.Now(), redemption.ID) - return &v1.TenantRedeemCodeRes{Data: result}, nil + return res, nil } func creditWalletForRedemption(ctx context.Context, tenantID int64, amount float64, redemptionID int64) int64 { diff --git a/internal/logic/tenant/ticket.go b/internal/logic/tenant/ticket.go index 6de46e0..1268d45 100644 --- a/internal/logic/tenant/ticket.go +++ b/internal/logic/tenant/ticket.go @@ -4,6 +4,7 @@ import ( "context" "github.com/qianfree/team-api/internal/dao" do "github.com/qianfree/team-api/internal/model/do" + "github.com/qianfree/team-api/internal/model/entity" v1 "github.com/qianfree/team-api/api/tenant/v1" "github.com/qianfree/team-api/internal/logic/common" @@ -74,7 +75,21 @@ func (s *sTenant) TicketGet(ctx context.Context, req *v1.TenantTicketGetReq) (*v tenantID := middleware.GetTenantID(ctx) userID := middleware.GetUserID(ctx) - var ticket map[string]any + type ticketRow struct { + Id int64 `json:"id" orm:"id"` + TenantId int64 `json:"tenant_id" orm:"tenant_id"` + UserId int64 `json:"user_id" orm:"user_id"` + Category string `json:"category" orm:"category"` + Title string `json:"title" orm:"title"` + Description string `json:"description" orm:"description"` + Urgency string `json:"urgency" orm:"urgency"` + Status string `json:"status" orm:"status"` + AssignedAdminId int64 `json:"assigned_admin_id" orm:"assigned_admin_id"` + AssignedAdminName string `json:"assigned_admin_name" orm:"assigned_admin_name"` + CreatedAt *gtime.Time `json:"created_at" orm:"created_at"` + UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at"` + } + var ticket *ticketRow err := dao.SptTickets.Ctx(ctx). LeftJoin("sys_admin_users sa", "spt_tickets.assigned_admin_id = sa.id"). Fields("spt_tickets.*, COALESCE(sa.username, '') as assigned_admin_name"). @@ -89,27 +104,54 @@ func (s *sTenant) TicketGet(ctx context.Context, req *v1.TenantTicketGetReq) (*v return nil, common.NewNotFoundError("工单") } - replies := make([]map[string]any, 0) + var replyRows []*v1.TenantTicketReplyItem err = dao.SptReplies.Ctx(ctx). Where("ticket_id", req.Id). OrderAsc("created_at"). - Scan(&replies) + Scan(&replyRows) if err != nil { return nil, err } - attachments := make([]map[string]any, 0) + var attachRows []*entity.SptAttachments err = dao.SptAttachments.Ctx(ctx). Where("ticket_id", req.Id). OrderAsc("created_at"). - Scan(&attachments) + Scan(&attachRows) if err != nil { return nil, err } - ticket["replies"] = replies - ticket["attachments"] = attachments - return &v1.TenantTicketGetRes{Data: ticket}, nil + attachments := make([]*v1.TenantTicketAttachmentItem, 0, len(attachRows)) + for _, a := range attachRows { + attachments = append(attachments, &v1.TenantTicketAttachmentItem{ + Id: a.Id, + TicketId: a.TicketId, + ReplyId: a.ReplyId, + FileName: a.FileName, + FileUrl: a.FileUrl, + FileSize: a.FileSize, + ContentType: a.ContentType, + CreatedAt: a.CreatedAt, + }) + } + + return &v1.TenantTicketGetRes{ + Id: ticket.Id, + TenantId: ticket.TenantId, + UserId: ticket.UserId, + Category: ticket.Category, + Title: ticket.Title, + Description: ticket.Description, + Urgency: ticket.Urgency, + Status: ticket.Status, + AssignedAdminId: ticket.AssignedAdminId, + AssignedAdminName: ticket.AssignedAdminName, + CreatedAt: ticket.CreatedAt, + UpdatedAt: ticket.UpdatedAt, + Replies: replyRows, + Attachments: attachments, + }, nil } // TicketReply 租户用户回复工单 diff --git a/tests/integration/admin/model_group_test.go b/tests/integration/admin/model_group_test.go index b0aba35..4156f5a 100644 --- a/tests/integration/admin/model_group_test.go +++ b/tests/integration/admin/model_group_test.go @@ -231,7 +231,7 @@ func TestModelGroupDuplicateCode(t *testing.T) { // 获取已创建分组的 code listResp := client.Get("/api/admin/model-groups", map[string]string{ - "page": "1", "page_size": "200", + "page": "1", "page_size": "100", }) listResp.AssertSuccess(t) diff --git a/tests/integration/tenant/project_test.go b/tests/integration/tenant/project_test.go index adce8cd..f0bc02f 100644 --- a/tests/integration/tenant/project_test.go +++ b/tests/integration/tenant/project_test.go @@ -44,18 +44,16 @@ func TestProjectCRUD(t *testing.T) { detailResp.AssertSuccess(t) var detail struct { - Data struct { - ID int64 `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Budget string `json:"budget"` - Status string `json:"status"` - } `json:"data"` + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Budget *string `json:"budget"` + Status string `json:"status"` } detailResp.DecodeData(t, &detail) - if detail.Data.ID != projectID { - t.Fatalf("expected id=%d, got %d", projectID, detail.Data.ID) + if detail.ID != projectID { + t.Fatalf("expected id=%d, got %d", projectID, detail.ID) } // --- Update ---