spark-server/internal/module/seed/logic/group.go
zhr 13b42cef7b chore: 合并本地改动与 main(t_ 表名、lint、音乐分类)
在 fast-forward 到 origin/main 后解决 music logic/repo 冲突,SQL 与模型统一 t_* 表名;Makefile 修正 golangci-lint 变量并令 test/deploy 依赖 lint;补齐 admin/transport 等 lint 修复与 music logic 单测以满足覆盖率门禁。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 10:56:59 +08:00

181 lines
5.8 KiB
Go

package logic
import (
"context"
"errors"
"fmt"
"time"
groupentity "spark-server/internal/module/group/entity"
msgentity "spark-server/internal/module/message/entity"
userentity "spark-server/internal/module/user/entity"
"spark-server/internal/pkg/errs"
)
func (s *Service) ensureSampleGroup(ctx context.Context, bots, targets []*userentity.User) (groupID int64, sampleCount int, err error) {
if len(bots) < userentity.SampleGroupBotCount {
return 0, 0, errs.ErrAvailable
}
owner := bots[0]
existing, err := s.groups.FindByOwnerAndName(ctx, owner.ID, userentity.SampleGroupName)
if err != nil && !errors.Is(err, errs.ErrNotFound) {
return 0, 0, err
}
var g *groupentity.Group
if existing != nil {
g = existing
} else {
g, err = s.createSampleGroup(ctx, bots[:userentity.SampleGroupBotCount])
if err != nil {
return 0, 0, err
}
}
if err := s.ensureGroupMembers(ctx, g, bots[:userentity.SampleGroupBotCount], targets); err != nil {
return 0, 0, err
}
n, err := s.ensureSampleMessages(ctx, g, bots[:userentity.SampleGroupBotCount])
if err != nil {
return 0, 0, err
}
return g.ID, n, nil
}
func (s *Service) createSampleGroup(ctx context.Context, bots []*userentity.User) (*groupentity.Group, error) {
convID, err := s.ids.NextID()
if err != nil {
return nil, err
}
now := time.Now().UnixMilli()
owner := bots[0]
g := &groupentity.Group{
ConversationID: convID, Name: userentity.SampleGroupName, OwnerID: owner.ID,
Status: groupentity.StatusActive, CreatedAtMS: now, UpdatedAtMS: now,
}
members := make([]*groupentity.Member, 0, len(bots))
for i, b := range bots {
role := groupentity.RoleMember
if i == 0 {
role = groupentity.RoleOwner
}
members = append(members, &groupentity.Member{
UserID: b.ID, Role: role, JoinedAtMS: now,
})
}
if err := s.groups.CreateGroup(ctx, g, members); err != nil {
if errors.Is(err, errs.ErrDuplicate) {
return s.groups.FindByOwnerAndName(ctx, owner.ID, userentity.SampleGroupName)
}
return nil, err
}
return g, nil
}
func (s *Service) ensureGroupMembers(ctx context.Context, g *groupentity.Group, bots, targets []*userentity.User) error {
now := time.Now().UnixMilli()
add := func(uid int64) error {
if _, err := s.groups.GetMember(ctx, g.ID, uid); err == nil {
return nil
} else if !errors.Is(err, errs.ErrNotFound) {
return err
}
return ignoreDup(s.groups.AddMembers(ctx, g.ID, []*groupentity.Member{
{UserID: uid, Role: groupentity.RoleMember, JoinedAtMS: now},
}))
}
for _, b := range bots {
if err := add(b.ID); err != nil {
return err
}
}
for _, u := range targets {
if err := add(u.ID); err != nil {
return err
}
}
return nil
}
type sampleSpec struct {
Key string
Sender int // bot index within sample bots slice
Type msgentity.Type
Content *msgentity.Content
}
func (s *Service) ensureSampleMessages(ctx context.Context, g *groupentity.Group, bots []*userentity.User) (int, error) {
specs := sampleSpecs()
n := 0
for i := range specs {
spec := specs[i]
senderID := int64(0)
if spec.Type != msgentity.TypeSystem {
senderID = bots[spec.Sender%len(bots)].ID
}
clientID := fmt.Sprintf("seed:group:sample:%s", spec.Key)
if senderID > 0 {
if _, err := s.msgs.GetByClientMsgID(ctx, senderID, g.ConversationID, clientID); err == nil {
n++
continue
} else if err != nil && !errors.Is(err, errs.ErrNotFound) {
return n, err
}
}
msgID, err := s.ids.NextID()
if err != nil {
return n, err
}
raw, err := spec.Content.Marshal()
if err != nil {
return n, err
}
now := time.Now().UnixMilli()
msg := &msgentity.Message{
ID: msgID, ConversationID: g.ConversationID, SenderID: senderID, GroupID: g.ID,
ClientMsgID: clientID, Type: spec.Type, Kind: msgentity.KindGroup,
Content: raw, Status: msgentity.StatusNormal, CreatedAtMS: now,
}
if err := s.msgs.Create(ctx, msg); err != nil {
if errors.Is(err, errs.ErrDuplicate) {
n++
continue
}
return n, err
}
if err := s.groups.UpdateLastMsg(ctx, g.ConversationID, msgID, now); err != nil {
return n, err
}
n++
}
return n, nil
}
func sampleSpecs() []sampleSpec {
return []sampleSpec{
{Key: "text", Sender: 0, Type: msgentity.TypeText, Content: msgentity.NewText("Hello from Spark Samples — multi-type English demo messages live here.")},
{Key: "image", Sender: 1, Type: msgentity.TypeImage, Content: msgentity.NewImage(&msgentity.Image{
Key: "seed/demo/image.jpg", Mime: "image/jpeg", Width: 800, Height: 600, Size: 120000,
ThumbKey: "seed/demo/image_thumb.jpg",
})},
{Key: "voice", Sender: 2, Type: msgentity.TypeVoice, Content: msgentity.NewVoice(&msgentity.Voice{
Key: "seed/demo/voice.m4a", DurationMS: 3200, Size: 48000,
})},
{Key: "video", Sender: 3, Type: msgentity.TypeVideo, Content: msgentity.NewVideo(&msgentity.Video{
Key: "seed/demo/video.mp4", Mime: "video/mp4", Width: 1280, Height: 720, DurationMS: 8000, Size: 1500000,
CoverKey: "seed/demo/video_cover.jpg",
})},
{Key: "emoji", Sender: 4, Type: msgentity.TypeEmoji, Content: msgentity.NewEmoji(&msgentity.Emoji{Pack: "spark", ID: "wave"})},
{Key: "gif", Sender: 5, Type: msgentity.TypeGIF, Content: msgentity.NewGIF(&msgentity.GIF{
Key: "seed/demo/cheer.gif", Mime: "image/gif", Width: 240, Height: 240, Size: 90000,
})},
{Key: "file", Sender: 6, Type: msgentity.TypeFile, Content: msgentity.NewFile(&msgentity.File{
Key: "seed/demo/readme.pdf", Name: "spark-samples.pdf", Size: 20480, Mime: "application/pdf",
})},
{Key: "location", Sender: 7, Type: msgentity.TypeLocation, Content: msgentity.NewLocation(&msgentity.Location{
Lat: 37.7749, Lng: -122.4194, Name: "Spark HQ", Address: "San Francisco, CA",
})},
{Key: "system", Sender: 0, Type: msgentity.TypeSystem, Content: msgentity.NewSystem(&msgentity.System{
Kind: groupentity.SystemKindCreated, Body: "Sample group created for English multi-type demos.",
})},
}
}