add: chat service

This commit is contained in:
wwweww
2026-04-24 20:43:53 +08:00
parent 4cc4c96b21
commit 756ca20c6d
43 changed files with 3035 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
package config
import "github.com/zeromicro/go-zero/zrpc"
type Config struct {
zrpc.RpcServerConf
}
@@ -0,0 +1,69 @@
package logic
import (
"context"
"errors"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type AddChatMessagesLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewAddChatMessagesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddChatMessagesLogic {
return &AddChatMessagesLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *AddChatMessagesLogic) AddChatMessages(in *pb.AddChatMessagesReq) (*pb.AddChatMessagesResp, error) {
if in.GetSessionId() <= 0 {
return nil, errors.New("sessionId is required")
}
if in.GetSenderId() <= 0 {
return nil, errors.New("senderId is required")
}
if in.GetContent() == "" {
return nil, errors.New("content is required")
}
store := l.svcCtx.Store
store.Mu.Lock()
defer store.Mu.Unlock()
if _, ok := store.Sessions[in.GetSessionId()]; !ok {
return nil, errors.New("session not found")
}
now := nowUnix(0)
msgType := in.GetType()
if msgType == "" {
msgType = "text"
}
msg := &pb.ChatMessages{
Id: store.NextMessage(),
SessionId: in.GetSessionId(),
SenderId: in.GetSenderId(),
Type: msgType,
Content: in.GetContent(),
CreatedAt: now,
}
store.Messages[msg.Id] = msg
store.SessionMessages[in.GetSessionId()] = append(store.SessionMessages[in.GetSessionId()], msg.Id)
session := store.Sessions[in.GetSessionId()]
session.LastMessage = in.GetContent()
session.LastMessageAt = now
session.UpdatedAt = now
return &pb.AddChatMessagesResp{Id: msg.Id}, nil
}
@@ -0,0 +1,64 @@
package logic
import (
"context"
"errors"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type AddChatSessionsLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewAddChatSessionsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddChatSessionsLogic {
return &AddChatSessionsLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *AddChatSessionsLogic) AddChatSessions(in *pb.AddChatSessionsReq) (*pb.AddChatSessionsResp, error) {
if in.GetType() == "" {
return nil, errors.New("type is required")
}
if in.GetCreatorId() <= 0 {
return nil, errors.New("creatorId is required")
}
store := l.svcCtx.Store
store.Mu.Lock()
defer store.Mu.Unlock()
now := nowUnix(0)
participants := append([]int64(nil), in.GetParticipants()...)
hasCreator := false
for _, p := range participants {
if p == in.GetCreatorId() {
hasCreator = true
break
}
}
if !hasCreator {
participants = append(participants, in.GetCreatorId())
}
session := &pb.ChatSessions{
Id: store.NextSession(),
Type: in.GetType(),
Name: in.GetName(),
CreatorId: in.GetCreatorId(),
Participants: participants,
CreatedAt: now,
UpdatedAt: now,
}
store.Sessions[session.Id] = session
return &pb.AddChatSessionsResp{Id: session.Id}, nil
}
@@ -0,0 +1,46 @@
package logic
import (
"context"
"errors"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type AddParticipantLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewAddParticipantLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddParticipantLogic {
return &AddParticipantLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *AddParticipantLogic) AddParticipant(in *pb.AddParticipantReq) (*pb.AddParticipantResp, error) {
store := l.svcCtx.Store
store.Mu.Lock()
defer store.Mu.Unlock()
session, ok := store.Sessions[in.GetSessionId()]
if !ok {
return nil, errors.New("session not found")
}
for _, p := range session.Participants {
if p == in.GetUserId() {
return &pb.AddParticipantResp{}, nil
}
}
session.Participants = append(session.Participants, in.GetUserId())
session.UpdatedAt = nowUnix(0)
return &pb.AddParticipantResp{}, nil
}
@@ -0,0 +1,45 @@
package logic
import (
"context"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type DelChatMessagesLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewDelChatMessagesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DelChatMessagesLogic {
return &DelChatMessagesLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *DelChatMessagesLogic) DelChatMessages(in *pb.DelChatMessagesReq) (*pb.DelChatMessagesResp, error) {
store := l.svcCtx.Store
store.Mu.Lock()
defer store.Mu.Unlock()
msg, ok := store.Messages[in.GetId()]
if ok {
ids := store.SessionMessages[msg.SessionId]
filtered := make([]int64, 0, len(ids))
for _, id := range ids {
if id != in.GetId() {
filtered = append(filtered, id)
}
}
store.SessionMessages[msg.SessionId] = filtered
}
delete(store.Messages, in.GetId())
return &pb.DelChatMessagesResp{}, nil
}
@@ -0,0 +1,35 @@
package logic
import (
"context"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type DelChatSessionsLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewDelChatSessionsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DelChatSessionsLogic {
return &DelChatSessionsLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *DelChatSessionsLogic) DelChatSessions(in *pb.DelChatSessionsReq) (*pb.DelChatSessionsResp, error) {
store := l.svcCtx.Store
store.Mu.Lock()
defer store.Mu.Unlock()
delete(store.Sessions, in.GetId())
delete(store.SessionMessages, in.GetId())
return &pb.DelChatSessionsResp{}, nil
}
@@ -0,0 +1,38 @@
package logic
import (
"context"
"errors"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type GetChatMessagesByIdLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetChatMessagesByIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetChatMessagesByIdLogic {
return &GetChatMessagesByIdLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *GetChatMessagesByIdLogic) GetChatMessagesById(in *pb.GetChatMessagesByIdReq) (*pb.GetChatMessagesByIdResp, error) {
store := l.svcCtx.Store
store.Mu.RLock()
defer store.Mu.RUnlock()
msg, ok := store.Messages[in.GetId()]
if !ok {
return nil, errors.New("message not found")
}
return &pb.GetChatMessagesByIdResp{ChatMessages: msg}, nil
}
@@ -0,0 +1,38 @@
package logic
import (
"context"
"errors"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type GetChatSessionsByIdLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetChatSessionsByIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetChatSessionsByIdLogic {
return &GetChatSessionsByIdLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *GetChatSessionsByIdLogic) GetChatSessionsById(in *pb.GetChatSessionsByIdReq) (*pb.GetChatSessionsByIdResp, error) {
store := l.svcCtx.Store
store.Mu.RLock()
defer store.Mu.RUnlock()
session, ok := store.Sessions[in.GetId()]
if !ok {
return nil, errors.New("session not found")
}
return &pb.GetChatSessionsByIdResp{ChatSessions: session}, nil
}
+10
View File
@@ -0,0 +1,10 @@
package logic
import "time"
func nowUnix(ts int64) int64 {
if ts > 0 {
return ts
}
return time.Now().Unix()
}
@@ -0,0 +1,47 @@
package logic
import (
"context"
"errors"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type RemoveParticipantLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewRemoveParticipantLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RemoveParticipantLogic {
return &RemoveParticipantLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *RemoveParticipantLogic) RemoveParticipant(in *pb.RemoveParticipantReq) (*pb.RemoveParticipantResp, error) {
store := l.svcCtx.Store
store.Mu.Lock()
defer store.Mu.Unlock()
session, ok := store.Sessions[in.GetSessionId()]
if !ok {
return nil, errors.New("session not found")
}
filtered := make([]int64, 0, len(session.Participants))
for _, p := range session.Participants {
if p != in.GetUserId() {
filtered = append(filtered, p)
}
}
session.Participants = filtered
session.UpdatedAt = nowUnix(0)
return &pb.RemoveParticipantResp{}, nil
}
@@ -0,0 +1,58 @@
package logic
import (
"context"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type SearchChatMessagesLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewSearchChatMessagesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SearchChatMessagesLogic {
return &SearchChatMessagesLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *SearchChatMessagesLogic) SearchChatMessages(in *pb.SearchChatMessagesReq) (*pb.SearchChatMessagesResp, error) {
store := l.svcCtx.Store
store.Mu.RLock()
defer store.Mu.RUnlock()
msgIDs := store.SessionMessages[in.GetSessionId()]
var results []*pb.ChatMessages
for _, id := range msgIDs {
msg, ok := store.Messages[id]
if !ok {
continue
}
if in.SenderId != nil && msg.SenderId != *in.SenderId {
continue
}
results = append(results, msg)
}
limit := in.GetLimit()
if limit <= 0 {
limit = 20
}
offset := in.GetPage() * limit
if offset >= int64(len(results)) {
return &pb.SearchChatMessagesResp{}, nil
}
end := offset + limit
if end > int64(len(results)) {
end = int64(len(results))
}
return &pb.SearchChatMessagesResp{ChatMessages: results[offset:end]}, nil
}
@@ -0,0 +1,65 @@
package logic
import (
"context"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type SearchChatSessionsLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewSearchChatSessionsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SearchChatSessionsLogic {
return &SearchChatSessionsLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *SearchChatSessionsLogic) SearchChatSessions(in *pb.SearchChatSessionsReq) (*pb.SearchChatSessionsResp, error) {
store := l.svcCtx.Store
store.Mu.RLock()
defer store.Mu.RUnlock()
var results []*pb.ChatSessions
for _, s := range store.Sessions {
if in.Type != nil && s.Type != *in.Type {
continue
}
if in.UserId != nil {
found := false
for _, p := range s.Participants {
if p == *in.UserId {
found = true
break
}
}
if !found {
continue
}
}
results = append(results, s)
}
limit := in.GetLimit()
if limit <= 0 {
limit = 20
}
offset := in.GetPage() * limit
if offset >= int64(len(results)) {
return &pb.SearchChatSessionsResp{}, nil
}
end := offset + limit
if end > int64(len(results)) {
end = int64(len(results))
}
return &pb.SearchChatSessionsResp{ChatSessions: results[offset:end]}, nil
}
@@ -0,0 +1,49 @@
package logic
import (
"context"
"errors"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateChatSessionsLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUpdateChatSessionsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateChatSessionsLogic {
return &UpdateChatSessionsLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *UpdateChatSessionsLogic) UpdateChatSessions(in *pb.UpdateChatSessionsReq) (*pb.UpdateChatSessionsResp, error) {
store := l.svcCtx.Store
store.Mu.Lock()
defer store.Mu.Unlock()
session, ok := store.Sessions[in.GetId()]
if !ok {
return nil, errors.New("session not found")
}
if in.Name != nil {
session.Name = *in.Name
}
if in.LastMessage != nil {
session.LastMessage = *in.LastMessage
}
if in.LastMessageAt != nil {
session.LastMessageAt = *in.LastMessageAt
}
session.UpdatedAt = nowUnix(0)
return &pb.UpdateChatSessionsResp{}, nil
}
@@ -0,0 +1,75 @@
package server
import (
"context"
"juwan-backend/app/chat/rpc/internal/logic"
"juwan-backend/app/chat/rpc/internal/svc"
"juwan-backend/app/chat/rpc/pb"
)
type ChatServiceServer struct {
svcCtx *svc.ServiceContext
pb.UnimplementedChatServiceServer
}
func NewChatServiceServer(svcCtx *svc.ServiceContext) *ChatServiceServer {
return &ChatServiceServer{
svcCtx: svcCtx,
}
}
func (s *ChatServiceServer) AddChatSessions(ctx context.Context, in *pb.AddChatSessionsReq) (*pb.AddChatSessionsResp, error) {
l := logic.NewAddChatSessionsLogic(ctx, s.svcCtx)
return l.AddChatSessions(in)
}
func (s *ChatServiceServer) UpdateChatSessions(ctx context.Context, in *pb.UpdateChatSessionsReq) (*pb.UpdateChatSessionsResp, error) {
l := logic.NewUpdateChatSessionsLogic(ctx, s.svcCtx)
return l.UpdateChatSessions(in)
}
func (s *ChatServiceServer) DelChatSessions(ctx context.Context, in *pb.DelChatSessionsReq) (*pb.DelChatSessionsResp, error) {
l := logic.NewDelChatSessionsLogic(ctx, s.svcCtx)
return l.DelChatSessions(in)
}
func (s *ChatServiceServer) GetChatSessionsById(ctx context.Context, in *pb.GetChatSessionsByIdReq) (*pb.GetChatSessionsByIdResp, error) {
l := logic.NewGetChatSessionsByIdLogic(ctx, s.svcCtx)
return l.GetChatSessionsById(in)
}
func (s *ChatServiceServer) SearchChatSessions(ctx context.Context, in *pb.SearchChatSessionsReq) (*pb.SearchChatSessionsResp, error) {
l := logic.NewSearchChatSessionsLogic(ctx, s.svcCtx)
return l.SearchChatSessions(in)
}
func (s *ChatServiceServer) AddParticipant(ctx context.Context, in *pb.AddParticipantReq) (*pb.AddParticipantResp, error) {
l := logic.NewAddParticipantLogic(ctx, s.svcCtx)
return l.AddParticipant(in)
}
func (s *ChatServiceServer) RemoveParticipant(ctx context.Context, in *pb.RemoveParticipantReq) (*pb.RemoveParticipantResp, error) {
l := logic.NewRemoveParticipantLogic(ctx, s.svcCtx)
return l.RemoveParticipant(in)
}
func (s *ChatServiceServer) AddChatMessages(ctx context.Context, in *pb.AddChatMessagesReq) (*pb.AddChatMessagesResp, error) {
l := logic.NewAddChatMessagesLogic(ctx, s.svcCtx)
return l.AddChatMessages(in)
}
func (s *ChatServiceServer) DelChatMessages(ctx context.Context, in *pb.DelChatMessagesReq) (*pb.DelChatMessagesResp, error) {
l := logic.NewDelChatMessagesLogic(ctx, s.svcCtx)
return l.DelChatMessages(in)
}
func (s *ChatServiceServer) GetChatMessagesById(ctx context.Context, in *pb.GetChatMessagesByIdReq) (*pb.GetChatMessagesByIdResp, error) {
l := logic.NewGetChatMessagesByIdLogic(ctx, s.svcCtx)
return l.GetChatMessagesById(in)
}
func (s *ChatServiceServer) SearchChatMessages(ctx context.Context, in *pb.SearchChatMessagesReq) (*pb.SearchChatMessagesResp, error) {
l := logic.NewSearchChatMessagesLogic(ctx, s.svcCtx)
return l.SearchChatMessages(in)
}
@@ -0,0 +1,15 @@
package svc
import "juwan-backend/app/chat/rpc/internal/config"
type ServiceContext struct {
Config config.Config
Store *ChatStore
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Store: NewChatStore(),
}
}
+38
View File
@@ -0,0 +1,38 @@
package svc
import (
"sync"
"juwan-backend/app/chat/rpc/pb"
)
type ChatStore struct {
Mu sync.RWMutex
nextSessionID int64
nextMessageID int64
Sessions map[int64]*pb.ChatSessions
Messages map[int64]*pb.ChatMessages
SessionMessages map[int64][]int64
}
func NewChatStore() *ChatStore {
return &ChatStore{
nextSessionID: 1000,
nextMessageID: 1000,
Sessions: make(map[int64]*pb.ChatSessions),
Messages: make(map[int64]*pb.ChatMessages),
SessionMessages: make(map[int64][]int64),
}
}
func (s *ChatStore) NextSession() int64 {
s.nextSessionID++
return s.nextSessionID
}
func (s *ChatStore) NextMessage() int64 {
s.nextMessageID++
return s.nextMessageID
}