Compare commits

...

30 Commits

Author SHA1 Message Date
7d77de67cb 모듈 업데이트 2025-08-05 21:49:09 +09:00
645a54146f 모듈 업데이트 2025-08-05 21:45:34 +09:00
6df3242f1a 파티에서 랜덤매칭 가능 2024-05-10 18:23:25 +09:00
8e34d64d07 종료 순서 변경 2024-02-16 17:37:28 +09:00
e821eb7433 $.size 제거 2024-02-16 14:58:24 +09:00
1922b9152b 그룹챗 redis 키 분리 2024-02-15 12:07:37 +09:00
7b2aa046bd gob.Register 누락 2024-02-14 18:18:02 +09:00
ef98a6bc54 EnterPublicChannel이 왜 여러번 불림??? 방어코드 추가 2024-02-14 17:20:05 +09:00
d80760b177 파티 나갈때 파티 doc 갱신 수정 2024-02-02 17:27:40 +09:00
e4b0f569e8 접속 종료시 pending 처리 2024-01-24 18:16:26 +09:00
2cf1ebb88f 모듈 업데이트 2024-01-22 17:16:26 +09:00
ddcfaf7968 거래채널 참가인원 수 문제 수정 2024-01-18 15:57:02 +09:00
a81549863a 방장 종료 처리 2024-01-18 14:40:39 +09:00
3f1e9ef5c9 voicechat 옮김 2024-01-08 20:25:49 +09:00
a503fe7f30 voice chat을 gocommon으로 옮김 2024-01-08 20:22:54 +09:00
5c9116461a eos 음성채팅 지원 추가 2024-01-04 19:04:48 +09:00
cacab9350d 인스턴트 그룹 단순화.한번에 빌드하는 방식으로. 2023-12-01 00:28:44 +09:00
7d707c4ee3 모듈 업데이트 2023-11-30 14:49:11 +09:00
c1cb226309 종료 안되는 문제 수정 2023-11-29 13:07:51 +09:00
168acce56e 종료 로그 추가 2023-11-28 22:34:18 +09:00
723bd44079 로비 ccu metric 가져옴 2023-11-28 01:18:50 +09:00
b20de99db2 모듈 업데이트 2023-11-28 00:58:07 +09:00
ae0b23f950 party member document 계층 오류 수정 2023-11-27 18:11:52 +09:00
2e2f7a151e tavern ccu 메트릭 수집 2023-11-24 16:30:47 +09:00
e169e72f66 모듈 업데이트 2023-11-24 00:33:00 +09:00
6af4c90280 모듈 업데이트 2023-11-24 00:18:32 +09:00
4ee3b70a46 UpdatePartyMemberDocument http api 추가 2023-11-21 17:06:27 +09:00
5025d41128 totalCCUWriter 제거 2023-11-20 16:09:58 +09:00
f96058057d ccu metric 추가 2023-11-16 19:59:25 +09:00
be15f3f854 모듈 업데이트 2023-11-16 19:59:13 +09:00
7 changed files with 374 additions and 502 deletions

View File

@ -1,6 +1,7 @@
package core
import (
"encoding/gob"
"encoding/json"
"fmt"
"net/http"
@ -38,6 +39,10 @@ type groupChat struct {
sendUpstreamMessage func(msg *wshandler.UpstreamMessage)
}
func init() {
gob.Register(map[string]string{})
}
func (gc *groupChat) Initialize(tv *Tavern, cfg configDocument) error {
rem, _ := json.Marshal(cfg)
if err := json.Unmarshal(rem, &gc.chatConfig); err != nil {
@ -106,23 +111,32 @@ func (gc *groupChat) Initialize(tv *Tavern, cfg configDocument) error {
}
func (gc *groupChat) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender) {
gc.rh.JSONSet(callby.Accid.Hex(), "$.channel", map[string]any{})
}
func (gc *groupChat) ClientDisconnected(msg string, callby *wshandler.Sender) {
docs, _ := gc.rh.JSONGetDocuments(callby.Accid.Hex(), "$.channel")
if msg == wshandler.ForceShutdownCloseMessage {
gc.rh.Del(gc.rh.Context(), "gc-"+callby.Accid.Hex()).Result()
return
}
if len(docs) > 0 {
for k, v := range docs[0] {
typename := k
chanid := v.(string)
chans, _ := gc.rh.HGetAll(gc.rh.Context(), "gc-"+callby.Accid.Hex()).Result()
gc.rh.Del(gc.rh.Context(), "gc-"+callby.Accid.Hex()).Result()
for typename, chanid := range chans {
gc.leaveRoom(chanid, callby.Accid)
if k == "public" {
gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
if typename == "public" {
cnt, err := gc.rh.JSONDel(chanid, "$.members."+callby.Alias)
if cnt > 0 {
if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
cfg.inoutChan <- "-" + callby.Alias
}
} else if err != nil {
logger.Println("groupchat ClientDisconnected JSONDel err :", err, callby.Alias)
} else {
logger.Println("groupchat ClientDisconnected JSONDel cnt 0 :", callby.Alias)
}
} else {
logger.Println("groupchat ClientDisconnected leave private channel :", chanid, callby.Alias)
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + chanid,
Body: map[string]any{"sender": callby.Alias},
@ -130,60 +144,83 @@ func (gc *groupChat) ClientDisconnected(msg string, callby *wshandler.Sender) {
})
}
}
}
}
func (gc *groupChat) EnterPublicChannel(ctx wshandler.ApiCallContext) {
chanid := ctx.Arguments[0].(string)
if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
size, err := gc.rh.JSONObjLen(chanid, "$.members")
if err != nil {
logger.Println("JSONGetInt64 failed :", chanid, err)
} else if len(size) == 0 || size[0] < cfg.Capacity {
// 입장
_, err := gc.rh.JSONSet(chanid, "$.members."+ctx.CallBy.Alias, 1)
if err == nil {
gc.enterRoom(chanid, ctx.CallBy.Accid)
gc.rh.JSONSet(ctx.CallBy.Accid.Hex(), "$.channel.public", chanid)
cfg.inoutChan <- "+" + ctx.CallBy.Alias
gc.LeavePublicChannel(ctx)
members, err := gc.rh.JSONGetDocuments(chanid, "$.members")
if err != nil {
logger.Println("JSONGetDocuments failed :", chanid, err)
chanid := ctx.Arguments[0].(string)
cfg, ok := gc.chatConfig.Channels[chanid]
if !ok {
return
}
toarr := make([]string, 0, len(members[0]))
atomicsize, _ := gc.rh.JSONObjLen(chanid, "$.members")
if len(atomicsize) == 0 {
return
}
if atomicsize[0] >= cfg.Capacity {
logger.Println("chatting channel is full :", chanid, atomicsize[0], cfg.Capacity)
return
}
cnt, _ := gc.rh.HSet(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public", chanid).Result()
if cnt == 0 {
logger.Println("HSet cnt 0 :", chanid, ctx.CallBy.Alias)
return
}
ok, err := gc.rh.JSONSet(chanid, "$.members."+ctx.CallBy.Alias, 1)
if err != nil {
gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public").Result()
logger.Println("JSONSet $.members failed :", err, chanid, ctx.CallBy.Alias)
return
}
if !ok {
gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public").Result()
logger.Println("JSONSet $.members not ok :", chanid, ctx.CallBy.Alias)
return
}
gc.enterRoom(chanid, ctx.CallBy.Accid)
cfg.inoutChan <- "+" + ctx.CallBy.Alias
members, _ := gc.rh.JSONGetDocuments(chanid, "$.members")
var toarr []string
if len(members) > 0 {
toarr = make([]string, 0, len(members[0]))
for k := range members[0] {
toarr = append(toarr, k)
}
} else {
toarr = []string{}
}
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: ctx.CallBy.Accid.Hex(),
Body: map[string]any{"members": toarr},
Tag: []string{"ChattingChannelProperties"},
})
} else {
logger.Println("JSONSet $.members failed :", chanid, *ctx.CallBy, err)
logger.Println(gc.rh.JSONGet(chanid, "$"))
}
} else {
// 풀방
logger.Println("chatting channel is full :", chanid, size, cfg.Capacity)
}
} else {
logger.Println("chatting channel not valid :", chanid)
}
}
func (gc *groupChat) LeavePublicChannel(ctx wshandler.ApiCallContext) {
chanid := ctx.Arguments[0].(string)
cnt, _ := gc.rh.JSONDel(ctx.CallBy.Accid.Hex(), "$.channel.public")
oldchan, _ := gc.rh.HGet(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public").Result()
if len(oldchan) > 0 {
gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public")
cnt, err := gc.rh.JSONDel(oldchan, "$.members."+ctx.CallBy.Alias)
if cnt > 0 {
gc.leaveRoom(chanid, ctx.CallBy.Accid)
gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
gc.leaveRoom(oldchan, ctx.CallBy.Accid)
if cfg, ok := gc.chatConfig.Channels[oldchan]; ok {
cfg.inoutChan <- "-" + ctx.CallBy.Alias
}
} else if err != nil {
logger.Println("groupchat LeavePublicChannel JSONDel err :", err, oldchan, ctx.CallBy.Alias)
} else {
logger.Println("groupchat LeavePublicChannel JSONDel cnt 0 :", oldchan, ctx.CallBy.Alias)
}
}
}
@ -207,7 +244,7 @@ func (gc *groupChat) EnterPrivateChannel(ctx wshandler.ApiCallContext) {
if len(reason) > 0 {
// 수락
ok, err := gc.rh.JSONSet(ctx.CallBy.Accid.Hex(), "$.channel."+typename, channel, gocommon.RedisonSetOptionNX)
ok, err := gc.rh.HSetNX(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), typename, channel).Result()
if err != nil || !ok {
// 이미 다른 private channel 참여 중
logger.Println("EnterPrivateChannel failed. HSetNX return err :", err, ctx.CallBy.Accid.Hex(), typename, channel)
@ -229,7 +266,7 @@ func (gc *groupChat) EnterPrivateChannel(ctx wshandler.ApiCallContext) {
func (gc *groupChat) LeavePrivateChannel(ctx wshandler.ApiCallContext) {
typename := ctx.Arguments[0].(string)
chanid := ctx.Arguments[1].(string)
cnt, _ := gc.rh.JSONDel(ctx.CallBy.Accid.Hex(), "$.channel."+typename)
cnt, _ := gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), typename).Result()
if cnt > 0 {
gc.leaveRoom(chanid, ctx.CallBy.Accid)
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
@ -288,14 +325,15 @@ func (gc *groupChat) QueryPlayerChattingChannel(w http.ResponseWriter, r *http.R
return
}
sub, err := gc.rh.JSONGetDocuments(accid.Hex(), "$.channel")
sub, err := gc.rh.HGetAll(gc.rh.Context(), "gc-"+accid.Hex()).Result()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if len(sub) > 0 {
gocommon.MakeEncoder(w, r).Encode(sub[0])
if sub == nil {
sub = make(map[string]string)
}
gocommon.MakeEncoder(w, r).Encode(sub)
}
func (gc *groupChat) SendMessageOnChannel(w http.ResponseWriter, r *http.Request) {

View File

@ -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,7 +13,7 @@ import (
)
type instantDoc struct {
Members map[string]any `json:"_members"`
Members map[string]primitive.M `json:"_members"`
Count int64 `json:"_count"`
Body primitive.M `json:"_body"`
Gid primitive.ObjectID `json:"_gid"`
@ -24,39 +22,6 @@ type instantDoc struct {
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,30 +33,6 @@ 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 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 {
@ -109,29 +50,6 @@ func (gd *instantDoc) removeMember(mid accountID) error {
return nil
}
func (gd *instantDoc) getMembers() (map[primitive.ObjectID]any, error) {
res, err := gd.rh.JSONGet(gd.strid(), "$._members")
if err == redis.Nil {
return nil, nil
}
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[primitive.ObjectID]any)
for k, v := range temp[0] {
out[gd.mid(k)] = v
}
return out, nil
}
type groupInstant struct {
sendUpstreamMessage func(*wshandler.UpstreamMessage)
enterRoom func(groupID, accountID)
@ -154,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
@ -240,225 +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 {
Gid primitive.ObjectID
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
}
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)
for _, m := range data.Members {
count, err := gi.rh.Exists(gi.rh.Context(), m.Mid.Hex()).Result()
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)
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
if count != 0 {
result.Success = append(result.Success, m.Mid)
}
}
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
}
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) {
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,
@ -466,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)
}

View File

@ -2,6 +2,7 @@ package core
import (
"context"
"encoding/gob"
"encoding/json"
"fmt"
"net/http"
@ -63,6 +64,11 @@ type partyDoc struct {
id groupID
}
func init() {
gob.Register(partyDoc{})
gob.Register(Invitation{})
}
func (gd *partyDoc) loadMemberFull(tid string) (bson.M, error) {
full, err := gd.rh.JSONGet(gd.strid(), "$._members."+tid)
if err != nil {
@ -170,10 +176,15 @@ func (gd *partyDoc) addInvite(mid accountID, body bson.M, ttl time.Duration, max
return newdoc, err
}
func (gd *partyDoc) addMember(mid accountID, character bson.M) (bson.M, error) {
func (gd *partyDoc) addMember(mid accountID, character bson.M, option string) (bson.M, error) {
tid := gd.tid(mid)
prefix := "$._members." + tid
if option == gocommon.RedisonSetOptionNX {
if _, err := gd.rh.JSONSet(gd.strid(), prefix, bson.M{"_body": character}, gocommon.RedisonSetOptionNX); err != nil {
return nil, err
}
} else {
if _, err := gd.rh.JSONSet(gd.strid(), prefix+"._body", character, gocommon.RedisonSetOptionXX); err != nil {
return nil, err
}
@ -181,11 +192,23 @@ func (gd *partyDoc) addMember(mid accountID, character bson.M) (bson.M, error) {
if err := gd.rh.JSONMDel(gd.strid(), []string{prefix + "._invite", prefix + "._invite_exp"}); err != nil {
return nil, err
}
}
gd.rh.Persist(gd.rh.Context(), gd.strid()).Result()
return gd.loadMemberFull(tid)
}
func (gd *partyDoc) getIncharge() string {
if len(gd.InCharge) == 0 {
incharge, err := gd.rh.JSONGet(gd.strid(), "$._incharge")
if err == nil && incharge != nil {
gd.InCharge = strings.Trim(incharge.(string), "[]\"")
}
}
return gd.InCharge
}
func (gd *partyDoc) removeMemberByTid(tid string) error {
_, err := gd.rh.JSONDel(gd.strid(), "$._members."+tid)
if err != nil {
@ -259,16 +282,13 @@ func (gp *groupParty) Initialize(tv *Tavern, cfg configDocument) error {
tv.wsh.EnterRoom(gid.Hex(), accid)
}
gp.leaveRoom = func(gid groupID, accid accountID) {
gp.rh.JSONDel(accid.Hex(), "$.party.state")
tv.wsh.LeaveRoom(gid.Hex(), accid)
}
return nil
}
func (gp *groupParty) RegisterApiFunctions() {
}
// JoinParty : 그룹에 참가
// - type : 그룹 타입
// - 그룹 타입에 맞는 키(주로 _id)
@ -278,8 +298,8 @@ func (gp *groupParty) JoinParty(w http.ResponseWriter, r *http.Request) {
var data struct {
Gid primitive.ObjectID
Mid primitive.ObjectID
First bool
Character bson.M
Option string
}
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
logger.Println("JoinParty failed. DecodeGob returns err :", err)
@ -287,9 +307,12 @@ func (gp *groupParty) JoinParty(w http.ResponseWriter, r *http.Request) {
return
}
// Option : ["NX" | "XX" | ""]
character := data.Character
gid := data.Gid
mid := data.Mid
option := strings.ToUpper(data.Option)
if gid.IsZero() || mid.IsZero() {
logger.Println("JoinParty failed. mid should be exist")
@ -310,7 +333,7 @@ func (gp *groupParty) JoinParty(w http.ResponseWriter, r *http.Request) {
}
// 내 정보 업데이트할 때에도 사용됨
if !data.First {
if option == "XX" {
// 이미 멤버여야 재입장 가능
path := "$._members." + gd.tid(mid) + "._body"
if _, err := gd.rh.JSONSet(gd.strid(), path, character, gocommon.RedisonSetOptionXX); err != nil {
@ -321,7 +344,7 @@ func (gp *groupParty) JoinParty(w http.ResponseWriter, r *http.Request) {
}
gp.rh.JSONSet(mid.Hex(), "$.party", bson.M{"id": gid.Hex()})
memdoc, err := gd.addMember(mid, character)
memdoc, err := gd.addMember(mid, character, option)
if err != nil {
logger.Println("JoinParty failed :", err)
w.WriteHeader(http.StatusInternalServerError)
@ -348,6 +371,26 @@ func (gp *groupParty) JoinParty(w http.ResponseWriter, r *http.Request) {
gocommon.MakeEncoder(w, r).Encode(gd.strid())
}
func (gp *groupParty) ForceClearPartyMember(w http.ResponseWriter, r *http.Request) {
var doc struct {
Gid primitive.ObjectID
Mid primitive.ObjectID
}
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
logger.Println("ConditionalClearPartyMember failed. DecodeGob returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
gd := partyDoc{
id: doc.Gid,
rh: gp.rh,
}
gd.removeMember(doc.Mid)
}
func (gp *groupParty) ConditionalClearPartyMember(w http.ResponseWriter, r *http.Request) {
var doc struct {
Gid string
@ -376,8 +419,15 @@ func (gp *groupParty) ConditionalClearPartyMember(w http.ResponseWriter, r *http
id: gid,
rh: gp.rh,
}
gd.removeMember(mid)
if gd.getIncharge() == gd.tid(mid) {
// 방장이 나갔다.
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + doc.Gid,
Body: bson.M{"gid": gid},
Tag: []string{"GroupDocFull", gid.Hex()},
})
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
} else {
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + doc.Gid,
Body: bson.M{
@ -385,6 +435,8 @@ func (gp *groupParty) ConditionalClearPartyMember(w http.ResponseWriter, r *http
},
Tag: []string{"MemberDocFull"},
})
gd.removeMember(mid)
}
}
if len(pids) == 0 {
@ -501,6 +553,24 @@ func (gp *groupParty) InviteToParty(w http.ResponseWriter, r *http.Request) {
})
}
func (gp *groupParty) UpdatePartyMemberDocument(w http.ResponseWriter, r *http.Request) {
var doc struct {
Gid string
Tid string
Fragment bson.M
}
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
logger.Println("UpdatePartyMemberDocument failed. DecodeGob returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
gidobj, _ := primitive.ObjectIDFromHex(doc.Gid)
mid := midFromTid(gidobj, doc.Tid)
gp.updateMemberDocument(gidobj, mid, doc.Fragment)
}
func (gp *groupParty) AcceptPartyInvitation(w http.ResponseWriter, r *http.Request) {
var doc struct {
Gid primitive.ObjectID
@ -564,7 +634,7 @@ func (gp *groupParty) AcceptPartyInvitation(w http.ResponseWriter, r *http.Reque
rh: gp.rh,
}
memberDoc, err := gd.addMember(mid, member)
memberDoc, err := gd.addMember(mid, member, "XX")
if err == nil {
// 기존 멤버에게 새 멤버를 알림
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
@ -617,7 +687,8 @@ func (gp *groupParty) updateMemberDocument(gid groupID, mid accountID, doc bson.
id: gid,
rh: gp.rh,
}
prefixPath := fmt.Sprintf("$._members.%s.", gd.tid(mid))
prefixPath := fmt.Sprintf("$._members.%s._body.", gd.tid(mid))
err := gp.rh.JSONMSetRel(gd.strid(), prefixPath, doc)
if err != nil {
return err
@ -770,6 +841,20 @@ func (gp *groupParty) ClientDisconnected(msg string, callby *wshandler.Sender) {
gp.leaveRoom(gid, callby.Accid)
if msg != "pending" {
gd := &partyDoc{
rh: gp.rh,
id: gid,
}
if gd.getIncharge() == gd.tid(callby.Accid) {
// 방장이 나감. 방 폭파
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gidstr,
Body: bson.M{"gid": gid},
Tag: []string{"GroupDocFull", gidstr},
})
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
} else {
// gid에는 제거 메시지 보냄
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gidstr,
@ -778,6 +863,8 @@ func (gp *groupParty) ClientDisconnected(msg string, callby *wshandler.Sender) {
},
Tag: []string{"MemberDocFull"},
})
gd.removeMember(callby.Accid)
}
}
}
}
@ -841,6 +928,15 @@ func (gp *groupParty) LeaveParty(ctx wshandler.ApiCallContext) {
return
}
if gd.getIncharge() == gd.tid(mid) {
// 방장이 나감. 방 폭파
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + gidstr,
Body: bson.M{"gid": gid},
Tag: []string{"GroupDocFull", gidstr},
})
gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
} else {
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: mid.Hex(),
@ -856,6 +952,8 @@ func (gp *groupParty) LeaveParty(ctx wshandler.ApiCallContext) {
},
Tag: []string{"MemberDocFull"},
})
gd.removeMember(mid)
}
gp.leaveRoom(gid, mid)
}

View File

@ -14,6 +14,7 @@ import (
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/metric"
"repositories.action2quare.com/ayo/gocommon/session"
"repositories.action2quare.com/ayo/gocommon/wshandler"
@ -27,6 +28,9 @@ type TavernConfig struct {
Group map[string]configDocument `json:"tavern_group_types"`
MaingateApiToken string `json:"maingate_api_token"`
RedisURL string `json:"tavern_redis_url"`
EosClientId string `json:"eos_client_id"`
EosClientSecret string `json:"eos_client_secret"`
EosDeploymentId string `json:"eos_deployment_id"`
macAddr string
}
@ -78,7 +82,10 @@ func New(context context.Context, wsh *wshandler.WebsocketHandler) (*Tavern, err
return tv, nil
}
var ccu = metric.MetricWriterNil
func (tv *Tavern) Cleanup() {
ccu.Set(0)
tv.mongoClient.Close()
}
@ -116,6 +123,7 @@ func (tv *Tavern) prepare(ctx context.Context) error {
tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(instant, "instant"))
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(instant, "instant"))
ccu = metric.NewMetric(metric.MetricGuage, "concurrent_user", "concurrent user count", map[string]string{"game": "lobby"})
return nil
}
@ -136,6 +144,7 @@ func (tv *Tavern) LeaveChannel(ctx wshandler.ApiCallContext) {
}
func (tv *Tavern) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender) {
ccu.Add(1)
tv.redison.Del(tv.redison.Context(), callby.Accid.Hex())
_, err := tv.redison.JSONSet(callby.Accid.Hex(), "$", bson.M{"_ts": time.Now().UTC().Unix()})
if err != nil {
@ -144,6 +153,7 @@ func (tv *Tavern) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender
}
func (tv *Tavern) ClientDisconnected(msg string, callby *wshandler.Sender) {
ccu.Add(-1)
tv.redison.Del(tv.redison.Context(), callby.Accid.Hex()).Result()
}

17
go.mod
View File

@ -1,29 +1,30 @@
module repositories.action2quare.com/ayo/tavern
go 1.20
go 1.22.1
require (
github.com/go-redis/redis/v8 v8.11.5
github.com/gorilla/websocket v1.5.0
go.mongodb.org/mongo-driver v1.11.7
repositories.action2quare.com/ayo/gocommon v0.0.0-20231016010153-0f302437251f
repositories.action2quare.com/ayo/gocommon v0.0.0-20250805124812-38a3da271a8e
)
require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/klauspost/compress v1.16.6 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/text v0.14.0 // indirect
)

53
go.sum
View File

@ -1,41 +1,52 @@
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -59,20 +70,21 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -80,7 +92,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -88,27 +101,23 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230911034515-1af5d7281946 h1:YSvgTNuHeKis37+FfOvzVLYCaXQ0oF+CWBTy4bRqq3g=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230911034515-1af5d7281946/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230919095017-49a3722a7e09 h1:KYpRt/gjwYePPIWLit+PVCF7V3FGyZXQ+0U7bTKH+sE=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230919095017-49a3722a7e09/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
repositories.action2quare.com/ayo/gocommon v0.0.0-20231012030311-0fb806bfda1e h1:WQmrM33bgcAJ1PRz/uaz1tyZxpSDOuuGl0ICJdh4K9U=
repositories.action2quare.com/ayo/gocommon v0.0.0-20231012030311-0fb806bfda1e/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
repositories.action2quare.com/ayo/gocommon v0.0.0-20231016010153-0f302437251f h1:S7J6nGOwe5c+JEXbdI0rZshVvEXZCFjUAY9e7Wd91tY=
repositories.action2quare.com/ayo/gocommon v0.0.0-20231016010153-0f302437251f/go.mod h1:XvklTTSvQX5uviivGBcZo8eIL+mV94W2e4uBBXcT5JY=
repositories.action2quare.com/ayo/gocommon v0.0.0-20250805124037-fb3f0385067a h1:NkFjJVZTNO6HsivUEikISfqF4O5zHk0VRdKOEOVTOG4=
repositories.action2quare.com/ayo/gocommon v0.0.0-20250805124037-fb3f0385067a/go.mod h1:q64I6gqlD61qwi9FfuPkwqy6Z6uzSHdcEjoHAJC27gQ=
repositories.action2quare.com/ayo/gocommon v0.0.0-20250805124812-38a3da271a8e h1:jxfkCjnDBPg4zw5oqecEdxjclpiBysBtNvI2ENqeG4E=
repositories.action2quare.com/ayo/gocommon v0.0.0-20250805124812-38a3da271a8e/go.mod h1:q64I6gqlD61qwi9FfuPkwqy6Z6uzSHdcEjoHAJC27gQ=

View File

@ -41,12 +41,16 @@ func main() {
serveMux := http.NewServeMux()
wsh.RegisterHandlers(serveMux, *prefix)
tv.RegisterHandlers(ctx, serveMux, *prefix)
server := gocommon.NewHTTPServer(serveMux)
logger.Println("tavern is started")
wsh.Start(ctx)
server.Start()
if err := server.Start(); err != nil {
logger.Println(err)
}
cancel()
tv.Cleanup()
wsh.Cleanup()
tv.Cleanup()
}
}