Files
remote-core/process/process_http.go
T
w11 c7dd0d9c6b fix(process): kill 兜底弃 taskkill 命令行, 改 Windows API 进程树强杀
- Toolhelp32 快照枚举父子关系→收集后代(防环)→OpenProcess+TerminateProcess 按 PID 定向结束
- 产品红线: 系统操作禁拼 cmd 命令字符串(2026-10-09 用户令, 与 zomaintain AGENTS §一同源)
- wire 不变: 仅错误文案 taskkill 段改'进程树终止'
2026-10-09 22:01:00 +08:00

120 lines
3.7 KiB
Go

// 远程进程管理:进程列表 / 结束进程。
// gopsutil 跨平台实现,与 file/shell 同为被控 HTTP 能力——BS 与客户机被控端共用,
// 经 zonat 隧道 + web-gateway 反代出网(typ=process,页面 www/process.html)。
package process
import (
"encoding/json"
"net/http"
"runtime"
"sort"
"github.com/shirou/gopsutil/v3/process"
"git.zeroonesoft.cn/golib/remote-core/webhttp"
)
// 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 webhttp.Router) {
r.GET("/process/list", listProcesses)
r.POST("/process/kill", killProcess)
}
func fail(w http.ResponseWriter, msg string) {
webhttp.WriteJSON(w, http.StatusOK, map[string]any{"code": 1, "msg": msg, "data": nil})
}
// listProcesses 全量进程列表(按内存占用降序)。
func listProcesses(w http.ResponseWriter, _ *http.Request) {
procs, err := process.Processes()
if err != nil {
fail(w, "枚举进程失败: "+err.Error())
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 })
webhttp.WriteJSON(w, http.StatusOK, map[string]any{"code": 0, "msg": "OK", "data": map[string]any{"list": items}})
}
// killProcess 结束进程: {pid: 必填, name: 可选校验(防 pid 复用误杀)}。
// Windows 上 gopsutil 权限不足时回退进程树强杀(Windows API: Toolhelp32 快照+TerminateProcess,
// 2026-10-09 起替代原 taskkill /F /T 命令行兜底——产品红线禁命令行查杀)。
func killProcess(w http.ResponseWriter, r *http.Request) {
var req struct {
Pid int32 `json:"pid"`
Name string `json:"name"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Pid <= 0 {
fail(w, "参数错误: pid 必填且为正整数")
return
}
p, err := process.NewProcess(req.Pid)
if err != nil {
fail(w, "进程不存在: "+err.Error())
return
}
if req.Name != "" {
if name, e := p.Name(); e == nil && name != "" && name != req.Name {
fail(w, "进程名不匹配(实际 "+name+"), 已拒绝结束")
return
}
}
if err := p.Kill(); err != nil {
if runtime.GOOS != "windows" {
fail(w, "结束进程失败: "+err.Error())
return
}
if e := killProcessTree(uint32(req.Pid)); e != nil {
fail(w, "结束进程失败: "+err.Error()+"; 进程树终止: "+e.Error())
return
}
}
webhttp.WriteJSON(w, http.StatusOK, map[string]any{"code": 0, "msg": "OK", "data": map[string]any{"pid": req.Pid}})
}