인스턴트 그룹 추가(랜덤매칭용)

This commit is contained in:
2023-10-06 11:13:03 +09:00
parent 2cec9b90fe
commit d7d7df4a28
4 changed files with 431 additions and 29 deletions

396
core/group_instant.go Normal file
View File

@ -0,0 +1,396 @@
package core
import (
"encoding/json"
"errors"
"net/http"
"github.com/go-redis/redis/v8"
"github.com/gorilla/websocket"
"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]any `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()
}
return gd.idstr
}
func (gd *instantDoc) tid(in accountID) string {
return makeTid(gd.Gid, in)
}
func (gd *instantDoc) mid(tid string) accountID {
tidobj, _ := primitive.ObjectIDFromHex(tid)
var out primitive.ObjectID
for i := range tidobj {
out[12-i-1] = gd.Gid[i] ^ tidobj[12-i-1]
}
return out
}
func (gd *instantDoc) addMember(mid accountID, character bson.M) (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) removeMemberByTid(tid string) error {
counts, _ := gd.rh.JSONNumIncrBy(gd.strid(), "$._count", -1)
if len(counts) == 0 {
// 이미 지워진 인스턴트그룹
return errGroupAlreadyDestroyed
}
if _, err := gd.rh.JSONDel(gd.strid(), "$._members."+tid); err != nil {
return err
}
gd.Count = counts[0]
return nil
}
func (gd *instantDoc) removeMember(mid accountID) error {
return gd.removeMemberByTid(gd.tid(mid))
}
func (gd *instantDoc) getMembers() (map[string]any, error) {
res, err := gd.rh.JSONGet(gd.strid(), "$._members")
if err != nil {
return nil, err
}
var temp []map[string]any
err = json.Unmarshal([]byte(res.(string)), &temp)
if err != nil {
return nil, err
}
out := make(map[string]any)
for k, v := range temp[0] {
out[gd.mid(k).Hex()] = v
}
return out, 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) RegisterApiFunctions() {
}
func (gi *groupInstant) Join(w http.ResponseWriter, r *http.Request) {
var data struct {
Gid primitive.ObjectID
Mid primitive.ObjectID
Character primitive.M
}
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
logger.Println("JoinParty failed. DecodeGob returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
character := data.Character
gid := data.Gid
mid := data.Mid
if gid.IsZero() || mid.IsZero() {
logger.Println("JoinParty failed. mid should be exist")
w.WriteHeader(http.StatusBadRequest)
return
}
gd, err := gi.find(gid)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if gd == nil {
// 그룹이 없다. 실패
w.WriteHeader(http.StatusBadRequest)
return
}
// 내 정보 업데이트할 때에도 사용됨
if memdoc, err := gd.addMember(mid, character); err == nil {
// 기존 유저에게 새 유저 알림
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gid.Hex(),
Body: map[string]any{
gd.tid(mid): memdoc,
},
Tag: []string{"MemberDocFull"},
})
gi.enterRoom(gid, mid)
// 최초 입장이라면 새 멤버에 그룹 전체를 알림
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: mid.Hex(),
Body: gd.loadFull(),
Tag: []string{"GroupDocFull"},
})
} else if err != nil {
logger.Error("JoinParty failed :", err)
w.WriteHeader(http.StatusInternalServerError)
}
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(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 := gd.removeMember(data.Mid); err != nil {
if err == errGroupAlreadyDestroyed {
// 정상
gocommon.MakeEncoder(w, r).Encode(int64(0))
return
}
logger.Println("groupInstant.Leave failed. gd.removeMember returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
gi.rh.JSONDel(data.Mid.Hex(), "$.instant.id")
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: data.Mid.Hex(),
Body: bson.M{"gid": data.Gid},
Tag: []string{"GroupDocFull", gd.strid()},
})
// gid에는 제거 메시지 보냄
gi.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gd.strid(),
Body: bson.M{
gd.tid(data.Mid): bson.M{},
},
Tag: []string{"MemberDocFull"},
})
gi.leaveRoom(gd.Gid, data.Mid)
if gd.Count == 0 {
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
}
gocommon.MakeEncoder(w, r).Encode(gd.Count)
}
func (gi *groupInstant) createInstantGroup(firstAcc primitive.ObjectID, firstChar primitive.M, instDoc primitive.M) (*instantDoc, error) {
newid := primitive.NewObjectID()
tid := makeTid(newid, firstAcc)
gd := &instantDoc{
Members: map[string]any{
tid: firstChar,
},
Body: instDoc,
Count: 1,
rh: gi.rh,
Gid: newid,
}
_, err := gi.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX)
if err != nil {
return nil, err
}
return gd, nil
}
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(conn *websocket.Conn, callby *wshandler.Sender) {
gids, _ := gi.rh.JSONGetString(callby.Accid.Hex(), "$.instant.id")
if len(gids) > 0 && len(gids[0]) > 0 {
gidstr := gids[0]
gid, _ := primitive.ObjectIDFromHex(gidstr)
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{},
},
Tag: []string{"MemberDocFull"},
})
gi.leaveRoom(gd.Gid, callby.Accid)
if gd.Count == 0 {
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
}
}
}