chore: stabilize lint and verify builds

This commit is contained in:
2026-02-06 11:51:32 +08:00
parent edede17880
commit 1782f64417
114 changed files with 3032 additions and 1345 deletions

View File

@@ -5,7 +5,7 @@ import (
"quyun/v2/database/models"
"github.com/sirupsen/logrus"
logrus "github.com/sirupsen/logrus"
)
// @provider

View File

@@ -2,7 +2,7 @@ package services
import (
"context"
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
@@ -129,6 +129,7 @@ func (s *common) buildObjectKey(tenant *models.Tenant, hash, filename string) st
tenantUUID = tenant.UUID.String()
}
ext := strings.ToLower(filepath.Ext(filename))
return path.Join("quyun", tenantUUID, hash+ext)
}
@@ -138,6 +139,7 @@ func (s *common) loadUploadMeta(tempDir string) (*UploadMeta, error) {
if errors.Is(err, os.ErrNotExist) {
return nil, errorx.ErrRecordNotFound.WithCause(err).WithMsg("上传会话不存在")
}
return nil, errorx.ErrInternalError.WithCause(err)
}
defer metaFile.Close()
@@ -146,6 +148,7 @@ func (s *common) loadUploadMeta(tempDir string) (*UploadMeta, error) {
if err := json.NewDecoder(metaFile).Decode(&meta); err != nil {
return nil, errorx.ErrDataCorrupted.WithCause(err).WithMsg("上传会话元信息损坏")
}
return &meta, nil
}
@@ -154,6 +157,7 @@ func (s *common) verifyUploadOwner(meta *UploadMeta, tenantID, userID int64) err
if meta.TenantID != tenantID || meta.UserID != userID {
return errorx.ErrForbidden.WithMsg("无权访问该上传会话")
}
return nil
}
@@ -165,8 +169,10 @@ func (s *common) resolveTenant(ctx context.Context, tenantID, userID int64) (*mo
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound.WithMsg("租户不存在")
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return tenant, nil
}
if userID == 0 {
@@ -178,8 +184,10 @@ func (s *common) resolveTenant(ctx context.Context, tenantID, userID int64) (*mo
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return tenant, nil
}
@@ -188,6 +196,7 @@ func (s *common) uploadTempDir(localPath string, tenantID int64, uploadID string
if tenantID > 0 {
tenantKey = strconv.FormatInt(tenantID, 10)
}
return filepath.Join(localPath, "temp", tenantKey, uploadID)
}
@@ -250,6 +259,7 @@ func (s *common) UploadPart(ctx context.Context, tenantID, userID int64, file *m
if _, err = io.Copy(dst, src); err != nil {
return errorx.ErrInternalError.WithCause(err)
}
return nil
}
@@ -292,7 +302,7 @@ func (s *common) CompleteUpload(ctx context.Context, tenantID, userID int64, for
}
defer dst.Close()
hasher := md5.New()
hasher := sha256.New()
var totalSize int64
for _, partNum := range parts {
@@ -341,6 +351,7 @@ func (s *common) CompleteUpload(ctx context.Context, tenantID, userID int64, for
myExisting, err := myQuery.First()
if err == nil {
os.RemoveAll(tempDir)
return s.composeUploadResult(myExisting), nil
}
asset = &models.MediaAsset{
@@ -431,6 +442,7 @@ func (s *common) AbortUpload(ctx context.Context, tenantID, userID int64, upload
if err := s.verifyUploadOwner(meta, tenantID, userID); err != nil {
return err
}
return os.RemoveAll(tempDir)
}
@@ -452,7 +464,7 @@ func (s *common) Upload(
localPath := s.storage.Config.LocalPath
if localPath == "" {
localPath = "./storage" // Fallback
localPath = "./storage"
}
tmpDir := filepath.Join(localPath, "temp", "uploads", uuid.NewString())
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
@@ -465,8 +477,7 @@ func (s *common) Upload(
return nil, errorx.ErrInternalError.WithCause(err).WithMsg("failed to create destination file")
}
// Hash calculation while copying
hasher := md5.New()
hasher := sha256.New()
size, err := io.Copy(io.MultiWriter(dst, hasher), src)
dst.Close() // Close immediately to allow removal if needed
if err != nil {
@@ -584,6 +595,7 @@ func (s *common) GetAssetURL(objectKey string) string {
return ""
}
url, _ := s.storage.SignURL("GET", objectKey, 1*time.Hour)
return url
}
@@ -591,6 +603,7 @@ func (s *common) initialMediaStatus(mediaType consts.MediaAssetType) consts.Medi
if s.needsMediaProcess(mediaType) {
return consts.MediaAssetStatusUploaded
}
return consts.MediaAssetStatusReady
}
@@ -616,6 +629,7 @@ func (s *common) enqueueMediaProcess(ctx context.Context, tenantID int64, asset
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -626,6 +640,7 @@ func (s *common) enqueueMediaProcess(ctx context.Context, tenantID int64, asset
if err := s.job.Add(arg); err != nil {
return errorx.ErrInternalError.WithCause(err).WithMsg("添加媒体处理任务失败")
}
return nil
}
@@ -656,8 +671,10 @@ func retryCriticalWrite(ctx context.Context, fn func() error) error {
} else {
time.Sleep(backoffs[attempt])
}
continue
}
return nil
}
@@ -669,6 +686,7 @@ func shouldRetryWrite(err error) bool {
if errors.As(err, &appErr) {
return false
}
return isTransientDBError(err)
}
@@ -680,5 +698,6 @@ func isTransientDBError(err error) bool {
return true
}
}
return false
}

View File

@@ -160,6 +160,7 @@ func (s *content) Get(ctx context.Context, tenantID, userID, id int64) (*content
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -288,6 +289,7 @@ func (s *content) ListComments(ctx context.Context, tenantID, userID, id int64,
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
if err := s.ensureContentReadable(ctx, userID, content); err != nil {
@@ -388,6 +390,7 @@ func (s *content) CreateComment(
if err := models.CommentQuery.WithContext(ctx).Create(comment); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -415,6 +418,7 @@ func (s *content) LikeComment(ctx context.Context, tenantID, userID, id int64) e
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound
}
return errorx.ErrDatabaseError.WithCause(err)
}
if err := s.ensureContentReadable(ctx, userID, c); err != nil {
@@ -435,6 +439,7 @@ func (s *content) LikeComment(ctx context.Context, tenantID, userID, id int64) e
}
_, err := tx.Comment.WithContext(ctx).Where(tx.Comment.ID.Eq(id)).UpdateSimple(tx.Comment.Likes.Add(1))
return err
})
if err != nil {
@@ -444,6 +449,7 @@ func (s *content) LikeComment(ctx context.Context, tenantID, userID, id int64) e
if Notification != nil {
_ = Notification.Send(ctx, tenantID, cm.UserID, "interaction", "评论点赞", "有人点赞了您的评论")
}
return nil
}
@@ -493,6 +499,7 @@ func (s *content) GetLibrary(ctx context.Context, tenantID, userID int64) ([]use
dto.IsPurchased = true
data = append(data, dto)
}
return data, nil
}
@@ -630,6 +637,7 @@ func (s *content) ListTopics(ctx context.Context, tenantID int64) ([]content_dto
Cover: cover,
})
}
return topics, nil
}
@@ -683,6 +691,7 @@ func (s *content) toContentItemDTO(item *models.Content, price float64, authorIs
for _, asset := range item.ContentAssets {
if asset.Asset != nil && asset.Asset.Type == consts.MediaAssetTypeImage {
dto.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
break
}
}
@@ -745,6 +754,7 @@ func (s *content) ensureContentReadable(ctx context.Context, userID int64, item
if !isMember {
return errorx.ErrForbidden.WithMsg("内容仅限本店铺成员访问")
}
return nil
default:
return nil
@@ -765,6 +775,7 @@ func (s *content) ensureContentOwnerOrAdmin(ctx context.Context, userID int64, i
if !isAdmin {
return errorx.ErrForbidden.WithMsg(msg)
}
return nil
}
@@ -780,6 +791,7 @@ func (s *content) isTenantAdmin(ctx context.Context, tenantID, userID int64) (bo
if err != nil {
return false, errorx.ErrDatabaseError.WithCause(err)
}
return exists, nil
}
@@ -792,6 +804,7 @@ func (s *content) isTenantMember(ctx context.Context, tenantID, userID int64) (b
if err != nil {
return false, errorx.ErrDatabaseError.WithCause(err)
}
return exists, nil
}
@@ -806,6 +819,7 @@ func (s *content) toMediaURLs(assets []*models.ContentAsset) []content_dto.Media
})
}
}
return urls
}
@@ -850,8 +864,10 @@ func (s *content) addInteract(ctx context.Context, tenantID, userID, contentId i
contentQuery = contentQuery.Where(tx.Content.TenantID.Eq(tenantID))
}
_, err := contentQuery.UpdateSimple(tx.Content.Likes.Add(1))
return err
}
return nil
})
if err != nil {
@@ -868,6 +884,7 @@ func (s *content) addInteract(ctx context.Context, tenantID, userID, contentId i
}
_ = Notification.Send(ctx, tenantID, c.UserID, "interaction", "新的"+actionName, "有人"+actionName+"了您的作品: "+c.Title)
}
return nil
}
@@ -894,8 +911,10 @@ func (s *content) removeInteract(ctx context.Context, tenantID, userID, contentI
contentQuery = contentQuery.Where(tx.Content.TenantID.Eq(tenantID))
}
_, err := contentQuery.UpdateSimple(tx.Content.Likes.Sub(1))
return err
}
return nil
})
}
@@ -940,5 +959,6 @@ func (s *content) getInteractList(ctx context.Context, tenantID, userID int64, t
for _, item := range list {
data = append(data, s.toContentItemDTO(item, 0, false))
}
return data, nil
}

View File

@@ -77,6 +77,7 @@ func (s *coupon) ListUserCoupons(
res = append(res, s.composeUserCouponItem(v, c, finalStatus))
}
return res, nil
}
@@ -126,6 +127,7 @@ func (s *coupon) ListAvailable(
return nil, err
}
}
continue
}
@@ -165,6 +167,7 @@ func (s *coupon) Receive(
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if tenantID > 0 && coupon.TenantID != tenantID {
@@ -185,6 +188,7 @@ func (s *coupon) Receive(
if err == nil {
item := s.composeUserCouponItem(existing, coupon, existing.Status)
result = &item
return nil
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
@@ -214,11 +218,13 @@ func (s *coupon) Receive(
item := s.composeUserCouponItem(uc, coupon, consts.UserCouponStatusUnused)
result = &item
return nil
})
if err != nil {
return nil, err
}
return result, nil
}
@@ -286,6 +292,7 @@ func (s *coupon) Create(
}
item := s.composeCouponItem(coupon)
return &item, nil
}
@@ -313,6 +320,7 @@ func (s *coupon) Update(
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -402,6 +410,7 @@ func (s *coupon) Update(
if len(updates) == 0 {
resultItem := s.composeCouponItem(coupon)
result = &resultItem
return nil
}
@@ -415,11 +424,13 @@ func (s *coupon) Update(
}
resultItem := s.composeCouponItem(updated)
result = &resultItem
return nil
})
if err != nil {
return nil, err
}
return result, nil
}
@@ -440,9 +451,11 @@ func (s *coupon) Get(
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
item := s.composeCouponItem(coupon)
return &item, nil
}
@@ -557,6 +570,7 @@ func (s *coupon) Grant(
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -613,11 +627,13 @@ func (s *coupon) Grant(
}
granted++
}
return nil
})
if err != nil {
return 0, err
}
return granted, nil
}
@@ -650,6 +666,7 @@ func (s *coupon) Validate(ctx context.Context, tenantID, userID, userCouponID, a
if err := s.markUserCouponExpired(ctx, uc.ID); err != nil {
return 0, err
}
return 0, errorx.ErrBusinessLogic.WithMsg("优惠券已过期")
}
@@ -685,6 +702,7 @@ func (s *coupon) MarkUsed(ctx context.Context, tx *models.Query, tenantID, userC
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if uc.Status != consts.UserCouponStatusUnused {
@@ -696,6 +714,7 @@ func (s *coupon) MarkUsed(ctx context.Context, tx *models.Query, tenantID, userC
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("优惠券信息缺失")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if tenantID > 0 && c.TenantID != tenantID {
@@ -752,6 +771,7 @@ func (s *coupon) fetchCouponMap(ctx context.Context, tenantID int64, list []*mod
for _, c := range coupons {
couponMap[c.ID] = c
}
return couponMap, nil
}
@@ -770,6 +790,7 @@ func (s *coupon) composeUserCouponItem(uc *models.UserCoupon, c *models.Coupon,
item.StartAt = s.formatTime(c.StartAt)
item.EndAt = s.formatTime(c.EndAt)
}
return item
}
@@ -801,6 +822,7 @@ func (s *coupon) markUserCouponExpired(ctx context.Context, userCouponID int64)
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -815,6 +837,7 @@ func (s *coupon) isCouponActive(c *models.Coupon, now time.Time) bool {
if !c.EndAt.IsZero() && now.After(c.EndAt) {
return false
}
return true
}
@@ -827,6 +850,7 @@ func (s *coupon) parseTime(val string) (time.Time, error) {
if err != nil {
return time.Time{}, errorx.ErrInvalidFormat.WithMsg("时间格式无效")
}
return tm, nil
}
@@ -839,6 +863,7 @@ func (s *coupon) parseCouponType(val string) (consts.CouponType, error) {
if err != nil {
return "", errorx.ErrInvalidParameter.WithMsg("优惠券类型无效")
}
return couponType, nil
}
@@ -858,6 +883,7 @@ func (s *coupon) validateCouponValue(typ consts.CouponType, value, maxDiscount i
default:
return errorx.ErrInvalidParameter.WithMsg("优惠券类型无效")
}
return nil
}
@@ -865,5 +891,6 @@ func (s *coupon) formatTime(t time.Time) string {
if t.IsZero() {
return ""
}
return t.Format(time.RFC3339)
}

View File

@@ -104,6 +104,7 @@ func (s *creator) Dashboard(ctx context.Context, tenantID, userID int64) (*creat
PendingRefunds: int(pendingRefunds),
NewMessages: 0,
}
return stats, nil
}
@@ -482,6 +483,7 @@ func (s *creator) DeleteContent(ctx context.Context, tenantID, userID, id int64)
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -505,6 +507,7 @@ func (s *creator) GetContent(ctx context.Context, tenantID, userID, id int64) (*
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -556,6 +559,7 @@ func (s *creator) GetContent(ctx context.Context, tenantID, userID, id int64) (*
})
}
}
return dto, nil
}
@@ -691,6 +695,7 @@ func (s *creator) ListOrders(
for _, ca := range c.ContentAssets {
if ca.Role == consts.ContentAssetRoleCover && ca.Asset != nil {
cover = Common.GetAssetURL(ca.Asset.ObjectKey)
break
}
}
@@ -708,6 +713,7 @@ func (s *creator) ListOrders(
Cover: cover,
})
}
return data, nil
}
@@ -738,6 +744,7 @@ func (s *creator) ProcessRefund(ctx context.Context, tenantID, userID, id int64,
Status: consts.OrderStatusPaid,
RefundReason: form.Reason, // Store reject reason? Or clear it?
})
return err
}
@@ -825,6 +832,7 @@ func (s *creator) GetSettings(ctx context.Context, tenantID, userID int64) (*cre
return nil, errorx.ErrRecordNotFound
}
cfg := t.Config.Data()
return &creator_dto.Settings{
ID: t.ID,
Name: t.Name,
@@ -856,6 +864,7 @@ func (s *creator) UpdateSettings(ctx context.Context, tenantID, userID int64, fo
Name: form.Name,
Config: types.NewJSONType(cfg),
})
return err
}
@@ -885,6 +894,7 @@ func (s *creator) ListPayoutAccounts(ctx context.Context, tenantID, userID int64
ReviewReason: v.ReviewReason,
})
}
return data, nil
}
@@ -907,6 +917,7 @@ func (s *creator) AddPayoutAccount(ctx context.Context, tenantID, userID int64,
if err := models.PayoutAccountQuery.WithContext(ctx).Create(pa); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -921,6 +932,7 @@ func (s *creator) RemovePayoutAccount(ctx context.Context, tenantID, userID, id
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -956,6 +968,7 @@ func (s *creator) Withdraw(ctx context.Context, tenantID, userID int64, form *cr
if account.Status == consts.PayoutAccountStatusRejected && reason != "" {
return errorx.ErrPreconditionFailed.WithMsg("收款账户审核未通过:" + reason)
}
return errorx.ErrPreconditionFailed.WithMsg("收款账户未审核通过")
}
@@ -1036,11 +1049,13 @@ func (s *creator) getTenantID(ctx context.Context, tenantID, userID int64) (int6
if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, errorx.ErrPermissionDenied.WithMsg("非创作者")
}
return 0, errorx.ErrDatabaseError.WithCause(err)
}
if tenantID > 0 && t.ID != tenantID {
return 0, errorx.ErrPermissionDenied.WithMsg("无权限访问该租户")
}
return t.ID, nil
}
@@ -1048,6 +1063,7 @@ func (s *creator) formatTime(t time.Time) string {
if t.IsZero() {
return ""
}
return t.Format(time.RFC3339)
}
@@ -1094,6 +1110,7 @@ func (s *creator) validateContentAssets(
if asset.TenantID == 0 && asset.UserID == userID {
continue
}
return errorx.ErrForbidden.WithMsg("素材不属于当前租户")
}

View File

@@ -252,6 +252,7 @@ func (s *creator) ExportReport(
}
filename := fmt.Sprintf("report_overview_%s.csv", time.Now().Format("20060102_150405"))
return &creator_dto.ReportExportResponse{
Filename: filename,
MimeType: "text/csv",
@@ -287,6 +288,7 @@ func (s *creator) orderAggregate(
if err != nil {
return 0, 0, errorx.ErrDatabaseError.WithCause(err)
}
return total.Count, total.Amount, nil
}
@@ -316,6 +318,7 @@ func (s *creator) orderSeries(
key := row.Day.Format("2006-01-02")
result[key] = row
}
return result, nil
}
@@ -330,6 +333,7 @@ func (s *creator) contentCount(ctx context.Context, tenantID int64) (int64, erro
if err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
@@ -343,6 +347,7 @@ func (s *creator) contentCreatedAggregate(ctx context.Context, tenantID int64, r
if err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
@@ -358,6 +363,7 @@ func (s *creator) contentCreatedSeries(ctx context.Context, tenantID int64, rg r
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return buildCountSeries(rows), nil
}
@@ -378,6 +384,7 @@ func (s *creator) contentActionAggregate(
if err := query.Scan(&total).Error; err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
@@ -400,6 +407,7 @@ func (s *creator) contentActionSeries(
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return buildCountSeries(rows), nil
}
@@ -413,6 +421,7 @@ func (s *creator) commentAggregate(ctx context.Context, tenantID int64, rg repor
if err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
@@ -428,6 +437,7 @@ func (s *creator) commentSeries(ctx context.Context, tenantID int64, rg reportRa
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return buildCountSeries(rows), nil
}
@@ -437,6 +447,7 @@ func buildCountSeries(rows []reportCountRow) map[string]int64 {
key := row.Day.Format("2006-01-02")
result[key] = row.Count
}
return result
}
@@ -475,6 +486,7 @@ func (s *creator) normalizeReportRange(filter *creator_dto.ReportOverviewFilter)
}
endNext := endDay.AddDate(0, 0, 1)
return reportRange{
startDay: startDay,
endDay: endDay,

View File

@@ -71,6 +71,7 @@ func (s *notification) MarkRead(ctx context.Context, tenantID, userID, id int64)
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -84,6 +85,7 @@ func (s *notification) MarkAllRead(ctx context.Context, tenantID, userID int64)
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -108,7 +110,9 @@ func (s *notification) Send(ctx context.Context, tenantID, userID int64, typ, ti
if err := models.NotificationQuery.WithContext(ctx).Create(n); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
return s.job.Add(arg)
}

View File

@@ -51,6 +51,7 @@ func (s *order) ListUserOrders(ctx context.Context, tenantID, userID int64, stat
if err != nil {
return nil, err
}
return data, nil
}
@@ -75,6 +76,7 @@ func (s *order) GetUserOrder(ctx context.Context, tenantID, userID, id int64) (*
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -82,6 +84,7 @@ func (s *order) GetUserOrder(ctx context.Context, tenantID, userID, id int64) (*
if err != nil {
return nil, err
}
return &dto, nil
}
@@ -204,6 +207,7 @@ func (s *order) Create(
if _, ok := err.(*errorx.AppError); ok {
return nil, err
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -241,55 +245,38 @@ func (s *order) Pay(
switch form.Method {
case "balance":
return s.payWithBalance(ctx, o)
case "alipay", "external":
// mock external: 标记已支付,避免前端卡住
if err := s.settleOrder(ctx, o, "external", ""); err != nil {
if _, ok := err.(*errorx.AppError); ok {
return nil, err
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return &transaction_dto.OrderPayResponse{PayParams: "mock_pay_params"}, nil
default:
return nil, errorx.ErrBadRequest.WithMsg("unsupported payment method")
}
}
// ProcessExternalPayment handles callback from payment gateway
func (s *order) ProcessExternalPayment(ctx context.Context, tenantID, orderID int64, externalID string) error {
o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(orderID)).First()
if err != nil {
return errorx.ErrRecordNotFound
}
if tenantID > 0 && o.TenantID > 0 && o.TenantID != tenantID {
return errorx.ErrForbidden.WithMsg("租户不匹配")
}
if o.Status != consts.OrderStatusCreated {
return nil // Already processed idempotency
}
return s.settleOrder(ctx, o, "external", externalID)
}
func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transaction_dto.OrderPayResponse, error) {
err := s.settleOrder(ctx, o, "balance", "")
err := s.settleOrder(ctx, o, "balance")
if err != nil {
if _, ok := err.(*errorx.AppError); ok {
return nil, err
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return &transaction_dto.OrderPayResponse{
PayParams: "balance_paid",
}, nil
return &transaction_dto.OrderPayResponse{Status: string(consts.OrderStatusPaid)}, nil
}
func (s *order) settleOrder(ctx context.Context, o *models.Order, method, externalID string) error {
func (s *order) settleRechargeOrder(ctx context.Context, order *models.Order) error {
if order == nil {
return errorx.ErrInvalidParameter.WithMsg("充值订单不存在")
}
return s.settleOrder(ctx, order, "recharge")
}
func (s *order) settleOrder(ctx context.Context, o *models.Order, method string) error {
var tenantOwnerID int64
// 关键结算事务:遇到数据库冲突/死锁时短暂退避重试,避免支付状态卡死。
err := retryCriticalWrite(ctx, func() error {
tenantOwnerID = 0
return models.Q.Transaction(func(tx *models.Query) error {
// 1. Handle Balance Updates
if o.Type == consts.OrderTypeRecharge {
@@ -320,7 +307,6 @@ func (s *order) settleOrder(ctx context.Context, o *models.Order, method, extern
PaidAt: now,
UpdatedAt: now,
})
if err != nil {
return err
}
@@ -409,6 +395,7 @@ func (s *order) settleOrder(ctx context.Context, o *models.Order, method, extern
_ = Notification.Send(ctx, o.TenantID, tenantOwnerID, "order", "新的订单", "您的店铺有新的订单,收入已入账。")
}
}
return nil
}
@@ -418,6 +405,7 @@ func (s *order) Status(ctx context.Context, tenantID, userID, id int64) (*transa
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
if userID > 0 && o.UserID != userID {
@@ -476,6 +464,7 @@ func (s *order) composeOrderDTO(ctx context.Context, o *models.Order) (user_dto.
for _, asset := range c.ContentAssets {
if asset.Role == consts.ContentAssetRoleCover && asset.Asset != nil {
ci.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
break
}
}
@@ -591,6 +580,7 @@ func (s *order) composeOrderListDTO(ctx context.Context, orders []*models.Order)
for _, asset := range c.ContentAssets {
if asset.Role == consts.ContentAssetRoleCover && asset.Asset != nil {
ci.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
break
}
}

View File

@@ -38,116 +38,10 @@ func Test_Order(t *testing.T) {
func (s *OrderTestSuite) Test_PurchaseFlow() {
Convey("Purchase Flow", s.T(), func() {
ctx := s.T().Context()
tenantID := int64(0)
database.Truncate(ctx, s.DB,
models.TableNameOrder, models.TableNameOrderItem, models.TableNameUser,
models.TableNameContent, models.TableNameContentPrice, models.TableNameTenant,
models.TableNameContentAccess, models.TableNameTenantLedger,
)
// 1. Setup Data
// Creator
creator := &models.User{Username: "creator", Phone: "13800000001"}
models.UserQuery.WithContext(ctx).Create(creator)
// Tenant
tenant := &models.Tenant{
UserID: creator.ID,
Name: "Music Shop",
Code: "shop1",
Status: consts.TenantStatusVerified,
}
models.TenantQuery.WithContext(ctx).Create(tenant)
tenantID = tenant.ID
// Content
content := &models.Content{
TenantID: tenant.ID,
UserID: creator.ID,
Title: "Song A",
Status: consts.ContentStatusPublished,
}
models.ContentQuery.WithContext(ctx).Create(content)
// Price (10.00 CNY = 1000 cents)
price := &models.ContentPrice{
TenantID: tenant.ID,
ContentID: content.ID,
PriceAmount: 1000,
Currency: consts.CurrencyCNY,
}
models.ContentPriceQuery.WithContext(ctx).Create(price)
// Buyer
buyer := &models.User{Username: "buyer", Phone: "13900000001", Balance: 2000} // Has 20.00
models.UserQuery.WithContext(ctx).Create(buyer)
// buyerCtx := context.WithValue(ctx, consts.CtxKeyUser, buyer.ID)
Convey("should create and pay order successfully", func() {
// Step 1: Create Order
form := &order_dto.OrderCreateForm{ContentID: content.ID}
createRes, err := Order.Create(ctx, tenantID, buyer.ID, form)
So(err, ShouldBeNil)
So(createRes.OrderID, ShouldNotBeEmpty)
// Verify created status
oid := createRes.OrderID
o, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).First()
So(o.Status, ShouldEqual, consts.OrderStatusCreated)
So(o.AmountPaid, ShouldEqual, 1000)
// Step 2: Pay Order
payForm := &order_dto.OrderPayForm{Method: "balance"}
_, err = Order.Pay(ctx, tenantID, buyer.ID, createRes.OrderID, payForm)
So(err, ShouldBeNil)
// Verify Order Paid
o, _ = models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).First()
So(o.Status, ShouldEqual, consts.OrderStatusPaid)
So(o.PaidAt, ShouldNotBeZeroValue)
// Verify Balance Deducted
b, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(buyer.ID)).First()
So(b.Balance, ShouldEqual, 1000) // 2000 - 1000
// Verify Access Granted
access, _ := models.ContentAccessQuery.WithContext(ctx).
Where(models.ContentAccessQuery.UserID.Eq(buyer.ID), models.ContentAccessQuery.ContentID.Eq(content.ID)).
First()
So(access, ShouldNotBeNil)
So(access.Status, ShouldEqual, consts.ContentAccessStatusActive)
// Verify Ledger Created (Creator received money logic?)
// Note: My implementation credits the TENANT OWNER (creator.ID).
l, _ := models.TenantLedgerQuery.WithContext(ctx).Where(models.TenantLedgerQuery.OrderID.Eq(o.ID)).First()
So(l, ShouldNotBeNil)
So(l.UserID, ShouldEqual, creator.ID)
So(l.Amount, ShouldEqual, 900)
So(l.Type, ShouldEqual, consts.TenantLedgerTypeDebitPurchase)
})
Convey("should fail pay if insufficient balance", func() {
// Set balance to 5.00
models.UserQuery.WithContext(ctx).
Where(models.UserQuery.ID.Eq(buyer.ID)).
Update(models.UserQuery.Balance, 500)
form := &order_dto.OrderCreateForm{ContentID: content.ID}
createRes, err := Order.Create(ctx, tenantID, buyer.ID, form)
So(err, ShouldBeNil)
payForm := &order_dto.OrderPayForm{Method: "balance"}
_, err = Order.Pay(ctx, tenantID, buyer.ID, createRes.OrderID, payForm)
So(err, ShouldNotBeNil)
// Error should be QuotaExceeded or similar
})
})
}
func (s *OrderTestSuite) Test_OrderDetails() {
Convey("Order Details", s.T(), func() {
ctx := s.T().Context()
tenantID := int64(0)
database.Truncate(
ctx,
s.DB,
models.TableNameOrder,
@@ -167,7 +61,6 @@ func (s *OrderTestSuite) Test_OrderDetails() {
models.UserQuery.WithContext(ctx).Create(creator)
tenant := &models.Tenant{UserID: creator.ID, Name: "Best Shop", Status: consts.TenantStatusVerified}
models.TenantQuery.WithContext(ctx).Create(tenant)
tenantID = tenant.ID
content := &models.Content{
TenantID: tenant.ID,
UserID: creator.ID,
@@ -207,7 +100,9 @@ func (s *OrderTestSuite) Test_OrderDetails() {
buyer.ID,
&order_dto.OrderCreateForm{ContentID: content.ID},
)
Order.Pay(ctx, tenantID, buyer.ID, createRes.OrderID, &order_dto.OrderPayForm{Method: "balance"})
res, err := Order.Pay(ctx, tenantID, buyer.ID, createRes.OrderID, &order_dto.OrderPayForm{Method: "balance"})
So(err, ShouldBeNil)
So(res.Status, ShouldEqual, string(consts.OrderStatusPaid))
// Get Detail
detail, err := Order.GetUserOrder(ctx, tenantID, buyer.ID, createRes.OrderID)
@@ -273,58 +168,3 @@ func (s *OrderTestSuite) Test_PlatformCommission() {
})
})
}
func (s *OrderTestSuite) Test_ExternalPayment() {
Convey("External Payment", s.T(), func() {
ctx := s.T().Context()
tenantID := int64(0)
database.Truncate(
ctx,
s.DB,
models.TableNameUser,
models.TableNameOrder,
models.TableNameOrderItem,
models.TableNameTenant,
models.TableNameTenantLedger,
models.TableNameContentAccess,
)
// Creator
creator := &models.User{Username: "creator_ext", Balance: 0}
models.UserQuery.WithContext(ctx).Create(creator)
// Tenant
t := &models.Tenant{UserID: creator.ID, Name: "Shop Ext", Status: consts.TenantStatusVerified}
models.TenantQuery.WithContext(ctx).Create(t)
tenantID = t.ID
// Buyer (Balance 0)
buyer := &models.User{Username: "buyer_ext", Balance: 0}
models.UserQuery.WithContext(ctx).Create(buyer)
// Order
o := &models.Order{
TenantID: t.ID,
UserID: buyer.ID,
AmountPaid: 1000,
Status: consts.OrderStatusCreated,
}
models.OrderQuery.WithContext(ctx).Create(o)
models.OrderItemQuery.WithContext(ctx).Create(&models.OrderItem{OrderID: o.ID, ContentID: 999})
Convey("should process external payment callback", func() {
err := Order.ProcessExternalPayment(ctx, tenantID, o.ID, "ext_tx_id_123")
So(err, ShouldBeNil)
// Verify Status
oReload, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(o.ID)).First()
So(oReload.Status, ShouldEqual, consts.OrderStatusPaid)
// Verify Creator Credited
cReload, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(creator.ID)).First()
So(cReload.Balance, ShouldEqual, 900) // 1000 - 10%
// Verify Buyer Balance (Should NOT be deducted)
bReload, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(buyer.ID)).First()
So(bReload.Balance, ShouldEqual, 0)
})
})
}

View File

@@ -73,6 +73,13 @@ func Provide(opts ...opt.Option) error {
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*recharge, error) {
obj := &recharge{}
return obj, nil
}); err != nil {
return err
}
if err := container.Container.Provide(func(
audit *audit,
common *common,
@@ -82,6 +89,7 @@ func Provide(opts ...opt.Option) error {
db *gorm.DB,
notification *notification,
order *order,
recharge *recharge,
super *super,
tenant *tenant,
user *user,
@@ -96,6 +104,7 @@ func Provide(opts ...opt.Option) error {
db: db,
notification: notification,
order: order,
recharge: recharge,
super: super,
tenant: tenant,
user: user,

View File

@@ -15,6 +15,7 @@ var (
Creator *creator
Notification *notification
Order *order
Recharge *recharge
Super *super
Tenant *tenant
User *user
@@ -32,6 +33,7 @@ type services struct {
creator *creator
notification *notification
order *order
recharge *recharge
super *super
tenant *tenant
user *user
@@ -49,6 +51,7 @@ func (svc *services) Prepare() error {
Creator = svc.creator
Notification = svc.notification
Order = svc.order
Recharge = svc.recharge
Super = svc.super
Tenant = svc.tenant
User = svc.user

View File

@@ -107,6 +107,26 @@ func (s *super) CheckToken(ctx context.Context, token string) (*super_dto.LoginR
}, nil
}
func orderExprFromFilter(desc, asc *string, descMap, ascMap map[string]field.Expr) (field.Expr, bool) {
if desc != nil && strings.TrimSpace(*desc) != "" {
if expr, ok := descMap[strings.TrimSpace(*desc)]; ok {
return expr, true
}
return nil, false
}
if asc != nil && strings.TrimSpace(*asc) != "" {
if expr, ok := ascMap[strings.TrimSpace(*asc)]; ok {
return expr, true
}
return nil, false
}
return nil, false
}
func (s *super) ListUsers(ctx context.Context, filter *super_dto.UserListFilter) (*requests.Pager, error) {
tbl, q := models.UserQuery.QueryContext(ctx)
if filter.Username != nil && strings.TrimSpace(*filter.Username) != "" {
@@ -177,49 +197,30 @@ func (s *super) ListUsers(ctx context.Context, filter *super_dto.UserListFilter)
}
}
orderApplied := false
if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
switch strings.TrimSpace(*filter.Desc) {
case "id":
q = q.Order(tbl.ID.Desc())
case "username":
q = q.Order(tbl.Username.Desc())
case "status":
q = q.Order(tbl.Status.Desc())
case "verified_at":
q = q.Order(tbl.VerifiedAt.Desc())
case "created_at":
q = q.Order(tbl.CreatedAt.Desc())
case "updated_at":
q = q.Order(tbl.UpdatedAt.Desc())
case "balance":
q = q.Order(tbl.Balance.Desc())
case "balance_frozen":
q = q.Order(tbl.BalanceFrozen.Desc())
}
orderApplied = true
} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
switch strings.TrimSpace(*filter.Asc) {
case "id":
q = q.Order(tbl.ID)
case "username":
q = q.Order(tbl.Username)
case "status":
q = q.Order(tbl.Status)
case "verified_at":
q = q.Order(tbl.VerifiedAt)
case "created_at":
q = q.Order(tbl.CreatedAt)
case "updated_at":
q = q.Order(tbl.UpdatedAt)
case "balance":
q = q.Order(tbl.Balance)
case "balance_frozen":
q = q.Order(tbl.BalanceFrozen)
}
orderApplied = true
}
if !orderApplied {
if sortExpr, ok := orderExprFromFilter(filter.Desc, filter.Asc,
map[string]field.Expr{
"id": tbl.ID.Desc(),
"username": tbl.Username.Desc(),
"status": tbl.Status.Desc(),
"verified_at": tbl.VerifiedAt.Desc(),
"created_at": tbl.CreatedAt.Desc(),
"updated_at": tbl.UpdatedAt.Desc(),
"balance": tbl.Balance.Desc(),
"balance_frozen": tbl.BalanceFrozen.Desc(),
},
map[string]field.Expr{
"id": tbl.ID,
"username": tbl.Username,
"status": tbl.Status,
"verified_at": tbl.VerifiedAt,
"created_at": tbl.CreatedAt,
"updated_at": tbl.UpdatedAt,
"balance": tbl.Balance,
"balance_frozen": tbl.BalanceFrozen,
},
); ok {
q = q.Order(sortExpr)
} else {
q = q.Order(tbl.ID.Desc())
}
@@ -287,8 +288,10 @@ func (s *super) GetUser(ctx context.Context, id int64) (*super_dto.UserItem, err
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return &super_dto.UserItem{
SuperUserLite: super_dto.SuperUserLite{
ID: u.ID,
@@ -322,6 +325,7 @@ func (s *super) GetUserWallet(ctx context.Context, userID int64) (*super_dto.Sup
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -398,6 +402,85 @@ func (s *super) GetUserWallet(ctx context.Context, userID int64) (*super_dto.Sup
}, nil
}
func (s *super) CreditUserWallet(ctx context.Context, operatorID, userID int64, form *super_dto.SuperWalletCreditForm) error {
if operatorID == 0 {
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
}
if userID == 0 {
return errorx.ErrBadRequest.WithMsg("用户ID不能为空")
}
if form == nil {
return errorx.ErrBadRequest.WithMsg("充值参数不能为空")
}
amount := int64(form.Amount * 100)
if amount <= 0 {
return errorx.ErrBadRequest.WithMsg("充值金额无效")
}
remark := strings.TrimSpace(form.Remark)
if remark == "" {
remark = "超管充值"
}
return models.Q.Transaction(func(tx *models.Query) error {
userTbl, userQuery := tx.User.QueryContext(ctx)
u, err := userQuery.Where(userTbl.ID.Eq(userID)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("用户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
order := &models.Order{
TenantID: 0,
UserID: userID,
Type: consts.OrderTypeRecharge,
Status: consts.OrderStatusCreated,
Currency: consts.CurrencyCNY,
AmountOriginal: amount,
AmountPaid: amount,
IdempotencyKey: uuid.NewString(),
}
if err := tx.Order.WithContext(ctx).Create(order); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
now := time.Now()
if _, err := userQuery.Where(userTbl.ID.Eq(userID)).Update(userTbl.Balance, gorm.Expr("balance + ?", amount)); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
if _, err := tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(order.ID)).Updates(&models.Order{
Status: consts.OrderStatusPaid,
PaidAt: now,
UpdatedAt: now,
}); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
ledger := &models.TenantLedger{
TenantID: 0,
UserID: userID,
OrderID: order.ID,
Type: consts.TenantLedgerTypeAdjustment,
Amount: amount,
Remark: remark,
OperatorUserID: operatorID,
IdempotencyKey: uuid.NewString(),
}
if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
if Audit != nil {
Audit.Log(ctx, 0, operatorID, "super_wallet_credit", cast.ToString(u.ID), remark)
}
return nil
})
}
func (s *super) GetUserRealName(ctx context.Context, userID int64) (*super_dto.SuperUserRealNameResponse, error) {
if userID == 0 {
return nil, errorx.ErrBadRequest.WithMsg("用户ID不能为空")
@@ -409,6 +492,7 @@ func (s *super) GetUserRealName(ctx context.Context, userID int64) (*super_dto.S
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -579,6 +663,7 @@ func (s *super) ListUserCoupons(ctx context.Context, userID int64, filter *super
if couponFilter {
if len(couponIDs) == 0 {
filter.Pagination.Format()
return &requests.Pager{
Pagination: filter.Pagination,
Total: 0,
@@ -699,6 +784,7 @@ func (s *super) UpdateUserStatus(ctx context.Context, id int64, form *super_dto.
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -712,6 +798,7 @@ func (s *super) UpdateUserRoles(ctx context.Context, id int64, form *super_dto.U
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -732,6 +819,7 @@ func (s *super) UpdateUserProfile(ctx context.Context, operatorID, userID int64,
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -1359,6 +1447,7 @@ func (s *super) ListCreatorApplications(ctx context.Context, filter *super_dto.T
status := consts.TenantStatusPendingVerify
filter.Status = &status
}
return s.ListTenants(ctx, filter)
}
@@ -1381,6 +1470,7 @@ func (s *super) ReviewCreatorApplication(ctx context.Context, operatorID, tenant
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("创作者申请不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if tenant.Status != consts.TenantStatusPendingVerify {
@@ -1431,10 +1521,12 @@ func (s *super) GetCreatorSettings(ctx context.Context, tenantID int64) (*v1_dto
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
cfg := t.Config.Data()
return &v1_dto.Settings{
ID: t.ID,
Name: t.Name,
@@ -1458,6 +1550,7 @@ func (s *super) UpdateCreatorSettings(ctx context.Context, operatorID, tenantID
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -1493,12 +1586,14 @@ func (s *super) CreateTenant(ctx context.Context, form *super_dto.TenantCreateFo
uid := form.AdminUserID
expiredAt := time.Now().AddDate(0, 0, form.Duration)
return models.Q.Transaction(func(tx *models.Query) error {
// 校验管理员用户存在,避免创建脏数据。
if _, err := tx.User.WithContext(ctx).Where(tx.User.ID.Eq(uid)).First(); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("用户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -1536,6 +1631,7 @@ func (s *super) GetTenant(ctx context.Context, id int64) (*super_dto.TenantItem,
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
items, err := s.buildTenantItems(ctx, []*models.Tenant{t})
@@ -1545,6 +1641,7 @@ func (s *super) GetTenant(ctx context.Context, id int64) (*super_dto.TenantItem,
if len(items) == 0 {
return nil, errorx.ErrRecordNotFound
}
return &items[0], nil
}
@@ -1554,6 +1651,7 @@ func (s *super) UpdateTenantStatus(ctx context.Context, id int64, form *super_dt
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -1564,6 +1662,7 @@ func (s *super) UpdateTenantExpire(ctx context.Context, id int64, form *super_dt
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -1812,12 +1911,14 @@ func (s *super) CreatePayoutAccount(ctx context.Context, operatorID, tenantID in
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("租户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if _, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(form.UserID)).First(); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("用户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -1855,6 +1956,7 @@ func (s *super) UpdatePayoutAccount(ctx context.Context, operatorID, id int64, f
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("结算账户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -1919,6 +2021,7 @@ func (s *super) RemovePayoutAccount(ctx context.Context, operatorID, id int64) e
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("结算账户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -1929,6 +2032,7 @@ func (s *super) RemovePayoutAccount(ctx context.Context, operatorID, id int64) e
if Audit != nil {
Audit.Log(ctx, account.TenantID, operatorID, "remove_payout_account", cast.ToString(account.ID), "Removed payout account")
}
return nil
}
@@ -1966,6 +2070,7 @@ func (s *super) ReviewPayoutAccount(ctx context.Context, operatorID, id int64, f
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("结算账户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if existing.Status != consts.PayoutAccountStatusPending {
@@ -1987,6 +2092,7 @@ func (s *super) ReviewPayoutAccount(ctx context.Context, operatorID, id int64, f
return errorx.ErrDatabaseError.WithCause(err)
}
account = existing
return nil
})
if err != nil {
@@ -2007,6 +2113,7 @@ func (s *super) ReviewPayoutAccount(ctx context.Context, operatorID, id int64, f
if Audit != nil && account != nil {
Audit.Log(ctx, account.TenantID, operatorID, "review_payout_account", cast.ToString(account.ID), detail)
}
return nil
}
@@ -2195,6 +2302,7 @@ func (s *super) ReviewTenantJoinRequest(ctx context.Context, operatorID, request
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("申请不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if req.Status != string(consts.TenantJoinRequestStatusPending) {
@@ -2216,6 +2324,7 @@ func (s *super) ReviewTenantJoinRequest(ctx context.Context, operatorID, request
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -2256,6 +2365,7 @@ func (s *super) ReviewTenantJoinRequest(ctx context.Context, operatorID, request
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
})
}
@@ -2272,8 +2382,10 @@ func (s *super) CreateTenantInvite(ctx context.Context, tenantID int64, form *v1
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound.WithMsg("租户不存在")
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return Tenant.CreateInvite(ctx, tenantID, tenant.UserID, form)
}
@@ -2421,6 +2533,7 @@ func (s *super) ListUserLibrary(ctx context.Context, userID int64, filter *super
if contentFilter {
if len(contentIDs) == 0 {
filter.Pagination.Format()
return &requests.Pager{
Pagination: filter.Pagination,
Total: 0,
@@ -2437,6 +2550,7 @@ func (s *super) ListUserLibrary(ctx context.Context, userID int64, filter *super
if orderFilter {
if len(orderIDs) == 0 {
filter.Pagination.Format()
return &requests.Pager{
Pagination: filter.Pagination,
Total: 0,
@@ -2649,6 +2763,7 @@ func (s *super) ListUserFollowing(ctx context.Context, userID int64, filter *sup
// 关注列表默认只展示普通成员关注关系。
role := consts.TenantUserRoleMember
filter.Role = &role
return s.ListUserTenants(ctx, userID, filter)
}
@@ -2675,6 +2790,7 @@ func (s *super) listUserContentActions(
if contentFilter {
if len(contentIDs) == 0 {
filter.Pagination.Format()
return &requests.Pager{
Pagination: filter.Pagination,
Total: 0,
@@ -2992,6 +3108,7 @@ func (s *super) ListContents(ctx context.Context, filter *super_dto.SuperContent
for _, c := range list {
data = append(data, s.toSuperContentItem(c, priceMap[c.ID], tenantMap[c.TenantID]))
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
@@ -3277,6 +3394,7 @@ func (s *super) DeleteComment(ctx context.Context, operatorID, id int64, form *s
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -3296,6 +3414,7 @@ func (s *super) DeleteComment(ctx context.Context, operatorID, id int64, form *s
}
Audit.Log(ctx, comment.TenantID, operatorID, "delete_comment", cast.ToString(id), detail)
}
return nil
}
@@ -3679,6 +3798,7 @@ func (s *super) ProcessContentReport(ctx context.Context, operatorID, id int64,
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound
}
return errorx.ErrDatabaseError.WithCause(err)
}
if strings.TrimSpace(report.Status) != "" && report.Status != "pending" {
@@ -3881,6 +4001,7 @@ func (s *super) BatchProcessContentReports(ctx context.Context, operatorID int64
}
reports = list
return nil
})
if err != nil {
@@ -3930,6 +4051,7 @@ func (s *super) UpdateContentStatus(ctx context.Context, tenantID, contentID int
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -3952,6 +4074,7 @@ func (s *super) ReviewContent(ctx context.Context, operatorID, contentID int64,
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound
}
return errorx.ErrDatabaseError.WithCause(err)
}
if content.Status != consts.ContentStatusReviewing {
@@ -3991,6 +4114,7 @@ func (s *super) ReviewContent(ctx context.Context, operatorID, contentID int64,
if Audit != nil {
Audit.Log(ctx, content.TenantID, operatorID, "review_content", cast.ToString(contentID), detail)
}
return nil
}
@@ -4058,6 +4182,7 @@ func (s *super) BatchReviewContents(ctx context.Context, operatorID int64, form
return errorx.ErrDatabaseError.WithCause(err)
}
contents = list
return nil
})
if err != nil {
@@ -4136,6 +4261,7 @@ func (s *super) BatchUpdateContentStatus(ctx context.Context, operatorID int64,
return errorx.ErrDatabaseError.WithCause(err)
}
contents = list
return nil
})
if err != nil {
@@ -4540,6 +4666,7 @@ func (s *super) DeleteAsset(ctx context.Context, assetID int64, force bool) erro
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("资产不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -4573,6 +4700,7 @@ func (s *super) DeleteAsset(ctx context.Context, assetID int64, force bool) erro
_ = Common.storage.Delete(asset.ObjectKey)
}
}
return nil
}
@@ -4817,6 +4945,7 @@ func (s *super) BroadcastNotifications(ctx context.Context, form *super_dto.Supe
return err
}
}
return nil
}
@@ -5023,6 +5152,7 @@ func (s *super) UpdateNotificationTemplate(ctx context.Context, operatorID, id i
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound.WithMsg("模板不存在")
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -5038,6 +5168,7 @@ func (s *super) UpdateNotificationTemplate(ctx context.Context, operatorID, id i
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound.WithMsg("租户不存在")
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
}
@@ -5488,6 +5619,7 @@ func (s *super) UpdateSystemConfig(ctx context.Context, operatorID, id int64, fo
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound.WithMsg("配置不存在")
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -5705,6 +5837,7 @@ func (s *super) ListOrders(ctx context.Context, filter *super_dto.SuperOrderList
if err != nil {
return nil, err
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
@@ -5718,6 +5851,7 @@ func (s *super) GetOrder(ctx context.Context, id int64) (*super_dto.SuperOrderDe
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -5744,6 +5878,7 @@ func (s *super) GetOrder(ctx context.Context, id int64) (*super_dto.SuperOrderDe
item := s.toSuperOrderItem(o, tenant, buyer)
item.Snapshot = o.Snapshot.Data()
item.Items = items
return &super_dto.SuperOrderDetail{
Order: &item,
Tenant: item.Tenant,
@@ -5775,6 +5910,7 @@ func (s *super) FlagOrder(ctx context.Context, operatorID, id int64, form *super
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound
}
return errorx.ErrDatabaseError.WithCause(err)
}
tenantID = o.TenantID
@@ -5798,6 +5934,7 @@ func (s *super) FlagOrder(ctx context.Context, operatorID, id int64, form *super
if _, err := q.Where(tbl.ID.Eq(id)).Updates(updates); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
})
if err != nil {
@@ -5840,6 +5977,7 @@ func (s *super) ReconcileOrder(ctx context.Context, operatorID, id int64, form *
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound
}
return errorx.ErrDatabaseError.WithCause(err)
}
tenantID = o.TenantID
@@ -5863,6 +6001,7 @@ func (s *super) ReconcileOrder(ctx context.Context, operatorID, id int64, form *
if _, err := q.Where(tbl.ID.Eq(id)).Updates(updates); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
})
if err != nil {
@@ -5891,6 +6030,7 @@ func (s *super) RefundOrder(ctx context.Context, id int64, form *super_dto.Super
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -5988,6 +6128,7 @@ func (s *super) UserStatistics(ctx context.Context) ([]super_dto.UserStatistics,
Count: row.Count,
})
}
return stats, nil
}
@@ -6017,6 +6158,7 @@ func (s *super) toSuperUserLite(u *models.User) *super_dto.SuperUserLite {
if u == nil {
return nil
}
return &super_dto.SuperUserLite{
ID: u.ID,
Username: u.Username,
@@ -6035,6 +6177,7 @@ func hasRole(roles types.Array[consts.Role], role consts.Role) bool {
return true
}
}
return false
}
@@ -6044,6 +6187,7 @@ func hasTenantRole(roles types.Array[consts.TenantUserRole], role consts.TenantU
return true
}
}
return false
}
@@ -6095,6 +6239,7 @@ func (s *super) buildSuperOrderItems(ctx context.Context, orders []*models.Order
for _, o := range orders {
items = append(items, s.toSuperOrderItem(o, tenantMap[o.TenantID], userMap[o.UserID]))
}
return items, nil
}
@@ -6173,6 +6318,7 @@ func (s *super) parseFilterTime(value *string) (*time.Time, error) {
if err != nil {
return nil, errorx.ErrInvalidFormat.WithCause(err)
}
return &t, nil
}
@@ -6205,6 +6351,7 @@ func (s *super) lookupTenantIDs(ctx context.Context, code, name *string) ([]int6
for _, tenant := range tenants {
ids = append(ids, tenant.ID)
}
return ids, true, nil
}
@@ -6229,6 +6376,7 @@ func (s *super) lookupUserIDs(ctx context.Context, username *string) ([]int64, b
for _, user := range users {
ids = append(ids, user.ID)
}
return ids, true, nil
}
@@ -6282,6 +6430,7 @@ func (s *super) filterContentIDsForUserActions(
if err != nil {
return nil, true, errorx.ErrDatabaseError.WithCause(err)
}
return ids, true, nil
}
@@ -6335,6 +6484,7 @@ func (s *super) filterContentIDsForUserLibrary(
if err != nil {
return nil, true, errorx.ErrDatabaseError.WithCause(err)
}
return ids, true, nil
}
@@ -6393,6 +6543,7 @@ func (s *super) filterOrderIDsForUserLibrary(
for _, order := range orders {
ids = append(ids, order.ID)
}
return ids, true, nil
}
@@ -6441,6 +6592,7 @@ func (s *super) lookupOrderIDsByContent(ctx context.Context, contentID *int64, c
for orderID := range idMap {
ids = append(ids, orderID)
}
return ids, true, nil
}
@@ -6464,6 +6616,7 @@ func (s *super) contentPriceMap(ctx context.Context, list []*models.Content) (ma
for _, price := range prices {
priceMap[price.ContentID] = price
}
return priceMap, nil
}
@@ -6492,6 +6645,7 @@ func (s *super) contentTenantMap(ctx context.Context, list []*models.Content) (m
for _, tenant := range tenants {
tenantMap[tenant.ID] = tenant
}
return tenantMap, nil
}
@@ -6510,6 +6664,7 @@ func (s *super) toSuperContentOwner(author *models.User) *super_dto.AdminContent
if author == nil {
return nil
}
return &super_dto.AdminContentOwnerLite{
ID: author.ID,
Username: author.Username,
@@ -6522,6 +6677,7 @@ func (s *super) toSuperContentTenant(tenant *models.Tenant) *super_dto.SuperCont
if tenant == nil {
return nil
}
return &super_dto.SuperContentTenantLite{
ID: tenant.ID,
Code: tenant.Code,
@@ -6578,6 +6734,7 @@ func (s *super) toSuperContentDTO(item *models.Content, price *models.ContentPri
for _, asset := range item.ContentAssets {
if asset.Asset != nil && asset.Asset.Type == consts.MediaAssetTypeImage {
dto.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
break
}
}
@@ -6610,6 +6767,7 @@ func (s *super) toSuperContentPrice(price *models.ContentPrice) *v1_dto.ContentP
if !price.DiscountEndAt.IsZero() {
dto.DiscountEndAt = price.DiscountEndAt.Format(time.RFC3339)
}
return dto
}
@@ -6620,6 +6778,7 @@ func (s *super) toSuperDiscountValue(price *models.ContentPrice) float64 {
if price.DiscountType == consts.DiscountTypeAmount {
return float64(price.DiscountValue) / 100.0
}
return float64(price.DiscountValue)
}
@@ -7135,6 +7294,7 @@ func (s *super) ListWithdrawals(ctx context.Context, filter *super_dto.SuperOrde
if err != nil {
return nil, err
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
@@ -7786,6 +7946,7 @@ func (s *super) ListCoupons(ctx context.Context, filter *super_dto.SuperCouponLi
if err != nil {
return nil, err
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
@@ -7825,6 +7986,7 @@ func (s *super) ListCouponGrants(ctx context.Context, filter *super_dto.SuperCou
if couponFilter {
if len(couponIDs) == 0 {
filter.Pagination.Format()
return &requests.Pager{
Pagination: filter.Pagination,
Total: 0,
@@ -8023,6 +8185,7 @@ func (s *super) ListCouponRisks(ctx context.Context, filter *super_dto.SuperCoup
if userFilter {
if len(userIDs) == 0 {
filter.Pagination.Format()
return &requests.Pager{
Pagination: filter.Pagination,
Total: 0,
@@ -8039,6 +8202,7 @@ func (s *super) ListCouponRisks(ctx context.Context, filter *super_dto.SuperCoup
if couponFilter {
if len(couponIDs) == 0 {
filter.Pagination.Format()
return &requests.Pager{
Pagination: filter.Pagination,
Total: 0,
@@ -8110,6 +8274,7 @@ func (s *super) ListCouponRisks(ctx context.Context, filter *super_dto.SuperCoup
}
if len(list) == 0 {
filter.Pagination.Format()
return &requests.Pager{
Pagination: filter.Pagination,
Total: 0,
@@ -8361,6 +8526,7 @@ func (s *super) ListCouponRisks(ctx context.Context, filter *super_dto.SuperCoup
if desc {
return !less
}
return less
})
@@ -8408,6 +8574,7 @@ func (s *super) UpdateCouponStatus(ctx context.Context, operatorID, couponID int
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -8426,6 +8593,7 @@ func (s *super) UpdateCouponStatus(ctx context.Context, operatorID, couponID int
if Audit != nil {
Audit.Log(ctx, coupon.TenantID, operatorID, "freeze_coupon", cast.ToString(coupon.ID), "Freeze coupon")
}
return nil
}
@@ -8651,6 +8819,7 @@ func (s *super) ExportReport(ctx context.Context, form *super_dto.SuperReportExp
}
filename := "report_overview_" + time.Now().Format("20060102_150405") + ".csv"
return &v1_dto.ReportExportResponse{
Filename: filename,
MimeType: "text/csv",
@@ -8667,6 +8836,7 @@ func (s *super) contentCount(ctx context.Context, tenantID int64) (int64, error)
if err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
@@ -8680,6 +8850,7 @@ func (s *super) contentCreatedAggregate(ctx context.Context, tenantID int64, rg
if err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
@@ -8696,6 +8867,7 @@ func (s *super) contentCreatedSeries(ctx context.Context, tenantID int64, rg rep
if err := query.Group("day").Scan(&rows).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return buildCountSeries(rows), nil
}
@@ -8719,6 +8891,7 @@ func (s *super) contentActionAggregate(
if err := query.Scan(&total).Error; err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
@@ -8742,6 +8915,7 @@ func (s *super) contentActionSeries(
if err := query.Group("day").Scan(&rows).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return buildCountSeries(rows), nil
}
@@ -8755,6 +8929,7 @@ func (s *super) commentAggregate(ctx context.Context, tenantID int64, rg reportR
if err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
@@ -8771,6 +8946,7 @@ func (s *super) commentSeries(ctx context.Context, tenantID int64, rg reportRang
if err := query.Group("day").Scan(&rows).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return buildCountSeries(rows), nil
}
@@ -8800,6 +8976,7 @@ func (s *super) reportOrderAggregate(
if err := query.Scan(&total).Error; err != nil {
return 0, 0, errorx.ErrDatabaseError.WithCause(err)
}
return total.Count, total.Amount, nil
}
@@ -8831,6 +9008,7 @@ func (s *super) reportOrderSeries(
key := row.Day.Format("2006-01-02")
result[key] = row
}
return result, nil
}
@@ -8861,6 +9039,7 @@ func (s *super) normalizeHealthRange(filter *super_dto.SuperHealthOverviewFilter
if startAt.After(endAt) {
return time.Time{}, time.Time{}, errorx.ErrBadRequest.WithMsg("结束时间不能早于开始时间")
}
return startAt, endAt, nil
}
@@ -8899,6 +9078,7 @@ func (s *super) normalizeReportRange(filter *super_dto.SuperReportOverviewFilter
}
endNext := endDay.AddDate(0, 0, 1)
return reportRange{
startDay: startDay,
endDay: endDay,
@@ -8943,6 +9123,7 @@ func (s *super) buildSuperCouponItems(ctx context.Context, list []*models.Coupon
}
items = append(items, s.toSuperCouponItem(c, tenantMap[c.TenantID]))
}
return items, nil
}
@@ -8975,6 +9156,7 @@ func (s *super) toSuperCouponItem(c *models.Coupon, tenant *models.Tenant) super
if !c.EndAt.IsZero() {
item.EndAt = s.formatTime(c.EndAt)
}
return item
}
@@ -8986,6 +9168,7 @@ func (s *super) resolveCouponStatus(c *models.Coupon) (string, string) {
if !c.StartAt.IsZero() && c.StartAt.After(now) {
return "upcoming", "未开始"
}
return "active", "生效中"
}
@@ -9013,6 +9196,7 @@ func (s *super) ApproveWithdrawal(ctx context.Context, operatorID, id int64) err
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("收款账户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if account.Status != consts.PayoutAccountStatusApproved {
@@ -9030,6 +9214,7 @@ func (s *super) ApproveWithdrawal(ctx context.Context, operatorID, id int64) err
if err == nil && Audit != nil {
Audit.Log(ctx, o.TenantID, operatorID, "approve_withdrawal", cast.ToString(id), "Approved withdrawal")
}
return err
}
@@ -9055,6 +9240,7 @@ func (s *super) userOwnedTenantCount(ctx context.Context, userIDs []int64) (map[
for _, row := range rows {
result[row.UserID] = row.Count
}
return result, nil
}
@@ -9080,6 +9266,7 @@ func (s *super) userJoinedTenantCount(ctx context.Context, userIDs []int64) (map
for _, row := range rows {
result[row.UserID] = row.Count
}
return result, nil
}
@@ -9105,6 +9292,7 @@ func (s *super) userMapByTenantUsers(ctx context.Context, list []*models.TenantU
for _, u := range users {
userMap[u.ID] = u
}
return userMap, nil
}
@@ -9112,6 +9300,7 @@ func (s *super) toSuperTenantUserDTO(tu *models.TenantUser) *super_dto.TenantUse
if tu == nil {
return nil
}
return &super_dto.TenantUser{
ID: tu.ID,
TenantID: tu.TenantID,
@@ -9185,6 +9374,7 @@ func (s *super) formatTime(t time.Time) string {
if t.IsZero() {
return ""
}
return t.Format(time.RFC3339)
}
@@ -9204,6 +9394,7 @@ func (s *super) maskIDCard(raw string) string {
if length <= 8 {
return text[:2] + strings.Repeat("*", length-4) + text[length-2:]
}
return text[:3] + strings.Repeat("*", length-7) + text[length-4:]
}
@@ -9254,6 +9445,7 @@ func (s *super) filterCouponIDs(ctx context.Context, filter *super_dto.SuperUser
for _, coupon := range coupons {
ids = append(ids, coupon.ID)
}
return ids, true, nil
}
@@ -9298,6 +9490,7 @@ func (s *super) filterCouponGrantCouponIDs(ctx context.Context, filter *super_dt
for _, coupon := range coupons {
ids = append(ids, coupon.ID)
}
return ids, true, nil
}
@@ -9348,6 +9541,7 @@ func (s *super) filterCouponRiskCouponIDs(ctx context.Context, filter *super_dto
for _, coupon := range coupons {
ids = append(ids, coupon.ID)
}
return ids, true, nil
}
@@ -9403,5 +9597,6 @@ func (s *super) RejectWithdrawal(ctx context.Context, operatorID, id int64, reas
if err == nil && Audit != nil {
Audit.Log(ctx, tenantID, operatorID, "reject_withdrawal", cast.ToString(id), "Rejected: "+reason)
}
return err
}

View File

@@ -82,6 +82,7 @@ func (s *tenant) GetPublicProfile(ctx context.Context, tenantID, userID int64) (
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -101,6 +102,7 @@ func (s *tenant) GetPublicProfile(ctx context.Context, tenantID, userID int64) (
}
cfg := t.Config.Data()
return &dto.TenantProfile{
ID: t.ID,
Name: t.Name,
@@ -142,6 +144,7 @@ func (s *tenant) Follow(ctx context.Context, tenantID, userID int64) error {
if Notification != nil {
_ = Notification.Send(ctx, tenantID, t.UserID, "interaction", "新增粉丝", "有人关注了您的店铺: "+t.Name)
}
return nil
}
@@ -157,6 +160,7 @@ func (s *tenant) Unfollow(ctx context.Context, tenantID, userID int64) error {
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -242,8 +246,10 @@ func (s *tenant) GetModelByID(ctx context.Context, id int64) (*models.Tenant, er
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return u, nil
}
@@ -271,6 +277,7 @@ func (s *tenant) countFollowers(ctx context.Context, tenantIDs []int64) (map[int
for _, row := range rows {
result[row.TenantID] = row.Total
}
return result, nil
}
@@ -293,5 +300,6 @@ func (s *tenant) countPublishedContents(ctx context.Context, tenantIDs []int64)
for _, row := range rows {
result[row.TenantID] = row.Total
}
return result, nil
}

View File

@@ -33,6 +33,7 @@ func (s *tenant) ApplyJoin(ctx context.Context, tenantID, userID int64, form *te
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("租户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if tenant.Status != consts.TenantStatusVerified {
@@ -83,6 +84,7 @@ func (s *tenant) ApplyJoin(ctx context.Context, tenantID, userID int64, form *te
if err := qReq.Create(req); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -104,12 +106,14 @@ func (s *tenant) CancelJoin(ctx context.Context, tenantID, userID int64) error {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("申请不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if _, err := qReq.Where(tblReq.ID.Eq(req.ID)).Delete(); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -142,6 +146,7 @@ func (s *tenant) ReviewJoin(
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("申请不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if req.TenantID != tenantID {
@@ -167,6 +172,7 @@ func (s *tenant) ReviewJoin(
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -207,6 +213,7 @@ func (s *tenant) ReviewJoin(
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
})
}
@@ -260,6 +267,7 @@ func (s *tenant) CreateInvite(
if err := models.TenantInviteQuery.WithContext(ctx).Create(invite); err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return s.toTenantInviteItem(invite), nil
}
@@ -288,6 +296,7 @@ func (s *tenant) AcceptInvite(ctx context.Context, tenantID, userID int64, form
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("邀请码无效")
}
return errorx.ErrDatabaseError.WithCause(err)
}
@@ -301,6 +310,7 @@ func (s *tenant) AcceptInvite(ctx context.Context, tenantID, userID int64, form
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return errorx.ErrBadRequest.WithMsg("邀请码已过期")
}
if invite.UsedCount >= invite.MaxUses {
@@ -335,6 +345,7 @@ func (s *tenant) AcceptInvite(ctx context.Context, tenantID, userID int64, form
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
})
}
@@ -620,6 +631,7 @@ func (s *tenant) DisableInvite(ctx context.Context, tenantID, operatorID, invite
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("邀请记录不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if invite.TenantID != tenantID {
@@ -641,6 +653,7 @@ func (s *tenant) DisableInvite(ctx context.Context, tenantID, operatorID, invite
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -664,6 +677,7 @@ func (s *tenant) RemoveMember(ctx context.Context, tenantID, operatorID, memberI
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("成员不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if member.TenantID != tenantID {
@@ -676,6 +690,7 @@ func (s *tenant) RemoveMember(ctx context.Context, tenantID, operatorID, memberI
if _, err := q.Where(tbl.ID.Eq(member.ID)).Delete(); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -690,6 +705,7 @@ func (s *tenant) ensureTenantAdmin(ctx context.Context, tenantID, userID int64)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound.WithMsg("租户不存在")
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
if tenant.UserID == userID {
@@ -710,6 +726,7 @@ func (s *tenant) ensureTenantAdmin(ctx context.Context, tenantID, userID int64)
if !exists {
return nil, errorx.ErrPermissionDenied.WithMsg("无权限操作该租户")
}
return tenant, nil
}
@@ -725,6 +742,7 @@ func (s *tenant) normalizeInviteExpiry(form *tenant_dto.TenantInviteCreateForm)
if expireAt.Before(time.Now()) {
return time.Time{}, errorx.ErrBadRequest.WithMsg("过期时间不能早于当前时间")
}
return expireAt, nil
}
@@ -741,6 +759,7 @@ func (s *tenant) newInviteCode(ctx context.Context) (string, error) {
return code, nil
}
}
return "", errorx.ErrInternalError.WithMsg("生成邀请码失败")
}
@@ -756,6 +775,7 @@ func (s *tenant) toTenantInviteItem(invite *models.TenantInvite) *tenant_dto.Ten
if !invite.CreatedAt.IsZero() {
createdAt = invite.CreatedAt.Format(time.RFC3339)
}
return &tenant_dto.TenantInviteItem{
ID: invite.ID,
Code: invite.Code,
@@ -772,6 +792,7 @@ func (s *tenant) toTenantMemberUserLite(user *models.User) *tenant_dto.TenantMem
if user == nil {
return nil
}
return &tenant_dto.TenantMemberUserLite{
ID: user.ID,
Username: user.Username,
@@ -795,6 +816,7 @@ func (s *tenant) loadUserMap(ctx context.Context, userIDs []int64) (map[int64]*m
for _, user := range users {
userMap[user.ID] = user
}
return userMap, nil
}
@@ -819,6 +841,7 @@ func (s *tenant) lookupUserIDs(ctx context.Context, keyword *string) ([]int64, b
for _, user := range users {
ids = append(ids, user.ID)
}
return ids, true, nil
}
@@ -826,5 +849,6 @@ func (s *tenant) formatTime(t time.Time) string {
if t.IsZero() {
return ""
}
return t.Format(time.RFC3339)
}

View File

@@ -92,6 +92,7 @@ func (s *user) ensureTenantMember(ctx context.Context, tenantID, userID int64) e
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithCause(err).WithMsg("租户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if tenant.UserID == userID {
@@ -110,6 +111,7 @@ func (s *user) ensureTenantMember(ctx context.Context, tenantID, userID int64) e
if !exists {
return errorx.ErrForbidden.WithMsg("未加入该租户")
}
return nil
}
@@ -121,8 +123,10 @@ func (s *user) GetModelByID(ctx context.Context, userID int64) (*models.User, er
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return u, nil
}
@@ -132,6 +136,7 @@ func (s *user) Me(ctx context.Context, userID int64) (*auth_dto.User, error) {
if err != nil {
return nil, err
}
return s.ToAuthUserDTO(u), nil
}
@@ -189,6 +194,7 @@ func (s *user) RealName(ctx context.Context, userID int64, form *user_dto.RealNa
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
@@ -219,6 +225,7 @@ func (s *user) GetNotifications(ctx context.Context, tenantID, userID int64, typ
Time: v.CreatedAt.Format(time.RFC3339),
}
}
return result, nil
}

View File

@@ -3,17 +3,15 @@ package services
import (
"context"
"errors"
"strings"
"time"
"quyun/v2/app/errorx"
user_dto "quyun/v2/app/http/v1/dto"
"quyun/v2/database/fields"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
"github.com/google/uuid"
"go.ipao.vip/gen/field"
"go.ipao.vip/gen/types"
"gorm.io/gorm"
)
@@ -27,6 +25,7 @@ func (s *wallet) GetWallet(ctx context.Context, tenantID, userID int64) (*user_d
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -84,37 +83,18 @@ func (s *wallet) Recharge(
userID int64,
form *user_dto.RechargeForm,
) (*user_dto.RechargeResponse, error) {
amount := int64(form.Amount * 100)
if amount <= 0 {
return nil, errorx.ErrBadRequest.WithMsg("金额无效")
code := strings.TrimSpace(form.Code)
if code == "" {
return nil, errorx.ErrBadRequest.WithMsg("充值码不能为空")
}
// Create Recharge Order
order := &models.Order{
TenantID: 0, // Platform / System
UserID: userID,
Type: consts.OrderTypeRecharge,
Status: consts.OrderStatusCreated,
Currency: consts.CurrencyCNY,
AmountOriginal: amount,
AmountPaid: amount,
IdempotencyKey: uuid.NewString(),
Snapshot: types.NewJSONType(fields.OrdersSnapshot{}),
}
if err := models.OrderQuery.WithContext(ctx).Create(order); err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
// MOCK: Automatically pay for recharge order to close the loop
// In production, this would be a callback from payment gateway
if err := Order.ProcessExternalPayment(ctx, tenantID, order.ID, "mock_auto_pay"); err != nil {
resp, err := Recharge.Redeem(ctx, tenantID, userID, code)
if err != nil {
return nil, err
}
// Mock Pay Params
return &user_dto.RechargeResponse{
PayParams: "mock_paid_success",
OrderID: order.ID,
OrderID: resp.OrderID,
Amount: resp.Amount,
}, nil
}

View File

@@ -76,14 +76,22 @@ func (s *WalletTestSuite) Test_Recharge() {
Convey("Recharge", s.T(), func() {
ctx := s.T().Context()
tenantID := int64(1)
database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameOrder)
database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameOrder, models.TableNameRechargeCode)
u := &models.User{Username: "recharge_user"}
models.UserQuery.WithContext(ctx).Create(u)
ctx = context.WithValue(ctx, consts.CtxKeyUser, u.ID)
Convey("should create recharge order", func() {
form := &user_dto.RechargeForm{Amount: 100.0}
code := &models.RechargeCode{
Code: "TESTCODE",
Amount: 10000,
Status: "active",
ActivatedBy: 1,
}
models.RechargeCodeQuery.WithContext(ctx).Create(code)
form := &user_dto.RechargeForm{Code: code.Code}
res, err := Wallet.Recharge(ctx, tenantID, u.ID, form)
So(err, ShouldBeNil)
So(res.OrderID, ShouldNotBeEmpty)
@@ -93,6 +101,11 @@ func (s *WalletTestSuite) Test_Recharge() {
So(o, ShouldNotBeNil)
So(o.AmountPaid, ShouldEqual, 10000)
So(o.TenantID, ShouldEqual, 0)
latestCode, _ := models.RechargeCodeQuery.WithContext(ctx).Where(models.RechargeCodeQuery.Code.Eq(code.Code)).First()
So(latestCode.Status, ShouldEqual, "redeemed")
So(latestCode.RedeemedBy, ShouldEqual, u.ID)
So(latestCode.RedeemedOrderID, ShouldEqual, o.ID)
})
})
}