feat: add TenantLedger model and query generation

- Introduced TenantLedger model with fields for managing tenant transactions, including ID, TenantID, UserID, OrderID, transaction Type, Amount, and balance details.
- Implemented CRUD operations for TenantLedger with methods for Create, Update, Delete, and Reload.
- Generated query methods for TenantLedger to facilitate database interactions, including filtering, pagination, and aggregation functions.
- Established relationships with Order model for foreign key references.
This commit is contained in:
2025-12-18 13:12:26 +08:00
parent f93caefcb2
commit 1da84f2af3
42 changed files with 6468 additions and 265 deletions

View File

@@ -603,6 +603,176 @@ const docTemplate = `{
}
}
},
"/t/{tenantCode}/v1/admin/orders": {
"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": "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"
},
{
"enum": [
"created",
"paid",
"refunding",
"refunded",
"canceled",
"failed"
],
"type": "string",
"x-enum-varnames": [
"OrderStatusCreated",
"OrderStatusPaid",
"OrderStatusRefunding",
"OrderStatusRefunded",
"OrderStatusCanceled",
"OrderStatusFailed"
],
"description": "Status filters orders by order status.",
"name": "status",
"in": "query"
},
{
"type": "integer",
"description": "UserID filters orders by buyer user id.",
"name": "user_id",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"$ref": "#/definitions/models.Order"
}
}
}
]
}
}
}
}
},
"/t/{tenantCode}/v1/admin/orders/{orderID}": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Tenant"
],
"summary": "订单详情(租户管理)",
"parameters": [
{
"type": "string",
"description": "Tenant Code",
"name": "tenantCode",
"in": "path",
"required": true
},
{
"type": "integer",
"format": "int64",
"description": "OrderID",
"name": "orderID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.AdminOrderDetail"
}
}
}
}
},
"/t/{tenantCode}/v1/admin/orders/{orderID}/refund": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Tenant"
],
"summary": "订单退款(租户管理)",
"parameters": [
{
"type": "string",
"description": "Tenant Code",
"name": "tenantCode",
"in": "path",
"required": true
},
{
"type": "integer",
"format": "int64",
"description": "OrderID",
"name": "orderID",
"in": "path",
"required": true
},
{
"description": "Form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AdminOrderRefundForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.Order"
}
}
}
}
},
"/t/{tenantCode}/v1/contents": {
"get": {
"consumes": [
@@ -781,6 +951,54 @@ const docTemplate = `{
}
}
},
"/t/{tenantCode}/v1/contents/{contentID}/purchase": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Tenant"
],
"summary": "购买内容(余额支付)",
"parameters": [
{
"type": "string",
"description": "Tenant Code",
"name": "tenantCode",
"in": "path",
"required": true
},
{
"type": "integer",
"format": "int64",
"description": "ContentID",
"name": "contentID",
"in": "path",
"required": true
},
{
"description": "Form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PurchaseContentForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.PurchaseContentResponse"
}
}
}
}
},
"/t/{tenantCode}/v1/me": {
"get": {
"consumes": [
@@ -811,9 +1029,138 @@ const docTemplate = `{
}
}
}
},
"/t/{tenantCode}/v1/orders": {
"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": "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"
},
{
"enum": [
"created",
"paid",
"refunding",
"refunded",
"canceled",
"failed"
],
"type": "string",
"x-enum-varnames": [
"OrderStatusCreated",
"OrderStatusPaid",
"OrderStatusRefunding",
"OrderStatusRefunded",
"OrderStatusCanceled",
"OrderStatusFailed"
],
"description": "Status filters orders by order status.",
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"$ref": "#/definitions/models.Order"
}
}
}
]
}
}
}
}
},
"/t/{tenantCode}/v1/orders/{orderID}": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Tenant"
],
"summary": "我的订单详情(当前租户)",
"parameters": [
{
"type": "string",
"description": "Tenant Code",
"name": "tenantCode",
"in": "path",
"required": true
},
{
"type": "integer",
"format": "int64",
"description": "OrderID",
"name": "orderID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.Order"
}
}
}
}
}
},
"definitions": {
"consts.ContentAccessStatus": {
"type": "string",
"enum": [
"active",
"revoked",
"expired"
],
"x-enum-varnames": [
"ContentAccessStatusActive",
"ContentAccessStatusRevoked",
"ContentAccessStatusExpired"
]
},
"consts.ContentAssetRole": {
"type": "string",
"enum": [
@@ -909,6 +1256,36 @@ const docTemplate = `{
"MediaAssetTypeImage"
]
},
"consts.OrderStatus": {
"type": "string",
"enum": [
"created",
"paid",
"refunding",
"refunded",
"canceled",
"failed"
],
"x-enum-varnames": [
"OrderStatusCreated",
"OrderStatusPaid",
"OrderStatusRefunding",
"OrderStatusRefunded",
"OrderStatusCanceled",
"OrderStatusFailed"
]
},
"consts.OrderType": {
"type": "string",
"enum": [
"content_purchase",
"topup"
],
"x-enum-varnames": [
"OrderTypeContentPurchase",
"OrderTypeTopup"
]
},
"consts.Role": {
"type": "string",
"enum": [
@@ -957,6 +1334,36 @@ const docTemplate = `{
"UserStatusBanned"
]
},
"dto.AdminOrderDetail": {
"type": "object",
"properties": {
"order": {
"description": "Order is the order with items preloaded.",
"allOf": [
{
"$ref": "#/definitions/models.Order"
}
]
}
}
},
"dto.AdminOrderRefundForm": {
"type": "object",
"properties": {
"force": {
"description": "Force indicates bypassing the default refund window check (paid_at + 24h).\n强制退款true 表示绕过默认退款时间窗限制(需审计)。",
"type": "boolean"
},
"idempotency_key": {
"description": "IdempotencyKey ensures refund request is processed at most once.\n幂等键同一笔退款重复请求时返回一致结果避免重复退款/重复回滚。",
"type": "string"
},
"reason": {
"description": "Reason is the human-readable refund reason used for audit.\n退款原因建议必填由业务侧校验用于审计与追责。",
"type": "string"
}
}
},
"dto.ContentAssetAttachForm": {
"type": "object",
"properties": {
@@ -1195,6 +1602,48 @@ const docTemplate = `{
}
}
},
"dto.PurchaseContentForm": {
"type": "object",
"properties": {
"idempotency_key": {
"description": "IdempotencyKey is used to ensure the purchase request is processed at most once.\n建议由客户端生成并保持稳定同一笔购买重复请求时返回相同结果避免重复扣款/重复下单。",
"type": "string"
}
}
},
"dto.PurchaseContentResponse": {
"type": "object",
"properties": {
"access": {
"description": "Access is the content access record after purchase grant.",
"allOf": [
{
"$ref": "#/definitions/models.ContentAccess"
}
]
},
"amount_paid": {
"description": "AmountPaid is the final paid amount in cents (CNY 分).",
"type": "integer"
},
"item": {
"description": "Item is the single order item of this purchase (current implementation is 1 order -\u003e 1 content).",
"allOf": [
{
"$ref": "#/definitions/models.OrderItem"
}
]
},
"order": {
"description": "Order is the created or existing order record (may be nil for owner/free-path without order).",
"allOf": [
{
"$ref": "#/definitions/models.Order"
}
]
}
}
},
"dto.TenantExpireUpdateForm": {
"type": "object",
"required": [
@@ -1387,43 +1836,113 @@ const docTemplate = `{
"type": "object",
"properties": {
"created_at": {
"description": "创建时间:默认 now();用于审计与排序",
"type": "string"
},
"deleted_at": {
"$ref": "#/definitions/gorm.DeletedAt"
"description": "软删除时间:非空表示已删除;对外接口需过滤",
"allOf": [
{
"$ref": "#/definitions/gorm.DeletedAt"
}
]
},
"description": {
"description": "描述:用于详情页展示;可为空字符串",
"type": "string"
},
"id": {
"description": "主键ID自增用于内容引用",
"type": "integer"
},
"preview_downloadable": {
"description": "试看是否允许下载:默认 false当前策略固定为不允许下载仅 streaming",
"type": "boolean"
},
"preview_seconds": {
"description": "试看秒数:默认 60只对 preview 资源生效;必须为正整数",
"type": "integer"
},
"published_at": {
"description": "发布时间:首次发布时写入;用于时间窗与排序",
"type": "string"
},
"status": {
"$ref": "#/definitions/consts.ContentStatus"
"description": "状态draft/reviewing/published/unpublished/blockedpublished 才对外展示",
"allOf": [
{
"$ref": "#/definitions/consts.ContentStatus"
}
]
},
"tenant_id": {
"description": "租户ID多租户隔离关键字段所有查询/写入必须限定 tenant_id",
"type": "integer"
},
"title": {
"description": "标题:用于列表展示与搜索;建议限制长度(由业务校验)",
"type": "string"
},
"updated_at": {
"description": "更新时间:默认 now();编辑内容时写入",
"type": "string"
},
"user_id": {
"description": "用户ID内容创建者/发布者;用于权限与审计(例如私有内容仅作者可见)",
"type": "integer"
},
"visibility": {
"$ref": "#/definitions/consts.ContentVisibility"
"description": "可见性public/tenant_only/private仅控制详情可见正片资源仍需按价格/权益校验",
"allOf": [
{
"$ref": "#/definitions/consts.ContentVisibility"
}
]
}
}
},
"models.ContentAccess": {
"type": "object",
"properties": {
"content_id": {
"description": "内容ID权益对应内容唯一约束 (tenant_id, user_id, content_id)",
"type": "integer"
},
"created_at": {
"description": "创建时间:默认 now();用于审计",
"type": "string"
},
"id": {
"description": "主键ID自增",
"type": "integer"
},
"order_id": {
"description": "订单ID产生该权益的订单可为空例如后台补发/迁移)",
"type": "integer"
},
"revoked_at": {
"description": "撤销时间:当 status=revoked 时写入;用于审计与追责",
"type": "string"
},
"status": {
"description": "权益状态active/revoked/expiredrevoked 表示立即失效(例如退款/违规)",
"allOf": [
{
"$ref": "#/definitions/consts.ContentAccessStatus"
}
]
},
"tenant_id": {
"description": "租户ID多租户隔离与内容、用户归属一致",
"type": "integer"
},
"updated_at": {
"description": "更新时间:默认 now();更新 status 时写入",
"type": "string"
},
"user_id": {
"description": "用户ID权益所属用户用于访问校验",
"type": "integer"
}
}
},
@@ -1431,30 +1950,43 @@ const docTemplate = `{
"type": "object",
"properties": {
"asset_id": {
"description": "资源ID关联 media_assets.id用于查询资源归属内容",
"type": "integer"
},
"content_id": {
"description": "内容ID关联 contents.id用于查询内容下资源列表",
"type": "integer"
},
"created_at": {
"description": "创建时间:默认 now();用于审计",
"type": "string"
},
"id": {
"description": "主键ID自增",
"type": "integer"
},
"role": {
"$ref": "#/definitions/consts.ContentAssetRole"
"description": "资源角色main/cover/previewpreview 必须为独立资源以满足禁下载与防绕过",
"allOf": [
{
"$ref": "#/definitions/consts.ContentAssetRole"
}
]
},
"sort": {
"description": "排序:同一 role 下的展示顺序,数值越小越靠前",
"type": "integer"
},
"tenant_id": {
"description": "租户ID多租户隔离必须与 content_id、asset_id 所属租户一致",
"type": "integer"
},
"updated_at": {
"description": "更新时间:默认 now();更新 sort/role 时写入",
"type": "string"
},
"user_id": {
"description": "用户ID操作人/绑定人;用于审计(通常为租户管理员或作者)",
"type": "integer"
}
}
@@ -1463,39 +1995,59 @@ const docTemplate = `{
"type": "object",
"properties": {
"content_id": {
"description": "内容ID唯一约束 (tenant_id, content_id);一个内容在一个租户内仅一份定价",
"type": "integer"
},
"created_at": {
"description": "创建时间:默认 now();用于审计",
"type": "string"
},
"currency": {
"$ref": "#/definitions/consts.Currency"
"description": "币种:当前固定 CNY金额单位为分",
"allOf": [
{
"$ref": "#/definitions/consts.Currency"
}
]
},
"discount_end_at": {
"description": "折扣结束时间:可为空;为空表示长期有效(由业务逻辑解释)",
"type": "string"
},
"discount_start_at": {
"description": "折扣开始时间:可为空;为空表示立即生效(由业务逻辑解释)",
"type": "string"
},
"discount_type": {
"$ref": "#/definitions/consts.DiscountType"
"description": "折扣类型none/percent/amount仅影响下单时成交价需写入订单快照",
"allOf": [
{
"$ref": "#/definitions/consts.DiscountType"
}
]
},
"discount_value": {
"description": "折扣值percent=0-100按业务校验amount=分none 时忽略",
"type": "integer"
},
"id": {
"description": "主键ID自增",
"type": "integer"
},
"price_amount": {
"description": "基础价格0 表示免费(可直接访问正片资源)",
"type": "integer"
},
"tenant_id": {
"description": "租户ID多租户隔离与内容归属一致",
"type": "integer"
},
"updated_at": {
"description": "更新时间:默认 now();更新价格/折扣时写入",
"type": "string"
},
"user_id": {
"description": "用户ID设置/更新价格的操作人(通常为 tenant_admin用于审计",
"type": "integer"
}
}
@@ -1504,42 +2056,218 @@ const docTemplate = `{
"type": "object",
"properties": {
"bucket": {
"description": "存储桶:对象所在 bucket与 provider 组合确定存储定位",
"type": "string"
},
"created_at": {
"description": "创建时间:默认 now();用于审计与排序",
"type": "string"
},
"deleted_at": {
"$ref": "#/definitions/gorm.DeletedAt"
"description": "软删除时间:非空表示已删除;对外接口需过滤",
"allOf": [
{
"$ref": "#/definitions/gorm.DeletedAt"
}
]
},
"id": {
"description": "主键ID自增仅用于内部关联",
"type": "integer"
},
"meta": {
"description": "元数据JSON包含 hash、duration、width、height、bitrate、codec 等;用于展示与计费/风控",
"type": "array",
"items": {
"type": "integer"
}
},
"object_key": {
"description": "对象键:对象在 bucket 内的 key不得暴露可长期复用的直链通过签名URL/token下发",
"type": "string"
},
"provider": {
"description": "存储提供方:例如 s3/minio/oss便于多存储扩展",
"type": "string"
},
"status": {
"$ref": "#/definitions/consts.MediaAssetStatus"
"description": "处理状态uploaded/processing/ready/failed/deletedready 才可被内容引用对外提供",
"allOf": [
{
"$ref": "#/definitions/consts.MediaAssetStatus"
}
]
},
"tenant_id": {
"description": "租户ID多租户隔离关键字段所有查询/写入必须限定 tenant_id",
"type": "integer"
},
"type": {
"$ref": "#/definitions/consts.MediaAssetType"
"description": "资源类型video/audio/image决定后续处理流程转码/缩略图/封面等)",
"allOf": [
{
"$ref": "#/definitions/consts.MediaAssetType"
}
]
},
"updated_at": {
"description": "更新时间:默认 now();更新状态/元数据时写入",
"type": "string"
},
"user_id": {
"description": "用户ID资源上传者用于审计与权限控制",
"type": "integer"
}
}
},
"models.Order": {
"type": "object",
"properties": {
"amount_discount": {
"description": "优惠金额amount_paid = amount_original - amount_discount下单时快照",
"type": "integer"
},
"amount_original": {
"description": "原价金额:分;未折扣前金额(用于展示与对账)",
"type": "integer"
},
"amount_paid": {
"description": "实付金额:分;从租户内余额扣款的金额(下单时快照)",
"type": "integer"
},
"created_at": {
"description": "创建时间:默认 now();用于审计与排序",
"type": "string"
},
"currency": {
"description": "币种:当前固定 CNY金额单位为分",
"allOf": [
{
"$ref": "#/definitions/consts.Currency"
}
]
},
"id": {
"description": "主键ID自增用于关联订单明细、账本流水、权益等",
"type": "integer"
},
"idempotency_key": {
"description": "幂等键:同一租户同一用户同一业务请求可用;用于防重复下单/重复扣款(建议由客户端生成)",
"type": "string"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/models.OrderItem"
}
},
"paid_at": {
"description": "支付/扣款完成时间:余额支付在 debit_purchase 成功后写入",
"type": "string"
},
"refund_forced": {
"description": "是否强制退款true 表示租户管理侧绕过时间窗执行退款(需审计)",
"type": "boolean"
},
"refund_operator_user_id": {
"description": "退款操作人用户ID租户管理员/系统;用于审计与追责",
"type": "integer"
},
"refund_reason": {
"description": "退款原因:后台/用户发起退款的原因说明;用于审计",
"type": "string"
},
"refunded_at": {
"description": "退款完成时间:退款落账成功后写入",
"type": "string"
},
"snapshot": {
"description": "订单快照JSON建议包含 content 标题/定价/折扣、请求来源等,避免改价影响历史展示",
"type": "array",
"items": {
"type": "integer"
}
},
"status": {
"description": "订单状态created/paid/refunding/refunded/canceled/failed状态变更需与账本/权益保持一致",
"allOf": [
{
"$ref": "#/definitions/consts.OrderStatus"
}
]
},
"tenant_id": {
"description": "租户ID多租户隔离关键字段所有查询/写入必须限定 tenant_id",
"type": "integer"
},
"type": {
"description": "订单类型content_purchase购买内容/topup充值当前默认 content_purchase",
"allOf": [
{
"$ref": "#/definitions/consts.OrderType"
}
]
},
"updated_at": {
"description": "更新时间:默认 now();状态变更/退款写入时更新",
"type": "string"
},
"user_id": {
"description": "用户ID下单用户buyer余额扣款与权益归属以该 user_id 为准",
"type": "integer"
}
}
},
"models.OrderItem": {
"type": "object",
"properties": {
"amount_paid": {
"description": "该行实付金额:分;通常等于订单 amount_paid单内容场景",
"type": "integer"
},
"content": {
"$ref": "#/definitions/models.Content"
},
"content_id": {
"description": "内容ID关联 contents.id用于生成/撤销 content_access",
"type": "integer"
},
"content_user_id": {
"description": "内容作者用户ID用于后续分成/对账扩展;当前可为 0 或写入内容创建者",
"type": "integer"
},
"created_at": {
"description": "创建时间:默认 now()",
"type": "string"
},
"id": {
"description": "主键ID自增",
"type": "integer"
},
"order": {
"$ref": "#/definitions/models.Order"
},
"order_id": {
"description": "订单ID关联 orders.id用于聚合订单明细",
"type": "integer"
},
"snapshot": {
"description": "内容快照JSON建议包含 title/price/discount 等,用于历史展示与审计",
"type": "array",
"items": {
"type": "integer"
}
},
"tenant_id": {
"description": "租户ID多租户隔离关键字段必须与 orders.tenant_id 一致",
"type": "integer"
},
"updated_at": {
"description": "更新时间:默认 now()",
"type": "string"
},
"user_id": {
"description": "用户ID下单用户buyer冗余字段用于查询加速与审计",
"type": "integer"
}
}
@@ -1594,6 +2322,10 @@ const docTemplate = `{
"balance": {
"type": "integer"
},
"balance_frozen": {
"description": "冻结余额:分/最小货币单位;下单冻结时从可用余额转入,最终扣款或回滚时转出;默认 0",
"type": "integer"
},
"created_at": {
"type": "string"
},
@@ -1718,7 +2450,7 @@ const docTemplate = `{
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/t/{tenantCode}/v1",
BasePath: "/",
Schemes: []string{},
Title: "ApiDoc",
Description: "Multi-tenant media platform backend API.",