mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-29 16:06:36 +08:00
1098 lines
27 KiB
Go
1098 lines
27 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-gost/core/service"
|
|
"github.com/go-gost/x/config"
|
|
parser "github.com/go-gost/x/config/parsing/service"
|
|
kill "github.com/go-gost/x/internal/util/port"
|
|
"github.com/go-gost/x/registry"
|
|
)
|
|
|
|
// swagger:parameters createServiceRequest
|
|
type createServiceRequest struct {
|
|
// in: body
|
|
Data config.ServiceConfig `json:"data"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response createServiceResponse
|
|
type createServiceResponse struct {
|
|
Data Response
|
|
}
|
|
|
|
// swagger:parameters createServicesRequest
|
|
type createServicesRequest struct {
|
|
// in: body
|
|
Data []config.ServiceConfig `json:"data"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response createServicesResponse
|
|
type createServicesResponse struct {
|
|
Data Response
|
|
}
|
|
|
|
func createService(ctx *gin.Context) {
|
|
// swagger:route POST /config/services Service createServiceRequest
|
|
//
|
|
// Create a new service, the name of the service must be unique in service list.
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: createServiceResponse
|
|
|
|
var req createServiceRequest
|
|
ctx.ShouldBindJSON(&req.Data)
|
|
|
|
name := strings.TrimSpace(req.Data.Name)
|
|
if name == "" {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "service name is required"))
|
|
return
|
|
}
|
|
req.Data.Name = name
|
|
|
|
if registry.ServiceRegistry().IsRegistered(name) {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
|
return
|
|
}
|
|
|
|
svc, err := parser.ParseService(&req.Data)
|
|
if err != nil {
|
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create service %s failed: %s", name, err.Error())))
|
|
return
|
|
}
|
|
|
|
if err := registry.ServiceRegistry().Register(name, svc); err != nil {
|
|
svc.Close()
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
|
return
|
|
}
|
|
|
|
go svc.Serve()
|
|
|
|
config.OnUpdate(func(c *config.Config) error {
|
|
c.Services = append(c.Services, &req.Data)
|
|
return nil
|
|
})
|
|
|
|
ctx.JSON(http.StatusOK, Response{
|
|
Msg: "OK",
|
|
})
|
|
}
|
|
|
|
func createServices(ctx *gin.Context) {
|
|
// swagger:route POST /config/services/batch Service createServicesRequest
|
|
//
|
|
// Create multiple services at once (transactional: all succeed or all fail).
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: createServicesResponse
|
|
|
|
var req createServicesRequest
|
|
ctx.ShouldBindJSON(&req.Data)
|
|
|
|
if len(req.Data) == 0 {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "services list cannot be empty"))
|
|
return
|
|
}
|
|
|
|
// 第一阶段:验证所有服务配置
|
|
var parsedServices []struct {
|
|
config config.ServiceConfig
|
|
service service.Service
|
|
}
|
|
|
|
for _, serviceConfig := range req.Data {
|
|
name := strings.TrimSpace(serviceConfig.Name)
|
|
if name == "" {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "service name is required"))
|
|
return
|
|
}
|
|
serviceConfig.Name = name
|
|
|
|
if registry.ServiceRegistry().IsRegistered(name) {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
|
return
|
|
}
|
|
|
|
svc, err := parser.ParseService(&serviceConfig)
|
|
if err != nil {
|
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create service %s failed: %s", name, err.Error())))
|
|
return
|
|
}
|
|
|
|
parsedServices = append(parsedServices, struct {
|
|
config config.ServiceConfig
|
|
service service.Service
|
|
}{serviceConfig, svc})
|
|
}
|
|
|
|
// 第二阶段:注册所有服务
|
|
var registeredServices []string
|
|
for _, ps := range parsedServices {
|
|
if err := registry.ServiceRegistry().Register(ps.config.Name, ps.service); err != nil {
|
|
// 如果注册失败,回滚已注册的服务
|
|
for _, regName := range registeredServices {
|
|
if svc := registry.ServiceRegistry().Get(regName); svc != nil {
|
|
registry.ServiceRegistry().Unregister(regName)
|
|
svc.Close()
|
|
}
|
|
}
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", ps.config.Name)))
|
|
return
|
|
}
|
|
registeredServices = append(registeredServices, ps.config.Name)
|
|
}
|
|
|
|
// 第三阶段:启动所有服务
|
|
for _, ps := range parsedServices {
|
|
if svc := registry.ServiceRegistry().Get(ps.config.Name); svc != nil {
|
|
go svc.Serve()
|
|
}
|
|
}
|
|
|
|
// 第四阶段:更新配置
|
|
config.OnUpdate(func(c *config.Config) error {
|
|
for _, ps := range parsedServices {
|
|
c.Services = append(c.Services, &ps.config)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
ctx.JSON(http.StatusOK, Response{
|
|
Msg: "OK",
|
|
})
|
|
}
|
|
|
|
// swagger:parameters updateServiceRequest
|
|
type updateServiceRequest struct {
|
|
// in: path
|
|
// required: true
|
|
Service string `uri:"service" json:"service"`
|
|
// in: body
|
|
Data config.ServiceConfig `json:"data"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response updateServiceResponse
|
|
type updateServiceResponse struct {
|
|
Data Response
|
|
}
|
|
|
|
// swagger:parameters updateServicesRequest
|
|
type updateServicesRequest struct {
|
|
// in: body
|
|
Data []config.ServiceConfig `json:"data"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response updateServicesResponse
|
|
type updateServicesResponse struct {
|
|
Data Response
|
|
}
|
|
|
|
func updateService(ctx *gin.Context) {
|
|
// swagger:route PUT /config/services/{service} Service updateServiceRequest
|
|
//
|
|
// Update service by name, the service must already exist.
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: updateServiceResponse
|
|
|
|
var req updateServiceRequest
|
|
ctx.ShouldBindUri(&req)
|
|
ctx.ShouldBindJSON(&req.Data)
|
|
|
|
name := strings.TrimSpace(req.Service)
|
|
|
|
old := registry.ServiceRegistry().Get(name)
|
|
if old == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
|
return
|
|
}
|
|
old.Close()
|
|
registry.ServiceRegistry().Unregister(name)
|
|
|
|
// 等待端口释放
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
req.Data.Name = name
|
|
|
|
svc, err := parser.ParseService(&req.Data)
|
|
if err != nil {
|
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create service %s failed: %s", name, err.Error())))
|
|
return
|
|
}
|
|
|
|
if err := registry.ServiceRegistry().Register(name, svc); err != nil {
|
|
svc.Close()
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
|
return
|
|
}
|
|
|
|
go svc.Serve()
|
|
|
|
config.OnUpdate(func(c *config.Config) error {
|
|
for i := range c.Services {
|
|
if c.Services[i].Name == name {
|
|
c.Services[i] = &req.Data
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
ctx.JSON(http.StatusOK, Response{
|
|
Msg: "OK",
|
|
})
|
|
}
|
|
|
|
func updateServices(ctx *gin.Context) {
|
|
// swagger:route PUT /config/services/batch Service updateServicesRequest
|
|
//
|
|
// Update multiple services at once (transactional: all succeed or all fail).
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: updateServicesResponse
|
|
|
|
var req updateServicesRequest
|
|
ctx.ShouldBindJSON(&req.Data)
|
|
|
|
if len(req.Data) == 0 {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "services list cannot be empty"))
|
|
return
|
|
}
|
|
|
|
// 第一阶段:验证所有服务存在
|
|
for _, serviceConfig := range req.Data {
|
|
name := strings.TrimSpace(serviceConfig.Name)
|
|
if name == "" {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "service name is required"))
|
|
return
|
|
}
|
|
serviceConfig.Name = name
|
|
|
|
old := registry.ServiceRegistry().Get(name)
|
|
if old == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
|
return
|
|
}
|
|
}
|
|
|
|
// 第二阶段:按照原来的updateService逻辑,逐个更新服务
|
|
for _, serviceConfig := range req.Data {
|
|
name := strings.TrimSpace(serviceConfig.Name)
|
|
serviceConfig.Name = name
|
|
|
|
// 1. 获取旧服务
|
|
old := registry.ServiceRegistry().Get(name)
|
|
|
|
// 2. 关闭旧服务
|
|
old.Close()
|
|
|
|
// 3. 从注册表移除旧服务
|
|
registry.ServiceRegistry().Unregister(name)
|
|
|
|
// 4. 解析新服务配置
|
|
svc, err := parser.ParseService(&serviceConfig)
|
|
if err != nil {
|
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create service %s failed: %s", name, err.Error())))
|
|
return
|
|
}
|
|
|
|
// 5. 注册新服务
|
|
if err := registry.ServiceRegistry().Register(name, svc); err != nil {
|
|
svc.Close()
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
|
return
|
|
}
|
|
|
|
// 6. 启动新服务
|
|
go svc.Serve()
|
|
}
|
|
|
|
// 第三阶段:更新配置
|
|
config.OnUpdate(func(c *config.Config) error {
|
|
for _, serviceConfig := range req.Data {
|
|
for i := range c.Services {
|
|
if c.Services[i].Name == serviceConfig.Name {
|
|
c.Services[i] = &serviceConfig
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
ctx.JSON(http.StatusOK, Response{
|
|
Msg: "OK",
|
|
})
|
|
}
|
|
|
|
// swagger:parameters deleteServiceRequest
|
|
type deleteServiceRequest struct {
|
|
// in: path
|
|
// required: true
|
|
Service string `uri:"service" json:"service"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response deleteServiceResponse
|
|
type deleteServiceResponse struct {
|
|
Data Response
|
|
}
|
|
|
|
// swagger:parameters deleteServicesRequest
|
|
type deleteServicesRequest struct {
|
|
// in: body
|
|
Services []string `json:"services"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response deleteServicesResponse
|
|
type deleteServicesResponse struct {
|
|
Data Response
|
|
}
|
|
|
|
func deleteService(ctx *gin.Context) {
|
|
// swagger:route DELETE /config/services/{service} Service deleteServiceRequest
|
|
//
|
|
// Delete service by name.
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: deleteServiceResponse
|
|
|
|
var req deleteServiceRequest
|
|
ctx.ShouldBindUri(&req)
|
|
|
|
name := strings.TrimSpace(req.Service)
|
|
|
|
svc := registry.ServiceRegistry().Get(name)
|
|
if svc == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
|
return
|
|
}
|
|
|
|
registry.ServiceRegistry().Unregister(name)
|
|
svc.Close()
|
|
|
|
config.OnUpdate(func(c *config.Config) error {
|
|
services := c.Services
|
|
c.Services = nil
|
|
for _, s := range services {
|
|
if s.Name == name {
|
|
continue
|
|
}
|
|
c.Services = append(c.Services, s)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
ctx.JSON(http.StatusOK, Response{
|
|
Msg: "OK",
|
|
})
|
|
}
|
|
|
|
func deleteServices(ctx *gin.Context) {
|
|
// swagger:route DELETE /config/services/batch Service deleteServicesRequest
|
|
//
|
|
// Delete multiple services at once (transactional: all succeed or all fail).
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: deleteServicesResponse
|
|
|
|
var req deleteServicesRequest
|
|
ctx.ShouldBindJSON(&req)
|
|
|
|
if len(req.Services) == 0 {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "services list cannot be empty"))
|
|
return
|
|
}
|
|
|
|
// 第一阶段:验证所有服务是否存在
|
|
var servicesToDelete []struct {
|
|
name string
|
|
service service.Service
|
|
}
|
|
|
|
for _, serviceName := range req.Services {
|
|
name := strings.TrimSpace(serviceName)
|
|
if name == "" {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "service name is required"))
|
|
return
|
|
}
|
|
|
|
svc := registry.ServiceRegistry().Get(name)
|
|
if svc == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
|
return
|
|
}
|
|
|
|
servicesToDelete = append(servicesToDelete, struct {
|
|
name string
|
|
service service.Service
|
|
}{name, svc})
|
|
}
|
|
|
|
// 第二阶段:删除所有服务
|
|
for _, std := range servicesToDelete {
|
|
registry.ServiceRegistry().Unregister(std.name)
|
|
std.service.Close()
|
|
}
|
|
|
|
// 第三阶段:更新配置
|
|
config.OnUpdate(func(c *config.Config) error {
|
|
services := c.Services
|
|
c.Services = nil
|
|
for _, s := range services {
|
|
shouldDelete := false
|
|
for _, std := range servicesToDelete {
|
|
if s.Name == std.name {
|
|
shouldDelete = true
|
|
break
|
|
}
|
|
}
|
|
if !shouldDelete {
|
|
c.Services = append(c.Services, s)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
ctx.JSON(http.StatusOK, Response{
|
|
Msg: "OK",
|
|
})
|
|
}
|
|
|
|
// swagger:parameters pauseServiceRequest
|
|
type pauseServiceRequest struct {
|
|
// in: path
|
|
// required: true
|
|
Service string `uri:"service" json:"service"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response pauseServiceResponse
|
|
type pauseServiceResponse struct {
|
|
Data Response
|
|
}
|
|
|
|
// swagger:parameters resumeServiceRequest
|
|
type resumeServiceRequest struct {
|
|
// in: path
|
|
// required: true
|
|
Service string `uri:"service" json:"service"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response resumeServiceResponse
|
|
type resumeServiceResponse struct {
|
|
Data Response
|
|
}
|
|
|
|
func pauseService(ctx *gin.Context) {
|
|
// swagger:route POST /config/services/{service}/pause Service pauseServiceRequest
|
|
//
|
|
// Pause service by name, the service will be stopped but configuration remains.
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: pauseServiceResponse
|
|
|
|
var req pauseServiceRequest
|
|
ctx.ShouldBindUri(&req)
|
|
|
|
name := strings.TrimSpace(req.Service)
|
|
|
|
svc := registry.ServiceRegistry().Get(name)
|
|
if svc == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
|
return
|
|
}
|
|
|
|
// 获取服务地址用于强制断开连接
|
|
var serviceAddr string
|
|
cfg := config.Global()
|
|
for _, s := range cfg.Services {
|
|
if s.Name == name {
|
|
serviceAddr = s.Addr
|
|
break
|
|
}
|
|
}
|
|
|
|
// 强制断开端口的所有连接
|
|
if serviceAddr != "" {
|
|
_ = kill.ForceClosePortConnections(serviceAddr)
|
|
}
|
|
|
|
// 更新配置中的暂停状态
|
|
config.OnUpdate(func(c *config.Config) error {
|
|
for i := range c.Services {
|
|
if c.Services[i].Name == name {
|
|
if c.Services[i].Metadata == nil {
|
|
c.Services[i].Metadata = make(map[string]any)
|
|
}
|
|
c.Services[i].Metadata["paused"] = true
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
ctx.JSON(http.StatusOK, Response{
|
|
Msg: "OK",
|
|
})
|
|
}
|
|
|
|
func resumeService(ctx *gin.Context) {
|
|
// swagger:route POST /config/services/{service}/resume Service resumeServiceRequest
|
|
//
|
|
// Resume paused service by name.
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: resumeServiceResponse
|
|
|
|
var req resumeServiceRequest
|
|
ctx.ShouldBindUri(&req)
|
|
|
|
name := strings.TrimSpace(req.Service)
|
|
|
|
// 检查服务是否存在于注册表中
|
|
existingSvc := registry.ServiceRegistry().Get(name)
|
|
if existingSvc == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
|
return
|
|
}
|
|
|
|
// 查找配置中的服务
|
|
var serviceConfig *config.ServiceConfig
|
|
cfg := config.Global()
|
|
for _, s := range cfg.Services {
|
|
if s.Name == name {
|
|
serviceConfig = s
|
|
break
|
|
}
|
|
}
|
|
|
|
if serviceConfig == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s configuration not found", name)))
|
|
return
|
|
}
|
|
|
|
// 检查是否处于暂停状态
|
|
if serviceConfig.Metadata == nil {
|
|
serviceConfig.Metadata = make(map[string]any)
|
|
}
|
|
|
|
paused, exists := serviceConfig.Metadata["paused"]
|
|
if !exists || paused != true {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, fmt.Sprintf("service %s is not paused", name)))
|
|
return
|
|
}
|
|
|
|
// 先关闭现有服务
|
|
existingSvc.Close()
|
|
registry.ServiceRegistry().Unregister(name)
|
|
|
|
// 等待端口释放
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// 重新解析并启动服务
|
|
svc, err := parser.ParseService(serviceConfig)
|
|
if err != nil {
|
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("resume service %s failed: %s", name, err.Error())))
|
|
return
|
|
}
|
|
|
|
if err := registry.ServiceRegistry().Register(name, svc); err != nil {
|
|
svc.Close()
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
|
return
|
|
}
|
|
|
|
go svc.Serve()
|
|
|
|
// 更新配置,移除暂停状态
|
|
config.OnUpdate(func(c *config.Config) error {
|
|
for i := range c.Services {
|
|
if c.Services[i].Name == name {
|
|
if c.Services[i].Metadata != nil {
|
|
delete(c.Services[i].Metadata, "paused")
|
|
// 如果 metadata 为空,设置为 nil
|
|
if len(c.Services[i].Metadata) == 0 {
|
|
c.Services[i].Metadata = nil
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
ctx.JSON(http.StatusOK, Response{
|
|
Msg: "OK",
|
|
})
|
|
}
|
|
|
|
// swagger:parameters getServiceStatusRequest
|
|
type getServiceStatusRequest struct {
|
|
// in: path
|
|
// required: true
|
|
Service string `uri:"service" json:"service"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response getServiceStatusResponse
|
|
type getServiceStatusResponse struct {
|
|
Data ServiceStatusInfo
|
|
}
|
|
|
|
type ServiceStatusInfo struct {
|
|
Name string `json:"name"`
|
|
Status string `json:"status"` // running, paused, stopped
|
|
Paused bool `json:"paused"`
|
|
}
|
|
|
|
func getServiceStatus(ctx *gin.Context) {
|
|
// swagger:route GET /config/services/{service}/status Service getServiceStatusRequest
|
|
//
|
|
// Get service status by name.
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: getServiceStatusResponse
|
|
|
|
var req getServiceStatusRequest
|
|
ctx.ShouldBindUri(&req)
|
|
|
|
name := strings.TrimSpace(req.Service)
|
|
|
|
// 检查服务是否在注册表中
|
|
svc := registry.ServiceRegistry().Get(name)
|
|
if svc == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
|
return
|
|
}
|
|
|
|
// 检查配置中的暂停状态
|
|
var paused bool
|
|
cfg := config.Global()
|
|
for _, s := range cfg.Services {
|
|
if s.Name == name && s.Metadata != nil {
|
|
if pausedVal, exists := s.Metadata["paused"]; exists && pausedVal == true {
|
|
paused = true
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
status := "running"
|
|
if paused {
|
|
status = "paused"
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, getServiceStatusResponse{
|
|
Data: ServiceStatusInfo{
|
|
Name: name,
|
|
Status: status,
|
|
Paused: paused,
|
|
},
|
|
})
|
|
}
|
|
|
|
// 回滚已暂停的服务 - 重新启动它们
|
|
func rollbackPausedServices(pausedServices []struct {
|
|
name string
|
|
service service.Service
|
|
serviceConfig *config.ServiceConfig
|
|
}) {
|
|
for _, pss := range pausedServices {
|
|
// 重新解析并启动服务
|
|
svc, err := parser.ParseService(pss.serviceConfig)
|
|
if err != nil {
|
|
continue // 回滚失败,记录日志但继续处理其他服务
|
|
}
|
|
|
|
if err := registry.ServiceRegistry().Register(pss.name, svc); err != nil {
|
|
svc.Close()
|
|
continue // 回滚失败,记录日志但继续处理其他服务
|
|
}
|
|
|
|
go svc.Serve()
|
|
|
|
// 移除暂停状态标记
|
|
config.OnUpdate(func(c *config.Config) error {
|
|
for i := range c.Services {
|
|
if c.Services[i].Name == pss.name {
|
|
if c.Services[i].Metadata != nil {
|
|
delete(c.Services[i].Metadata, "paused")
|
|
if len(c.Services[i].Metadata) == 0 {
|
|
c.Services[i].Metadata = nil
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
// 回滚已恢复的服务 - 将它们重新暂停
|
|
func rollbackResumedServices(resumedServices []struct {
|
|
name string
|
|
service service.Service
|
|
serviceConfig *config.ServiceConfig
|
|
}) {
|
|
for _, rss := range resumedServices {
|
|
// 关闭已恢复的服务
|
|
if svc := registry.ServiceRegistry().Get(rss.name); svc != nil {
|
|
svc.Close()
|
|
}
|
|
|
|
// 重新标记为暂停状态
|
|
config.OnUpdate(func(c *config.Config) error {
|
|
for i := range c.Services {
|
|
if c.Services[i].Name == rss.name {
|
|
if c.Services[i].Metadata == nil {
|
|
c.Services[i].Metadata = make(map[string]any)
|
|
}
|
|
c.Services[i].Metadata["paused"] = true
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
// swagger:parameters pauseServicesRequest
|
|
type pauseServicesRequest struct {
|
|
// in: body
|
|
Services []string `json:"services"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response pauseServicesResponse
|
|
type pauseServicesResponse struct {
|
|
Data Response
|
|
}
|
|
|
|
// swagger:parameters resumeServicesRequest
|
|
type resumeServicesRequest struct {
|
|
// in: body
|
|
Services []string `json:"services"`
|
|
}
|
|
|
|
// successful operation.
|
|
// swagger:response resumeServicesResponse
|
|
type resumeServicesResponse struct {
|
|
Data Response
|
|
}
|
|
|
|
func pauseServices(ctx *gin.Context) {
|
|
// swagger:route POST /config/services/batch/pause Service pauseServicesRequest
|
|
//
|
|
// Pause multiple services at once (transactional: all succeed or all fail).
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: pauseServicesResponse
|
|
|
|
var req pauseServicesRequest
|
|
ctx.ShouldBindJSON(&req)
|
|
|
|
if len(req.Services) == 0 {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "services list cannot be empty"))
|
|
return
|
|
}
|
|
|
|
// 第一阶段:验证所有服务是否存在,并筛选需要暂停的服务
|
|
var servicesToPause []struct {
|
|
name string
|
|
service service.Service
|
|
}
|
|
var skippedServices []string
|
|
|
|
cfg := config.Global()
|
|
for _, serviceName := range req.Services {
|
|
name := strings.TrimSpace(serviceName)
|
|
if name == "" {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "service name is required"))
|
|
return
|
|
}
|
|
|
|
svc := registry.ServiceRegistry().Get(name)
|
|
if svc == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
|
return
|
|
}
|
|
|
|
// 检查服务是否已经暂停
|
|
var serviceConfig *config.ServiceConfig
|
|
for _, s := range cfg.Services {
|
|
if s.Name == name {
|
|
serviceConfig = s
|
|
break
|
|
}
|
|
}
|
|
|
|
// 如果服务已经暂停,跳过
|
|
if serviceConfig != nil && serviceConfig.Metadata != nil {
|
|
if pausedVal, exists := serviceConfig.Metadata["paused"]; exists && pausedVal == true {
|
|
skippedServices = append(skippedServices, name)
|
|
continue
|
|
}
|
|
}
|
|
|
|
servicesToPause = append(servicesToPause, struct {
|
|
name string
|
|
service service.Service
|
|
}{name, svc})
|
|
}
|
|
|
|
// 第二阶段:事务性暂停所有服务
|
|
var pausedServices []struct {
|
|
name string
|
|
service service.Service
|
|
serviceConfig *config.ServiceConfig
|
|
}
|
|
|
|
// 获取服务配置
|
|
serviceConfigs := make(map[string]*config.ServiceConfig)
|
|
for _, s := range cfg.Services {
|
|
serviceConfigs[s.Name] = s
|
|
}
|
|
|
|
// 逐个暂停服务,如果失败则回滚
|
|
for _, stp := range servicesToPause {
|
|
serviceConfig := serviceConfigs[stp.name]
|
|
if serviceConfig == nil {
|
|
// 找不到配置,回滚已暂停的服务
|
|
rollbackPausedServices(pausedServices)
|
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("service %s configuration not found", stp.name)))
|
|
return
|
|
}
|
|
|
|
// 强制断开端口的所有连接
|
|
if serviceConfig.Addr != "" {
|
|
_ = kill.ForceClosePortConnections(serviceConfig.Addr)
|
|
}
|
|
|
|
// 记录已暂停的服务
|
|
pausedServices = append(pausedServices, struct {
|
|
name string
|
|
service service.Service
|
|
serviceConfig *config.ServiceConfig
|
|
}{stp.name, stp.service, serviceConfig})
|
|
}
|
|
|
|
// 第三阶段:更新配置,标记暂停状态
|
|
err := config.OnUpdate(func(c *config.Config) error {
|
|
for _, stp := range servicesToPause {
|
|
for i := range c.Services {
|
|
if c.Services[i].Name == stp.name {
|
|
if c.Services[i].Metadata == nil {
|
|
c.Services[i].Metadata = make(map[string]any)
|
|
}
|
|
c.Services[i].Metadata["paused"] = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
// 配置更新失败,需要回滚所有暂停的服务
|
|
rollbackPausedServices(pausedServices)
|
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("Failed to update config, rolling back paused services: %v", err)))
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, Response{
|
|
Msg: "OK",
|
|
})
|
|
}
|
|
|
|
func resumeServices(ctx *gin.Context) {
|
|
// swagger:route POST /config/services/batch/resume Service resumeServicesRequest
|
|
//
|
|
// Resume multiple paused services at once (transactional: all succeed or all fail).
|
|
//
|
|
// Security:
|
|
// basicAuth: []
|
|
//
|
|
// Responses:
|
|
// 200: resumeServicesResponse
|
|
|
|
var req resumeServicesRequest
|
|
ctx.ShouldBindJSON(&req)
|
|
|
|
if len(req.Services) == 0 {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "services list cannot be empty"))
|
|
return
|
|
}
|
|
|
|
// 第一阶段:验证所有服务是否存在,并筛选需要恢复的服务
|
|
var servicesToResume []struct {
|
|
name string
|
|
service service.Service
|
|
serviceConfig *config.ServiceConfig
|
|
}
|
|
var skippedServices []string
|
|
|
|
cfg := config.Global()
|
|
for _, serviceName := range req.Services {
|
|
name := strings.TrimSpace(serviceName)
|
|
if name == "" {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "service name is required"))
|
|
return
|
|
}
|
|
|
|
// 检查服务是否存在
|
|
svc := registry.ServiceRegistry().Get(name)
|
|
if svc == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
|
return
|
|
}
|
|
|
|
// 查找配置中的服务
|
|
var serviceConfig *config.ServiceConfig
|
|
for _, s := range cfg.Services {
|
|
if s.Name == name {
|
|
serviceConfig = s
|
|
break
|
|
}
|
|
}
|
|
|
|
if serviceConfig == nil {
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s configuration not found", name)))
|
|
return
|
|
}
|
|
|
|
// 检查是否处于暂停状态
|
|
paused := false
|
|
if serviceConfig.Metadata != nil {
|
|
if pausedVal, exists := serviceConfig.Metadata["paused"]; exists && pausedVal == true {
|
|
paused = true
|
|
}
|
|
}
|
|
|
|
// 如果服务没有暂停(即正在运行),跳过
|
|
if !paused {
|
|
skippedServices = append(skippedServices, name)
|
|
continue
|
|
}
|
|
|
|
servicesToResume = append(servicesToResume, struct {
|
|
name string
|
|
service service.Service
|
|
serviceConfig *config.ServiceConfig
|
|
}{name, svc, serviceConfig})
|
|
}
|
|
|
|
// 第二阶段:事务性恢复所有服务
|
|
var resumedServices []struct {
|
|
name string
|
|
service service.Service
|
|
serviceConfig *config.ServiceConfig
|
|
}
|
|
|
|
// 逐个恢复服务,如果失败则回滚
|
|
for _, str := range servicesToResume {
|
|
// 先关闭现有服务
|
|
str.service.Close()
|
|
registry.ServiceRegistry().Unregister(str.name)
|
|
|
|
// 等待端口释放
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// 重新解析并启动服务
|
|
svc, err := parser.ParseService(str.serviceConfig)
|
|
if err != nil {
|
|
// 恢复失败,回滚已恢复的服务
|
|
rollbackResumedServices(resumedServices)
|
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("resume service %s failed: %s", str.name, err.Error())))
|
|
return
|
|
}
|
|
|
|
if err := registry.ServiceRegistry().Register(str.name, svc); err != nil {
|
|
svc.Close()
|
|
// 恢复失败,回滚已恢复的服务
|
|
rollbackResumedServices(resumedServices)
|
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", str.name)))
|
|
return
|
|
}
|
|
|
|
go svc.Serve()
|
|
|
|
// 记录已成功恢复的服务
|
|
resumedServices = append(resumedServices, str)
|
|
}
|
|
|
|
// 第三阶段:更新配置,移除暂停状态
|
|
err := config.OnUpdate(func(c *config.Config) error {
|
|
for _, str := range servicesToResume {
|
|
for i := range c.Services {
|
|
if c.Services[i].Name == str.name {
|
|
if c.Services[i].Metadata != nil {
|
|
delete(c.Services[i].Metadata, "paused")
|
|
// 如果 metadata 为空,设置为 nil
|
|
if len(c.Services[i].Metadata) == 0 {
|
|
c.Services[i].Metadata = nil
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
// 配置更新失败,回滚所有已恢复的服务
|
|
rollbackResumedServices(resumedServices)
|
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("Failed to update config, rolling back resumed services: %v", err)))
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, Response{
|
|
Msg: "OK",
|
|
})
|
|
}
|