@microi.net/cli 4.6.4 → 4.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/mcp-server.js +98 -92
  2. package/dist/microi-cli.js +177 -26
  3. package/dist/microi-skills.meta.json +147 -141
  4. package/dist/microi.skills/.microi-skills-version.json +2 -2
  5. package/dist/microi.skills/README.md +3 -2
  6. package/dist/microi.skills/ai-engine/SKILL.md +38 -11
  7. package/dist/microi.skills/app-store/SKILL.md +134 -104
  8. package/dist/microi.skills/microi-ai-application/SKILL.md +8 -0
  9. package/dist/microi.skills/microi-client-frontend/SKILL.md +403 -403
  10. package/dist/microi.skills/microi-db-schema/SKILL.md +165 -165
  11. package/dist/microi.skills/microi-deployment/SKILL.md +29 -3
  12. package/dist/microi.skills/microi-docs-coverage/references/capability-map.md +4 -3
  13. package/dist/microi.skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +10 -3
  14. package/dist/microi.skills/microi-form-engine/SKILL.md +165 -165
  15. package/dist/microi.skills/microi-system-delivery/SKILL.md +207 -200
  16. package/dist/microi.skills/microi-ui/SKILL.md +330 -330
  17. package/dist/microi.skills/microi.v8.js +1818 -1758
  18. package/dist/microi.skills/ocr-engine/SKILL.md +111 -0
  19. package/dist/microi.skills/ocr-engine/agents/openai.yaml +4 -0
  20. package/dist/microi.skills/page-engine/SKILL.md +2 -0
  21. package/dist/microi.skills/performance-testing/SKILL.md +2 -2
  22. package/dist/microi.skills/playwright-e2e/SKILL.md +14 -40
  23. package/dist/microi.skills/print-engine/SKILL.md +9 -3
  24. package/dist/microi.skills/report-engine/SKILL.md +1 -1
  25. package/dist/microi.skills/translate-engine/SKILL.md +47 -5
  26. package/dist/microi.skills/ui-design/SKILL.md +1596 -1596
  27. package/dist/microi.skills/ui-design/assets/templates/MCI-DESIGN.md +199 -199
  28. package/dist/microi.skills/ui-design/references/design-pattern-library.md +184 -184
  29. package/dist/microi.skills/ui-design/references/mci-design-contract.md +163 -163
  30. package/dist/microi.skills/v8-file-upload/SKILL.md +8 -0
  31. package/dist/microi.skills/v8-frontend-events/SKILL.md +4 -1
  32. package/dist/microi.skills/v8-frontend-events/references/bluetooth-print.md +28 -22
  33. package/dist/microi.skills/v8-http-integration/SKILL.md +22 -1
  34. package/dist/microi.skills/v8-saas-multi-tenant/SKILL.md +2 -1
  35. package/dist/microi.skills/v8-security/SKILL.md +7 -6
  36. package/dist/microi.skills/v8-utilities/references/server-api-index.md +1 -0
  37. package/dist/microi.skills/workspace-conventions/SKILL.md +15 -23
  38. package/package.json +1 -1
@@ -1,163 +1,163 @@
1
- # MCI-DESIGN 设计契约
2
-
3
- 大型项目在根目录维护 `MCI-DESIGN.md`,让界面意图、精确取值和组件状态可以版本管理、自动检查,并由人和 AI 共同延续。它是 `Microi.UI/src/theme/tokens.css` 的项目级语义说明,不替代真实源码 token,也不替代线框、原型和视觉验收。
4
-
5
- ## 双层单源模型
6
-
7
- 设计契约必须同时包含两层,缺少任一层都不完整:
8
-
9
- 1. **机器可读层**:颜色、字体、间距、圆角、阴影、组件状态和引用关系。它回答“准确使用什么值”,用于检查、差异比较和生成运行时变量。
10
- 2. **人类可读层**:产品对象、受众、情绪目标、视觉隐喻、信息层级、选择理由和明确禁区。它回答“为什么这样设计”,帮助接手者在契约没有覆盖的新场景中作出一致判断。
11
-
12
- 精确值会随项目变化,设计理由决定取舍方向。若两层暂时冲突,先核对用户最新要求和合法源码,再同时修订契约与实现,禁止只改其中一边。
13
-
14
- ## 意图优先级
15
-
16
- 设计前按以下顺序收敛,不从颜色选择器开始:
17
-
18
- 1. 用户进入页面后的首要任务与成功结果。
19
- 2. 页面希望产生的一个主情绪,例如可信、安静、敏捷、温暖或专注。
20
- 3. 一个具体视觉隐喻,例如“安静的专业工作台”“柔和的手工纸张”“夜间发光仪表舱”。具体隐喻比堆叠“高级、现代、极简”等形容词更能约束色彩、材质、密度和动效。
21
- 4. 一组明确的“应当 / 禁止”,把隐含边界写出来。
22
- 5. 最后才是 token、组件和页面实现。
23
-
24
- 一个页面只保留一个主隐喻,辅助气质最多两项。不要把温暖圆润、深色霓虹、通透玻璃和高密度数据等多套语言同时堆在一个界面。
25
-
26
- ## 固定章节顺序
27
-
28
- 核心章节按以下顺序书写,便于稳定解析和审查:
29
-
30
- 1. 产品概览与目标用户
31
- 2. 视觉性格与情绪目标
32
- 3. 颜色
33
- 4. 字体
34
- 5. 布局与间距
35
- 6. 层级、材质与形状
36
- 7. 组件与状态
37
- 8. 页面模式与信息架构
38
- 9. 动效与媒体
39
- 10. 响应式与安全区
40
- 11. 可访问性、性能与降级
41
- 12. 应当与禁止
42
-
43
- 允许在末尾增加项目专属章节,但必须保留未知扩展内容,不能重复核心章节,也不能用近似拼写制造第二套同义章节。确实不适用的项目可以省略某一规则,但要在“有意省略”中说明理由。
44
-
45
- ## 机器可读层
46
-
47
- 机器块应使用语义命名,避免 `blue500`、`bigRadius`、`shadow2` 这类只描述外观、不说明用途的名字。组件可通过 `{路径}` 引用共享 token;引用必须存在,不能形成循环。
48
-
49
- ```yaml
50
- contract:
51
- version: 1
52
- project: 示例项目
53
- mode: data-workspace
54
- intent: 安静、清晰、可快速扫描的专业工作台
55
-
56
- tokens:
57
- color:
58
- canvas: var(--mci-bg-base)
59
- surface: var(--mci-bg-card)
60
- surfaceElevated: var(--mci-bg-elevated)
61
- textPrimary: var(--mci-text-primary)
62
- textSecondary: var(--mci-text-secondary)
63
- primary: var(--mci-color-primary)
64
- danger: var(--mci-color-danger)
65
- typography:
66
- title: { size: 16px, lineHeight: 1.45, weight: 700 }
67
- body: { size: 14px, lineHeight: 1.65, weight: 400 }
68
- meta: { size: 12px, lineHeight: 1.5, weight: 500 }
69
- spacing:
70
- compact: 8px
71
- control: 12px
72
- card: 16px
73
- section: 24px
74
- shape:
75
- control: var(--mci-shape-input)
76
- card: var(--mci-shape-card)
77
- pill: var(--mci-radius-full)
78
- elevation:
79
- card: var(--mci-shadow-card)
80
- cardHover: var(--mci-shadow-card-hover)
81
-
82
- components:
83
- dataCard:
84
- background: "{tokens.color.surface}"
85
- radius: "{tokens.shape.card}"
86
- padding: "{tokens.spacing.card}"
87
- states:
88
- default: { elevation: "{tokens.elevation.card}" }
89
- hover: { elevation: "{tokens.elevation.cardHover}", lift: -2px }
90
- focus: { outline: "{tokens.color.primary}" }
91
- selected: { border: "{tokens.color.primary}" }
92
- disabled: { opacity: 0.56 }
93
-
94
- omissions:
95
- - rule: backgroundVideo
96
- reason: 高频数据页不需要持续媒体,减少干扰与资源开销
97
- ```
98
-
99
- 机器块至少满足:
100
-
101
- - 有主色、页面底色、表面色、主/次文字色和危险色。
102
- - 字体角色包含字号、行高、字重;间距、圆角和层级都有明确单位或 `--mci-*` 引用。
103
- - 组件状态覆盖适用的 default、hover、focus、pressed、loading、empty、error、disabled、selected、success。
104
- - 同一语义只定义一次;组件优先引用共享 token,而不是重新复制值。
105
- - Alpha、混色和玻璃表面必须说明叠加在哪种底色上;只给透明度不算完整颜色定义。
106
-
107
- ## 人类可读层
108
-
109
- 每个核心选择都要写一行理由,尤其是:
110
-
111
- - 为什么这种气质适合目标用户与任务。
112
- - 颜色如何区分主动作、状态、表面和内容层级。
113
- - 字体如何形成阅读节奏,中文和数字如何共存。
114
- - 内部紧凑与外部宽松分别使用哪些间距。
115
- - 深度来自色调层、细边框、环境阴影、透明材质还是实体投影。
116
- - 圆角、切角、胶囊或有机形状表达什么性格。
117
- - 卡片、按钮、输入、导航、弹层在所有状态下如何变化。
118
- - 哪些装饰会破坏任务,应明确禁止。
119
-
120
- ## 可迁移的视觉气质
121
-
122
- 以下不是成品主题,而是把意图翻译为系统规则的示例:
123
-
124
- | 气质 | 色彩与材质 | 形状与间距 | 动效 | 禁止 |
125
- | --- | --- | --- | --- | --- |
126
- | 温暖友好 | 暖白或浅沙底、自然低饱和强调色、轻触感层次 | 圆润或轻微有机形,外部留白宽松 | 柔和上浮与按压 | 冷硬高对比、密集霓虹、尖锐切角 |
127
- | 深色发光 | 深色表面、少量青蓝紫发光语义、清晰高对比文字 | 边界精确、圆角克制、层级紧凑 | 短促聚焦与状态脉冲 | 大面积炫光、彩虹状态色、持续漂移 |
128
- | 雾感通透 | 灰白低彩底、细线边框、轻透明表面 | 大留白、轻圆角、细腻分层 | 慢速淡入与透明度变化 | 厚重投影、不透明色块堆叠、过度模糊 |
129
-
130
- ## 后台数据卡片契约
131
-
132
- 后台数据卡片是高频操作容器,不是营销海报:
133
-
134
- - 固定信息顺序:真实图片或紧凑身份标记 → 标题与状态 → 2—4 个关键字段 → 时间/辅助标签 → 操作区。
135
- - 配置了图片但当前行无图时,使用 40—44px 的首字/图标标记;禁止生成占据卡片三分之一以上的装饰占位图。
136
- - 默认桌面四列,显式列数配置优先;中等宽度自动降为三列或两列,移动端单列。卡片最小可读宽度优先于“同屏塞更多”。
137
- - 标题最多两行,主次文字、金额、状态与时间有固定层级;标签只表达状态或分类,不把每个字段都做成胶囊。
138
- - 操作区优先一个主动作、一至两个次动作,其余收进“更多”;危险动作不得与主动作同权。移动端触控目标不小于 44px。
139
- - 骨架屏必须复刻最终标题、字段和操作区的几何结构;空态解释原因并提供下一步。
140
- - 整卡可进入详情时必须提供键盘焦点、Enter/Space 触发和可见 focus;内部按钮阻止事件冒泡。
141
-
142
- ## 契约检查、差异与输出
143
-
144
- 每次修改契约或 UI 时,按顺序检查:
145
-
146
- 1. **结构**:核心章节存在且顺序正确,无重复章节、疑似拼写错误或无法识别的 token 组。
147
- 2. **类型**:颜色、长度、数字、布尔值和状态对象类型正确,token 式文本没有被误放在普通说明里而漏解析。
148
- 3. **引用**:所有 `{路径}` 可解析,无循环引用;未被组件、页面或输出消费的孤立 token 要删除或说明用途。
149
- 4. **语义**:主色、表面、文字层级、组件状态和有意省略完整;命名能解释用途。
150
- 5. **可访问性**:正文和交互态对比度满足项目标准,焦点可见,键盘路径连续,低动效降级完整。
151
- 6. **差异门禁**:评审设计契约的语义变更和实现变更是否同时出现;意外删除、重命名或大范围 token 漂移应阻止合入。
152
- 7. **输出**:需要生成运行时变量或其它主题格式时,只由已校验的机器块派生,禁止维护第二份手工 token。
153
-
154
- 契约格式若仍在演进,项目必须锁定 `contract.version`,升级时先查看差异并一次性迁移。跨平台脚本要提供不依赖文件扩展名的稳定入口,避免不同终端执行出不同结果。
155
-
156
- ## AI 使用规则
157
-
158
- - 开始实现前完整读取契约和 `ui-design` skill;先复述页面任务、主气质和三条禁止事项,再写页面。
159
- - 契约缺少的精确值优先继承 Microi.UI token;不能用“看起来差不多”的硬编码补洞。
160
- - 先稳定颜色、字体、间距、层级和形状,再定义组件状态;不要在基础 token 尚未收敛时过早堆复杂组件结构。
161
- - 新模式在两个以上页面重复时,先更新契约,再抽成 `Mci*` 或项目级 `mci-*` 组件。
162
- - 修改契约后至少截图一张受影响页面的桌面和移动版本,并覆盖亮/暗主题及相关业务状态。
163
- - 契约、源码、浏览器截图三者冲突时不得宣称完成;修复后重新执行结构检查、定向测试和视觉验收。
1
+ # MCI-DESIGN 设计契约
2
+
3
+ 大型项目在根目录维护 `MCI-DESIGN.md`,让界面意图、精确取值和组件状态可以版本管理、自动检查,并由人和 AI 共同延续。它是 `Microi.UI/src/theme/tokens.css` 的项目级语义说明,不替代真实源码 token,也不替代线框、原型和视觉验收。
4
+
5
+ ## 双层单源模型
6
+
7
+ 设计契约必须同时包含两层,缺少任一层都不完整:
8
+
9
+ 1. **机器可读层**:颜色、字体、间距、圆角、阴影、组件状态和引用关系。它回答“准确使用什么值”,用于检查、差异比较和生成运行时变量。
10
+ 2. **人类可读层**:产品对象、受众、情绪目标、视觉隐喻、信息层级、选择理由和明确禁区。它回答“为什么这样设计”,帮助接手者在契约没有覆盖的新场景中作出一致判断。
11
+
12
+ 精确值会随项目变化,设计理由决定取舍方向。若两层暂时冲突,先核对用户最新要求和合法源码,再同时修订契约与实现,禁止只改其中一边。
13
+
14
+ ## 意图优先级
15
+
16
+ 设计前按以下顺序收敛,不从颜色选择器开始:
17
+
18
+ 1. 用户进入页面后的首要任务与成功结果。
19
+ 2. 页面希望产生的一个主情绪,例如可信、安静、敏捷、温暖或专注。
20
+ 3. 一个具体视觉隐喻,例如“安静的专业工作台”“柔和的手工纸张”“夜间发光仪表舱”。具体隐喻比堆叠“高级、现代、极简”等形容词更能约束色彩、材质、密度和动效。
21
+ 4. 一组明确的“应当 / 禁止”,把隐含边界写出来。
22
+ 5. 最后才是 token、组件和页面实现。
23
+
24
+ 一个页面只保留一个主隐喻,辅助气质最多两项。不要把温暖圆润、深色霓虹、通透玻璃和高密度数据等多套语言同时堆在一个界面。
25
+
26
+ ## 固定章节顺序
27
+
28
+ 核心章节按以下顺序书写,便于稳定解析和审查:
29
+
30
+ 1. 产品概览与目标用户
31
+ 2. 视觉性格与情绪目标
32
+ 3. 颜色
33
+ 4. 字体
34
+ 5. 布局与间距
35
+ 6. 层级、材质与形状
36
+ 7. 组件与状态
37
+ 8. 页面模式与信息架构
38
+ 9. 动效与媒体
39
+ 10. 响应式与安全区
40
+ 11. 可访问性、性能与降级
41
+ 12. 应当与禁止
42
+
43
+ 允许在末尾增加项目专属章节,但必须保留未知扩展内容,不能重复核心章节,也不能用近似拼写制造第二套同义章节。确实不适用的项目可以省略某一规则,但要在“有意省略”中说明理由。
44
+
45
+ ## 机器可读层
46
+
47
+ 机器块应使用语义命名,避免 `blue500`、`bigRadius`、`shadow2` 这类只描述外观、不说明用途的名字。组件可通过 `{路径}` 引用共享 token;引用必须存在,不能形成循环。
48
+
49
+ ```yaml
50
+ contract:
51
+ version: 1
52
+ project: 示例项目
53
+ mode: data-workspace
54
+ intent: 安静、清晰、可快速扫描的专业工作台
55
+
56
+ tokens:
57
+ color:
58
+ canvas: var(--mci-bg-base)
59
+ surface: var(--mci-bg-card)
60
+ surfaceElevated: var(--mci-bg-elevated)
61
+ textPrimary: var(--mci-text-primary)
62
+ textSecondary: var(--mci-text-secondary)
63
+ primary: var(--mci-color-primary)
64
+ danger: var(--mci-color-danger)
65
+ typography:
66
+ title: { size: 16px, lineHeight: 1.45, weight: 700 }
67
+ body: { size: 14px, lineHeight: 1.65, weight: 400 }
68
+ meta: { size: 12px, lineHeight: 1.5, weight: 500 }
69
+ spacing:
70
+ compact: 8px
71
+ control: 12px
72
+ card: 16px
73
+ section: 24px
74
+ shape:
75
+ control: var(--mci-shape-input)
76
+ card: var(--mci-shape-card)
77
+ pill: var(--mci-radius-full)
78
+ elevation:
79
+ card: var(--mci-shadow-card)
80
+ cardHover: var(--mci-shadow-card-hover)
81
+
82
+ components:
83
+ dataCard:
84
+ background: "{tokens.color.surface}"
85
+ radius: "{tokens.shape.card}"
86
+ padding: "{tokens.spacing.card}"
87
+ states:
88
+ default: { elevation: "{tokens.elevation.card}" }
89
+ hover: { elevation: "{tokens.elevation.cardHover}", lift: -2px }
90
+ focus: { outline: "{tokens.color.primary}" }
91
+ selected: { border: "{tokens.color.primary}" }
92
+ disabled: { opacity: 0.56 }
93
+
94
+ omissions:
95
+ - rule: backgroundVideo
96
+ reason: 高频数据页不需要持续媒体,减少干扰与资源开销
97
+ ```
98
+
99
+ 机器块至少满足:
100
+
101
+ - 有主色、页面底色、表面色、主/次文字色和危险色。
102
+ - 字体角色包含字号、行高、字重;间距、圆角和层级都有明确单位或 `--mci-*` 引用。
103
+ - 组件状态覆盖适用的 default、hover、focus、pressed、loading、empty、error、disabled、selected、success。
104
+ - 同一语义只定义一次;组件优先引用共享 token,而不是重新复制值。
105
+ - Alpha、混色和玻璃表面必须说明叠加在哪种底色上;只给透明度不算完整颜色定义。
106
+
107
+ ## 人类可读层
108
+
109
+ 每个核心选择都要写一行理由,尤其是:
110
+
111
+ - 为什么这种气质适合目标用户与任务。
112
+ - 颜色如何区分主动作、状态、表面和内容层级。
113
+ - 字体如何形成阅读节奏,中文和数字如何共存。
114
+ - 内部紧凑与外部宽松分别使用哪些间距。
115
+ - 深度来自色调层、细边框、环境阴影、透明材质还是实体投影。
116
+ - 圆角、切角、胶囊或有机形状表达什么性格。
117
+ - 卡片、按钮、输入、导航、弹层在所有状态下如何变化。
118
+ - 哪些装饰会破坏任务,应明确禁止。
119
+
120
+ ## 可迁移的视觉气质
121
+
122
+ 以下不是成品主题,而是把意图翻译为系统规则的示例:
123
+
124
+ | 气质 | 色彩与材质 | 形状与间距 | 动效 | 禁止 |
125
+ | --- | --- | --- | --- | --- |
126
+ | 温暖友好 | 暖白或浅沙底、自然低饱和强调色、轻触感层次 | 圆润或轻微有机形,外部留白宽松 | 柔和上浮与按压 | 冷硬高对比、密集霓虹、尖锐切角 |
127
+ | 深色发光 | 深色表面、少量青蓝紫发光语义、清晰高对比文字 | 边界精确、圆角克制、层级紧凑 | 短促聚焦与状态脉冲 | 大面积炫光、彩虹状态色、持续漂移 |
128
+ | 雾感通透 | 灰白低彩底、细线边框、轻透明表面 | 大留白、轻圆角、细腻分层 | 慢速淡入与透明度变化 | 厚重投影、不透明色块堆叠、过度模糊 |
129
+
130
+ ## 后台数据卡片契约
131
+
132
+ 后台数据卡片是高频操作容器,不是营销海报:
133
+
134
+ - 固定信息顺序:真实图片或紧凑身份标记 → 标题与状态 → 2—4 个关键字段 → 时间/辅助标签 → 操作区。
135
+ - 配置了图片但当前行无图时,使用 40—44px 的首字/图标标记;禁止生成占据卡片三分之一以上的装饰占位图。
136
+ - 默认桌面四列,显式列数配置优先;中等宽度自动降为三列或两列,移动端单列。卡片最小可读宽度优先于“同屏塞更多”。
137
+ - 标题最多两行,主次文字、金额、状态与时间有固定层级;标签只表达状态或分类,不把每个字段都做成胶囊。
138
+ - 操作区优先一个主动作、一至两个次动作,其余收进“更多”;危险动作不得与主动作同权。移动端触控目标不小于 44px。
139
+ - 骨架屏必须复刻最终标题、字段和操作区的几何结构;空态解释原因并提供下一步。
140
+ - 整卡可进入详情时必须提供键盘焦点、Enter/Space 触发和可见 focus;内部按钮阻止事件冒泡。
141
+
142
+ ## 契约检查、差异与输出
143
+
144
+ 每次修改契约或 UI 时,按顺序检查:
145
+
146
+ 1. **结构**:核心章节存在且顺序正确,无重复章节、疑似拼写错误或无法识别的 token 组。
147
+ 2. **类型**:颜色、长度、数字、布尔值和状态对象类型正确,token 式文本没有被误放在普通说明里而漏解析。
148
+ 3. **引用**:所有 `{路径}` 可解析,无循环引用;未被组件、页面或输出消费的孤立 token 要删除或说明用途。
149
+ 4. **语义**:主色、表面、文字层级、组件状态和有意省略完整;命名能解释用途。
150
+ 5. **可访问性**:正文和交互态对比度满足项目标准,焦点可见,键盘路径连续,低动效降级完整。
151
+ 6. **差异门禁**:评审设计契约的语义变更和实现变更是否同时出现;意外删除、重命名或大范围 token 漂移应阻止合入。
152
+ 7. **输出**:需要生成运行时变量或其它主题格式时,只由已校验的机器块派生,禁止维护第二份手工 token。
153
+
154
+ 契约格式若仍在演进,项目必须锁定 `contract.version`,升级时先查看差异并一次性迁移。跨平台脚本要提供不依赖文件扩展名的稳定入口,避免不同终端执行出不同结果。
155
+
156
+ ## AI 使用规则
157
+
158
+ - 开始实现前完整读取契约和 `ui-design` skill;先复述页面任务、主气质和三条禁止事项,再写页面。
159
+ - 契约缺少的精确值优先继承 Microi.UI token;不能用“看起来差不多”的硬编码补洞。
160
+ - 先稳定颜色、字体、间距、层级和形状,再定义组件状态;不要在基础 token 尚未收敛时过早堆复杂组件结构。
161
+ - 新模式在两个以上页面重复时,先更新契约,再抽成 `Mci*` 或项目级 `mci-*` 组件。
162
+ - 修改契约后至少截图一张受影响页面的桌面和移动版本,并覆盖亮/暗主题及相关业务状态。
163
+ - 契约、源码、浏览器截图三者冲突时不得宣称完成;修复后重新执行结构检查、定向测试和视觉验收。
@@ -186,6 +186,14 @@ var extractResult = V8.Method.ExtractZip({
186
186
  - 安装脚本还必须同步当前有效 `sys_config`:`ApiBase` 使用对外可访问的 API 端口,`FileServer` 使用 `http://<访问IP>:<MinIO API端口>/mci-public`。`ApiBase` 不能误用 Web 前端端口,因为 V8 代码会直接在其后拼接 `/api/...` 或 `/apiengine/...`。
187
187
  - 安装验收必须使用真实登录 Token 分别执行一次 `Limit=false` 和 `Limit=true` 上传:公有文件匿名访问应返回 `200`,私有文件匿名访问应返回 `403`,私有文件通过签名 URL 访问应返回 `200`,并核对下载内容与上传内容一致。
188
188
 
189
+ ### 复盘:旧空库缺少可选字段导致 MinIO 初始化后中断
190
+
191
+ - 触发场景:MinIO 容器、私有桶和公有桶均已成功创建,但安装器更新 `sys_osclients` 时因旧库缺少 `NetworkIsInternet` 返回 `Unknown column`,整套安装停在 API 部署之前。
192
+ - 根因:安装器在 API/Upgrade 尚未启动时依赖了并非 MinIO 必需、且存量数据库不保证存在的旧可选字段;同时只按 `OsClient` 更新且没有写后回读。
193
+ - 通用规则:MinIO 安装前先校验真正必需的物理字段,并按 `OsClient + OsClientType + OsClientNetwork + IsEnable + IsDeleted` 唯一定位运行租户。内外网端点由 API 允许的启动项 `OsClientNetwork` 选择,安装器不得再写 `NetworkIsInternet`;配置更新后逐字段回读一致才继续。
194
+ - 恢复规则:桶初始化成功而配置写入失败不需要删除桶或数据卷。中断的新安装应先备份现有 Compose 并记录绑定数据目录,只对对应编排执行不带 `-v` 的 `docker compose down`,保留数据恢复点后再使用最新版脚本;禁止直接删除数据库或对象存储目录。
195
+ - 自动化检查:用一个不含 `NetworkIsInternet`、但包含 MinIO 必需字段的临时 MySQL 表执行 schema、唯一租户、UPDATE 和回读闭环;再插入重复三参数租户,断言安装器失败关闭且不批量覆盖。
196
+
189
197
  ### 复盘:MinIO 已可上传但系统设置仍指向官方地址
190
198
 
191
199
  - 触发场景:一键安装和桶初始化均成功,用户手工上传也成功,但读取系统设置时发现 `ApiBase`、`FileServer` 仍是空库模板中的官方地址。
@@ -199,6 +199,9 @@ V8.RefreshTable({ _PageIndex: 1 });
199
199
  | `V8.Method.ScanCode({...})` | 调用当前终端支持的扫码能力 |
200
200
  | `V8.Print.isConnected()` | 检查当前蓝牙写特征是否仍可用 |
201
201
  | `V8.Print.OpenBluetoothPage()` | 在用户手势中打开蓝牙连接页,返回 Promise |
202
+ | `V8.Print.reconnect()` | 使用已记住的设备授权或设备 ID 尝试重连 |
203
+ | `V8.Print.getConnectionState()` | 获取连接、设备、记忆和错误状态快照 |
204
+ | `V8.Print.subscribeConnection(listener)` | 订阅应用级共享连接状态,返回取消订阅函数 |
202
205
  | `V8.Print.prepareSend(bytes)` | 串行分包发送 TSC 或 ESC/POS 字节,必须 `await` |
203
206
 
204
207
  `V8.OpenAnyForm` 只发起打开动作,不返回“用户关闭后的 Promise”。需要替换
@@ -234,7 +237,7 @@ try {
234
237
  }
235
238
  ```
236
239
 
237
- `prepareSend` 成功只证明字节已经写入蓝牙特征,不代表打印机已走纸、无缺纸或无硬件故障。批量打印必须逐条 `await`,不得用固定 `setTimeout` 猜测完成时间,也不得用 `Promise.all` 并发写同一设备。完整挂载范围、连接语义、批量恢复、安全与硬件验收见 `references/bluetooth-print.md`;源码级 TSC/ESC 方法表见 `references/bluetooth-print-api.md`。
240
+ PC/平板顶部导航与移动端【我的】页共用同一个应用级 `V8.Print` 实例,用户可先在全局入口连接,再进入任意模块打印。`prepareSend` 内部会把不同 V8 上下文排入同一发送队列;成功只证明字节已经写入蓝牙特征,不代表打印机已走纸、无缺纸或无硬件故障。批量打印仍应逐条 `await`,不得用固定 `setTimeout` 猜测完成时间,也不要用 `Promise.all` 表达同一设备的并行打印。完整挂载范围、连接语义、批量恢复、安全与硬件验收见 `references/bluetooth-print.md`;源码级 TSC/ESC 方法表见 `references/bluetooth-print-api.md`。
238
241
 
239
242
  ### 常用上下文差异
240
243
 
@@ -29,9 +29,12 @@
29
29
  Microi 浏览器/5+App 前端 V8 中可用,不属于后端接口引擎、后端表单事件或微信小程序
30
30
  原生 BLE API。
31
31
 
32
- 基础 V8 对象会把同一个 `Print` 状态复制给多个前端 V8 上下文。分包游标、打印份数和
33
- 连接引用均为可变状态,所以同一页面的所有打印任务必须共用一条串行队列,不能认为
34
- 不同按钮或不同 V8 对象彼此隔离。
32
+ 三个入口都会取得同一个应用级 `Print` 单例。分包游标、打印份数和连接引用仍是可变状态,
33
+ 但 `prepareSend` 已把所有前端 V8 上下文排进同一条运行时发送队列。业务代码仍应逐次
34
+ `await` 保持明确的结果顺序,不能认为不同按钮或不同 V8 对象彼此隔离。
35
+
36
+ PC/平板顶部导航和移动端【我的】页的蓝牙入口也使用该单例:它们展示实时连接状态和设备名,
37
+ 点击后复用 `OpenBluetoothPage()`,因此用户可以先在全局入口连接,再进入任意模块执行 V8 打印。
35
38
 
36
39
  ## 运行环境与能力判断
37
40
 
@@ -55,22 +58,23 @@ ESC/POS 原生字节通过 BLE 写入才属于 `V8.Print`。
55
58
  |---|---|
56
59
  | `createNew()` | 新建 TSC/TSPL 标签指令构建器 |
57
60
  | `createNewESC()` | 新建 ESC/POS 小票指令构建器 |
58
- | `OpenBluetoothPage()` | 返回 `Promise<boolean>`;在连接弹窗关闭时解析,值表示关闭时是否有连接信息 |
59
- | `isConnected()` | Web 端检查实时 GATT 与写特征;5+App 端只检查已保存的设备/写特征 ID |
60
- | `prepareSend(bytes)` | 未连接时先打开连接页,然后按包串行写入;必须 `await` 并捕获失败 |
61
+ | `OpenBluetoothPage()` | 返回 `Promise<boolean>`;在连接弹窗关闭时解析,重复打开复用同一个 Promise |
62
+ | `isConnected()` | Web 端检查实时 GATT 与写特征;5+App 结合连接事件在线标记与设备/写特征 ID |
63
+ | `reconnect()` | 使用已记住的设备 ID 或浏览器保留的设备授权重连,不弹选择框 |
64
+ | `getConnectionState()` | 返回可展示的连接、记忆、设备、错误和重连状态快照 |
65
+ | `subscribeConnection(listener)` | 立即回调当前快照并持续通知状态变化,返回取消订阅函数 |
66
+ | `prepareSend(bytes)` | 先尝试恢复连接,再进入应用级队列按包串行写入;必须 `await` 并捕获失败 |
61
67
  | `Send(bytes)` | 依赖 `prepareSend` 已设置的内部游标,属于内部状态机入口,业务代码不要直接调用 |
62
- | `setOneTimeData(bytes)` | 设置 BLE 包长;源码不校验,内置候选为 20–190、步长 10,默认 20 |
63
- | `setPrinterNum(num)` | 重复发送同一缓冲区;源码不校验,内置候选为整数 1–9 |
64
- | `disconnect()` | 主动断开并清理当前设备、写特征和会话元数据 |
68
+ | `setOneTimeData(bytes)` | 设置 BLE 包长;只接受 1–512 整数,连接页候选 20–190,默认 20 |
69
+ | `setPrinterNum(num)` | 重复发送同一缓冲区;只接受 1–99 整数,连接页候选 1–9 |
70
+ | `disconnect()` | 主动断开、停止自动重连并忘记当前设备 |
65
71
  | `BLEInformation` | 最近设备/服务/特征元数据,只用于诊断,不代表实时连接或打印回执 |
66
72
 
67
- `OpenBluetoothPage()` 已打开时再次调用会返回 `false`。它不是“连接成功事件”;用户连上
68
- 设备后仍要关闭弹窗,调用方才能继续。`prepareSend` 虽会自动打开连接页,但业务按钮主动
69
- 建立连接更容易给出清晰提示。
70
-
71
- 连接元数据会写入 `sessionStorage`,但当前 `restoreBLEInfo()` 没有进入初始化调用链,页面
72
- 刷新后不会自动恢复可发送的 GATT/特征引用。刷新、跨页面重建或断线后应重新连接。
73
- 5+App 的 `isConnected()` 只验证 ID 是否存在,因此发送仍可能因物理断线失败。
73
+ `OpenBluetoothPage()` 不是“连接成功事件”;用户连上设备后仍要关闭弹窗,调用方才能继续。
74
+ 设备元数据会写入 `localStorage` 与兼容用 `sessionStorage`。应用初始化、页面恢复、重新获得
75
+ 焦点和意外断线时会做有限次数自动重连:5+App 使用设备 ID;Web 端只有浏览器保留授权且
76
+ 支持 `navigator.bluetooth.getDevices()` 时才可无弹窗恢复。系统蓝牙、浏览器权限、设备电源、
77
+ 休眠、距离等仍会造成真实断线;重试结束后必须让用户从全局入口重新选择。
74
78
 
75
79
  ## 最小安全流程
76
80
 
@@ -85,7 +89,8 @@ async function ensurePrinterConnected() {
85
89
  if (!V8.Print) throw new Error('当前前端未加载蓝牙打印能力');
86
90
  if (V8.Print.isConnected()) return;
87
91
 
88
- var connected = await V8.Print.OpenBluetoothPage();
92
+ var connected = await V8.Print.reconnect();
93
+ if (!connected) connected = await V8.Print.OpenBluetoothPage();
89
94
  if (!connected || !V8.Print.isConnected()) {
90
95
  throw new Error('未连接蓝牙打印机');
91
96
  }
@@ -141,7 +146,8 @@ async function printBatch(rows, startIndex) {
141
146
  ```
142
147
 
143
148
  - 不用固定 `setTimeout(3000)` 猜测上一张是否完成。
144
- - 不用 `Promise.all`,也不要让两个按钮同时调用 `prepareSend`。
149
+ - 不用 `Promise.all` 表达同一设备的并行打印。运行时会把同时到达的调用排队,但业务仍应逐条
150
+ `await`,以便准确记录哪一条成功或失败。
145
151
  - 大批次分段并持久化 `NextIndex`;页面关闭、断连或写失败后从失败位置人工确认再恢复。
146
152
  - `setPrinterNum(n)` 只适合同一缓冲区重复发送,不适合每张内容不同的批次。
147
153
  - 业务落库与蓝牙打印不是原子事务。用稳定业务单号支持受控重打,不重复执行业务写入。
@@ -153,14 +159,14 @@ async function printBatch(rows, startIndex) {
153
159
  当前没有公开的自定义服务配置,并选择枚举到的第一个可写特征;其它型号可能需要扩展源码。
154
160
  - `prepareSend` 默认每包 20 字节、包间约 20ms;同一缓冲区多份打印间约 100ms。这只是
155
161
  BLE 写节奏,不是打印完成等待时间。包长必须是已实测的正整数,空缓冲区不得发送。
156
- - 当前分包公式是 `floor(length / packetSize) + 1`。长度恰好整除包长时会尝试额外写一个
157
- 0 字节末包;某些 BLE 栈会拒绝。实机出现此问题时应修复适配器并回归,不要靠并发或吞错绕过。
162
+ - 当前分包公式使用 `Math.ceil(length / packetSize)`,长度恰好整除时不会产生 0 字节末包;
163
+ 空数据、非法包长和非法份数会直接抛错。合法数值仍须按目标打印机实测。
158
164
  - TSC 与 ESC 文本使用仓库内置 `encoding.js` + `encoding-indexes.js` 转为 GB18030,运行时
159
165
  不请求网络。编码成功不等于打印机字体、代码页和固件支持全部字符;Emoji 等仍需实机验证。
160
166
  - `setBitmap` 接受 ImageData 风格 `{ width, height, data }` RGBA 数据。当前黑白转换较简单,
161
167
  大图可能产生大缓冲区;先缩放、二值化并用小图测试。
162
- - `V8.Print` 没有任务锁和队列。跨 V8 上下文并发会互相覆盖 `currentTime`、`looptime`、
163
- `lastData` 等共享状态,必须由业务侧全局串行化。
168
+ - `V8.Print` 使用应用级共享发送队列,跨 V8 上下文不会再并发覆盖 `currentTime`、`looptime`、
169
+ `lastData` 等共享状态。队列只保证写入顺序,不提供打印机 ACK、业务事务或自动重打语义。
164
170
 
165
171
  ## 安全边界
166
172
 
@@ -63,7 +63,28 @@ if (resp.StatusCode < 200 || resp.StatusCode >= 300) {
63
63
  | `FilesByteBase64` / `FilesByteString` | 文件字段对象,键同时作为字段名和文件名。 |
64
64
 
65
65
  `GetResponse/PostResponse/PatchResponse` 返回 `Content`、`Headers`、`RawBytes`、`StatusCode`、`ErrorMessage`。后端 `RawBytes` 是 `.NET byte[]`,前端是 `Uint8Array`。
66
-
66
+
67
+ ## V8.AI 与底层 HTTP
68
+
69
+ - 前端 V8 的平台 AI 普通调用优先 `await V8.AI.Chat(...)`。它自动使用当前 ApiBase 和平台登录头、接收响应 Token 轮换,并清除调用参数中的租户、身份、Endpoint、ApiKey 和认证头覆盖;只有确认问题适合进入 URL 日志时才用 `V8.AI.ChatGet(...)`。
70
+ - 浏览器打字机效果使用 `await V8.AI.ChatStream(param, onChunk, { Signal })`。它解析 `message/result/error/done` SSE,`onChunk` 接收真实增量;页面关闭时通过 `AbortController` 取消读取。
71
+ - 后端 V8 直接使用 `await V8.AI.Chat(...)`、`ChatStream(...)`、`NL2SQL(...)` 或管理员限定的 `NL2V8(...)`。对象在服务端绑定当前 `OsClient` 与认证用户,匿名上下文拒绝;禁止退回到自请求当前 API、转发 Token 或接受用户指定 Endpoint/ApiKey 的包装方式。
72
+ - MCP 使用专用 `microi_chat`,由 MCP 连接提供 Token 与租户,只接受对话白名单参数并返回最终 `DosResult`。它不是逐 token MCP 流;其它平台写操作继续使用对应写 Tool 的确认与回读规则。
73
+ - `Chat/ChatStream` 虽兼容 GET/POST,含问题、附件和会话上下文时一律优先 POST,避免敏感内容进入 URL、代理日志和浏览器历史。`V8.Http` 继续用于通用第三方 HTTP 集成,不要重复实现平台 AI 的认证或 SSE 解析器。
74
+
75
+ ```javascript
76
+ // 前端或后端 V8:普通 AI 对话
77
+ var result = await V8.AI.Chat({
78
+ UserChatMsg: '归纳当前工单',
79
+ AiModel: 'MiniMax-M3',
80
+ AiModelId: '当前租户启用的 mic_ai 记录Id'
81
+ });
82
+ if (result.Code != 1) V8.Tips(result.Msg || 'AI调用失败', false);
83
+ else V8.Result = result.Data;
84
+ ```
85
+
86
+ 完整授权矩阵、SSE、后端安全边界与 MCP 示例维护在官网现有 `system-engine/ai-engine.md`,不要新建重复文档。
87
+
67
88
  ## POST 请求(对象参数格式)
68
89
 
69
90
  > V8 接口引擎中必须使用对象参数格式。尤其禁止 `V8.Http.Get(url)`:当前 .NET 同名重载包含 `Task<string> Get(string)`,Jint 可能把字符串调用解析为异步重载,脚本最终拿到 `[object Promise]`。GET 必须写成 `V8.Http.Get({ Url: url })`;第三方登录、微信 `jscode2session`、AccessToken 等链路保存后必须用无效 code 烟测,确认返回的是第三方明确错误而不是 Promise。
@@ -53,7 +53,8 @@ V8.OsClientModel.AliOssPublicDomain // 可公开的文件域名
53
53
 
54
54
  - 所有可变业务逻辑默认必须由接口引擎编排,包括但不限于租户开通、开库、初始化、归属修复、官网个人中心、付费额度等 SaaS 业务流程。C# 后端只暴露原子 V8 能力,例如建库、导入空库模板、复制 `sys_config`、刷新 SaaS 缓存、补偿回滚、字段兜底等;不要把可变业务分支写死到 Controller 或 `TenantProvisioningService` 这类后端定制代码里。接口引擎缺少能力时,优先扩展 `V8.Method`/V8 引擎原子函数,再由接口引擎调用。
55
55
  - 主租户由运行环境决定:优先读取环境变量 `OsClient`,其次读取 `appsettings.json` 的 `AppSettings:OsClient`。只有这条主租户 `sys_osclients` 数据中的平台级字段会作为全局配置生效。
56
- - 普通业务与运行参数统一从主租户 `sys_osclients` `sys_config` 读取,未配置时使用代码安全默认值;不要再为同一参数增加 `MICROI_*` `DOS_ORM_*` 环境变量。数据库、Redis 和必要密钥属于启动基础设施,继续使用现有专用安全配置;节点身份由平台自动生成。
56
+ - API 启动配置只有十项白名单:`OsClient`、`OsClientType`、`OsClientNetwork`、`OsClientDbType`、`OsClientDbConn`、`OsClientRedisHost`、`OsClientRedisPort`、`OsClientRedisPwd`、`OsClientRedisDataBase`、`OsClientDbMongoConn`。除这十项外,普通业务、运行参数、密钥路径、重试、限额和安全策略统一从主租户 `sys_osclients` 或按租户从 `sys_config` 读取,未配置时使用代码安全默认值;官方 License 恢复次数/间隔与固定私钥挂载 `/app/microi_private.pem` 是信任链例外,禁止创建对应 SaaS 字段。禁止再增加 `MICROI_*`、`DOS_ORM_*`、自定义 `AppSettings` 节点或动态名称的环境变量读取。节点身份由平台自动生成。
57
+ - `ASPNETCORE_*`、`DOTNET_*` 仅用于 .NET 宿主;构建、安装、测试、MCP、发布脚本可使用自身进程变量,但 API 生产代码不得把它们当业务配置。新增 SaaS 运行字段必须配套独立或既有 Tab、幂等升级、缓存刷新、敏感字段脱敏、子租户不继承和源码扫描测试。
57
58
  - 文件上传的租户业务开关与额度按“当前租户 `sys_osclients` → 代码默认值”解析;平台固定灾难保护、HTTP/Multipart/Form 和反向代理上限不可由租户覆盖,也不要求安装者维护额外上传环境变量。
58
59
  - 类似 MQTT 端口、PressureGuard、V8Limits、OrmLimits、StartupLimits、SecurityGuard 这类影响整进程资源的配置,不能让每个子租户各自抬高全局上限。子租户同名隔离字段只能降低自己的并发、等待时间或资源额度,用于隔离弱租户、试用租户或异常租户。
59
60
  - 修改 `sys_osclients` 的表、字段、数据源或配置值后,必须刷新 SaaS 引擎运行缓存,并回读验证字段 `Component`、`Data`、`Config`、实际数据值和前端真实消费结果。不要只看 MCP 写入成功。
@@ -29,7 +29,7 @@ var openaiKey = 'sk-xxxxxxxxxx';
29
29
 
30
30
  子租户缺少 RabbitMQ/MQTT/Search 独立凭据时必须失败关闭,禁止回退主租户账号。新租户开通只有在外部 broker/search 中真实创建 user、vhost、ACL 或 API Key 后,才能标记对应服务可用。
31
31
 
32
- 登录和管理端必须强制 HTTPS。登录 RSA 只用于避免密码在请求体、代理调试界面中直接显示,不能替代 HTTPS,也不能作为身份认证或密码存储密钥。平台为兼容已发布客户、旧前端和浏览器缓存,保留历史登录 RSA 密钥对作为缺省回退;安全修复不得直接删除该回退并造成全量客户无法登录。需要部署专属密钥时,服务端通过 `MICROI_LOGIN_RSA_PRIVATE_KEY` 或受限密钥文件注入私钥,同时通过 `MICROI_LOGIN_RSA_PUBLIC_KEY` / `Security:LoginRsaPublicKey` 向匿名 `GetSysConfig` 提供匹配公钥;两端必须成对切换。源码、V8、前端业务代码和日志中仍禁止新增或输出其它真正的业务私钥、JWT 密钥、支付密钥及对象存储凭据。
32
+ 登录和管理端必须强制 HTTPS。登录 RSA 只用于避免密码在请求体、代理调试界面中直接显示,不能替代 HTTPS,也不能作为身份认证或密码存储密钥。平台为兼容已发布客户、旧前端和浏览器缓存,保留历史登录 RSA 密钥对作为缺省回退;安全修复不得直接删除该回退并造成全量客户无法登录。需要部署专属密钥时,在主租户 SaaS 引擎【后端运行配置】中成对维护 `BackendLoginRsaPrivateKey` `BackendLoginRsaPublicKey`;私钥只允许可信服务端读取,匿名 `GetSysConfig` 只返回匹配公钥。源码、环境变量、普通 V8、前端业务代码和日志中仍禁止新增或输出真正的业务私钥、JWT 密钥、支付密钥及对象存储凭据。
33
33
 
34
34
  吾码现有多端兼容约定是:主 SaaS 引擎 `sys_osclients.CorsAllowOrigins` 为空时默认允许全部跨域,便于本地开发、独立前端、H5 和不同租户域名访问;配置了来源后才按精确来源或通配符限制。安全修复不得把“未配置”改成默认拒绝,否则会造成所有存量部署和本地调试突然失效。CORS 不是鉴权边界,权限仍必须依赖 Token、租户隔离、菜单/表权限和服务端数据范围。
35
35
 
@@ -98,8 +98,9 @@ V8.Db.FromSql("SELECT * FROM " + V8.Param.table).ToArray();
98
98
  - 菜单 `SqlWhere`、`SqlJoin` / `JoinTables` 数据范围必须在服务端形成的**真实列表、计数和导出查询**中执行,不能只用于界面展示或查询后过滤。单行详情只校验同表菜单访问权,不应用这些模块列表过滤;它们也不是行级写权限。
99
99
  - 主表新增、修改、删除分别由当前角色的 `Add`、`Edit`、`Del` 权限控制;不得把查询 SqlWhere 追加到写入 SQL,也不得因为查询包含跨表 Join 拒绝已获授权的写入。需要“仅可修改本人数据”等业务限制时,在 `SubmitBeforeServerV8` 或专用接口引擎中以可信服务器代码校验,并统一写入 `TenantId`、负责人、创建人等归属字段。
100
100
  - 导入、导出必须携带真实菜单上下文,并分别拥有 `Import`、`Export`;不能用 Table 级直接授权绕过。
101
- - SaaS 配置、接口引擎、表/字段元数据、菜单角色、系统用户、任务、数据源、MQ/MQTT、页面/打印/工作流、扩展数据库等平台敏感表,对 `Level < 9999` 的通用客户端 FormEngine 硬拒绝。错误的菜单或 Table 授权不能覆盖。
102
- - 匿名读取/新增仅适用于 `diy_table` 明确开启匿名能力的普通业务表;敏感平台表必须先于匿名开关拒绝。
101
+ - 平台表必须按后端 `PlatformResourceSecurity` 分级:账号/角色/权限、SaaS 配置、接口引擎、表字段元数据、任务、数据源、密钥和基础设施等管理员专用表,对 `Level < 9999` 的通用客户端 FormEngine 全操作硬拒绝;工作流、微服务/商店、蓝图和微应用运行元数据只允许显式授权后的 `Read/List`,写入仍硬拒绝;`mic_page/mic_print` 按真实菜单或 Table 的 `Read/Add/Edit/Del` 权限管理。
102
+ - 匿名读取/新增仅适用于 `diy_table` 明确开启匿名能力的普通业务表;上述三类平台表必须先于匿名开关拒绝。
103
+ - 角色增删改接口不能只相信 Token 缓存或前端禁用状态,必须覆盖请求中的 `_CurrentUser/OsClient`,并从租户主库复核活动用户、数据库 Level 与有效角色 Level。角色降级要先同步受影响用户 Level,再提升共享授权 `epoch`,避免旧令牌窗口;Postman 伪造 `_IsAdmin/Level/RoleIds` 必须失败。
103
104
  - 权限 JSON、角色 Id 或菜单上下文解析失败时必须失败关闭;角色 Id 使用精确集合匹配,禁止 `Contains` 子串判断。
104
105
 
105
106
  标准菜单模块继承菜单权限,不需要维护“角色 × 全部业务表”的巨大矩阵,也不能要求所有历史前端 V8 立即补传菜单 Id。新前端在当前表上下文应由平台 facade 自动携带真实菜单;历史无菜单请求继续由后端安全推断。【高级表权限】只用于确实没有任何菜单入口的定制页面/SDK,并按最小权限授予。
@@ -353,7 +354,7 @@ try {
353
354
  - 普通访问默认阈值为 10 秒 600 请求/120 异常。VS Code 多服务器源码拉取不能通过减少并发或请求量规避;符合条件的只读 V8Debug `Get/List` 使用独立桶,默认 10 秒 6000 请求/1200 异常。
354
355
  - 受信判断必须同时满足:服务端共享登录态确认 `Level >= 9999`、请求 Token 是活动 `ClientType=VSCode` Token、请求 `did` 与该 Token 保存的 Did 完全一致、路由为只读 `/api/V8Debug/Get*` 或 `/api/V8Debug/List*`。自报 `ClientType`、User-Agent、`X-User-Level`、单独伪造 did 都不可信;Update/Create/Execute/Upload/Finalize 和 FormEngine 写入不放宽。
355
356
  - 普通请求的计数 scope 固定为当前 API 主运行实例,不能使用请求方可控的 `OsClient` Query/Header 选择 Redis 桶;只有已通过上述联合校验的 VS Code profile 才能使用活动 Token 服务端绑定的租户 scope。测试必须覆盖轮换两个真实租户 Key 仍落入同一普通桶。
356
- - SecurityGuard 只读取 `Connection.RemoteIpAddress`,不得直接解析 `X-Forwarded-For`/`X-Real-IP`。宿主在 `UseForwardedHeaders` 中只信任 `ForwardedHeaders:KnownProxies` 的精确 IP 和 `KnownNetworks` 的受控 CIDR(环境变量可用 `ForwardedHeaders__KnownProxies__0` / `ForwardedHeaders__KnownNetworks__0`);禁止 `0.0.0.0/0`、`::/0`。历史 `SecurityRespectForwardedHeaders` 字段不能建立 Header 信任。测试必须覆盖公网 Remote IP 携带伪造 `X-Forwarded-For: 127.0.0.1` 时仍识别公网 IP。
357
+ - SecurityGuard 只读取 `Connection.RemoteIpAddress`,不得直接解析 `X-Forwarded-For`/`X-Real-IP`。宿主在 `UseForwardedHeaders` 中只信任主租户 SaaS 引擎【后端运行配置】的 `BackendForwardedKnownProxies` 精确 IP 和 `BackendForwardedKnownNetworks` 受控 CIDR;禁止使用环境变量、自定义 `appsettings` 节点、`0.0.0.0/0` 或 `::/0`。历史 `SecurityRespectForwardedHeaders` 字段不能建立 Header 信任。该宿主配置变更需滚动重启节点,测试必须覆盖公网 Remote IP 携带伪造 `X-Forwarded-For: 127.0.0.1` 时仍识别公网 IP。
357
358
  - 自动封禁按 `ExpiresAtUtc` 到期解除。立即解除只能从未被封禁的管理网络,以平台超级管理员进入【系统日志 → 安全防护】操作 `/api/SecurityGuard/UnblockIp`;同一被封出口不能给自己解封。
358
359
  - 固定可信出口才可精确加入当前服务器匹配 `sys_osclients.SecurityWhitelistIps`。禁止全网段、动态用户 IP 或请求参数自动入白名单,也不要关闭安全防护或全局放大普通阈值。
359
360
  - 多节点封禁、解封和到期状态必须进入共享 Redis/数据库;本机静态字典只能做缓存。Redis 可用且共享 block 不存在就是权威已解封,节点必须删除本机旧 block,禁止把旧状态回写复活;Redis 不可用时才允许本机降级。验收要让同一出口分别命中至少两个 API 节点,覆盖普通阈值、受信读取、伪造 Header、手动解除和自动到期。
@@ -373,7 +374,7 @@ try {
373
374
  - [ ] 所有数据库查询使用参数化(`_Where` 或 `@p0`)
374
375
  - [ ] 把 Token 认证与表/菜单/操作授权分开;列表/写入显式菜单严格精确校验,历史唯一详情只校验同表已授权菜单访问权
375
376
  - [ ] 当前表前端 facade 自动注入真实菜单,跨表不借用主菜单;可信后端 V8 由服务端标记且不要求菜单
376
- - [ ] 敏感平台表仅限 `Level >= 9999` 的可信管理链路,Import/Export 必须携带真实菜单并有专项权限
377
+ - [ ] 平台表按管理员专用、只读委托、按角色管理三级执行;全部拒绝匿名,Import/Export 必须携带真实菜单并有专项权限
377
378
  - [ ] 菜单 `SqlWhere` / `SqlJoin` 覆盖真实列表、计数和导出查询;详情只校验同表菜单访问;主表新增/修改/删除/导入只按专项操作权限,不把查询范围带入写 SQL;行级写业务限制由后端 V8/接口引擎校验
378
379
  - [ ] 授权缓存按租户使用 Redis `epoch` 和用户级快照;冷加载读主库,权限变更递增 `epoch`
379
380
  - [ ] 关键操作校验 `V8.CurrentUser` 权限
@@ -392,7 +393,7 @@ try {
392
393
  - 触发场景:`Level=9999` 管理员在表单设计器保存时,外层 `UptFormData`、内部 `UptDiyFieldList` 或 `AddDiyField` 返回 `NoAuth`;无 HTTP 用户的升级任务写 `sys_apiengine/sys_menu/diy_field` 也被拒绝。
393
394
  - 根因:Redis 保留了旧结构的授权快照,新字段反序列化为 `0/false`;同时更新前旧记录读取或动态新增字段把已校验上下文降成裸 `JObject` / 匿名参数,丢失管理员或可信服务端来源。批量字段保存若逐字段调用完整 CRUD,还会把授权、V8 和缓存工作放大 N 倍。
394
395
  - 通用规则:授权快照使用独立契约版本;内部嵌套调用必须显式传递原始客户端管理员上下文,或由真正的服务端任务构造不可伪造的强类型可信参数,不能依赖 `_InvokeType` 或 CLR/JObject 猜测。
395
- - 自动化检查:预置缺少新字段的旧 Redis 快照后验证新版本 Key 不命中;分别覆盖管理员设计器批量字段保存/新增字段、普通用户直接写保护表被拒绝、升级程序可信写入成功,以及 HTTP JSON 伪造可信字段仍失败。
396
+ - 自动化检查:预置缺少新字段的旧 Redis 快照后验证新版本 Key 不命中;分别覆盖管理员设计器批量字段保存/新增字段、普通用户直接写管理员专用表被拒绝、只读委托表写入被拒绝、`mic_print` 有权读取成功、升级程序可信写入成功,以及 HTTP JSON 伪造可信字段仍失败。
396
397
 
397
398
  ## 浏览器访问密钥
398
399
 
@@ -108,6 +108,7 @@ MD5/SHA1 仅为兼容摘要;任何摘要都不能直接作为新密码存储
108
108
  | HTTP | `V8.Http.Get/Post/Patch`、`GetResponse/PostResponse/PatchResponse` 及真实 `*Async` 版本 |
109
109
  | 图片 | `V8.Image.Create/Merge/Overlay/Watermark/Resize/Crop/Rotate/Flip/Draw/Convert/GetInfo/CreateQRCode` |
110
110
  | Office | `V8.Office.ExportExcel/ExcelToList/ExportWord/ExportPowerPoint/SendEmail` |
111
+ | OCR | `await V8.OCR.Recognize({...})`;服务端租户配置与调用参数隔离,详见 `ocr-engine` |
111
112
  | 文件 | `V8.HDFS`、`V8.Method.Upload/GetPrivateFileUrl` |
112
113
  | MQ | `V8.MQ.SendMsg` |
113
114
  | 短信 | `V8.Sms.Send` |