인스턴트 그룹 단순화.한번에 빌드하는 방식으로.

This commit is contained in:
2023-12-01 00:28:44 +09:00
parent 7d707c4ee3
commit cacab9350d
2 changed files with 79 additions and 344 deletions

View File

@ -1,11 +1,9 @@
package core package core
import ( import (
"encoding/json"
"errors" "errors"
"net/http" "net/http"
"github.com/go-redis/redis/v8"
"repositories.action2quare.com/ayo/gocommon" "repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/wshandler" "repositories.action2quare.com/ayo/gocommon/wshandler"
@ -15,7 +13,7 @@ import (
) )
type instantDoc struct { type instantDoc struct {
Members map[string]any `json:"_members"` Members map[string]primitive.M `json:"_members"`
Count int64 `json:"_count"` Count int64 `json:"_count"`
Body primitive.M `json:"_body"` Body primitive.M `json:"_body"`
Gid primitive.ObjectID `json:"_gid"` Gid primitive.ObjectID `json:"_gid"`
@ -24,39 +22,6 @@ type instantDoc struct {
idstr string 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 { func (gd *instantDoc) strid() string {
if len(gd.idstr) == 0 { if len(gd.idstr) == 0 {
gd.idstr = gd.Gid.Hex() gd.idstr = gd.Gid.Hex()
@ -68,21 +33,6 @@ func (gd *instantDoc) tid(in accountID) string {
return makeTid(gd.Gid, in) 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") var errGroupAlreadyDestroyed = errors.New("instant group is already destroyed")
func (gd *instantDoc) removeMember(mid accountID) error { func (gd *instantDoc) removeMember(mid accountID) error {
@ -122,48 +72,6 @@ func (gi *groupInstant) Initialize(tv *Tavern) error {
return nil 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) { func (gi *groupInstant) UpdateInstantDocument(w http.ResponseWriter, r *http.Request) {
var data struct { var data struct {
Gid primitive.ObjectID Gid primitive.ObjectID
@ -208,230 +116,56 @@ 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 { var data struct {
Gid primitive.ObjectID Members []struct {
Mid primitive.ObjectID Mid primitive.ObjectID
Character primitive.M Character primitive.M
} }
Body primitive.M
}
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil { if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
logger.Println("JoinParty failed. DecodeGob returns err :", err) logger.Println("JoinParty failed. DecodeGob returns err :", err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
if data.Gid.IsZero() || data.Mid.IsZero() { // data.Members가 다 살아있는지 부터 확인
logger.Println("groupInstant.Join failed. gid or mid is zero") var result struct {
w.WriteHeader(http.StatusBadRequest) Gid primitive.ObjectID // 실패하면 없음
return Success []primitive.ObjectID
} }
for _, m := range data.Members {
gd, err := gi.find(data.Gid) count, err := gi.rh.Exists(gi.rh.Context(), m.Mid.Hex()).Result()
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 { if err != nil {
logger.Println("groupInstant.Create failed. gp.createInstantGroup() return err :", err) logger.Println("instant.Build failed. Exists returns err :", err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
// 내가 wshandler room에 입장 if count != 0 {
gi.enterRoom(gd.Gid, data.Mid) result.Success = append(result.Success, m.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 len(result.Success) < len(data.Members) {
if err := gd.removeMember(mid); err != nil { // 이탈자가 있군
if err == errGroupAlreadyDestroyed { // 여기서 중단
// 정상 gocommon.MakeEncoder(w, r).Encode(result)
gd.Count = 0
return nil
}
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)
return 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() 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{ gd := &instantDoc{
Members: map[string]any{ Members: members,
tid: firstChar, Body: data.Body,
}, Count: int64(len(members)),
Body: instDoc,
Count: 1,
rh: gi.rh, rh: gi.rh,
Gid: newid, Gid: newid,
@ -439,66 +173,71 @@ func (gi *groupInstant) createInstantGroup(firstAcc primitive.ObjectID, firstCha
_, err := gi.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX) _, err := gi.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX)
if err != nil { if err != nil {
return nil, err logger.Println("instant.Build failed. JSONSet returns err :", err)
} w.WriteHeader(http.StatusInternalServerError)
return gd, nil return
} }
func (gi *groupInstant) find(id groupID) (*instantDoc, error) { result.Gid = newid
if id.IsZero() { for _, char := range members {
return nil, nil delete(char, "_id")
} }
_, err := gi.rh.JSONObjLen(id.Hex(), "$") // 멤버에 그룹 전체를 알림
if err == redis.Nil { for _, m := range data.Members {
return nil, nil midstr := m.Mid.Hex()
} gi.rh.JSONSet(midstr, "$.instant", bson.M{"id": gd.strid()})
if err != nil { gi.enterRoom(newid, m.Mid)
return nil, err gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: midstr,
Body: gd,
Tag: []string{"GroupDocFull"},
})
} }
return &instantDoc{ gocommon.MakeEncoder(w, r).Encode(result)
rh: gi.rh,
Gid: id,
}, nil
} }
func (gi *groupInstant) ClientDisconnected(msg string, callby *wshandler.Sender) { func (gi *groupInstant) makeClientClean(accid primitive.ObjectID) {
gids, _ := gi.rh.JSONGetString(callby.Accid.Hex(), "$.instant.id") gids, _ := gi.rh.JSONGetString(accid.Hex(), "$.instant.id")
if len(gids) > 0 && len(gids[0]) > 0 { if len(gids) > 0 && len(gids[0]) > 0 {
gidstr := gids[0] gidstr := gids[0]
gid, _ := primitive.ObjectIDFromHex(gidstr) gid, _ := primitive.ObjectIDFromHex(gidstr)
gi.rh.JSONDel(accid.Hex(), "$.instant.id")
gi.leaveRoom(gid, accid)
// gid에는 제거 메시지 보냄
gd := instantDoc{ gd := instantDoc{
Gid: gid, Gid: gid,
rh: gi.rh, 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{ gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gd.strid(), Target: "#" + gd.strid(),
Body: bson.M{ Body: bson.M{
gd.tid(callby.Accid): bson.M{}, gd.tid(accid): bson.M{},
}, },
Tag: []string{"MemberDocFull"}, Tag: []string{"MemberDocFull"},
}) })
gi.leaveRoom(gd.Gid, callby.Accid) gd.removeMember(accid)
if gd.Count == 0 { if gd.Count == 0 {
gd.rh.Del(gd.rh.Context(), gd.strid()).Result() 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)
}

View File

@ -265,10 +265,6 @@ func (gp *groupParty) Initialize(tv *Tavern, cfg configDocument) error {
return nil return nil
} }
func (gp *groupParty) RegisterApiFunctions() {
}
// JoinParty : 그룹에 참가 // JoinParty : 그룹에 참가
// - type : 그룹 타입 // - type : 그룹 타입
// - 그룹 타입에 맞는 키(주로 _id) // - 그룹 타입에 맞는 키(주로 _id)