package core import ( "errors" "net/http" "repositories.action2quare.com/ayo/gocommon" "repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/wshandler" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) type instantDoc struct { Members map[string]primitive.M `json:"_members"` Count int64 `json:"_count"` Body primitive.M `json:"_body"` Gid primitive.ObjectID `json:"_gid"` rh *gocommon.RedisonHandler idstr string } func (gd *instantDoc) strid() string { if len(gd.idstr) == 0 { gd.idstr = gd.Gid.Hex() } return gd.idstr } func (gd *instantDoc) tid(in accountID) string { return makeTid(gd.Gid, in) } var errGroupAlreadyDestroyed = errors.New("instant group is already destroyed") func (gd *instantDoc) removeMember(mid accountID) error { counts, _ := gd.rh.JSONNumIncrBy(gd.strid(), "$._count", -1) if len(counts) == 0 { // 이미 지워진 인스턴트그룹 return errGroupAlreadyDestroyed } if _, err := gd.rh.JSONDel(gd.strid(), "$._members."+gd.tid(mid)); err != nil { return err } gd.Count = counts[0] return nil } type groupInstant struct { sendUpstreamMessage func(*wshandler.UpstreamMessage) enterRoom func(groupID, accountID) leaveRoom func(groupID, accountID) rh *gocommon.RedisonHandler } func (gi *groupInstant) Initialize(tv *Tavern) error { gi.rh = tv.redison gi.sendUpstreamMessage = func(msg *wshandler.UpstreamMessage) { tv.wsh.SendUpstreamMessage(msg) } gi.enterRoom = func(gid groupID, accid accountID) { tv.wsh.EnterRoom(gid.Hex(), accid) } gi.leaveRoom = func(gid groupID, accid accountID) { tv.wsh.LeaveRoom(gid.Hex(), accid) } return nil } func (gi *groupInstant) UpdateInstantDocument(w http.ResponseWriter, r *http.Request) { var data struct { Gid primitive.ObjectID Doc primitive.M Result string } if err := gocommon.MakeDecoder(r).Decode(&data); err != nil { logger.Println("UpdateInstantDocument failed. Decode returns err :", err) w.WriteHeader(http.StatusInternalServerError) return } gd := partyDoc{ id: data.Gid, rh: gi.rh, } if err := gi.rh.JSONMSetRel(gd.strid(), "$.", data.Doc); err != nil { logger.Println("UpdateInstantDocument failed. JSONMSetRel returns err :", err) w.WriteHeader(http.StatusInternalServerError) return } // 업데이트 알림 gi.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: "#" + gd.strid(), Body: data.Doc, Tag: []string{"GroupDocFragment"}, }) if data.Result == "after" { fulldoc := gd.loadFull() if fulldoc != nil { tids := fulldoc["_members"].(map[string]any) mids := make(map[string]any) for k, v := range tids { mid := midFromTid(data.Gid, k) mids[mid.Hex()] = v } fulldoc["_members"] = mids } gocommon.MakeEncoder(w, r).Encode(fulldoc) } } func (gi *groupInstant) Build(w http.ResponseWriter, r *http.Request) { var data struct { Members []struct { Mid primitive.ObjectID Character primitive.M } Body primitive.M } if err := gocommon.MakeDecoder(r).Decode(&data); err != nil { logger.Println("JoinParty failed. DecodeGob returns err :", err) w.WriteHeader(http.StatusInternalServerError) return } // data.Members가 다 살아있는지 부터 확인 var result struct { Gid primitive.ObjectID // 실패하면 없음 Success []primitive.ObjectID } for _, m := range data.Members { count, err := gi.rh.Exists(gi.rh.Context(), m.Mid.Hex()).Result() if err != nil { logger.Println("instant.Build failed. Exists returns err :", err) w.WriteHeader(http.StatusInternalServerError) return } if count != 0 { result.Success = append(result.Success, m.Mid) } } if len(result.Success) < len(data.Members) { // 이탈자가 있군 // 여기서 중단 gocommon.MakeEncoder(w, r).Encode(result) return } newid := primitive.NewObjectID() members := make(map[string]primitive.M) for _, m := range data.Members { tid := makeTid(newid, m.Mid) members[tid] = m.Character } gd := &instantDoc{ Members: members, Body: data.Body, Count: int64(len(members)), rh: gi.rh, Gid: newid, } _, err := gi.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX) if err != nil { logger.Println("instant.Build failed. JSONSet returns err :", err) w.WriteHeader(http.StatusInternalServerError) return } result.Gid = newid for _, char := range members { delete(char, "_id") } // 멤버에 그룹 전체를 알림 for _, m := range data.Members { midstr := m.Mid.Hex() gi.rh.JSONSet(midstr, "$.instant", bson.M{"id": gd.strid()}) gi.enterRoom(newid, m.Mid) gi.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: midstr, Body: gd, Tag: []string{"GroupDocFull"}, }) } gocommon.MakeEncoder(w, r).Encode(result) } func (gi *groupInstant) makeClientClean(accid primitive.ObjectID) { gids, _ := gi.rh.JSONGetString(accid.Hex(), "$.instant.id") if len(gids) > 0 && len(gids[0]) > 0 { gidstr := gids[0] gid, _ := primitive.ObjectIDFromHex(gidstr) gi.rh.JSONDel(accid.Hex(), "$.instant.id") gi.leaveRoom(gid, accid) // gid에는 제거 메시지 보냄 gd := instantDoc{ Gid: gid, rh: gi.rh, } gi.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: "#" + gd.strid(), Body: bson.M{ gd.tid(accid): bson.M{}, }, Tag: []string{"MemberDocFull"}, }) gd.removeMember(accid) if gd.Count == 0 { gd.rh.Del(gd.rh.Context(), gd.strid()).Result() } } } func (gi *groupInstant) MakeClientClean(w http.ResponseWriter, r *http.Request) { var accid primitive.ObjectID if err := gocommon.MakeDecoder(r).Decode(&accid); err != nil { logger.Println("MakeClientClean failed. decode returns err :", err) return } gi.makeClientClean(accid) } func (gi *groupInstant) ClientDisconnected(msg string, callby *wshandler.Sender) { gi.makeClientClean(callby.Accid) }