订单接口最小化与私有文件访问加固

- 订单列表使用独立最小 DTO 并分页,号主待办提供独立接口与统计
- 用户 token 增加版本控制,冻结/改密/退出即时撤销会话
- 移除 URL token 传参,SSE 与接口统一使用 HttpOnly Cookie
- 私有文件按上传归属与业务关联授权,收款凭证转私有访问并校验归属
- 公开商品接口返回最小字段,隐藏号主身份与内部状态
- 每日清理超过 30 天未关联业务的上传归属,上传归属失败时补偿删除对象
This commit is contained in:
yml2213
2026-08-16 21:47:46 +08:00
parent f48da14ed2
commit 85332df2bd
63 changed files with 1827 additions and 290 deletions
+112 -18
View File
@@ -11,32 +11,116 @@ import (
"gorm.io/gorm"
)
func (r *Repository) ListForUser(ctx context.Context, userID uint64) ([]OrderDTO, error) {
func (r *Repository) ListForUser(ctx context.Context, userID uint64, page, pageSize int) (*UserPaginatedResult, error) {
page, pageSize = normalizePagination(page, pageSize)
base := r.baseQuery(ctx).Where("o.renter_id = ? OR o.owner_id = ?", userID, userID)
var total int64
if err := r.db.WithContext(ctx).Table("rental_orders AS o").
Where("o.renter_id = ? OR o.owner_id = ?", userID, userID).
Count(&total).Error; err != nil {
return nil, err
}
var rows []orderRow
db := r.db.WithContext(ctx)
err := r.baseQuery(ctx).
Where("o.renter_id = ? OR o.owner_id = ?", userID, userID).
err := base.
Order("o.id DESC").
Offset((page - 1) * pageSize).
Limit(pageSize).
Scan(&rows).Error
if err != nil {
return nil, err
}
items := make([]OrderDTO, 0, len(rows))
items := make([]UserOrderListItemDTO, 0, len(rows))
paymentTimeoutMinutes := pendingPaymentTimeoutMinutes(db)
for _, row := range rows {
dto := row.toDTOForUser(userID)
applyPaymentDeadline(&dto, row.RentalOrder, paymentTimeoutMinutes)
if shouldAttachCheckout(row.Status) {
dto.Checkout = r.latestCheckoutDTOForUser(ctx, row.ID, userID, row.RentalOrder)
var deadline *time.Time
if row.Status == orderStatusPendingPayment && paymentTimeoutMinutes > 0 {
value := row.CreatedAt.Add(time.Duration(paymentTimeoutMinutes) * time.Minute)
deadline = &value
}
items = append(items, dto)
items = append(items, row.toUserListItem(userID, deadline))
}
return items, nil
return &UserPaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
}
func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*PaginatedResult, error) {
func (r *Repository) ListSellerHandoffs(ctx context.Context, userID uint64, query SellerHandoffQuery) (*SellerHandoffPaginatedResult, error) {
page, pageSize := normalizePagination(query.Page, query.PageSize)
base := r.baseQuery(ctx).
Where("o.owner_id = ?", userID).
Where("o.status IN ?", sellerHandoffStatuses())
if query.Status != "" {
base = base.Where("o.status = ?", query.Status)
}
var total int64
if err := base.Session(&gorm.Session{}).Count(&total).Error; err != nil {
return nil, err
}
var rows []orderRow
if err := base.Order("o.id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Scan(&rows).Error; err != nil {
return nil, err
}
items := make([]UserOrderListItemDTO, 0, len(rows))
for _, row := range rows {
items = append(items, row.toUserListItem(userID, nil))
}
metrics, err := r.sellerHandoffMetrics(ctx, userID)
if err != nil {
return nil, err
}
return &SellerHandoffPaginatedResult{
Items: items,
Total: total,
Page: page,
PageSize: pageSize,
Metrics: metrics,
}, nil
}
func (r *Repository) sellerHandoffMetrics(ctx context.Context, userID uint64) (SellerHandoffMetrics, error) {
base := r.db.WithContext(ctx).Table("rental_orders AS o").Where("o.owner_id = ?", userID)
var metrics SellerHandoffMetrics
if err := base.Where("o.status = ? AND o.handoff_status = ?", orderStatusPendingHandoff, handoffStatusPendingOwner).Count(&metrics.PendingHandoff).Error; err != nil {
return SellerHandoffMetrics{}, err
}
if err := r.db.WithContext(ctx).Table("rental_orders AS o").Where("o.owner_id = ? AND o.status = ?", userID, orderStatusPendingCheckoutConfirm).Count(&metrics.PendingCheckout).Error; err != nil {
return SellerHandoffMetrics{}, err
}
if err := r.db.WithContext(ctx).Table("rental_orders AS o").Where("o.owner_id = ? AND o.status IN ?", userID, sellerHandoffAbnormalStatuses()).Count(&metrics.Abnormal).Error; err != nil {
return SellerHandoffMetrics{}, err
}
return metrics, nil
}
func sellerHandoffStatuses() []string {
return []string{
orderStatusPendingHandoff,
orderStatusRenting,
orderStatusOverdue,
orderStatusPendingCheckoutConfirm,
orderStatusPendingCheckoutAccept,
orderStatusCheckoutDisputing,
"disputing",
orderStatusAbnormal,
}
}
func sellerHandoffAbnormalStatuses() []string {
return []string{orderStatusOverdue, orderStatusCheckoutDisputing, "disputing", orderStatusAbnormal}
}
func isSellerHandoffStatus(status string) bool {
for _, candidate := range sellerHandoffStatuses() {
if status == candidate {
return true
}
}
return false
}
func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*AdminOrderListPaginatedResult, error) {
var total int64
db := r.db.WithContext(ctx)
countDB := applyAdminOrderFilters(r.adminOrderJoinQuery(ctx), query)
if err := countDB.Count(&total).Error; err != nil {
return nil, err
@@ -61,15 +145,12 @@ func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*Pag
return nil, err
}
items := make([]OrderDTO, 0, len(rows))
paymentTimeoutMinutes := pendingPaymentTimeoutMinutes(db)
items := make([]AdminOrderListItemDTO, 0, len(rows))
for _, row := range rows {
dto := row.toAdminDTO()
applyPaymentDeadline(&dto, row.RentalOrder, paymentTimeoutMinutes)
items = append(items, dto)
items = append(items, row.toAdminListItem())
}
return &PaginatedResult{
return &AdminOrderListPaginatedResult{
Items: items,
Total: total,
Page: page,
@@ -77,6 +158,19 @@ func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*Pag
}, nil
}
func normalizePagination(page, pageSize int) (int, int) {
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
if pageSize > 100 {
pageSize = 100
}
return page, pageSize
}
func applyAdminOrderFilters(db *gorm.DB, query AdminOrderQuery) *gorm.DB {
if query.Status != "" {
db = db.Where("o.status = ?", query.Status)