diff --git a/app/app.go b/app/app.go new file mode 100644 index 0000000..5b04f71 --- /dev/null +++ b/app/app.go @@ -0,0 +1,61 @@ +package app + +import ( + "common/discover" + "common/log" + "fmt" + "gateway/config" + "github.com/judwhite/go-svc" +) + +type Program struct { + moduleList []Module // 模块列表 +} + +type Module interface { + init() error + start() error + stop() error +} + +func (p *Program) Init(_ svc.Environment) error { + p.moduleList = append(p.moduleList, &ModuleBase{}) + p.moduleList = append(p.moduleList, &ModuleDB{}) + p.moduleList = append(p.moduleList, &ModulePrometheus{}) + p.moduleList = append(p.moduleList, &ModuleWebServer{}) + p.moduleList = append(p.moduleList, &ModuleWebsocketServer{}) + p.moduleList = append(p.moduleList, &ModuleGrpcServer{}) + + for _, module := range p.moduleList { + if err := module.init(); err != nil { + return err + } + } + log.Infof(fmt.Sprintf("%v Init successful...", config.Get().App.Name)) + return nil +} + +func (p *Program) Start() error { + for _, module := range p.moduleList { + if err := module.start(); err != nil { + return err + } + } + discover.Listen() + + log.Infof(fmt.Sprintf("%v Start successful...", config.Get().App.Name)) + return nil +} + +func (p *Program) Stop() error { + discover.Close() + for i := len(p.moduleList) - 1; i >= 0; i-- { + module := p.moduleList[i] + if err := module.stop(); err != nil { + log.Errorf("module stop error: %v", err) + } + } + + log.Infof(fmt.Sprintf("%v Stop successful...", config.Get().App.Name)) + return nil +} diff --git a/app/base.go b/app/base.go new file mode 100644 index 0000000..c59624f --- /dev/null +++ b/app/base.go @@ -0,0 +1,33 @@ +package app + +import ( + "common/log" + "common/utils" + "gateway/config" + "math/rand" +) + +// ModuleBase 基础模块,或者一些零散的模块 +type ModuleBase struct { +} + +func (p *ModuleBase) init() error { + // 配置 + if err := config.LoadConfig(); err != nil { + return err + } + cfg := config.Get() + // 日志 + log.Init(cfg.Log.Debug, cfg.Log.MaxSize, cfg.Log.MaxBackups, cfg.Log.MaxAge, cfg.Log.Level) + // 雪花 + utils.InitSnowflake(int64(rand.Intn(1000))) + return nil +} + +func (p *ModuleBase) start() error { + return nil +} + +func (p *ModuleBase) stop() error { + return nil +} diff --git a/app/db.go b/app/db.go new file mode 100644 index 0000000..9cf44ca --- /dev/null +++ b/app/db.go @@ -0,0 +1,23 @@ +package app + +import ( + "common/db" + "gateway/config" +) + +// ModuleDB 数据库模块 +type ModuleDB struct { + dbModule *db.ModuleDB +} + +func (p *ModuleDB) init() error { + return p.dbModule.Init(config.Get().DB) +} + +func (p *ModuleDB) start() error { + return nil +} + +func (p *ModuleDB) stop() error { + return p.dbModule.Stop() +} diff --git a/app/grpc.go b/app/grpc.go new file mode 100644 index 0000000..2a08f80 --- /dev/null +++ b/app/grpc.go @@ -0,0 +1,27 @@ +package app + +import ( + "common/net/grpc/service" + "gateway/config" + "gateway/internal/grpc_server/server" +) + +// ModuleGrpcServer Grpc服务模块 +type ModuleGrpcServer struct { + server service.IService +} + +func (m *ModuleGrpcServer) init() error { + return nil +} + +func (m *ModuleGrpcServer) start() error { + m.server = server.NewServer(config.Get().Serve.Grpc.TTL) + m.server.Init(config.Get().Serve.Grpc.Address, config.Get().Serve.Grpc.Port) + return nil +} + +func (m *ModuleGrpcServer) stop() error { + m.server.Close() + return nil +} diff --git a/app/prometheus.go b/app/prometheus.go new file mode 100644 index 0000000..f864925 --- /dev/null +++ b/app/prometheus.go @@ -0,0 +1,47 @@ +package app + +import ( + "common/log" + "context" + "errors" + "fmt" + "gateway/config" + "github.com/prometheus/client_golang/prometheus/promhttp" + "net/http" + "sync" +) + +// ModulePrometheus 普罗米修斯模块 +type ModulePrometheus struct { + wg *sync.WaitGroup + server *http.Server +} + +func (m *ModulePrometheus) init() error { + m.wg = &sync.WaitGroup{} + return nil +} + +func (m *ModulePrometheus) start() error { + m.wg.Add(1) + go func() { + defer m.wg.Done() + m.server = &http.Server{ + Addr: fmt.Sprintf("%v:%v", config.Get().Metric.Prometheus.Address, config.Get().Metric.Prometheus.Port), + Handler: promhttp.Handler(), + } + if err := m.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Errorf("prometheus server failed: %v", err.Error()) + } + log.Infof("prometheus server stop.") + }() + return nil +} + +func (m *ModulePrometheus) stop() error { + if err := m.server.Shutdown(context.Background()); err != nil { + log.Errorf("stop prometheus server failed: %v", err) + } + m.wg.Wait() + return nil +} diff --git a/app/web.go b/app/web.go new file mode 100644 index 0000000..1ea66de --- /dev/null +++ b/app/web.go @@ -0,0 +1,47 @@ +package app + +import ( + "common/log" + "context" + "errors" + "fmt" + "gateway/config" + "gateway/internal/net/http_gateway" + "net/http" + "sync" +) + +// ModuleWebServer Web服务模块 +type ModuleWebServer struct { + wg *sync.WaitGroup + server *http.Server +} + +func (m *ModuleWebServer) init() error { + m.wg = &sync.WaitGroup{} + return nil +} + +func (m *ModuleWebServer) start() error { + m.wg.Add(1) + go func() { + defer m.wg.Done() + m.server = &http.Server{ + Addr: fmt.Sprintf("%v:%v", config.Get().Serve.Http.Address, config.Get().Serve.Http.Port), + Handler: http_gateway.InitRouter(), + } + if err := m.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Errorf("http server failed: %v", err.Error()) + } + log.Infof("http server stop.") + }() + return nil +} + +func (m *ModuleWebServer) stop() error { + if err := m.server.Shutdown(context.Background()); err != nil { + log.Errorf("stop http server failed: %v", err) + } + m.wg.Wait() + return nil +} diff --git a/app/websocket.go b/app/websocket.go new file mode 100644 index 0000000..e82f012 --- /dev/null +++ b/app/websocket.go @@ -0,0 +1,56 @@ +package app + +import ( + "common/log" + "common/net/socket/websocket" + "fmt" + "gateway/config" + "gateway/internal/net/ws_gateway" + "github.com/panjf2000/gnet/v2" + "sync" + "time" +) + +// ModuleWebsocketServer Websocket服务模块 +type ModuleWebsocketServer struct { + wg *sync.WaitGroup + server *websocket.WSServer +} + +func (m *ModuleWebsocketServer) init() error { + m.wg = &sync.WaitGroup{} + m.server = websocket.NewWSServer( + &ws_gateway.GatewayWsServer{}, + log.GetLogger().Named("ws_server"), + 5*time.Second, + ) + return nil +} + +func (m *ModuleWebsocketServer) start() error { + m.wg.Add(1) + go func() { + defer m.wg.Done() + _ = m.server.Run( + fmt.Sprintf("tcp4://0.0.0.0:%v", config.Get().Serve.Socket.Web.Port), + true, + 0, + gnet.TCPNoDelay, + 64*1024, + 64*1024, + true, + false, + true, + log.GetLogger().Named("GNET"), + ) + }() + return nil +} + +func (m *ModuleWebsocketServer) stop() error { + if err := m.server.Stop(); err != nil { + log.Errorf("stop websocket server failed: %v", err) + } + m.wg.Wait() + return nil +} diff --git a/config/config.dev.yaml b/config/config.dev.yaml new file mode 100644 index 0000000..804ae4c --- /dev/null +++ b/config/config.dev.yaml @@ -0,0 +1,42 @@ +app: + name: "gateway-dev" + +log: + debug: true + level: "debug" + maxSize: 10 + maxBackups: 100 + maxAge: 7 + +metric: + prometheus: + address: "0.0.0.0" + port: 8504 + +db: + etcd: + endpoints: [ "10.0.40.9:2379" ] + redis: + addr: "47.108.184.184:6379" + password: "lQ7aM8oB6lK0iD5k" + db: 0 + +serve: + grpc: + address: "10.0.40.199" + port: 8500 + ttl: 20 + socket: + web: + address: "0.0.0.0" + port: 8501 + raw: + address: "0.0.0.0" + port: 8502 + http: + address: "0.0.0.0" + port: 8503 + +auth: + secret: "bMa3mU4oCsX2KBex5o7GzwSnACpumFq3SdlDXYZgVTU=" + expire: 259200 diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..9d14ea8 --- /dev/null +++ b/config/config.go @@ -0,0 +1,39 @@ +package config + +import "common/config" + +const ( + path = "./config" + KeyUserAccessToken = "user:access:%v" + KeyUserRefreshToken = "user:refresh:%v" +) + +// PublicPaths 不需要鉴权的接口,硬编码注册 +var PublicPaths = []string{ + "/user/info", +} + +type Config struct { + App *config.AppConfig `yaml:"app"` + Log *config.LogConfig `yaml:"log"` + Metric *config.MetricConfig `yaml:"metric"` + DB *config.DBConfig `yaml:"db"` + Serve *config.ServeConfig `yaml:"serve"` + Auth *struct { + Secret string `yaml:"secret"` + Expire int64 `yaml:"expire"` + } +} + +var cfg *Config + +// LoadConfig 加载应用配置 +func LoadConfig() error { + c, err := config.LoadConfig(path, cfg) + cfg = c + return err +} + +func Get() *Config { + return cfg +} diff --git a/config/config.prod.yaml b/config/config.prod.yaml new file mode 100644 index 0000000..a944c9e --- /dev/null +++ b/config/config.prod.yaml @@ -0,0 +1,42 @@ +app: + name: "gateway-prod" + +log: + debug: false + level: "debug" + maxSize: 10 + maxBackups: 100 + maxAge: 7 + +metric: + prometheus: + address: "0.0.0.0" + port: 8504 + +db: + etcd: + endpoints: [ "172.18.28.0:2379" ] + redis: + addr: "172.18.28.0:6379" + password: "lQ7aM8oB6lK0iD5k" + db: 0 + +serve: + grpc: + address: "172.18.28.0" + port: 8500 + ttl: 20 + socket: + web: + address: "0.0.0.0" + port: 8501 + raw: + address: "0.0.0.0" + port: 8502 + http: + address: "0.0.0.0" + port: 8503 + +auth: + secret: "bMa3mU4oCsX2KBex5o7GzwSnACpumFq3SdlDXYZgVTU=" + expire: 259200 \ No newline at end of file diff --git a/deploy/Dockerfile b/deploy/Dockerfile new file mode 100644 index 0000000..d18484e --- /dev/null +++ b/deploy/Dockerfile @@ -0,0 +1,12 @@ +FROM alpine:3.23.2 + +RUN apk add --no-cache tzdata && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo "Asia/Shanghai" > /etc/timezone + +COPY server-gateway /app/server-gateway +COPY ../config /app/config +RUN chmod 777 /app/server-gateway + +WORKDIR /app +CMD ["./server-gateway"] \ No newline at end of file diff --git a/deploy/Jenkinsfile b/deploy/Jenkinsfile new file mode 100644 index 0000000..c3a6655 --- /dev/null +++ b/deploy/Jenkinsfile @@ -0,0 +1,139 @@ +pipeline { + agent none + + environment { + // 仓库 + REPO_URL = 'http://47.108.184.184/gitea/HuXiaoHei/Game.git' + REPO_CREDENTIALS_ID = '80805ba2-f4ac-4d84-aee6-d4cce5fc0a96' + + // Registry + REGISTRY_URL = 'registry.hlsq.asia' + REGISTRY_CREDENTIALS_ID = '0d79fc0b-a150-470b-bd0d-2639b2826031' + + // 部署目标服务器 + SERVER_HOST = 'www.hlsq.asia' + SERVER_USER = 'root' + SSH_CREDENTIALS_ID = '10e0830d-4d03-4879-9ee4-03a4c55513ad' + + // 基础信息 + APP_NAME = 'server-gateway' + GO_MOD_CACHE_DIR = '/home/pi/Desktop/docker/jenkins/caches/go-mod' + GO_BUILD_CACHE_DIR = '/home/pi/Desktop/docker/jenkins/caches/go-build' + } + + options { + skipDefaultCheckout true + } + + stages { + stage('Checkout') { + agent any + steps { + checkout([ + $class: 'GitSCM', + branches: [[name: '*/master']], + doGenerateSubmoduleConfigurations: false, + extensions: [], + userRemoteConfigs: [[ + credentialsId: env.REPO_CREDENTIALS_ID, + url: env.REPO_URL + ]] + ]) + // 立刻保存Git的Commit,避免并发问题 + script { + def shortCommit = sh(script: 'git rev-parse --short=8 HEAD', returnStdout: true).trim() + env.IMAGE_TAG = "${env.REGISTRY_URL}/${env.APP_NAME}:${shortCommit}" + echo "Checked out commit: ${env.IMAGE_TAG}" + } + } + } + + stage('Build Go Binary') { + agent { + docker { + image 'golang:1.23.1-alpine' + reuseNode true + args '-u root:root -v $GO_MOD_CACHE_DIR:/go/pkg/mod -v $GO_BUILD_CACHE_DIR:/root/.cache/go-build' + } + } + steps { + dir('Server/gateway') { + sh """ + export GOPROXY=https://goproxy.cn,direct + export CGO_ENABLED=0 + export GOOS=linux + export GOARCH=amd64 + + go build -o ${env.APP_NAME} . + """ + } + } + } + + stage('Push Docker Image') { + agent any + steps { + dir('Server/gateway') { + script { + withCredentials([usernamePassword( + credentialsId: env.REGISTRY_CREDENTIALS_ID, + usernameVariable: 'DOCKER_USER', + passwordVariable: 'DOCKER_PASS' + )]) { + sh """ + echo "$DOCKER_PASS" | docker login --username "$DOCKER_USER" --password-stdin ${env.REGISTRY_URL} + docker build -t ${env.IMAGE_TAG} . + docker push ${env.IMAGE_TAG} + docker rmi ${env.IMAGE_TAG} + docker logout ${env.REGISTRY_URL} + """ + } + } + } + } + } + + stage('Deploy to Server') { + agent any + steps { + script { + withCredentials([ + usernamePassword( + credentialsId: env.REGISTRY_CREDENTIALS_ID, + usernameVariable: 'DOCKER_USER', + passwordVariable: 'DOCKER_PASS' + ) + ]) { + sshagent (credentials: [env.SSH_CREDENTIALS_ID]) { + sh """ + ssh -o StrictHostKeyChecking=no ${env.SERVER_USER}@${env.SERVER_HOST} << EOF + set -e + echo '${DOCKER_PASS}' | docker login --username '${DOCKER_USER}' --password-stdin ${env.REGISTRY_URL} + docker pull ${env.IMAGE_TAG} + docker stop ${env.APP_NAME} 2>/dev/null || true + docker rm ${env.APP_NAME} 2>/dev/null || true + docker run -d \\ + --name ${env.APP_NAME} \\ + --restart unless-stopped \\ + -p 8500-8504:8500-8504 \\ + --env XH_G_ENV=prod \\ + -v /root/server/logs/gateway_log/:/app/logs \\ + ${env.IMAGE_TAG} + docker logout ${env.REGISTRY_URL} + """ + } + } + } + } + } + } + + post { + success { + echo '✅ 构建、推送镜像与部署成功!' + } + failure { + echo '❌ 构建失败,请检查日志。' + } + } +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9c6aaa9 --- /dev/null +++ b/go.mod @@ -0,0 +1,95 @@ +module gateway + +go 1.23.1 + +require ( + common v0.0.0-00010101000000-000000000000 + github.com/gin-contrib/cors v1.7.6 + github.com/gin-gonic/gin v1.11.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 + github.com/judwhite/go-svc v1.2.1 + github.com/prometheus/client_golang v1.20.5 + go.uber.org/zap v1.27.0 + google.golang.org/grpc v1.71.1 + google.golang.org/protobuf v1.36.9 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bwmarrin/snowflake v0.3.0 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.4.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/natefinch/lumberjack v2.0.0+incompatible // indirect + github.com/panjf2000/ants/v2 v2.11.3 // indirect + github.com/panjf2000/gnet/v2 v2.9.7 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/redis/go-redis/v9 v9.10.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/spf13/viper v1.21.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + go.etcd.io/etcd/api/v3 v3.6.1 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.1 // indirect + go.etcd.io/etcd/client/v3 v3.6.1 // indirect + go.uber.org/mock v0.5.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/mod v0.26.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.35.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/gorm v1.31.1 // indirect +) + +replace common => ../common diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e914118 --- /dev/null +++ b/go.sum @@ -0,0 +1,264 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= +github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= +github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= +github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/judwhite/go-svc v1.2.1 h1:a7fsJzYUa33sfDJRF2N/WXhA+LonCEEY8BJb1tuS5tA= +github.com/judwhite/go-svc v1.2.1/go.mod h1:mo/P2JNX8C07ywpP9YtO2gnBgnUiFTHqtsZekJrUuTk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= +github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= +github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= +github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= +github.com/panjf2000/gnet/v2 v2.9.1 h1:bKewICy/0xnQ9PMzNaswpe/Ah14w1TrRk91LHTcbIlA= +github.com/panjf2000/gnet/v2 v2.9.1/go.mod h1:WQTxDWYuQ/hz3eccH0FN32IVuvZ19HewEWx0l62fx7E= +github.com/panjf2000/gnet/v2 v2.9.7 h1:6zW7Jl3oAfXwSuh1PxHLndoL2MQRWx0AJR6aaQjxUgA= +github.com/panjf2000/gnet/v2 v2.9.7/go.mod h1:WQTxDWYuQ/hz3eccH0FN32IVuvZ19HewEWx0l62fx7E= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +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/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs= +github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/etcd/api/v3 v3.6.1 h1:yJ9WlDih9HT457QPuHt/TH/XtsdN2tubyxyQHSHPsEo= +go.etcd.io/etcd/api/v3 v3.6.1/go.mod h1:lnfuqoGsXMlZdTJlact3IB56o3bWp1DIlXPIGKRArto= +go.etcd.io/etcd/client/pkg/v3 v3.6.1 h1:CxDVv8ggphmamrXM4Of8aCC8QHzDM4tGcVr9p2BSoGk= +go.etcd.io/etcd/client/pkg/v3 v3.6.1/go.mod h1:aTkCp+6ixcVTZmrJGa7/Mc5nMNs59PEgBbq+HCmWyMc= +go.etcd.io/etcd/client/v3 v3.6.1 h1:KelkcizJGsskUXlsxjVrSmINvMMga0VWwFF0tSPGEP0= +go.etcd.io/etcd/client/v3 v3.6.1/go.mod h1:fCbPUdjWNLfx1A6ATo9syUmFVxqHH9bCnPLBZmnLmMY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +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= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/internal/grpc_server/server/server.go b/internal/grpc_server/server/server.go new file mode 100644 index 0000000..89dc7a2 --- /dev/null +++ b/internal/grpc_server/server/server.go @@ -0,0 +1,58 @@ +package server + +import ( + "common/log" + "common/proto/sc/sc_pb" + "common/proto/ss/grpc_pb" + "gateway/internal/handler/ws_handler" + "google.golang.org/protobuf/proto" + "sync" +) + +func (s *Server) ToClient(server grpc_pb.Gateway_ToClientServer) error { + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + defer func() { + if err := recover(); err != nil { + log.Errorf("Action panic: %v", err) + } + }() + for { + if args, err := server.Recv(); err != nil { + return + } else { + if args.USN == -1 { + + //utils.WorkerPool(ws_handler.UserMgr.GetAllInterface(), func(task interface{}) { + // client := task.(*ws_handler.Client) + // client.WriteBytes(sc_pb.MessageID(args.MessageID), args.Payload) + //}) + + data, err := proto.Marshal(&sc_pb.Message{ + ID: sc_pb.MessageID(args.MessageID), + Payload: args.Payload, + }) + if err != nil { + log.Errorf("ToClient proto.Marshal error: %v", err) + continue + } + for _, client := range ws_handler.UserMgr.GetAll() { + client.WriteBytesPreMarshal(data) + } + + //for _, client := range ws_handler.UserMgr.GetAll() { + // client.WriteBytes(sc_pb.MessageID(args.MessageID), args.Payload) + //} + } else { + if client := ws_handler.UserMgr.GetByUSN(args.USN); client != nil { + client.WriteBytes(sc_pb.MessageID(args.MessageID), args.Payload) + } + } + } + } + }() + wg.Wait() + return server.SendAndClose(&grpc_pb.ToClientResp{}) +} diff --git a/internal/grpc_server/server/server_init.go b/internal/grpc_server/server/server_init.go new file mode 100644 index 0000000..c677208 --- /dev/null +++ b/internal/grpc_server/server/server_init.go @@ -0,0 +1,34 @@ +package server + +import ( + "common/discover/common" + "common/net/grpc/service" + "common/proto/ss/grpc_pb" + "gateway/internal/handler/ws_handler" + "google.golang.org/grpc" +) + +type Server struct { + grpc_pb.UnimplementedGatewayServer + service.Base +} + +func NewServer(ttl int64) *Server { + s := &Server{ + Base: service.Base{ + Target: common.KeyDiscoverGateway, + EtcdTTL: ttl, + }, + } + s.Base.OnInit = s.OnInit + s.Base.OnClose = s.OnClose + return s +} + +func (s *Server) OnInit(serve *grpc.Server) { + ws_handler.GatewaySID = s.SID + grpc_pb.RegisterGatewayServer(serve, s) +} + +func (s *Server) OnClose() { +} diff --git a/internal/grpc_server/stream_client/scene.go b/internal/grpc_server/stream_client/scene.go new file mode 100644 index 0000000..90e6ac2 --- /dev/null +++ b/internal/grpc_server/stream_client/scene.go @@ -0,0 +1,65 @@ +package stream_client + +import ( + "common/log" + "common/net/grpc/service" + "context" + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" +) + +var sceneServerM map[int64]map[SceneFun]grpc.ClientStream // map[sid]map[方法名]流连接 + +type SceneFun int + +const ( + FunAction SceneFun = iota +) + +func init() { + sceneServerM = make(map[int64]map[SceneFun]grpc.ClientStream) +} + +func findSceneBySID(sid int64, fun SceneFun) (grpc.ClientStream, error) { + g := sceneServerM[sid] + if g == nil { + g = make(map[SceneFun]grpc.ClientStream) + sceneServerM[sid] = g + } + sceneLink := g[fun] + if sceneLink == nil { + sceneClient, err := service.SceneNewClient(sid) + if err != nil { + log.Errorf("cannot find sceneClient: %v", err) + return nil, err + } + var link grpc.ClientStream + switch fun { + case FunAction: + link, err = sceneClient.Action(context.Background()) + } + if err != nil { + log.Errorf("findSceneBySID %v err: %v, sid: %v", fun, err, sid) + return nil, err + } + g[fun] = link + sceneLink = link + } + return sceneLink, nil +} + +func SendMessageToScene(sid int64, fun SceneFun, msg proto.Message, re ...bool) error { + stream, err := findSceneBySID(sid, fun) + if err != nil { + return err + } + if err = stream.SendMsg(msg); err != nil { + if re == nil || !re[0] { + _ = stream.CloseSend() + delete(sceneServerM[sid], fun) + return SendMessageToScene(sid, fun, msg, true) + } + return err + } + return nil +} diff --git a/internal/handler/http_handler/login.go b/internal/handler/http_handler/login.go new file mode 100644 index 0000000..897ce5f --- /dev/null +++ b/internal/handler/http_handler/login.go @@ -0,0 +1,118 @@ +package http_handler + +import ( + "common/db/redis" + "common/log" + "common/net/grpc/service" + "common/net/http/http_resp" + "common/proto/ss/grpc_pb" + "common/utils" + "context" + "fmt" + "gateway/config" + "github.com/gin-gonic/gin" + "time" +) + +// 这个模块处理用户登录 + +type LoginReq struct { + Phone string `json:"phone" binding:"required,min=1"` + Code string `json:"code" binding:"required,min=1"` +} + +type LoginResp struct { + USN int64 `json:"usn"` + Name string `json:"name"` + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` +} + +func Login(c *gin.Context) { + req := &LoginReq{} + if err := c.ShouldBindJSON(req); err != nil { + http_resp.JsonBadRequest(c) + return + } + client, err := service.UserNewClientLB() + if err != nil { + log.Errorf("Login UserNewClientLB error: %v", err) + http_resp.JsonOK(c, http_resp.Error(http_resp.Failed)) + return + } + login, err := client.Login(c, &grpc_pb.LoginReq{ + Phone: req.Phone, + Code: req.Code, + }) + if err != nil { + log.Errorf("Login Login error: %v", err) + http_resp.JsonOK(c, http_resp.Error(http_resp.Failed)) + return + } + + at, rt, err := genToken(c, login.USN) + http_resp.JsonOK(c, http_resp.Success(&LoginResp{ + USN: login.USN, + Name: login.Name, + AccessToken: at, + RefreshToken: rt, + })) +} + +type RefreshTokenReq struct { + RefreshToken string `json:"refreshToken" binding:"required,min=1"` +} + +type RefreshTokenResp struct { + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` +} + +func RefreshToken(c *gin.Context) { + req := &RefreshTokenReq{} + if err := c.ShouldBindJSON(req); err != nil { + http_resp.JsonBadRequest(c) + return + } + claims, err := utils.ParseToken(req.RefreshToken, config.Get().Auth.Secret) + if err != nil { + http_resp.JsonOK(c, http_resp.Error(http_resp.TokenInvalid)) + return + } + if redis.GetClient().Get(c, fmt.Sprintf(config.KeyUserRefreshToken, claims.USN)).String() != req.RefreshToken { + http_resp.JsonOK(c, http_resp.Error(http_resp.TokenInvalid)) + return + } + at, rt, err := genToken(c, claims.USN) + if err != nil { + log.Errorf("RefreshToken genToken error: %v, usn: %v", err, claims.USN) + http_resp.JsonOK(c, http_resp.Error(http_resp.Failed)) + return + } + + http_resp.JsonOK(c, http_resp.Success(&RefreshTokenResp{ + AccessToken: at, + RefreshToken: rt, + })) +} + +func genToken(ctx context.Context, usn int64) (string, string, error) { + at, err := genTokenOne(ctx, config.KeyUserAccessToken, usn, 2*time.Hour) + if err != nil { + return "", "", err + } + rt, err := genTokenOne(ctx, config.KeyUserRefreshToken, usn, 3*24*time.Hour) + if err != nil { + return "", "", err + } + return at, rt, nil +} + +func genTokenOne(ctx context.Context, key string, usn int64, ttl time.Duration) (string, error) { + token, err := utils.GenToken(usn, config.Get().Auth.Secret, time.Duration(config.Get().Auth.Expire)*time.Second) + if err != nil { + return "", err + } + redis.GetClient().Set(ctx, fmt.Sprintf(key, usn), token, ttl) + return token, err +} diff --git a/internal/handler/http_handler/test.go b/internal/handler/http_handler/test.go new file mode 100644 index 0000000..eaf5a3b --- /dev/null +++ b/internal/handler/http_handler/test.go @@ -0,0 +1,27 @@ +package http_handler + +import ( + "common/net/http/http_resp" + "github.com/gin-gonic/gin" +) + +// 这个模块处理用户登录 + +type TestReq struct { +} + +type TestResp struct { + Info string `json:"info"` +} + +func Test(c *gin.Context) { + req := &TestReq{} + if err := c.ShouldBindJSON(req); err != nil { + http_resp.JsonBadRequest(c) + return + } + + http_resp.JsonOK(c, http_resp.Success(&TestResp{ + Info: "成功了", + })) +} diff --git a/internal/handler/ws_handler/client.go b/internal/handler/ws_handler/client.go new file mode 100644 index 0000000..a5da24b --- /dev/null +++ b/internal/handler/ws_handler/client.go @@ -0,0 +1,106 @@ +package ws_handler + +import ( + "common/log" + "common/net/socket" + "context" + "fmt" + "go.uber.org/zap" + "runtime/debug" + "sync" + "time" +) + +var GatewaySID int64 + +type Client struct { + sync.WaitGroup + conn socket.ISocketConn // Socket + mailChan chan Event // 邮箱队列 + logger *zap.SugaredLogger // 日志 + ctx context.Context // 上下文 + cancel context.CancelFunc // 取消上下文 + heartBeat time.Time // 最后一次心跳 + + USN int64 // 用户ID + SceneSID int64 // 场景服ID + InstanceID int32 // 副本ID,副本类型 + UniqueNo int64 // 副本唯一编号 +} + +func NewClient(usn int64, conn socket.ISocketConn) *Client { + client := &Client{ + USN: usn, + conn: conn, + logger: log.GetLogger().Named(fmt.Sprintf("usn:%v", usn)), + heartBeat: time.Now(), + mailChan: make(chan Event, 1024), + } + client.ctx, client.cancel = context.WithCancel(context.Background()) + client.Add(1) + go client.Loop() + return client +} + +func (c *Client) Loop() { + defer func() { + if err := recover(); err != nil { + c.logger.Errorf("Client Loop err: %v", err) + debug.PrintStack() + } + }() + defer c.onClose() + t := time.NewTicker(20 * time.Second) + defer t.Stop() + for { + select { + case <-c.ctx.Done(): + return + case evt, ok := <-c.mailChan: + if ok { + c.handle(evt) + } + case <-t.C: + _ = c.conn.Ping() + if time.Now().Sub(c.heartBeat) > 60*time.Second { + return + } + } + } +} + +func (c *Client) OnEvent(event Event) { + defer func() { + if err := recover(); err != nil { + c.logger.Warnf(fmt.Sprintf("send event chan error: %v", err)) + } + }() + select { + case c.mailChan <- event: + default: + c.logger.Warnf("Client mailChan full") + } +} + +// CloseClient 关闭客户端(同步,会等待onClose执行完成) +func (c *Client) CloseClient() { + if c.cancel != nil { + c.cancel() + c.Wait() + } +} + +// onClose 客户端关闭自动触发 +func (c *Client) onClose() { + if c.conn != nil { + _ = c.conn.Close() + c.conn = nil + } + if c.mailChan != nil { + close(c.mailChan) + c.mailChan = nil + } + UserMgr.Delete(c.USN) + c.onLeave() + c.Done() +} diff --git a/internal/handler/ws_handler/client_write.go b/internal/handler/ws_handler/client_write.go new file mode 100644 index 0000000..e76c046 --- /dev/null +++ b/internal/handler/ws_handler/client_write.go @@ -0,0 +1,57 @@ +package ws_handler + +import ( + "common/proto/sc/sc_pb" + "google.golang.org/protobuf/proto" +) + +// WriteMessage 向客户端发送消息 +func (c *Client) WriteMessage(id sc_pb.MessageID, data proto.Message) { + if c.conn == nil || c.conn.IsClose() { + return + } + d, err := proto.Marshal(data) + if err != nil { + c.logger.Errorf("WriteMessage proto.Marshal err: %v", err) + return + } + m, err := proto.Marshal(&sc_pb.Message{ + ID: id, + Payload: d, + }) + if err != nil { + c.logger.Errorf("WriteMessage proto.Marshal err: %v", err) + return + } + if err = c.conn.Write(m); err != nil { + c.logger.Errorf("WriteMessage err: %v", err) + } +} + +// WriteBytes 向客户端发送字节数据 +func (c *Client) WriteBytes(id sc_pb.MessageID, data []byte) { + if c.conn == nil || c.conn.IsClose() { + return + } + m, err := proto.Marshal(&sc_pb.Message{ + ID: id, + Payload: data, + }) + if err != nil { + c.logger.Errorf("WriteBytes proto.Marshal err: %v", err) + return + } + if err = c.conn.Write(m); err != nil { + c.logger.Errorf("WriteBytes err: %v", err) + } +} + +// WriteBytesPreMarshal 向客户端发送字节数据(需要预先打包,适合广播相同数据) +func (c *Client) WriteBytesPreMarshal(data []byte) { + if c.conn == nil || c.conn.IsClose() { + return + } + if err := c.conn.Write(data); err != nil { + c.logger.Errorf("WriteBytes err: %v", err) + } +} diff --git a/internal/handler/ws_handler/event.go b/internal/handler/ws_handler/event.go new file mode 100644 index 0000000..7a1ef51 --- /dev/null +++ b/internal/handler/ws_handler/event.go @@ -0,0 +1,13 @@ +package ws_handler + +type Event interface { +} + +type ClientEvent struct { + Event + Msg []byte +} + +type PongEvent struct { + Event +} diff --git a/internal/handler/ws_handler/handler.go b/internal/handler/ws_handler/handler.go new file mode 100644 index 0000000..9577e19 --- /dev/null +++ b/internal/handler/ws_handler/handler.go @@ -0,0 +1,97 @@ +package ws_handler + +import ( + "common/net/grpc/service" + "common/proto/sc/sc_pb" + "common/proto/ss/grpc_pb" + "gateway/internal/grpc_server/stream_client" + "google.golang.org/protobuf/proto" + "time" +) + +func (c *Client) handle(event Event) { + switch e := event.(type) { + case *ClientEvent: + msg := &sc_pb.Message{} + if err := proto.Unmarshal(e.Msg, msg); err != nil { + c.logger.Errorf("handle event proto.Unmarshal err: %v", err) + c.cancel() + return + } + switch msg.ID { + case sc_pb.MessageID_MESSAGE_ID_ENTER_INSTANCE: + m := &sc_pb.C2S_EnterInstance{} + if err := proto.Unmarshal(msg.Payload, m); err != nil { + c.logger.Errorf("handle event proto.Unmarshal err: %v", err) + c.cancel() + return + } + c.onEnter(m) + case sc_pb.MessageID_MESSAGE_ID_ACTION: + m := &sc_pb.C2S_Action{} + if err := proto.Unmarshal(msg.Payload, m); err != nil { + c.logger.Errorf("handle event proto.Unmarshal err: %v", err) + c.cancel() + return + } + c.onAction(m) + } + case *PongEvent: + c.heartBeat = time.Now() + } +} + +func (c *Client) onEnter(msg *sc_pb.C2S_EnterInstance) { + client, err := service.SceneNewClientLB() + if err != nil { + c.logger.Errorf("SceneNewClient err: %v", err) + return + } + resp, err := client.Enter(c.ctx, &grpc_pb.EnterReq{ + USN: c.USN, + GatewaySID: GatewaySID, + InstanceID: msg.InstanceID, + }) + if err != nil { + c.logger.Errorf("enter err: %v", err) + return + } + c.SceneSID = resp.SceneSID + c.UniqueNo = resp.UniqueNo + c.InstanceID = msg.InstanceID + c.WriteBytes(sc_pb.MessageID(resp.MessageID), resp.Payload) +} + +func (c *Client) onLeave() { + client, err := service.SceneNewClient(c.SceneSID) + if err != nil { + c.logger.Errorf("SceneNewClient err: %v", err) + return + } + _, err = client.Leave(c.ctx, &grpc_pb.LeaveReq{ + USN: c.USN, + GatewaySID: GatewaySID, + InstanceID: c.InstanceID, + UniqueNo: c.UniqueNo, + }) + if err != nil { + c.logger.Errorf("leave err: %v", err) + return + } +} + +func (c *Client) onAction(msg *sc_pb.C2S_Action) { + if c.SceneSID == 0 { + return + } + if err := stream_client.SendMessageToScene(c.SceneSID, stream_client.FunAction, &grpc_pb.ActionReq{ + UniqueNo: c.UniqueNo, + USN: c.USN, + Action: int32(msg.Action), + DirX: msg.DirX, + DirY: msg.DirY, + SkillID: msg.SkillID, + }); err != nil { + c.logger.Errorf("send action err: %v", err) + } +} diff --git a/internal/handler/ws_handler/manager.go b/internal/handler/ws_handler/manager.go new file mode 100644 index 0000000..8cbd1a4 --- /dev/null +++ b/internal/handler/ws_handler/manager.go @@ -0,0 +1,58 @@ +package ws_handler + +import ( + "sync" +) + +var UserMgr *userManager + +type userManager struct { + userMap map[int64]*Client + sync.RWMutex +} + +func init() { + UserMgr = &userManager{ + userMap: make(map[int64]*Client), + } +} + +func (m *userManager) Add(usn int64, client *Client) { + m.Lock() + defer m.Unlock() + m.userMap[usn] = client +} + +func (m *userManager) Delete(usn int64) { + m.Lock() + defer m.Unlock() + delete(m.userMap, usn) +} + +func (m *userManager) GetAll() map[int64]*Client { + m.RLock() + defer m.RUnlock() + + copyMap := make(map[int64]*Client, len(m.userMap)) + for k, v := range m.userMap { + copyMap[k] = v + } + return copyMap +} + +func (m *userManager) GetAllInterface() []interface{} { + m.RLock() + defer m.RUnlock() + + r := make([]interface{}, 0) + for _, v := range m.userMap { + r = append(r, v) + } + return r +} + +func (m *userManager) GetByUSN(usn int64) *Client { + m.RLock() + defer m.RUnlock() + return m.userMap[usn] +} diff --git a/internal/net/http_gateway/middleward.go b/internal/net/http_gateway/middleward.go new file mode 100644 index 0000000..0af71d4 --- /dev/null +++ b/internal/net/http_gateway/middleward.go @@ -0,0 +1,78 @@ +package http_gateway + +import ( + "common/net/http/http_resp" + "common/utils" + "fmt" + "gateway/config" + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "strconv" + "strings" + "time" +) + +func corsConfig() cors.Config { + return cors.Config{ + AllowMethods: []string{"GET", "POST", "OPTIONS"}, + AllowHeaders: []string{"Content-Type", "Authorization"}, + AllowCredentials: false, + AllowAllOrigins: true, + MaxAge: 12 * time.Hour, + } +} + +func ginLogger(logger *zap.SugaredLogger) gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + path := c.Request.URL.Path + c.Next() + cost := time.Since(start) + + logger.Infof(fmt.Sprintf( + "HTTP Method:%v Code:%v Time:%v IP:%v Path:%v", + c.Request.Method, + c.Writer.Status(), + cost, + c.ClientIP(), + path), + ) + } +} + +func authJwt() gin.HandlerFunc { + return func(c *gin.Context) { + // 如果是Public接口,有Token就读,没有就算了 + public := false + for _, path := range config.PublicPaths { + if strings.HasPrefix(c.Request.URL.Path, path) { + public = true + break + } + } + + token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ") + if token == "" { + if public { + c.Next() + return + } + http_resp.AbortUnauthorized(c) + return + } + claims, err := utils.ParseToken(token, config.Get().Auth.Secret) + if err != nil { + if public { + c.Next() + return + } + http_resp.AbortUnauthorized(c) + return + } + + // 这里将Header写到请求中,grpc-gateway框架会读取然后传给grpc服务 + c.Request.Header.Set("X-Usn", strconv.Itoa(int(claims.USN))) + c.Next() + } +} diff --git a/internal/net/http_gateway/router.go b/internal/net/http_gateway/router.go new file mode 100644 index 0000000..9127bf2 --- /dev/null +++ b/internal/net/http_gateway/router.go @@ -0,0 +1,93 @@ +package http_gateway + +import ( + "common/log" + "common/net/grpc/service" + "common/net/http/http_resp" + "common/proto/ss/grpc_pb" + "context" + "gateway/internal/handler/http_handler" + "gateway/internal/net/http_gateway/wrapper" + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/protobuf/encoding/protojson" +) + +func InitServeMux() *runtime.ServeMux { + baseMarshaler := &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: false, + EmitUnpopulated: true, + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + DiscardUnknown: true, + }, + } + unifiedMarshaler := wrapper.NewWrappedMarshaler(baseMarshaler) + + mux := runtime.NewServeMux( + runtime.WithMarshalerOption(runtime.MIMEWildcard, unifiedMarshaler), + runtime.WithErrorHandler(wrapper.ErrorHandler), + runtime.WithIncomingHeaderMatcher(func(header string) (string, bool) { + if header == "X-Usn" { + return "X-Usn", true + } + return runtime.DefaultHeaderMatcher(header) + }), + ) + return mux +} + +func InitRouter() *gin.Engine { + gin.SetMode(gin.ReleaseMode) + + r := gin.New() + r.Use( + gin.Recovery(), + ginLogger(log.GetLogger().Named("GIN")), + cors.New(corsConfig()), + ) + + r.HandleMethodNotAllowed = true + r.NoMethod(func(c *gin.Context) { + http_resp.JsonMethodNotAllowed(c) + }) + r.NoRoute(func(c *gin.Context) { + http_resp.JsonNotFound(c) + }) + + initBaseRoute(r.Group("/")) + + auth := r.Group("/") + auth.Use(authJwt()) + + // 用户中心 + initUserPath(auth) + + return r +} + +func initBaseRoute(r *gin.RouterGroup) { + g := r.Group("/gw") + g.POST("/login", http_handler.Login) + g.POST("/refresh_token", http_handler.RefreshToken) + g.GET("/test", http_handler.Test) +} + +func initUserPath(r *gin.RouterGroup) { + g := r.Group("/user") + client, err := service.UserNewClientLB() + if err != nil { + log.Errorf("get user conn failed: %v", err) + return + } + + gwMux := InitServeMux() + if err = grpc_pb.RegisterUserHandlerClient(context.Background(), gwMux, client); err != nil { + log.Errorf("RegisterUserHandlerClient err: %v", err) + return + } + + g.Any("/*path", gin.WrapH(gwMux)) +} diff --git a/internal/net/http_gateway/wrapper/error_handler.go b/internal/net/http_gateway/wrapper/error_handler.go new file mode 100644 index 0000000..a89b508 --- /dev/null +++ b/internal/net/http_gateway/wrapper/error_handler.go @@ -0,0 +1,57 @@ +package wrapper + +import ( + "common/net/http/http_resp" + "common/proto/ss/ss_common" + "context" + "encoding/json" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "net/http" +) + +// ErrorHandler 将 gRPC 错误转为统一 JSON 格式 +func ErrorHandler(_ context.Context, _ *runtime.ServeMux, _ runtime.Marshaler, w http.ResponseWriter, _ *http.Request, err error) { + st, ok := status.FromError(err) + if !ok { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + _ = json.NewEncoder(w).Encode(http_resp.Error(http_resp.Failed)) + return + } + + code, msg := 0, "" + for _, detail := range st.Details() { + if errorInfo, ok := detail.(*ss_common.ErrorInfo); ok { + code = int(errorInfo.Code) + msg = errorInfo.Msg + break + } + } + if code == 0 { + code = http_resp.Failed.Code() + msg = http_resp.Failed.Error() + } + if st.Code() == codes.Unknown || + st.Code() == codes.Unimplemented || + st.Code() == codes.NotFound { + msg = st.Message() + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(grpcCodeToHTTPCode(st.Code())) + _ = json.NewEncoder(w).Encode(http_resp.Error(http_resp.NewCode(code, msg))) +} + +// 这里定义 Internal 属于业务错误,其他的属于 500 报错 +func grpcCodeToHTTPCode(c codes.Code) int { + switch c { + case codes.OK, codes.Unknown: + return http.StatusOK + case codes.Unimplemented, codes.NotFound: + return http.StatusNotFound + default: + return http.StatusInternalServerError + } +} diff --git a/internal/net/http_gateway/wrapper/marshal.go b/internal/net/http_gateway/wrapper/marshal.go new file mode 100644 index 0000000..18782d0 --- /dev/null +++ b/internal/net/http_gateway/wrapper/marshal.go @@ -0,0 +1,42 @@ +package wrapper + +import ( + "common/net/http/http_resp" + "encoding/json" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "io" +) + +// WrappedMarshaler 自动包装响应为 { code, message, data } +type WrappedMarshaler struct { + inner runtime.Marshaler +} + +func NewWrappedMarshaler(inner runtime.Marshaler) *WrappedMarshaler { + return &WrappedMarshaler{inner: inner} +} + +// Marshal 将 gRPC 响应包装成统一格式 +func (w *WrappedMarshaler) Marshal(v interface{}) ([]byte, error) { + dataBytes, err := w.inner.Marshal(v) + if err != nil { + return json.Marshal(http_resp.Error(http_resp.Failed)) + } + return json.Marshal(http_resp.Success(json.RawMessage(dataBytes))) +} + +func (w *WrappedMarshaler) Unmarshal(data []byte, v interface{}) error { + return w.inner.Unmarshal(data, v) +} + +func (w *WrappedMarshaler) NewDecoder(r io.Reader) runtime.Decoder { + return w.inner.NewDecoder(r) +} + +func (w *WrappedMarshaler) NewEncoder(wr io.Writer) runtime.Encoder { + return w.inner.NewEncoder(wr) +} + +func (w *WrappedMarshaler) ContentType(v interface{}) string { + return "application/json" +} diff --git a/internal/net/ws_gateway/server.go b/internal/net/ws_gateway/server.go new file mode 100644 index 0000000..5506a43 --- /dev/null +++ b/internal/net/ws_gateway/server.go @@ -0,0 +1,74 @@ +package ws_gateway + +import ( + "common/log" + "common/net/socket" + "common/utils" + "fmt" + "gateway/internal/handler/ws_handler" + "go.uber.org/zap" + "strconv" + "time" +) + +type GatewayWsServer struct { + logger *zap.SugaredLogger +} + +func (g *GatewayWsServer) OnOpen(conn socket.ISocketConn) ([]byte, socket.Action) { + g.logger = log.GetLogger().Named(fmt.Sprintf("addr:%v", conn.RemoteAddr())) + return nil, socket.None +} + +func (g *GatewayWsServer) OnHandShake(conn socket.ISocketConn, bytes []byte, callback func(conn socket.ISocketConn, bytes []byte)) socket.Action { + token, ok := conn.GetParam("token").(string) + if !ok { + g.logger.Warnf("token is not string") + return socket.Close + } + //claims, err := utils.ParseToken(token, config.Get().Auth.Secret) + //if err != nil { + // g.logger.Warnf("token is invalid") + // return socket.Close + //} + + t, _ := strconv.Atoi(token) + claims := utils.Claims{ + USN: int64(t), + } + go func(shResp []byte) { + if oldClient := ws_handler.UserMgr.GetByUSN(claims.USN); oldClient != nil { + oldClient.CloseClient() + } + client := ws_handler.NewClient(claims.USN, conn) + ws_handler.UserMgr.Add(claims.USN, client) + conn.SetParam("client", client) + callback(conn, shResp) + }(bytes) + return socket.None +} + +func (g *GatewayWsServer) OnMessage(conn socket.ISocketConn, bytes []byte) socket.Action { + client, ok := conn.GetParam("client").(*ws_handler.Client) + if !ok || client.USN == 0 { + return socket.Close + } + client.OnEvent(&ws_handler.ClientEvent{Msg: bytes}) + return socket.None +} + +func (g *GatewayWsServer) OnPong(conn socket.ISocketConn) { + client, ok := conn.GetParam("client").(*ws_handler.Client) + if !ok || client.USN == 0 { + return + } + client.OnEvent(&ws_handler.PongEvent{}) +} + +func (g *GatewayWsServer) OnClose(_ socket.ISocketConn, _ error) socket.Action { + return socket.Close +} + +func (g *GatewayWsServer) OnTick() (time.Duration, socket.Action) { + return 5 * time.Second, socket.None +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..44b42fd --- /dev/null +++ b/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "gateway/app" + "github.com/judwhite/go-svc" + "syscall" +) + +func main() { + if err := svc.Run(&app.Program{}, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL); err != nil { + fmt.Println(err) + } +}