From ba61a11659eb790d98d41ce61d38dd001ccfa56c Mon Sep 17 00:00:00 2001 From: mountain Date: Mon, 17 Jul 2023 17:47:07 +0900 Subject: [PATCH] =?UTF-8?q?gob=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/apiimpl.go | 8 +-- core/group.go | 2 +- core/group_memory.go | 130 ++++++++++++++++++++++++++++++---------- core/redison_handler.go | 12 +++- core/tavern.go | 8 +++ go.mod | 2 +- go.sum | 2 + 7 files changed, 125 insertions(+), 39 deletions(-) diff --git a/core/apiimpl.go b/core/apiimpl.go index 8e58e25..402f0fb 100644 --- a/core/apiimpl.go +++ b/core/apiimpl.go @@ -2,7 +2,6 @@ package core import ( "encoding/json" - "io" "net/http" common "repositories.action2quare.com/ayo/gocommon" @@ -486,14 +485,15 @@ func (sub *subTavern) UpdateGroupDocument(w http.ResponseWriter, r *http.Request return } - body, err := io.ReadAll(r.Body) - if err != nil { + var frag bson.M + dec := json.NewDecoder(r.Body) + if err := dec.Decode(&frag); err != nil { logger.Error("UpdateGroupDocument failed. readBsonDoc err :", err) w.WriteHeader(http.StatusBadRequest) return } - if err := group.UpdateGroupDocument(gid, body); err != nil { + if err := group.UpdateGroupDocument(gid, frag); err != nil { logger.Error("UpdateGroupDocument failed. group.UpdateGroupDocument returns err :", err) w.WriteHeader(http.StatusBadRequest) return diff --git a/core/group.go b/core/group.go index 44de821..7ccade3 100644 --- a/core/group.go +++ b/core/group.go @@ -37,5 +37,5 @@ type group interface { Leave(groupID primitive.ObjectID, memberID primitive.ObjectID) error UpdateMemberDocument(groupID primitive.ObjectID, memberID primitive.ObjectID, doc bson.M) error Dismiss(groupID primitive.ObjectID) error - UpdateGroupDocument(groupID primitive.ObjectID, body []byte) error + UpdateGroupDocument(groupID primitive.ObjectID, doc bson.M) error } diff --git a/core/group_memory.go b/core/group_memory.go index 7b0b8f6..fcdebfb 100644 --- a/core/group_memory.go +++ b/core/group_memory.go @@ -2,7 +2,10 @@ package core import ( "context" + "encoding/gob" + "encoding/json" "errors" + "fmt" "net/url" "time" @@ -18,6 +21,11 @@ import ( type accountID = primitive.ObjectID type ticketID = primitive.ObjectID type groupID = primitive.ObjectID +type Body = bson.M + +func init() { + gob.Register(memberDoc{}) +} func makeTid(gid groupID, in accountID) string { var out primitive.ObjectID @@ -28,32 +36,36 @@ func makeTid(gid groupID, in accountID) string { } type Invitation struct { - GroupID groupID `json:"gid"` - TicketID string `json:"tid"` - Inviter bson.M `json:"inviter"` - ExpireAtUTC int64 `json:"expire_at_utc"` + GroupID groupID `json:"_gid"` + TicketID string `json:"_tid"` + Inviter bson.M `json:"_inviter"` // memberDoc.Body + ExpireAtUTC int64 `json:"_expire_at_utc"` } // 플레이어한테 공유하는 멤버 정보 type memberDoc struct { - Body bson.M `json:"body"` - Invite bool `json:"invite"` - InviteExpire int64 `json:"invite_exp"` + Body `json:",inline"` + Invite bool `json:"_invite"` + InviteExpire int64 `json:"_invite_exp"` } -type GroupDocBody = bson.M type InvitationFail bson.M type groupDoc struct { - Body GroupDocBody `json:"body"` - Members map[string]*memberDoc `json:"members"` - InCharge string `json:"incharge"` + Body `json:",inline"` + Members map[string]*memberDoc `json:"_members"` + InCharge string `json:"_incharge"` rh *RedisonHandler id groupID idhex string } +type groupDocWithId struct { + *groupDoc `json:",inline"` + Gid string `json:"_gid"` +} + func (gd *groupDoc) strid() string { if len(gd.idhex) == 0 { gd.idhex = gd.id.Hex() @@ -75,11 +87,11 @@ func (gd *groupDoc) mid(tid string) accountID { } func (gd *groupDoc) addInvite(inviteeDoc bson.M, ttl time.Duration, max int) (*memberDoc, error) { - mid := inviteeDoc["_mid"].(accountID) - body := inviteeDoc["body"].(bson.M) + targetmid := inviteeDoc["_mid"].(accountID) + targetbody := inviteeDoc["body"].(bson.M) // 초대 가능한 빈 자리가 있나 - tids, err := gd.rh.JSONObjKeys(gd.strid(), "$.members") + tids, err := gd.rh.JSONObjKeys(gd.strid(), "$._members") if err != nil { return nil, err } @@ -87,19 +99,21 @@ func (gd *groupDoc) addInvite(inviteeDoc bson.M, ttl time.Duration, max int) (*m now := time.Now().UTC() createNewDoc := func() *memberDoc { return &memberDoc{ - Body: body, + Body: targetbody, Invite: true, InviteExpire: now.Add(ttl).Unix(), } } + newtid := gd.tid(targetmid) if len(tids) < max { + // 빈자리를 찾았다. newdoc := createNewDoc() - _, err := gd.rh.JSONSet(gd.strid(), "$.members."+gd.tid(mid), newdoc) + _, err := gd.rh.JSONSet(gd.strid(), "$._members."+newtid, newdoc) return newdoc, err } - expires, err := gd.rh.JSONGetInt64(gd.strid(), "$.members..invite_exp") + expires, err := gd.rh.JSONGetInt64(gd.strid(), "$._members.._invite_exp") if err != nil { return nil, err } @@ -108,7 +122,7 @@ func (gd *groupDoc) addInvite(inviteeDoc bson.M, ttl time.Duration, max int) (*m for i, expire := range expires { if expire < now.Unix() { // 만료된 초대가 있네? 지우자 - delpaths = append(delpaths, "$.members."+tids[i]) + delpaths = append(delpaths, "$._members."+tids[i]) } } @@ -122,18 +136,18 @@ func (gd *groupDoc) addInvite(inviteeDoc bson.M, ttl time.Duration, max int) (*m } newdoc := createNewDoc() - _, err = gd.rh.JSONSet(gd.strid(), "$.members."+mid.Hex(), newdoc) + _, err = gd.rh.JSONSet(gd.strid(), "$._members."+newtid, newdoc) return newdoc, err } func (gd *groupDoc) addMember(mid accountID, doc bson.M) (*memberDoc, error) { memdoc := &memberDoc{ + Body: doc, Invite: false, InviteExpire: 0, - Body: doc, } - if _, err := gd.rh.JSONSet(gd.strid(), "$.members."+gd.tid(mid), memdoc, SetOptionXX); err != nil { + if _, err := gd.rh.JSONSet(gd.strid(), "$._members."+gd.tid(mid), memdoc, SetOptionXX); err != nil { return nil, err } @@ -141,7 +155,7 @@ func (gd *groupDoc) addMember(mid accountID, doc bson.M) (*memberDoc, error) { } func (gd *groupDoc) removeMember(mid accountID) error { - _, err := gd.rh.JSONDel(gd.strid(), "$.members."+gd.tid(mid)) + _, err := gd.rh.JSONDel(gd.strid(), "$._members."+gd.tid(mid)) return err } @@ -265,6 +279,15 @@ func (gm *groupInMemory) Invite(gid groupID, mid accountID, inviterDoc bson.M, i } // 내가 wshandler room에 입장 gm.sendEnterRoomMessage(gid, mid) + + gm.sendUpstreamMessage(&wshandler.UpstreamMessage{ + Target: "@" + mid.Hex(), + Body: groupDocWithId{ + groupDoc: gd, + Gid: gd.strid(), + }, + Tag: []string{"GroupDocFull"}, + }) } newdoc, err := gd.addInvite(inviteeDoc, time.Duration(gm.InviteExpire+1)*time.Second, gm.MaxMember) @@ -273,10 +296,11 @@ func (gm *groupInMemory) Invite(gid groupID, mid accountID, inviterDoc bson.M, i } // 초대 중 표시 - _, err = gm.rh.SetNX(gm.rh.ctx, targetid.Hex(), mid.Hex(), time.Duration(gm.InviteExpire)*time.Second).Result() + success, err := gm.rh.SetNX(gm.rh.ctx, targetid.Hex(), mid.Hex(), time.Duration(gm.InviteExpire)*time.Second).Result() if err != nil { return "", err } + logger.Println("invitation key :", targetid.Hex(), success) // invitee에게 알림 gm.sendUpstreamMessage(&wshandler.UpstreamMessage{ @@ -300,6 +324,8 @@ func (gm *groupInMemory) CancelInvitation(gid groupID, tid ticketID) error { var errInvitationExpired = errors.New("invitation is already expired") func (gm *groupInMemory) AcceptInvitation(gid groupID, mid accountID, member bson.M) error { + logger.Println("accept invitation key :", mid.Hex()) + cnt, err := gm.rh.Del(gm.rh.ctx, mid.Hex()).Result() if err != nil { return err @@ -314,9 +340,39 @@ func (gm *groupInMemory) AcceptInvitation(gid groupID, mid accountID, member bso rh: gm.rh, } - _, err = gd.addMember(mid, member) + memberDoc, err := gd.addMember(mid, member) if err == nil { + // 기존 멤버에게 새 멤버를 알림 + gm.sendUpstreamMessage(&wshandler.UpstreamMessage{ + Target: "#" + gid.Hex(), + Body: map[string]any{ + gd.tid(mid): memberDoc, + }, + Tag: []string{"MemberDocFull"}, + }) gm.sendEnterRoomMessage(gid, mid) + + // 새 멤버에 그룹 전체를 알림 + full, err := gm.rh.JSONGet(gd.strid(), "$") + if err != nil { + return err + } + + logger.Println(full) + var temp []groupDoc + err = json.Unmarshal([]byte(full.(string)), &temp) + if err != nil { + return err + } + + gm.sendUpstreamMessage(&wshandler.UpstreamMessage{ + Target: "@" + mid.Hex(), + Body: groupDocWithId{ + groupDoc: &temp[0], + Gid: gd.strid(), + }, + Tag: []string{"GroupDocFull"}, + }) } // 실패 @@ -353,11 +409,11 @@ func (gm *groupInMemory) Leave(gid groupID, mid accountID) error { return err } - // 나한테는 빈 FullGroupDoc을 보낸다. + // 나한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐 gm.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: "@" + mid.Hex(), Body: bson.M{"gid": gid}, - Tag: []string{"FullGroupDoc", gid.Hex()}, + Tag: []string{"GroupDocFull", gid.Hex()}, }) gm.sendLeaveRoomMessage(gid, mid) @@ -369,7 +425,8 @@ func (gm *groupInMemory) UpdateMemberDocument(gid groupID, mid accountID, doc bs id: gid, rh: gm.rh, } - _, err := gm.rh.JSONSet(gd.strid(), "$.members."+gd.tid(mid)+".body", doc, SetOptionXX) + prefixPath := fmt.Sprintf("$._members.%s.", gd.tid(mid)) + err := gm.rh.JSONMSetRel(gd.strid(), prefixPath, doc) if err != nil { return err } @@ -379,7 +436,7 @@ func (gm *groupInMemory) UpdateMemberDocument(gid groupID, mid accountID, doc bs Body: map[string]any{ gd.tid(mid): doc, }, - Tag: []string{"GroupDocBody"}, + Tag: []string{"MemberDocFragment"}, }) return nil @@ -389,13 +446,24 @@ func (gm *groupInMemory) Dismiss(gid groupID) error { return nil } -func (gm *groupInMemory) UpdateGroupDocument(gid groupID, body []byte) error { +func (gm *groupInMemory) UpdateGroupDocument(gid groupID, frag bson.M) error { gd := groupDoc{ id: gid, rh: gm.rh, } - _, err := gm.rh.JSONSet(gd.strid(), "$.members.body", body, SetOptionXX) - return err + + if err := gm.rh.JSONMSetRel(gd.strid(), "$.", frag); err != nil { + return err + } + + // 업데이트 알림 + gm.sendUpstreamMessage(&wshandler.UpstreamMessage{ + Target: "#" + gid.Hex(), + Body: frag, + Tag: []string{"GroupDocFragment"}, + }) + + return nil } func (cfg *groupConfig) prepareInMemory(ctx context.Context, typename string, sub *subTavern) (group, error) { diff --git a/core/redison_handler.go b/core/redison_handler.go index beadec0..4e89b8d 100644 --- a/core/redison_handler.go +++ b/core/redison_handler.go @@ -58,14 +58,18 @@ func appendArgs[T any](args []any, ext ...T) []any { return args } -func (rh *RedisonHandler) JSONMSet(key string, kv map[string]any) error { +func (rh *RedisonHandler) JSONMSetRel(key string, prefixPath string, kv map[string]any) error { + if len(prefixPath) > 0 && !strings.HasSuffix(prefixPath, ".") { + prefixPath += "." + } + pl := rh.Pipeline() for path, obj := range kv { b, err := json.Marshal(obj) if err != nil { return err } - pl.Do(rh.ctx, "JSON.SET", key, path, b) + pl.Do(rh.ctx, "JSON.SET", key, prefixPath+path, b) } cmders, err := pl.Exec(rh.ctx) @@ -81,6 +85,10 @@ func (rh *RedisonHandler) JSONMSet(key string, kv map[string]any) error { return nil } +func (rh *RedisonHandler) JSONMSet(key string, kv map[string]any) error { + return rh.JSONMSetRel(key, "", kv) +} + func (rh *RedisonHandler) JSONSet(key, path string, obj any, opts ...SetOption) (bool, error) { b, err := json.Marshal(obj) if err != nil { diff --git a/core/tavern.go b/core/tavern.go index 47b70aa..7c9cb36 100644 --- a/core/tavern.go +++ b/core/tavern.go @@ -228,6 +228,14 @@ func (sub *subTavern) clientMessageReceived(sender *wshandler.Sender, messageTyp if group := sub.groups[typename]; group != nil { group.UpdateMemberDocument(gidobj, sender.Accid, doc) } + + case "UpdateGroupDocument": + typename := args[0].(string) + gidobj, _ := primitive.ObjectIDFromHex(args[1].(string)) + doc := args[2].(map[string]any) + if group := sub.groups[typename]; group != nil { + group.UpdateGroupDocument(gidobj, doc) + } } } } diff --git a/go.mod b/go.mod index 78b2570..f5a45e9 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/go-redis/redis/v8 v8.11.5 go.mongodb.org/mongo-driver v1.11.7 - repositories.action2quare.com/ayo/gocommon v0.0.0-20230716093911-66aea48fb732 + repositories.action2quare.com/ayo/gocommon v0.0.0-20230717084540-29843802ff0e ) require ( diff --git a/go.sum b/go.sum index 140bd3f..f486b47 100644 --- a/go.sum +++ b/go.sum @@ -110,3 +110,5 @@ repositories.action2quare.com/ayo/gocommon v0.0.0-20230716073702-8f6c87a8aeb8 h1 repositories.action2quare.com/ayo/gocommon v0.0.0-20230716073702-8f6c87a8aeb8/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw= repositories.action2quare.com/ayo/gocommon v0.0.0-20230716093911-66aea48fb732 h1:Aq4E8kn1mN5z4ZpRYo5VFj2KektVNrTTuk0HocYMDCk= repositories.action2quare.com/ayo/gocommon v0.0.0-20230716093911-66aea48fb732/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw= +repositories.action2quare.com/ayo/gocommon v0.0.0-20230717084540-29843802ff0e h1:/eG6tAQzEaN178Aib+/erjHrE/+IjIVLRSmP4gx6D7E= +repositories.action2quare.com/ayo/gocommon v0.0.0-20230717084540-29843802ff0e/go.mod h1:PdpZ16O1czKKxCxn+0AFNaEX/0kssYwC3G8jR0V7ybw=