Files
managed-portal/internal/server/server.go
2026-04-27 10:04:36 +08:00

224 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package server
import (
"errors"
"io"
"net/http"
"os"
"strconv"
"strings"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"managed-portal/internal/config"
"managed-portal/internal/managed"
"managed-portal/internal/webdevice"
)
type Server struct {
cfg *config.Config
engine *gin.Engine
managedManager *managed.Manager
webDeviceSvc *webdevice.Service
}
func New(cfg *config.Config) *Server {
if cfg == nil {
cfg = config.Load()
}
engine := gin.New()
engine.Use(gin.Logger(), gin.Recovery())
engine.Use(cors.Default())
srv := &Server{
cfg: cfg,
engine: engine,
}
srv.managedManager = managed.NewManager(loadRegistry(cfg.RegistryPath), nil, nil)
srv.webDeviceSvc = webdevice.NewService()
srv.registerRoutes()
return srv
}
func (s *Server) registerRoutes() {
api := s.engine.Group("/api")
api.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
api.GET("/managed-services", s.listManagedServices)
api.GET("/managed-services/:id", s.getManagedService)
api.PUT("/managed-services/:id/config", s.updateManagedServiceConfig)
api.POST("/managed-services/:id/restart", s.restartManagedService)
api.GET("/managed-services/:id/results/summary", s.getManagedServiceSummary)
api.GET("/managed-services/:id/results/files", s.listManagedServiceFiles)
api.GET("/managed-services/:id/results/preview", s.previewManagedServiceFile)
api.GET("/managed-services/:id/results/download", s.downloadManagedServiceFile)
api.GET("/web-devices/scan", s.scanWebDevices)
s.engine.Any("/proxy/web/:ip/*proxyPath", s.proxyWebDevice)
}
func (s *Server) Engine() *gin.Engine {
return s.engine
}
func (s *Server) Start() error {
return s.engine.Run(s.cfg.HTTPAddr)
}
func loadRegistry(path string) *managed.Registry {
registry, err := managed.LoadRegistry(path)
if err != nil {
return managed.EmptyRegistry()
}
return registry
}
func (s *Server) listManagedServices(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"services": s.managedManager.List(),
})
}
func (s *Server) getManagedService(c *gin.Context) {
service, err := s.managedManager.Detail(c.Param("id"))
if err != nil {
s.handleManagedError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"service": service})
}
func (s *Server) updateManagedServiceConfig(c *gin.Context) {
var req struct {
RTSPURL string `json:"rtsp_url"`
RTSP string `json:"rtsp"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
rtsp := strings.TrimSpace(req.RTSPURL)
if rtsp == "" {
rtsp = strings.TrimSpace(req.RTSP)
}
service, err := s.managedManager.UpdateRTSP(c.Param("id"), rtsp)
if err != nil {
s.handleManagedError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"service": service})
}
func (s *Server) restartManagedService(c *gin.Context) {
service, err := s.managedManager.Restart(c.Param("id"))
if err != nil {
s.handleManagedError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"service": service})
}
func (s *Server) getManagedServiceSummary(c *gin.Context) {
summary, err := s.managedManager.Summary(c.Param("id"))
if err != nil {
s.handleManagedError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"summary": summary})
}
func (s *Server) listManagedServiceFiles(c *gin.Context) {
files, err := s.managedManager.Files(c.Param("id"))
if err != nil {
s.handleManagedError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"files": files})
}
func (s *Server) previewManagedServiceFile(c *gin.Context) {
lines := 2000
if raw := strings.TrimSpace(c.Query("lines")); raw != "" {
parsed, err := strconv.Atoi(raw)
if err != nil || parsed <= 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "lines 参数必须为正整数"})
return
}
if parsed > 2000 {
parsed = 2000
}
lines = parsed
}
preview, err := s.managedManager.PreviewFile(c.Param("id"), c.Query("path"), lines)
if err != nil {
s.handleManagedError(c, err)
return
}
c.JSON(http.StatusOK, preview)
}
func (s *Server) downloadManagedServiceFile(c *gin.Context) {
resp, err := s.managedManager.Download(c.Request.Context(), c.Param("id"), c.Query("path"))
if err != nil {
s.handleManagedError(c, err)
return
}
defer resp.Body.Close()
if contentType := resp.Header.Get("Content-Type"); contentType != "" {
c.Header("Content-Type", contentType)
}
if contentDisposition := resp.Header.Get("Content-Disposition"); contentDisposition != "" {
c.Header("Content-Disposition", contentDisposition)
}
if _, err := io.Copy(c.Writer, resp.Body); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "读取文件失败"})
return
}
}
func (s *Server) handleManagedError(c *gin.Context, err error) {
switch {
case errors.Is(err, managed.ErrServiceNotFound):
c.JSON(http.StatusNotFound, gin.H{"error": "被管理服务不存在"})
case os.IsNotExist(err):
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
case strings.Contains(err.Error(), "rtsp url"):
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
case strings.Contains(err.Error(), "invalid file path"):
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
}
func (s *Server) scanWebDevices(c *gin.Context) {
result, err := s.webDeviceSvc.Scan(c.Request)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取网卡信息失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
func (s *Server) proxyWebDevice(c *gin.Context) {
err := s.webDeviceSvc.ProxyHTTP(c.Writer, c.Request, c.Param("ip"), c.Param("proxyPath"))
switch {
case err == nil:
return
case errors.Is(err, webdevice.ErrInvalidTargetIP):
c.JSON(http.StatusBadRequest, gin.H{"error": "仅支持内网IPv4地址"})
case errors.Is(err, webdevice.ErrTargetNotAllowed):
c.JSON(http.StatusForbidden, gin.H{"error": "目标IP未在扫描结果中请先扫描网页设备"})
case errors.Is(err, webdevice.ErrInvalidProxyURL):
c.JSON(http.StatusInternalServerError, gin.H{"error": "代理目标无效"})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
}