fix(test):修复单元测试发现的问题
This commit is contained in:
1 parent
b9d363cd09
commit
cdd314675b
29 files changed
+388
-167
No files matched your search
+10
-2
@@ -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 导出操作日志请求
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 设置租户关联分组响应
|
||||
|
||||
+14
-2
@@ -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"`
|
||||
}
|
||||
|
||||
// === 实时监控 ===
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
// === 站内消息管理 ===
|
||||
|
||||
@@ -53,7 +53,7 @@ type OrderDetailReq struct {
|
||||
}
|
||||
|
||||
type OrderDetailRes struct {
|
||||
Data map[string]any `json:"data"`
|
||||
*OrderItem
|
||||
}
|
||||
|
||||
type OrderRefundReq struct {
|
||||
|
||||
+37
-1
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -80,7 +80,7 @@ type TenantOrderDetailReq struct {
|
||||
}
|
||||
|
||||
type TenantOrderDetailRes struct {
|
||||
Data map[string]any `json:"data"`
|
||||
*TenantOrderItem
|
||||
}
|
||||
|
||||
type TenantOrderCancelReq struct {
|
||||
|
||||
@@ -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 权限) ===
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 通知
|
||||
|
||||
@@ -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 发起退款
|
||||
|
||||
@@ -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 分配工单给管理员
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 --
|
||||
|
||||
@@ -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 更新通知偏好
|
||||
|
||||
@@ -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 创建订单
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 应用优惠码到订单
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 租户用户回复工单
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
Reference in new issue
Block a user