diff --git a/backend/app/http/tenant/dto/order_admin_export.go b/backend/app/http/tenant/dto/order_admin_export.go new file mode 100644 index 0000000..fc035f7 --- /dev/null +++ b/backend/app/http/tenant/dto/order_admin_export.go @@ -0,0 +1,13 @@ +package dto + +// AdminOrderExportResponse 租户管理员订单导出响应(CSV 文本)。 +type AdminOrderExportResponse struct { + // Filename 建议文件名:前端可用于下载时的默认文件名。 + Filename string `json:"filename"` + + // ContentType 内容类型:当前固定为 text/csv。 + ContentType string `json:"content_type"` + + // CSV CSV 文本内容:UTF-8 编码,包含表头与数据行;前端可直接下载为文件。 + CSV string `json:"csv"` +} diff --git a/backend/app/http/tenant/order_admin.go b/backend/app/http/tenant/order_admin.go index 9ef8c4c..c3cb85a 100644 --- a/backend/app/http/tenant/order_admin.go +++ b/backend/app/http/tenant/order_admin.go @@ -63,6 +63,41 @@ func (*orderAdmin) adminOrderList( return services.Order.AdminOrderPage(ctx, tenant.ID, filter) } +// adminOrderExport +// +// @Summary 订单导出(租户管理) +// @Tags Tenant +// @Accept json +// @Produce json +// @Param tenantCode path string true "Tenant Code" +// @Param filter query dto.AdminOrderListFilter true "Filter" +// @Success 200 {object} dto.AdminOrderExportResponse +// +// @Router /t/:tenantCode/v1/admin/orders/export [get] +// @Bind tenant local key(tenant) +// @Bind tenantUser local key(tenant_user) +// @Bind filter query +func (*orderAdmin) adminOrderExport( + ctx fiber.Ctx, + tenant *models.Tenant, + tenantUser *models.TenantUser, + filter *dto.AdminOrderListFilter, +) (*dto.AdminOrderExportResponse, error) { + if err := requireTenantAdmin(tenantUser); err != nil { + return nil, err + } + if filter == nil { + filter = &dto.AdminOrderListFilter{} + } + + log.WithFields(log.Fields{ + "tenant_id": tenant.ID, + "user_id": tenantUser.UserID, + }).Info("tenant.admin.orders.export") + + return services.Order.AdminOrderExportCSV(ctx.Context(), tenant.ID, filter) +} + // adminOrderDetail // // @Summary 订单详情(租户管理) diff --git a/backend/app/http/tenant/routes.gen.go b/backend/app/http/tenant/routes.gen.go index ad60076..bb17cf0 100644 --- a/backend/app/http/tenant/routes.gen.go +++ b/backend/app/http/tenant/routes.gen.go @@ -156,6 +156,13 @@ func (r *Routes) Register(router fiber.Router) { Local[*models.TenantUser]("tenant_user"), PathParam[int64]("orderID"), )) + r.log.Debugf("Registering route: Get /t/:tenantCode/v1/admin/orders/export -> orderAdmin.adminOrderExport") + router.Get("/t/:tenantCode/v1/admin/orders/export"[len(r.Path()):], DataFunc3( + r.orderAdmin.adminOrderExport, + Local[*models.Tenant]("tenant"), + Local[*models.TenantUser]("tenant_user"), + Query[dto.AdminOrderListFilter]("filter"), + )) r.log.Debugf("Registering route: Post /t/:tenantCode/v1/admin/orders/:orderID/refund -> orderAdmin.adminRefund") router.Post("/t/:tenantCode/v1/admin/orders/:orderID/refund"[len(r.Path()):], DataFunc4( r.orderAdmin.adminRefund, diff --git a/backend/app/services/order.go b/backend/app/services/order.go index 07b8afe..a359335 100644 --- a/backend/app/services/order.go +++ b/backend/app/services/order.go @@ -1,7 +1,9 @@ package services import ( + "bytes" "context" + "encoding/csv" "encoding/json" "errors" "fmt" @@ -26,6 +28,158 @@ import ( "go.ipao.vip/gen/types" ) +// AdminOrderExportCSV 租户管理员导出订单列表(CSV 文本)。 +func (s *order) AdminOrderExportCSV(ctx context.Context, tenantID int64, filter *dto.AdminOrderListFilter) (*dto.AdminOrderExportResponse, error) { + if tenantID <= 0 { + return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id must be > 0") + } + if filter == nil { + filter = &dto.AdminOrderListFilter{} + } + + // 导出属于高消耗操作:限制最大行数,避免拖垮数据库。 + const maxRows = 5000 + + logrus.WithFields(logrus.Fields{ + "tenant_id": tenantID, + "max_rows": maxRows, + "user_id": lo.FromPtr(filter.UserID), + "username": filter.UsernameTrimmed(), + "content_id": lo.FromPtr(filter.ContentID), + "content_title": filter.ContentTitleTrimmed(), + "type": lo.FromPtr(filter.Type), + "status": lo.FromPtr(filter.Status), + }).Info("services.order.admin.export_csv") + + tbl, query := models.OrderQuery.QueryContext(ctx) + conds := []gen.Condition{tbl.TenantID.Eq(tenantID)} + if filter.UserID != nil { + conds = append(conds, tbl.UserID.Eq(*filter.UserID)) + } + if filter.Type != nil { + conds = append(conds, tbl.Type.Eq(*filter.Type)) + } + if filter.Status != nil { + conds = append(conds, tbl.Status.Eq(*filter.Status)) + } + if filter.CreatedAtFrom != nil { + conds = append(conds, tbl.CreatedAt.Gte(*filter.CreatedAtFrom)) + } + if filter.CreatedAtTo != nil { + conds = append(conds, tbl.CreatedAt.Lte(*filter.CreatedAtTo)) + } + if filter.PaidAtFrom != nil { + conds = append(conds, tbl.PaidAt.Gte(*filter.PaidAtFrom)) + } + if filter.PaidAtTo != nil { + conds = append(conds, tbl.PaidAt.Lte(*filter.PaidAtTo)) + } + if filter.AmountPaidMin != nil { + conds = append(conds, tbl.AmountPaid.Gte(*filter.AmountPaidMin)) + } + if filter.AmountPaidMax != nil { + conds = append(conds, tbl.AmountPaid.Lte(*filter.AmountPaidMax)) + } + + if username := filter.UsernameTrimmed(); username != "" { + uTbl, _ := models.UserQuery.QueryContext(ctx) + query = query.LeftJoin(uTbl, uTbl.ID.EqCol(tbl.UserID)) + conds = append(conds, uTbl.Username.Like(database.WrapLike(username))) + } + + needItemJoin := (filter.ContentID != nil && *filter.ContentID > 0) || filter.ContentTitleTrimmed() != "" + if needItemJoin { + oiTbl, _ := models.OrderItemQuery.QueryContext(ctx) + query = query.LeftJoin(oiTbl, oiTbl.OrderID.EqCol(tbl.ID)) + if filter.ContentID != nil && *filter.ContentID > 0 { + conds = append(conds, oiTbl.ContentID.Eq(*filter.ContentID)) + } + if title := filter.ContentTitleTrimmed(); title != "" { + cTbl, _ := models.ContentQuery.QueryContext(ctx) + query = query.LeftJoin(cTbl, cTbl.ID.EqCol(oiTbl.ContentID)) + conds = append(conds, cTbl.Title.Like(database.WrapLike(title))) + } + query = query.Group(tbl.ID) + } + + // 排序:复用 AdminOrderPage 的白名单,避免任意字段导致注入/慢查询。 + orderBys := make([]field.Expr, 0, 4) + allowedAsc := map[string]field.Expr{ + "id": tbl.ID.Asc(), + "created_at": tbl.CreatedAt.Asc(), + "paid_at": tbl.PaidAt.Asc(), + "amount_paid": tbl.AmountPaid.Asc(), + } + allowedDesc := map[string]field.Expr{ + "id": tbl.ID.Desc(), + "created_at": tbl.CreatedAt.Desc(), + "paid_at": tbl.PaidAt.Desc(), + "amount_paid": tbl.AmountPaid.Desc(), + } + for _, f := range filter.AscFields() { + f = strings.TrimSpace(f) + if f == "" { + continue + } + if ob, ok := allowedAsc[f]; ok { + orderBys = append(orderBys, ob) + } + } + for _, f := range filter.DescFields() { + f = strings.TrimSpace(f) + if f == "" { + continue + } + if ob, ok := allowedDesc[f]; ok { + orderBys = append(orderBys, ob) + } + } + if len(orderBys) == 0 { + orderBys = append(orderBys, tbl.ID.Desc()) + } else { + orderBys = append(orderBys, tbl.ID.Desc()) + } + + items, err := query.Where(conds...).Order(orderBys...).Limit(maxRows).Find() + if err != nil { + return nil, err + } + + buf := &bytes.Buffer{} + w := csv.NewWriter(buf) + _ = w.Write([]string{"id", "tenant_id", "user_id", "type", "status", "amount_paid", "paid_at", "created_at"}) + for _, it := range items { + if it == nil { + continue + } + paidAt := "" + if !it.PaidAt.IsZero() { + paidAt = it.PaidAt.UTC().Format(time.RFC3339) + } + _ = w.Write([]string{ + fmt.Sprintf("%d", it.ID), + fmt.Sprintf("%d", it.TenantID), + fmt.Sprintf("%d", it.UserID), + string(it.Type), + string(it.Status), + fmt.Sprintf("%d", it.AmountPaid), + paidAt, + it.CreatedAt.UTC().Format(time.RFC3339), + }) + } + w.Flush() + if err := w.Error(); err != nil { + return nil, err + } + + filename := fmt.Sprintf("tenant_%d_orders_%s.csv", tenantID, time.Now().UTC().Format("20060102_150405")) + return &dto.AdminOrderExportResponse{ + Filename: filename, + ContentType: "text/csv", + CSV: buf.String(), + }, nil +} + // PurchaseOrderSnapshot 为“内容购买订单”的下单快照(用于历史展示与争议审计)。 type PurchaseOrderSnapshot struct { // ContentID 内容ID。 diff --git a/backend/app/services/order_test.go b/backend/app/services/order_test.go index b88aba7..3d730fd 100644 --- a/backend/app/services/order_test.go +++ b/backend/app/services/order_test.go @@ -827,6 +827,56 @@ func (s *OrderTestSuite) Test_AdminOrderDetail() { }) } +func (s *OrderTestSuite) Test_AdminOrderExportCSV() { + Convey("Order.AdminOrderExportCSV", s.T(), func() { + ctx := s.T().Context() + now := time.Now().UTC() + tenantID := int64(1) + + s.truncate(ctx, models.TableNameOrderItem, models.TableNameOrder, models.TableNameUser, models.TableNameContent) + + Convey("参数非法应返回错误", func() { + _, err := Order.AdminOrderExportCSV(ctx, 0, &dto.AdminOrderListFilter{}) + So(err, ShouldNotBeNil) + }) + + Convey("导出应返回 CSV 且包含表头", func() { + u := &models.User{ + Username: "alice", + Password: "x", + Roles: types.NewArray([]consts.Role{consts.RoleUser}), + Status: consts.UserStatusVerified, + Metas: types.JSON([]byte("{}")), + CreatedAt: now, + UpdatedAt: now, + } + So(u.Create(ctx), ShouldBeNil) + + o := &models.Order{ + TenantID: tenantID, + UserID: u.ID, + Type: consts.OrderTypeContentPurchase, + Status: consts.OrderStatusPaid, + Currency: consts.CurrencyCNY, + AmountPaid: 123, + Snapshot: types.JSON([]byte("{}")), + PaidAt: now, + CreatedAt: now, + UpdatedAt: now, + } + So(o.Create(ctx), ShouldBeNil) + + resp, err := Order.AdminOrderExportCSV(ctx, tenantID, &dto.AdminOrderListFilter{}) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.ContentType, ShouldEqual, "text/csv") + So(resp.Filename, ShouldContainSubstring, "tenant_1_orders_") + So(resp.CSV, ShouldContainSubstring, "id,tenant_id,user_id,type,status,amount_paid,paid_at,created_at") + So(resp.CSV, ShouldContainSubstring, "content_purchase") + }) + }) +} + func (s *OrderTestSuite) Test_AdminRefundOrder() { Convey("Order.AdminRefundOrder", s.T(), func() { ctx := s.T().Context() diff --git a/backend/docs/docs.go b/backend/docs/docs.go index b485c4c..2721df3 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -1102,6 +1102,157 @@ const docTemplate = `{ } } }, + "/t/{tenantCode}/v1/admin/orders/export": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tenant" + ], + "summary": "订单导出(租户管理)", + "parameters": [ + { + "type": "string", + "description": "Tenant Code", + "name": "tenantCode", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "AmountPaidMax 实付金额上限(可选):amount_paid \u003c= 该值(单位分)。", + "name": "amount_paid_max", + "in": "query" + }, + { + "type": "integer", + "description": "AmountPaidMin 实付金额下限(可选):amount_paid \u003e= 该值(单位分)。", + "name": "amount_paid_min", + "in": "query" + }, + { + "type": "string", + "description": "Asc specifies comma-separated field names to sort ascending by.", + "name": "asc", + "in": "query" + }, + { + "type": "integer", + "description": "ContentID 内容ID(可选):通过 order_items 关联过滤。", + "name": "content_id", + "in": "query" + }, + { + "type": "string", + "description": "ContentTitle 内容标题关键字(可选):通过 order_items + contents 关联,模糊匹配 contents.title(like)。", + "name": "content_title", + "in": "query" + }, + { + "type": "string", + "description": "CreatedAtFrom 创建时间起(可选):created_at \u003e= 该时间(用于按创建时间筛选)。", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "description": "CreatedAtTo 创建时间止(可选):created_at \u003c= 该时间(用于按创建时间筛选)。", + "name": "created_at_to", + "in": "query" + }, + { + "type": "string", + "description": "Desc specifies comma-separated field names to sort descending by.", + "name": "desc", + "in": "query" + }, + { + "type": "integer", + "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.", + "name": "page", + "in": "query" + }, + { + "type": "string", + "description": "PaidAtFrom 支付时间起(可选):paid_at \u003e= 该时间(用于按支付时间筛选)。", + "name": "paid_at_from", + "in": "query" + }, + { + "type": "string", + "description": "PaidAtTo 支付时间止(可选):paid_at \u003c= 该时间(用于按支付时间筛选)。", + "name": "paid_at_to", + "in": "query" + }, + { + "enum": [ + "created", + "paid", + "refunding", + "refunded", + "canceled", + "failed" + ], + "type": "string", + "x-enum-varnames": [ + "OrderStatusCreated", + "OrderStatusPaid", + "OrderStatusRefunding", + "OrderStatusRefunded", + "OrderStatusCanceled", + "OrderStatusFailed" + ], + "description": "Status 订单状态(可选):created/paid/refunding/refunded/canceled/failed。", + "name": "status", + "in": "query" + }, + { + "enum": [ + "content_purchase", + "topup" + ], + "type": "string", + "x-enum-varnames": [ + "OrderTypeContentPurchase", + "OrderTypeTopup" + ], + "description": "Type 订单类型(可选):content_purchase/topup 等。", + "name": "type", + "in": "query" + }, + { + "type": "integer", + "description": "UserID 下单用户ID(可选):按买家用户ID精确过滤。", + "name": "user_id", + "in": "query" + }, + { + "type": "string", + "description": "Username 下单用户用户名关键字(可选):模糊匹配 users.username(like)。", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.AdminOrderExportResponse" + } + } + } + } + }, "/t/{tenantCode}/v1/admin/orders/{orderID}": { "get": { "consumes": [ @@ -2251,6 +2402,23 @@ const docTemplate = `{ } } }, + "dto.AdminOrderExportResponse": { + "type": "object", + "properties": { + "content_type": { + "description": "ContentType 内容类型:当前固定为 text/csv。", + "type": "string" + }, + "csv": { + "description": "CSV CSV 文本内容:UTF-8 编码,包含表头与数据行;前端可直接下载为文件。", + "type": "string" + }, + "filename": { + "description": "Filename 建议文件名:前端可用于下载时的默认文件名。", + "type": "string" + } + } + }, "dto.AdminOrderRefundForm": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 0bb6e18..4b86012 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -1096,6 +1096,157 @@ } } }, + "/t/{tenantCode}/v1/admin/orders/export": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tenant" + ], + "summary": "订单导出(租户管理)", + "parameters": [ + { + "type": "string", + "description": "Tenant Code", + "name": "tenantCode", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "AmountPaidMax 实付金额上限(可选):amount_paid \u003c= 该值(单位分)。", + "name": "amount_paid_max", + "in": "query" + }, + { + "type": "integer", + "description": "AmountPaidMin 实付金额下限(可选):amount_paid \u003e= 该值(单位分)。", + "name": "amount_paid_min", + "in": "query" + }, + { + "type": "string", + "description": "Asc specifies comma-separated field names to sort ascending by.", + "name": "asc", + "in": "query" + }, + { + "type": "integer", + "description": "ContentID 内容ID(可选):通过 order_items 关联过滤。", + "name": "content_id", + "in": "query" + }, + { + "type": "string", + "description": "ContentTitle 内容标题关键字(可选):通过 order_items + contents 关联,模糊匹配 contents.title(like)。", + "name": "content_title", + "in": "query" + }, + { + "type": "string", + "description": "CreatedAtFrom 创建时间起(可选):created_at \u003e= 该时间(用于按创建时间筛选)。", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "description": "CreatedAtTo 创建时间止(可选):created_at \u003c= 该时间(用于按创建时间筛选)。", + "name": "created_at_to", + "in": "query" + }, + { + "type": "string", + "description": "Desc specifies comma-separated field names to sort descending by.", + "name": "desc", + "in": "query" + }, + { + "type": "integer", + "description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.", + "name": "page", + "in": "query" + }, + { + "type": "string", + "description": "PaidAtFrom 支付时间起(可选):paid_at \u003e= 该时间(用于按支付时间筛选)。", + "name": "paid_at_from", + "in": "query" + }, + { + "type": "string", + "description": "PaidAtTo 支付时间止(可选):paid_at \u003c= 该时间(用于按支付时间筛选)。", + "name": "paid_at_to", + "in": "query" + }, + { + "enum": [ + "created", + "paid", + "refunding", + "refunded", + "canceled", + "failed" + ], + "type": "string", + "x-enum-varnames": [ + "OrderStatusCreated", + "OrderStatusPaid", + "OrderStatusRefunding", + "OrderStatusRefunded", + "OrderStatusCanceled", + "OrderStatusFailed" + ], + "description": "Status 订单状态(可选):created/paid/refunding/refunded/canceled/failed。", + "name": "status", + "in": "query" + }, + { + "enum": [ + "content_purchase", + "topup" + ], + "type": "string", + "x-enum-varnames": [ + "OrderTypeContentPurchase", + "OrderTypeTopup" + ], + "description": "Type 订单类型(可选):content_purchase/topup 等。", + "name": "type", + "in": "query" + }, + { + "type": "integer", + "description": "UserID 下单用户ID(可选):按买家用户ID精确过滤。", + "name": "user_id", + "in": "query" + }, + { + "type": "string", + "description": "Username 下单用户用户名关键字(可选):模糊匹配 users.username(like)。", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.AdminOrderExportResponse" + } + } + } + } + }, "/t/{tenantCode}/v1/admin/orders/{orderID}": { "get": { "consumes": [ @@ -2245,6 +2396,23 @@ } } }, + "dto.AdminOrderExportResponse": { + "type": "object", + "properties": { + "content_type": { + "description": "ContentType 内容类型:当前固定为 text/csv。", + "type": "string" + }, + "csv": { + "description": "CSV CSV 文本内容:UTF-8 编码,包含表头与数据行;前端可直接下载为文件。", + "type": "string" + }, + "filename": { + "description": "Filename 建议文件名:前端可用于下载时的默认文件名。", + "type": "string" + } + } + }, "dto.AdminOrderRefundForm": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 058a8a9..1dfdb84 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -187,6 +187,18 @@ definitions: - $ref: '#/definitions/models.Order' description: Order is the order with items preloaded. type: object + dto.AdminOrderExportResponse: + properties: + content_type: + description: ContentType 内容类型:当前固定为 text/csv。 + type: string + csv: + description: CSV CSV 文本内容:UTF-8 编码,包含表头与数据行;前端可直接下载为文件。 + type: string + filename: + description: Filename 建议文件名:前端可用于下载时的默认文件名。 + type: string + type: object dto.AdminOrderRefundForm: properties: force: @@ -1926,6 +1938,112 @@ paths: summary: 订单退款(租户管理) tags: - Tenant + /t/{tenantCode}/v1/admin/orders/export: + get: + consumes: + - application/json + parameters: + - description: Tenant Code + in: path + name: tenantCode + required: true + type: string + - description: AmountPaidMax 实付金额上限(可选):amount_paid <= 该值(单位分)。 + in: query + name: amount_paid_max + type: integer + - description: AmountPaidMin 实付金额下限(可选):amount_paid >= 该值(单位分)。 + in: query + name: amount_paid_min + type: integer + - description: Asc specifies comma-separated field names to sort ascending by. + in: query + name: asc + type: string + - description: ContentID 内容ID(可选):通过 order_items 关联过滤。 + in: query + name: content_id + type: integer + - description: ContentTitle 内容标题关键字(可选):通过 order_items + contents 关联,模糊匹配 contents.title(like)。 + in: query + name: content_title + type: string + - description: CreatedAtFrom 创建时间起(可选):created_at >= 该时间(用于按创建时间筛选)。 + in: query + name: created_at_from + type: string + - description: CreatedAtTo 创建时间止(可选):created_at <= 该时间(用于按创建时间筛选)。 + in: query + name: created_at_to + type: string + - description: Desc specifies comma-separated field names to sort descending + by. + in: query + name: desc + type: string + - description: Limit is page size; only values in {10,20,50,100} are accepted + (otherwise defaults to 10). + in: query + name: limit + type: integer + - description: Page is 1-based page index; values <= 0 are normalized to 1. + in: query + name: page + type: integer + - description: PaidAtFrom 支付时间起(可选):paid_at >= 该时间(用于按支付时间筛选)。 + in: query + name: paid_at_from + type: string + - description: PaidAtTo 支付时间止(可选):paid_at <= 该时间(用于按支付时间筛选)。 + in: query + name: paid_at_to + type: string + - description: Status 订单状态(可选):created/paid/refunding/refunded/canceled/failed。 + enum: + - created + - paid + - refunding + - refunded + - canceled + - failed + in: query + name: status + type: string + x-enum-varnames: + - OrderStatusCreated + - OrderStatusPaid + - OrderStatusRefunding + - OrderStatusRefunded + - OrderStatusCanceled + - OrderStatusFailed + - description: Type 订单类型(可选):content_purchase/topup 等。 + enum: + - content_purchase + - topup + in: query + name: type + type: string + x-enum-varnames: + - OrderTypeContentPurchase + - OrderTypeTopup + - description: UserID 下单用户ID(可选):按买家用户ID精确过滤。 + in: query + name: user_id + type: integer + - description: Username 下单用户用户名关键字(可选):模糊匹配 users.username(like)。 + in: query + name: username + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.AdminOrderExportResponse' + summary: 订单导出(租户管理) + tags: + - Tenant /t/{tenantCode}/v1/admin/users: get: consumes: diff --git a/backend/tests/tenant.http b/backend/tests/tenant.http index 02e2eee..4ff8b0c 100644 --- a/backend/tests/tenant.http +++ b/backend/tests/tenant.http @@ -146,6 +146,11 @@ GET {{ host }}/t/{{ tenantCode }}/v1/admin/orders?page=1&limit=10&username=alice Content-Type: application/json Authorization: Bearer {{ token }} +### Tenant Admin - Orders export (CSV text) +GET {{ host }}/t/{{ tenantCode }}/v1/admin/orders/export?username=alice&content_title=Go&type=content_purchase&desc=paid_at +Content-Type: application/json +Authorization: Bearer {{ token }} + ### Tenant Admin - Order detail @orderID = 1 GET {{ host }}/t/{{ tenantCode }}/v1/admin/orders/{{ orderID }}