spark-server/internal/module/square/logic/notice.go
DESKTOP-L8OUL9F\gfl0034 53426af122 feat: 多广场 channel、音乐公开 API 与订阅 package_name
支持 square/squarenovel/squaresteps/squarehealth 实例与 category 筛选;音乐增加免登录 grouped/track;订阅套餐按 package_name 区分;补 migration 0030/0031 与 OpenAPI。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 09:02:28 +08:00

143 lines
3.9 KiB
Go

package logic
import (
"context"
"time"
"spark-server/internal/module/square/entity"
"spark-server/internal/pkg/errs"
)
type NoticeView struct {
ID int64 `json:"id"`
Kind entity.NoticeKind `json:"kind"`
From UserCard `json:"from"`
PostID int64 `json:"post_id"`
CommentID int64 `json:"comment_id,omitempty"`
Read bool `json:"read"`
CreatedAt int64 `json:"created_at"`
}
type NoticeList struct {
Items []*NoticeView `json:"items"`
NextCursor int64 `json:"next_cursor"`
}
type NoticeUnread struct {
HasNew bool `json:"has_new"`
LatestID int64 `json:"latest_id"`
}
type NoticeReadInput struct {
Cursor int64 `json:"cursor" validate:"omitempty,gte=0" label:"field.square_notice_cursor"`
}
func (s *Service) ListNotices(ctx context.Context, userID int64, channel entity.Channel, cursor int64, limit int) (*NoticeList, error) {
if !s.ready() || userID <= 0 {
return nil, errs.ErrUnauthorized
}
channel = normalizeChannel(channel)
if !channel.Valid() {
return nil, errs.ErrInvalidArgument
}
limit = entity.ClampLimit(limit)
rows, err := s.repo.ListNotices(ctx, userID, channel, cursor, limit)
if err != nil {
return nil, err
}
uids := make([]int64, 0, len(rows))
for _, n := range rows {
uids = append(uids, n.FromUserID)
}
users := s.loadUsers(ctx, uids)
remarks := s.loadRemarks(ctx, userID, uids)
items := make([]*NoticeView, 0, len(rows))
next := int64(0)
for _, n := range rows {
items = append(items, &NoticeView{
ID: n.ID, Kind: n.Kind, From: cardOf(users[n.FromUserID], remarks[n.FromUserID]),
PostID: n.PostID, CommentID: n.CommentID, Read: n.ReadAtMS > 0, CreatedAt: n.CreatedAtMS,
})
next = n.ID
}
return &NoticeList{Items: items, NextCursor: next}, nil
}
func (s *Service) ReadNotices(ctx context.Context, userID int64, channel entity.Channel, in *NoticeReadInput) error {
if !s.ready() || userID <= 0 {
return errs.ErrUnauthorized
}
channel = normalizeChannel(channel)
if !channel.Valid() {
return errs.ErrInvalidArgument
}
cursor := int64(0)
if in != nil {
cursor = in.Cursor
}
if err := s.repo.MarkNoticesRead(ctx, userID, channel, cursor); err != nil {
return err
}
if cursor > 0 {
return s.repo.AdvanceNoticeRead(ctx, userID, channel, cursor)
}
latest, err := s.repo.LatestNoticeID(ctx, userID, channel)
if err != nil {
return err
}
if latest > 0 {
return s.repo.AdvanceNoticeRead(ctx, userID, channel, latest)
}
return nil
}
func (s *Service) NoticeUnread(ctx context.Context, userID int64, channel entity.Channel) (*NoticeUnread, error) {
if !s.ready() || userID <= 0 {
return nil, errs.ErrUnauthorized
}
channel = normalizeChannel(channel)
if !channel.Valid() {
return nil, errs.ErrInvalidArgument
}
st, err := s.repo.GetNoticeState(ctx, userID, channel)
if err != nil {
return nil, err
}
latest, err := s.repo.LatestNoticeID(ctx, userID, channel)
if err != nil {
return nil, err
}
return &NoticeUnread{HasNew: latest > st.LastReadNoticeID, LatestID: latest}, nil
}
func (s *Service) notify(ctx context.Context, rows []*entity.Notice) error {
out := make([]*entity.Notice, 0, len(rows))
now := time.Now().UnixMilli()
for _, n := range rows {
if n == nil || n.ToUserID <= 0 || n.ToUserID == n.FromUserID {
continue
}
id, err := s.ids.NextID()
if err != nil {
return err
}
n.ID = id
n.CreatedAtMS = now
if n.Channel == "" {
n.Channel = entity.ChannelSquare
}
out = append(out, n)
}
return s.repo.CreateNotices(ctx, out)
}
func (s *Service) newNotice(to, from int64, kind entity.NoticeKind, channel entity.Channel, postID, commentID int64) *entity.Notice {
if to <= 0 || to == from {
return nil
}
if channel == "" {
channel = entity.ChannelSquare
}
return &entity.Notice{ToUserID: to, Channel: channel, FromUserID: from, Kind: kind, PostID: postID, CommentID: commentID}
}