Compare commits
93 Commits
wshandler_
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d77de67cb | |||
| 645a54146f | |||
| 6df3242f1a | |||
| 8e34d64d07 | |||
| e821eb7433 | |||
| 1922b9152b | |||
| 7b2aa046bd | |||
| ef98a6bc54 | |||
| d80760b177 | |||
| e4b0f569e8 | |||
| 2cf1ebb88f | |||
| ddcfaf7968 | |||
| a81549863a | |||
| 3f1e9ef5c9 | |||
| a503fe7f30 | |||
| 5c9116461a | |||
| cacab9350d | |||
| 7d707c4ee3 | |||
| c1cb226309 | |||
| 168acce56e | |||
| 723bd44079 | |||
| b20de99db2 | |||
| ae0b23f950 | |||
| 2e2f7a151e | |||
| e169e72f66 | |||
| 6af4c90280 | |||
| 4ee3b70a46 | |||
| 5025d41128 | |||
| f96058057d | |||
| be15f3f854 | |||
| 91d7eb612e | |||
| 22ac7c391c | |||
| e8ea58cea0 | |||
| ca2f3b8794 | |||
| 5fc64d7471 | |||
| 21ec68d27e | |||
| 99c10986d6 | |||
| dffe1cfee5 | |||
| bd2408c66f | |||
| b9339166b9 | |||
| 8dea97d956 | |||
| 708f9d6caf | |||
| dd37659089 | |||
| bdcb86c1c7 | |||
| 6e709c9454 | |||
| b9d2451902 | |||
| e8f74bcd19 | |||
| b44a6b1fd8 | |||
| d7d7df4a28 | |||
| 2cec9b90fe | |||
| 6706d7d02e | |||
| 675bcbad9e | |||
| e18a5eafe0 | |||
| ad185b8d01 | |||
| d3614392c2 | |||
| 19eacf0d4c | |||
| ea28d93f7d | |||
| 374992d55f | |||
| a9b0cf2493 | |||
| 3dde7ccaf5 | |||
| 1d14fb659d | |||
| ce50657734 | |||
| 4a51f7d433 | |||
| 6410056c87 | |||
| f7173a4f49 | |||
| 9b0c4a121a | |||
| e0504f688a | |||
| fd1502e52a | |||
| cb5cd280b9 | |||
| a4923fa0a1 | |||
| 2534aa2a36 | |||
| a404764abf | |||
| 9a734f9f4d | |||
| 884fb0080f | |||
| a08353a920 | |||
| 5e953d6131 | |||
| 3d2ed40b1e | |||
| 9de686e828 | |||
| 12ddd2cbfb | |||
| 2b0e60a06a | |||
| 922f55740b | |||
| b6262515e0 | |||
| 90d0fd319d | |||
| 310397dd2b | |||
| bb6a741d63 | |||
| 4f1c79d3b7 | |||
| 07cb4848fe | |||
| 8dded8b907 | |||
| 454aae5294 | |||
| 30005ea0e3 | |||
| 9df68a4d07 | |||
| 8f2860165b | |||
| 3a1d0da531 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
.vscode/
|
||||
__debug_bin.exe
|
||||
*.log
|
||||
config.json
|
||||
tavern.exe
|
||||
|
||||
50
config.json
50
config.json
@ -1 +1,49 @@
|
||||
{}
|
||||
{
|
||||
"region_storage": {
|
||||
"default": {
|
||||
"mongo": "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
||||
"redis": {
|
||||
"cache": "redis://192.168.8.94:6380/0",
|
||||
"session": "redis://192.168.8.94:6380/1",
|
||||
"tx": "redis://192.168.8.94:6380/2",
|
||||
"tavern": "redis://192.168.8.94:6380/3",
|
||||
"wshandler": "redis://192.168.8.94:6380/4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"session_storage": "redis://192.168.8.94:6380/3",
|
||||
"session_ttl": 3600,
|
||||
|
||||
"maingate_session_storage": "redis://192.168.8.94:6380/1",
|
||||
"maingate_session_ttl" : 3600,
|
||||
"maingate_api_token": "63d08aa34f0162622c11284b",
|
||||
|
||||
"tavern_redis_url": "redis://192.168.8.94:6380/4",
|
||||
"tavern_service_url": "http://localhost/tavern",
|
||||
"tavern_group_types": {
|
||||
"party": {
|
||||
"max_member": 3,
|
||||
"invite_ttl": 30
|
||||
},
|
||||
"chat" : {
|
||||
"default_capacity" : 1000,
|
||||
"channels" : {
|
||||
"bazzar-1" : {
|
||||
"name" : "FText(bazzar-1)"
|
||||
},
|
||||
"bazzar-2" : {
|
||||
"name" : "FText(bazzar-2)"
|
||||
},
|
||||
"bazzar-3" : {
|
||||
"name" : "FText(bazzar-3)"
|
||||
},
|
||||
"bazzar-4" : {
|
||||
"name" : "FText(bazzar-4)"
|
||||
},
|
||||
"bazzar-5" : {
|
||||
"name" : "FText(bazzar-5)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,65 +1,46 @@
|
||||
{
|
||||
"region_storage" : {
|
||||
"default" : {
|
||||
"mongo" : "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
||||
"redis" : {
|
||||
"url" : "redis://192.168.8.94:6379",
|
||||
"offset" : {
|
||||
"cache" : 0,
|
||||
"session" : 1,
|
||||
"ranking" : 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"dev" : {
|
||||
"mongo" : "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
||||
"redis" : {
|
||||
"url" : "redis://192.168.8.94:6379",
|
||||
"offset" : {
|
||||
"cache" : 0,
|
||||
"session" : 1,
|
||||
"ranking" : 2
|
||||
}
|
||||
"region_storage": {
|
||||
"default": {
|
||||
"mongo": "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
||||
"redis": {
|
||||
"cache": "redis://192.168.8.94:6380/0",
|
||||
"session": "redis://192.168.8.94:6380/1",
|
||||
"tx": "redis://192.168.8.94:6380/2",
|
||||
"tavern": "redis://192.168.8.94:6380/3",
|
||||
"wshandler": "redis://192.168.8.94:6380/4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"maingate_mongodb_url" : "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
||||
"maingate_service_url" : "http://localhost/maingate",
|
||||
"maingate_api_token" : "63d08aa34f0162622c11284b",
|
||||
"session_storage": "redis://192.168.8.94:6380/5",
|
||||
"session_ttl": 3600,
|
||||
|
||||
"tavern_service_url" : "http://localhost/tavern",
|
||||
"tavern_group_types" : {
|
||||
"subjugate" : {
|
||||
"text_search_field" : ["name"],
|
||||
"unique_index" : ["name,_id", "_id,members", "name,hidden"],
|
||||
"search_index" : ["rules"],
|
||||
"member_index" : ["_gid,candidate,luts","_gid,luts","_gid,expiring"],
|
||||
"invite_ttl" : 30,
|
||||
"candidate_ttl" : 3600,
|
||||
"invitee_exlusive" : true,
|
||||
"invitee_is_member" : true,
|
||||
"max_member" : 4
|
||||
"maingate_api_token": "63d08aa34f0162622c11284b",
|
||||
|
||||
"tavern_redis_url": "redis://192.168.8.94:6380/7",
|
||||
"tavern_service_url": "http://localhost/tavern",
|
||||
"tavern_group_types": {
|
||||
"party": {
|
||||
"max_member": 3,
|
||||
"invite_ttl": 30
|
||||
},
|
||||
"lobby" : {
|
||||
"max_member" : 3,
|
||||
"invitee_exlusive" : true,
|
||||
"invitee_is_member" : true,
|
||||
"transient" : true,
|
||||
"invite_ttl" : 30
|
||||
}
|
||||
},
|
||||
|
||||
"ws_sync_pipeline" : "redis://192.168.8.94:6379/3",
|
||||
|
||||
"services" : {
|
||||
"kingdom" : {
|
||||
"개발중" : {
|
||||
"url" :"http://localhost/warehouse/dev",
|
||||
"development" : true
|
||||
},
|
||||
"개인서버" : {
|
||||
"url" : "http://localhost/warehouse/private",
|
||||
"development" : false
|
||||
"chat" : {
|
||||
"default_capacity" : 1000,
|
||||
"channels" : {
|
||||
"bazzar-1" : {
|
||||
"name" : "FText(bazzar-1)"
|
||||
},
|
||||
"bazzar-2" : {
|
||||
"name" : "FText(bazzar-2)"
|
||||
},
|
||||
"bazzar-3" : {
|
||||
"name" : "FText(bazzar-3)"
|
||||
},
|
||||
"bazzar-4" : {
|
||||
"name" : "FText(bazzar-4)"
|
||||
},
|
||||
"bazzar-5" : {
|
||||
"name" : "FText(bazzar-5)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
750
core/apiimpl.go
750
core/apiimpl.go
@ -1,750 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
common "repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// CreateGroup : 그룹 생성
|
||||
// - 그룹 : 멤버와 권한을 관리할 수 있다. 그룹 타입에 따라 디비에 저장되거나 메모리에만 존재한다.
|
||||
// - 생성 요청이 오면 파티를 만든다. 파티을 만들 수 있는지 여부는 서비스에서 결정할 것이고, 이 요청을 호출했다는 것은 서비스가 결정한 그룹 생성 조건을 다 통과했다는 의미이다.
|
||||
// - parameter :
|
||||
// - type : 그룹 종류. 그룹 종류에 따라 인덱스와 쿼리 가능 field가 다르다.
|
||||
func (sub *subTavern) CreateGroup(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
grouptype := sub.groups[typename]
|
||||
if grouptype == nil {
|
||||
logger.Println("CreateGroup failed. group type is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
doc := bson.M{}
|
||||
if err := readBsonDoc(r.Body, &doc); err != nil {
|
||||
logger.Error("CreateGroup failed. readBsonDoc returns err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
inserted, err := grouptype.Create(r.Form, doc)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(inserted[:])
|
||||
}
|
||||
|
||||
// JoinGroup : 그룹에 참가
|
||||
// - type : 그룹 타입
|
||||
// - 그룹 타입에 맞는 키(주로 _id)
|
||||
// - member_id : 참가 멤버의 아이디
|
||||
// - body : 멤버의 속성 bson document
|
||||
func (sub *subTavern) JoinGroup(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("JoinGroup failed. group type is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
doc := bson.M{}
|
||||
if err := readBsonDoc(r.Body, &doc); err != nil {
|
||||
logger.Error("JoinGroup failed. readBsonDoc returns err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
gidobj, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("JoinGroup failed. gid is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
midobj, midok := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
tidobj, tidok := common.ReadObjectIDFormValue(r.Form, "tid")
|
||||
if !midok && !tidok {
|
||||
// 둘다 없네?
|
||||
logger.Println("JoinGroup failed. tid or mid should be exist")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if candidate, ok := common.ReadBoolFormValue(r.Form, "candidate"); ok && candidate {
|
||||
err = group.Candidate(gidobj, midobj, doc)
|
||||
} else {
|
||||
tidobj, err = group.Join(gidobj, midobj, tidobj, doc)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"gid": gidobj.Hex(),
|
||||
"tid": tidobj.Hex(),
|
||||
})
|
||||
} else if err == errGroupNotExist {
|
||||
w.Write([]byte("{}"))
|
||||
} else if err != nil {
|
||||
logger.Error("JoinGroup failed :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// Invite : 초대
|
||||
// - type : 초대 타입 (required)
|
||||
// - from : 초대하는 자 (required)
|
||||
// - to : 초대받는 자 (required)
|
||||
// - timeout : 초대 유지시간(optional. 없으면 config 기본 값)
|
||||
// - (body) : 검색시 노출되는 document
|
||||
func (sub *subTavern) Invite(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("Invite failed. group type is missing :", r)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("Invite failed. gid is missing :", r)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
if !ok {
|
||||
logger.Println("Invite failed. mid is missing :", r)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var reqdoc struct {
|
||||
Inviter bson.M `bson:"inviter"`
|
||||
Invitee bson.M `bson:"invitee"`
|
||||
}
|
||||
if err := readBsonDoc(r.Body, &reqdoc); err != nil {
|
||||
logger.Error("Invite failed. readBsonDoc returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := group.Invite(gid, mid, reqdoc.Inviter, reqdoc.Invitee)
|
||||
if err != nil {
|
||||
logger.Error("Invite failed. group.Invite returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(result))
|
||||
}
|
||||
|
||||
func (sub *subTavern) CancelInvitation(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("CancelInvitation failed. group type is missing :", r)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
tid, ok := common.ReadObjectIDFormValue(r.Form, "tid")
|
||||
if !ok {
|
||||
logger.Println("CancelInvitation failed. form value 'tid' is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("CancelInvitation failed. form value 'gid' is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := group.CancelInvitation(gid, tid); err != nil {
|
||||
logger.Println("CancelInvitation failed. group.CancelInvitation returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (sub *subTavern) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("CancelInvitation failed. group type is missing :", r)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gid, _ := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
mid, _ := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
tid, ok := common.ReadObjectIDFormValue(r.Form, "tid")
|
||||
if !ok {
|
||||
logger.Println("CancelInvitation failed. form value 'tid' is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var member bson.M
|
||||
if err := readBsonDoc(r.Body, &member); err != nil {
|
||||
logger.Error("AcceptInvitation failed. readBsonDoc returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
gidbytes, err := group.AcceptInvitation(gid, mid, tid, member)
|
||||
if err != nil {
|
||||
logger.Error("AcceptInvitation failed. group.AcceptInvitation returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(gidbytes.Hex()))
|
||||
}
|
||||
|
||||
func (sub *subTavern) DenyInvitation(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("DenyInvitation failed. group type is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gid, _ := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
mid, _ := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
tid, ok := common.ReadObjectIDFormValue(r.Form, "tid")
|
||||
if !ok {
|
||||
logger.Println("DenyInvitation failed. tid is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := group.DenyInvitation(gid, mid, tid)
|
||||
if err != nil {
|
||||
logger.Error("DenyInvitation failed. group.DenyInvitation returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sub *subTavern) QueryInvitations(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("QueryInvitations failed. group type is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
if !ok {
|
||||
logger.Println("QueryInvitations failed. mid is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var after primitive.Timestamp
|
||||
if v, ok := common.ReadStringFormValue(r.Form, "after"); ok && v != "0.0" {
|
||||
after = common.DotStringToTimestamp(v)
|
||||
}
|
||||
|
||||
result, err := group.QueryInvitations(mid, after)
|
||||
|
||||
if err != nil {
|
||||
logger.Println("QueryInvitations failed. group.QueryInvitations returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeBsonArr(w, result); err != nil {
|
||||
logger.Println("QueryInvitations failed. Encode returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sub *subTavern) QueryOnlineGroup(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("QueryOnlineGroup failed. group type is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var cmd *redis.StringSliceCmd
|
||||
scoreStart, _ := common.ReadStringFormValue(r.Form, "score_start")
|
||||
scoreStop, _ := common.ReadStringFormValue(r.Form, "score_stop")
|
||||
|
||||
if len(scoreStart) > 0 || len(scoreStop) > 0 {
|
||||
if len(scoreStart) == 0 {
|
||||
scoreStart = "-inf"
|
||||
}
|
||||
if len(scoreStop) == 0 {
|
||||
scoreStop = "+inf"
|
||||
}
|
||||
cmd = sub.wsh.RedisSync.ZRangeArgs(context.Background(), redis.ZRangeArgs{
|
||||
Key: onlineGroupQueryKey(typename),
|
||||
ByScore: true,
|
||||
Start: scoreStart,
|
||||
Stop: scoreStop,
|
||||
Rev: true,
|
||||
Count: 1,
|
||||
})
|
||||
} else {
|
||||
// 아무거나
|
||||
cmd = sub.wsh.RedisSync.ZRandMember(context.Background(), onlineGroupQueryKey(typename), 1, false)
|
||||
}
|
||||
|
||||
result, err := cmd.Result()
|
||||
if err != nil {
|
||||
logger.Error("QueryOnlineGroup failed. redid.ZRandMember returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeBsonDoc(w, bson.M{"r": result})
|
||||
}
|
||||
|
||||
func (sub *subTavern) SearchGroup(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("SearchGroup failed. group type is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
projection, _ := common.ReadStringFormValue(r.Form, "projection")
|
||||
|
||||
var filter bson.M
|
||||
if err := readBsonDoc(r.Body, &filter); err != nil {
|
||||
logger.Error("SearchGroup failed. readBsonDoc returns err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := group.FindAll(filter, projection, primitive.Timestamp{})
|
||||
if err != nil {
|
||||
logger.Error("SearchGroup failed. FindAll err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeBsonArr(w, result); err != nil {
|
||||
logger.Error("json marshal failed :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sub *subTavern) QueryOnlineState(w http.ResponseWriter, r *http.Request) {
|
||||
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
if !ok {
|
||||
logger.Println("IsOnline failed. mid is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
state := sub.wsh.GetState(sub.region, mid)
|
||||
w.Write([]byte(state))
|
||||
}
|
||||
|
||||
func (sub *subTavern) IsOnline(w http.ResponseWriter, r *http.Request) {
|
||||
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
if !ok {
|
||||
logger.Println("IsOnline failed. mid is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if state := sub.wsh.GetState(sub.region, mid); len(state) > 0 {
|
||||
w.Write([]byte("true"))
|
||||
} else {
|
||||
w.Write([]byte("false"))
|
||||
}
|
||||
}
|
||||
|
||||
// QueryGroup : 그룹조회
|
||||
// - type : 그룹 타입
|
||||
// - 그룹 타입에 맞는 키(주로 _id)
|
||||
// - projection : select할 필드. ,로 구분
|
||||
func (sub *subTavern) QueryGroup(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("QueryGroup failed. group type is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "_id")
|
||||
if !ok {
|
||||
logger.Println("QueryGroup failed. _id is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
projection, _ := common.ReadStringFormValue(r.Form, "projection")
|
||||
|
||||
after, _ := common.ReadStringFormValue(r.Form, "after")
|
||||
if after != "0.0" {
|
||||
projection += ",+luts"
|
||||
}
|
||||
|
||||
result, err := group.FindOne(gid, projection)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if result == nil {
|
||||
return
|
||||
}
|
||||
if len(after) > 0 {
|
||||
if luts, ok := result["luts"].(primitive.Timestamp); ok {
|
||||
afterts := common.DotStringToTimestamp(after)
|
||||
if primitive.CompareTimestamp(luts, afterts) < 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := writeBsonDoc(w, result); err != nil {
|
||||
logger.Error("json marshal failed :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// QueryGroupMembers : 그룹내 멤버 조회
|
||||
// - type : 그룹 타입
|
||||
// - 그룹 타입에 맞는 키(주로 _id)
|
||||
// - projection : select할 필드. ,로 구분
|
||||
func (sub *subTavern) QueryGroupMembers(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("QueryGroupMembers failed. group type is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gidobj, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("QueryGroupMembers failed. _id is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
midobj, _ := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
var after primitive.Timestamp
|
||||
if ts, ok := common.ReadStringFormValue(r.Form, "after"); ok && ts != "0.0" {
|
||||
after = common.DotStringToTimestamp(ts)
|
||||
}
|
||||
projection, _ := common.ReadStringFormValue(r.Form, "projection")
|
||||
|
||||
result, err := group.QueryMembers(gidobj, midobj, projection, after)
|
||||
if err != nil {
|
||||
logger.Error("QueryGroupMembers failed. FindAll err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeBsonDoc(w, result); err != nil {
|
||||
logger.Error("QueryGroupMembers failed. writeBsonArr err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sub *subTavern) QueryGroupMember(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("QueryGroupMember failed. group type is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("QueryGroupMember failed. gid is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mid, midok := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
tid, tidok := common.ReadObjectIDFormValue(r.Form, "tid")
|
||||
if !midok && !tidok {
|
||||
// 둘 중 하나는 있어야지
|
||||
logger.Println("QueryGroupMember failed. tid and mid are both missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
projection, _ := common.ReadStringFormValue(r.Form, "projection")
|
||||
result, err := group.QueryMember(gid, mid, tid, projection)
|
||||
if err != nil {
|
||||
logger.Println("QueryGroupMember failed. group.QueryMember returns err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeBsonDoc(w, result); err != nil {
|
||||
logger.Error("QueryGroupMember failed. writeBsonDoc err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// LeaveGroup : 그룹에서 나감 or 내보냄
|
||||
// - type : 그룹 타입
|
||||
// - 그룹 타입에 맞는 키(주로 _id)
|
||||
// - member_id : 나갈 멤버의 아이디
|
||||
func (sub *subTavern) LeaveGroup(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("LeaveGroup failed. group type is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("LeaveGroup failed. gid is missing :", r.Form)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
mid, midok := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
tid, tidok := common.ReadObjectIDFormValue(r.Form, "tid")
|
||||
if !midok && !tidok {
|
||||
// 둘 중 하나는 있어야지
|
||||
logger.Println("LeaveGroup failed. tid and mid are both missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := group.Leave(gid, mid, tid); err != nil {
|
||||
// 둘 중 하나는 있어야지
|
||||
logger.Println("LeaveGroup failed. group.Leave returns err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func (sub *subTavern) UpdateMemberDocument(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("DismissGroup failed. type is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
midobj, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
if !ok {
|
||||
logger.Println("UpdateMemberDocument failed. member_id is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gidobj, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("UpdateMemberDocument failed. _id is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var updatedoc bson.M
|
||||
if err := readBsonDoc(r.Body, &updatedoc); err != nil {
|
||||
logger.Error("UpdateMemberDocument failed. body decoding error :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := group.UpdateMemberDocument(gidobj, midobj, updatedoc); err != nil {
|
||||
logger.Println("UpdateMemberDocument failed :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sub *subTavern) DismissGroup(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("DismissGroup failed. type is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("DismissGroup failed. gid is missing :")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := group.Dismiss(gid); err != nil {
|
||||
logger.Error("DismissGroup failed. group.Dismiss returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sub *subTavern) UpdateGroupDocument(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("UpdateGroupDocument failed. type is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("UpdateGroupDocument failed. gid is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
logger.Error("UpdateGroupDocument failed. readBsonDoc err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := group.UpdateGroupDocument(gid, body); err != nil {
|
||||
logger.Error("UpdateGroupDocument failed. group.UpdateGroupDocument returns err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sub *subTavern) PauseGroupMember(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("DismissGroup failed. type is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
midobj, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
if !ok {
|
||||
logger.Println("UpdateMemberDocument failed. member_id is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gidobj, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("UpdateMemberDocument failed. _id is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
group.PauseMember(gidobj, midobj)
|
||||
}
|
||||
|
||||
func (sub *subTavern) DropPausedMember(w http.ResponseWriter, r *http.Request) {
|
||||
typename, _ := common.ReadStringFormValue(r.Form, "type")
|
||||
group := sub.groups[typename]
|
||||
if group == nil {
|
||||
logger.Println("DropDeadMember failed. type is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
|
||||
if !ok {
|
||||
logger.Println("DropDeadMember failed. gid is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
|
||||
if !ok {
|
||||
logger.Println("DropDeadMember failed. mid is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := group.DropPausedMember(gid, mid); err != nil {
|
||||
logger.Error("DropDeadMember failed. group.DropDeadMember returns err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// func (sub *subTavern) deliveryMessageHandler(deliveryChan <-chan wshandler.DeliveryMessage) {
|
||||
// defer func() {
|
||||
// r := recover()
|
||||
// if r != nil {
|
||||
// logger.Error(r)
|
||||
// }
|
||||
// }()
|
||||
|
||||
// redisSync := sub.wsh.RedisSync
|
||||
// for msg := range deliveryChan {
|
||||
// mid := msg.Alias
|
||||
// if msg.Body != nil {
|
||||
// buffer := msg.Body
|
||||
|
||||
// var channame string
|
||||
// for i, ch := range buffer {
|
||||
// if ch == 0 {
|
||||
// channame = string(buffer[:i])
|
||||
// buffer = buffer[i+1:]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// if len(channame) == 0 {
|
||||
// continue
|
||||
// }
|
||||
|
||||
// buffer = append(mid[:], buffer...)
|
||||
// _, err := redisSync.Publish(context.Background(), channame, buffer).Result()
|
||||
// if err != nil {
|
||||
// logger.Error(err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// if len(msg.Command) > 0 {
|
||||
// switch msg.Command {
|
||||
// case "pause":
|
||||
// gidtype := msg.Conn.GetTag("gid")
|
||||
// if len(gidtype) > 0 {
|
||||
// tokens := strings.SplitN(gidtype, "@", 2)
|
||||
// gidobj, _ := primitive.ObjectIDFromHex(tokens[0])
|
||||
// gtype := tokens[1]
|
||||
// group := sub.groups[gtype]
|
||||
// if group != nil {
|
||||
// group.PauseMember(gidobj, msg.Alias, msg.Conn)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// logger.Println("delivery chan fin")
|
||||
// }
|
||||
@ -1,46 +1,3 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type groupConfig struct {
|
||||
UniqueIndex []string `json:"unique_index"`
|
||||
SearchIndex []string `json:"search_index"`
|
||||
MemberIndex []string `json:"member_index"`
|
||||
TextSearchFields []string `json:"text_search_field"`
|
||||
InviteExpire int32 `json:"invite_ttl"` // 그룹이 개인에게 보낸 초대장 만료 기한
|
||||
CandidateExpire int32 `json:"candidate_ttl"` // 개인이 그룹에게 보낸 신청서 만료 기한
|
||||
InviteeExlusive bool `json:"invitee_exlusive"`
|
||||
InviteeIsMember bool `json:"invitee_is_member"`
|
||||
MaxMember int `json:"max_member"`
|
||||
Transient bool `json:"transient"`
|
||||
|
||||
Name string
|
||||
}
|
||||
|
||||
type group interface {
|
||||
Create(form url.Values, doc bson.M) (primitive.ObjectID, error)
|
||||
Candidate(groupID primitive.ObjectID, memberID primitive.ObjectID, doc bson.M) error
|
||||
Join(groupID primitive.ObjectID, memberID primitive.ObjectID, ticketID primitive.ObjectID, doc bson.M) (newTicketID primitive.ObjectID, err error)
|
||||
FindTicketID(groupID primitive.ObjectID, memberID primitive.ObjectID) primitive.ObjectID
|
||||
Invite(groupID primitive.ObjectID, memberID primitive.ObjectID, inviterDoc bson.M, inviteeDoc bson.M) (string, error)
|
||||
CancelInvitation(groupID primitive.ObjectID, ticketID primitive.ObjectID) error
|
||||
AcceptInvitation(groupID primitive.ObjectID, mid primitive.ObjectID, ticketID primitive.ObjectID, member bson.M) (primitive.ObjectID, error)
|
||||
DenyInvitation(groupID primitive.ObjectID, mid primitive.ObjectID, ticketID primitive.ObjectID) error
|
||||
QueryInvitations(memberID primitive.ObjectID, after primitive.Timestamp) ([]bson.M, error)
|
||||
Exist(groupID primitive.ObjectID, filter bson.M) (bool, error)
|
||||
FindAll(filter bson.M, projection string, after primitive.Timestamp) ([]bson.M, error)
|
||||
FindOne(groupID primitive.ObjectID, projection string) (bson.M, error)
|
||||
QueryMembers(groupID primitive.ObjectID, requesterID primitive.ObjectID, projection string, after primitive.Timestamp) (map[string]bson.M, error)
|
||||
QueryMember(groupID primitive.ObjectID, memberID primitive.ObjectID, ticketID primitive.ObjectID, projection string) (bson.M, error)
|
||||
Leave(groupID primitive.ObjectID, memberID primitive.ObjectID, ticketID primitive.ObjectID) error
|
||||
DropPausedMember(groupID primitive.ObjectID, memberID primitive.ObjectID) error
|
||||
PauseMember(groupID primitive.ObjectID, memberID primitive.ObjectID) error
|
||||
UpdateMemberDocument(groupID primitive.ObjectID, memberID primitive.ObjectID, doc bson.M) error
|
||||
Dismiss(groupID primitive.ObjectID) error
|
||||
UpdateGroupDocument(groupID primitive.ObjectID, body []byte) error
|
||||
}
|
||||
type configDocument map[string]any
|
||||
|
||||
348
core/group_chat.go
Normal file
348
core/group_chat.go
Normal file
@ -0,0 +1,348 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/gorilla/websocket"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
||||
)
|
||||
|
||||
type channelID = string
|
||||
type channelConfig struct {
|
||||
Capacity int64 `json:"capacity"`
|
||||
Size int64 `json:"size"`
|
||||
Key string `json:"key"`
|
||||
Members map[string]int32 `json:"members"`
|
||||
emptyJson string
|
||||
inoutChan chan string
|
||||
}
|
||||
|
||||
type chatConfig struct {
|
||||
DefaultCapacity int64 `json:"default_capacity"`
|
||||
Channels map[string]*channelConfig `json:"channels"`
|
||||
}
|
||||
|
||||
type groupChat struct {
|
||||
chatConfig
|
||||
rh *gocommon.RedisonHandler
|
||||
enterRoom func(channelID, accountID)
|
||||
leaveRoom func(channelID, accountID)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
gc.enterRoom = func(chanid channelID, accid accountID) {
|
||||
tv.wsh.EnterRoom(string(chanid), accid)
|
||||
}
|
||||
gc.leaveRoom = func(chanid channelID, accid accountID) {
|
||||
tv.wsh.LeaveRoom(string(chanid), accid)
|
||||
}
|
||||
gc.sendUpstreamMessage = func(msg *wshandler.UpstreamMessage) {
|
||||
tv.wsh.SendUpstreamMessage(msg)
|
||||
}
|
||||
|
||||
gc.rh = tv.redison
|
||||
|
||||
for name, cfg := range gc.chatConfig.Channels {
|
||||
if cfg.Capacity == 0 {
|
||||
cfg.Capacity = gc.chatConfig.DefaultCapacity
|
||||
}
|
||||
cfg.Key = name
|
||||
cfg.Size = 0
|
||||
|
||||
jm, _ := json.Marshal(cfg)
|
||||
cfg.emptyJson = fmt.Sprintf("[%s]", string(jm))
|
||||
cfg.Members = make(map[string]int32)
|
||||
_, err := gc.rh.JSONSet(name, "$", cfg)
|
||||
if *devflag && err != nil {
|
||||
gc.rh.JSONDel(name, "$")
|
||||
_, err = gc.rh.JSONSet(name, "$", cfg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inoutchan := make(chan string, 10)
|
||||
cfg.inoutChan = inoutchan
|
||||
|
||||
go func(chanid string) {
|
||||
var cur []string
|
||||
tick := time.After(3 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-tick:
|
||||
tick = time.After(3 * time.Second)
|
||||
if len(cur) > 0 {
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + chanid,
|
||||
Body: map[string]any{"inout": cur},
|
||||
Tag: []string{"ChattingChannelProperties"},
|
||||
})
|
||||
cur = nil
|
||||
}
|
||||
|
||||
case m := <-inoutchan:
|
||||
cur = append(cur, m)
|
||||
}
|
||||
}
|
||||
}(name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc *groupChat) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender) {
|
||||
}
|
||||
|
||||
func (gc *groupChat) ClientDisconnected(msg string, callby *wshandler.Sender) {
|
||||
if msg == wshandler.ForceShutdownCloseMessage {
|
||||
gc.rh.Del(gc.rh.Context(), "gc-"+callby.Accid.Hex()).Result()
|
||||
return
|
||||
}
|
||||
|
||||
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 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},
|
||||
Tag: []string{typename + ".LeavePrivateChannel"},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *groupChat) EnterPublicChannel(ctx wshandler.ApiCallContext) {
|
||||
gc.LeavePublicChannel(ctx)
|
||||
|
||||
chanid := ctx.Arguments[0].(string)
|
||||
cfg, ok := gc.chatConfig.Channels[chanid]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
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"},
|
||||
})
|
||||
}
|
||||
|
||||
func (gc *groupChat) LeavePublicChannel(ctx wshandler.ApiCallContext) {
|
||||
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(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *groupChat) TextMessage(ctx wshandler.ApiCallContext) {
|
||||
chanid := ctx.Arguments[0].(string)
|
||||
msg := ctx.Arguments[1].(string)
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + chanid,
|
||||
Body: map[string]any{"sender": ctx.CallBy.Alias, "msg": msg},
|
||||
Tag: []string{"TextMessage"},
|
||||
})
|
||||
}
|
||||
|
||||
func (gc *groupChat) EnterPrivateChannel(ctx wshandler.ApiCallContext) {
|
||||
typename := ctx.Arguments[0].(string)
|
||||
channel := ctx.Arguments[1].(string)
|
||||
var reason string
|
||||
if len(ctx.Arguments) > 2 {
|
||||
reason = ctx.Arguments[2].(string)
|
||||
}
|
||||
|
||||
if len(reason) > 0 {
|
||||
// 수락
|
||||
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)
|
||||
return
|
||||
}
|
||||
gc.enterRoom(channel, ctx.CallBy.Accid)
|
||||
}
|
||||
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + channel,
|
||||
Body: map[string]any{
|
||||
"sender": ctx.CallBy.Alias,
|
||||
"msg": reason,
|
||||
},
|
||||
Tag: []string{typename + ".EnterPrivateChannel"},
|
||||
})
|
||||
}
|
||||
|
||||
func (gc *groupChat) LeavePrivateChannel(ctx wshandler.ApiCallContext) {
|
||||
typename := ctx.Arguments[0].(string)
|
||||
chanid := ctx.Arguments[1].(string)
|
||||
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{
|
||||
Target: "#" + chanid,
|
||||
Body: map[string]any{"sender": ctx.CallBy.Alias},
|
||||
Tag: []string{typename + ".LeavePrivateChannel"},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *groupChat) FetchChattingChannels(w http.ResponseWriter, r *http.Request) {
|
||||
var prefix string
|
||||
if err := gocommon.MakeDecoder(r).Decode(&prefix); err != nil {
|
||||
logger.Println("FetchChattingChannels failed. ReadJsonDocumentFromBody returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if len(prefix) == 0 {
|
||||
logger.Println("FetchChattingChannel failed. prefix is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var rows []string
|
||||
for name, cfg := range gc.chatConfig.Channels {
|
||||
if len(prefix) > 0 {
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
onechan, err := gc.rh.JSONGet(name, "$")
|
||||
if err != nil && err != redis.Nil {
|
||||
logger.Println("FetchChattingChannel failed. HGetAll return err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err == redis.Nil || onechan == nil {
|
||||
rows = append(rows, cfg.emptyJson)
|
||||
} else {
|
||||
// json array로 나온다
|
||||
rows = append(rows, strings.Trim(onechan.(string), "[]"))
|
||||
}
|
||||
}
|
||||
|
||||
gocommon.MakeEncoder(w, r).Encode(rows)
|
||||
}
|
||||
|
||||
func (gc *groupChat) QueryPlayerChattingChannel(w http.ResponseWriter, r *http.Request) {
|
||||
var accid primitive.ObjectID
|
||||
if err := gocommon.MakeDecoder(r).Decode(&accid); err != nil {
|
||||
logger.Println("QueryPlayerChattingChannel failed. ReadJsonDocumentFromBody returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sub, err := gc.rh.HGetAll(gc.rh.Context(), "gc-"+accid.Hex()).Result()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if sub == nil {
|
||||
sub = make(map[string]string)
|
||||
}
|
||||
gocommon.MakeEncoder(w, r).Encode(sub)
|
||||
}
|
||||
|
||||
func (gc *groupChat) SendMessageOnChannel(w http.ResponseWriter, r *http.Request) {
|
||||
var msg wshandler.UpstreamMessage
|
||||
if err := gocommon.MakeDecoder(r).Decode(&msg); err != nil {
|
||||
logger.Println("SendMessageOnChannel failed. ReadJsonDocumentFromBody return err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gc.sendUpstreamMessage(&msg)
|
||||
}
|
||||
243
core/group_instant.go
Normal file
243
core/group_instant.go
Normal file
@ -0,0 +1,243 @@
|
||||
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)
|
||||
}
|
||||
1150
core/group_memory.go
1150
core/group_memory.go
File diff suppressed because it is too large
Load Diff
979
core/group_party.go
Normal file
979
core/group_party.go
Normal file
@ -0,0 +1,979 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"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 accountID = primitive.ObjectID
|
||||
type groupID = primitive.ObjectID
|
||||
|
||||
func makeTid(gid groupID, in accountID) string {
|
||||
var out primitive.ObjectID
|
||||
for i := range in {
|
||||
out[12-i-1] = gid[i] ^ in[12-i-1]
|
||||
}
|
||||
return out.Hex()
|
||||
}
|
||||
|
||||
func midFromTid(gid groupID, in string) accountID {
|
||||
h, _ := primitive.ObjectIDFromHex(in)
|
||||
|
||||
var out accountID
|
||||
for i := range h {
|
||||
out[12-i-1] = gid[i] ^ h[12-i-1]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type Invitation struct {
|
||||
GroupID groupID `json:"_gid"`
|
||||
TicketID string `json:"_tid"`
|
||||
Inviter bson.M `json:"_inviter"` // memberDoc.Body
|
||||
ExpireAtUTC int64 `json:"_expire_at_utc"`
|
||||
}
|
||||
|
||||
// 플레이어한테 공유하는 멤버 정보
|
||||
type memberDoc struct {
|
||||
Body bson.M `json:"_body"`
|
||||
Invite bool `json:"_invite"`
|
||||
InviteExpire int64 `json:"_invite_exp"`
|
||||
}
|
||||
|
||||
type InvitationFail bson.M
|
||||
|
||||
type partyDoc struct {
|
||||
Members map[string]any `json:"_members"`
|
||||
InCharge string `json:"_incharge"`
|
||||
Gid string `json:"_gid"`
|
||||
|
||||
rh *gocommon.RedisonHandler
|
||||
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 {
|
||||
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 *partyDoc) 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 *partyDoc) strid() string {
|
||||
if len(gd.Gid) == 0 {
|
||||
gd.Gid = gd.id.Hex()
|
||||
}
|
||||
return gd.Gid
|
||||
}
|
||||
|
||||
func (gd *partyDoc) tid(in accountID) string {
|
||||
return makeTid(gd.id, in)
|
||||
}
|
||||
|
||||
func (gd *partyDoc) mid(tid string) accountID {
|
||||
tidobj, _ := primitive.ObjectIDFromHex(tid)
|
||||
var out primitive.ObjectID
|
||||
for i := range tidobj {
|
||||
out[12-i-1] = gd.id[i] ^ tidobj[12-i-1]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (gd *partyDoc) addInvite(mid accountID, body bson.M, ttl time.Duration, max int) (*memberDoc, error) {
|
||||
targetmid := mid
|
||||
targetbody := body
|
||||
|
||||
// 초대 가능한 빈 자리가 있나
|
||||
tids, err := gd.rh.JSONObjKeys(gd.strid(), "$._members")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
createNewDoc := func() *memberDoc {
|
||||
return &memberDoc{
|
||||
Body: targetbody,
|
||||
Invite: true,
|
||||
InviteExpire: now.Add(ttl).Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
newtid := gd.tid(targetmid)
|
||||
if len(tids) < max {
|
||||
// 빈자리를 찾았다.
|
||||
newdoc := createNewDoc()
|
||||
_, err := gd.rh.JSONSet(gd.strid(), "$._members."+newtid, newdoc)
|
||||
return newdoc, err
|
||||
}
|
||||
|
||||
expires, err := gd.rh.JSONGetInt64(gd.strid(), "$._members.._invite_exp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var delpaths []string
|
||||
for i, expire := range expires {
|
||||
if expire < now.Unix() {
|
||||
// 만료된 초대가 있네? 지우자
|
||||
delpaths = append(delpaths, "$._members."+tids[i])
|
||||
}
|
||||
}
|
||||
|
||||
if len(delpaths) == 0 {
|
||||
// 빈자리가 없다
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := gd.rh.JSONMDel(gd.strid(), delpaths); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newdoc := createNewDoc()
|
||||
_, err = gd.rh.JSONSet(gd.strid(), "$._members."+newtid, newdoc)
|
||||
return newdoc, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
counts, err := gd.rh.JSONObjLen(gd.strid(), "$._members")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(counts) > 0 && counts[0] == 0 {
|
||||
_, err = gd.rh.Del(gd.rh.Context(), gd.strid()).Result()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (gd *partyDoc) removeMember(mid accountID) error {
|
||||
return gd.removeMemberByTid(gd.tid(mid))
|
||||
}
|
||||
|
||||
func (gd *partyDoc) 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] {
|
||||
body := v.(map[string]any)["_body"]
|
||||
out[gd.mid(k).Hex()] = body
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type partyConfig struct {
|
||||
InviteExpire int32 `json:"invite_ttl"` // 그룹이 개인에게 보낸 초대장 만료 기한
|
||||
MaxMember int `json:"max_member"`
|
||||
|
||||
Name string
|
||||
}
|
||||
|
||||
type groupParty struct {
|
||||
partyConfig
|
||||
sendUpstreamMessage func(*wshandler.UpstreamMessage)
|
||||
enterRoom func(groupID, accountID)
|
||||
leaveRoom func(groupID, accountID)
|
||||
rh *gocommon.RedisonHandler
|
||||
}
|
||||
|
||||
func (gp *groupParty) Initialize(tv *Tavern, cfg configDocument) error {
|
||||
rem, _ := json.Marshal(cfg)
|
||||
err := json.Unmarshal(rem, &gp.partyConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gp.rh = tv.redison
|
||||
gp.sendUpstreamMessage = func(msg *wshandler.UpstreamMessage) {
|
||||
tv.wsh.SendUpstreamMessage(msg)
|
||||
}
|
||||
gp.enterRoom = func(gid groupID, accid accountID) {
|
||||
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
|
||||
}
|
||||
|
||||
// JoinParty : 그룹에 참가
|
||||
// - type : 그룹 타입
|
||||
// - 그룹 타입에 맞는 키(주로 _id)
|
||||
// - member_id : 참가 멤버의 아이디
|
||||
// - body : 멤버의 속성 bson document
|
||||
func (gp *groupParty) JoinParty(w http.ResponseWriter, r *http.Request) {
|
||||
var data struct {
|
||||
Gid primitive.ObjectID
|
||||
Mid primitive.ObjectID
|
||||
Character bson.M
|
||||
Option string
|
||||
}
|
||||
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
||||
logger.Println("JoinParty failed. DecodeGob returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
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")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gd, err := gp.find(gid)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if gd == nil {
|
||||
// 그룹이 없다. 없을 수도 있지
|
||||
gocommon.MakeEncoder(w, r).Encode("")
|
||||
return
|
||||
}
|
||||
|
||||
// 내 정보 업데이트할 때에도 사용됨
|
||||
if option == "XX" {
|
||||
// 이미 멤버여야 재입장 가능
|
||||
path := "$._members." + gd.tid(mid) + "._body"
|
||||
if _, err := gd.rh.JSONSet(gd.strid(), path, character, gocommon.RedisonSetOptionXX); err != nil {
|
||||
// 멤버가 아니네? 그새 파티장이 쫓아냈을 수도 있다.
|
||||
gocommon.MakeEncoder(w, r).Encode("")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
gp.rh.JSONSet(mid.Hex(), "$.party", bson.M{"id": gid.Hex()})
|
||||
memdoc, err := gd.addMember(mid, character, option)
|
||||
if err != nil {
|
||||
logger.Println("JoinParty failed :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 기존 유저에게 새 유저 알림
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + gid.Hex(),
|
||||
Body: map[string]any{
|
||||
gd.tid(mid): memdoc,
|
||||
},
|
||||
Tag: []string{"MemberDocFull"},
|
||||
})
|
||||
|
||||
gp.enterRoom(gid, mid)
|
||||
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: mid.Hex(),
|
||||
Body: gd.loadFull(),
|
||||
Tag: []string{"GroupDocFull"},
|
||||
})
|
||||
|
||||
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
|
||||
Mid string
|
||||
}
|
||||
|
||||
// accid가 접속해 있지 않으면 파티에서 나간 걸로 간주하고
|
||||
// accid가 접속해 있으면 아무것도 하지 않는다.
|
||||
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
|
||||
logger.Println("ConditionalClearPartyMember failed. DecodeGob returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
pids, err := gp.rh.JSONGetString(doc.Mid, "$.party.id")
|
||||
if err != nil {
|
||||
logger.Println("ConditionalClearPartyMember failed. gp.rh.JSONGetString returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
removeMember := func() {
|
||||
gid, _ := primitive.ObjectIDFromHex(doc.Gid)
|
||||
mid, _ := primitive.ObjectIDFromHex(doc.Mid)
|
||||
gd := partyDoc{
|
||||
id: gid,
|
||||
rh: gp.rh,
|
||||
}
|
||||
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{
|
||||
gd.tid(mid): bson.M{},
|
||||
},
|
||||
Tag: []string{"MemberDocFull"},
|
||||
})
|
||||
gd.removeMember(mid)
|
||||
}
|
||||
}
|
||||
|
||||
if len(pids) == 0 {
|
||||
// 없다.
|
||||
// doc.Gid에서 제거
|
||||
removeMember()
|
||||
} else if pids[0] != doc.Gid {
|
||||
// 다른 파티? 기존 파티에서 제거
|
||||
removeMember()
|
||||
}
|
||||
}
|
||||
|
||||
// InviteToParty : 초대
|
||||
// - type : 초대 타입 (required)
|
||||
// - from : 초대하는 자 (required)
|
||||
// - to : 초대받는 자 (required)
|
||||
// - timeout : 초대 유지시간(optional. 없으면 config 기본 값)
|
||||
// - (body) : 검색시 노출되는 document
|
||||
func (gp *groupParty) InviteToParty(w http.ResponseWriter, r *http.Request) {
|
||||
var doc struct {
|
||||
Gid primitive.ObjectID
|
||||
Mid primitive.ObjectID
|
||||
Target primitive.ObjectID
|
||||
Inviter bson.M
|
||||
Invitee bson.M
|
||||
InitialPartyDoc bson.M
|
||||
}
|
||||
|
||||
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
|
||||
logger.Println("InviteToParty failed. DecodeGob returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
targetid := doc.Target
|
||||
gid := doc.Gid
|
||||
mid := doc.Mid
|
||||
|
||||
// targetid에 초대한 mid가 들어있다.
|
||||
success, err := gp.rh.SetNX(context.Background(), "inv."+targetid.Hex(), mid.Hex(), time.Duration(gp.InviteExpire)*time.Second).Result()
|
||||
if err != nil {
|
||||
logger.Println("InviteToParty failed. gp.rh.SetNX() return err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !success {
|
||||
// 이미 초대 중이다.
|
||||
// inviter한테 알려줘야 한다.
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: mid.Hex(),
|
||||
Body: doc.Invitee,
|
||||
Tag: []string{"InvitationFail"},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
gd, err := gp.find(gid)
|
||||
if err != nil {
|
||||
logger.Println("InviteToParty failed. gp.find() return err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if gd == nil {
|
||||
gid = primitive.NewObjectID()
|
||||
gd, err = gp.createGroup(gid, mid, doc.Inviter, doc.InitialPartyDoc)
|
||||
if err != nil {
|
||||
logger.Println("InviteToParty failed. gp.createGroup() return err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 내가 wshandler room에 입장
|
||||
gp.enterRoom(gid, mid)
|
||||
gp.rh.JSONSet(mid.Hex(), "$.party", bson.M{"id": gid.Hex()})
|
||||
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: mid.Hex(),
|
||||
Body: gd,
|
||||
Tag: []string{"GroupDocFull"},
|
||||
})
|
||||
}
|
||||
|
||||
newdoc, err := gd.addInvite(targetid, doc.Invitee, time.Duration(gp.InviteExpire+1)*time.Second, gp.MaxMember)
|
||||
if err != nil {
|
||||
logger.Println("InviteToParty failed. gp.addInvite() return err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// invitee에게 알림
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: targetid.Hex(),
|
||||
Body: Invitation{
|
||||
GroupID: gid,
|
||||
TicketID: gd.tid(targetid),
|
||||
Inviter: doc.Inviter,
|
||||
ExpireAtUTC: newdoc.InviteExpire,
|
||||
},
|
||||
Tag: []string{"Invitation"},
|
||||
})
|
||||
|
||||
// 그룹에게 알림
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + gid.Hex(),
|
||||
Body: bson.M{
|
||||
gd.tid(targetid): bson.M{
|
||||
"nickname": doc.Invitee["nickname"],
|
||||
"_invite": true,
|
||||
},
|
||||
},
|
||||
Tag: []string{"MemberDocFull"},
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
Mid primitive.ObjectID
|
||||
Tid string
|
||||
Character bson.M
|
||||
}
|
||||
if err := gocommon.MakeDecoder(r).Decode(&doc); err != nil {
|
||||
logger.Println("AcceptPartyInvitation failed. DecodeGob returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
gid := doc.Gid
|
||||
mid := doc.Mid
|
||||
member := doc.Character
|
||||
|
||||
cnt, err := gp.rh.Del(context.Background(), "inv."+mid.Hex()).Result()
|
||||
if err != nil {
|
||||
logger.Println("AcceptPartyInvitation failed. gp.rh.Del returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if cnt == 0 {
|
||||
// 만료됨
|
||||
w.WriteHeader(http.StatusGatewayTimeout)
|
||||
return
|
||||
}
|
||||
|
||||
pids, err := gp.rh.JSONGetString(mid.Hex(), "$.party.id")
|
||||
if err != nil {
|
||||
logger.Println("AcceptPartyInvitation failed. gp.rh.JSONGetString returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if len(pids) > 0 && len(pids[0]) > 0 {
|
||||
// 기존에 이미 파티에 들어가 있다.
|
||||
// 기존 파티에서는 탈퇴
|
||||
oldgid, _ := primitive.ObjectIDFromHex(pids[0])
|
||||
oldgd := &partyDoc{
|
||||
id: oldgid,
|
||||
rh: gp.rh,
|
||||
}
|
||||
|
||||
// gid에는 제거 메시지 보냄
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + oldgd.strid(),
|
||||
Body: bson.M{
|
||||
oldgd.tid(mid): bson.M{},
|
||||
},
|
||||
Tag: []string{"MemberDocFull"},
|
||||
})
|
||||
|
||||
gp.leaveRoom(oldgid, mid)
|
||||
}
|
||||
|
||||
gd := &partyDoc{
|
||||
id: gid,
|
||||
rh: gp.rh,
|
||||
}
|
||||
|
||||
memberDoc, err := gd.addMember(mid, member, "XX")
|
||||
if err == nil {
|
||||
// 기존 멤버에게 새 멤버를 알림
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + gid.Hex(),
|
||||
Body: map[string]any{
|
||||
gd.tid(mid): memberDoc,
|
||||
},
|
||||
Tag: []string{"MemberDocFull"},
|
||||
})
|
||||
|
||||
gp.enterRoom(gid, mid)
|
||||
|
||||
// 현재 내 파티를 기록
|
||||
gp.rh.JSONSet(mid.Hex(), "$.party", bson.M{"id": gid.Hex()})
|
||||
|
||||
// 새 멤버에 그룹 전체를 알림
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: mid.Hex(),
|
||||
Body: gd.loadFull(),
|
||||
Tag: []string{"GroupDocFull"},
|
||||
})
|
||||
} else {
|
||||
logger.Println("AcceptPartyInvitation failed. group.AcceptPartyInvitation returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (gp *groupParty) QueryPartyMemberState(w http.ResponseWriter, r *http.Request) {
|
||||
var mid primitive.ObjectID
|
||||
if err := gocommon.MakeDecoder(r).Decode(&mid); err != nil {
|
||||
logger.Println("DenyPartyInvitation failed. DecodeGob returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if cnt, _ := gp.rh.Exists(gp.rh.Context(), mid.Hex()).Result(); cnt == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
states, _ := gp.rh.JSONGetString(mid.Hex(), "$.party.state")
|
||||
if len(states) > 0 && len(states[0]) > 0 {
|
||||
gocommon.MakeEncoder(w, r).Encode(states[0])
|
||||
} else {
|
||||
gocommon.MakeEncoder(w, r).Encode("connected")
|
||||
}
|
||||
}
|
||||
|
||||
func (gp *groupParty) updateMemberDocument(gid groupID, mid accountID, doc bson.M) error {
|
||||
gd := &partyDoc{
|
||||
id: gid,
|
||||
rh: gp.rh,
|
||||
}
|
||||
prefixPath := fmt.Sprintf("$._members.%s._body.", gd.tid(mid))
|
||||
|
||||
err := gp.rh.JSONMSetRel(gd.strid(), prefixPath, doc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if newstate, ok := doc["_state"]; ok {
|
||||
gp.rh.JSONSet(mid.Hex(), "$.party.state", newstate)
|
||||
}
|
||||
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + gid.Hex(),
|
||||
Body: map[string]any{
|
||||
gd.tid(mid): doc,
|
||||
},
|
||||
Tag: []string{"MemberDocFragment"},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gp *groupParty) updatePartyDocument(gid groupID, frag bson.M) error {
|
||||
gd := partyDoc{
|
||||
id: gid,
|
||||
rh: gp.rh,
|
||||
}
|
||||
if err := gp.rh.JSONMSetRel(gd.strid(), "$.", frag); err != nil {
|
||||
return err
|
||||
}
|
||||
// 업데이트 알림
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + gid.Hex(),
|
||||
Body: frag,
|
||||
Tag: []string{"GroupDocFragment"},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gp *groupParty) UpdatePartyDocument(w http.ResponseWriter, r *http.Request) {
|
||||
var data struct {
|
||||
Gid primitive.ObjectID
|
||||
Doc bson.M
|
||||
}
|
||||
|
||||
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
||||
logger.Println("UpdatePartyDocument failed. DecodeGob returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
gid := data.Gid
|
||||
frag := data.Doc
|
||||
|
||||
if err := gp.updatePartyDocument(gid, frag); err != nil {
|
||||
logger.Println("UpdatePartyDocument failed. group.UpdatePartyDocument returns err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (gp *groupParty) QueryPartyMembers(w http.ResponseWriter, r *http.Request) {
|
||||
var gid primitive.ObjectID
|
||||
if err := gocommon.MakeDecoder(r).Decode(&gid); err != nil {
|
||||
logger.Println("QueryPartyMembers failed. DecodeGob returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
gd := partyDoc{
|
||||
id: gid,
|
||||
rh: gp.rh,
|
||||
}
|
||||
|
||||
members, err := gd.getMembers()
|
||||
if err != nil {
|
||||
logger.Println("QueryPartyMembers failed. group.QueryPartyMembers returns err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := gocommon.MakeEncoder(w, r).Encode(members); err != nil {
|
||||
logger.Println("QueryPartyMembers failed. writeBsonDoc return err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (gp *groupParty) createGroup(newid groupID, charge accountID, chargeDoc bson.M, initialGroupDoc bson.M) (*partyDoc, error) {
|
||||
tid := makeTid(newid, charge)
|
||||
|
||||
gd := &partyDoc{
|
||||
Members: map[string]any{
|
||||
tid: &memberDoc{
|
||||
Body: chargeDoc,
|
||||
Invite: false,
|
||||
InviteExpire: 0,
|
||||
},
|
||||
},
|
||||
InCharge: tid,
|
||||
Gid: newid.Hex(),
|
||||
|
||||
rh: gp.rh,
|
||||
id: newid,
|
||||
}
|
||||
|
||||
var err error
|
||||
if initialGroupDoc != nil {
|
||||
initialGroupDoc["_members"] = gd.Members
|
||||
initialGroupDoc["_incharge"] = gd.InCharge
|
||||
initialGroupDoc["_gid"] = gd.Gid
|
||||
|
||||
_, err = gp.rh.JSONSet(gd.strid(), "$", initialGroupDoc, gocommon.RedisonSetOptionNX)
|
||||
} else {
|
||||
_, err = gp.rh.JSONSet(gd.strid(), "$", gd, gocommon.RedisonSetOptionNX)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gd, nil
|
||||
}
|
||||
|
||||
func (gp *groupParty) find(id groupID) (*partyDoc, error) {
|
||||
if id.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, err := gp.rh.JSONObjLen(id.Hex(), "$")
|
||||
if err == redis.Nil {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &partyDoc{
|
||||
rh: gp.rh,
|
||||
id: id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (gp *groupParty) ClientDisconnected(msg string, callby *wshandler.Sender) {
|
||||
gids, _ := gp.rh.JSONGetString(callby.Accid.Hex(), "$.party.id")
|
||||
|
||||
if len(gids) > 0 && len(gids[0]) > 0 {
|
||||
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
|
||||
gidstr := gids[0]
|
||||
gid, _ := primitive.ObjectIDFromHex(gidstr)
|
||||
|
||||
// 나를 먼저 룸에서 빼야 나한테 메시지가 안감
|
||||
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,
|
||||
Body: bson.M{
|
||||
makeTid(gid, callby.Accid): bson.M{},
|
||||
},
|
||||
Tag: []string{"MemberDocFull"},
|
||||
})
|
||||
gd.removeMember(callby.Accid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gp *groupParty) UpdatePartyMemberDocumentDirect(ctx wshandler.ApiCallContext) {
|
||||
gidobj, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
|
||||
doc := ctx.Arguments[1].(map[string]any)
|
||||
|
||||
gp.updateMemberDocument(gidobj, ctx.CallBy.Accid, doc)
|
||||
}
|
||||
|
||||
func (gp *groupParty) UpdatePartyDocumentDirect(ctx wshandler.ApiCallContext) {
|
||||
gidobj, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
|
||||
doc := ctx.Arguments[1].(map[string]any)
|
||||
|
||||
gp.updatePartyDocument(gidobj, doc)
|
||||
}
|
||||
|
||||
func (gp *groupParty) LeaveParty(ctx wshandler.ApiCallContext) {
|
||||
gids, _ := gp.rh.JSONGetString(ctx.CallBy.Accid.Hex(), "$.party.id")
|
||||
|
||||
if len(gids) == 0 || len(gids[0]) == 0 {
|
||||
return
|
||||
}
|
||||
// mid한테는 빈 GroupDocFull을 보낸다. 그러면 지워짐
|
||||
gidstr := gids[0]
|
||||
gid, _ := primitive.ObjectIDFromHex(gidstr)
|
||||
mid := ctx.CallBy.Accid
|
||||
tid := ctx.Arguments[0].(string)
|
||||
|
||||
gd := partyDoc{
|
||||
id: gid,
|
||||
rh: gp.rh,
|
||||
}
|
||||
|
||||
var err error
|
||||
if len(tid) > 0 {
|
||||
if tid != gd.tid(mid) {
|
||||
// mid가 incharge여야 한다. 그래야 tid를 쫓아낼 수 있음
|
||||
incharge, err := gp.rh.JSONGet(gd.strid(), "$._incharge")
|
||||
if err != nil {
|
||||
logger.Println("LeaveParty failed. gp.rh.JSONGet returns err :", err)
|
||||
return
|
||||
}
|
||||
if !strings.Contains(incharge.(string), gd.tid(mid)) {
|
||||
// incharge가 아니네?
|
||||
logger.Println("LeaveParty failed. mid is not incharge")
|
||||
return
|
||||
}
|
||||
mid = midFromTid(gd.id, tid)
|
||||
}
|
||||
err = gd.removeMemberByTid(tid)
|
||||
} else {
|
||||
err = gd.removeMember(mid)
|
||||
// 내가 나갔다
|
||||
gp.rh.JSONDel(mid.Hex(), "$.party.id")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Println("LeaveParty failed. gd.removeMember returns err :", err)
|
||||
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(),
|
||||
Body: bson.M{"gid": gid},
|
||||
Tag: []string{"GroupDocFull", gid.Hex()},
|
||||
})
|
||||
|
||||
// gid에는 제거 메시지 보냄
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + gd.strid(),
|
||||
Body: bson.M{
|
||||
tid: bson.M{},
|
||||
},
|
||||
Tag: []string{"MemberDocFull"},
|
||||
})
|
||||
gd.removeMember(mid)
|
||||
}
|
||||
|
||||
gp.leaveRoom(gid, mid)
|
||||
}
|
||||
|
||||
func (gp *groupParty) DenyPartyInvitation(ctx wshandler.ApiCallContext) {
|
||||
gid, _ := primitive.ObjectIDFromHex(ctx.Arguments[0].(string))
|
||||
mid := ctx.CallBy.Accid
|
||||
|
||||
gp.rh.Del(context.Background(), "inv."+mid.Hex()).Result()
|
||||
gd := partyDoc{
|
||||
id: gid,
|
||||
rh: gp.rh,
|
||||
}
|
||||
gd.removeMember(mid)
|
||||
|
||||
gp.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + gd.strid(),
|
||||
Body: bson.M{
|
||||
gd.tid(mid): bson.M{},
|
||||
},
|
||||
Tag: []string{"MemberDocFull"},
|
||||
})
|
||||
}
|
||||
@ -1,97 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type connection struct {
|
||||
locker sync.Mutex
|
||||
alias string
|
||||
tags []string
|
||||
onClose map[string]func()
|
||||
}
|
||||
|
||||
func (rc *connection) addTag(name, val string) {
|
||||
rc.locker.Lock()
|
||||
defer rc.locker.Unlock()
|
||||
|
||||
prefix := name + "="
|
||||
for i, tag := range rc.tags {
|
||||
if strings.HasPrefix(tag, prefix) {
|
||||
rc.tags[i] = prefix + val
|
||||
return
|
||||
}
|
||||
}
|
||||
rc.tags = append(rc.tags, prefix+val)
|
||||
}
|
||||
|
||||
func (rc *connection) removeTag(name string, val string) {
|
||||
rc.locker.Lock()
|
||||
defer rc.locker.Unlock()
|
||||
|
||||
whole := fmt.Sprintf("%s=%s", name, val)
|
||||
for i, tag := range rc.tags {
|
||||
if tag == whole {
|
||||
if i == 0 && len(rc.tags) == 1 {
|
||||
rc.tags = nil
|
||||
} else {
|
||||
lastidx := len(rc.tags) - 1
|
||||
if i < lastidx {
|
||||
rc.tags[i] = rc.tags[lastidx]
|
||||
}
|
||||
rc.tags = rc.tags[:lastidx]
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *connection) registOnCloseFunc(name string, f func()) {
|
||||
rc.locker.Lock()
|
||||
defer rc.locker.Unlock()
|
||||
|
||||
if rc.onClose == nil {
|
||||
f()
|
||||
return
|
||||
}
|
||||
rc.onClose[name] = f
|
||||
}
|
||||
|
||||
func (rc *connection) hasOnCloseFunc(name string) bool {
|
||||
rc.locker.Lock()
|
||||
defer rc.locker.Unlock()
|
||||
|
||||
if rc.onClose == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := rc.onClose[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (rc *connection) unregistOnCloseFunc(name string) (out func()) {
|
||||
rc.locker.Lock()
|
||||
defer rc.locker.Unlock()
|
||||
|
||||
if rc.onClose == nil {
|
||||
return
|
||||
}
|
||||
out = rc.onClose[name]
|
||||
delete(rc.onClose, name)
|
||||
return
|
||||
}
|
||||
|
||||
func (rc *connection) cleanup() {
|
||||
rc.locker.Lock()
|
||||
defer rc.locker.Unlock()
|
||||
|
||||
cp := rc.onClose
|
||||
rc.onClose = nil
|
||||
go func() {
|
||||
for _, f := range cp {
|
||||
f()
|
||||
}
|
||||
}()
|
||||
}
|
||||
331
core/tavern.go
331
core/tavern.go
@ -6,130 +6,41 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/gorilla/websocket"
|
||||
"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"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/bsonrw"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMaxMemory = 32 << 10 // 32 KB
|
||||
)
|
||||
|
||||
func writeBsonArr(w io.Writer, src []bson.M) error {
|
||||
return writeBsonDoc(w, bson.M{
|
||||
"r": src,
|
||||
})
|
||||
}
|
||||
|
||||
func onlineGroupQueryKey(prefix string) string {
|
||||
return prefix + "_olg"
|
||||
}
|
||||
|
||||
func writeBsonDoc[T any](w io.Writer, src T) error {
|
||||
rw, err := bsonrw.NewBSONValueWriter(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enc, err := bson.NewEncoder(rw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return enc.Encode(src)
|
||||
}
|
||||
|
||||
func readBsonDoc(r io.Reader, src any) error {
|
||||
body, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
decoder, err := bson.NewDecoder(bsonrw.NewBSONDocumentReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = decoder.Decode(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
var devflag = flagx.Bool("dev", false, "")
|
||||
|
||||
type TavernConfig struct {
|
||||
gocommon.RegionStorageConfig `json:",inline"`
|
||||
|
||||
GroupTypes map[string]*groupConfig `json:"tavern_group_types"`
|
||||
MaingateApiToken string `json:"maingate_api_token"`
|
||||
RedisURL string `json:"tavern_redis_url"`
|
||||
macAddr string
|
||||
session.SessionConfig `json:",inline"`
|
||||
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
|
||||
}
|
||||
|
||||
var config TavernConfig
|
||||
|
||||
type connectionMap struct {
|
||||
sync.Mutex
|
||||
conns map[primitive.ObjectID]*connection
|
||||
}
|
||||
|
||||
func (cm *connectionMap) add(accid accountID, alias string) {
|
||||
cm.Lock()
|
||||
defer cm.Unlock()
|
||||
|
||||
old := cm.conns[accid]
|
||||
if old != nil {
|
||||
old.cleanup()
|
||||
}
|
||||
cm.conns[accid] = &connection{
|
||||
alias: alias,
|
||||
onClose: make(map[string]func()),
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *connectionMap) remove(accid accountID) {
|
||||
cm.Lock()
|
||||
defer cm.Unlock()
|
||||
|
||||
old := cm.conns[accid]
|
||||
if old != nil {
|
||||
delete(cm.conns, accid)
|
||||
old.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *connectionMap) get(accid accountID) *connection {
|
||||
cm.Lock()
|
||||
defer cm.Unlock()
|
||||
|
||||
return cm.conns[accid]
|
||||
}
|
||||
|
||||
type Tavern struct {
|
||||
subTaverns []*subTavern
|
||||
wsh *wshandler.WebsocketHandler
|
||||
}
|
||||
|
||||
type subTavern struct {
|
||||
mongoClient gocommon.MongoClient
|
||||
wsh *wshandler.WebsocketHandler
|
||||
region string
|
||||
groups map[string]group
|
||||
methods map[string]reflect.Method
|
||||
cm connectionMap
|
||||
wsh *wshandler.WebsocketHandler
|
||||
mongoClient gocommon.MongoClient
|
||||
redison *gocommon.RedisonHandler
|
||||
httpApiBorker gocommon.HttpApiBroker
|
||||
}
|
||||
|
||||
func getMacAddr() (string, error) {
|
||||
@ -149,19 +60,14 @@ func getMacAddr() (string, error) {
|
||||
}
|
||||
|
||||
// New :
|
||||
func New(context context.Context, wsh *wshandler.WebsocketHandler, inconfig *TavernConfig) (*Tavern, error) {
|
||||
if inconfig == nil {
|
||||
var loaded TavernConfig
|
||||
if err := gocommon.LoadConfig(&loaded); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inconfig = &loaded
|
||||
func New(context context.Context, wsh *wshandler.WebsocketHandler) (*Tavern, error) {
|
||||
if err := gocommon.LoadConfig(&config); err != nil {
|
||||
return nil, logger.ErrorWithCallStack(err)
|
||||
}
|
||||
|
||||
config = *inconfig
|
||||
macaddr, err := getMacAddr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, logger.ErrorWithCallStack(err)
|
||||
}
|
||||
config.macAddr = macaddr
|
||||
tv := &Tavern{
|
||||
@ -170,111 +76,110 @@ func New(context context.Context, wsh *wshandler.WebsocketHandler, inconfig *Tav
|
||||
|
||||
if err = tv.prepare(context); err != nil {
|
||||
logger.Println("tavern prepare() failed :", err)
|
||||
return nil, err
|
||||
return nil, logger.ErrorWithCallStack(err)
|
||||
}
|
||||
|
||||
return tv, nil
|
||||
}
|
||||
|
||||
var ccu = metric.MetricWriterNil
|
||||
|
||||
func (tv *Tavern) Cleanup() {
|
||||
for _, st := range tv.subTaverns {
|
||||
st.mongoClient.Close()
|
||||
}
|
||||
ccu.Set(0)
|
||||
tv.mongoClient.Close()
|
||||
}
|
||||
|
||||
// func (tv *Tavern) SocketMessageReceived(accid primitive.ObjectID, alias string, messageType wshandler.WebSocketMessageType, body io.Reader) {
|
||||
// switch messageType {
|
||||
// case wshandler.Connected:
|
||||
|
||||
// case wshandler.Disconnected:
|
||||
// }
|
||||
// // gidtype := msg.Conn.GetTag("gid")
|
||||
// // if len(gidtype) > 0 {
|
||||
// // tokens := strings.SplitN(gidtype, "@", 2)
|
||||
// // gidobj, _ := primitive.ObjectIDFromHex(tokens[0])
|
||||
// // gtype := tokens[1]
|
||||
// // group := sub.groups[gtype]
|
||||
// // if group != nil {
|
||||
// // group.PauseMember(gidobj, msg.Alias, msg.Conn)
|
||||
// // }
|
||||
// }
|
||||
|
||||
func (tv *Tavern) prepare(ctx context.Context) error {
|
||||
for region := range config.RegionStorage {
|
||||
var dbconn gocommon.MongoClient
|
||||
var err error
|
||||
var groupinstance group
|
||||
|
||||
var tmp *subTavern
|
||||
methods := make(map[string]reflect.Method)
|
||||
tp := reflect.TypeOf(tmp)
|
||||
for i := 0; i < tp.NumMethod(); i++ {
|
||||
method := tp.Method(i)
|
||||
methods[method.Name] = method
|
||||
}
|
||||
|
||||
sub := &subTavern{
|
||||
wsh: tv.wsh,
|
||||
mongoClient: dbconn,
|
||||
region: region,
|
||||
methods: methods,
|
||||
cm: connectionMap{
|
||||
conns: make(map[primitive.ObjectID]*connection),
|
||||
},
|
||||
}
|
||||
|
||||
groups := make(map[string]group)
|
||||
for typename, cfg := range config.GroupTypes {
|
||||
cfg.Name = typename
|
||||
if cfg.Transient {
|
||||
groupinstance, err = cfg.prepareInMemory(ctx, typename, sub)
|
||||
//} else {
|
||||
// TODO : db
|
||||
// if !dbconn.Connected() {
|
||||
// dbconn, err = gocommon.NewMongoClient(ctx, url.Mongo, region)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// groupinstance, err = cfg.preparePersistent(ctx, region, dbconn, tv.wsh)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
groups[typename] = groupinstance
|
||||
}
|
||||
sub.groups = groups
|
||||
|
||||
tv.subTaverns = append(tv.subTaverns, sub)
|
||||
redisClient, err := gocommon.NewRedisClient(config.RedisURL)
|
||||
if err != nil {
|
||||
return logger.ErrorWithCallStack(err)
|
||||
}
|
||||
|
||||
tv.redison = gocommon.NewRedisonHandler(redisClient.Context(), redisClient)
|
||||
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(tv, "tv"))
|
||||
|
||||
if cfg, ok := config.Group["chat"]; ok {
|
||||
chat := new(groupChat)
|
||||
if err := chat.Initialize(tv, cfg); err != nil {
|
||||
return logger.ErrorWithCallStack(err)
|
||||
}
|
||||
tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(chat, "chat"))
|
||||
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(chat, "chat"))
|
||||
}
|
||||
|
||||
if cfg, ok := config.Group["party"]; ok {
|
||||
party := new(groupParty)
|
||||
if err := party.Initialize(tv, cfg); err != nil {
|
||||
return logger.ErrorWithCallStack(err)
|
||||
}
|
||||
tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(party, "party"))
|
||||
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(party, "party"))
|
||||
}
|
||||
|
||||
instant := new(groupInstant)
|
||||
if err := instant.Initialize(tv); err != nil {
|
||||
return logger.ErrorWithCallStack(err)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (tv *Tavern) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
|
||||
for _, sub := range tv.subTaverns {
|
||||
tv.wsh.RegisterReceiver(sub.region, sub.clientMessageReceived)
|
||||
var pattern string
|
||||
if sub.region == "default" {
|
||||
pattern = gocommon.MakeHttpHandlerPattern(prefix, "api")
|
||||
} else {
|
||||
pattern = gocommon.MakeHttpHandlerPattern(prefix, sub.region, "api")
|
||||
}
|
||||
serveMux.HandleFunc(pattern, sub.api)
|
||||
}
|
||||
// tv.wsh.RegisterReceiver(tv)
|
||||
pattern := gocommon.MakeHttpHandlerPattern(prefix, "api")
|
||||
serveMux.HandleFunc(pattern, tv.api)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sub *subTavern) clientMessageReceived(sender *wshandler.Sender, messageType wshandler.WebSocketMessageType, body io.Reader) {
|
||||
if messageType == wshandler.Connected {
|
||||
sub.cm.add(sender.Accid, sender.Alias)
|
||||
} else if messageType == wshandler.Disconnected {
|
||||
sub.cm.remove(sender.Accid)
|
||||
func (tv *Tavern) EnterChannel(ctx wshandler.ApiCallContext) {
|
||||
tv.wsh.EnterRoom(ctx.Arguments[0].(string), ctx.CallBy.Accid)
|
||||
}
|
||||
|
||||
func (tv *Tavern) LeaveChannel(ctx wshandler.ApiCallContext) {
|
||||
tv.wsh.LeaveRoom(ctx.Arguments[0].(string), ctx.CallBy.Accid)
|
||||
}
|
||||
|
||||
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 {
|
||||
logger.Println("OnClientMessageReceived HSet error :", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sub *subTavern) api(w http.ResponseWriter, r *http.Request) {
|
||||
func (tv *Tavern) ClientDisconnected(msg string, callby *wshandler.Sender) {
|
||||
ccu.Add(-1)
|
||||
tv.redison.Del(tv.redison.Context(), callby.Accid.Hex()).Result()
|
||||
}
|
||||
|
||||
func (tv *Tavern) OnRoomCreated(name string) {
|
||||
cnt, err := tv.redison.IncrBy(tv.redison.Context(), "_ref_"+name, 1).Result()
|
||||
if err != nil && !errors.Is(err, redis.Nil) {
|
||||
logger.Println("OnRoomCreated JSONSet failed :", err)
|
||||
return
|
||||
}
|
||||
|
||||
if cnt == 1 {
|
||||
tv.redison.JSONSet(name, "$", map[string]any{}, gocommon.RedisonSetOptionNX)
|
||||
}
|
||||
}
|
||||
|
||||
func (tv *Tavern) OnRoomDestroyed(name string) {
|
||||
cnt, err := tv.redison.IncrBy(tv.redison.Context(), "_ref_"+name, -1).Result()
|
||||
if err != nil {
|
||||
logger.Println("OnRoomDestroyed JSONNumIncrBy failed :", err)
|
||||
} else if cnt == 0 {
|
||||
tv.redison.Del(tv.redison.Context(), "_ref_"+name)
|
||||
tv.redison.JSONDel(name, "$")
|
||||
}
|
||||
}
|
||||
|
||||
func (tv *Tavern) api(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
@ -293,29 +198,5 @@ func (sub *subTavern) api(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
operation := r.URL.Query().Get("operation")
|
||||
if len(operation) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
method, ok := sub.methods[operation]
|
||||
if !ok {
|
||||
// 없는 operation
|
||||
logger.Println("fail to call api. operation is not valid :", operation)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if r.PostForm == nil {
|
||||
r.ParseMultipartForm(defaultMaxMemory)
|
||||
}
|
||||
|
||||
args := []reflect.Value{
|
||||
reflect.ValueOf(sub),
|
||||
reflect.ValueOf(w),
|
||||
reflect.ValueOf(r),
|
||||
}
|
||||
|
||||
method.Func.Call(args)
|
||||
tv.httpApiBorker.Call(w, r)
|
||||
}
|
||||
|
||||
113
core/tavern_test.go
Normal file
113
core/tavern_test.go
Normal file
@ -0,0 +1,113 @@
|
||||
// warroom project main.go
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
)
|
||||
|
||||
func TestPubSub(t *testing.T) {
|
||||
opt0, _ := redis.ParseURL("redis://192.168.8.94:6380/0")
|
||||
opt1, _ := redis.ParseURL("redis://192.168.8.94:6380/1")
|
||||
|
||||
rc0 := redis.NewClient(opt0)
|
||||
rc1 := redis.NewClient(opt1)
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
rc1.Publish(context.Background(), "__testchan", "real???")
|
||||
fmt.Println("published")
|
||||
}()
|
||||
|
||||
pubsub := rc0.Subscribe(context.Background(), "__testchan")
|
||||
msg, err := pubsub.ReceiveMessage(context.Background())
|
||||
fmt.Println(msg.Payload, err)
|
||||
}
|
||||
func makeHash(chanName string, index uint32) string {
|
||||
for len(chanName) < 12 {
|
||||
chanName += chanName
|
||||
}
|
||||
left := chanName[:6]
|
||||
right := chanName[len(chanName)-6:]
|
||||
base := []byte(left + right)
|
||||
for i := 0; i < 12; i++ {
|
||||
base[i] += base[12-i-1]
|
||||
}
|
||||
|
||||
bs := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bs, index)
|
||||
for i, c := range bs {
|
||||
base[i] ^= c
|
||||
}
|
||||
var gid primitive.ObjectID
|
||||
copy(gid[:], base)
|
||||
|
||||
return gid.Hex()
|
||||
}
|
||||
|
||||
func TestNameHash(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
makeHash("Urud", uint32(i))
|
||||
fmt.Printf("Urud:%d - %s\n", i, makeHash("Urud", uint32(i)))
|
||||
makeHash("Sheldon", uint32(i))
|
||||
fmt.Printf("Sheldon:%d - %s\n", i, makeHash("Sheldon", uint32(i)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReJSON(t *testing.T) {
|
||||
rc := redis.NewClient(&redis.Options{Addr: "192.168.8.94:6380"})
|
||||
rh := gocommon.NewRedisonHandler(context.Background(), rc)
|
||||
|
||||
success, err := rc.HSetNX(context.Background(), "setnxtest", "cap", 100).Result()
|
||||
fmt.Println(success, err)
|
||||
success, err = rc.HSetNX(context.Background(), "setnxtest", "cap", 100).Result()
|
||||
fmt.Println(success, err)
|
||||
|
||||
testDoc := map[string]any{
|
||||
"members": map[string]any{
|
||||
"mid2": map[string]any{
|
||||
"key": "val",
|
||||
"exp": 20202020,
|
||||
},
|
||||
"mid1": map[string]any{
|
||||
"key": "val",
|
||||
"exp": 10101010,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
gd := partyDoc{
|
||||
id: primitive.NewObjectID(),
|
||||
}
|
||||
|
||||
midin := primitive.NewObjectID()
|
||||
tid := gd.tid(midin)
|
||||
midout := gd.mid(tid)
|
||||
logger.Println(midin, tid, midout)
|
||||
|
||||
logger.Println(rh.JSONSet("jsontest", "$", testDoc))
|
||||
logger.Println(rh.JSONGet("jsontest", "$"))
|
||||
logger.Println(rh.JSONResp("jsontest", "$.members"))
|
||||
logger.Println(rh.JSONGetString("jsontest", "$.members..key"))
|
||||
logger.Println(rh.JSONGetInt64("jsontest", "$.members..exp"))
|
||||
logger.Println(rh.JSONObjKeys("jsontest", "$.members"))
|
||||
|
||||
err = rh.JSONMSet("jsontest", map[string]any{
|
||||
"$.members.mid1.key": "newval",
|
||||
"$.members.mid2.key": "newval",
|
||||
})
|
||||
logger.Println(err)
|
||||
|
||||
logger.Println(rh.JSONGet("jsontest", "$"))
|
||||
logger.Println(rh.JSONMDel("jsontest", []string{"$.members.mid1", "$.members.mid2"}))
|
||||
logger.Println(rh.JSONGet("jsontest", "$"))
|
||||
logger.Println(rh.JSONObjLen("jsontest", "$.members"))
|
||||
}
|
||||
13
go.mod
13
go.mod
@ -1,27 +1,30 @@
|
||||
module repositories.action2quare.com/ayo/tavern
|
||||
|
||||
go 1.19
|
||||
go 1.22.1
|
||||
|
||||
require (
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
go.mongodb.org/mongo-driver v1.11.7
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711062613-74829b93ac1b
|
||||
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.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/crypto v0.18.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
||||
|
||||
57
go.sum
57
go.sum
@ -1,40 +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 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/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=
|
||||
@ -58,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=
|
||||
@ -79,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=
|
||||
@ -87,32 +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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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-20230710053024-a842845685ee h1:Aau1j/b9wI4nyvrM7m1Q+2xkcW1Qo7i3q+QBD4Umnzg=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230710053024-a842845685ee/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711003621-3bb985d0b617 h1:91mBIGIyxzcnvOaIdegUuV+i9xs8YTSRcmyRaIytzx8=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711003621-3bb985d0b617/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711005604-a42eb2888e97 h1:ARzXt3HBmiAUDyACfNm5Kvz1JMTn7+ryE03kB8x/km0=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711005604-a42eb2888e97/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711033135-d7b26608df94 h1:VrNj5gBFFN9/roWCxyBCZ2gu5k58eremNHQvQNPrfrU=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711033135-d7b26608df94/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711035757-9fd0dd00cb7d h1:RdxKmMc7kHrTk+SvTYse2IGxmdDhbEDeM0fKAUW+G+w=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711035757-9fd0dd00cb7d/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711053010-4acb81a20d9c h1:SktFqjnc/UOMjJrq/brSw5lQjW1IA+KkB5YgeovusmQ=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711053010-4acb81a20d9c/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711062613-74829b93ac1b h1:04rlgT+zeKSpekyleb8Mfi8kENIoka5DYJLuk65wqxc=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230711062613-74829b93ac1b/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
||||
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=
|
||||
|
||||
25
main.go
25
main.go
@ -9,8 +9,9 @@ import (
|
||||
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
||||
"repositories.action2quare.com/ayo/tavern/core"
|
||||
|
||||
common "repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
"repositories.action2quare.com/ayo/gocommon/session"
|
||||
)
|
||||
|
||||
var prefix = flagx.String("prefix", "", "")
|
||||
@ -20,28 +21,36 @@ func main() {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var config core.TavernConfig
|
||||
if err := common.LoadConfig(&config); err != nil {
|
||||
if err := gocommon.LoadConfig(&config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
authcache, err := common.NewAuthCollectionGlobal(ctx, config.MaingateApiToken)
|
||||
consumer, err := session.NewConsumerWithConfig(ctx, config.SessionConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
wsh := wshandler.NewWebsocketHandler(authcache)
|
||||
if tv, err := core.New(ctx, wsh, &config); err != nil {
|
||||
wsh, err := wshandler.NewWebsocketHandler(consumer, config.RedisURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if tv, err := core.New(ctx, wsh); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
serveMux := http.NewServeMux()
|
||||
wsh.RegisterHandlers(serveMux, *prefix)
|
||||
tv.RegisterHandlers(ctx, serveMux, *prefix)
|
||||
server := common.NewHTTPServer(serveMux)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user