chat document update
This commit is contained in:
@ -2,10 +2,14 @@ package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
||||
@ -24,61 +28,17 @@ type chatConfig struct {
|
||||
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}
|
||||
`
|
||||
var accidHex func(primitive.ObjectID) string
|
||||
var accidstrHex func(string) string
|
||||
|
||||
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 {
|
||||
if err := json.Unmarshal(rem, &gc.chatConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -92,26 +52,49 @@ func (gc *groupChat) Initialize(sub *subTavern, cfg configDocument) error {
|
||||
sub.wsh.SendUpstreamMessage(sub.region, msg)
|
||||
}
|
||||
|
||||
gc.rh = gocommon.NewRedisonHandler(sub.redisClient.Context(), sub.redisClient)
|
||||
gc.incSizeScript = incScript
|
||||
gc.decSizeScript = decScript
|
||||
gc.rh = sub.redison
|
||||
|
||||
sub.apiFuncs.registApiFunction("CreateChattingChannel", gc.CreateChattingChannel)
|
||||
sub.apiFuncs.registApiFunction("FetchChattingChannels", gc.FetchChattingChannels)
|
||||
sub.apiFuncs.registApiFunction("BroadcastMessageOnChannel", gc.BroadcastMessageOnChannel)
|
||||
|
||||
sub.apiFuncs.registApiFunction("QueryPlayerChattingChannel", gc.QueryPlayerChattingChannel)
|
||||
sub.apiFuncs.registApiFunction("SendMessageOnChannel", gc.SendMessageOnChannel)
|
||||
sub.apiFuncs.registApiFunction("UpdateChannelDocument", gc.UpdateChannelDocument)
|
||||
for name, cfg := range gc.chatConfig.Channels {
|
||||
if _, ok := cfg["capacity"]; !ok {
|
||||
cfg["capacity"] = gc.chatConfig.DefaultCapacity
|
||||
} else {
|
||||
cfg["capacity"] = int64(cfg["capacity"].(float64))
|
||||
}
|
||||
cfg["key"] = name
|
||||
cfg["size"] = int32(0)
|
||||
|
||||
_, err := gc.rh.JSONSet(name, "$", cfg)
|
||||
if *devflag && err != nil {
|
||||
gc.rh.Del(gc.rh.Context(), name).Result()
|
||||
_, err = gc.rh.JSONSet(name, "$", cfg)
|
||||
}
|
||||
|
||||
_, err := gc.rh.HMSet(gc.rh.Context(), name, cfg).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ts := fmt.Sprintf("%x-", time.Now().Unix())
|
||||
if *devflag {
|
||||
accidHex = func(accid primitive.ObjectID) string {
|
||||
return ts + accid.Hex()
|
||||
}
|
||||
accidstrHex = func(accid string) string {
|
||||
return ts + accid
|
||||
}
|
||||
} else {
|
||||
accidHex = func(accid primitive.ObjectID) string {
|
||||
return accid.Hex()
|
||||
}
|
||||
accidstrHex = func(accid string) string {
|
||||
return accid
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -119,47 +102,65 @@ func (gc *groupChat) ClientMessageReceived(sender *wshandler.Sender, mt wshandle
|
||||
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"},
|
||||
})
|
||||
if _, ok := gc.chatConfig.Channels[chanid]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
size, err := gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
|
||||
if err != nil {
|
||||
logger.Println("JSONMGet failed :", err)
|
||||
continue
|
||||
}
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + chanid,
|
||||
Body: map[string]any{"size": size},
|
||||
Tag: []string{"ChattingChannelProperties"},
|
||||
})
|
||||
}
|
||||
if _, err := gc.rh.Del(gc.rh.Context(), accidHex(sender.Accid)).Result(); err != nil {
|
||||
logger.Println(err)
|
||||
}
|
||||
|
||||
} else if mt == wshandler.BinaryMessage {
|
||||
commandline := message.([]any)
|
||||
cmd := commandline[0].(string)
|
||||
args := commandline[1:]
|
||||
switch cmd {
|
||||
case "EnterChattingChannel":
|
||||
case "EnterPublicChannel":
|
||||
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"},
|
||||
})
|
||||
if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
|
||||
size, err := gc.rh.JSONGetInt64(chanid, "$.size")
|
||||
if err != nil || len(size) == 0 {
|
||||
logger.Println("JSONGetInt64 failed :", chanid, err)
|
||||
} else if size[0] < cfg["capacity"].(int64) {
|
||||
// 입장
|
||||
newsize, err := gc.rh.JSONNumIncrBy(chanid, "$.size", 1)
|
||||
if err == nil {
|
||||
gc.enterRoom(chanid, sender.Accid)
|
||||
gc.rh.HSet(gc.rh.Context(), accidHex(sender.Accid), "cc_pub", chanid)
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + chanid,
|
||||
Body: map[string]any{"size": newsize[0]},
|
||||
Tag: []string{"ChattingChannelProperties"},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 풀방
|
||||
logger.Println("chatting channel is full :", chanid, size)
|
||||
}
|
||||
} else {
|
||||
// 입장 실패 알림
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "@" + sender.Accid.Hex(),
|
||||
Body: map[string]string{"id": chanid, "err": err.Error()},
|
||||
Tag: []string{"EnterChattingChannelFailed"},
|
||||
})
|
||||
logger.Println("chatting channel not valid :", chanid)
|
||||
}
|
||||
|
||||
case "LeaveChattingChannel":
|
||||
case "LeavePublicChannel":
|
||||
chanid := args[0].(string)
|
||||
gc.rh.HDel(gc.rh.Context(), accidHex(sender.Accid), "cc_pub")
|
||||
gc.leaveRoom(chanid, sender.Accid)
|
||||
sizeseq, err := gc.rh.EvalSha(gc.rh.Context(), gc.decSizeScript, []string{chanid, sender.Alias}).Result()
|
||||
newsize, err := gc.rh.JSONNumIncrBy(chanid, "$.size", 1)
|
||||
if err == nil {
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + chanid,
|
||||
Body: map[string]any{"size": sizeseq.([]any)[0], "seq": sizeseq.([]any)[1]},
|
||||
Body: map[string]any{"size": newsize[0]},
|
||||
Tag: []string{"ChattingChannelProperties"},
|
||||
})
|
||||
}
|
||||
@ -172,31 +173,52 @@ func (gc *groupChat) ClientMessageReceived(sender *wshandler.Sender, mt wshandle
|
||||
Body: map[string]any{"sender": sender.Alias, "msg": msg},
|
||||
Tag: []string{"TextMessage"},
|
||||
})
|
||||
|
||||
case "EnterPrivateChannel":
|
||||
typename := args[0].(string)
|
||||
channel := args[1].(string)
|
||||
var reason string
|
||||
if len(args) > 2 {
|
||||
reason = args[2].(string)
|
||||
}
|
||||
|
||||
if len(reason) > 0 {
|
||||
// 수락
|
||||
ok, err := gc.rh.HSetNX(gc.rh.Context(), accidHex(sender.Accid), "cc_"+typename, channel).Result()
|
||||
if err != nil || !ok {
|
||||
// 이미 다른 private channel 참여 중
|
||||
logger.Println("EnterPrivateChannel failed. HSetNX return err :", err, sender.Accid.Hex(), typename, channel)
|
||||
return
|
||||
}
|
||||
gc.enterRoom(channel, sender.Accid)
|
||||
}
|
||||
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + channel,
|
||||
Body: map[string]any{
|
||||
"sender": sender.Alias,
|
||||
"msg": reason,
|
||||
"typename": typename,
|
||||
},
|
||||
Tag: []string{"EnterPrivateChannel"},
|
||||
})
|
||||
|
||||
case "LeavePrivateChannel":
|
||||
typename := args[0].(string)
|
||||
channel := args[1].(string)
|
||||
cnt, _ := gc.rh.HDel(gc.rh.Context(), accidHex(sender.Accid), "cc_"+typename).Result()
|
||||
if cnt > 0 {
|
||||
gc.leaveRoom(channel, sender.Accid)
|
||||
}
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + channel,
|
||||
Body: map[string]any{"sender": sender.Alias, "typename": typename},
|
||||
Tag: []string{"LeavePrivateChannel"},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -212,29 +234,170 @@ func (gc *groupChat) FetchChattingChannels(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
var channels []map[string]string
|
||||
var rows []string
|
||||
for _, key := range keys {
|
||||
onechan, err := gc.rh.HGetAll(gc.rh.Context(), key).Result()
|
||||
onechan, err := gc.rh.JSONGet(key, "$")
|
||||
if err != nil {
|
||||
logger.Println("FetchChattingChannel failed. HGetAll return err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
channels = append(channels, onechan)
|
||||
row := onechan.(string)
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
w.Write([]byte("[]"))
|
||||
} else if len(rows) == 1 {
|
||||
w.Write([]byte(rows[0]))
|
||||
} else {
|
||||
first := rows[0]
|
||||
w.Write([]byte(first[:len(first)-1]))
|
||||
for i := 1; i < len(rows); i++ {
|
||||
mid := rows[i]
|
||||
w.Write([]byte(","))
|
||||
w.Write([]byte(mid[1 : len(mid)-1]))
|
||||
}
|
||||
w.Write([]byte("]"))
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *groupChat) QueryPlayerChattingChannel(w http.ResponseWriter, r *http.Request) {
|
||||
accid, _ := gocommon.ReadStringFormValue(r.Form, "accid")
|
||||
typename, _ := gocommon.ReadStringFormValue(r.Form, "typename")
|
||||
|
||||
var fields []string
|
||||
if len(typename) == 0 {
|
||||
fields = []string{"cc_pub"}
|
||||
} else {
|
||||
fields = []string{"cc_pub", "cc_" + typename}
|
||||
}
|
||||
|
||||
chans, err := gc.rh.HMGet(gc.rh.Context(), accidstrHex(accid), fields...).Result()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
output := make(map[string]string)
|
||||
if len(chans) > 0 && chans[0] != nil {
|
||||
output["public"] = chans[0].(string)
|
||||
}
|
||||
if len(chans) > 1 && chans[1] != nil {
|
||||
output[typename] = chans[1].(string)
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
enc.Encode(channels)
|
||||
enc.Encode(output)
|
||||
}
|
||||
|
||||
func (gc *groupChat) UpdateChannelDocument(w http.ResponseWriter, r *http.Request) {
|
||||
channel, _ := gocommon.ReadStringFormValue(r.Form, "channel")
|
||||
key, _ := gocommon.ReadStringFormValue(r.Form, "key")
|
||||
ret, _ := gocommon.ReadStringFormValue(r.Form, "return")
|
||||
if len(channel) == 0 || len(key) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
bt, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var doc map[string]any
|
||||
if err := bson.Unmarshal(bt, &doc); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if ret == "before" {
|
||||
before, err2 := gc.rh.JSONGet(channel, "$")
|
||||
if err2 != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
w.Write([]byte(before.(string)))
|
||||
}
|
||||
}()
|
||||
} else if ret == "after" {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
after, err2 := gc.rh.JSONGet(channel, "$")
|
||||
if err2 != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
} else {
|
||||
w.Write([]byte(after.(string)))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
_, err = gc.rh.JSONSet(channel, "$."+key, doc)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *groupChat) SendMessageOnChannel(w http.ResponseWriter, r *http.Request) {
|
||||
channel, _ := gocommon.ReadStringFormValue(r.Form, "channel")
|
||||
target, _ := gocommon.ReadStringFormValue(r.Form, "target")
|
||||
tag, _ := gocommon.ReadStringFormValue(r.Form, "tag")
|
||||
if len(channel) == 0 || len(target) == 0 || len(tag) == 0 {
|
||||
logger.Println("SendMessageOnChannel failed. channel or target or tag is empty")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var doc map[string]any
|
||||
bt, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
logger.Println("SendMessageOnChannel failed. io.ReadAll returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if len(bt) > 0 {
|
||||
if err := bson.Unmarshal(bt, &doc); err != nil {
|
||||
logger.Println("SendMessageOnChannel failed. decode returns err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: fmt.Sprintf("%s@%s", target, channel),
|
||||
Body: doc,
|
||||
Tag: []string{tag},
|
||||
})
|
||||
}
|
||||
|
||||
func (gc *groupChat) BroadcastMessageOnChannel(w http.ResponseWriter, r *http.Request) {
|
||||
nickname, _ := gocommon.ReadStringFormValue(r.Form, "nickname")
|
||||
channel, _ := gocommon.ReadStringFormValue(r.Form, "channel")
|
||||
tag, _ := gocommon.ReadStringFormValue(r.Form, "tag")
|
||||
text, _ := io.ReadAll(r.Body)
|
||||
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + channel,
|
||||
Body: map[string]any{"sender": nickname, "msg": string(text)},
|
||||
Tag: []string{"TextMessage"},
|
||||
})
|
||||
if len(tag) > 0 {
|
||||
var doc map[string]any
|
||||
if err := bson.Unmarshal(text, &doc); err == nil {
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + channel,
|
||||
Body: doc,
|
||||
Tag: []string{tag},
|
||||
})
|
||||
} else {
|
||||
logger.Println("BroadcastMessageOnChannel failed :", err)
|
||||
}
|
||||
} else {
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + channel,
|
||||
Body: map[string]any{"sender": nickname, "msg": string(text)},
|
||||
Tag: []string{"TextMessage"},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user