feat: tenant content publish

This commit is contained in:
2025-12-25 14:29:16 +08:00
parent a66c0d9b90
commit 6542c71ec0
15 changed files with 1082 additions and 4 deletions

View File

@@ -95,6 +95,44 @@ func (*contentAdmin) create(ctx fiber.Ctx, tenant *models.Tenant, tenantUser *mo
return services.Content.Create(ctx, tenant.ID, tenantUser.UserID, form)
}
// publish
//
// @Summary 内容发布(创建+绑定资源+定价)
// @Tags Tenant
// @Accept json
// @Produce json
// @Param tenantCode path string true "Tenant Code"
// @Param form body dto.ContentPublishForm true "Form"
// @Success 200 {object} dto.ContentPublishResponse
//
// @Router /t/:tenantCode/v1/admin/contents/publish [post]
// @Bind tenant local key(tenant)
// @Bind tenantUser local key(tenant_user)
// @Bind form body
func (*contentAdmin) publish(ctx fiber.Ctx, tenant *models.Tenant, tenantUser *models.TenantUser, form *dto.ContentPublishForm) (*dto.ContentPublishResponse, error) {
if err := requireTenantAdmin(tenantUser); err != nil {
return nil, err
}
log.WithFields(log.Fields{
"tenant_id": tenant.ID,
"user_id": tenantUser.UserID,
}).Info("tenant.admin.contents.publish")
res, err := services.Content.Publish(ctx.Context(), tenant.ID, tenantUser.UserID, form)
if err != nil {
return nil, err
}
return &dto.ContentPublishResponse{
Content: res.Content,
Price: res.Price,
CoverAssets: res.CoverAssets,
MainAssets: res.MainAssets,
ContentTypes: res.ContentTypes,
}, nil
}
// update
//
// @Summary 更新内容(标题/描述/状态等)

View File

@@ -0,0 +1,55 @@
package dto
import (
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
)
// ContentPublishForm 租户管理员提交“内容发布”表单(创建内容 + 绑定资源 + 定价)。
// 说明:
// - 内容类型支持组合:文字/音频/视频/多图可同时存在;
// - 文字内容通过 Detail 是否为空来判断;
// - 音频/视频/多图通过对应资源列表是否为空来判断(资源需为 ready 且属于当前租户)。
type ContentPublishForm struct {
// Title 标题:用于列表展示与搜索;必填。
Title string `json:"title,omitempty"`
// Summary 简介:用于列表/卡片展示的短文本;可选,建议 <= 256 字符。
Summary string `json:"summary,omitempty"`
// Detail 详细:用于详情页的长文本;可选;当非空时视为“文字内容”类型存在。
Detail string `json:"detail,omitempty"`
// Tags 标签:用于分类/检索;字符串数组;会做 trim/去重;可为空。
Tags []string `json:"tags,omitempty"`
// CoverAssetIDs 展示图(封面图)资源 ID 列表1-3 张;每个资源必须为 image/main/ready。
CoverAssetIDs []int64 `json:"cover_asset_ids,omitempty"`
// AudioAssetIDs 音频资源 ID 列表:可为空;每个资源必须为 audio/main/ready。
AudioAssetIDs []int64 `json:"audio_asset_ids,omitempty"`
// VideoAssetIDs 视频资源 ID 列表:可为空;每个资源必须为 video/main/ready。
VideoAssetIDs []int64 `json:"video_asset_ids,omitempty"`
// ImageAssetIDs 多图内容资源 ID 列表:可为空;每个资源必须为 image/main/ready数量 >= 2 时视为“多图内容”类型存在。
ImageAssetIDs []int64 `json:"image_asset_ids,omitempty"`
// PriceAmount 价格单位为分0 表示免费;必填(前端可默认填 0
PriceAmount int64 `json:"price_amount,omitempty"`
// Currency 币种:当前固定为 CNY可不传后端默认 CNY
Currency consts.Currency `json:"currency,omitempty"`
// Visibility 可见性:控制“详情页”可见范围;默认 tenant_only。
Visibility consts.ContentVisibility `json:"visibility,omitempty"`
// PreviewSeconds 试看秒数:仅对 preview 资源生效;默认 60必须为正整数。
PreviewSeconds *int32 `json:"preview_seconds,omitempty"`
}
// ContentPublishResponse 内容发布结果(便于前端一次性拿到核心信息)。
type ContentPublishResponse struct {
// Content 内容主体(包含标题/简介/详细/状态等)。
Content *models.Content `json:"content"`
// Price 定价信息(单位分)。
Price *models.ContentPrice `json:"price"`
// CoverAssets 封面图绑定结果role=cover
CoverAssets []*models.ContentAsset `json:"cover_assets,omitempty"`
// MainAssets 主资源绑定结果role=main可能包含音频/视频/图片)。
MainAssets []*models.ContentAsset `json:"main_assets,omitempty"`
// ContentTypes 内容类型列表text/audio/video/image/multi_image用于前端展示
ContentTypes []string `json:"content_types,omitempty"`
}

View File

@@ -112,6 +112,13 @@ func (r *Routes) Register(router fiber.Router) {
PathParam[int64]("contentID"),
Body[dto.ContentAssetAttachForm]("form"),
))
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/admin/contents/publish -> contentAdmin.publish")
router.Post("/t/:tenantCode/v1/admin/contents/publish"[len(r.Path()):], DataFunc3(
r.contentAdmin.publish,
Local[*models.Tenant]("tenant"),
Local[*models.TenantUser]("tenant_user"),
Body[dto.ContentPublishForm]("form"),
))
r.log.Debugf("Registering route: Put /t/:tenantCode/v1/admin/contents/:contentID/price -> contentAdmin.upsertPrice")
router.Put("/t/:tenantCode/v1/admin/contents/:contentID/price"[len(r.Path()):], DataFunc4(
r.contentAdmin.upsertPrice,