package user import ( "context" "fmt" "hash/fnv" "strconv" "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/pkg/constant" "github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) type GetInviteSalesLogic struct { logger.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewGetInviteSalesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetInviteSalesLogic { return &GetInviteSalesLogic{ Logger: logger.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *GetInviteSalesLogic) GetInviteSales(req *types.GetInviteSalesRequest) (resp *types.GetInviteSalesResponse, err error) { // 1. Get current user u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User) if !ok { l.Errorw("[GetInviteSales] user not found in context") return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access") } userId := u.Id // 2. Count total sales var totalSales int64 db := l.svcCtx.DB.WithContext(l.ctx). Table("`order` o"). Joins("JOIN user u ON o.user_id = u.id"). Where("u.referer_id = ? AND o.status = ?", userId, 5) if req.StartTime > 0 { db = db.Where("o.updated_at >= FROM_UNIXTIME(?)", req.StartTime) } if req.EndTime > 0 { db = db.Where("o.updated_at <= FROM_UNIXTIME(?)", req.EndTime) } err = db.Count(&totalSales).Error if err != nil { l.Errorw("[GetInviteSales] count sales failed", logger.Field("error", err.Error()), logger.Field("user_id", userId)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count sales failed: %v", err.Error()) } // 3. Pagination if req.Page < 1 { req.Page = 1 } if req.Size < 1 { req.Size = 10 } if req.Size > 100 { req.Size = 100 } offset := (req.Page - 1) * req.Size // 4. Get sales data type OrderWithUser struct { Amount int64 `gorm:"column:amount"` UpdatedAt int64 `gorm:"column:updated_at"` UserId int64 `gorm:"column:user_id"` ProductName string `gorm:"column:product_name"` Quantity int64 `gorm:"column:quantity"` } var orderData []OrderWithUser query := l.svcCtx.DB.WithContext(l.ctx). Table("`order` o"). Select("o.amount, CAST(UNIX_TIMESTAMP(o.updated_at) * 1000 AS SIGNED) as updated_at, u.id as user_id, s.name as product_name, o.quantity"). Joins("JOIN user u ON o.user_id = u.id"). Joins("LEFT JOIN subscribe s ON o.subscribe_id = s.id"). Where("u.referer_id = ? AND o.status = ?", userId, 5) // status 5: Finished if req.StartTime > 0 { query = query.Where("o.updated_at >= FROM_UNIXTIME(?)", req.StartTime) } if req.EndTime > 0 { query = query.Where("o.updated_at <= FROM_UNIXTIME(?)", req.EndTime) } err = query.Order("o.updated_at DESC"). Limit(req.Size). Offset(offset). Scan(&orderData).Error if err != nil { l.Errorw("[GetInviteSales] query sales failed", logger.Field("error", err.Error()), logger.Field("user_id", userId)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query sales failed: %v", err.Error()) } // 5. Get sales list const HashSalt = "ppanel_invite_sales_v1" // Fixed Key var list []types.InvitedUserSale for _, order := range orderData { // Calculate unique numeric hash (FNV-64a) h := fnv.New64a() h.Write([]byte(HashSalt)) h.Write([]byte(strconv.FormatInt(order.UserId, 10))) // Truncate to 10 digits using modulo 10^10 hashVal := h.Sum64() % 10000000000 userHashStr := fmt.Sprintf("%010d", hashVal) // Format product name as "{{ quantity }}天VPN服务" productName := fmt.Sprintf("%d天VPN服务", order.Quantity) if order.Quantity <= 0 { productName = "1天VPN服务" } list = append(list, types.InvitedUserSale{ Amount: float64(order.Amount) / 100.0, // Convert cents to dollars UpdatedAt: order.UpdatedAt, UserHash: userHashStr, ProductName: productName, }) } return &types.GetInviteSalesResponse{ Total: totalSales, List: list, }, nil }