后端 GET /im/messages 增加 start/end(最长12个月),前端格式化 ts 并提供日/周/月/年/总/自定义筛选。 Co-authored-by: Cursor <cursoragent@cursor.com>
130 lines
3.6 KiB
Go
130 lines
3.6 KiB
Go
package logic
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"spark-server/internal/pkg/errs"
|
||
)
|
||
|
||
const maxMessageRangeMonths = 12
|
||
|
||
func (s *Service) ImStats(ctx context.Context) (*ImStats, error) {
|
||
if !s.ready() {
|
||
return nil, ErrNotReady
|
||
}
|
||
return s.repo.ImStats(ctx)
|
||
}
|
||
|
||
func (s *Service) ListMessages(ctx context.Context, q string, limit int, start, end string) ([]ImMessageView, error) {
|
||
if !s.ready() {
|
||
return nil, ErrNotReady
|
||
}
|
||
if limit <= 0 || limit > 500 {
|
||
limit = 100
|
||
}
|
||
from, to, err := parseMessageTimeRange(start, end)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
q = strings.TrimSpace(q)
|
||
rows, err := s.repo.ListRecentMessages(ctx, limit*3, from, to)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out := make([]ImMessageView, 0, limit)
|
||
for i := range rows {
|
||
v := messageView(&rows[i])
|
||
if q != "" && !messageMatch(&v, q) {
|
||
continue
|
||
}
|
||
out = append(out, v)
|
||
if len(out) >= limit {
|
||
break
|
||
}
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
// parseMessageTimeRange accepts YYYY-MM-DD start/end (Asia/Shanghai, inclusive calendar days).
|
||
// Empty both means no filter; one-sided or span > 12 months is invalid.
|
||
func parseMessageTimeRange(start, end string) (from, to *time.Time, err error) {
|
||
start = strings.TrimSpace(start)
|
||
end = strings.TrimSpace(end)
|
||
if start == "" && end == "" {
|
||
return nil, nil, nil
|
||
}
|
||
if start == "" || end == "" {
|
||
return nil, nil, errs.ErrInvalidArgument.WithMsg("start 与 end 需同时提供(YYYY-MM-DD)")
|
||
}
|
||
loc, locErr := time.LoadLocation("Asia/Shanghai")
|
||
if locErr != nil {
|
||
loc = time.FixedZone("CST", 8*3600)
|
||
}
|
||
fromDay, err := time.ParseInLocation("2006-01-02", start, loc)
|
||
if err != nil {
|
||
return nil, nil, errs.ErrInvalidArgument.WithMsg("start 日期格式应为 YYYY-MM-DD")
|
||
}
|
||
endDay, err := time.ParseInLocation("2006-01-02", end, loc)
|
||
if err != nil {
|
||
return nil, nil, errs.ErrInvalidArgument.WithMsg("end 日期格式应为 YYYY-MM-DD")
|
||
}
|
||
if endDay.Before(fromDay) {
|
||
return nil, nil, errs.ErrInvalidArgument.WithMsg("end 不能早于 start")
|
||
}
|
||
// Inclusive end day → exclusive upper bound at next midnight.
|
||
toExclusive := endDay.AddDate(0, 0, 1)
|
||
if toExclusive.After(fromDay.AddDate(0, maxMessageRangeMonths, 0)) {
|
||
return nil, nil, errs.ErrInvalidArgument.WithMsg(fmt.Sprintf("查询跨度最长 %d 个月", maxMessageRangeMonths))
|
||
}
|
||
return &fromDay, &toExclusive, nil
|
||
}
|
||
|
||
func (s *Service) ListConversations(ctx context.Context, userID int64) (convs []ImConversationView, groups []GroupView, stats *ImStats, err error) {
|
||
if !s.ready() {
|
||
return nil, nil, nil, ErrNotReady
|
||
}
|
||
if userID <= 0 {
|
||
stats, err = s.ImStats(ctx)
|
||
return nil, nil, stats, err
|
||
}
|
||
rows, err := s.repo.ListUserConversations(ctx, userID)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
convs = make([]ImConversationView, 0, len(rows))
|
||
groups = make([]GroupView, 0)
|
||
seenGroup := map[int64]struct{}{}
|
||
for i := range rows {
|
||
chatType := "private"
|
||
r := &rows[i]
|
||
if r.Type == 2 {
|
||
chatType = "group"
|
||
}
|
||
key := fmt.Sprintf("c:%d", r.ID)
|
||
if chatType == "private" {
|
||
key = fmt.Sprintf("p2p:%d_%d", r.UserSmall, r.UserBig)
|
||
} else if r.GroupID > 0 {
|
||
key = fmt.Sprintf("group:%d", r.GroupID)
|
||
}
|
||
convs = append(convs, ImConversationView{
|
||
Key: key, ChatType: chatType,
|
||
UserA: r.UserSmall, UserB: r.UserBig, GroupID: r.GroupID,
|
||
LastMsgID: r.LastMsgID, UpdatedAt: r.LastMsgAt.Format(time.RFC3339),
|
||
})
|
||
if r.GroupID > 0 {
|
||
if _, ok := seenGroup[r.GroupID]; !ok {
|
||
seenGroup[r.GroupID] = struct{}{}
|
||
gv, gerr := s.GroupDetail(ctx, strconv.FormatInt(r.GroupID, 10))
|
||
if gerr == nil && gv != nil {
|
||
groups = append(groups, *gv)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return convs, groups, nil, nil
|
||
}
|