feat: 添加作品购买人列表功能
Some checks failed
build quyun / Build (push) Failing after 1m26s

This commit is contained in:
2025-12-20 23:09:01 +08:00
parent e2be734b82
commit 65d40fa631
8 changed files with 223 additions and 7 deletions

View File

@@ -195,6 +195,21 @@ func (ctl *posts) Show(ctx fiber.Ctx, post *models.Post) (*PostItem, error) {
}, nil
}
// Buyers
//
// @Summary 作品购买人列表
// @Tags Admin Posts
// @Produce json
// @Param id path int64 true "作品 ID"
// @Param pagination query requests.Pagination false "分页参数"
// @Success 200 {object} requests.Pager{items=dto.PostBuyerItem} "成功"
// @Router /admin/v1/posts/:id/buyers [get]
// @Bind post path key(id) model(id)
// @Bind pagination query
func (ctl *posts) Buyers(ctx fiber.Ctx, post *models.Post, pagination *requests.Pagination) (*requests.Pager, error) {
return services.Posts.Buyers(ctx, post.ID, pagination)
}
// SendTo
//
// @Summary 赠送作品给用户

View File

@@ -114,6 +114,15 @@ func (r *Routes) Register(router fiber.Router) {
return models.PostQuery.WithContext(ctx).Where(field.NewUnsafeFieldRaw("id = ?", v)).First()
},
))
r.log.Debugf("Registering route: Get /admin/v1/posts/:id/buyers -> posts.Buyers")
router.Get("/admin/v1/posts/:id/buyers"[len(r.Path()):], DataFunc2(
r.posts.Buyers,
func(ctx fiber.Ctx) (*models.Post, error) {
v := fiber.Params[int](ctx, "id")
return models.PostQuery.WithContext(ctx).Where(field.NewUnsafeFieldRaw("id = ?", v)).First()
},
Query[requests.Pagination]("pagination"),
))
r.log.Debugf("Registering route: Post /admin/v1/posts -> posts.Create")
router.Post("/admin/v1/posts"[len(r.Path()):], Func1(
r.posts.Create,

View File

@@ -0,0 +1,17 @@
package dto
import "time"
type PostBuyerItem struct {
UserID int64 `json:"user_id"` // 用户 ID购买人唯一标识用于管理端关联用户详情/后续操作)
Username string `json:"username"` // 用户名(购买人展示名称;可能为空或默认值,前端需兼容)
Avatar string `json:"avatar"` // 用户头像 URL用于列表展示可能为空前端需提供占位图/降级展示)
Phone string `json:"phone"` // 用户手机号(管理端可见;用于客服联系/核对身份,可能为空)
BoughtAt time.Time `json:"bought_at"` // 购买时间(以 user_posts.created_at 为准;用于排序/审计)
Price int64 `json:"price"` // 购买价格(单位:分;-1 表示管理员赠送/免费,非负为实际支付金额)
}

View File

@@ -127,6 +127,62 @@ func (m *posts) Bought(ctx context.Context, userId int64, pagination *requests.P
}, nil
}
// Buyers 获取某个作品的购买人列表(管理端使用)
func (m *posts) Buyers(ctx context.Context, postID int64, pagination *requests.Pagination) (*requests.Pager, error) {
pagination.Format()
// 先分页查询购买记录,避免一次性拉取全量 user_posts 造成内存/延迟抖动
tblUserPost, queryUserPost := models.UserPostQuery.QueryContext(ctx)
queryUserPost = queryUserPost.
Where(tblUserPost.PostID.Eq(postID)).
Order(tblUserPost.CreatedAt.Desc())
userPosts, cnt, err := queryUserPost.FindByPage(int(pagination.Offset()), int(pagination.Limit))
if err != nil {
return nil, err
}
if len(userPosts) == 0 {
return &requests.Pager{
Items: []dto.PostBuyerItem{},
Total: cnt,
Pagination: *pagination,
}, nil
}
// 批量回表查询用户信息,避免 N+1
userIDs := lo.Uniq(lo.Map(userPosts, func(item *models.UserPost, _ int) int64 {
return item.UserID
}))
tblUser, queryUser := models.UserQuery.QueryContext(ctx)
users, err := queryUser.Where(tblUser.ID.In(userIDs...)).Find()
if err != nil {
return nil, err
}
userMap := lo.KeyBy(users, func(item *models.User) int64 { return item.ID })
items := make([]dto.PostBuyerItem, 0, len(userPosts))
for _, item := range userPosts {
user, ok := userMap[item.UserID]
if !ok {
continue
}
items = append(items, dto.PostBuyerItem{
UserID: user.ID,
Username: user.Username,
Avatar: user.Avatar,
Phone: user.Phone,
BoughtAt: item.CreatedAt,
Price: item.Price,
})
}
return &requests.Pager{
Items: items,
Total: cnt,
Pagination: *pagination,
}, nil
}
// GetPostsMapByIDs
func (m *posts) GetPostsMapByIDs(ctx context.Context, ids []int64) (map[int64]*models.Post, error) {
tbl, query := models.PostQuery.QueryContext(ctx)

View File

@@ -1,6 +1,8 @@
package services
import (
"quyun/v2/providers/ali"
"go.ipao.vip/atom"
"go.ipao.vip/atom/container"
"go.ipao.vip/atom/contracts"
@@ -52,8 +54,12 @@ func Provide(opts ...opt.Option) error {
}, atom.GroupInitial); err != nil {
return err
}
if err := container.Container.Provide(func() (*users, error) {
obj := &users{}
if err := container.Container.Provide(func(
smsNotifyClient *ali.SMSNotifyClient,
) (*users, error) {
obj := &users{
smsNotifyClient: smsNotifyClient,
}
if err := obj.Prepare(); err != nil {
return nil, err
}

View File

@@ -40,7 +40,6 @@ func NewSMSNotifyClient(cfg *Config) (*SMSNotifyClient, error) {
}
func (c *SMSNotifyClient) SendTo(phone string) (string, error) {
req := &dypnsapi20170525.SendSmsVerifyCodeRequest{
SignName: tea.String("速通互联验证码"),
TemplateCode: tea.String("100001"),