2026-05-08 07:27:06 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-05-12 23:53:16 +08:00
|
|
|
|
"apigo.cc/go/config"
|
2026-05-09 17:20:32 +08:00
|
|
|
|
"apigo.cc/go/discover"
|
2026-05-08 07:27:06 +08:00
|
|
|
|
"apigo.cc/go/log"
|
2026-05-10 12:44:25 +08:00
|
|
|
|
"apigo.cc/go/redis"
|
2026-05-09 22:51:14 +08:00
|
|
|
|
"apigo.cc/go/safe"
|
2026-05-12 23:18:31 +08:00
|
|
|
|
"apigo.cc/go/starter"
|
2026-05-08 07:27:06 +08:00
|
|
|
|
"context"
|
2026-05-09 17:20:32 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
|
"golang.org/x/net/http2"
|
|
|
|
|
|
"golang.org/x/net/http2/h2c"
|
2026-05-08 07:27:06 +08:00
|
|
|
|
"net"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"os"
|
2026-05-09 17:20:32 +08:00
|
|
|
|
"strings"
|
2026-05-08 07:27:06 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-09 21:11:50 +08:00
|
|
|
|
// GlobalDiscoverer 供服务框架内部使用的发现实例
|
|
|
|
|
|
var GlobalDiscoverer *discover.Discoverer
|
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
// WebServer 实现了 starter.Service 和 starter.Reloader 接口
|
|
|
|
|
|
type WebServer struct {
|
2026-05-09 17:20:32 +08:00
|
|
|
|
server *http.Server
|
|
|
|
|
|
listener net.Listener
|
|
|
|
|
|
Addr string
|
|
|
|
|
|
useDiscover bool
|
2026-05-09 21:11:50 +08:00
|
|
|
|
discoverer *discover.Discoverer
|
2026-05-12 23:53:16 +08:00
|
|
|
|
logger *log.Logger
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
// NewWebServer 创建并返回一个新的 WebServer 实例
|
|
|
|
|
|
func NewWebServer() *WebServer {
|
|
|
|
|
|
return &WebServer{}
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
// Start 启动服务,实现 starter.Service 接口
|
|
|
|
|
|
func (ws *WebServer) Start(ctx context.Context, logger *log.Logger) error {
|
2026-05-12 23:53:16 +08:00
|
|
|
|
if logger == nil {
|
|
|
|
|
|
logger = log.DefaultLogger
|
|
|
|
|
|
}
|
|
|
|
|
|
ws.logger = logger
|
|
|
|
|
|
|
2026-05-09 17:20:32 +08:00
|
|
|
|
listenStr := Config.Listen
|
2026-05-12 23:18:31 +08:00
|
|
|
|
ws.useDiscover = false
|
2026-05-09 17:20:32 +08:00
|
|
|
|
|
|
|
|
|
|
if listenStr == "" {
|
|
|
|
|
|
listenStr = ":0,h2c"
|
2026-05-12 23:18:31 +08:00
|
|
|
|
ws.useDiscover = true
|
2026-05-09 17:20:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析第一个监听配置
|
|
|
|
|
|
part := strings.Split(listenStr, "|")[0]
|
|
|
|
|
|
addr, opts, _ := strings.Cut(part, ",")
|
|
|
|
|
|
|
|
|
|
|
|
protocol := "http"
|
|
|
|
|
|
for _, opt := range strings.Split(opts, ",") {
|
|
|
|
|
|
opt = strings.ToLower(strings.TrimSpace(opt))
|
|
|
|
|
|
if opt == "h2c" || opt == "h2" {
|
|
|
|
|
|
protocol = opt
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !strings.Contains(addr, ":") {
|
|
|
|
|
|
addr = ":" + addr
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否需要启动服务发现
|
2026-05-10 12:44:25 +08:00
|
|
|
|
if Config.App == "" {
|
|
|
|
|
|
Config.App = GetDefaultName()
|
2026-05-09 17:20:32 +08:00
|
|
|
|
}
|
2026-05-10 12:44:25 +08:00
|
|
|
|
appName := Config.App
|
2026-05-09 22:51:14 +08:00
|
|
|
|
if appName != "" || Config.Register != "" {
|
2026-05-12 23:18:31 +08:00
|
|
|
|
ws.useDiscover = true
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 12:44:25 +08:00
|
|
|
|
// 初始化服务器唯一标识 (8位,物理上限 3,844/s)
|
|
|
|
|
|
serverId = IDMaker.Get8Bytes4KPerSecond()
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化分布式 ID 生成器
|
|
|
|
|
|
if Config.IdServer != "" {
|
|
|
|
|
|
rd := redis.GetRedis(Config.IdServer, log.New(serverId))
|
|
|
|
|
|
if rd.Error == nil {
|
|
|
|
|
|
IDMaker = redis.NewIDMaker(rd)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 17:20:32 +08:00
|
|
|
|
listener, err := net.Listen("tcp", addr)
|
2026-05-08 07:27:06 +08:00
|
|
|
|
if err != nil {
|
2026-05-12 23:18:31 +08:00
|
|
|
|
return fmt.Errorf("failed to listen on %s: %w", addr, err)
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
ws.listener = listener
|
|
|
|
|
|
ws.Addr = listener.Addr().String()
|
|
|
|
|
|
serverAddr = ws.Addr
|
2026-05-08 07:27:06 +08:00
|
|
|
|
|
2026-05-09 17:20:32 +08:00
|
|
|
|
// 如果使用了随机端口且没有明确指定不需要服务发现,则开启
|
|
|
|
|
|
if addr == ":0" || strings.HasSuffix(addr, ":0") {
|
2026-05-12 23:18:31 +08:00
|
|
|
|
ws.useDiscover = true
|
2026-05-09 17:20:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h2s := &http2.Server{}
|
|
|
|
|
|
var handler http.Handler = &RouteHandler{}
|
|
|
|
|
|
if protocol == "h2c" {
|
|
|
|
|
|
handler = h2c.NewHandler(handler, h2s)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
ws.server = &http.Server{
|
2026-05-10 12:44:25 +08:00
|
|
|
|
Handler: handler,
|
|
|
|
|
|
ReadTimeout: time.Duration(Config.ReadTimeout) * time.Millisecond,
|
|
|
|
|
|
ReadHeaderTimeout: time.Duration(Config.ReadHeaderTimeout) * time.Millisecond,
|
|
|
|
|
|
WriteTimeout: time.Duration(Config.WriteTimeout) * time.Millisecond,
|
|
|
|
|
|
IdleTimeout: time.Duration(Config.IdleTimeout) * time.Millisecond,
|
|
|
|
|
|
MaxHeaderBytes: Config.MaxHeaderBytes,
|
2026-05-09 17:20:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动服务发现
|
2026-05-12 23:18:31 +08:00
|
|
|
|
if ws.useDiscover {
|
|
|
|
|
|
_, port, _ := net.SplitHostPort(ws.Addr)
|
2026-05-09 17:20:32 +08:00
|
|
|
|
ip := GetServerIp()
|
|
|
|
|
|
discoverAddr := fmt.Sprintf("%s:%s", ip, port)
|
|
|
|
|
|
|
2026-05-09 22:51:14 +08:00
|
|
|
|
// 转换配置
|
|
|
|
|
|
discConf := discover.Config{
|
|
|
|
|
|
Weight: Config.Weight,
|
|
|
|
|
|
CallRetryTimes: 10, // Default
|
|
|
|
|
|
Calls: make(map[string]discover.CallConfig),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if discConf.Weight <= 0 {
|
|
|
|
|
|
discConf.Weight = 100
|
|
|
|
|
|
}
|
2026-05-09 21:11:50 +08:00
|
|
|
|
|
2026-05-09 22:51:14 +08:00
|
|
|
|
for name, call := range Config.Calls {
|
|
|
|
|
|
dc := discover.CallConfig{
|
|
|
|
|
|
Http2: call.Http2,
|
|
|
|
|
|
SSL: call.SSL,
|
|
|
|
|
|
}
|
|
|
|
|
|
if call.Timeout > 0 {
|
|
|
|
|
|
dc.Timeout = time.Duration(call.Timeout) * time.Millisecond
|
|
|
|
|
|
} else if Config.RedirectTimeout > 0 {
|
|
|
|
|
|
dc.Timeout = time.Duration(Config.RedirectTimeout) * time.Millisecond
|
|
|
|
|
|
}
|
|
|
|
|
|
if call.Token != "" {
|
|
|
|
|
|
dc.Token = safe.NewSafeBuf([]byte(call.Token))
|
|
|
|
|
|
}
|
|
|
|
|
|
discConf.Calls[name] = dc
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析必需的 Register,支持环境变量 fallback
|
|
|
|
|
|
registry := Config.Register
|
|
|
|
|
|
if registry == "" {
|
|
|
|
|
|
registry = os.Getenv("DISCOVER_REGISTRY")
|
|
|
|
|
|
}
|
2026-05-09 21:11:50 +08:00
|
|
|
|
if registry == "" {
|
|
|
|
|
|
registry = "127.0.0.1:6379::15" // Default fallback
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
ws.discoverer = discover.Start(registry, appName, discoverAddr, logger, discConf)
|
|
|
|
|
|
GlobalDiscoverer = ws.discoverer
|
|
|
|
|
|
if ws.discoverer != nil {
|
|
|
|
|
|
logger.Info("discover registered", "app", appName, "addr", discoverAddr)
|
2026-05-09 17:20:32 +08:00
|
|
|
|
}
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
errChan := make(chan error, 1)
|
2026-05-08 07:27:06 +08:00
|
|
|
|
|
|
|
|
|
|
go func() {
|
2026-05-12 23:18:31 +08:00
|
|
|
|
logger.Info("service starting", "addr", ws.Addr, "proto", protocol)
|
|
|
|
|
|
if err := ws.server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
|
|
|
|
|
errChan <- err
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}
|
2026-05-12 23:18:31 +08:00
|
|
|
|
close(errChan)
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}()
|
2026-05-12 23:18:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 短暂等待验证是否闪退
|
|
|
|
|
|
select {
|
|
|
|
|
|
case err := <-errChan:
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
case <-time.After(100 * time.Millisecond):
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
// Stop 停止服务,实现 starter.Service 接口
|
|
|
|
|
|
func (ws *WebServer) Stop(ctx context.Context) error {
|
2026-05-12 23:53:16 +08:00
|
|
|
|
logger := ws.logger
|
|
|
|
|
|
if logger == nil {
|
|
|
|
|
|
logger = log.DefaultLogger
|
|
|
|
|
|
}
|
|
|
|
|
|
logger.Info("service stopping")
|
2026-05-12 23:18:31 +08:00
|
|
|
|
if ws.discoverer != nil {
|
|
|
|
|
|
ws.discoverer.Stop()
|
|
|
|
|
|
}
|
|
|
|
|
|
if ws.server != nil {
|
|
|
|
|
|
if err := ws.server.Shutdown(ctx); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-12 23:53:16 +08:00
|
|
|
|
logger.Info("service stopped")
|
2026-05-12 23:18:31 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 11:11:13 +08:00
|
|
|
|
// Status 检查服务健康状态,实现 starter.Service 接口
|
|
|
|
|
|
func (ws *WebServer) Status() (string, error) {
|
2026-05-12 23:18:31 +08:00
|
|
|
|
if ws.server == nil {
|
2026-05-13 11:11:13 +08:00
|
|
|
|
return "", fmt.Errorf("server is not running")
|
2026-05-09 17:20:32 +08:00
|
|
|
|
}
|
2026-05-13 11:11:13 +08:00
|
|
|
|
return ws.Addr, nil
|
2026-05-12 23:18:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Reload 实现配置重新加载,实现 starter.Reloader 接口
|
|
|
|
|
|
func (ws *WebServer) Reload() error {
|
2026-05-12 23:53:16 +08:00
|
|
|
|
logger := ws.logger
|
|
|
|
|
|
if logger == nil {
|
|
|
|
|
|
logger = log.DefaultLogger
|
|
|
|
|
|
}
|
|
|
|
|
|
logger.Info("reloading configurations...")
|
|
|
|
|
|
|
|
|
|
|
|
// 重新加载配置文件中的策略
|
|
|
|
|
|
appName := Config.App
|
|
|
|
|
|
if appName == "" {
|
|
|
|
|
|
appName = GetDefaultName()
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := config.Load(&Config, appName); err != nil {
|
|
|
|
|
|
logger.Error("failed to load config during reload", "error", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
ApplyConfig()
|
|
|
|
|
|
|
|
|
|
|
|
// 触发业务挂载的 Hook
|
2026-05-12 23:18:31 +08:00
|
|
|
|
return triggerReload()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AsyncServer 兼容旧版异步服务实例
|
|
|
|
|
|
type AsyncServer struct {
|
|
|
|
|
|
*WebServer
|
|
|
|
|
|
}
|
2026-05-09 17:20:32 +08:00
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
// Stop 兼容旧版的无参数停止方法
|
|
|
|
|
|
func (as *AsyncServer) Stop() {
|
2026-05-10 22:40:31 +08:00
|
|
|
|
stopTimeout := time.Duration(Config.StopTimeout) * time.Millisecond
|
|
|
|
|
|
if stopTimeout <= 0 {
|
|
|
|
|
|
stopTimeout = 5 * time.Second
|
|
|
|
|
|
}
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), stopTimeout)
|
2026-05-08 07:27:06 +08:00
|
|
|
|
defer cancel()
|
2026-05-12 23:18:31 +08:00
|
|
|
|
_ = as.WebServer.Stop(ctx)
|
|
|
|
|
|
}
|
2026-05-08 07:27:06 +08:00
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
// AsyncStart 兼容旧版的异步启动方法
|
|
|
|
|
|
func AsyncStart() *AsyncServer {
|
|
|
|
|
|
ws := NewWebServer()
|
|
|
|
|
|
_ = ws.Start(context.Background(), log.DefaultLogger)
|
|
|
|
|
|
return &AsyncServer{WebServer: ws}
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
// Wait 等待服务结束 (兼容旧版,直接阻塞)
|
2026-05-08 07:27:06 +08:00
|
|
|
|
func (as *AsyncServer) Wait() {
|
2026-05-12 23:18:31 +08:00
|
|
|
|
select {}
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 23:18:31 +08:00
|
|
|
|
// Start 兼容旧版的同步启动方法 (通过内部注册 starter 实现)
|
2026-05-08 07:27:06 +08:00
|
|
|
|
func Start() {
|
2026-05-12 23:18:31 +08:00
|
|
|
|
stopTimeout := time.Duration(Config.StopTimeout) * time.Millisecond
|
|
|
|
|
|
if stopTimeout <= 0 {
|
|
|
|
|
|
stopTimeout = 5 * time.Second
|
|
|
|
|
|
}
|
|
|
|
|
|
starter.Register("web-server", NewWebServer(), 100, 5*time.Second, stopTimeout)
|
|
|
|
|
|
starter.Run()
|
2026-05-08 07:27:06 +08:00
|
|
|
|
}
|