仰望星辰工作室

clearlove2.1

clearlove2.1/ internal/plugin/sandbox.go 2.6 KB · 103 行 原始文件
1// 沙箱与治理:权限校验、审计日志、失败熔断。
2package plugin
3
4import (
5 "errors"
6 "strings"
7 "sync"
8 "time"
9
10 "clearlove/internal/database"
11 "clearlove/internal/models"
12 "clearlove/internal/util"
13)
14
15var errNoApp = errors.New("插件未加载")
16
17// permError 权限未声明错误
18type permError struct{ slug, perm string }
19
20func (e *permError) Error() string {
21 return "插件 " + e.slug + " 未声明权限 " + e.perm + ",请在 plugin.json 的 permissions 中声明后重试"
22}
23
24// requirePerm 运行时侧的权限校验入口(Host API 统一从这里进入)
25func (rt *jsRuntime) requirePerm(perm string) error {
26 if rt == nil || rt.app == nil {
27 return errNoApp
28 }
29 return rt.app.requirePerm(perm)
30}
31
32// requirePerm 校验插件是否声明了指定权限(能力白名单的唯一入口)
33func (a *App) requirePerm(perm string) error {
34 if a == nil || a.Plugin == nil {
35 return errNoApp
36 }
37 if !a.Plugin.HasPermission(perm) {
38 return &permError{slug: a.Plugin.SlugOf(), perm: perm}
39 }
40 return nil
41}
42
43// logPlugin 写插件运行日志(失败静默,不阻断插件逻辑)
44func logPlugin(slug, action, detail string, dur time.Duration) {
45 if database.DB == nil {
46 return
47 }
48 detail = strings.TrimSpace(detail)
49 if len(detail) > 2000 {
50 detail = detail[:2000]
51 }
52 _, _ = database.DB.Exec(
53 "INSERT INTO plugin_logs(plugin_slug,action,detail,duration_ms,created_at) VALUES(?,?,?,?,?)",
54 slug, action, detail, dur.Milliseconds(), models.Now())
55}
56
57// ---------- 失败熔断 ----------
58
59const failThreshold = 20 // 连续失败阈值,达到后自动禁用插件
60
61var (
62 failMu sync.Mutex
63 failCount = map[string]int{}
64)
65
66// noteFailure 记录一次插件失败;达到阈值自动禁用,避免拖垮站点
67func noteFailure(a *App, err error) {
68 if a == nil || a.Plugin == nil {
69 return
70 }
71 slug := a.Plugin.SlugOf()
72 failMu.Lock()
73 failCount[slug]++
74 n := failCount[slug]
75 failMu.Unlock()
76 util.Log("warn", "插件 %s 执行失败(%d/%d): %v", slug, n, failThreshold, err)
77 if n == failThreshold {
78 util.Log("error", "插件 %s 连续失败达到 %d 次,已自动禁用", slug, failThreshold)
79 logPlugin(slug, "disable", "连续失败自动禁用", 0)
80 SetEnabled(a.Plugin.Name, false)
81 UnloadApp(a.Dir)
82 failMu.Lock()
83 delete(failCount, slug)
84 failMu.Unlock()
85 }
86}
87
88// noteSuccess 重置失败计数
89func noteSuccess(a *App) {
90 if a == nil || a.Plugin == nil {
91 return
92 }
93 failMu.Lock()
94 delete(failCount, a.Plugin.SlugOf())
95 failMu.Unlock()
96}
97
98// FailCount 查询插件当前连续失败次数(后台展示用)
99func FailCount(slug string) int {
100 failMu.Lock()
101 defer failMu.Unlock()
102 return failCount[slug]
103}