wshandler와 분리 중

This commit is contained in:
2023-07-06 00:53:53 +09:00
parent 3c14e7e4c5
commit 8d0f21077d
7 changed files with 108 additions and 1422 deletions

View File

@ -132,147 +132,6 @@ func (sub *subTavern) JoinGroup(w http.ResponseWriter, r *http.Request) {
} }
} }
func (sub *subTavern) EnterCandidateChannel(w http.ResponseWriter, r *http.Request) {
typename, _ := common.ReadStringFormValue(r.Form, "type")
if _, ok := sub.groups[typename]; !ok {
logger.Println("EnterCandidateChannel failed. group type is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
midobj, ok := common.ReadObjectIDFormValue(r.Form, "mid")
if !ok {
logger.Println("EnterCandidateChannel failed. mid is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
gidobj, ok := common.ReadObjectIDFormValue(r.Form, "gid")
if !ok {
logger.Println("EnterCandidateChannel failed. gid is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
// candidate channel은 big endian 최상위 비트가 1
gidobj[0] |= 0x80
if conn := sub.wsh.Conn(sub.region, midobj); conn != nil {
richConnOuter{wsh: sub.wsh, rc: conn}.JoinTag(sub.region, gidobj, midobj, typename)
} else {
sub.wshRpc.caller.One(midobj).JoinTag(sub.region, gidobj, midobj, typename)
}
}
func (sub *subTavern) LeaveCandidateChannel(w http.ResponseWriter, r *http.Request) {
typename, _ := common.ReadStringFormValue(r.Form, "type")
if _, ok := sub.groups[typename]; !ok {
logger.Println("EnterCandidateChannel failed. group type is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
midobj, ok := common.ReadObjectIDFormValue(r.Form, "mid")
if !ok {
logger.Println("EnterCandidateChannel failed. mid is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
gidobj, ok := common.ReadObjectIDFormValue(r.Form, "gid")
if !ok {
logger.Println("EnterCandidateChannel failed. gid is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
// candidate channel은 big endian 최상위 비트가 1
gidobj[0] |= 0x80
if conn := sub.wsh.Conn(sub.region, midobj); conn != nil {
richConnOuter{wsh: sub.wsh, rc: conn}.LeaveTag(sub.region, gidobj, midobj, typename)
} else {
sub.wshRpc.caller.One(midobj).LeaveTag(sub.region, gidobj, midobj, typename)
}
}
func (sub *subTavern) EnterGroupChannel(w http.ResponseWriter, r *http.Request) {
typename, _ := common.ReadStringFormValue(r.Form, "type")
group := sub.groups[typename]
if group == nil {
logger.Println("EnterGroupChannel failed. group type is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
midobj, ok := common.ReadObjectIDFormValue(r.Form, "mid")
if !ok {
logger.Println("EnterGroupChannel failed. mid is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
gidobj, ok := common.ReadObjectIDFormValue(r.Form, "gid")
if !ok {
logger.Println("EnterGroupChannel failed. gid is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
tid := group.FindTicketID(gidobj, midobj)
if tid.IsZero() {
logger.Println("EnterGroupChannel failed. tid is zero")
w.WriteHeader(http.StatusBadRequest)
return
}
if conn := sub.wsh.Conn(sub.region, midobj); conn != nil {
richConnOuter{wsh: sub.wsh, rc: conn}.JoinTag(sub.region, gidobj, tid, typename)
} else {
sub.wshRpc.caller.One(midobj).JoinTag(sub.region, gidobj, tid, typename)
}
writeBsonDoc(w, primitive.M{"_id": tid})
}
func (sub *subTavern) SetStateInGroup(w http.ResponseWriter, r *http.Request) {
gid, ok := common.ReadObjectIDFormValue(r.Form, "gid")
if !ok {
logger.Println("SetStateInGroup failed. tag is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
if !ok {
logger.Println("SetStateInGroup failed. mid form value is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
state, ok := common.ReadStringFormValue(r.Form, "state")
if !ok {
logger.Println("SetStateInGroup failed. state is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
typename, ok := common.ReadStringFormValue(r.Form, "type")
if !ok {
logger.Println("SetStateInGroup failed. type is missing :", r)
w.WriteHeader(http.StatusBadRequest)
return
}
var doc bson.M
if err := readBsonDoc(r.Body, &doc); err != nil {
logger.Error("SetStateInGroup failed. readBsonDoc err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
tid := doc["_id"].(primitive.ObjectID)
if conn := sub.wsh.Conn(sub.region, mid); conn != nil {
richConnOuter{wsh: sub.wsh, rc: conn}.SetStateInTag(sub.region, gid, tid, state, typename)
} else {
sub.wshRpc.caller.One(mid).SetStateInTag(sub.region, gid, tid, state, typename)
}
}
// Invite : 초대 // Invite : 초대
// - type : 초대 타입 (required) // - type : 초대 타입 (required)
// - from : 초대하는 자 (required) // - from : 초대하는 자 (required)
@ -490,119 +349,6 @@ func (sub *subTavern) QueryInvitations(w http.ResponseWriter, r *http.Request) {
} }
} }
func (sub *subTavern) TurnGroupOnline(w http.ResponseWriter, r *http.Request) {
// group을 online 상태로 만든다.
// 요청을 보내는 클라이언트의 conn이 끊이면 online에서 제거한다.
// online인 group을 가지고 뭘 할지는 게임이 알아서...
typename, _ := common.ReadStringFormValue(r.Form, "type")
group := sub.groups[typename]
if group == nil {
logger.Println("TurnGroupOnline failed. group type is missing :", r.Form)
w.WriteHeader(http.StatusBadRequest)
return
}
gid, ok := common.ReadObjectIDFormValue(r.Form, "_id")
if !ok {
logger.Println("TurnGroupOnline failed. group id '_id' form value is missing :", r.Form)
w.WriteHeader(http.StatusBadRequest)
return
}
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
if !ok {
logger.Println("TurnGroupOnline failed. mid form value is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
var filter bson.M
if err := readBsonDoc(r.Body, &filter); err != nil {
logger.Error("TurnGroupOnline failed. readBsonDoc return err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
exist, err := group.Exist(gid, filter)
if err != nil {
logger.Error("TurnGroupOnline failed. FindOne return err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if !exist {
logger.Println("TurnGroupOnline failed. filter not match", filter)
w.WriteHeader(http.StatusBadRequest)
return
}
score, ok := common.ReadFloatFormValue(r.Form, "score")
if !ok {
score = 100
}
if conn := sub.wsh.Conn(sub.region, mid); conn != nil {
err = richConnOuter{wsh: sub.wsh, rc: conn}.TurnGroupOnline(onlineGroupQueryKey(typename), gid, score)
} else {
err = sub.wshRpc.caller.One(mid).TurnGroupOnline(onlineGroupQueryKey(typename), gid, score)
}
if err != nil {
logger.Error("TurnGroupOnline failed. TurnGroupOnline err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
}
func (sub *subTavern) TurnGroupOffline(w http.ResponseWriter, r *http.Request) {
// group을 offline 상태로 만든다.
// 요청을 보내는 클라이언트의 conn이 끊이면 online에서 제거한다.
// online인 group을 가지고 뭘 할지는 게임이 알아서...
typename, _ := common.ReadStringFormValue(r.Form, "type")
group := sub.groups[typename]
if group == nil {
logger.Println("TurnGroupOffline failed. group type is missing :", r.Form)
w.WriteHeader(http.StatusBadRequest)
return
}
gid, ok := common.ReadObjectIDFormValue(r.Form, "_id")
if !ok {
logger.Println("TurnGroupOffline failed. group id '_id' form value is missing :", r.Form)
w.WriteHeader(http.StatusBadRequest)
return
}
mid, ok := common.ReadObjectIDFormValue(r.Form, "mid")
if !ok {
logger.Println("TurnGroupOffline failed. mid form value is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
// onlinename := onlineGroupQueryKey(typename)
// if onClose := conn.UnregistOnCloseFunc(onlinename); onClose != nil {
// onClose()
// } else {
// gid, ok := common.ReadStringFormValue(form, "_id")
// if ok {
// sub.redisSync.ZRem(context.Background(), onlinename, gid)
// }
// }
var err error
if conn := sub.wsh.Conn(sub.region, mid); conn != nil {
err = richConnOuter{wsh: sub.wsh, rc: conn}.TurnGroupOffline(onlineGroupQueryKey(typename), gid)
} else {
err = sub.wshRpc.caller.One(mid).TurnGroupOffline(onlineGroupQueryKey(typename), gid)
}
if err != nil {
logger.Error("TurnGroupOffline failed. TurnGroupOnline err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
}
func (sub *subTavern) QueryOnlineGroup(w http.ResponseWriter, r *http.Request) { func (sub *subTavern) QueryOnlineGroup(w http.ResponseWriter, r *http.Request) {
typename, _ := common.ReadStringFormValue(r.Form, "type") typename, _ := common.ReadStringFormValue(r.Form, "type")
group := sub.groups[typename] group := sub.groups[typename]
@ -690,11 +436,7 @@ func (sub *subTavern) QueryOnlineState(w http.ResponseWriter, r *http.Request) {
return return
} }
state, err := sub.wsh.GetState(mid) state := sub.wsh.GetState(sub.region, mid)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write([]byte(state)) w.Write([]byte(state))
} }
@ -706,13 +448,7 @@ func (sub *subTavern) IsOnline(w http.ResponseWriter, r *http.Request) {
return return
} }
ok, err := sub.wsh.IsOnline(mid) if state := sub.wsh.GetState(sub.region, mid); len(state) > 0 {
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if ok {
w.Write([]byte("true")) w.Write([]byte("true"))
} else { } else {
w.Write([]byte("false")) w.Write([]byte("false"))

View File

@ -588,10 +588,6 @@ func multicast(conns []*wshandler.Richconn, raw []byte) {
rconn.WriteBytes(raw) rconn.WriteBytes(raw)
} }
} }
func multicastTyped[T any](conns []*wshandler.Richconn, msg T) {
bt, _ := json.Marshal(makeTypeMessage(msg))
go multicast(conns, bt)
}
func broadcastTypedMessage[T any](gm *groupInMemory, gid groupID, msg T) { func broadcastTypedMessage[T any](gm *groupInMemory, gid groupID, msg T) {
if gd := gm.groups.find(gid); gd != nil { if gd := gm.groups.find(gid); gd != nil {
@ -1172,7 +1168,7 @@ func (cfg *groupConfig) prepareInMemory(ctx context.Context, region string, type
call := func() { call := func() {
method, ok := reflect.TypeOf(gm).MethodByName(fn) method, ok := reflect.TypeOf(gm).MethodByName(fn)
if !ok { if !ok {
logger.Printf("%s message decode failed :", targetbt, msg.Payload, err) logger.Println("message decode failed :", err, targetbt, msg.Payload)
} }
args := []reflect.Value{ args := []reflect.Value{

File diff suppressed because it is too large Load Diff

View File

@ -1,73 +1,97 @@
package core package core
import ( import (
"context" "fmt"
"strings"
"repositories.action2quare.com/ayo/gocommon/logger" "sync"
"repositories.action2quare.com/ayo/gocommon/wshandler"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/bson/primitive"
) )
type richConnOuter struct { type connection struct {
wsh *wshandler.WebsocketHandler locker sync.Mutex
rc *wshandler.Richconn alias string
tags []string
onClose map[string]func()
} }
func (sub richConnOuter) JoinTag(region string, tag primitive.ObjectID, tid primitive.ObjectID, hint string) error { func (rc *connection) addTag(name, val string) {
sub.wsh.JoinTag(region, tag, tid, sub.rc, hint) rc.locker.Lock()
defer rc.locker.Unlock()
wsh := sub.wsh prefix := name + "="
sub.rc.RegistOnCloseFunc(tag.Hex(), func() { for i, tag := range rc.tags {
wsh.LeaveTag(region, tag, tid) if strings.HasPrefix(tag, prefix) {
}) rc.tags[i] = prefix + val
return nil return
} }
func (sub richConnOuter) LeaveTag(region string, tag primitive.ObjectID, tid primitive.ObjectID, hint string) error {
sub.SetStateInTag(region, tag, tid, "", hint)
return sub.wsh.LeaveTag(region, tag, tid)
}
func (sub richConnOuter) SetStateInTag(region string, tag primitive.ObjectID, tid primitive.ObjectID, state string, hint string) error {
return sub.wsh.SetStateInTag(region, tag, tid, state, hint)
}
func (sub richConnOuter) TurnGroupOnline(key string, gid primitive.ObjectID, score float64) error {
gidhex := gid.Hex()
_, err := sub.wsh.RedisSync.ZAdd(context.Background(), key, &redis.Z{Score: score, Member: gidhex}).Result()
if err != nil {
logger.Error("TurnGroupOnline failed. redis.ZAdd return err :", err)
return err
} }
rc.tags = append(rc.tags, prefix+val)
sub.rc.RegistOnCloseFunc(key, func() {
sub.wsh.RedisSync.ZRem(context.Background(), key, gidhex)
})
return nil
} }
func (sub richConnOuter) TurnGroupOffline(key string, gid primitive.ObjectID) error { func (rc *connection) getTag(name string) string {
f := sub.rc.UnregistOnCloseFunc(key) rc.locker.Lock()
if f != nil { defer rc.locker.Unlock()
prefix := name + "="
for _, tag := range rc.tags {
if strings.HasPrefix(tag, prefix) {
return tag[len(prefix):]
}
}
return ""
}
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() f()
} else { return
sub.wsh.RedisSync.ZRem(context.Background(), key, gid.Hex())
} }
return nil rc.onClose[name] = f
} }
func (sub richConnOuter) SendMessage(doc []byte) error { func (rc *connection) hasOnCloseFunc(name string) bool {
return sub.rc.WriteBytes(doc) rc.locker.Lock()
defer rc.locker.Unlock()
if rc.onClose == nil {
return false
}
_, ok := rc.onClose[name]
return ok
} }
func (sub richConnOuter) SendMessageToTag(region string, tag primitive.ObjectID, msg []byte) error { func (rc *connection) unregistOnCloseFunc(name string) (out func()) {
sub.wsh.BroadcastRaw(region, tag, msg) rc.locker.Lock()
return nil defer rc.locker.Unlock()
}
func (sub richConnOuter) CloseOnPurpose() error { if rc.onClose == nil {
return sub.rc.Close() return
}
out = rc.onClose[name]
delete(rc.onClose, name)
return
} }

View File

@ -9,6 +9,7 @@ import (
"reflect" "reflect"
"strings" "strings"
"repositories.action2quare.com/ayo/gocommon"
common "repositories.action2quare.com/ayo/gocommon" common "repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/wshandler" "repositories.action2quare.com/ayo/gocommon/wshandler"
@ -71,41 +72,12 @@ func readBsonDoc(r io.Reader, src any) error {
return nil return nil
} }
type rpcCallDomain[T any] struct {
rpcCallChanName string
caller rpc.RpcCaller
callee rpc.RpcCallee[T]
methods map[string]reflect.Method
}
func createRpcCallDomain[CalleeType any](syncConn *redis.Client, creator func(*wshandler.Richconn) *CalleeType) rpcCallDomain[CalleeType] {
var tmp *CalleeType
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
}
rpcChanName := "conn_rpc_channel_" + tp.Name()
publishFunc := func(bt []byte) error {
_, err := syncConn.Publish(context.Background(), rpcChanName, bt).Result()
return err
}
return rpcCallDomain[CalleeType]{
rpcCallChanName: rpcChanName,
caller: rpc.NewRpcCaller(publishFunc),
callee: rpc.NewRpcCallee(creator),
methods: methods,
}
}
type TavernConfig struct { type TavernConfig struct {
common.RegionStorageConfig `json:",inline"` common.RegionStorageConfig `json:",inline"`
GroupTypes map[string]*groupConfig `json:"tavern_group_types"` GroupTypes map[string]*groupConfig `json:"tavern_group_types"`
MaingateApiToken string `json:"maingate_api_token"` MaingateApiToken string `json:"maingate_api_token"`
RedisURL string `json:"tavern_redis_url"`
macAddr string macAddr string
} }
@ -122,7 +94,8 @@ type subTavern struct {
region string region string
groups map[string]group groups map[string]group
methods map[string]reflect.Method methods map[string]reflect.Method
wshRpc rpcCallDomain[richConnOuter]
redisClient *redis.Client
} }
func getMacAddr() (string, error) { func getMacAddr() (string, error) {
@ -190,12 +163,18 @@ type groupPipelineDocument struct {
} }
func (tv *Tavern) prepare(ctx context.Context) error { func (tv *Tavern) prepare(ctx context.Context) error {
redisClient, err := gocommon.NewRedisClient(config.RedisURL, 0)
if err != nil {
logger.Error("config tavern_redis_url is not valid or missing")
return err
}
for region, url := range config.RegionStorage { for region, url := range config.RegionStorage {
var dbconn common.MongoClient var dbconn common.MongoClient
var err error var err error
var groupinstance group var groupinstance group
if err := rpc.IsCallerCalleeMethodMatch[richConnOuter](); err != nil { if err := rpc.IsCallerCalleeMethodMatch[connection](); err != nil {
return err return err
} }
@ -212,25 +191,23 @@ func (tv *Tavern) prepare(ctx context.Context) error {
mongoClient: dbconn, mongoClient: dbconn,
region: region, region: region,
methods: methods, methods: methods,
redisClient: redisClient,
} }
sub.wshRpc = createRpcCallDomain(tv.wsh.RedisSync, func(rc *wshandler.Richconn) *richConnOuter {
return &richConnOuter{wsh: sub.wsh, rc: rc}
})
groups := make(map[string]group) groups := make(map[string]group)
for typename, cfg := range config.GroupTypes { for typename, cfg := range config.GroupTypes {
cfg.Name = typename cfg.Name = typename
if cfg.Transient { if cfg.Transient {
groupinstance, err = cfg.prepareInMemory(ctx, region, typename, tv.wsh) groupinstance, err = cfg.prepareInMemory(ctx, region, typename, tv.wsh)
} else { } else {
if !dbconn.Connected() { // TODO : db
dbconn, err = common.NewMongoClient(ctx, url.Mongo, region) // if !dbconn.Connected() {
if err != nil { // dbconn, err = common.NewMongoClient(ctx, url.Mongo, region)
return err // if err != nil {
} // return err
} // }
groupinstance, err = cfg.preparePersistent(ctx, region, dbconn, tv.wsh) // }
// groupinstance, err = cfg.preparePersistent(ctx, region, dbconn, tv.wsh)
} }
if err != nil { if err != nil {
return err return err
@ -246,9 +223,6 @@ func (tv *Tavern) prepare(ctx context.Context) error {
} }
func (tv *Tavern) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error { func (tv *Tavern) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
// request는 항상 서비스 서버를 거쳐서 들어온다. [client] <--tls--> [service server] <--http--> tavern
// 클라이언트는 tavern으로부터 메시지를 수신할 뿐, 송신하지 못한다.
// 단, 요청은 https 서비스 서버를 통해 들어오고 클라이언트는 ws으로 수신만 한다는 원칙이 유지되어야 한다.(채팅 메시지는 예외?)
for _, sub := range tv.subTaverns { for _, sub := range tv.subTaverns {
var pattern string var pattern string
if sub.region == "default" { if sub.region == "default" {

2
go.mod
View File

@ -6,7 +6,7 @@ require (
github.com/go-redis/redis/v8 v8.11.5 github.com/go-redis/redis/v8 v8.11.5
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
go.mongodb.org/mongo-driver v1.11.7 go.mongodb.org/mongo-driver v1.11.7
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22 repositories.action2quare.com/ayo/gocommon v0.0.0-20230705150145-7ef3e68d161e
) )
require ( require (

6
go.sum
View File

@ -104,3 +104,9 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22 h1:DImSGNxZrc+Q4WlS1OKMsLAScEfDYLX4XMJdjAaVnXc= repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22 h1:DImSGNxZrc+Q4WlS1OKMsLAScEfDYLX4XMJdjAaVnXc=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns= repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230705022744-edd2f7aab52e h1:l9aqmNpEF8V1o0b3eCT/nhC+O1dXMUcPzBPewbshuDI=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230705022744-edd2f7aab52e/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230705132657-822681bf74cf h1:VQ78wRZaKHnWOM+Y2ZxB/EVNopzC4DNbwihledqjwy8=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230705132657-822681bf74cf/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230705150145-7ef3e68d161e h1:LNzK2Fhl1X8JQfn7XsoQwz2H/LY7YmMehEPqCyXgV1U=
repositories.action2quare.com/ayo/gocommon v0.0.0-20230705150145-7ef3e68d161e/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=