与 file/shell 同为被控 HTTP 能力: BS 与客户机被控端共用, 经 zonat 隧道 + web-gateway typ=process 反代出网
119 lines
3.8 KiB
Go
119 lines
3.8 KiB
Go
// 远程进程管理:进程列表 / 结束进程。
|
|
// gopsutil 跨平台实现,与 file/shell 同为被控 HTTP 能力——BS 与客户机被控端共用,
|
|
// 经 zonat 隧道 + web-gateway 反代出网(typ=process,页面 www/process.html)。
|
|
|
|
package process
|
|
|
|
import (
|
|
"net/http"
|
|
"os/exec"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/shirou/gopsutil/v3/process"
|
|
)
|
|
|
|
// ProcItem 进程列表行。
|
|
type ProcItem struct {
|
|
Pid int32 `json:"pid"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
User string `json:"user"`
|
|
CPUPercent float64 `json:"cpuPercent"` // 进程存活期累计 CPU 占用率(gopsutil 口径)
|
|
MemRSS uint64 `json:"memBytes"` // 物理内存占用(字节)
|
|
MemPercent float32 `json:"memPercent"`
|
|
CreateTime int64 `json:"createTime"` // 启动时间(Unix 毫秒)
|
|
Exe string `json:"exe"`
|
|
Cmdline string `json:"cmdline"`
|
|
}
|
|
|
|
// RegisterRoutersProcess 装配进程管理路由: GET /process/list, POST /process/kill。
|
|
func RegisterRoutersProcess(r *gin.RouterGroup) {
|
|
r.GET("/process/list", listProcesses())
|
|
r.POST("/process/kill", killProcess())
|
|
}
|
|
|
|
// listProcesses 全量进程列表(按内存占用降序)。
|
|
func listProcesses() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
procs, err := process.Processes()
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "msg": "枚举进程失败: " + err.Error(), "data": nil})
|
|
return
|
|
}
|
|
items := make([]ProcItem, 0, len(procs))
|
|
for _, p := range procs {
|
|
item := ProcItem{Pid: p.Pid}
|
|
if name, e := p.Name(); e == nil {
|
|
item.Name = name
|
|
}
|
|
if st, e := p.Status(); e == nil && len(st) > 0 {
|
|
item.Status = st[0]
|
|
}
|
|
if u, e := p.Username(); e == nil {
|
|
item.User = u
|
|
}
|
|
if cpu, e := p.CPUPercent(); e == nil {
|
|
item.CPUPercent = cpu
|
|
}
|
|
if mem, e := p.MemoryInfo(); e == nil {
|
|
item.MemRSS = mem.RSS
|
|
}
|
|
if mp, e := p.MemoryPercent(); e == nil {
|
|
item.MemPercent = mp
|
|
}
|
|
if ct, e := p.CreateTime(); e == nil {
|
|
item.CreateTime = ct
|
|
}
|
|
if exe, e := p.Exe(); e == nil {
|
|
item.Exe = exe
|
|
}
|
|
if cmd, e := p.Cmdline(); e == nil {
|
|
item.Cmdline = cmd
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
sort.Slice(items, func(i, j int) bool { return items[i].MemPercent > items[j].MemPercent })
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "OK", "data": gin.H{"list": items}})
|
|
}
|
|
}
|
|
|
|
// killProcess 结束进程: {pid: 必填, name: 可选校验(防 pid 复用误杀)}。
|
|
// Windows 上 gopsutil 权限不足时回退 taskkill /F /T(连子进程树强杀)。
|
|
func killProcess() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var req struct {
|
|
Pid int32 `json:"pid"`
|
|
Name string `json:"name"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil || req.Pid <= 0 {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "msg": "参数错误: pid 必填且为正整数", "data": nil})
|
|
return
|
|
}
|
|
p, err := process.NewProcess(req.Pid)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "msg": "进程不存在: " + err.Error(), "data": nil})
|
|
return
|
|
}
|
|
if req.Name != "" {
|
|
if name, e := p.Name(); e == nil && name != "" && name != req.Name {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "msg": "进程名不匹配(实际 " + name + "), 已拒绝结束", "data": nil})
|
|
return
|
|
}
|
|
}
|
|
if err := p.Kill(); err != nil {
|
|
if runtime.GOOS != "windows" {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "msg": "结束进程失败: " + err.Error(), "data": nil})
|
|
return
|
|
}
|
|
if out, e := exec.Command("taskkill", "/F", "/T", "/PID", strconv.Itoa(int(req.Pid))).CombinedOutput(); e != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "msg": "结束进程失败: " + err.Error() + "; taskkill: " + string(out), "data": nil})
|
|
return
|
|
}
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "OK", "data": gin.H{"pid": req.Pid}})
|
|
}
|
|
}
|