mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-05-11 18:10:23 +08:00
功能: NodeHandler 新增批量创建/轮换 Token/install-token/预览端点
This commit is contained in:
@@ -5,18 +5,32 @@ import (
|
||||
stdhttp "net/http"
|
||||
"strconv"
|
||||
|
||||
"backupx/server/internal/apperror"
|
||||
"backupx/server/internal/installscript"
|
||||
"backupx/server/internal/service"
|
||||
"backupx/server/pkg/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type NodeHandler struct {
|
||||
service *service.NodeService
|
||||
auditService *service.AuditService
|
||||
service *service.NodeService
|
||||
auditService *service.AuditService
|
||||
installTokenSvc *service.InstallTokenService
|
||||
externalURL string
|
||||
}
|
||||
|
||||
func NewNodeHandler(service *service.NodeService, auditService *service.AuditService) *NodeHandler {
|
||||
return &NodeHandler{service: service, auditService: auditService}
|
||||
func NewNodeHandler(
|
||||
nodeService *service.NodeService,
|
||||
auditService *service.AuditService,
|
||||
installTokenSvc *service.InstallTokenService,
|
||||
externalURL string,
|
||||
) *NodeHandler {
|
||||
return &NodeHandler{
|
||||
service: nodeService,
|
||||
auditService: auditService,
|
||||
installTokenSvc: installTokenSvc,
|
||||
externalURL: externalURL,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NodeHandler) List(c *gin.Context) {
|
||||
@@ -128,3 +142,135 @@ func (h *NodeHandler) Heartbeat(c *gin.Context) {
|
||||
}
|
||||
response.Success(c, gin.H{"status": "ok"})
|
||||
}
|
||||
|
||||
// BatchCreate 批量创建远程节点。
|
||||
func (h *NodeHandler) BatchCreate(c *gin.Context) {
|
||||
var input struct {
|
||||
Names []string `json:"names" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(stdhttp.StatusBadRequest, gin.H{"code": "INVALID_INPUT", "message": err.Error()})
|
||||
return
|
||||
}
|
||||
results, err := h.service.BatchCreate(c.Request.Context(), input.Names)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
recordAudit(c, h.auditService, "node", "batch_create", "node", "",
|
||||
fmt.Sprintf("%d", len(results)), fmt.Sprintf("批量创建 %d 个节点", len(results)))
|
||||
response.Success(c, results)
|
||||
}
|
||||
|
||||
// RotateToken 轮换节点的 agent token。
|
||||
func (h *NodeHandler) RotateToken(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
tok, err := h.service.RotateToken(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
recordAudit(c, h.auditService, "node", "rotate_token", "node",
|
||||
fmt.Sprintf("%d", id), "",
|
||||
fmt.Sprintf("轮换节点 Token (ID: %d)", id))
|
||||
response.Success(c, gin.H{"newToken": tok})
|
||||
}
|
||||
|
||||
// CreateInstallToken 生成一次性安装令牌。
|
||||
func (h *NodeHandler) CreateInstallToken(c *gin.Context) {
|
||||
if h.installTokenSvc == nil {
|
||||
response.Error(c, apperror.New(stdhttp.StatusServiceUnavailable,
|
||||
"INSTALL_TOKEN_DISABLED", "一键部署未启用", nil))
|
||||
return
|
||||
}
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
var input struct {
|
||||
Mode string `json:"mode"`
|
||||
Arch string `json:"arch"`
|
||||
AgentVersion string `json:"agentVersion"`
|
||||
DownloadSrc string `json:"downloadSrc"`
|
||||
TTLSeconds int `json:"ttlSeconds"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(stdhttp.StatusBadRequest, gin.H{"code": "INVALID_INPUT", "message": err.Error()})
|
||||
return
|
||||
}
|
||||
// 默认值
|
||||
if input.Mode == "" {
|
||||
input.Mode = "systemd"
|
||||
}
|
||||
if input.Arch == "" {
|
||||
input.Arch = "auto"
|
||||
}
|
||||
if input.DownloadSrc == "" {
|
||||
input.DownloadSrc = "github"
|
||||
}
|
||||
if input.TTLSeconds == 0 {
|
||||
input.TTLSeconds = 900
|
||||
}
|
||||
|
||||
out, err := h.installTokenSvc.Create(c.Request.Context(), service.InstallTokenInput{
|
||||
NodeID: uint(id),
|
||||
Mode: input.Mode,
|
||||
Arch: input.Arch,
|
||||
AgentVersion: input.AgentVersion,
|
||||
DownloadSrc: input.DownloadSrc,
|
||||
TTLSeconds: input.TTLSeconds,
|
||||
CreatedByID: 0, // 如需关联 userID,后续可通过 auth 中间件注入
|
||||
})
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
recordAudit(c, h.auditService, "install_token", "create", "node",
|
||||
fmt.Sprintf("%d", id), out.Node.Name,
|
||||
fmt.Sprintf("生成 %s/%s install token TTL=%ds", input.Mode, input.Arch, input.TTLSeconds))
|
||||
|
||||
masterURL := resolveMasterURL(c, h.externalURL)
|
||||
body := gin.H{
|
||||
"installToken": out.Token,
|
||||
"expiresAt": out.ExpiresAt,
|
||||
"url": masterURL + "/install/" + out.Token,
|
||||
"composeUrl": "",
|
||||
}
|
||||
if input.Mode == "docker" {
|
||||
body["composeUrl"] = masterURL + "/install/" + out.Token + "/compose.yml"
|
||||
}
|
||||
response.Success(c, body)
|
||||
}
|
||||
|
||||
// PreviewScript 预览安装脚本(token 字段用 <AGENT_TOKEN> 占位,不消费 install token)。
|
||||
// 用于 UI Step 3 展开"脚本预览"。
|
||||
func (h *NodeHandler) PreviewScript(c *gin.Context) {
|
||||
mode := c.DefaultQuery("mode", "systemd")
|
||||
arch := c.DefaultQuery("arch", "auto")
|
||||
ver := c.Query("agentVersion")
|
||||
if ver == "" {
|
||||
c.JSON(stdhttp.StatusBadRequest, gin.H{"code": "INVALID_INPUT", "message": "agentVersion required"})
|
||||
return
|
||||
}
|
||||
src := c.DefaultQuery("downloadSrc", "github")
|
||||
ctx := installscript.Context{
|
||||
MasterURL: resolveMasterURL(c, h.externalURL),
|
||||
AgentToken: "<AGENT_TOKEN>",
|
||||
AgentVersion: ver,
|
||||
Mode: mode,
|
||||
Arch: arch,
|
||||
DownloadBase: installscript.DownloadBaseFor(src),
|
||||
InstallPrefix: "/opt/backupx-agent",
|
||||
}
|
||||
script, err := installscript.RenderScript(ctx)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
c.Data(stdhttp.StatusOK, "text/x-shellscript; charset=utf-8", []byte(script))
|
||||
}
|
||||
|
||||
@@ -141,7 +141,8 @@ func NewRouter(deps RouterDependencies) *gin.Engine {
|
||||
database.POST("/discover", databaseHandler.Discover)
|
||||
}
|
||||
|
||||
nodeHandler := NewNodeHandler(deps.NodeService, deps.AuditService)
|
||||
// 临时让 build 通过:InstallTokenService 与 externalURL 传 nil 和 "",Task 11 会替换成真实值
|
||||
nodeHandler := NewNodeHandler(deps.NodeService, deps.AuditService, nil, "")
|
||||
nodes := api.Group("/nodes")
|
||||
nodes.Use(AuthMiddleware(deps.JWTManager))
|
||||
nodes.GET("", nodeHandler.List)
|
||||
|
||||
Reference in New Issue
Block a user