Add files via upload

This commit is contained in:
公明
2026-03-25 02:17:01 +08:00
committed by GitHub
parent f6525674d2
commit 60846b2152
4 changed files with 137 additions and 5 deletions

View File

@@ -862,7 +862,9 @@ func setupRoutes(
protected.POST("/webshell/connections", webshellHandler.CreateConnection)
protected.GET("/webshell/connections/:id/ai-history", webshellHandler.GetAIHistory)
protected.GET("/webshell/connections/:id/ai-conversations", webshellHandler.ListAIConversations)
protected.GET("/webshell/connections/:id/state", webshellHandler.GetConnectionState)
protected.PUT("/webshell/connections/:id", webshellHandler.UpdateConnection)
protected.PUT("/webshell/connections/:id/state", webshellHandler.SaveConnectionState)
protected.DELETE("/webshell/connections/:id", webshellHandler.DeleteConnection)
protected.POST("/webshell/exec", webshellHandler.Exec)
protected.POST("/webshell/file", webshellHandler.FileOp)

View File

@@ -240,6 +240,15 @@ func (db *DB) initTables() error {
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);`
// 创建 WebShell 连接扩展状态表(前端工作区/终端状态持久化)
createWebshellConnectionStatesTable := `
CREATE TABLE IF NOT EXISTS webshell_connection_states (
connection_id TEXT PRIMARY KEY,
state_json TEXT NOT NULL DEFAULT '{}',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (connection_id) REFERENCES webshell_connections(id) ON DELETE CASCADE
);`
// 创建索引
createIndexes := `
CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id);
@@ -267,6 +276,7 @@ func (db *DB) initTables() error {
CREATE INDEX IF NOT EXISTS idx_batch_task_queues_created_at ON batch_task_queues(created_at);
CREATE INDEX IF NOT EXISTS idx_batch_task_queues_title ON batch_task_queues(title);
CREATE INDEX IF NOT EXISTS idx_webshell_connections_created_at ON webshell_connections(created_at);
CREATE INDEX IF NOT EXISTS idx_webshell_connection_states_updated_at ON webshell_connection_states(updated_at);
`
if _, err := db.Exec(createConversationsTable); err != nil {
@@ -329,6 +339,10 @@ func (db *DB) initTables() error {
return fmt.Errorf("创建webshell_connections表失败: %w", err)
}
if _, err := db.Exec(createWebshellConnectionStatesTable); err != nil {
return fmt.Errorf("创建webshell_connection_states表失败: %w", err)
}
// 为已有表添加新字段(如果不存在)- 必须在创建索引之前
if err := db.migrateConversationsTable(); err != nil {
db.logger.Warn("迁移conversations表失败", zap.Error(err))

View File

@@ -19,6 +19,42 @@ type WebShellConnection struct {
CreatedAt time.Time `json:"createdAt"`
}
// GetWebshellConnectionState 获取连接关联的持久化状态 JSON不存在时返回 "{}"
func (db *DB) GetWebshellConnectionState(connectionID string) (string, error) {
var stateJSON string
err := db.QueryRow(`SELECT state_json FROM webshell_connection_states WHERE connection_id = ?`, connectionID).Scan(&stateJSON)
if err == sql.ErrNoRows {
return "{}", nil
}
if err != nil {
db.logger.Error("查询 WebShell 连接状态失败", zap.Error(err), zap.String("connectionID", connectionID))
return "", err
}
if stateJSON == "" {
stateJSON = "{}"
}
return stateJSON, nil
}
// UpsertWebshellConnectionState 保存连接关联的持久化状态 JSON
func (db *DB) UpsertWebshellConnectionState(connectionID, stateJSON string) error {
if stateJSON == "" {
stateJSON = "{}"
}
query := `
INSERT INTO webshell_connection_states (connection_id, state_json, updated_at)
VALUES (?, ?, ?)
ON CONFLICT(connection_id) DO UPDATE SET
state_json = excluded.state_json,
updated_at = excluded.updated_at
`
if _, err := db.Exec(query, connectionID, stateJSON, time.Now()); err != nil {
db.logger.Error("保存 WebShell 连接状态失败", zap.Error(err), zap.String("connectionID", connectionID))
return err
}
return nil
}
// ListWebshellConnections 列出所有 WebShell 连接,按创建时间倒序
func (db *DB) ListWebshellConnections() ([]WebShellConnection, error) {
query := `

View File

@@ -3,6 +3,7 @@ package handler
import (
"bytes"
"database/sql"
"encoding/json"
"io"
"net/http"
"net/url"
@@ -104,10 +105,10 @@ func (h *WebShellHandler) CreateConnection(c *gin.Context) {
ID: "ws_" + strings.ReplaceAll(uuid.New().String(), "-", "")[:12],
URL: req.URL,
Password: strings.TrimSpace(req.Password),
Type: shellType,
Method: method,
Type: shellType,
Method: method,
CmdParam: strings.TrimSpace(req.CmdParam),
Remark: strings.TrimSpace(req.Remark),
Remark: strings.TrimSpace(req.Remark),
CreatedAt: time.Now(),
}
if err := h.db.CreateWebshellConnection(conn); err != nil {
@@ -197,6 +198,85 @@ func (h *WebShellHandler) DeleteConnection(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// GetConnectionState 获取 WebShell 连接关联的前端持久化状态GET /api/webshell/connections/:id/state
func (h *WebShellHandler) GetConnectionState(c *gin.Context) {
if h.db == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "database not available"})
return
}
id := strings.TrimSpace(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
return
}
conn, err := h.db.GetWebshellConnection(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if conn == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "connection not found"})
return
}
stateJSON, err := h.db.GetWebshellConnectionState(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var state interface{}
if err := json.Unmarshal([]byte(stateJSON), &state); err != nil {
state = map[string]interface{}{}
}
c.JSON(http.StatusOK, gin.H{"state": state})
}
// SaveConnectionState 保存 WebShell 连接关联的前端持久化状态PUT /api/webshell/connections/:id/state
func (h *WebShellHandler) SaveConnectionState(c *gin.Context) {
if h.db == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "database not available"})
return
}
id := strings.TrimSpace(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
return
}
conn, err := h.db.GetWebshellConnection(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if conn == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "connection not found"})
return
}
var req struct {
State json.RawMessage `json:"state"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
raw := req.State
if len(raw) == 0 {
raw = json.RawMessage(`{}`)
}
if len(raw) > 2*1024*1024 {
c.JSON(http.StatusBadRequest, gin.H{"error": "state payload too large (max 2MB)"})
return
}
var anyJSON interface{}
if err := json.Unmarshal(raw, &anyJSON); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "state must be valid json"})
return
}
if err := h.db.UpsertWebshellConnectionState(id, string(raw)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// GetAIHistory 获取指定 WebShell 连接的 AI 助手对话历史GET /api/webshell/connections/:id/ai-history
func (h *WebShellHandler) GetAIHistory(c *gin.Context) {
if h.db == nil {
@@ -267,8 +347,8 @@ type FileOpRequest struct {
URL string `json:"url" binding:"required"`
Password string `json:"password"`
Type string `json:"type"`
Method string `json:"method"` // GET 或 POST空则默认 POST
CmdParam string `json:"cmd_param"` // 命令参数名,如 cmd/xxx空则默认 cmd
Method string `json:"method"` // GET 或 POST空则默认 POST
CmdParam string `json:"cmd_param"` // 命令参数名,如 cmd/xxx空则默认 cmd
Action string `json:"action" binding:"required"` // list, read, delete, write, mkdir, rename, upload, upload_chunk
Path string `json:"path"`
TargetPath string `json:"target_path"` // rename 时目标路径