package user import ( "context" "encoding/json" logmodel "github.com/perfect-panel/server/internal/model/log" "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/hash" "github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" ) const ( inviteRecordRoleInviter = "inviter" inviteRecordRoleInvitee = "invitee" ) type GetInviteRecordsLogic struct { logger.Logger ctx context.Context svcCtx *svc.ServiceContext } type inviteRecordLog struct { Id int64 `gorm:"column:id"` ObjectId int64 `gorm:"column:object_id"` Content string `gorm:"column:content"` CreatedAt int64 `gorm:"column:created_at"` } type inviteGiftContent struct { OrderNo string `json:"order_no"` Amount int64 `json:"amount"` } type parsedInviteRecordLog struct { log inviteRecordLog content inviteGiftContent } type inviteOrderUser struct { OrderNo string `gorm:"column:order_no"` UserId int64 `gorm:"column:user_id"` SubscriptionUserId int64 `gorm:"column:subscription_user_id"` RefererId int64 `gorm:"column:referer_id"` } // Get invite gift records func NewGetInviteRecordsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetInviteRecordsLogic { return &GetInviteRecordsLogic{ Logger: logger.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *GetInviteRecordsLogic) GetInviteRecords(req *types.GetInviteRecordsRequest) (resp *types.GetInviteRecordsResponse, err error) { u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User) if !ok { l.Errorw("[GetInviteRecords] user not found in context") return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access") } normalizeInviteRecordsPagination(req) visibleUserIds, err := l.resolveInviteRecordVisibleUserIds(u.Id) if err != nil { l.Errorw("[GetInviteRecords] resolve visible users failed", logger.Field("error", err.Error()), logger.Field("user_id", u.Id)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "resolve visible users failed: %v", err.Error()) } query := l.svcCtx.DB.WithContext(l.ctx). Table("system_logs"). Where("type = ? AND object_id IN ?", logmodel.TypeGift.Uint8(), visibleUserIds). Where("JSON_VALID(content) = 1"). Where("JSON_UNQUOTE(JSON_EXTRACT(content, '$.remark')) = ?", "邀请赠送") if req.StartTime > 0 { query = query.Where("created_at >= FROM_UNIXTIME(?)", req.StartTime) } if req.EndTime > 0 { query = query.Where("created_at <= FROM_UNIXTIME(?)", req.EndTime) } var logs []inviteRecordLog if err = query. Select("id, object_id, content, CAST(UNIX_TIMESTAMP(created_at) * 1000 AS SIGNED) AS created_at"). Order("created_at DESC, id DESC"). Scan(&logs).Error; err != nil { l.Errorw("[GetInviteRecords] query logs failed", logger.Field("error", err.Error()), logger.Field("user_id", u.Id)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query logs failed: %v", err.Error()) } parsedLogs, orderNos := l.parseInviteRecordContents(logs) if len(logs) == 0 || len(parsedLogs) == 0 { return &types.GetInviteRecordsResponse{Total: 0, List: []types.InviteRecord{}}, nil } orders, err := l.queryInviteRecordOrders(orderNos) if err != nil { l.Errorw("[GetInviteRecords] query orders failed", logger.Field("error", err.Error()), logger.Field("user_id", u.Id)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query orders failed: %v", err.Error()) } visibleUserIdSet := make(map[int64]struct{}, len(visibleUserIds)) for _, userId := range visibleUserIds { visibleUserIdSet[userId] = struct{}{} } allRecords := make([]types.InviteRecord, 0, len(parsedLogs)) for _, parsed := range parsedLogs { content := parsed.content logItem := parsed.log orderInfo, hasOrder := orders[content.OrderNo] if !l.canViewInviteRecord(u.Id, logItem.ObjectId, visibleUserIdSet, hasOrder, orderInfo) { continue } record := types.InviteRecord{ Role: inviteRecordRoleInviter, GiftDays: content.Amount, OrderNo: content.OrderNo, CreatedAt: logItem.CreatedAt, } if hasOrder { peerId := orderInfo.UserId if orderInfo.UserId == u.Id { record.Role = inviteRecordRoleInvitee peerId = u.RefererId } if peerId > 0 { record.PeerHash = hash.InvitePeerHash(peerId) } } allRecords = append(allRecords, record) } total := int64(len(allRecords)) list := paginateInviteRecords(allRecords, req.Page, req.Size) return &types.GetInviteRecordsResponse{ Total: total, List: list, }, nil } func (l *GetInviteRecordsLogic) resolveInviteRecordVisibleUserIds(currentUserId int64) ([]int64, error) { visibleUserIds := []int64{currentUserId} var relation struct { FamilyId int64 `gorm:"column:family_id"` Role uint8 `gorm:"column:role"` OwnerUserId int64 `gorm:"column:owner_user_id"` } err := l.svcCtx.DB.WithContext(l.ctx). Model(&user.UserFamilyMember{}). Select("user_family_member.family_id, user_family_member.role, user_family.owner_user_id"). Joins("JOIN user_family ON user_family.id = user_family_member.family_id AND user_family.deleted_at IS NULL AND user_family.status = ?", user.FamilyStatusActive). Where("user_family_member.user_id = ? AND user_family_member.status = ? AND user_family_member.deleted_at IS NULL", currentUserId, user.FamilyMemberActive). First(&relation).Error if err == nil { if relation.Role != user.FamilyRoleOwner && relation.OwnerUserId > 0 && relation.OwnerUserId != currentUserId { visibleUserIds = append(visibleUserIds, relation.OwnerUserId) } return visibleUserIds, nil } if errors.Is(err, gorm.ErrRecordNotFound) { return visibleUserIds, nil } return nil, err } func (l *GetInviteRecordsLogic) canViewInviteRecord(currentUserId, logObjectId int64, visibleUserIds map[int64]struct{}, hasOrder bool, orderInfo inviteOrderUser) bool { if _, ok := visibleUserIds[logObjectId]; !ok { return false } if logObjectId == currentUserId { if hasOrder { return orderInfo.UserId == currentUserId || orderInfo.RefererId == currentUserId } return true } return true } func normalizeInviteRecordsPagination(req *types.GetInviteRecordsRequest) { if req.Page < 1 { req.Page = 1 } if req.Size < 1 { req.Size = 10 } if req.Size > 100 { req.Size = 100 } } func paginateInviteRecords(records []types.InviteRecord, page, size int) []types.InviteRecord { start := (page - 1) * size if start >= len(records) { return []types.InviteRecord{} } end := start + size if end > len(records) { end = len(records) } return records[start:end] } func (l *GetInviteRecordsLogic) parseInviteRecordContents(logs []inviteRecordLog) ([]parsedInviteRecordLog, []string) { parsedLogs := make([]parsedInviteRecordLog, 0, len(logs)) orderNos := make([]string, 0, len(logs)) for _, logItem := range logs { var content inviteGiftContent if err := json.Unmarshal([]byte(logItem.Content), &content); err != nil { l.Infow("[GetInviteRecords] parse content failed", logger.Field("error", err.Error()), logger.Field("log_id", logItem.Id)) continue } parsedLogs = append(parsedLogs, parsedInviteRecordLog{log: logItem, content: content}) if content.OrderNo != "" { orderNos = append(orderNos, content.OrderNo) } } return parsedLogs, orderNos } func (l *GetInviteRecordsLogic) queryInviteRecordOrders(orderNos []string) (map[string]inviteOrderUser, error) { orders := make(map[string]inviteOrderUser, len(orderNos)) if len(orderNos) == 0 { return orders, nil } var orderData []inviteOrderUser err := l.svcCtx.DB.WithContext(l.ctx). Table("`order`"). Select("`order`.order_no, `order`.user_id, `order`.subscription_user_id, invitee.referer_id"). Joins("LEFT JOIN user invitee ON invitee.id = `order`.user_id"). Where("`order`.order_no IN ?", orderNos). Scan(&orderData).Error if err != nil { return nil, err } for _, item := range orderData { orders[item.OrderNo] = item } return orders, nil }