feat(P3): 私信 + #话题标签 + @提及 完成
This commit is contained in:
@@ -1,82 +1,82 @@
|
||||
package feed
|
||||
|
||||
import "time"
|
||||
|
||||
type FeedAuthor struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type FeedVideoItem struct {
|
||||
ID uint `json:"id"`
|
||||
Author FeedAuthor `json:"author"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
PlayURL string `json:"play_url"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
CreateTime int64 `json:"create_time"`
|
||||
LikesCount int64 `json:"likes_count"`
|
||||
IsLiked bool `json:"is_liked"`
|
||||
}
|
||||
|
||||
type ListLatestRequest struct {
|
||||
Limit int `json:"limit"`
|
||||
LatestTime int64 `json:"latest_time"`
|
||||
}
|
||||
|
||||
type ListLatestResponse struct {
|
||||
VideoList []FeedVideoItem `json:"video_list"`
|
||||
NextTime int64 `json:"next_time"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
type ListLikesCountRequest struct {
|
||||
Limit int `json:"limit"`
|
||||
LikesCountBefore *int64 `json:"likes_count_before,omitempty"`
|
||||
IDBefore *uint `json:"id_before,omitempty"`
|
||||
}
|
||||
|
||||
type LikesCountCursor struct {
|
||||
LikesCount int64
|
||||
ID uint
|
||||
}
|
||||
|
||||
type ListLikesCountResponse struct {
|
||||
VideoList []FeedVideoItem `json:"video_list"`
|
||||
NextLikesCountBefore *int64 `json:"next_likes_count_before,omitempty"`
|
||||
NextIDBefore *uint `json:"next_id_before,omitempty"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
type ListByFollowingRequest struct {
|
||||
Limit int `json:"limit"`
|
||||
LatestTime int64 `json:"latest_time"`
|
||||
}
|
||||
|
||||
type ListByFollowingResponse struct {
|
||||
VideoList []FeedVideoItem `json:"video_list"`
|
||||
NextTime int64 `json:"next_time"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
type ListByPopularityRequest struct {
|
||||
Limit int `json:"limit"`
|
||||
AsOf int64 `json:"as_of"` // 服务器返回的分钟时间戳;第一页传0
|
||||
Offset int `json:"offset"` // 下一页从这里开始;第一页传0
|
||||
LatestIDBefore *uint `json:"latest_id_before,omitempty"`
|
||||
|
||||
// DB fallback 用(可选)
|
||||
LatestPopularity int64 `json:"latest_popularity"`
|
||||
LatestBefore time.Time `json:"latest_before"`
|
||||
}
|
||||
|
||||
type ListByPopularityResponse struct {
|
||||
VideoList []FeedVideoItem `json:"video_list"`
|
||||
AsOf int64 `json:"as_of"`
|
||||
NextOffset int `json:"next_offset"`
|
||||
HasMore bool `json:"has_more"`
|
||||
|
||||
NextLatestPopularity *int64 `json:"next_latest_popularity,omitempty"`
|
||||
NextLatestBefore *time.Time `json:"next_latest_before,omitempty"`
|
||||
NextLatestIDBefore *uint `json:"next_latest_id_before,omitempty"`
|
||||
}
|
||||
package feed
|
||||
|
||||
import "time"
|
||||
|
||||
type FeedAuthor struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type FeedVideoItem struct {
|
||||
ID uint `json:"id"`
|
||||
Author FeedAuthor `json:"author"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
PlayURL string `json:"play_url"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
CreateTime int64 `json:"create_time"`
|
||||
LikesCount int64 `json:"likes_count"`
|
||||
IsLiked bool `json:"is_liked"`
|
||||
}
|
||||
|
||||
type ListLatestRequest struct {
|
||||
Limit int `json:"limit"`
|
||||
LatestTime int64 `json:"latest_time"`
|
||||
}
|
||||
|
||||
type ListLatestResponse struct {
|
||||
VideoList []FeedVideoItem `json:"video_list"`
|
||||
NextTime int64 `json:"next_time"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
type ListLikesCountRequest struct {
|
||||
Limit int `json:"limit"`
|
||||
LikesCountBefore *int64 `json:"likes_count_before,omitempty"`
|
||||
IDBefore *uint `json:"id_before,omitempty"`
|
||||
}
|
||||
|
||||
type LikesCountCursor struct {
|
||||
LikesCount int64
|
||||
ID uint
|
||||
}
|
||||
|
||||
type ListLikesCountResponse struct {
|
||||
VideoList []FeedVideoItem `json:"video_list"`
|
||||
NextLikesCountBefore *int64 `json:"next_likes_count_before,omitempty"`
|
||||
NextIDBefore *uint `json:"next_id_before,omitempty"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
type ListByFollowingRequest struct {
|
||||
Limit int `json:"limit"`
|
||||
LatestTime int64 `json:"latest_time"`
|
||||
}
|
||||
|
||||
type ListByFollowingResponse struct {
|
||||
VideoList []FeedVideoItem `json:"video_list"`
|
||||
NextTime int64 `json:"next_time"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
type ListByPopularityRequest struct {
|
||||
Limit int `json:"limit"`
|
||||
AsOf int64 `json:"as_of"` // 服务器返回的分钟时间戳;第一页传0
|
||||
Offset int `json:"offset"` // 下一页从这里开始;第一页传0
|
||||
LatestIDBefore *uint `json:"latest_id_before,omitempty"`
|
||||
|
||||
// DB fallback 用(可选)
|
||||
LatestPopularity int64 `json:"latest_popularity"`
|
||||
LatestBefore time.Time `json:"latest_before"`
|
||||
}
|
||||
|
||||
type ListByPopularityResponse struct {
|
||||
VideoList []FeedVideoItem `json:"video_list"`
|
||||
AsOf int64 `json:"as_of"`
|
||||
NextOffset int `json:"next_offset"`
|
||||
HasMore bool `json:"has_more"`
|
||||
|
||||
NextLatestPopularity *int64 `json:"next_latest_popularity,omitempty"`
|
||||
NextLatestBefore *time.Time `json:"next_latest_before,omitempty"`
|
||||
NextLatestIDBefore *uint `json:"next_latest_id_before,omitempty"`
|
||||
}
|
||||
|
||||
@@ -174,3 +174,28 @@ func nonNilFeedVideoItems(items []FeedVideoItem) []FeedVideoItem {
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func (h *FeedHandler) ListByTag(c *gin.Context) {
|
||||
var req struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if req.TagName == "" {
|
||||
c.JSON(400, gin.H{"error": "tag_name is required"})
|
||||
return
|
||||
}
|
||||
if req.Limit <= 0 || req.Limit > 50 {
|
||||
req.Limit = 10
|
||||
}
|
||||
viewerAccountID, _ := jwt.GetAccountID(c)
|
||||
items, err := h.service.ListByTag(c.Request.Context(), req.TagName, req.Limit, viewerAccountID)
|
||||
if err != nil {
|
||||
c.JSON(apierror.ClassifyHTTPStatus(err), gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"video_list": nonNilFeedVideoItems(items)})
|
||||
}
|
||||
|
||||
@@ -1,103 +1,115 @@
|
||||
package feed
|
||||
|
||||
import (
|
||||
"context"
|
||||
"feedsystem_video_go/internal/social"
|
||||
"feedsystem_video_go/internal/video"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type FeedRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewFeedRepository(db *gorm.DB) *FeedRepository {
|
||||
return &FeedRepository{db: db}
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) ListLatest(ctx context.Context, limit int, latestBefore time.Time) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
query := repo.db.WithContext(ctx).Model(&video.Video{}).
|
||||
Order("create_time DESC")
|
||||
if !latestBefore.IsZero() {
|
||||
query = query.Where("create_time < ?", latestBefore)
|
||||
}
|
||||
if err := query.Limit(limit).Find(&videos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return videos, nil
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) ListLikesCountWithCursor(ctx context.Context, limit int, cursor *LikesCountCursor) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
query := repo.db.WithContext(ctx).Model(&video.Video{}).
|
||||
Order("likes_count DESC, id DESC")
|
||||
|
||||
if cursor != nil {
|
||||
query = query.Where(
|
||||
"(likes_count < ?) OR (likes_count = ? AND id < ?)",
|
||||
cursor.LikesCount,
|
||||
cursor.LikesCount, cursor.ID,
|
||||
)
|
||||
}
|
||||
|
||||
if err := query.Limit(limit).Find(&videos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return videos, nil
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) ListByFollowing(ctx context.Context, limit int, viewerAccountID uint, latestBefore time.Time) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
query := repo.db.WithContext(ctx).Model(&video.Video{}).
|
||||
Order("create_time DESC")
|
||||
if viewerAccountID > 0 {
|
||||
followingSubQuery := repo.db.WithContext(ctx).
|
||||
Model(&social.Social{}).
|
||||
Select("vlogger_id").
|
||||
Where("follower_id = ?", viewerAccountID)
|
||||
query = query.Where("author_id IN (?)", followingSubQuery)
|
||||
}
|
||||
if !latestBefore.IsZero() {
|
||||
query = query.Where("create_time < ?", latestBefore)
|
||||
}
|
||||
if err := query.Limit(limit).Find(&videos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return videos, nil
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) ListByPopularity(ctx context.Context, limit int, popularityBefore int64, timeBefore time.Time, idBefore uint) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
query := repo.db.WithContext(ctx).Model(&video.Video{}).
|
||||
Order("popularity DESC, create_time DESC, id DESC")
|
||||
|
||||
// 只有当游标完整提供时才加过滤(popularity 允许为 0)
|
||||
if !timeBefore.IsZero() && idBefore > 0 {
|
||||
query = query.Where(
|
||||
"(popularity < ?) OR (popularity = ? AND create_time < ?) OR (popularity = ? AND create_time = ? AND id < ?)",
|
||||
popularityBefore,
|
||||
popularityBefore, timeBefore,
|
||||
popularityBefore, timeBefore, idBefore,
|
||||
)
|
||||
}
|
||||
|
||||
if err := query.Limit(limit).Find(&videos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return videos, nil
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) GetByIDs(ctx context.Context, ids []uint) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
if len(ids) == 0 {
|
||||
return videos, nil
|
||||
}
|
||||
if err := repo.db.WithContext(ctx).Model(&video.Video{}).
|
||||
Where("id IN ?", ids).Find(&videos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return videos, nil
|
||||
package feed
|
||||
|
||||
import (
|
||||
"context"
|
||||
"feedsystem_video_go/internal/social"
|
||||
"feedsystem_video_go/internal/video"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type FeedRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewFeedRepository(db *gorm.DB) *FeedRepository {
|
||||
return &FeedRepository{db: db}
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) ListLatest(ctx context.Context, limit int, latestBefore time.Time) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
query := repo.db.WithContext(ctx).Model(&video.Video{}).
|
||||
Order("create_time DESC")
|
||||
if !latestBefore.IsZero() {
|
||||
query = query.Where("create_time < ?", latestBefore)
|
||||
}
|
||||
if err := query.Limit(limit).Find(&videos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return videos, nil
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) ListLikesCountWithCursor(ctx context.Context, limit int, cursor *LikesCountCursor) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
query := repo.db.WithContext(ctx).Model(&video.Video{}).
|
||||
Order("likes_count DESC, id DESC")
|
||||
|
||||
if cursor != nil {
|
||||
query = query.Where(
|
||||
"(likes_count < ?) OR (likes_count = ? AND id < ?)",
|
||||
cursor.LikesCount,
|
||||
cursor.LikesCount, cursor.ID,
|
||||
)
|
||||
}
|
||||
|
||||
if err := query.Limit(limit).Find(&videos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return videos, nil
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) ListByFollowing(ctx context.Context, limit int, viewerAccountID uint, latestBefore time.Time) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
query := repo.db.WithContext(ctx).Model(&video.Video{}).
|
||||
Order("create_time DESC")
|
||||
if viewerAccountID > 0 {
|
||||
followingSubQuery := repo.db.WithContext(ctx).
|
||||
Model(&social.Social{}).
|
||||
Select("vlogger_id").
|
||||
Where("follower_id = ?", viewerAccountID)
|
||||
query = query.Where("author_id IN (?)", followingSubQuery)
|
||||
}
|
||||
if !latestBefore.IsZero() {
|
||||
query = query.Where("create_time < ?", latestBefore)
|
||||
}
|
||||
if err := query.Limit(limit).Find(&videos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return videos, nil
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) ListByPopularity(ctx context.Context, limit int, popularityBefore int64, timeBefore time.Time, idBefore uint) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
query := repo.db.WithContext(ctx).Model(&video.Video{}).
|
||||
Order("popularity DESC, create_time DESC, id DESC")
|
||||
|
||||
// 只有当游标完整提供时才加过滤(popularity 允许为 0)
|
||||
if !timeBefore.IsZero() && idBefore > 0 {
|
||||
query = query.Where(
|
||||
"(popularity < ?) OR (popularity = ? AND create_time < ?) OR (popularity = ? AND create_time = ? AND id < ?)",
|
||||
popularityBefore,
|
||||
popularityBefore, timeBefore,
|
||||
popularityBefore, timeBefore, idBefore,
|
||||
)
|
||||
}
|
||||
|
||||
if err := query.Limit(limit).Find(&videos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return videos, nil
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) GetByIDs(ctx context.Context, ids []uint) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
if len(ids) == 0 {
|
||||
return videos, nil
|
||||
}
|
||||
if err := repo.db.WithContext(ctx).Model(&video.Video{}).
|
||||
Where("id IN ?", ids).Find(&videos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return videos, nil
|
||||
}
|
||||
|
||||
func (repo *FeedRepository) ListByTag(ctx context.Context, tagName string, limit int) ([]*video.Video, error) {
|
||||
var videos []*video.Video
|
||||
err := repo.db.WithContext(ctx).Model(&video.Video{}).Table("videos").
|
||||
Joins("JOIN video_tags ON video_tags.video_id = videos.id").
|
||||
Joins("JOIN tags ON tags.id = video_tags.tag_id").
|
||||
Where("tags.name = ?", tagName).
|
||||
Order("videos.create_time desc").
|
||||
Limit(limit).
|
||||
Find(&videos).Error
|
||||
return videos, err
|
||||
}
|
||||
|
||||
@@ -545,3 +545,11 @@ func buildOrderedResult(orderedIDs []uint, dataMap map[uint]*video.Video) []*vid
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (f *FeedService) ListByTag(ctx context.Context, tagName string, limit int, viewerAccountID uint) ([]FeedVideoItem, error) {
|
||||
videos, err := f.repo.ListByTag(ctx, tagName, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.buildFeedVideos(ctx, videos, viewerAccountID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user