package core import ( "encoding/json" "io" "net/http" "reflect" "repositories.action2quare.com/ayo/gocommon" "repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/wshandler" ) func init() { groupTypeContainer()["chat"] = reflect.TypeOf(&groupChat{}) } type channelID = string type chatConfig struct { DefaultCapacity int64 `json:"default_capacity"` Channels map[string]map[string]any `json:"channels"` } type groupChat struct { chatConfig rh *gocommon.RedisonHandler incSizeScript string decSizeScript string enterRoom func(channelID, accountID) leaveRoom func(channelID, accountID) sendUpstreamMessage func(msg *wshandler.UpstreamMessage) } var increaseSizeScript = ` local cap = redis.call("HGET", KEYS[1], "capacity") local newseq = redis.call("HINCRBY", KEYS[1], "seq", 1) local newsize = redis.call("HINCRBY", KEYS[1], "size", 1) if tonumber(cap) < newsize then redis.call("HINCRBY", KEYS[1], "size", -1) return {err = "channel is full"} end redis.call("HSET", "_m_"..KEYS[1], KEYS[2], ARGV[1]) return {newsize, newseq} ` var decreaseSizeScript = ` local exists = redis.call("EXISTS", "_m_"..KEYS[1]) if exists == 0 then return {err = "not target"} end local newseq = redis.call("HINCRBY", KEYS[1], "seq", 1) local newsize = redis.call("HINCRBY", KEYS[1], "size", -1) redis.call("HDEL", "_m_"..KEYS[1], KEYS[2]) return {newsize, newseq} ` func (gc *groupChat) Initialize(sub *subTavern, cfg configDocument) error { incScript, err := sub.redisClient.ScriptLoad(sub.redisClient.Context(), increaseSizeScript).Result() if err != nil { return err } decScript, err := sub.redisClient.ScriptLoad(sub.redisClient.Context(), decreaseSizeScript).Result() if err != nil { return err } // newsize, err := sub.redisClient.EvalSha(sub.redisClient.Context(), incScript, []string{"myhash", "alias"}, "accid").Result() // if err != nil { // return err // } // logger.Println(newsize.([]any)) // newsize, err = sub.redisClient.EvalSha(sub.redisClient.Context(), decScript, []string{"myhash", "alias"}).Result() // if err != nil { // return err // } // logger.Println(newsize.([]any)) rem, _ := json.Marshal(cfg) if err = json.Unmarshal(rem, &gc.chatConfig); err != nil { return err } gc.enterRoom = func(chanid channelID, accid accountID) { sub.wsh.EnterRoom(sub.region, string(chanid), accid) } gc.leaveRoom = func(chanid channelID, accid accountID) { sub.wsh.LeaveRoom(sub.region, string(chanid), accid) } gc.sendUpstreamMessage = func(msg *wshandler.UpstreamMessage) { sub.wsh.SendUpstreamMessage(sub.region, msg) } gc.rh = gocommon.NewRedisonHandler(sub.redisClient.Context(), sub.redisClient) gc.incSizeScript = incScript gc.decSizeScript = decScript sub.apiFuncs.registApiFunction("CreateChattingChannel", gc.CreateChattingChannel) sub.apiFuncs.registApiFunction("FetchChattingChannels", gc.FetchChattingChannels) sub.apiFuncs.registApiFunction("BroadcastMessageOnChannel", gc.BroadcastMessageOnChannel) for name, cfg := range gc.chatConfig.Channels { if _, ok := cfg["capacity"]; !ok { cfg["capacity"] = gc.chatConfig.DefaultCapacity } cfg["key"] = name _, err := gc.rh.HMSet(gc.rh.Context(), name, cfg).Result() if err != nil { return err } } return nil } func (gc *groupChat) ClientMessageReceived(sender *wshandler.Sender, mt wshandler.WebSocketMessageType, message any) { if mt == wshandler.Disconnected { rooms := message.([]string) for _, chanid := range rooms { sizeseq, err := gc.rh.EvalSha(gc.rh.Context(), gc.decSizeScript, []string{chanid, sender.Alias}).Result() if err == nil { gc.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: "#" + chanid, Body: map[string]any{"size": sizeseq.([]any)[0], "seq": sizeseq.([]any)[1]}, Tag: []string{"ChattingChannelProperties"}, }) } } } else if mt == wshandler.BinaryMessage { commandline := message.([]any) cmd := commandline[0].(string) args := commandline[1:] switch cmd { case "EnterChattingChannel": chanid := args[0].(string) sizeseq, err := gc.rh.EvalSha(gc.rh.Context(), gc.incSizeScript, []string{chanid, sender.Alias}, sender.Accid.Hex()).Result() if err == nil { gc.enterRoom(chanid, sender.Accid) gc.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: "#" + chanid, Body: map[string]any{"size": sizeseq.([]any)[0], "seq": sizeseq.([]any)[1]}, Tag: []string{"ChattingChannelProperties"}, }) } else { // 입장 실패 알림 gc.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: "@" + sender.Accid.Hex(), Body: map[string]string{"id": chanid, "err": err.Error()}, Tag: []string{"EnterChattingChannelFailed"}, }) } case "LeaveChattingChannel": chanid := args[0].(string) gc.leaveRoom(chanid, sender.Accid) sizeseq, err := gc.rh.EvalSha(gc.rh.Context(), gc.decSizeScript, []string{chanid, sender.Alias}).Result() if err == nil { gc.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: "#" + chanid, Body: map[string]any{"size": sizeseq.([]any)[0], "seq": sizeseq.([]any)[1]}, Tag: []string{"ChattingChannelProperties"}, }) } case "TextMessage": chanid := args[0].(string) msg := args[1].(string) gc.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: "#" + chanid, Body: map[string]any{"sender": sender.Alias, "msg": msg}, Tag: []string{"TextMessage"}, }) } } } func (gc *groupChat) CreateChattingChannel(w http.ResponseWriter, r *http.Request) { chanstr, _ := gocommon.ReadStringFormValue(r.Form, "name") if len(chanstr) == 0 { logger.Println("CreateChattingChannel failed. name is missing") w.WriteHeader(http.StatusBadRequest) return } capacity, _ := gocommon.ReadIntegerFormValue(r.Form, "capacity") if capacity == 0 { capacity = gc.chatConfig.DefaultCapacity } _, err := gc.rh.HSetNX(gc.rh.Context(), chanstr, "capacity", capacity).Result() if err != nil { logger.Println("CreateChattingChannel failed. HSetNX returns err :", err) w.WriteHeader(http.StatusInternalServerError) return } } func (gc *groupChat) FetchChattingChannels(w http.ResponseWriter, r *http.Request) { prefix, _ := gocommon.ReadStringFormValue(r.Form, "prefix") if len(prefix) == 0 { logger.Println("FetchChattingChannel failed. prefix is missing") w.WriteHeader(http.StatusBadRequest) return } keys, err := gc.rh.Keys(gc.rh.Context(), prefix+"*").Result() if err != nil { logger.Println("FetchChattingChannel failed. Keys return err :", err) w.WriteHeader(http.StatusInternalServerError) return } var channels []map[string]string for _, key := range keys { onechan, err := gc.rh.HGetAll(gc.rh.Context(), key).Result() if err != nil { logger.Println("FetchChattingChannel failed. HGetAll return err :", err) w.WriteHeader(http.StatusInternalServerError) return } channels = append(channels, onechan) } enc := json.NewEncoder(w) enc.Encode(channels) } func (gc *groupChat) BroadcastMessageOnChannel(w http.ResponseWriter, r *http.Request) { nickname, _ := gocommon.ReadStringFormValue(r.Form, "nickname") channel, _ := gocommon.ReadStringFormValue(r.Form, "channel") text, _ := io.ReadAll(r.Body) gc.sendUpstreamMessage(&wshandler.UpstreamMessage{ Target: "#" + channel, Body: map[string]any{"sender": nickname, "msg": string(text)}, Tag: []string{"TextMessage"}, }) }