diff --git a/core/group_instant.go b/core/group_instant.go index 008577a..abdb772 100644 --- a/core/group_instant.go +++ b/core/group_instant.go @@ -1,11 +1,9 @@ package core import ( - "encoding/json" "errors" "net/http" - "github.com/go-redis/redis/v8" "repositories.action2quare.com/ayo/gocommon" "repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/wshandler" @@ -15,48 +13,15 @@ import ( ) type instantDoc struct { - Members map[string]any `json:"_members"` - Count int64 `json:"_count"` - Body primitive.M `json:"_body"` - Gid primitive.ObjectID `json:"_gid"` + 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) loadMemberFull(tid string) (bson.M, error) { - full, err := gd.rh.JSONGet(gd.strid(), "$._members."+tid) - if err != nil { - return nil, err - } - - bt := []byte(full.(string)) - bt = bt[1 : len(bt)-1] - - var doc bson.M - if err = json.Unmarshal(bt, &doc); err != nil { - return nil, err - } - - return doc, nil -} - -func (gd *instantDoc) loadFull() (doc bson.M) { - // 새 멤버에 그룹 전체를 알림 - full, err := gd.rh.JSONGet(gd.strid(), "$") - if err == nil { - bt := []byte(full.(string)) - bt = bt[1 : len(bt)-1] - err = json.Unmarshal(bt, &doc) - if err != nil { - logger.Println("loadFull err :", err) - } - } else { - logger.Println("loadFull err :", err) - } - return -} - func (gd *instantDoc) strid() string { if len(gd.idstr) == 0 { gd.idstr = gd.Gid.Hex() @@ -68,21 +33,6 @@ func (gd *instantDoc) tid(in accountID) string { return makeTid(gd.Gid, in) } -func (gd *instantDoc) addMember(mid accountID, character any) (bson.M, error) { - tid := gd.tid(mid) - if _, err := gd.rh.JSONSet(gd.strid(), "$._members."+tid, character); err != nil { - return nil, err - } - counts, err := gd.rh.JSONNumIncrBy(gd.strid(), "$._count", 1) - if err != nil { - return nil, err - } - - gd.Count = counts[0] - - return gd.loadMemberFull(tid) -} - var errGroupAlreadyDestroyed = errors.New("instant group is already destroyed") func (gd *instantDoc) removeMember(mid accountID) error { @@ -122,48 +72,6 @@ func (gi *groupInstant) Initialize(tv *Tavern) error { return nil } -func (gi *groupInstant) RegisterApiFunctions() { - -} - -func (gi *groupInstant) join(gd *instantDoc, mid primitive.ObjectID, character any) error { - // 내 정보 업데이트할 때에도 사용됨 - memdoc, err := gd.addMember(mid, character) - if err != nil { - return err - } - delete(memdoc, "_id") - - // 기존 유저에게 새 유저 알림 - gi.sendUpstreamMessage(&wshandler.UpstreamMessage{ - Target: "#" + gd.strid(), - Body: map[string]any{ - gd.tid(mid): memdoc, - }, - Tag: []string{"MemberDocFull"}, - }) - - gi.rh.JSONSet(mid.Hex(), "$.instant", bson.M{"id": gd.strid()}) - - full := gd.loadFull() - if f, ok := full["_members"]; ok { - members := f.(map[string]any) - for _, char := range members { - delete(char.(map[string]any), "_id") - } - } - - // 최초 입장이라면 새 멤버에 그룹 전체를 알림 - gi.sendUpstreamMessage(&wshandler.UpstreamMessage{ - Target: mid.Hex(), - Body: full, - Tag: []string{"GroupDocFull"}, - }) - gi.enterRoom(gd.Gid, mid) - - return nil -} - func (gi *groupInstant) UpdateInstantDocument(w http.ResponseWriter, r *http.Request) { var data struct { Gid primitive.ObjectID @@ -208,11 +116,13 @@ func (gi *groupInstant) UpdateInstantDocument(w http.ResponseWriter, r *http.Req } } -func (gi *groupInstant) Join(w http.ResponseWriter, r *http.Request) { +func (gi *groupInstant) Build(w http.ResponseWriter, r *http.Request) { var data struct { - Gid primitive.ObjectID - Mid primitive.ObjectID - Character primitive.M + 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) @@ -220,218 +130,42 @@ func (gi *groupInstant) Join(w http.ResponseWriter, r *http.Request) { return } - if data.Gid.IsZero() || data.Mid.IsZero() { - logger.Println("groupInstant.Join failed. gid or mid is zero") - w.WriteHeader(http.StatusBadRequest) - return + // data.Members가 다 살아있는지 부터 확인 + var result struct { + Gid primitive.ObjectID // 실패하면 없음 + Success []primitive.ObjectID } - - gd, err := gi.find(data.Gid) - if err != nil || gd == nil { - logger.Println("groupInstant.Join failed. gi find return err :", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - if err := gi.join(gd, data.Mid, data.Character); err != nil { - logger.Println("groupInstant.Join failed :", err) - w.WriteHeader(http.StatusInternalServerError) - } else { - gocommon.MakeEncoder(w, r).Encode(gd.Count) - } -} - -func (gi *groupInstant) Create(w http.ResponseWriter, r *http.Request) { - var data struct { - Mid primitive.ObjectID - Body primitive.M - Character primitive.M - } - if err := gocommon.MakeDecoder(r).Decode(&data); err != nil { - logger.Println("CreateParty failed. Decode returns err :", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - gd, err := gi.createInstantGroup(data.Mid, data.Character, data.Body) - if err != nil { - logger.Println("groupInstant.Create failed. gp.createInstantGroup() return err :", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - // 내가 wshandler room에 입장 - gi.enterRoom(gd.Gid, data.Mid) - gi.rh.JSONSet(data.Mid.Hex(), "$.instant", bson.M{"id": gd.strid()}) - gi.sendUpstreamMessage(&wshandler.UpstreamMessage{ - Target: data.Mid.Hex(), - Body: gd, - Tag: []string{"GroupDocFull"}, - }) - - gocommon.MakeEncoder(w, r).Encode(gd.Gid) -} - -func (gi *groupInstant) Delete(w http.ResponseWriter, r *http.Request) { - var gid primitive.ObjectID - if err := gocommon.MakeDecoder(r).Decode(&gid); err != nil { - logger.Println("CreateParty failed. Decode returns err :", err) - w.WriteHeader(http.StatusInternalServerError) - return - } -} - -func (gi *groupInstant) leave(gd *instantDoc, mid primitive.ObjectID) error { - if err := gd.removeMember(mid); err != nil { - if err == errGroupAlreadyDestroyed { - // 정상 - gd.Count = 0 - return nil + 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) } - return err } - gi.rh.JSONDel(mid.Hex(), "$.instant.id") - - // gid에는 제거 메시지 보냄 - gi.sendUpstreamMessage(&wshandler.UpstreamMessage{ - Target: "#" + gd.strid(), - Body: bson.M{ - gd.tid(mid): bson.M{}, - }, - Tag: []string{"MemberDocFull"}, - }) - - gi.leaveRoom(gd.Gid, mid) - - if gd.Count == 0 { - gd.rh.Del(gd.rh.Context(), gd.strid()).Result() - } - - return nil -} - -func (gi *groupInstant) Leave(w http.ResponseWriter, r *http.Request) { - var data struct { - Gid primitive.ObjectID - Mid primitive.ObjectID - } - if err := gocommon.MakeDecoder(r).Decode(&data); err != nil { - logger.Println("RemoveFromParty failed. Decode returns err :", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - gd := instantDoc{ - Gid: data.Gid, - rh: gi.rh, - } - - if err := gi.leave(&gd, data.Mid); err != nil { - logger.Println("groupInstant.Leave failed. gd.removeMember returns err :", err) - w.WriteHeader(http.StatusInternalServerError) + if len(result.Success) < len(data.Members) { + // 이탈자가 있군 + // 여기서 중단 + gocommon.MakeEncoder(w, r).Encode(result) return } - gocommon.MakeEncoder(w, r).Encode(gd.Count) -} - -func (gi *groupInstant) Merge(w http.ResponseWriter, r *http.Request) { - gocommon.MakeEncoder(w, r).Encode(struct { - From int64 - Into int64 - }{From: -1, Into: 0}) // -1: 알수 없음, 0: 비었음 - - // var data struct { - // From primitive.ObjectID - // Into primitive.ObjectID - // Max int64 - // } - // if err := gocommon.MakeDecoder(r).Decode(&data); err != nil { - // logger.Println("RemoveFromParty failed. Decode returns err :", err) - // w.WriteHeader(http.StatusInternalServerError) - // return - // } - // // From에 있는 mid를 Into로 옮김 - // gdinto, err := gi.find(data.Into) - // if err != nil { - // logger.Println("groupInstant.Merge failed. gd.getMembers returns err :", err) - // w.WriteHeader(http.StatusInternalServerError) - // return - // } - - // if gdinto == nil { - // // 이미 나갔다. 머지 중단 - // gocommon.MakeEncoder(w, r).Encode(struct { - // From int64 - // Into int64 - // }{From: -1, Into: 0}) // -1: 알수 없음, 0: 비었음 - // return - // } - - // gdfrom := instantDoc{ - // Gid: data.From, - // rh: gi.rh, - // } - // fromMembers, err := gdfrom.getMembers() - // if err != nil { - // logger.Println("groupInstant.Merge failed. gd.getMembers returns err :", err) - // w.WriteHeader(http.StatusInternalServerError) - // return - // } - - // if len(fromMembers) == 0 { - // // gdfrom이 비었다. 머지 중단 - // gocommon.MakeEncoder(w, r).Encode(struct { - // From int64 - // Into int64 - // }{From: 0, Into: -1}) - // return - // } - - // var movedmids []primitive.ObjectID - // for mid, doc := range fromMembers { - // gi.join(gdinto, mid, doc) - // gi.leaveRoom(gdfrom.Gid, mid) - // movedmids = append(movedmids, mid) - - // if gdinto.Count == data.Max { - // break - // } - // } - - // if len(movedmids) == int(gdfrom.Count) { - // gi.rh.JSONDel(gdfrom.strid(), "$") - // } else { - // for _, mid := range movedmids { - // gdfrom.removeMember(mid) - - // // gid에는 제거 메시지 보냄 - // gi.sendUpstreamMessage(&wshandler.UpstreamMessage{ - // Target: "#" + gdfrom.strid(), - // Body: bson.M{ - // gdfrom.tid(mid): bson.M{}, - // }, - // Tag: []string{"MemberDocFull"}, - // }) - // } - // } - - // gocommon.MakeEncoder(w, r).Encode(struct { - // From int64 - // Into int64 - // }{From: gdfrom.Count, Into: gdinto.Count}) -} - -func (gi *groupInstant) createInstantGroup(firstAcc primitive.ObjectID, firstChar primitive.M, instDoc primitive.M) (*instantDoc, error) { newid := primitive.NewObjectID() - tid := makeTid(newid, firstAcc) + members := make(map[string]primitive.M) + for _, m := range data.Members { + tid := makeTid(newid, m.Mid) + members[tid] = m.Character + } gd := &instantDoc{ - Members: map[string]any{ - tid: firstChar, - }, - Body: instDoc, - Count: 1, + Members: members, + Body: data.Body, + Count: int64(len(members)), rh: gi.rh, Gid: newid, @@ -439,66 +173,71 @@ func (gi *groupInstant) createInstantGroup(firstAcc primitive.ObjectID, firstCha _, err := gi.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX) if err != nil { - return nil, err + logger.Println("instant.Build failed. JSONSet returns err :", err) + w.WriteHeader(http.StatusInternalServerError) + return } - return gd, nil + + 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) find(id groupID) (*instantDoc, error) { - if id.IsZero() { - return nil, nil - } - - _, err := gi.rh.JSONObjLen(id.Hex(), "$") - if err == redis.Nil { - return nil, nil - } - if err != nil { - return nil, err - } - - return &instantDoc{ - rh: gi.rh, - Gid: id, - }, nil -} - -func (gi *groupInstant) ClientDisconnected(msg string, callby *wshandler.Sender) { - gids, _ := gi.rh.JSONGetString(callby.Accid.Hex(), "$.instant.id") +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.rh.JSONDel(callby.Accid.Hex(), "$.instant.id") - - if err := gd.removeMember(callby.Accid); err != nil { - if err == errGroupAlreadyDestroyed { - // 정상 - return - } - logger.Println("ClientDisconnected failed. gd.removeMember returns err :", err) - return - } - - // gid에는 제거 메시지 보냄 gi.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: "#" + gd.strid(), Body: bson.M{ - gd.tid(callby.Accid): bson.M{}, + gd.tid(accid): bson.M{}, }, Tag: []string{"MemberDocFull"}, }) - gi.leaveRoom(gd.Gid, callby.Accid) - + 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) +} diff --git a/core/group_party.go b/core/group_party.go index 0b76298..ea0dadb 100644 --- a/core/group_party.go +++ b/core/group_party.go @@ -265,10 +265,6 @@ func (gp *groupParty) Initialize(tv *Tavern, cfg configDocument) error { return nil } -func (gp *groupParty) RegisterApiFunctions() { - -} - // JoinParty : 그룹에 참가 // - type : 그룹 타입 // - 그룹 타입에 맞는 키(주로 _id)