feat app 模块化启动

This commit is contained in:
2025-12-13 18:22:35 +08:00
parent 71d4e593c7
commit bc656247c9
41 changed files with 730 additions and 253 deletions

View File

@@ -10,7 +10,7 @@ class WebSocketService {
} }
connect(squareId, onMessage, onStatusChange) { connect(squareId, onMessage, onStatusChange) {
const wsUrl = `ws://localhost:8501/?token=${squareId}`; const wsUrl = `wss://www.hlsq.asia/ws/?token=${squareId}`;
try { try {
const ws = new WebSocket(wsUrl); const ws = new WebSocket(wsUrl);

6
Public/Publish/cmd.txt Normal file
View File

@@ -0,0 +1,6 @@
docker compose -p db -f docker-compose-db.yml up -d
docker compose -p tool -f docker-compose-tool.yml up -d
chown -R 1000:1000 jenkins/
ssh -L 2379:localhost:2379 root@47.108.184.184

View File

@@ -0,0 +1,31 @@
services:
mysql:
image: mysql:latest
container_name: mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: gR9pV4tY7zR6qL3e
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- ./db/mysql:/var/lib/mysql
redis:
image: redis:latest
container_name: redis
restart: always
ports:
- "6379:6379"
volumes:
- ./db/redis:/data
command: redis-server --requirepass lQ7aM8oB6lK0iD5k
etcd:
image: bitnami/etcd:latest
container_name: etcd
restart: always
ports:
- "2379:2379"
environment:
ALLOW_NONE_AUTHENTICATION: "yes"

View File

@@ -0,0 +1,26 @@
services:
jenkins:
image: jenkins/jenkins:lts
container_name: jenkins
command: "--prefix=/jenkins"
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./jenkins:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
environment:
- TZ=Asia/Shanghai
nginx:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/html:/var/www/html
- ./nginx/certs:/etc/nginx/certs:ro

View File

@@ -1,81 +0,0 @@
version: '3.8'
services:
# ETCD 分布式键值存储
etcd:
image: quay.io/coreos/etcd:v3.5.4
container_name: etcd
ports:
- "2379:2379" # 客户端连接端口
- "2380:2380" # 节点通信端口
environment:
ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
ETCD_ADVERTISE_CLIENT_URLS: "http://etcd:2379"
ETCD_LISTEN_PEER_URLS: "http://0.0.0.0:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS: "http://etcd:2380"
ETCD_NAME: "etcd-single"
ETCD_INITIAL_CLUSTER: "etcd-single=http://etcd:2380"
ETCD_INITIAL_CLUSTER_TOKEN: "etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE: "new"
volumes:
- etcd-data:/etcd-data
networks:
- app-network
# Redis 缓存数据库
redis:
image: redis:7.0-alpine
container_name: redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
command: redis-server --save 60 1 --loglevel warning
# MySQL 数据库
mysql:
image: mysql:8.0
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: rootpassword # 请更改为强密码
MYSQL_DATABASE: app_db
MYSQL_USER: app_user
MYSQL_PASSWORD: userpassword # 请更改为强密码
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./mysql-init:/docker-entrypoint-initdb.d # 初始化SQL脚本目录
networks:
- app-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 10s
retries: 5
# ETCD 可视化界面 (Web UI)
etcd-viewer:
image: deltaprojects/etcdkeeper:latest
container_name: etcd-viewer
ports:
- "8080:8080"
environment:
ETCD_HOST: etcd
ETCD_PORT: 2379
networks:
- app-network
depends_on:
- etcd
# 数据卷声明
volumes:
etcd-data:
redis-data:
mysql-data:
# 网络配置
networks:
app-network:
driver: bridge

11
Server/Gateway/Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM alpine:latest
RUN apk add --no-cache tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
COPY gateway/ /app/
RUN chmod 777 /app/server-gateway
WORKDIR /app
CMD ["./server-gateway"]

View File

@@ -1,81 +1,60 @@
package app package app
import ( import (
"common/db/etcd"
"common/discover" "common/discover"
"common/log" "common/log"
"common/net/grpc/service"
"common/net/socket/websocket"
"fmt" "fmt"
"gateway/config" "gateway/config"
"gateway/grpc_server/server"
"github.com/gin-gonic/gin"
"github.com/judwhite/go-svc" "github.com/judwhite/go-svc"
"runtime/debug"
"sync"
) )
type Program struct { type Program struct {
wg *sync.WaitGroup moduleList []Module // 模块列表
server service.IService // grpc服务 }
webServer *gin.Engine // web服务
wsServer *websocket.WSServer // websocket服务 type Module interface {
stop chan bool Init() error
Start() error
Stop() error
} }
func (p *Program) Init(_ svc.Environment) error { func (p *Program) Init(_ svc.Environment) error {
if err := p.initBase(); err != nil { p.moduleList = append(p.moduleList, &ModuleBase{})
p.moduleList = append(p.moduleList, &ModuleDB{})
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 return err
} }
cfg := config.Get()
log.Infof(fmt.Sprintf("%v starting...", cfg.App.Name))
if err := p.initDB(cfg); err != nil {
return err
}
if err := p.initWebServer(cfg); err != nil {
return err
}
if err := p.initWsServer(cfg); err != nil {
return err
} }
log.Infof(fmt.Sprintf("%v Init successful...", config.Get().App.Name))
return nil return nil
} }
func (p *Program) Start() error { func (p *Program) Start() error {
defer func() { for _, module := range p.moduleList {
if err := recover(); err != nil { if err := module.Start(); err != nil {
fmt.Printf("Start err: %v", err) return err
debug.PrintStack() }
_ = p.Stop()
} }
}()
discover.Listen() discover.Listen()
p.server = server.NewServer(config.Get().Serve.Grpc.TTL)
p.server.Init(config.Get().Serve.Grpc.Address, config.Get().Serve.Grpc.Port)
go func() {
cfg := config.Get()
_ = p.wsServer.Run(
log.GetLogger().Named("gnet"),
fmt.Sprintf("tcp4://0.0.0.0:%v", cfg.Serve.Socket.Web.Port),
true, true, false, false, true, 8,
)
}()
log.Infof(fmt.Sprintf("%v Start successful...", config.Get().App.Name))
return nil return nil
} }
func (p *Program) Stop() error { func (p *Program) Stop() error {
defer func() {
if err := recover(); err != nil {
fmt.Printf("Stop err: %v", err)
debug.PrintStack()
}
}()
discover.Close() discover.Close()
p.server.Close() for i := len(p.moduleList) - 1; i >= 0; i-- {
_ = etcd.Close() 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 return nil
} }

View File

@@ -7,7 +7,11 @@ import (
"math/rand" "math/rand"
) )
func (p *Program) initBase() error { // ModuleBase 基础模块,或者一些零散的模块
type ModuleBase struct {
}
func (p *ModuleBase) Init() error {
// 配置 // 配置
if err := config.LoadConfig(); err != nil { if err := config.LoadConfig(); err != nil {
return err return err
@@ -17,6 +21,13 @@ func (p *Program) initBase() error {
log.Init(cfg.Log.Debug, cfg.Log.MaxSize, cfg.Log.MaxBackups, cfg.Log.MaxAge, cfg.Log.Level) log.Init(cfg.Log.Debug, cfg.Log.MaxSize, cfg.Log.MaxBackups, cfg.Log.MaxAge, cfg.Log.Level)
// 雪花 // 雪花
utils.InitSnowflake(int64(rand.Intn(1000))) utils.InitSnowflake(int64(rand.Intn(1000)))
return nil
}
func (p *ModuleBase) Start() error {
return nil
}
func (p *ModuleBase) Stop() error {
return nil return nil
} }

View File

@@ -2,10 +2,16 @@ package app
import ( import (
"common/db/etcd" "common/db/etcd"
"common/log"
"gateway/config" "gateway/config"
) )
func (p *Program) initDB(cfg *config.Config) error { // ModuleDB 数据库模块
type ModuleDB struct {
}
func (p *ModuleDB) Init() error {
cfg := config.Get()
// ETCD // ETCD
if err := etcd.Init(cfg.DB.Etcd.Address); err != nil { if err := etcd.Init(cfg.DB.Etcd.Address); err != nil {
return err return err
@@ -13,8 +19,13 @@ func (p *Program) initDB(cfg *config.Config) error {
return nil return nil
} }
func (p *Program) stopDB() error { func (p *ModuleDB) Start() error {
_ = etcd.Close() return nil
}
func (p *ModuleDB) Stop() error {
if err := etcd.Close(); err != nil {
log.Errorf("close etcd failed: %v", err)
}
return nil return nil
} }

View File

@@ -0,0 +1,27 @@
package app
import (
"common/net/grpc/service"
"gateway/config"
"gateway/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
}

View File

@@ -1,16 +1,49 @@
package app package app
import ( import (
"common/log"
"errors"
"fmt"
"gateway/config" "gateway/config"
"gateway/net/http_gateway" "gateway/net/http_gateway"
"github.com/gin-gonic/gin"
"net/http"
"sync"
) )
func (p *Program) initWebServer(cfg *config.Config) error { // ModuleWebServer Web服务模块
p.webServer = http_gateway.InitRouter(cfg) type ModuleWebServer struct {
wg *sync.WaitGroup
server *http.Server
router *gin.Engine
}
func (m *ModuleWebServer) Init() error {
m.wg = &sync.WaitGroup{}
m.router = http_gateway.InitRouter(config.Get())
return nil return nil
} }
func (p *Program) stopWebServer() error { 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: m.router,
}
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.Close(); err != nil {
log.Errorf("stop http server failed: %v", err)
}
m.wg.Wait()
return nil return nil
} }

View File

@@ -3,37 +3,51 @@ package app
import ( import (
"common/log" "common/log"
"common/net/socket/websocket" "common/net/socket/websocket"
"fmt"
"gateway/config" "gateway/config"
"gateway/net/ws_gateway" "gateway/net/ws_gateway"
"sync"
"time" "time"
) )
func (p *Program) initWsServer(cfg *config.Config) error { // ModuleWebsocketServer Websocket服务模块
//p.wsServer = websocket.NewWSServer( type ModuleWebsocketServer struct {
// &ws_gateway.GatewayWsServer{ wg *sync.WaitGroup
// Decoder: &ws_gateway.Decoder{}, server *websocket.WSServer
// }, }
// log.GetLogger(),
// fmt.Sprintf("tcp4://%v:%d", cfg.SocketServer.Host, cfg.SocketServer.Port), func (m *ModuleWebsocketServer) Init() error {
// true, m.wg = &sync.WaitGroup{}
// true, m.server = websocket.NewWSServer(
// false,
// false,
// true,
// 8,
// 5*time.Second,
//)
p.wsServer = websocket.NewWSServer(
&ws_gateway.GatewayWsServer{}, &ws_gateway.GatewayWsServer{},
log.GetLogger().Named("ws_server"), log.GetLogger().Named("ws_server"),
5*time.Second, 5*time.Second,
) )
return nil return nil
} }
func (p *Program) stopWsServer() error { func (m *ModuleWebsocketServer) Start() error {
_ = p.wsServer.Stop() m.wg.Add(1)
go func() {
defer m.wg.Done()
_ = m.server.Run(
log.GetLogger().Named("GNET"),
fmt.Sprintf("tcp4://0.0.0.0:%v", config.Get().Serve.Socket.Web.Port),
true,
true,
false,
false,
true,
8,
)
}()
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 return nil
} }

View File

@@ -4,8 +4,8 @@ app:
log: log:
debug: true debug: true
level: "debug" level: "debug"
maxSize: 100 maxSize: 10
maxBackups: 3 maxBackups: 100
maxAge: 7 maxAge: 7
db: db:
@@ -19,11 +19,11 @@ serve:
ttl: 20 ttl: 20
socket: socket:
web: web:
address: "0.0.0.0" address: "127.0.0.1"
port: 8501 port: 8501
raw: raw:
address: "0.0.0.0" address: "127.0.0.1"
port: 8502 port: 8502
http: http:
address: "0.0.0.0" address: "127.0.0.1"
port: 8503 port: 8503

View File

@@ -0,0 +1,29 @@
app:
name: "gateway-prod"
log:
debug: false
level: "debug"
maxSize: 10
maxBackups: 100
maxAge: 7
db:
etcd:
address: [ "172.18.28.0:2379" ]
serve:
grpc:
address: "172.18.28.0"
port: 8500
ttl: 20
socket:
web:
address: "172.18.28.0"
port: 8501
raw:
address: "172.18.28.0"
port: 8502
http:
address: "172.18.28.0"
port: 8503

View File

@@ -0,0 +1,18 @@
package helper
type Pagination struct {
Page int `json:"page"` // 页
Size int `json:"size"` // 页大小
}
//func SetBaseQuery(page *Pagination, order string) func(db *gorm.DB) *gorm.DB {
// return func(db *gorm.DB) *gorm.DB {
// if page != nil {
// db = db.Offset((page.Page - 1) * page.Size).Limit(page.Size)
// }
// if len(order) > 0 {
// db = db.Order(order)
// }
// return db
// }
//}

View File

@@ -0,0 +1,31 @@
package render
var (
OK = NewCode(0, "成功")
Failed = NewCode(1, "失败")
ParamError = NewCode(1001, "参数错误")
NameEmpty = NewCode(1002, "名称不能为空")
NameDuplicate = NewCode(1003, "名称或编号不能重复")
ListEmpty = NewCode(1004, "列表不能为空")
RepeatCommit = NewCode(1005, "请勿重复提交")
)
type Code struct {
code int
message string
}
func NewCode(code int, message string) *Code {
return &Code{
code: code,
message: message,
}
}
func (c *Code) Code() int {
return c.code
}
func (c *Code) Message() string {
return c.message
}

View File

@@ -0,0 +1,30 @@
package render
import (
"github.com/gin-gonic/gin"
"net/http"
)
type RespJsonData struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func Json(c *gin.Context, code *Code, data interface{}) {
result := &RespJsonData{
Code: code.Code(),
Msg: code.Message(),
Data: data,
}
c.JSON(http.StatusOK, result)
}
func AbortJson(c *gin.Context, code *Code, data interface{}) {
result := &RespJsonData{
Code: code.Code(),
Msg: code.Message(),
Data: data,
}
c.AbortWithStatusJSON(http.StatusOK, result)
}

View File

@@ -0,0 +1,16 @@
package http_handler
import (
"common/utils"
"gateway/handler/http_handler/helper/render"
"github.com/gin-gonic/gin"
)
type UniqueIDResp struct {
ID string `json:"id"`
}
// GenSnowflake 生成雪花ID
func GenSnowflake(c *gin.Context) {
render.Json(c, render.OK, &UniqueIDResp{ID: utils.SnowflakeInstance().Generate().String()})
}

View File

@@ -0,0 +1,37 @@
package http_gateway
import (
"fmt"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"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),
)
}
}

View File

@@ -1,44 +1,43 @@
package http_gateway package http_gateway
import ( import (
"common/log"
"gateway/config" "gateway/config"
"gateway/handler/http_handler"
"github.com/gin-contrib/cors" "github.com/gin-contrib/cors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"time"
) )
func InitRouter(cfg *config.Config) *gin.Engine { func InitRouter(cfg *config.Config) *gin.Engine {
r := gin.Default() gin.SetMode(gin.ReleaseMode)
r.Use(gin.Recovery())
r.Use(cors.New(getCorsConfig())) r := gin.New()
r.Use(
gin.Recovery(),
ginLogger(log.GetLogger().Named("GIN")),
cors.New(corsConfig()),
)
r.MaxMultipartMemory = 8 << 20
r.HandleMethodNotAllowed = true r.HandleMethodNotAllowed = true
r.NoMethod(func(c *gin.Context) { r.NoMethod(func(c *gin.Context) {
c.JSON(http.StatusMethodNotAllowed, gin.H{ c.JSON(http.StatusMethodNotAllowed, gin.H{
"result": false, "result": false,
"error": "Method Not Allowed", "error": "Method Not Allowed",
}) })
return
}) })
r.NoRoute(func(c *gin.Context) { r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{ c.JSON(http.StatusNotFound, gin.H{
"result": false, "result": false,
"error": "Endpoint Not Found", "error": "Endpoint Not Found",
}) })
return
}) })
initBaseRouter(r)
return r return r
} }
func getCorsConfig() cors.Config { func initBaseRouter(router *gin.Engine) {
return cors.Config{ g := router.Group("/b")
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"}, g.POST("/snowflake", http_handler.GenSnowflake) // 生成雪花
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization", "X-Forwarded-For", "User-Agent", "Referer", "X-Token", "token", "Token", "company-id", "lang", "source"},
AllowCredentials: false,
AllowAllOrigins: true,
MaxAge: 12 * time.Hour,
}
} }

22
Server/build-all.bat Normal file
View File

@@ -0,0 +1,22 @@
@echo off
setlocal
cd "%~dp0"
set GOOS=linux
set GOARCH=amd64
echo [INFO] Build started...
echo.
for %%p in (gateway scene) do (
echo [BUILD] server-%%p...
cd %%p
go build -o server-%%p
move server-%%p .. >nul
cd ..
)
echo.
echo [INFO] Build finished successfully!
endlocal

View File

@@ -1,6 +1,7 @@
package config package config
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/spf13/viper" "github.com/spf13/viper"
"strings" "strings"
@@ -28,11 +29,14 @@ func LoadConfig[T any](configDir string, configPtr *T) (*T, error) {
v.SetConfigType("yaml") v.SetConfigType("yaml")
if err := v.ReadInConfig(); err != nil { if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("读取配置失败: %w", err) return nil, fmt.Errorf("failed to read config: %w", err)
} }
if err := v.Unmarshal(&configPtr); err != nil { if err := v.Unmarshal(&configPtr); err != nil {
return nil, fmt.Errorf("解析配置失败: %w", err) return nil, fmt.Errorf("failed to unmarshal config: %w", err)
} }
marshal, _ := json.Marshal(configPtr)
fmt.Printf("Configuration loading completed: %v\n", string(marshal))
return configPtr, nil return configPtr, nil
} }

View File

@@ -1,28 +1,77 @@
package etcd package etcd
import ( import (
"context"
"go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/client/v3"
"go.uber.org/zap"
"time" "time"
) )
var cli *clientv3.Client var instance *Client
type Client struct {
cli *clientv3.Client
log *zap.Logger
}
// Init 初始化
func Init(endpoints []string) error { func Init(endpoints []string) error {
client, err := clientv3.New(clientv3.Config{ client, err := clientv3.New(clientv3.Config{
Endpoints: endpoints, Endpoints: endpoints,
DialTimeout: 5 * time.Second, DialTimeout: 5 * time.Second,
}) })
cli = client instance = &Client{
cli: client,
}
return err return err
} }
func Client() *clientv3.Client { // Close 关闭
return cli
}
func Close() error { func Close() error {
if cli != nil { if instance != nil && instance.cli != nil {
return cli.Close() return instance.cli.Close()
} }
return nil return nil
} }
func GetClient() *Client {
return instance
}
// Get 获取数据
func (c *Client) Get(key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
return c.cli.Get(ctx, key, opts...)
}
// Put 创建数据
func (c *Client) Put(key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
return c.cli.Put(ctx, key, val, opts...)
}
// Grant 创建租约
func (c *Client) Grant(ttl int64) (*clientv3.LeaseGrantResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
return c.cli.Grant(ctx, ttl)
}
// KeepAlive 保活租约
func (c *Client) KeepAlive(id clientv3.LeaseID) (<-chan *clientv3.LeaseKeepAliveResponse, error) {
return c.cli.KeepAlive(context.Background(), id)
}
// Revoke 撤销租约
func (c *Client) Revoke(id clientv3.LeaseID) (*clientv3.LeaseRevokeResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
return c.cli.Revoke(ctx, id)
}
// Watch 监听数据
func (c *Client) Watch(key string, opts ...clientv3.OpOption) clientv3.WatchChan {
return c.cli.Watch(context.Background(), key, opts...)
}

View File

@@ -3,27 +3,23 @@ package common
import ( import (
"common/db/etcd" "common/db/etcd"
"common/log" "common/log"
"context"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
// NewLeaseAndKeepAlive 创建租约并保活 // NewLeaseAndKeepAlive 创建租约并保活
func NewLeaseAndKeepAlive(ttl int64) (clientv3.LeaseID, error) { func NewLeaseAndKeepAlive(ttl int64) (clientv3.LeaseID, error) {
lease, err := etcd.Client().Grant(context.Background(), ttl) lease, err := etcd.GetClient().Grant(ttl)
if err != nil { if err != nil {
return 0, err return 0, err
} }
chKeepAlive, err := etcd.Client().KeepAlive(context.Background(), lease.ID) chKeepAlive, err := etcd.GetClient().KeepAlive(lease.ID)
if err != nil { if err != nil {
return 0, err return 0, err
} }
go func() { go func(leaseID clientv3.LeaseID) {
for r := range chKeepAlive { for range chKeepAlive {
if r == nil {
log.Errorf("lease timeout!")
return
} }
} log.Warnf("Lease %x expired or revoked", leaseID)
}() }(lease.ID)
return lease.ID, nil return lease.ID, nil
} }

View File

@@ -4,7 +4,6 @@ import (
"common/db/etcd" "common/db/etcd"
"common/discover/common" "common/discover/common"
"common/log" "common/log"
"context"
"fmt" "fmt"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
"strconv" "strconv"
@@ -42,7 +41,7 @@ func RegisterInstance(sid int64, instanceID int, uniqueNo, ttl int64) error {
return err return err
} }
key := fmt.Sprintf("%v/%v/%v", common.KeyDiscoverInstance, instanceID, uniqueNo) key := fmt.Sprintf("%v/%v/%v", common.KeyDiscoverInstance, instanceID, uniqueNo)
_, err = etcd.Client().Put(context.Background(), key, strconv.Itoa(int(sid)), clientv3.WithLease(leaseID)) _, err = etcd.GetClient().Put(key, strconv.Itoa(int(sid)), clientv3.WithLease(leaseID))
if err != nil { if err != nil {
return err return err
} }
@@ -55,7 +54,7 @@ func UnRegisterInstance(uniqueNo int64) {
serverMU.Lock() serverMU.Lock()
defer serverMU.Unlock() defer serverMU.Unlock()
if leaseID, ok := instanceLeaseM[uniqueNo]; ok { if leaseID, ok := instanceLeaseM[uniqueNo]; ok {
_, err := etcd.Client().Revoke(context.Background(), leaseID) _, err := etcd.GetClient().Revoke(leaseID)
if err != nil { if err != nil {
log.Errorf("UnRegisterInstance err: %v", err) log.Errorf("UnRegisterInstance err: %v", err)
} }

View File

@@ -3,8 +3,10 @@ package discover
import ( import (
"common/db/etcd" "common/db/etcd"
"common/discover/common" "common/discover/common"
"common/log"
"common/utils" "common/utils"
"context" "context"
"fmt"
"go.etcd.io/etcd/api/v3/mvccpb" "go.etcd.io/etcd/api/v3/mvccpb"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
"strconv" "strconv"
@@ -46,18 +48,19 @@ func Listen() {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
log.Infof(fmt.Sprintf("Discover start listen..."))
// 服务 // 服务
serviceAll, _ := etcd.Client().Get(stopCtx, common.KeyDiscoverService, clientv3.WithPrefix()) serviceAll, _ := etcd.GetClient().Get(common.KeyDiscoverService, clientv3.WithPrefix())
for _, kv := range serviceAll.Kvs { for _, kv := range serviceAll.Kvs {
onServerChange(clientv3.EventTypePut, string(kv.Key), string(kv.Value)) onServerChange(clientv3.EventTypePut, string(kv.Key), string(kv.Value))
} }
chService := etcd.Client().Watch(stopCtx, common.KeyDiscoverService, clientv3.WithPrefix(), clientv3.WithRev(serviceAll.Header.Revision+1)) chService := etcd.GetClient().Watch(common.KeyDiscoverService, clientv3.WithPrefix(), clientv3.WithRev(serviceAll.Header.Revision+1))
// 副本 // 副本
instanceAll, _ := etcd.Client().Get(stopCtx, common.KeyDiscoverInstance, clientv3.WithPrefix()) instanceAll, _ := etcd.GetClient().Get(common.KeyDiscoverInstance, clientv3.WithPrefix())
for _, kv := range instanceAll.Kvs { for _, kv := range instanceAll.Kvs {
onInstanceChange(clientv3.EventTypePut, string(kv.Key), string(kv.Value), nil) onInstanceChange(clientv3.EventTypePut, string(kv.Key), string(kv.Value), nil)
} }
chInstance := etcd.Client().Watch(stopCtx, common.KeyDiscoverScene, clientv3.WithPrefix(), clientv3.WithRev(instanceAll.Header.Revision+1), clientv3.WithPrevKV()) chInstance := etcd.GetClient().Watch(common.KeyDiscoverScene, clientv3.WithPrefix(), clientv3.WithRev(instanceAll.Header.Revision+1), clientv3.WithPrevKV())
for { for {
select { select {
case msg := <-chService: case msg := <-chService:

View File

@@ -5,7 +5,6 @@ import (
"common/discover/common" "common/discover/common"
"common/log" "common/log"
"common/net/grpc/grpc_conn" "common/net/grpc/grpc_conn"
"context"
"fmt" "fmt"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -51,7 +50,7 @@ func RegisterGrpcServer(target string, sid int64, addr string, ttl int64) error
if err != nil { if err != nil {
return err return err
} }
_, err = etcd.Client().Put(context.Background(), fmt.Sprintf("%v/%v", target, sid), addr, clientv3.WithLease(leaseID)) _, err = etcd.GetClient().Put(fmt.Sprintf("%v/%v", target, sid), addr, clientv3.WithLease(leaseID))
if err != nil { if err != nil {
return err return err
} }
@@ -64,7 +63,7 @@ func UnRegisterGrpcServer(sid int64) {
serverMU.Lock() serverMU.Lock()
defer serverMU.Unlock() defer serverMU.Unlock()
if leaseID, ok := serverLeaseM[sid]; ok { if leaseID, ok := serverLeaseM[sid]; ok {
_, err := etcd.Client().Revoke(context.Background(), leaseID) _, err := etcd.GetClient().Revoke(leaseID)
if err != nil { if err != nil {
log.Errorf("server.go UnRegisterGrpcServer err: %v", err) log.Errorf("server.go UnRegisterGrpcServer err: %v", err)
} }

View File

@@ -0,0 +1,11 @@
FROM alpine:latest
RUN apk add --no-cache tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
COPY gateway/ /app/
RUN chmod 777 /app/server-gateway
WORKDIR /app
CMD ["./server-gateway"]

View File

@@ -0,0 +1,29 @@
app:
name: "gateway-prod"
log:
debug: false
level: "debug"
maxSize: 10
maxBackups: 100
maxAge: 7
db:
etcd:
address: [ "172.18.28.0:2379" ]
serve:
grpc:
address: "172.18.28.0"
port: 8500
ttl: 20
socket:
web:
address: "172.18.28.0"
port: 8501
raw:
address: "172.18.28.0"
port: 8502
http:
address: "172.18.28.0"
port: 8503

View File

@@ -0,0 +1,19 @@
@echo off
call ../build-all.bat
echo.
rd /s /q gateway
xcopy ..\gateway\config\config.prod.yaml gateway\config\ >nul
xcopy ..\gateway\Dockerfile gateway\ >nul
move ..\server-gateway gateway\ >nul
echo [INFO] Copy gateway finished.
rd /s /q scene
xcopy ..\scene\config\config.prod.yaml scene\config\ >nul
xcopy ..\scene\Dockerfile scene\ >nul
move ..\server-scene scene\ >nul
echo [INFO] Copy scene finished.
echo.
pause

View File

@@ -0,0 +1,7 @@
#!/bin/bash
docker stop server-gateway
docker rm server-gateway
docker rmi server-gateway:latest
docker build -f ./gateway/Dockerfile . -t server-gateway
docker run -d --name server-gateway -p 8500-8503:8500-8503 --privileged=true --env XH_G_ENV=prod -v /root/server/logs/gateway_log/:/app/logs server-gateway

View File

@@ -0,0 +1,7 @@
#!/bin/bash
docker stop server-scene
docker rm server-scene
docker rmi server-scene:latest
docker build -f ./scene/Dockerfile . -t server-scene
docker run -d --name server-scene -p 8504:8504 --privileged=true --env XH_G_ENV=prod -v /root/server/logs/scene_log/:/app/logs server-scene

View File

@@ -0,0 +1,11 @@
FROM alpine:latest
RUN apk add --no-cache tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
COPY scene/ /app/
RUN chmod 777 /app/server-scene
WORKDIR /app
CMD ["./server-scene"]

View File

@@ -0,0 +1,19 @@
app:
name: "scene-prod"
log:
debug: false
level: "debug"
maxSize: 10
maxBackups: 100
maxAge: 7
db:
etcd:
address: [ "172.18.28.0:2379" ]
serve:
grpc:
address: "172.18.28.0"
port: 8504
ttl: 20

11
Server/scene/Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM alpine:latest
RUN apk add --no-cache tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
COPY scene/ /app/
RUN chmod 777 /app/server-scene
WORKDIR /app
CMD ["./server-scene"]

View File

@@ -1,63 +1,58 @@
package app package app
import ( import (
"common/db/etcd"
"common/discover" "common/discover"
"common/log" "common/log"
"common/net/grpc/service"
"fmt" "fmt"
"github.com/judwhite/go-svc" "github.com/judwhite/go-svc"
"runtime/debug"
"scene/config" "scene/config"
"scene/grpc_server/server"
"sync"
) )
type Program struct { type Program struct {
wg *sync.WaitGroup moduleList []Module // 模块列表
server service.IService // grpc服务 }
stop chan bool
type Module interface {
Init() error
Start() error
Stop() error
} }
func (p *Program) Init(_ svc.Environment) error { func (p *Program) Init(_ svc.Environment) error {
if err := p.initBase(); err != nil { p.moduleList = append(p.moduleList, &ModuleBase{})
p.moduleList = append(p.moduleList, &ModuleDB{})
p.moduleList = append(p.moduleList, &ModuleGrpcServer{})
for _, module := range p.moduleList {
if err := module.Init(); err != nil {
return err return err
} }
cfg := config.Get()
log.Infof(fmt.Sprintf("%v starting...", cfg.App.Name))
if err := p.initDB(cfg); err != nil {
return err
} }
log.Infof(fmt.Sprintf("%v Init successful...", config.Get().App.Name))
return nil return nil
} }
func (p *Program) Start() error { func (p *Program) Start() error {
defer func() { for _, module := range p.moduleList {
if err := recover(); err != nil { if err := module.Start(); err != nil {
fmt.Printf("Start err: %v", err) return err
debug.PrintStack() }
_ = p.Stop()
} }
}()
discover.Listen() discover.Listen()
p.server = server.NewServer(config.Get().Serve.Grpc.TTL)
p.server.Init(config.Get().Serve.Grpc.Address, config.Get().Serve.Grpc.Port)
log.Infof(fmt.Sprintf("%v Start successful...", config.Get().App.Name))
return nil return nil
} }
func (p *Program) Stop() error { func (p *Program) Stop() error {
defer func() {
if err := recover(); err != nil {
fmt.Printf("Stop err: %v", err)
debug.PrintStack()
}
}()
discover.Close() discover.Close()
p.server.Close() for i := len(p.moduleList) - 1; i >= 0; i-- {
_ = etcd.Close() 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 return nil
} }

View File

@@ -7,7 +7,11 @@ import (
"scene/config" "scene/config"
) )
func (p *Program) initBase() error { // ModuleBase 基础模块,或者一些零散的模块
type ModuleBase struct {
}
func (p *ModuleBase) Init() error {
// 配置 // 配置
if err := config.LoadConfig(); err != nil { if err := config.LoadConfig(); err != nil {
return err return err
@@ -17,6 +21,13 @@ func (p *Program) initBase() error {
log.Init(cfg.Log.Debug, cfg.Log.MaxSize, cfg.Log.MaxBackups, cfg.Log.MaxAge, cfg.Log.Level) log.Init(cfg.Log.Debug, cfg.Log.MaxSize, cfg.Log.MaxBackups, cfg.Log.MaxAge, cfg.Log.Level)
// 雪花 // 雪花
utils.InitSnowflake(int64(rand.Intn(1000))) utils.InitSnowflake(int64(rand.Intn(1000)))
return nil
}
func (p *ModuleBase) Start() error {
return nil
}
func (p *ModuleBase) Stop() error {
return nil return nil
} }

View File

@@ -2,10 +2,16 @@ package app
import ( import (
"common/db/etcd" "common/db/etcd"
"common/log"
"scene/config" "scene/config"
) )
func (p *Program) initDB(cfg *config.Config) error { // ModuleDB 数据库模块
type ModuleDB struct {
}
func (p *ModuleDB) Init() error {
cfg := config.Get()
// ETCD // ETCD
if err := etcd.Init(cfg.DB.Etcd.Address); err != nil { if err := etcd.Init(cfg.DB.Etcd.Address); err != nil {
return err return err
@@ -13,8 +19,13 @@ func (p *Program) initDB(cfg *config.Config) error {
return nil return nil
} }
func (p *Program) stopDB() error { func (p *ModuleDB) Start() error {
_ = etcd.Close() return nil
}
func (p *ModuleDB) Stop() error {
if err := etcd.Close(); err != nil {
log.Errorf("close etcd failed: %v", err)
}
return nil return nil
} }

27
Server/scene/app/grpc.go Normal file
View File

@@ -0,0 +1,27 @@
package app
import (
"common/net/grpc/service"
"scene/config"
"scene/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
}

View File

@@ -4,8 +4,8 @@ app:
log: log:
debug: true debug: true
level: "debug" level: "debug"
maxSize: 100 maxSize: 10
maxBackups: 3 maxBackups: 100
maxAge: 7 maxAge: 7
db: db:

View File

@@ -0,0 +1,19 @@
app:
name: "scene-prod"
log:
debug: false
level: "debug"
maxSize: 10
maxBackups: 100
maxAge: 7
db:
etcd:
address: [ "172.18.28.0:2379" ]
serve:
grpc:
address: "172.18.28.0"
port: 8504
ttl: 20