This commit is contained in:
2022-03-17 15:55:27 +08:00
commit bd5a9fad97
92 changed files with 13861 additions and 0 deletions

38
logic/CHANGELOG.md Normal file
View File

@@ -0,0 +1,38 @@
版本号`major.minor.patch`具体规则如下:
- major主版本号如有重大版本重构则该字段递增通常各主版本间接口不兼容。
- minor次版本号各次版本号间接口保持兼容如有接口新增或优化则该字段递增。
- patch补丁号如有功能改善或缺陷修复则该字段递增。
## version 3.1.4 @2021.10.28
优化 log 模块
配置文件更新
新增
```toml
[log]
Level="debug"
Mode="console"
Path=""
Display="json"
```
## version 3.1.3 @2021.7.29
修复当 addr 为空产生 panic
## version 3.1.2 @2021.7.22
**Feature**
- 支持根据 server 使用指定位置的 comet rpc @3.1.2 2021.7.22
## example x.x.x @yy.mm.dd
**Feature**
**Bug Fixes**
**Improvement**
**Breaking Change**

23
logic/auth/auth.go Normal file
View File

@@ -0,0 +1,23 @@
package auth
import "time"
var execAuth = make(map[string]CreateFunc)
type CreateFunc func(url string, timeout time.Duration) Auth
func Register(name string, exec CreateFunc) {
execAuth[name] = exec
}
func Load(name string) (CreateFunc, error) {
exec, ok := execAuth[name]
if !ok {
return nil, nil
}
return exec, nil
}
type Auth interface {
DoAuth(token string) (string, error)
}

67
logic/auth/dtalk/auth.go Normal file
View File

@@ -0,0 +1,67 @@
package acc
import (
"encoding/json"
"errors"
"time"
"gitlab.33.cn/btrade/auto_trade_tools/reqtypes"
"gitlab.33.cn/btrade/auto_trade_tools/util"
)
type talkClient struct {
url string
timeout time.Duration
}
func (a *talkClient) DoAuth(token string) (uid string, err error) {
var (
bytes []byte
)
headers := map[string]string{}
headers["FZM-SIGNATURE"] = token
bytes, err = util.HttpReq(&reqtypes.HttpParams{
Method: "POST",
ReqUrl: a.url,
HeaderMap: headers,
Timeout: a.timeout,
})
if err != nil {
return
}
var res map[string]interface{}
err = json.Unmarshal(bytes, &res)
if err != nil {
return
}
if e, ok := res["error"]; ok {
err = errors.New(e.(string))
return
}
if _, ok := res["data"]; !ok {
err = errors.New("invalid auth res")
return
}
data, ok := res["data"].(map[string]interface{})
if !ok {
err = errors.New("invalid auth data format")
return
}
if _, ok := data["address"]; !ok {
err = errors.New("invalid auth data")
return
}
uid, ok = data["address"].(string)
if !ok {
err = errors.New("invalid auth data id format")
return
}
return
}

View File

@@ -0,0 +1,17 @@
package acc
import (
"time"
"gitlab.33.cn/chat/im/logic/auth"
)
const Name = "dtalk"
func init() {
auth.Register(Name, NewAuth)
}
func NewAuth(url string, timeout time.Duration) auth.Auth {
return &talkClient{url: url, timeout: timeout}
}

67
logic/auth/zhaobi/auth.go Normal file
View File

@@ -0,0 +1,67 @@
package acc
import (
"encoding/json"
"errors"
"time"
"gitlab.33.cn/btrade/auto_trade_tools/reqtypes"
"gitlab.33.cn/btrade/auto_trade_tools/util"
)
type talkClient struct {
url string
timeout time.Duration
}
func (a *talkClient) DoAuth(token string) (uid string, err error) {
var (
bytes []byte
)
headers := map[string]string{}
headers["Authorization"] = token
bytes, err = util.HttpReq(&reqtypes.HttpParams{
Method: "GET",
ReqUrl: a.url,
HeaderMap: headers,
Timeout: a.timeout,
})
if err != nil {
return
}
var res map[string]interface{}
err = json.Unmarshal(bytes, &res)
if err != nil {
return
}
if e, ok := res["error"]; ok {
err = errors.New(e.(string))
return
}
if _, ok := res["data"]; !ok {
err = errors.New("invalid auth res")
return
}
data, ok := res["data"].(map[string]interface{})
if !ok {
err = errors.New("invalid auth data format")
return
}
if _, ok := data["user_id"]; !ok {
err = errors.New("invalid auth data")
return
}
uid, ok = data["user_id"].(string)
if !ok {
err = errors.New("invalid auth data id format")
return
}
return
}

View File

@@ -0,0 +1,17 @@
package acc
import (
"time"
"gitlab.33.cn/chat/im/logic/auth"
)
const Name = "zb_otc"
func init() {
auth.Register(Name, NewAuth)
}
func NewAuth(url string, timeout time.Duration) auth.Auth {
return &talkClient{url: url, timeout: timeout}
}

130
logic/cmd/main.go Normal file
View File

@@ -0,0 +1,130 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"net"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"github.com/Terry-Mao/goim/pkg/ip"
"github.com/opentracing/opentracing-go"
"github.com/rs/zerolog/log"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
xlog "gitlab.33.cn/chat/im-pkg/log"
"gitlab.33.cn/chat/im-pkg/trace"
"gitlab.33.cn/chat/im/logic"
_ "gitlab.33.cn/chat/im/logic/auth/dtalk"
_ "gitlab.33.cn/chat/im/logic/auth/zhaobi"
"gitlab.33.cn/chat/im/logic/conf"
"gitlab.33.cn/chat/im/logic/grpc"
"gitlab.33.cn/chat/im/naming"
)
const (
srvName = "logic"
)
var (
debug bool
)
func init() {
flag.BoolVar(&debug, "debug", false, "sets log level to debug")
}
var (
// projectVersion 项目版本
projectVersion = "3.1.4"
// goVersion go版本
goVersion = ""
// gitCommit git提交commit id
gitCommit = ""
// buildTime 编译时间
buildTime = ""
isShowVersion = flag.Bool("version", false, "show project version")
)
// showVersion 显示项目版本信息
func showVersion(isShow bool) {
if isShow {
fmt.Printf("Project: %s\n", srvName)
fmt.Printf(" Version: %s\n", projectVersion)
fmt.Printf(" Go Version: %s\n", goVersion)
fmt.Printf(" Git Commit: %s\n", gitCommit)
fmt.Printf(" Build Time: %s\n", buildTime)
os.Exit(0)
}
}
func main() {
flag.Parse()
showVersion(*isShowVersion)
if err := conf.Init(); err != nil {
panic(err)
}
//log init
var err error
log.Logger, err = xlog.Init(conf.Conf.Log)
if err != nil {
panic(err)
}
log.Logger.With().Str("service", srvName)
byte, _ := json.Marshal(conf.Conf)
log.Info().Str("config", string(byte)).Send()
// trace init
tracer, tracerCloser := trace.Init(srvName, conf.Conf.Trace, config.Logger(jaeger.NullLogger))
//不然后续不会有Jaeger实例
opentracing.SetGlobalTracer(tracer)
srv := logic.New(conf.Conf)
rpcSrv := grpc.New(conf.Conf.RPCServer, srv)
go func() {
if err := http.ListenAndServe(":8001", nil); err != nil {
panic(err)
}
}()
// register logic
_, port, _ := net.SplitHostPort(conf.Conf.RPCServer.Addr)
addr := fmt.Sprintf("%s:%s", ip.InternalIP(), port)
if err := naming.Register(conf.Conf.Reg.RegAddrs, conf.Conf.Reg.SrvName, addr, conf.Conf.Reg.Schema, 15); err != nil {
panic(err)
}
fmt.Println("register ok")
// signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info().Str("signal", s.String()).Send()
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
if err := naming.UnRegister(conf.Conf.Reg.SrvName, addr, conf.Conf.Reg.Schema); err != nil {
log.Error().Err(err).Msg("naming.UnRegister")
}
srv.Close()
rpcSrv.GracefulStop()
if err := tracerCloser.Close(); err != nil {
log.Error().Err(err).Msg("tracer close failed")
}
//log.Flush()
return
case syscall.SIGHUP:
default:
return
}
}
}

6
logic/cmd/run.sh Normal file
View File

@@ -0,0 +1,6 @@
# -log_dir=log 日志输出文件夹
# -logtostderr=true 打印到标准错误而不是文件。
# -alsologtostderr=true 同时打印到标准错误。
# -v=4 设置日志级别
# -vmodule=main=5 设置单个文件日志级别
./main -v=4 -log_dir=log -alsologtostderr=true

164
logic/conf/conf.go Normal file
View File

@@ -0,0 +1,164 @@
package conf
import (
"flag"
"os"
"time"
"github.com/BurntSushi/toml"
xtime "github.com/Terry-Mao/goim/pkg/time"
"github.com/uber/jaeger-client-go"
traceConfig "github.com/uber/jaeger-client-go/config"
xlog "gitlab.33.cn/chat/im-pkg/log"
)
var (
confPath string
regAddrs string
// Conf config
Conf *Config
)
func init() {
var (
defAddrs = os.Getenv("REGADDRS")
)
flag.StringVar(&confPath, "conf", "logic.toml", "default config path")
flag.StringVar(&regAddrs, "reg", defAddrs, "etcd register addrs. eg:127.0.0.1:2379")
}
// Init init config.
func Init() (err error) {
Conf = Default()
_, err = toml.DecodeFile(confPath, &Conf)
return
}
// Default new a config with specified defualt value.
func Default() *Config {
return &Config{
Env: "",
Log: xlog.Config{
Level: "debug",
Mode: "console",
Path: "",
Display: "console",
},
Trace: traceConfig.Configuration{
ServiceName: "answer",
Gen128Bit: true,
Sampler: &traceConfig.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &traceConfig.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "127.0.0.1:6831",
},
},
Reg: &Reg{
Schema: "im",
SrvName: "logic",
RegAddrs: regAddrs,
},
CometRPCClient: &RPCClient{
Schema: "im",
SrvName: "comet",
Dial: xtime.Duration(time.Second),
Timeout: xtime.Duration(time.Second)},
RPCServer: &RPCServer{
Network: "tcp",
Addr: "3119",
Timeout: xtime.Duration(time.Second),
IdleTimeout: xtime.Duration(time.Second * 60),
MaxLifeTime: xtime.Duration(time.Hour * 2),
ForceCloseWait: xtime.Duration(time.Second * 20),
KeepAliveInterval: xtime.Duration(time.Second * 60),
KeepAliveTimeout: xtime.Duration(time.Second * 20),
},
Backoff: &Backoff{MaxDelay: 300, BaseDelay: 3, Factor: 1.8, Jitter: 1.3},
}
}
// Config config.
type Config struct {
Env string
Log xlog.Config
Trace traceConfig.Configuration
Reg *Reg
CometRPCClient *RPCClient
RPCServer *RPCServer
Kafka *Kafka
Redis *Redis
Node *Node
Backoff *Backoff
Apps []*App
}
// Reg is service register/discovery config
type Reg struct {
Schema string
SrvName string // call
RegAddrs string // etcd addrs, seperate by ','
}
type App struct {
AppId string
AuthUrl string
Timeout xtime.Duration
}
// Node node config.
type Node struct {
HeartbeatMax int
Heartbeat xtime.Duration
}
// Backoff backoff.
type Backoff struct {
MaxDelay int32
BaseDelay int32
Factor float32
Jitter float32
}
// Redis .
type Redis struct {
Network string
Addr string
Auth string
Active int
Idle int
DialTimeout xtime.Duration
ReadTimeout xtime.Duration
WriteTimeout xtime.Duration
IdleTimeout xtime.Duration
Expire xtime.Duration
}
// Kafka .
type Kafka struct {
Topic string
Brokers []string
}
// RPCClient is RPC client config.
type RPCClient struct {
Schema string
SrvName string
Dial xtime.Duration
Timeout xtime.Duration
}
// RPCServer is RPC server config.
type RPCServer struct {
Network string
Addr string
Timeout xtime.Duration
IdleTimeout xtime.Duration
MaxLifeTime xtime.Duration
ForceCloseWait xtime.Duration
KeepAliveInterval xtime.Duration
KeepAliveTimeout xtime.Duration
}

69
logic/conf/logic.toml Normal file
View File

@@ -0,0 +1,69 @@
env="debug"
[log]
Level="debug"
Mode="console"
Path=""
Display="json"
[Trace]
ServiceName=""
Gen128Bit=true
[Trace.Sampler]
Type="const"
Param=1.0
[Trace.Reporter]
LogSpans=true
LocalAgentHostPort="172.16.101.130:6831"
[reg]
schema = "im"
srvName = "logic"
regAddrs = "127.0.0.1:2379"
[node]
heartbeat = "4m"
heartbeatMax = 2
[backoff]
maxDelay = 300
baseDelay = 3
factor = 1.8
jitter = 0.3
[RPCServer]
Network = "tcp"
Addr = ":3119"
Timeout = "1s"
KeepAliveMaxConnectionIdle = "60s"
KeepAliveMaxConnectionAge = "2h"
KeepAliveMaxMaxConnectionAgeGrace = "20s"
KeepAliveTime = "60s"
KeepAliveTimeout = "20s"
[CometRPCClient]
schema = "im"
srvName = "comet"
dial = "1s"
timeout = "1s"
[kafka]
topic = "goim-push-topic"
brokers = ["127.0.0.1:9092"]
[redis]
network = "tcp"
addr = "127.0.0.1:6379"
active = 60000
idle = 1024
dialTimeout = "200ms"
readTimeout = "500ms"
writeTimeout = "500ms"
idleTimeout = "120s"
expire = "30m"
[[apps]]
appId = "dtalk"
authUrl = "http://127.0.0.1:18002/user/login"
timeout = "1s"

72
logic/dao/dao.go Normal file
View File

@@ -0,0 +1,72 @@
package dao
import (
"context"
"time"
"github.com/gomodule/redigo/redis"
"gitlab.33.cn/chat/im/logic/conf"
kafka "gopkg.in/Shopify/sarama.v1"
)
// Dao dao.
type Dao struct {
c *conf.Config
kafkaPub kafka.SyncProducer
redis *redis.Pool
redisExpire int32
}
// New new a dao and return.
func New(c *conf.Config) *Dao {
d := &Dao{
c: c,
kafkaPub: newKafkaPub(c.Kafka),
redis: newRedis(c.Redis),
redisExpire: int32(time.Duration(c.Redis.Expire) / time.Second),
}
return d
}
func newKafkaPub(c *conf.Kafka) kafka.SyncProducer {
kc := kafka.NewConfig()
kc.Producer.RequiredAcks = kafka.WaitForAll // Wait for all in-sync replicas to ack the message
kc.Producer.Retry.Max = 10 // Retry up to 10 times to produce the message
kc.Producer.Return.Successes = true
kc.Version = kafka.V0_11_0_2
pub, err := kafka.NewSyncProducer(c.Brokers, kc)
if err != nil {
panic(err)
}
return pub
}
func newRedis(c *conf.Redis) *redis.Pool {
return &redis.Pool{
MaxIdle: c.Idle,
MaxActive: c.Active,
IdleTimeout: time.Duration(c.IdleTimeout),
Dial: func() (redis.Conn, error) {
conn, err := redis.Dial(c.Network, c.Addr,
redis.DialConnectTimeout(time.Duration(c.DialTimeout)),
redis.DialReadTimeout(time.Duration(c.ReadTimeout)),
redis.DialWriteTimeout(time.Duration(c.WriteTimeout)),
redis.DialPassword(c.Auth),
)
if err != nil {
return nil, err
}
return conn, nil
},
}
}
// Close close the resource.
func (d *Dao) Close() error {
return d.redis.Close()
}
// Ping dao ping.
func (d *Dao) Ping(c context.Context) error {
return d.pingRedis(c)
}

44
logic/dao/kafka.go Normal file
View File

@@ -0,0 +1,44 @@
package dao
import (
"context"
"fmt"
"github.com/opentracing/opentracing-go"
"gitlab.33.cn/chat/im-pkg/trace"
comet "gitlab.33.cn/chat/im/api/comet/grpc"
"github.com/golang/protobuf/proto"
"github.com/rs/zerolog/log"
pb "gitlab.33.cn/chat/im/api/logic/grpc"
"gopkg.in/Shopify/sarama.v1"
)
// PushMsg push a message to databus.
func (d *Dao) PublishMsg(ctx context.Context, appId string, fromId string, op comet.Op, key string, msg []byte) (err error) {
tracer := opentracing.GlobalTracer()
span, ctx := opentracing.StartSpanFromContextWithTracer(ctx, tracer, fmt.Sprintf("Publish -%v-%v", appId, op.String()))
defer span.Finish()
pushMsg := &pb.BizMsg{
AppId: appId,
FromId: fromId,
Op: int32(op),
Key: key,
Msg: msg,
}
b, err := proto.Marshal(pushMsg)
if err != nil {
return
}
appTopic := fmt.Sprintf("goim-%s-topic", appId)
m := &sarama.ProducerMessage{
Key: sarama.StringEncoder(fromId),
Topic: appTopic,
Value: sarama.ByteEncoder(b),
}
trace.InjectMQHeader(tracer, span.Context(), ctx, m)
if _, _, err = d.kafkaPub.SendMessage(m); err != nil {
log.Error().Interface("pushMsg", pushMsg).Err(err).Msg("kafkaPub.SendMessage error")
}
return
}

32
logic/dao/main_test.go Normal file
View File

@@ -0,0 +1,32 @@
package dao
import (
"os"
"testing"
"time"
xtime "github.com/Terry-Mao/goim/pkg/time"
"github.com/gomodule/redigo/redis"
"gitlab.33.cn/chat/im/logic/conf"
)
var (
testConf *conf.Config
testRedis *redis.Pool
)
func TestMain(m *testing.M) {
testRedis = newRedis(&conf.Redis{
Network: "tcp",
Addr: "127.0.0.1:6379",
Auth: "",
Active: 60000,
Idle: 1024,
DialTimeout: xtime.Duration(200 * time.Millisecond),
ReadTimeout: xtime.Duration(500 * time.Millisecond),
WriteTimeout: xtime.Duration(500 * time.Millisecond),
IdleTimeout: xtime.Duration(120 * time.Second),
Expire: xtime.Duration(30 * time.Minute),
})
os.Exit(m.Run())
}

300
logic/dao/redis.go Normal file
View File

@@ -0,0 +1,300 @@
package dao
import (
"context"
"errors"
"fmt"
"strings"
"github.com/gomodule/redigo/redis"
"github.com/rs/zerolog/log"
)
const (
MidFmt = "%s:%v" // {appId}:{uid}
_prefixMidServer = "mid_%s:%v" // mid_{appId}:{uid} -> key:server
_prefixKeyServer = "key_%s" // key_{key} -> server
_prefixKeyUser = "usr_%s" // usr_{key} -> {appId}:{uid}
_prefixGroupServer = "group_%s:%s" // group_{appId}:{gid} -> {server}:{score}
)
func keyMidServer(appId string, mid string) string {
return fmt.Sprintf(_prefixMidServer, appId, mid)
}
func keyKeyServer(key string) string {
return fmt.Sprintf(_prefixKeyServer, key)
}
func keyKeyUser(key string) string {
return fmt.Sprintf(_prefixKeyUser, key)
}
func keyGroupServer(appId, gid string) string {
return fmt.Sprintf(_prefixGroupServer, appId, gid)
}
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get()
_, err = conn.Do("SET", "PING", "PONG")
conn.Close()
return
}
func (d *Dao) GetMember(c context.Context, key string) (appId string, mid string, err error) {
conn := d.redis.Get()
defer conn.Close()
ss, err := redis.String(conn.Do("GET", keyKeyUser(key)))
if err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.DO(GET)")
return "", "", err
}
arr := strings.Split(ss, ":")
if len(arr) != 2 {
return "", "", errors.New("invalid key")
}
appId = arr[0]
mid = arr[1]
return
}
func (d *Dao) GetServer(c context.Context, key string) (server string, err error) {
conn := d.redis.Get()
defer conn.Close()
if server, err = redis.String(conn.Do("GET", keyKeyServer(key))); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.DO(GET)")
}
return
}
func (d *Dao) AddMapping(c context.Context, mid string, appId string, key string, server string) (err error) {
conn := d.redis.Get()
defer conn.Close()
var n = 4
if mid != "" {
if err = conn.Send("HSET", keyMidServer(appId, mid), key, server); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(HSET %s,%s,%s,%s) error", appId, mid, server, key))
return
}
if err = conn.Send("EXPIRE", keyMidServer(appId, mid), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s,%s,%s,%s)", appId, mid, key, server))
return
}
n += 2
}
if err = conn.Send("SET", keyKeyServer(key), server); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(HSET %s,%s,%s) error", mid, server, key))
return
}
if err = conn.Send("EXPIRE", keyKeyServer(key), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s,%s,%s) error", mid, key, server))
return
}
user := fmt.Sprintf(MidFmt, appId, mid)
if err = conn.Send("SET", keyKeyUser(key), user); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(HSET %s,%s,%s) error", mid, appId, key))
return
}
if err = conn.Send("EXPIRE", keyKeyUser(key), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s,%s,%s) error", mid, appId, key))
return
}
if err = conn.Flush(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Flush() error")
return
}
for i := 0; i < n; i++ {
if _, err = conn.Receive(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Receive() error")
return
}
}
return
}
// ExpireMapping expire a mapping.
func (d *Dao) ExpireMapping(c context.Context, mid string, appId string, key string) (has bool, err error) {
conn := d.redis.Get()
defer conn.Close()
var n = 2
if mid != "" {
if err = conn.Send("EXPIRE", keyMidServer(appId, mid), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s) error", keyMidServer(appId, mid)))
return
}
n++
}
if err = conn.Send("EXPIRE", keyKeyServer(key), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s) error", keyKeyServer(key)))
return
}
if err = conn.Send("EXPIRE", keyKeyUser(key), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s) error", keyKeyServer(key)))
return
}
if err = conn.Flush(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Flush() error")
return
}
for i := 0; i < n; i++ {
if has, err = redis.Bool(conn.Receive()); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Receive() error")
return
}
}
return
}
func (d *Dao) DelMapping(c context.Context, mid string, appId string, key string) (has bool, err error) {
conn := d.redis.Get()
defer conn.Close()
var n = 2
if mid != "" {
if err = conn.Send("HDEL", keyMidServer(appId, mid), key); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(HDEL %s) error", keyMidServer(appId, mid)))
return
}
n++
}
if err = conn.Send("DEL", keyKeyServer(key)); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(DEL %s) error", keyKeyServer(key)))
return
}
if err = conn.Send("DEL", keyKeyUser(key)); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(DEL %s) error", keyKeyUser(key)))
return
}
if err = conn.Flush(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Flush() error")
return
}
for i := 0; i < n; i++ {
if has, err = redis.Bool(conn.Receive()); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Receive() error")
return
}
}
return
}
// ServersByKeys get a server by key.
func (d *Dao) ServersByKeys(c context.Context, keys []string) (res []string, err error) {
conn := d.redis.Get()
defer conn.Close()
var args []interface{}
for _, key := range keys {
args = append(args, keyKeyServer(key))
}
if res, err = redis.Strings(conn.Do("MGET", args...)); err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("conn.Do(MGET %v) error", args))
}
return
}
// KeysByMids get a key server by mid.
func (d *Dao) KeysByMids(c context.Context, appId string, mids []string) (ress map[string]string, olMids []string, err error) {
conn := d.redis.Get()
defer conn.Close()
ress = make(map[string]string)
for _, mid := range mids {
if err = conn.Send("HGETALL", keyMidServer(appId, mid)); err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("conn.Do(HGETALL %s) error", mid))
return
}
}
if err = conn.Flush(); err != nil {
log.Error().Err(err).Msg("conn.Flush() error")
return
}
for idx := 0; idx < len(mids); idx++ {
var (
res map[string]string
)
if res, err = redis.StringMap(conn.Receive()); err != nil {
log.Error().Err(err).Msg("conn.Receive() error")
return
}
if len(res) > 0 {
olMids = append(olMids, mids[idx])
}
for k, v := range res {
ress[k] = v
}
}
return
}
//groups
func (d *Dao) IncGroupServer(c context.Context, appId, key, server string, gid []string) (err error) {
conn := d.redis.Get()
defer conn.Close()
var n = 0
for _, g := range gid {
if err = conn.Send("ZINCRBY", keyGroupServer(appId, g), "1", server); err != nil {
log.Error().Str("key", key).Err(err).Msg(
fmt.Sprintf("conn.Send(ZINCRBY %s,%s,%s) error", keyGroupServer(appId, g), "1", server))
return
}
//if err = conn.Send("EXPIRE", keyGroupServer(appId, g), d.redisExpire); err != nil {
// log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s,%s,%s,%s)", appId, mid, key, server))
// return
//}
n++
}
if err = conn.Flush(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Flush() error")
return
}
for i := 0; i < n; i++ {
if _, err = conn.Receive(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Receive() error")
return
}
}
return
}
func (d *Dao) DecGroupServer(c context.Context, appId, key, server string, gid []string) (err error) {
conn := d.redis.Get()
defer conn.Close()
var n = 0
for _, g := range gid {
if err = conn.Send("ZINCRBY", keyGroupServer(appId, g), "-1", server); err != nil {
log.Error().Str("key", key).Err(err).Msg(
fmt.Sprintf("conn.Send(ZINCRBY %s,%s,%s) error", keyGroupServer(appId, g), "-1", server))
return
}
//if err = conn.Send("EXPIRE", keyGroupServer(appId, g), d.redisExpire); err != nil {
// log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s,%s,%s,%s)", appId, mid, key, server))
// return
//}
n++
}
if err = conn.Flush(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Flush() error")
return
}
for i := 0; i < n; i++ {
if _, err = conn.Receive(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Receive() error")
return
}
}
return
}
// KeysByMids get a key server by mid.
func (d *Dao) ServersByGid(c context.Context, appId string, gid string) (res []string, err error) {
conn := d.redis.Get()
defer conn.Close()
res = make([]string, 0)
ress := make(map[string]string)
if ress, err = redis.StringMap(conn.Do("ZRANGE", keyGroupServer(appId, gid), "0", "-1", "WITHSCORES")); err != nil {
log.Error().Str("appId", appId).Str("gid", gid).Err(err).Msg(
fmt.Sprintf("conn.DO(ZRANGE %s,%s,%s,%s) error", keyGroupServer(appId, gid), "0", "-1", "WITHSCORES"))
}
for k, _ := range ress {
res = append(res, k)
}
return
}

231
logic/dao/redis_test.go Normal file
View File

@@ -0,0 +1,231 @@
package dao
import (
"context"
"github.com/gomodule/redigo/redis"
"gopkg.in/Shopify/sarama.v1"
"testing"
"gitlab.33.cn/chat/im/logic/conf"
)
func TestDao_IncGroupServer(t *testing.T) {
type fields struct {
c *conf.Config
kafkaPub sarama.SyncProducer
redis *redis.Pool
redisExpire int32
}
type args struct {
c context.Context
appId string
key string
server string
gid []string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
// TODO: Add test cases.
{
name: "",
fields: fields{
c: testConf,
kafkaPub: nil,
redis: testRedis,
redisExpire: 0,
},
args: args{
c: nil,
appId: "dtalk",
key: "1",
server: "grpc://172.0.0.1:8080",
gid: []string{"1"},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Dao{
c: tt.fields.c,
kafkaPub: tt.fields.kafkaPub,
redis: tt.fields.redis,
redisExpire: tt.fields.redisExpire,
}
if err := d.IncGroupServer(tt.args.c, tt.args.appId, tt.args.key, tt.args.server, tt.args.gid); (err != nil) != tt.wantErr {
t.Errorf("IncGroupServer() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestDao_DecGroupServer(t *testing.T) {
type fields struct {
c *conf.Config
kafkaPub sarama.SyncProducer
redis *redis.Pool
redisExpire int32
}
type args struct {
c context.Context
appId string
key string
server string
gid []string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
// TODO: Add test cases.
{
name: "",
fields: fields{
c: testConf,
kafkaPub: nil,
redis: testRedis,
redisExpire: 0,
},
args: args{
c: nil,
appId: "dtalk",
key: "1",
server: "grpc://172.0.0.1:8080",
gid: []string{"1"},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Dao{
c: tt.fields.c,
kafkaPub: tt.fields.kafkaPub,
redis: tt.fields.redis,
redisExpire: tt.fields.redisExpire,
}
if err := d.DecGroupServer(tt.args.c, tt.args.appId, tt.args.key, tt.args.server, tt.args.gid); (err != nil) != tt.wantErr {
t.Errorf("IncGroupServer() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestDao_ServersByGid(t *testing.T) {
type fields struct {
c *conf.Config
kafkaPub sarama.SyncProducer
redis *redis.Pool
redisExpire int32
}
type args struct {
c context.Context
appId string
gid string
}
tests := []struct {
name string
fields fields
args args
wantRes []string
wantErr bool
}{
// TODO: Add test cases.
{
name: "",
fields: fields{
c: nil,
kafkaPub: nil,
redis: testRedis,
redisExpire: 0,
},
args: args{
c: nil,
appId: "dtalk",
gid: "1",
},
wantRes: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Dao{
c: tt.fields.c,
kafkaPub: tt.fields.kafkaPub,
redis: tt.fields.redis,
redisExpire: tt.fields.redisExpire,
}
gotRes, err := d.ServersByGid(tt.args.c, tt.args.appId, tt.args.gid)
if (err != nil) != tt.wantErr {
t.Errorf("ServersByGid() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i, re := range gotRes {
t.Logf("got %v:%v\n", i, re)
}
})
}
}
func TestDao_KeysByMids(t *testing.T) {
type fields struct {
c *conf.Config
kafkaPub sarama.SyncProducer
redis *redis.Pool
redisExpire int32
}
type args struct {
c context.Context
appId string
mids []string
}
tests := []struct {
name string
fields fields
args args
wantRess map[string]string
wantOlMids []string
wantErr bool
}{
{
name: "",
fields: fields{
c: testConf,
kafkaPub: nil,
redis: testRedis,
redisExpire: 0,
},
args: args{
c: context.Background(),
appId: "dtalk",
mids: []string{"1ygj6Un2UzL2rev6ub6NukWrGcKjW8LoG", "1LNaxM1BtkkRpWEGty8bDxmvWwRwxsCy1B", "14si8HGSBKN2B4Ps7QQJeRLvqWoXHX2NwB", ""},
},
wantRess: nil,
wantOlMids: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Dao{
c: tt.fields.c,
kafkaPub: tt.fields.kafkaPub,
redis: tt.fields.redis,
redisExpire: tt.fields.redisExpire,
}
gotRess, gotOlMids, err := d.KeysByMids(tt.args.c, tt.args.appId, tt.args.mids)
if err != nil {
t.Error(err)
}
t.Log(gotRess)
t.Log(gotOlMids)
})
}
}

187
logic/grpc/server.go Normal file
View File

@@ -0,0 +1,187 @@
package grpc
import (
"context"
"net"
"time"
"github.com/golang/protobuf/proto"
"gitlab.33.cn/chat/im-pkg/trace"
pb "gitlab.33.cn/chat/im/api/logic/grpc"
"gitlab.33.cn/chat/im/logic"
"gitlab.33.cn/chat/im/logic/conf"
"google.golang.org/grpc"
_ "google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/keepalive"
)
func New(c *conf.RPCServer, l *logic.Logic) *grpc.Server {
keepParams := grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: time.Duration(c.IdleTimeout),
MaxConnectionAgeGrace: time.Duration(c.ForceCloseWait),
Time: time.Duration(c.KeepAliveInterval),
Timeout: time.Duration(c.KeepAliveTimeout),
MaxConnectionAge: time.Duration(c.MaxLifeTime),
})
connectionTimeout := grpc.ConnectionTimeout(time.Duration(c.Timeout))
srv := grpc.NewServer(keepParams, connectionTimeout,
grpc.ChainUnaryInterceptor(
trace.OpentracingServerInterceptor,
))
pb.RegisterLogicServer(srv, &server{srv: l})
lis, err := net.Listen(c.Network, c.Addr)
if err != nil {
panic(err)
}
go func() {
if err := srv.Serve(lis); err != nil {
panic(err)
}
}()
return srv
}
type server struct {
pb.UnimplementedLogicServer
srv *logic.Logic
}
var _ pb.LogicServer = &server{}
// Connect connect a conn.
func (s *server) Connect(ctx context.Context, req *pb.ConnectReq) (*pb.ConnectReply, error) {
mid, appId, key, hb, err := s.srv.Connect(ctx, req.Server, req.Proto)
if err != nil {
return nil, err
}
return &pb.ConnectReply{Key: key, AppId: appId, Mid: mid, Heartbeat: hb}, nil
}
// Disconnect disconnect a conn.
func (s *server) Disconnect(ctx context.Context, req *pb.DisconnectReq) (*pb.Reply, error) {
_, err := s.srv.Disconnect(ctx, req.Key, req.Server)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true}, nil
}
// Heartbeat beartbeat a conn.
func (s *server) Heartbeat(ctx context.Context, req *pb.HeartbeatReq) (*pb.Reply, error) {
if err := s.srv.Heartbeat(ctx, req.Key, req.Server); err != nil {
return nil, err
}
return &pb.Reply{IsOk: true}, nil
}
// Receive receive a message from client.
func (s *server) Receive(ctx context.Context, req *pb.ReceiveReq) (*pb.Reply, error) {
if err := s.srv.Receive(ctx, req.Key, req.Proto); err != nil {
return nil, err
}
return &pb.Reply{IsOk: true}, nil
}
// Push push a biz message to client.
func (s *server) PushByMids(ctx context.Context, req *pb.MidsMsg) (*pb.Reply, error) {
reply, err := s.srv.PushByMids(ctx, req.AppId, req.ToIds, req.Msg)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) PushByKeys(ctx context.Context, req *pb.KeysMsg) (*pb.Reply, error) {
reply, err := s.srv.PushByKeys(ctx, req.AppId, req.ToKeys, req.Msg)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) PushGroup(ctx context.Context, req *pb.GroupMsg) (*pb.Reply, error) {
reply, err := s.srv.PushGroup(ctx, req.AppId, req.Group, req.Msg)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) JoinGroupsByKeys(ctx context.Context, req *pb.GroupsKey) (*pb.Reply, error) {
reply, err := s.srv.JoinGroupsByKeys(ctx, req.AppId, req.Keys, req.Gid)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) JoinGroupsByMids(ctx context.Context, req *pb.GroupsMid) (*pb.Reply, error) {
reply, err := s.srv.JoinGroupsByMids(ctx, req.AppId, req.Mids, req.Gid)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) LeaveGroupsByKeys(ctx context.Context, req *pb.GroupsKey) (*pb.Reply, error) {
reply, err := s.srv.LeaveGroupsByKeys(ctx, req.AppId, req.Keys, req.Gid)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) LeaveGroupsByMids(ctx context.Context, req *pb.GroupsMid) (*pb.Reply, error) {
reply, err := s.srv.LeaveGroupsByMids(ctx, req.AppId, req.Mids, req.Gid)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) DelGroups(ctx context.Context, req *pb.DelGroupsReq) (*pb.Reply, error) {
reply, err := s.srv.DelGroups(ctx, req.AppId, req.Gid)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}

426
logic/logic.go Normal file
View File

@@ -0,0 +1,426 @@
package logic
import (
"context"
"errors"
"fmt"
"time"
"github.com/golang/protobuf/proto"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"gitlab.33.cn/chat/im-pkg/trace"
comet "gitlab.33.cn/chat/im/api/comet/grpc"
"gitlab.33.cn/chat/im/common"
"gitlab.33.cn/chat/im/logic/auth"
"gitlab.33.cn/chat/im/logic/conf"
"gitlab.33.cn/chat/im/logic/dao"
"gitlab.33.cn/chat/im/naming"
xkey "gitlab.33.cn/chat/im/naming/balancer/key"
"google.golang.org/grpc"
"google.golang.org/grpc/resolver"
)
var ErrInvalidAuthReq = errors.New("ErrInvalidAuthReq")
var ErrInvalidAppId = errors.New("ErrInvalidAppId")
type Logic struct {
c *conf.Config
dao *dao.Dao
cometClient comet.CometClient
apps map[string]auth.Auth
}
func New(c *conf.Config) (l *Logic) {
cometClient, err := newCometClient(c)
if err != nil {
panic(err)
}
l = &Logic{
c: c,
dao: dao.New(c),
cometClient: cometClient,
apps: make(map[string]auth.Auth),
}
l.loadApps()
return l
}
func newCometClient(c *conf.Config) (comet.CometClient, error) {
rb := naming.NewResolver(c.Reg.RegAddrs, c.CometRPCClient.Schema)
resolver.Register(rb)
addr := fmt.Sprintf("%s:///%s", c.CometRPCClient.Schema, c.CometRPCClient.SrvName) // "schema://[authority]/service"
fmt.Println("rpc client call addr:", addr)
conn, err := common.NewGRPCConnWithKey(addr, time.Duration(c.CometRPCClient.Dial), grpc.WithUnaryInterceptor(trace.OpentracingClientInterceptor))
if err != nil {
panic(err)
}
return comet.NewCometClient(conn), err
}
func (l *Logic) loadApps() {
for _, app := range l.c.Apps {
newAuth, _ := auth.Load(app.AppId)
if newAuth == nil {
panic("exec auth not exist:" + app.AppId)
}
exec := newAuth(app.AuthUrl, time.Duration(app.Timeout))
l.apps[app.AppId] = exec
}
}
func (l *Logic) Close() {
l.dao.Close()
}
// Connect connected a conn.
func (l *Logic) Connect(c context.Context, server string, p *comet.Proto) (mid string, appId string, key string, hb int64, err error) {
var (
authMsg comet.AuthMsg
bytes []byte
)
log.Info().Str("appId", authMsg.AppId).Bytes("body", p.Body).Str("token", authMsg.Token).Msg("call Connect")
err = proto.Unmarshal(p.Body, &authMsg)
if err != nil {
return
}
if authMsg.AppId == "" || authMsg.Token == "" {
err = ErrInvalidAuthReq
return
}
log.Info().Str("appId", authMsg.AppId).Str("token", authMsg.Token).Msg("call auth")
appId = authMsg.AppId
authExec, _ := l.apps[authMsg.AppId]
if authExec == nil {
err = ErrInvalidAppId
return
}
mid, err = authExec.DoAuth(authMsg.Token)
if err != nil {
return
}
hb = int64(l.c.Node.Heartbeat) * int64(l.c.Node.HeartbeatMax)
key = uuid.New().String() //连接标识
if err = l.dao.AddMapping(c, mid, appId, key, server); err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("l.dao.AddMapping(%s,%s,%s) error", mid, key, server))
return
}
log.Info().Str("key", key).Str("mid", mid).Str("appId", appId).Str("comet", server).Msg("conn connected")
// notify biz user connected
bytes, err = proto.Marshal(p)
if err != nil {
return
}
err = l.dao.PublishMsg(c, appId, mid, comet.Op_Auth, key, bytes)
return
}
// Disconnect disconnect a conn.
func (l *Logic) Disconnect(c context.Context, key string, server string) (has bool, err error) {
appId, mid, err := l.dao.GetMember(c, key)
if err != nil {
return false, err
}
if has, err = l.dao.DelMapping(c, mid, appId, key); err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("l.dao.DelMapping(%s,%s) error", mid, appId))
return
}
log.Info().Str("key", key).Str("mid", mid).Str("appId", appId).Str("comet", server).Msg("conn disconnected")
// notify biz user disconnected
l.dao.PublishMsg(c, appId, mid, comet.Op_Disconnect, key, nil)
return
}
// Heartbeat heartbeat a conn.
func (l *Logic) Heartbeat(c context.Context, key string, server string) (err error) {
appId, mid, err := l.dao.GetMember(c, key)
if err != nil {
return err
}
var has bool
has, err = l.dao.ExpireMapping(c, mid, appId, key)
if err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("l.dao.ExpireMapping(%s,%s) error", mid, appId))
return
}
if !has {
if err = l.dao.AddMapping(c, mid, appId, key, server); err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("l.dao.AddMapping(%s,%s,%s) error", mid, appId, server))
return
}
}
log.Debug().Str("key", key).Str("mid", mid).Str("appId", appId).Str("comet", server).Msg("conn heartbeat")
return
}
// Receive receive a message from client.
func (l *Logic) Receive(c context.Context, key string, p *comet.Proto) (err error) {
appId, mid, err := l.dao.GetMember(c, key)
if err != nil {
return err
}
log.Debug().Str("appId", appId).Str("mid", mid).Interface("proto", p).Msg("receive proto")
msg, err := proto.Marshal(p)
if err != nil {
return err
}
return l.dao.PublishMsg(c, appId, mid, comet.Op(p.Op), key, msg)
}
// PushByMids Push push a biz message to client.
func (l *Logic) PushByMids(c context.Context, appId string, toIds []string, msg []byte) (reply *comet.PushMsgReply, err error) {
log.Debug().Str("appId", appId).Strs("mids", toIds).Msg("PushByMids start")
var p comet.Proto
err = proto.Unmarshal(msg, &p)
if err != nil {
return
}
server2keys, err := l.getServerByMids(c, appId, toIds)
if err != nil {
return
}
for server, keys := range server2keys {
log.Debug().Strs("keys", keys).Str("server", server).Msg("PushByMids pushing")
reply, err = l.cometClient.PushMsg(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.PushMsgReq{Keys: keys, Proto: &p})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("PushByMids l.cometClient.PushMsg")
}
}
return
}
// PushByKeys Push push a biz message to client.
func (l *Logic) PushByKeys(c context.Context, appId string, keys []string, msg []byte) (reply *comet.PushMsgReply, err error) {
log.Debug().Str("appId", appId).Strs("keys", keys).Msg("PushByKeys start")
var p comet.Proto
err = proto.Unmarshal(msg, &p)
if err != nil {
return
}
server2keys, err := l.getServerByKeys(c, keys)
if err != nil {
return
}
for server, keys := range server2keys {
log.Debug().Strs("keys", keys).Str("server", server).Msg("PushByMids pushing")
reply, err = l.cometClient.PushMsg(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.PushMsgReq{Keys: keys, Proto: &p})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("PushByMids l.cometClient.PushMsg")
}
}
return
}
// PushGroup Push push a biz message to client.
func (l *Logic) PushGroup(c context.Context, appId string, group string, msg []byte) (reply *comet.BroadcastGroupReply, err error) {
log.Debug().Str("appId", appId).Str("group", group).Msg("PushGroup start")
var p comet.Proto
err = proto.Unmarshal(msg, &p)
if err != nil {
return
}
cometServers, err := l.dao.ServersByGid(c, appId, group)
if err != nil {
return
}
for _, server := range cometServers {
log.Debug().Str("appId", appId).Str("group", group).Str("server", server).Msg("PushGroup pushing")
reply, err = l.cometClient.BroadcastGroup(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.BroadcastGroupReq{GroupID: appGid(appId, group), Proto: &p})
if err != nil {
log.Error().Err(err).Str("appId", appId).Str("group", group).Str("server", server).Msg("PushGroup l.cometClient.BroadcastGroup")
}
}
return
}
// JoinGroupsByMids Push push a biz message to client.
func (l *Logic) JoinGroupsByMids(c context.Context, appId string, mids []string, gids []string) (reply *comet.JoinGroupsReply, err error) {
log.Debug().Str("appId", appId).Strs("mids", mids).Strs("group", gids).Msg("JoinGroupsByMids start")
server2keys, err := l.getServerByMids(c, appId, mids)
if err != nil {
return
}
for server, keys := range server2keys {
for _, key := range keys {
_ = l.dao.IncGroupServer(c, appId, key, server, gids)
}
log.Debug().Strs("keys", keys).Str("server", server).Msg("JoinGroupsByMids pushing")
reply, err = l.cometClient.JoinGroups(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.JoinGroupsReq{Keys: keys, Gid: appGids(appId, gids)})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("JoinGroupsByMids l.cometClient.JoinGroups")
}
}
return
}
// JoinGroupsByKeys Push push a biz message to client.
func (l *Logic) JoinGroupsByKeys(c context.Context, appId string, keys []string, gids []string) (reply *comet.JoinGroupsReply, err error) {
log.Debug().Str("appId", appId).Strs("keys", keys).Strs("group", gids).Msg("JoinGroupsByKeys start")
server2keys, err := l.getServerByKeys(c, keys)
if err != nil {
return
}
for server, keys := range server2keys {
for _, key := range keys {
_ = l.dao.IncGroupServer(c, appId, key, server, gids)
}
log.Debug().Strs("keys", keys).Str("server", server).Msg("JoinGroupsByKeys pushing")
reply, err = l.cometClient.JoinGroups(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.JoinGroupsReq{Keys: keys, Gid: appGids(appId, gids)})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("JoinGroupsByKeys l.cometClient.JoinGroups")
}
}
return
}
// LeaveGroupsByMids Push push a biz message to client.
func (l *Logic) LeaveGroupsByMids(c context.Context, appId string, mids []string, gids []string) (reply *comet.LeaveGroupsReply, err error) {
log.Debug().Str("appId", appId).Strs("mids", mids).Strs("group", gids).Msg("LeaveGroupsByMids start")
server2keys, err := l.getServerByMids(c, appId, mids)
if err != nil {
return
}
for server, keys := range server2keys {
for _, key := range keys {
_ = l.dao.DecGroupServer(c, appId, key, server, gids)
}
log.Debug().Strs("keys", keys).Str("server", server).Msg("LeaveGroupsByMids pushing")
reply, err = l.cometClient.LeaveGroups(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.LeaveGroupsReq{Keys: keys, Gid: appGids(appId, gids)})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("LeaveGroupsByMids l.cometClient.LeaveGroups")
}
}
return
}
// LeaveGroupsByKeys Push push a biz message to client.
func (l *Logic) LeaveGroupsByKeys(c context.Context, appId string, keys []string, gids []string) (reply *comet.LeaveGroupsReply, err error) {
log.Debug().Str("appId", appId).Strs("keys", keys).Strs("group", gids).Msg("LeaveGroupsByKeys start")
server2keys, err := l.getServerByKeys(c, keys)
if err != nil {
return
}
for server, keys := range server2keys {
for _, key := range keys {
_ = l.dao.DecGroupServer(c, appId, key, server, gids)
}
log.Debug().Strs("keys", keys).Str("server", server).Msg("LeaveGroupsByKeys pushing")
reply, err = l.cometClient.LeaveGroups(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.LeaveGroupsReq{Keys: keys, Gid: appGids(appId, gids)})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("LeaveGroupsByKeys l.cometClient.LeaveGroups")
}
}
return
}
// DelGroups Push push a biz message to client.
func (l *Logic) DelGroups(c context.Context, appId string, gids []string) (reply *comet.DelGroupsReply, err error) {
log.Debug().Str("appId", appId).Strs("groups", gids).Msg("DelGroups start")
server2gids := make(map[string][]string)
for _, gid := range gids {
cometServers, err := l.dao.ServersByGid(c, appId, gid)
if err != nil {
continue
}
for _, server := range cometServers {
server2gids[server] = append(server2gids[server], gid)
}
}
for server, gids := range server2gids {
log.Debug().Str("appId", appId).Interface("gids", gids).Str("server", server).Msg("DelGroups pushing")
reply, err = l.cometClient.DelGroups(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.DelGroupsReq{Gid: appGids(appId, gids)})
if err != nil {
log.Error().Err(err).Str("appId", appId).Strs("groups", gids).Str("server", server).Msg("DelGroups l.cometClient.DelGroups")
}
}
return
}
// getServerByMids 通过 appid 和 mids 找到所在的 comet servers
func (l *Logic) getServerByMids(c context.Context, appId string, mids []string) (map[string][]string, error) {
ress, _, err := l.dao.KeysByMids(c, appId, mids)
if err != nil {
return nil, err
}
server2keys := make(map[string][]string)
for k, s := range ress {
server2keys[s] = append(server2keys[s], k)
}
return server2keys, nil
}
// getServerByKeys 通过 keys 找到所在的 comet servers
func (l *Logic) getServerByKeys(c context.Context, keys []string) (map[string][]string, error) {
server2keys := make(map[string][]string)
for _, key := range keys {
server, _ := l.dao.GetServer(c, key)
server2keys[server] = append(server2keys[server], key)
}
return server2keys, nil
}
func appGid(appId, gid string) string {
return fmt.Sprintf("%s_%s", appId, gid)
}
func appGids(appId string, gids []string) []string {
res := make([]string, 0, len(gids))
for _, g := range gids {
res = append(res, appGid(appId, g))
}
return res
}
func convertAddress(addr string) string {
// todo
if len(addr) == 0 {
return addr
}
switch addr[0] {
case 'g':
return addr[7:]
default:
return addr
}
}