package services import ( "context" "errors" "quyun/v2/app/errorx" "quyun/v2/app/http/v1/dto" "quyun/v2/app/requests" "quyun/v2/database/models" "quyun/v2/pkg/consts" "go.ipao.vip/gen/types" "gorm.io/gorm" ) // @provider type tenant struct{} func (s *tenant) List(ctx context.Context, tenantID int64, filter *dto.TenantListFilter) (*requests.Pager, error) { tbl, q := models.TenantQuery.QueryContext(ctx) q = q.Where(tbl.Status.Eq(consts.TenantStatusVerified)) if tenantID > 0 { q = q.Where(tbl.ID.Eq(tenantID)) } if filter.Keyword != nil && *filter.Keyword != "" { q = q.Where(tbl.Name.Like("%" + *filter.Keyword + "%")) } filter.Pagination.Format() total, err := q.Count() if err != nil { return nil, errorx.ErrDatabaseError.WithCause(err) } list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find() if err != nil { return nil, errorx.ErrDatabaseError.WithCause(err) } tenantIDs := make([]int64, 0, len(list)) for _, t := range list { tenantIDs = append(tenantIDs, t.ID) } // 批量统计关注数与内容数,避免逐条 Count。 followerMap, err := s.countFollowers(ctx, tenantIDs) if err != nil { return nil, err } contentMap, err := s.countPublishedContents(ctx, tenantIDs) if err != nil { return nil, err } var data []dto.TenantProfile for _, t := range list { cfg := t.Config.Data() data = append(data, dto.TenantProfile{ ID: t.ID, Name: t.Name, Avatar: cfg.Avatar, Bio: cfg.Bio, Stats: dto.Stats{ Followers: int(followerMap[t.ID]), Contents: int(contentMap[t.ID]), }, }) } return &requests.Pager{ Pagination: filter.Pagination, Total: total, Items: data, }, nil } func (s *tenant) GetPublicProfile(ctx context.Context, tenantID, userID int64) (*dto.TenantProfile, error) { t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tenantID)).First() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errorx.ErrRecordNotFound } return nil, errorx.ErrDatabaseError.WithCause(err) } // Stats followers, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(tenantID)).Count() contents, _ := models.ContentQuery.WithContext(ctx). Where(models.ContentQuery.TenantID.Eq(tenantID), models.ContentQuery.Status.Eq(consts.ContentStatusPublished)). Count() // Following status isFollowing := false if userID > 0 { uid := userID isFollowing, _ = models.TenantUserQuery.WithContext(ctx). Where(models.TenantUserQuery.TenantID.Eq(tenantID), models.TenantUserQuery.UserID.Eq(uid)). Exists() } cfg := t.Config.Data() return &dto.TenantProfile{ ID: t.ID, Name: t.Name, Avatar: cfg.Avatar, Cover: cfg.Cover, Bio: cfg.Bio, Description: cfg.Description, Stats: dto.Stats{ Followers: int(followers), Contents: int(contents), }, IsFollowing: isFollowing, }, nil } func (s *tenant) Follow(ctx context.Context, tenantID, userID int64) error { if userID == 0 { return errorx.ErrUnauthorized } uid := userID // Check if tenant exists t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tenantID)).First() if err != nil { return errorx.ErrRecordNotFound } tu := &models.TenantUser{ TenantID: tenantID, UserID: uid, Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}, Status: consts.UserStatusVerified, } if err := models.TenantUserQuery.WithContext(ctx).Save(tu); err != nil { return errorx.ErrDatabaseError.WithCause(err) } if Notification != nil { _ = Notification.Send(ctx, tenantID, t.UserID, "interaction", "新增粉丝", "有人关注了您的店铺: "+t.Name) } return nil } func (s *tenant) Unfollow(ctx context.Context, tenantID, userID int64) error { if userID == 0 { return errorx.ErrUnauthorized } uid := userID _, err := models.TenantUserQuery.WithContext(ctx). Where(models.TenantUserQuery.TenantID.Eq(tenantID), models.TenantUserQuery.UserID.Eq(uid)). Delete() if err != nil { return errorx.ErrDatabaseError.WithCause(err) } return nil } func (s *tenant) ListFollowed(ctx context.Context, tenantID, userID int64) ([]dto.TenantProfile, error) { if userID == 0 { return nil, errorx.ErrUnauthorized } uid := userID tbl, q := models.TenantUserQuery.QueryContext(ctx) q = q.Where(tbl.UserID.Eq(uid)) if tenantID > 0 { q = q.Where(tbl.TenantID.Eq(tenantID)) } list, err := q.Find() if err != nil { return nil, errorx.ErrDatabaseError.WithCause(err) } if len(list) == 0 { return []dto.TenantProfile{}, nil } tenantIDs := make([]int64, 0, len(list)) tenantIDSet := make(map[int64]struct{}, len(list)) for _, tu := range list { if _, ok := tenantIDSet[tu.TenantID]; ok { continue } tenantIDs = append(tenantIDs, tu.TenantID) tenantIDSet[tu.TenantID] = struct{}{} } tblTenant, qTenant := models.TenantQuery.QueryContext(ctx) tenants, err := qTenant.Where(tblTenant.ID.In(tenantIDs...)).Find() if err != nil { return nil, errorx.ErrDatabaseError.WithCause(err) } tenantMap := make(map[int64]*models.Tenant, len(tenants)) for _, t := range tenants { tenantMap[t.ID] = t } // 批量统计关注数与内容数,避免逐条 Count。 followerMap, err := s.countFollowers(ctx, tenantIDs) if err != nil { return nil, err } contentMap, err := s.countPublishedContents(ctx, tenantIDs) if err != nil { return nil, err } var data []dto.TenantProfile for _, tu := range list { t, ok := tenantMap[tu.TenantID] if !ok { continue } cfg := t.Config.Data() data = append(data, dto.TenantProfile{ ID: t.ID, Name: t.Name, Avatar: cfg.Avatar, Stats: dto.Stats{ Followers: int(followerMap[tu.TenantID]), Contents: int(contentMap[tu.TenantID]), }, IsFollowing: true, }) } return data, nil } // GetModelByID 获取指定 ID 的model func (s *tenant) GetModelByID(ctx context.Context, id int64) (*models.Tenant, error) { tbl, query := models.TenantQuery.QueryContext(ctx) u, err := query.Where(tbl.ID.Eq(id)).First() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errorx.ErrRecordNotFound } return nil, errorx.ErrDatabaseError.WithCause(err) } return u, nil } type tenantStatRow struct { TenantID int64 `gorm:"column:tenant_id"` Total int64 `gorm:"column:total"` } func (s *tenant) countFollowers(ctx context.Context, tenantIDs []int64) (map[int64]int64, error) { if len(tenantIDs) == 0 { return map[int64]int64{}, nil } tbl, q := models.TenantUserQuery.QueryContext(ctx) var rows []tenantStatRow err := q.Where(tbl.TenantID.In(tenantIDs...)). Select(tbl.TenantID, tbl.ALL.Count().As("total")). Group(tbl.TenantID). Scan(&rows) if err != nil { return nil, errorx.ErrDatabaseError.WithCause(err) } result := make(map[int64]int64, len(rows)) for _, row := range rows { result[row.TenantID] = row.Total } return result, nil } func (s *tenant) countPublishedContents(ctx context.Context, tenantIDs []int64) (map[int64]int64, error) { if len(tenantIDs) == 0 { return map[int64]int64{}, nil } tbl, q := models.ContentQuery.QueryContext(ctx) var rows []tenantStatRow err := q.Where(tbl.TenantID.In(tenantIDs...), tbl.Status.Eq(consts.ContentStatusPublished)). Select(tbl.TenantID, tbl.ALL.Count().As("total")). Group(tbl.TenantID). Scan(&rows) if err != nil { return nil, errorx.ErrDatabaseError.WithCause(err) } result := make(map[int64]int64, len(rows)) for _, row := range rows { result[row.TenantID] = row.Total } return result, nil }