@microi.net/cli 5.2.7 → 5.2.8

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.
@@ -22,12 +22,12 @@ description: Microi 应用商城开发、打包、安装和升级规范。用于
22
22
  - `Microi.Upgrade` 只保留应用商城/安装器启动前必需的核心物理兼容或协议迁移,并要求持久化版本门、共享租约、幂等、失败不推进版本。禁止每次启动对每个租户重跑不断增长的历史迁移清单。
23
23
  - 平台通用缺陷的交付证据必须分开记录:源码修复、官方母版资源、商城包发布后回读、目标租户后台安装任务、目标租户资源回读和真实 UI/接口验收;其中任一步未完成都不能笼统称为“已发布并安装”。
24
24
  - 任何计划让全部吾码租户通过“安装/更新官方应用”获得的标准字段、布局、菜单、页面或种子数据,必须先在官方 `microi_itdos` 主租户创建并回读,再从该主租户按精确菜单/表资源导出新版包、单调递增应用版本并发布。包正文以 `PackageHdfsPath + PackageSha256 + PackageSize` 为事实源;`AppPakcet` 只作旧版读取兼容,验证完成后应为空。禁止先只在客户/子租户补字段,再用本地手工 JSON 冒充官方母版;客户验证应发生在官方包发布之后。
25
- - 同一标准能力若同时属于基础 SaaS 空库包和独立官方应用(例如系统设置、系统账号),两条交付链都要更新:基础包保证新租户初始化完整,独立应用保证存量租户可增量安装。发布后分别核对 `PackageInfo.Version`、物理列、`diy_field` 布局节点、商城行 `AppVersion` 和 HDFS 包指针/下载哈希,不能用其中一条替代另一条。
25
+ - 同一标准能力若同时属于基础 SaaS 空库包和独立官方应用(例如系统设置、系统账号),两条交付链都要更新:基础包保证新租户初始化完整,独立应用保证存量租户可增量安装。表、字段和初始化模板可按这两个目标分别交付,但同一个 Managed ApiEngineKey 必须只有一个官方包所有者,禁止 SaaS、Store 与独立应用重复携带后互相覆盖。发布后分别核对 `PackageInfo.Version`、物理列、`diy_field` 布局节点、接口引擎单一归属、商城行 `AppVersion` 和 HDFS 包指针/下载哈希,不能用其中一条替代另一条。
26
26
 
27
27
  ## 吾码创建人开发时的强制发布闭环
28
28
 
29
29
  - 每次任务先检查工作区根的 `Microi.Server/Microi.net/`。只有目录存在且 `rg --files Microi.Server/Microi.net` 能找到至少一个真实源码文件时,才确认当前是吾码创建人在官方完整源码工作区开发;空目录不算,且不要求本次修改位于该目录。确认后,对任意目录中的平台基础能力执行修改、构建或交付时,官方应用数据包都不是“以后再补”的附加产物,而是本次实现的组成部分。目录缺失或为空时按普通用户工作区处理;纯审查、解释或诊断仍保持只读。
30
- - 触发资源包括系统设置、表、字段、Tab、菜单、权限、接口引擎、事件、数据源、页面、打印、工作流、任务、平台内置微服务和可幂等种子数据。开始修改时就确定资源归属:系统设置、登录身份、个人中心等通常归 `app.microi.saas-engine`;表单引擎归 `app.microi.form-engine`;模块引擎归 `app.microi.module-engine`;应用商城归 `app.microi.store`。同一能力跨多个包时逐包更新,禁止只挑一个包。
30
+ - 触发资源包括系统设置、表、字段、Tab、菜单、权限、接口引擎、事件、数据源、页面、打印、工作流、任务、平台内置微服务和可幂等种子数据。开始修改时就确定资源归属:租户开通、启动投影与基础空库归 `app.microi.saas-engine`;用户偏好、个人资料及其租户 Hook 归 `app.microi.sys_user`;租户系统设置编排及其 Hook 归 `app.microi.sys-config`;表单引擎归 `app.microi.form-engine`;模块引擎归 `app.microi.module-engine`;应用商城归 `app.microi.store`。同一能力跨多个包时逐包更新,但同一 Managed ApiEngineKey 仍须单一归属。
31
31
  - 强制闭环依次包含:①源码和定向测试;②通过绑定 `https://api.itdos.com + OsClient=iTdos` 的 `microi_itdos` 更新并回读官方母版资源;③从母版导出或按受审计发布契约生成本地应用包,单调提升包版本并核对资源数量、版本和 SHA-256;④发布对应官方 Platform 应用;⑤重新读取商城行的小型 HDFS 指针,用公有下载或受权私有下载取得原始 JSON,核对 `Published/IsApprove`、`AppVersion`、`PackageInfo.Version`、UTF-8 字节数、SHA-256 和资源正文;⑥立即做一次同输入幂等重跑,确认无重复升版或漂移。任务还指定目标租户时,再安装/更新并等待后台任务 `Succeeded` 后回读真实资源。
32
32
  - 本地包文件、生成器成功、单元测试通过、返回 TaskId 或 HTTP 200 都不能代替官方主数据库与商城回读。`.resource-sync-base` 只能在官网发布后逐项哈希一致时由同步器推进,不得与本地候选一起手工修改。若官方身份、MCP 登录或发布门禁失效,必须保留准确的未发布边界并修复链路;不得把本地 JSON 宣称为“其它吾码用户已经可以安装”。
33
33
  - 应用包不得携带真实地图 Key、Token、连接串或其它租户秘密。浏览器供应商 Key 等配置只交付字段/设置模板和安全读取能力,实际值由每个目标租户在安装后自行填写。
@@ -45,7 +45,8 @@ description: Microi 应用商城开发、打包、安装和升级规范。用于
45
45
  - 新发布包必须声明 `ResourcePolicies.ApiEngines`,不得再依赖“同 Key 直接覆盖”。官方不可随租户修改的核心使用 `{ Ownership:'Application', UpgradePolicy:'Managed' }`;提供给租户改业务的 Hook 使用 `{ Ownership:'Tenant', UpgradePolicy:'CreateIfMissing' }`。
46
46
  - 发布器从上一版安装包正文的 `SysApiEngines` 计算 `BaseHash`;正文可能来自已验证的 HDFS 指针或旧版 `AppPakcet`。导入成功后把本版摘要写入 `sys_microistoreversion.InstallResult.ResourceState.ApiEngines`。普通/社区应用仍按 Base/Local/Incoming 三方保护:`Local == Base` 才更新,`Local != Base && Local != Incoming` 必须冲突回滚。唯一覆盖例外是从固定 `https://api.itdos.com + iTdos` 实时回读并校验为官方 `ApplicationType=Platform` 的应用:其中 `Ownership=Application + UpgradePolicy=Managed` 属于平台发行物,安装/更新按包覆盖本地差异;离线包、自报官方、非 Platform 来源都不能获得该权限。
47
47
  - `CreateIfMissing` 只在目标 Key 不存在时创建,存在时不得对齐 Id、源码、启用状态或其它字段。扩展模板发布后即归租户维护;后续版本禁止把同一 Key 改回 `Managed` 接管,确需新的官方核心时发布新 Key 并显式迁移。
48
- - 官方功能采用“Managed 核心 + CreateIfMissing Hook”。核心只提供稳定协议和默认行为,并在可信官方 Platform 包更新时覆盖升级;客户日志、写表、通知和业务动作放 Hook,并以稳定 `EventId`、唯一约束或 outbox 幂等。`CreateIfMissing` 一旦交给租户维护,即使后续官方包误改为 Managed 也必须冲突回滚。
48
+ - 官方功能采用“Managed 核心 + CreateIfMissing Hook”。核心只提供稳定协议和默认行为,并在可信官方 Platform 包更新时覆盖升级;客户日志、写表、通知和业务动作放 Hook,并以稳定 `EventId`、唯一约束或 outbox 幂等。`CreateIfMissing` 一旦交给租户维护,即使后续官方包误改为 Managed 也必须冲突回滚。
49
+ - 每个官方包内的接口引擎源码顶部都必须有醒目所有权提示。Managed 提示必须写明所属官方应用、从可信官方源安装/更新/重新安装会恢复官方代码,并指向该应用的 CreateIfMissing Hook;CreateIfMissing 提示必须写明首次创建后归租户维护、官方升级不得覆盖。官方 SSO、登录、通知等核心在安全阶段调用 Hook 时,只传脱敏上下文,禁止传 Token、Secret、密码或原始协议断言。
49
50
  - 历史包未声明策略时只能按旧兼容流程安装;重新发布时发布器必须生成策略。验收至少覆盖首次安装、可信官方 Managed 本地有差异仍覆盖、普通应用核心差异冲突回滚、Hook 被改后保持原样、重复安装、两节点竞态,以及官方发布数据库连 `ValidateOnly` 也禁止执行安装器。
50
51
 
51
52
  ## 安装流程
@@ -57,9 +58,11 @@ description: Microi 应用商城开发、打包、安装和升级规范。用于
57
58
  5. `PostSchema` 完成后,在独立 `ScheduleJobs` checkpoint 中幂等安装定时任务并回读 Quartz 运行元数据。
58
59
  6. 写入成功后刷新共享缓存版本。
59
60
  7. 回读表、字段、引擎、菜单、权限、页面、定时任务等关键资源。
60
- 8. 做 HTTP、UI 和权限冒烟;全部成功后才标记安装版本。
61
-
62
- 安装中断后从 checkpoint 幂等恢复;不能依赖当前 API 节点内存。
61
+ 8. 做 HTTP、UI 和权限冒烟;全部成功后才标记安装版本。
62
+
63
+ 安装中断后从 checkpoint 幂等恢复;不能依赖当前 API 节点内存。
64
+
65
+ 后台任务中心的 `POST /api/BackgroundTask/List` 只返回有界分页的状态、进度、时间和 `HasLog/HasResult` 摘要;日志、结果、参数、可信用户快照与 checkpoint 必须按任务所有权在详情接口按需读取,不能为列表轮询重复返回大字段。
63
66
 
64
67
  ## 权限与租户
65
68
 
@@ -72,7 +75,7 @@ description: Microi 应用商城开发、打包、安装和升级规范。用于
72
75
  - 安装次数回传属于非阻塞幂等遥测,不是应用导入事务的成功条件。来源节点返回旧格式 `True`、空响应、非 JSON、业务失败或请求异常时,只能写入带 `OperationId`/`InstallationKey` 的 warning 诊断,不得用 `_error_` 标记或回滚已经成功导入的应用;重试仍须复用同一幂等键。
73
76
  - “全部安装/更新”固定只处理 `ApplicationType=Platform` 的官方平台应用中未安装与存在新版本的项目,不得把 UniApp、Web、MicroService 或其它社区/AI 应用整库安装;已是最新版的应用不重新安装。批量计划、子项状态、checkpoint 和进度必须持久化到共享数据库/后台任务,支持多节点抢占、失败重试和重启恢复,不能依赖进程内集合或浏览器状态。
74
77
  - 主租户批量维护全部子租户时,前端入口和接口引擎都必须校验主租户上下文及 `Level >= 9999`,再由可信控制面为每个启用子租户创建独立持久后台任务;父任务必须按子任务真实百分比聚合进度,等全部子任务终态后才成功或失败,并在通知中心保留每个租户、阶段和原始失败原因。所有子任务可立即创建,但固定商城工作器必须通过配置租户 Redis 的集群并发租约跨租户串行执行,避免共享物理库并发 DDL/元数据写入死锁;分片幂等任务使用足以覆盖短时死锁和滚动重启的有界重试预算,禁止无限重试。`MaxAttempts` 表示连续失败预算:任一分片成功写入 checkpoint 后必须把 `AttemptCount` 与陈旧 `LastError` 清零,不能让数百个成功分片之间偶发的网络错误按任务生命周期累计并误终结。分片重新入队后必须按 `COALESCE(NextRunTime, CreateTime)` 选取最早就绪任务,禁止只按 `CreateTime` 让最早长任务重复抢占全部分片。商城包属于只读权威数据,可对空响应做有界退避重试,并优先用完整响应的 `Content`、`RawBytes`、HTTP 状态和传输错误诊断;不得把该规则扩展到安装写入请求。目标租户固定商城工作器只允许从受信任官方谱系向更高版本刷新;同版本不同源码、目标端更新版或未知谱系必须失败关闭。历史空库缺少生成实体所需物理列时,导入器须在首次 FormEngine 调用前幂等补齐并回读,不能再把真实表结构错误包装成 `Value cannot be null (source)`。
75
- - 必须随所有后端版本自动落地的平台基础能力,仍要封装成受信任的官方 Platform 应用包,再由升级器调用统一 `import-microi-store-package` 幂等导入;禁止把表、字段、页面或微服务复制成定制 C# 迁移。身份验证、登录方式、个人中心与租户系统设置使用 `app.microi.saas-engine.json`:携带 `mci_system_setting`、`mci_user_external_identity`、默认设置和平台内置微服务。默认行必须使用 `InsertIfMissing + ConfigKey`,只补缺失,不覆盖 `ValueSource=Tenant` 的租户值或租户后来明确关闭的功能。小型平台启动微服务应以 `Source=NotIncluded + Build=DatabaseOnly + StorageMode=db` 随程序集交付并接受 256 文件/5MB、逐文件哈希和无源码门禁,使新租户在 HDFS 故障时仍能打开商城与恢复入口;普通应用安装、源码编辑和文件能力继续失败关闭,不得伪装成全平台健康。
78
+ - 必须随所有后端版本自动落地的平台基础能力,仍要封装成受信任的官方 Platform 应用包,再由升级器调用统一 `import-microi-store-package` 幂等导入;禁止把表、字段、页面或微服务复制成定制 C# 迁移。`app.microi.saas-engine.json` 可携带基础空库所需的 `mci_system_setting`、`mci_user_external_identity`、默认设置和平台内置微服务;系统账号的 `platform-user-update-preferences`、`platform-user-update-profile`、`platform-user-custom-hook` 只由 `app.microi.sys_user.json` 交付,租户设置的 `platform-tenant-system-settings`、`platform-system-settings-custom-hook` 只由 `app.microi.sys-config.json` 交付。SaaS/Store 不得保留这些 Key、策略或 RequiredPlatformCapabilities。默认行必须使用 `InsertIfMissing + ConfigKey`,只补缺失,不覆盖 `ValueSource=Tenant` 的租户值或租户后来明确关闭的功能。小型平台启动微服务应以 `Source=NotIncluded + Build=DatabaseOnly + StorageMode=db` 随程序集交付并接受 256 文件/5MB、逐文件哈希和无源码门禁,使新租户在 HDFS 故障时仍能打开商城与恢复入口;普通应用安装、源码编辑和文件能力继续失败关闭,不得伪装成全平台健康。
76
79
  - 批量任务已经以“一个应用”为外层持久化恢复单元。规模可控的小型官方包应在一个事务中完成,避免对同一包体按 8 个字段反复下载、解析和重新排队;超过字段、表、DDL、流程、随包数据或资产安全阈值的大包继续使用内部 checkpoint 分片。热更新发现旧版批量计划不含 `ApplicationType` 时,必须丢弃旧计划并重新盘点,不能继续安装历史计划中的社区应用。
77
80
  - MySQL 宽表触发 65,535 字节行内上限时,只允许把不参与索引的 `varchar` 配置列无损提升为 `mediumtext`,并把类型覆盖持久化到后台任务 checkpoint;索引列和非行宽错误必须失败关闭。发布包对长连接串、密钥、回调地址、域名/白名单等字段应直接使用 `mediumtext`,同时更新 `DiyFields` 与建表 DDL,不能长期依赖安装时猜测。
78
81
  - 卸载是破坏性操作,必须明确列出将删除/保留的资源、二次确认并优先软删除/归档业务数据。
@@ -84,9 +87,10 @@ description: Microi 应用商城开发、打包、安装和升级规范。用于
84
87
  - 发布顺序必须是“UTF-8 JSON 上传 → HDFS 回读 → 字节数和 SHA-256 一致 → 写入不可变包索引及商城指针 → 以原值 CAS 清空 `AppPakcet`”。普通发布不得仅凭 Redis 命中跳过 HDFS 回读;只有受控历史压缩可复用内容寻址缓存,安装端仍必须独立下载并校验。公开包可通过 FileServer/CDN 公有地址读取;私有包只能由来源后端在当前授权身份下签发短期下载地址,Token、签名 URL 和包正文不得进入浏览器配置、日志或后台任务参数。
85
88
  - `mic_data_version` 历史快照保留不可变 `StoreVersionId` 和同一组包指针,因此旧版本仍可精确安装,不再要求 `Data` 内含完整 JSON。导入器先校验快照版本/应用身份,再按指针下载并在解析前核对大小与哈希;指针缺失时才回退旧版内联字段,禁止快照不匹配时退回当前版本。
86
89
  - 旧库容量治理使用 `compact-microi-store-packages` 超级管理员持久后台任务:先幂等补齐物理列,再用有界 `Id` 游标逐批处理;每个包都先上传回读,随后 CAS 清理当前行或历史快照。禁止对数 GB `mic_data_version.Data` 执行全表 `LIKE`/包正文计数。逻辑大字段清空后,MySQL 表空间文件是否立即缩小取决于存储引擎;`OPTIMIZE TABLE` 只能在备份完成的维护窗口由管理员另行执行,不能由应用安装或压缩任务自动触发。
87
- - 商城来源以 `ApiBase + OsClient` 唯一定位。添加来源先只读发现系统标题、验证码策略和公开应用数;需要私有应用时再登录。帐号、密码、Token 不能写浏览器配置或商城来源 JSON;密码只用于本次登录,长会话 Token 以 `MCP/Mobile` 非 PC 客户端签发,并由当前租户后端加密保存到 `mci_system_setting`,浏览器只持有不具备取密能力的凭据 Key。
88
- - 后端来源代理必须固定已保存的 `ApiBase + OsClient`,拒绝过期 Token、访问密钥会话、非超级管理员、非 HTTPS 外网地址、重定向漂移和超限响应;Token 不得返回浏览器、日志、审计或应用包。退出登录同步删除服务端密文。
89
- - 商城主页面统一承载应用市场、已安装、我发布的应用、安装离线包和来源管理,不能再通过独立菜单或路由割裂上下文。来源增删改启停逐项自动保存;来源管理、详情和复杂配置使用平台统一 `80%` 可拖动大圆角 Dialog,遮罩服从正向开关 `sys_config.FormMaskBlur`,缺失或 `0/false` 默认关闭毛玻璃。
90
+ - 商城来源以 `ApiBase + OsClient` 唯一定位。添加来源先只读发现系统标题、验证码策略和公开应用数;需要私有应用时再登录。帐号、密码、Token 不能写浏览器配置或商城来源 JSON;密码只用于本次登录,长会话 Token 以 `MCP/Mobile` 非 PC 客户端签发,并由当前租户后端加密保存到 `mci_system_setting`,浏览器只持有不具备取密能力的凭据 Key。
91
+ - 后端来源代理必须固定已保存的 `ApiBase + OsClient`,拒绝过期 Token、访问密钥会话、非超级管理员、非 HTTPS 外网地址、重定向漂移和超限响应;Token 不得返回浏览器、日志、审计或应用包。退出登录同步删除服务端密文。
92
+ - 商城源业务由 `app.microi.store` 单一拥有的 `platform-marketplace-source`(`Managed`)编排,租户扩展只写 `platform-marketplace-source-hook`(`CreateIfMissing`,默认可执行正文精确为 `return { Code : 1 };`)。登录必须在远端配置读取、密码发送和凭据保存之前执行 `BeforeMarketplaceSourceLogin`;断开必须在删除服务端凭据之前执行 `BeforeMarketplaceSourceDisconnect`。Hook 失败直接阻断操作。Before Hook 仅允许 `Stage / SourceApiEngineKey / Action / SourceId`,不得传 `ApiBase`、远端 `OsClient`、账号、密码、Token、签名地址或凭据密文;协议、加密和密钥隔离继续由可信网关负责。
93
+ - 商城主页面统一承载应用市场、已安装、我发布的应用、安装离线包和来源管理,不能再通过独立菜单或路由割裂上下文。来源增删改启停逐项自动保存;来源管理、详情和复杂配置使用平台统一 `80%` 可拖动大圆角 Dialog,遮罩服从正向开关 `sys_config.FormMaskBlur`,缺失或 `0/false` 默认关闭毛玻璃。
90
94
  - 每张应用卡必须显示预览图、公开范围、分类、最新版本、当前租户已安装版本及状态色。来源卡必须显示其公开数和当前授权可访问总数;平台官方发布节点只显示“平台官方应用源”身份标记,不显示安装、更新或重新安装操作。
91
95
  - 安装可明确选择 `sys_microistore` 当前版本或 `mic_data_version` 中仍含完整包正文或已验证包指针的历史快照;后台任务在首次取包时必须把计划中的 `AppVersion` 解析为匹配且可安装的不可变 `StoreVersionId`,写入 checkpoint,并在全部后续分片一直传到详情取包。发布方中途升版时继续完成已锁定快照,新版留给下一轮盘点;快照缺失、版本不匹配、身份变化或快照 Id 漂移必须失败关闭,禁止退回易变当前行。实际安装版本写回 `sys_microistoreversion`。回退旧版属于重新安装,不得静默换成最新版。
92
96
  - 应用详情的版本选择必须服务端分页和搜索,默认每页不超过 20 条;当前版本固定置顶,历史版本只返回含完整旧包或有效 HDFS 指针的可安装状态。禁止用 `_PageSize:500` 或一次加载全部版本后在浏览器过滤。翻页、搜索与页大小切换都必须保持已选版本语义并显示总数。
@@ -148,12 +152,25 @@ description: Microi 应用商城开发、打包、安装和升级规范。用于
148
152
  - [ ] 缓存刷新后远端 API 与真实 UI 通过
149
153
  - [ ] 卸载范围明确、可审计、可恢复或已提示不可恢复
150
154
 
151
- ## 复盘:商城按钮已到达但依赖接口引擎缺失
155
+ ## 复盘:商城按钮已到达但依赖接口引擎缺失
152
156
 
153
157
  - 触发场景:子租户更新应用商城后能够看到“全部安装/更新”,点击却提示 `sys_apiengine` 中不存在按钮调用的接口;继续临时补引擎后,还可能因目标租户缺少批量计划表再次失败。
154
158
  - 根因:商城元数据版本、`AppPakcet.PackageInfo.Version` 与真实包正文发生分叉,页面按钮被单独更新;同时应用导入器把接口引擎新增/更新失败只写进 Debug 后继续返回成功,没有做写后回读,批量引擎又依赖未随同一应用包交付的表。
155
159
  - 通用规则:按钮及其调用的接口引擎、表和权限必须属于同一个单调版本应用包;任何依赖写入失败或回读内容不一致都要让安装事务失败。可复用的批量状态优先写入平台后台任务 `CheckpointJson`,避免仅为批量编排增加未交付的租户表。商城行版本、包内版本和包内容哈希必须同时回读一致,禁止单独提升商城行版本或只更新菜单。
156
- - 自动化检查:应用包契约测试必须从按钮代码提取接口 Key,断言包内存在启用且配置正确的引擎并与独立源码逐字一致;导入器测试覆盖新增失败、更新失败、缓存清理后回读缺失和源码不一致均返回 `Code=0`;真实子租户更新后回读 `sys_menu + sys_apiengine + 后台任务`,再实际执行一次“无需更新”和至少一个安装/更新计划。
160
+ - 自动化检查:应用包契约测试必须从按钮代码提取接口 Key,断言包内存在启用且配置正确的引擎并与独立源码逐字一致;导入器测试覆盖新增失败、更新失败、缓存清理后回读缺失和源码不一致均返回 `Code=0`;真实子租户更新后回读 `sys_menu + sys_apiengine + 后台任务`,再实际执行一次“无需更新”和至少一个安装/更新计划。
161
+
162
+ ## 复盘:新版前端先启动、基础包却遗漏登录菜单依赖
163
+
164
+ - 触发场景:租户先升级新版前后端,登录后路由初始化固定调用 `/apiengine/platform-sys-menu`;目标库此前没有该接口。应用商城包只补了 `platform-background-task`,而菜单接口仍由安装顺序靠后的 SaaS 包顺带携带;启动完整性检查也只验证后台任务接口,于是数据库版本和商城版本都显示最新,但用户在进入应用商城之前已经因 `NoExistData sys_apiengine` 全站不可用。
165
+ - 根因:把单个已修复依赖误当成完整的启动依赖集合,包资源闭包、`NeedRefresh` 完成判定和安装后强回读没有使用同一清单;发布验收只覆盖已有目标租户,未覆盖“新版二进制 + 历史库恰好缺少某一启动接口”的升级排列。
166
+ - 通用规则:凡是登录、菜单构建、恢复入口或应用商城打开之前必调的表、接口引擎和微服务路由,都属于启动依赖闭包,必须由安装顺序最前的同一个官方 Platform 包原子交付。每项接口必须同时声明固定自定义地址、最低版本、启用/匿名/HTTP 状态、`Managed` 策略、V8 原子能力和包内能力标记;启动完整性检查与安装后回读必须遍历同一依赖清单,任意一项缺失都触发修复且失败不推进版本。其它后置应用携带相同资源只能作兼容副本,不能成为启动正确性的唯一来源。
167
+ - 竞态与验收:前端可对明确的“启动 Managed 资源尚未落库”做不超过一分钟的有界退避,并在耗尽后显示精确包名和最低版本;它不能吞掉鉴权错误、普通网络错误,也不能代替服务端修复。契约测试须逐项删除或降级每个启动依赖,断言包校验、租户完整性判断和安装后回读都失败;真实验收至少覆盖一个历史缺失租户,先证明旧状态会失败,再安装精确商城版本、回读接口源码/地址/策略、执行菜单动作、刷新登录后页面,最后做同版本无操作复跑。
168
+
169
+ ## 复盘:应用包切换 HDFS 后旧导入器无法更新自己
170
+
171
+ - 触发场景:商城行与不可变版本快照已经只保存 HDFS 路径、大小和 SHA-256;历史租户仍运行只读取 `AppPakcet` 的导入器。用户尝试先更新“应用商城”以获得新版导入器时,旧导入器从商城源取得空 `AppPakcet`,在 3% 直接报“Package不能为空”,形成更新器无法更新自己的引导死锁。
172
+ - 通用协议:新版导入器请求商城模型时必须显式声明 `PackagePointerMode=HdfsV1`,自行下载一次并校验 UTF-8 字节数与 SHA-256。商城模型面对未声明该能力的旧调用端,允许从同一个受信 HDFS 指针读取并严格校验正文,只在本次响应的 `AppPakcet` 中临时回填;绝对禁止写回 `sys_microistore`、`mic_data_version`、任务参数或检查点。私有包仍必须使用短期授权地址,大小上限、HTTP 状态和摘要任一异常都失败关闭。
173
+ - 发布顺序与验收:先把兼容桥更新到官方商城源的 `get-microi-store-model`,再发布包含新版模型接口、导入器和启动依赖的精确应用商城版本。自动化同时覆盖新版请求零回填、旧请求可安装、摘要/大小不符拒绝、数据库无包正文字段更新;真实历史租户必须保留旧导入器启动第一次正式更新,等待终态成功并回读新版导入器,然后立即同版本复跑为零更新。直接通过 MCP 替换目标导入器只能作为已故障租户的最后恢复手段,不能代替协议兼容性验收。
157
174
 
158
175
  ## 复盘:可信后台任务被 StopHttp 提前拦截
159
176
 
@@ -103,7 +103,13 @@ await V8.Notification.MarkRead({ All: true });
103
103
 
104
104
  ## 应用商城交付
105
105
 
106
- “消息通知”应用包必须包含 `mic_msgset`、`mic_msg_event_log`、`wx_tpl_msg`、`wx_mp`、`wx_mini_program` 五张结构资源,以及相关菜单、`msg_event`、`msg_internal_list`、`msg_internal_mark_read` 和必要索引。`wx_mp`、`wx_mini_program` 只交付物理表结构与表单字段元数据,不得携带数据集;否则既可能泄露真实公众号/小程序密钥,也会覆盖目标租户配置。`sys_user.WxMpId` 和 `wx_tpl_msg` 会读取 `wx_mp`,漏包会使 `/system/diy-user` 等无关页面在加载 Select 数据源时触发 `GetDiyFieldSqlData` 缺表错误。包内不得包含真实公众号 Token/AppSecret、用户接收人、OpenId、历史发送记录或租户专属 URL。先 `ValidateOnly`,再在全新或缺表目标租户真实安装,回读五张表、应用版本和依赖页面;结构校验不能替代真实安装验收。
106
+ “消息通知”应用包必须包含 `mic_msgset`、`mic_msg_event_log`、`wx_tpl_msg`、`wx_mp`、`wx_mini_program` 五张结构资源,以及相关菜单、`msg_event`、`msg_internal_list`、`msg_internal_mark_read`、`platform-chat-system-message`、`platform-chat-runtime`、`platform-message-notification-custom-hook` 和必要索引。`wx_mp`、`wx_mini_program` 只交付物理表结构与表单字段元数据,不得携带数据集;否则既可能泄露真实公众号/小程序密钥,也会覆盖目标租户配置。`sys_user.WxMpId` 和 `wx_tpl_msg` 会读取 `wx_mp`,漏包会使 `/system/diy-user` 等无关页面在加载 Select 数据源时触发 `GetDiyFieldSqlData` 缺表错误。包内不得包含真实公众号 Token/AppSecret、用户接收人、OpenId、历史发送记录或租户专属 URL。先 `ValidateOnly`,再在全新或缺表目标租户真实安装,回读五张表、应用版本和依赖页面;结构校验不能替代真实安装验收。
107
+
108
+ 系统聊天门面 `platform-chat-system-message`(`Managed`)完成平台超级管理员校验后,只转调本应用单一拥有的 `platform-chat-runtime`(`Managed`)。运行时统一编排 `PersistMessage / PersistSystemMessage / PersistAssistantMessage / GetHistoryAndMarkRead / GetUnreadCount / TouchContact / ListContacts / DeleteContact`;Hub/Controller 旧入口只保留 DiyToken 认证、SignalR 投递和 AI 流式协议,不得直连 MongoDB/FormEngine 复制业务。访问密钥会话、空 Token、伪造租户/发送人必须失败关闭。
109
+
110
+ `platform-chat-runtime` 以 `V8.CurrentUser` / `V8.OsClient` 为唯一身份与租户事实源,对“租户 + 稳定 RequestId”生成确定性 Mongo `_id`;只有全部载荷哈希一致才复用旧记录。MongoDB 不参与 `V8.DbTrans`,因此消息/已读/联系人成功后即是已提交事实;SignalR、投影或 After Hook 失败必须保持 `Code=1` 并通过 `DataAppend.HookWarning` / `ProjectionWarnings` 告警,不得伪装未发生而引导盲目重试。调用 `V8.MongoDb.UptFormDataByWhere` / `DelFormDataByWhere` 时必须使用包含当前用户/权威资源边界的非空参数化 `_Where`,规则详见 `../v8-mongodb/SKILL.md`。
111
+
112
+ 租户个性化仅写入 `platform-message-notification-custom-hook`(`CreateIfMissing`),默认正文必须精确为 `return { Code : 1 };`。运行时在 `BeforeChatRuntime / AfterChatRuntime` 调用 Hook:Before 失败在 Mongo 写前阻断,After 失败只告警。Hook 只接收 `Stage`、`SourceApiEngineKey`、`Action`、`ActorUserId`、`PeerUserId`、`MessageId`、`MessageType`;正文、头像、OpenId、Token 与其它秘密不得进入租户扩展。三项接口的源码顶部都要保留官方恢复/租户不覆盖提示,并在包合同测试中逐字核对独立源码、包内副本、所有权策略和 HTTP/匿名开关。
107
113
 
108
114
  ## 最低验收
109
115
 
@@ -113,3 +119,4 @@ await V8.Notification.MarkRead({ All: true });
113
119
  4. 用户只能查询和标记自己的通知;危险链接、超长正文、跨租户接收人和匿名调用被拒绝。
114
120
  5. 公众号/服务号发送主体与小程序跳转目标分别验证,不把 `MiniProgramAppId` 当作模板发送主体。
115
121
  6. 源码定向测试、后端编译、远端 MCP 回读、真实浏览器点击和商城安装/校验分别报告;未执行的生产发布不得写成已上线。
122
+ 7. 聊天空 Token/访问密钥/伪造租户失败关闭;相同 `RequestId` 并发只有一份 Mongo 事实,不同载荷冲突拒绝;已持久后 SignalR/After Hook 失败仍返回成功并可回读。
@@ -33,18 +33,21 @@ description: Microi V8 与 MCP 文件上传下载指南。用于处理流式 AI
33
33
  - `RichText.Limit=false` 只用于需匿名长期访问的官网公告、商品详情等公开正文;内部内容用 `true`。普通交互式帐号即使传 `false`,后端仍可按安全策略强制私有,客户端必须以上传响应的实际 `Limit` 为准。
34
34
  - RichText 分别配置 `Image`、`Video`、`File` 的 `Enabled/MaxSize/MaxCount`;图片另传 `Preview/CompressMaxSize/CompressMaxWidth`,并继续遵循“原图私有、展示图公有或私有”的压缩链路。附件类型白名单用 `File.Accept` 进一步收紧,不能放宽服务端白名单。
35
35
  - 私有正文持久化 `/__microi_richtext_private__/...` 稳定对象标识,严禁保存对象存储签名 URL、`OpenPrivateFile` Ticket、DiyToken 或其它会过期的凭据。每次打开记录时携带 `FormEngineKey/FormDataId/FieldId/SysMenuId` 批量换取短效审计代理地址。
36
- - 私有文件后端授权必须重新校验当前租户、菜单、表、行和 RichText 字段,并确认所请求路径精确存在于 `img/video/source.src` 或 `a.href`;正文文字、`data-src/data-href`、脚本标签和前缀相似路径都必须失败关闭。
36
+ - 私有文件后端授权必须重新校验当前租户、菜单、表、行和 RichText 字段,并确认所请求路径精确存在于 `img/video/source.src` 或 `a.href`;普通上传字段的对象/数组只认 `Path/FilePath/FilePathName`,不得递归把 `Name/Size/Metadata` 等任意标量当作路径。未经当前租户 FileServer 主机权威校验的绝对 HTTP(S) URL 不得等价为本地对象 Key;正文文字、`data-src/data-href`、脚本标签和前缀相似路径都必须失败关闭。
37
37
  - 外部匿名页面没有后台记录权限上下文,不能解析私有标识。公开文章必须由有权发布公有资产的超级管理员显式使用公有桶,不得通过延长私有 URL 有效期模拟公开资源。
38
38
 
39
+ 官网客户端读取私有文件统一调用 `/apiengine/platform-private-file-url`,提交 `FilePathName` 或有界 `FilePathNames`,并按资源类型提供权威定位参数:普通表单字段使用 `FormEngineKey + FormDataId + FieldId + SysMenuId`;用户头像使用 `ResourceKind=UserAvatar + ResourceId=用户Id`;菜单/部门导入模板分别使用 `MenuImportTemplate`、`DeptImportTemplate` 与对应记录 Id。CAD 私有派生预览使用 `ResourceKind=FormFieldDerivedPreview`,除表单四元组外必须同时提交字段中保存的 `OriginalFilePathName` 和单个派生 `FilePathName`;后端只接受同目录同 basename 的 DWG→`_preview.dxf`、STEP/STP→`_preview.stl` 唯一映射,并在对象存在后签名。文件柜对象使用 `ResourceKind=FileManagerObject`,`ResourceId` 必须与单个 `FilePathName` 大小写精确相同,并提交能力探针返回的当前租户权威 `SysMenuId`;此类签名只允许平台超级管理员 DiyToken 会话,访问密钥和普通菜单用户一律拒绝。后端会从权威字段或对象存储重新读取并精确匹配路径;管理员也不能只传裸路径绕过对象引用,普通客户端禁止换取私有文件原始 Byte/Stream。旧 `/api/HDFS/GetPrivateFileUrl` 与 `/api/HDFS/MallFileUrl` 只保留令牌格式兼容并转发同一 Managed 接口,新代码不得继续引用。
40
+
39
41
  <!-- microi-progressive:begin -->
40
- <!-- microi-progressive:chunk id=v8-file-upload-000 sha256=b48ce09f93a43efd30e700af637db3881355e8d2baaf45165ea2bd0bdfda25cf -->
42
+ <!-- microi-progressive:chunk id=v8-file-upload-000 sha256=841bb634227ea21cf97abcbee5dc6220042e73139170e6a6c304bfce83274e7a -->
41
43
  ## 核心 API
42
44
 
43
45
  | API | 说明 |
44
46
  |-----|------|
45
47
  | `V8.FilesByteBase64` | 接收上传时携带的文件字典 `{ FileName: base64 }` |
46
48
  | `V8.Method.Upload({...})` | 服务端上传文件到 HDFS(推荐) |
47
- | `V8.Method.GetPrivateFileUrl({FilePathName})` | 生成私有桶临时访问 URL |
49
+ | `V8.Method.GetPrivateFileUrl({FilePathName})` | 生成私有桶临时访问 URL |
50
+ | `/apiengine/platform-private-file-url` | 官网 PC/UniApp 按菜单、记录、字段和对象引用换取私有文件短链 |
48
51
  | `V8.Http.GetResponse({Url}).RawBytes` | 下载远程文件为字节数组 |
49
52
  | 接口返回 `{ FileName, ContentType, FileByteBase64 }` | 接口直接响应文件 |
50
53
 
@@ -63,10 +66,10 @@ description: Microi V8 与 MCP 文件上传下载指南。用于处理流式 AI
63
66
  可信后端 V8 可用 `V8.Http.GetResponse({ Url: url }).RawBytes` 下载,再用 `System.Convert.ToBase64String` 和 `V8.Method.Upload` 上传。该路径同样必须校验域名、大小、Content-Type、后缀和最终重定向目标。
64
67
 
65
68
  <!-- /microi-progressive:chunk -->
66
- <!-- microi-progressive:chunk id=v8-file-upload-002 sha256=dc69d3bcadb1b40d98e1346a8d9d0a3599cae7f7dd6b850dd95ad054c747b2dd -->
69
+ <!-- microi-progressive:chunk id=v8-file-upload-002 sha256=c20863b68d13b3bd41caee90a5e12878501a35b3d9f8d40a80efe3f6b46125a9 -->
67
70
  ## 接收前端上传的文件
68
71
 
69
- 前端发起文件上传时,平台自动把文件以 base64 形式注入到 `V8.FilesByteBase64`:
72
+ 前端发起文件上传时,平台自动把文件以 base64 形式注入到 `V8.FilesByteBase64`:
70
73
 
71
74
  ```javascript
72
75
  // V8.FilesByteBase64 = { '文件名1.png': 'base64...', '文件名2.pdf': 'base64...' }
@@ -165,7 +168,7 @@ Unity `Data`、WASM、Windows 安装包、视频模型等发布资产不得进
165
168
  - 生产 H5 不能只依赖 `uni.uploadFile`。页面从 `uni.chooseImage` 得到的 `tempFiles[0].file`、`tempFiles[0]`、`blob:` / `data:` 临时路径都要传给 `V8.uploadFile`,并设置 `preferFetch:true`;SDK 必须能用 `fetch + FormData` 兜底,否则线上可能报 `未找到 MicroiV8 上传适配器。`。
166
169
 
167
170
  <!-- /microi-progressive:chunk -->
168
- <!-- microi-progressive:chunk id=v8-file-upload-003 sha256=a31f0170454bb9e44007e24ed280873e99ae4ca4f4ecc7307ecc3e738eb51df9 -->
171
+ <!-- microi-progressive:chunk id=v8-file-upload-003 sha256=d02dfde4abd2349cd92de1daee129bc08ba42142b5f6229736588cb3e0dbf43c -->
169
172
  ## 跨平台文件同步登录会话
170
173
 
171
174
  文件柜、文件同步等需要连接另一套 Microi API 的工具,必须把远程平台视为独立登录会话:
@@ -175,7 +178,7 @@ Unity `Data`、WASM、Windows 安装包、视频模型等发布资产不得进
175
178
  - 密码和 Token 只能由受保护的接口引擎写入、读取和清理。数据库必须保存可校验的加密密文,普通 FormEngine 列表不得返回密文字段。
176
179
  - 密码和 Token 使用 `V8.Method.ProtectApiEngineSecret/UnprotectApiEngineSecret`,由宿主把密文绑定当前 `OsClient + ApiEngineKey`;不得从已脱敏的 `V8.OsClientModel` 读取 `AuthSecret/DbConn`,也不得使用进程级临时密钥。接口引擎 Key 必须稳定,确保服务重启和应用升级后仍能解密历史连接。
177
180
  - 历史连接列表只返回脱敏元数据;一键重连时再按记录 Id 和当前用户读取凭据。删除连接必须同时清除保存的密码和 Token。
178
- - 远程目标登录后必须调用文件柜能力探针(如 `mci_file_sync_capability`)检查同步协议版本。接口不存在、返回 404/非标准结果或协议版本过低时,提示目标平台更新【文件柜】应用,不得继续同步。
181
+ - 远程目标登录后必须调用文件柜能力探针 `mci_file_sync_capability` 检查同步协议版本,并使用其 `Data.FileManagerSysMenuId` 作为目标租户权威文件柜菜单;禁止硬编码发布端菜单 Id。接口不存在、未返回菜单 Id、返回 404/非标准结果或协议版本过低时,提示目标平台更新【文件柜】应用,不得继续同步。
179
182
  - 验收至少覆盖:登录成功显示身份、退出后 Token 清空、历史连接一键重连、删除连接、密文落库、服务重启后仍可解密、目标平台缺少能力接口时的升级提示。
180
183
 
181
184
  <!-- /microi-progressive:chunk -->
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: v8-mongodb
3
- description: Microi V8 MongoDB 指南。用于使用 V8.MongoDb AddFormData、UptFormData、DelFormData、GetFormData、GetTableData、对象过滤和文档 Id。
3
+ description: Microi V8 MongoDB 指南。用于使用 V8.MongoDb AddFormData、UptFormData、UptFormDataByWhere、DelFormData、DelFormDataByWhere、GetFormData、GetTableData、对象过滤和文档 Id。
4
4
  ---
5
5
 
6
6
  > **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
@@ -14,8 +14,10 @@ description: Microi V8 MongoDB 指南。用于使用 V8.MongoDb AddFormData、Up
14
14
  | 方法 | 说明 |
15
15
  |------|------|
16
16
  | `V8.MongoDb.AddFormData({...})` | 新增文档 |
17
- | `V8.MongoDb.UptFormData({...})` | 修改文档(按 Id) |
18
- | `V8.MongoDb.DelFormData({...})` | 删除文档(按 Id) |
17
+ | `V8.MongoDb.UptFormData({...})` | 修改文档(按 Id) |
18
+ | `V8.MongoDb.UptFormDataByWhere({...})` | 按非空 `_Where` 批量修改,禁止修改 `_id` |
19
+ | `V8.MongoDb.DelFormData({...})` | 删除文档(按 Id) |
20
+ | `V8.MongoDb.DelFormDataByWhere({...})` | 按非空 `_Where` 批量删除 |
19
21
  | `V8.MongoDb.GetFormData({...})` | 查询单个文档(按 Id) |
20
22
  | `V8.MongoDb.GetTableData({...})` | 查询文档列表 |
21
23
  | `V8.MongoDb.NewId()` | 生成 MongoDB Id |
@@ -40,7 +42,7 @@ V8.MongoDb.AddFormData({
40
42
  ## 修改文档
41
43
 
42
44
  ```javascript
43
- V8.MongoDb.UptFormData({
45
+ V8.MongoDb.UptFormData({
44
46
  DbName: 'sys_log_2024',
45
47
  TableName: 'log_2024_12',
46
48
  Id: V8.Param.id, // 必传
@@ -48,18 +50,50 @@ V8.MongoDb.UptFormData({
48
50
  Action: '更新操作',
49
51
  UpdateTime: DateNow('yyyy-MM-dd HH:mm:ss')
50
52
  }
51
- });
52
- ```
53
+ });
54
+ ```
55
+
56
+ ### 按条件批量修改
57
+
58
+ ```javascript
59
+ var result = V8.MongoDb.UptFormDataByWhere({
60
+ DbName: 'diy_chat_' + V8.OsClient.toLowerCase(),
61
+ TableName: 'chat_' + DateNow('yyyy'),
62
+ _Where: [
63
+ ['FromUserId', '=', V8.Param.PeerUserId],
64
+ ['ToUserId', '=', V8.CurrentUser.Id],
65
+ ['IsRead', '=', false]
66
+ ],
67
+ _FormData: { IsRead: true }
68
+ });
69
+ ```
70
+
71
+ `_Where` 缺失、为空或包含无效条件时必须返回失败,不得执行全集合更新。`_FormData` 禁止设置 `_id` 或 `_id.*`。返回值的 `Data` 包含 `MatchedCount` 和 `ModifiedCount`。
53
72
 
54
73
  ## 删除文档
55
74
 
56
75
  ```javascript
57
- V8.MongoDb.DelFormData({
76
+ V8.MongoDb.DelFormData({
58
77
  DbName: 'sys_log_2024',
59
78
  TableName: 'log_2024_12',
60
79
  Id: V8.Param.id // 必传
61
- });
62
- ```
80
+ });
81
+ ```
82
+
83
+ ### 按条件批量删除
84
+
85
+ ```javascript
86
+ var result = V8.MongoDb.DelFormDataByWhere({
87
+ DbName: 'diy_chat_' + V8.OsClient.toLowerCase(),
88
+ TableName: 'chat_last_contact',
89
+ _Where: [
90
+ ['UserId', '=', V8.CurrentUser.Id],
91
+ ['ContactUserId', '=', V8.Param.PeerUserId]
92
+ ]
93
+ });
94
+ ```
95
+
96
+ 删除条件必须包含当前权威用户/资源边界,不能只用可伪造的前端参数。返回值的 `Data` 包含 `DeletedCount`。
63
97
 
64
98
  ## 查询单个文档
65
99
 
@@ -74,14 +108,18 @@ var result = V8.MongoDb.GetFormData({
74
108
  ## 查询文档列表
75
109
 
76
110
  ```javascript
77
- var result = V8.MongoDb.GetTableData({
111
+ var result = V8.MongoDb.GetTableData({
78
112
  DbName: 'sys_log_2024',
79
113
  TableName: 'log_2024_12',
80
114
  _Where: [
81
115
  ['Type', '=', '访问菜单'],
82
116
  ['OR', 'Type', '=', '点击V8按钮']
83
- ]
84
- });
117
+ ],
118
+ _OrderBy: 'CreateTime',
119
+ _OrderByType: 'DESC',
120
+ _PageIndex: 1,
121
+ _PageSize: 20
122
+ });
85
123
  ```
86
124
 
87
125
  ## 实战模式
@@ -143,9 +181,11 @@ return { Code: 1, Data: result };
143
181
 
144
182
  ## 注意事项
145
183
 
146
- - MongoDB 参数统一使用**对象格式**:`{ DbName, TableName, Id, _FormData, _Where }`
147
- - `DbName` 是 MongoDB 数据库名,`TableName` 是集合名
148
- - `_Where` 条件语法与 `V8.FormEngine` 一致
149
- - 适合存储日志、IoT 数据、大文档等非结构化 / 海量数据
150
- - 建议按时间分库分表(如 `log_2024_01`),便于清理历史数据
151
- - MongoDB 操作不参与 `V8.DbTrans` 事务
184
+ - MongoDB 参数统一使用**对象格式**:`{ DbName, TableName, Id, _FormData, _Where, _OrderBy, _OrderByType }`
185
+ - `DbName` 是 MongoDB 数据库名,`TableName` 是集合名
186
+ - `_Where` 条件语法与 `V8.FormEngine` 一致
187
+ - 批量写入仅接受安全字段名、已知操作符和非空条件;条件中的租户、用户和资源 Id 应来自 `V8.OsClient` / `V8.CurrentUser` / 权威回查,不得信任 `V8.Param` 中的同名身份字段
188
+ - 非主库 V8 运行时会把 MongoDB 操作绑定到当前租户;显式传入其它 `OsClient` 不能跨租户
189
+ - 适合存储日志、IoT 数据、大文档等非结构化 / 海量数据
190
+ - 建议按时间分库分表(如 `log_2024_01`),便于清理历史数据
191
+ - MongoDB 操作不参与 `V8.DbTrans` 事务。批量写入 `Code=1` 后即是已提交事实;后续 Hook/投递失败应返回警告或进入补偿,不得返回“未发生”导致盲目重试,重试必须有稳定业务 Id 幂等
@@ -54,8 +54,11 @@ V8.OsClientModel.AliOssPublicDomain // 可公开的文件域名
54
54
  主库 `sys_osclients` 保存租户数据库/Redis/存储路由及影响整个 API 进程的平台级配置;目标租户建立上下文后,`sys_config` 和 `mci_system_setting` 从该租户自己的数据库读取。平台级配置必须遵守“主租户为准、子租户只能降额隔离”的规则:
55
55
 
56
56
  - 所有可变业务逻辑默认必须由接口引擎编排,包括但不限于租户开通、开库、初始化、归属修复、官网个人中心、付费额度等 SaaS 业务流程。C# 后端只暴露原子 V8 能力,例如建库、导入空库模板、复制 `sys_config`、刷新 SaaS 缓存、补偿回滚、字段兜底等;不要把可变业务分支写死到 Controller 或 `TenantProvisioningService` 这类后端定制代码里。接口引擎缺少能力时,优先扩展 `V8.Method`/V8 引擎原子函数,再由接口引擎调用。
57
+ - 官方租户开通固定由 SaaS 包的 `platform-create-tenant` 编排:`platform-runtime-custom-hook` 的 Before 阶段可以阻断,`AuthorizeCurrentUserTenantProvisioning` / `ProvisionCurrentUserTenant` 只从可信主租户 DiyToken 派生所有者与密码材料;创建完成后的 Hook/审计失败只能返回 Warning,不能诱导重复创建。系统账号偏好/资料与租户系统设置分别由 `app.microi.sys_user`、`app.microi.sys-config` 单一拥有,SaaS/Store 不得复制这些 Managed ApiEngineKey。
58
+ - 旧版 UniApp 的 `microi-init` 是 SaaS 包继续托管的 Managed 兼容门面。匿名阶段只组合 `platform-os-client-by-domain` 与 `platform-sys-config` 的公开投影;原始 DiyToken 必须由 `GetCurrentToken` 和 `RefreshLoginUser(..., rawToken)` 在后端重新验证且绑定当前租户,菜单再由只允许该固定 Key 调用的 `GetLegacyInitMenuTree(rawToken, osClient?)` 复用 `SysMenuLogic` 权威角色过滤。禁止在匿名 V8 中直接通过 FormEngine 读取 `sys_menu`;域名解析到其它租户时返回目标 `OsClient` 并要求重试,禁止匿名跨租户读取配置或菜单。
57
59
  - 主租户由运行环境决定:优先读取环境变量 `OsClient`,其次读取 `appsettings.json` 的 `AppSettings:OsClient`。只有这条主租户 `sys_osclients` 数据中的平台级字段会作为全局配置生效。
58
60
  - API 启动配置只有十项白名单:`OsClient`、`OsClientType`、`OsClientNetwork`、`OsClientDbType`、`OsClientDbConn`、`OsClientRedisHost`、`OsClientRedisPort`、`OsClientRedisPwd`、`OsClientRedisDataBase`、`OsClientDbMongoConn`。除这十项外,部署/节点级运行参数与基础设施秘密从主控 `sys_osclients` 读取;允许子租户自行维护且需要浏览器判断的业务开关、入口显示和公开交互配置使用该租户 `sys_config` 实体字段,OAuth/第三方集成的凭据、RP/Origin/Issuer/Scope 与仅后端参数使用 `mci_system_setting`,未配置时使用代码安全默认值。官方 License 恢复次数/间隔与固定私钥挂载 `/app/microi_private.pem` 是信任链例外。禁止再增加 `MICROI_*`、`DOS_ORM_*`、自定义 `AppSettings` 节点或动态名称的环境变量读取。节点身份由平台自动生成。
61
+ - 存量畅捷通/微信 OAuth C# 协议网关有一组已发布到 SaaS 引擎的兼容字段:`OAuthReturnUrlOrigins`、`ChanjetOAuthState`、`ChanjetAesKey`、`ChanjetAppKey`、`WeChatTemplateAppId`、`WeChatTemplateAppSecret`、`WeChatTemplateId`、`WeChatMiniProgramAppId`。只能通过 Core 的租户绑定协议设置原子按请求权威 `OsClient` 读取,禁止使用主租户 `ConfigHelper` RuntimeConfigurationReader;整组字段不得复制给新租户,其中 OAuthState/AES Key/AppKey/AppSecret 不得进入 `V8.OsClientModel` 或前端投影且必须在审计中掩码。新集成不得继续扩展该兼容集合,仍使用当前租户 `mci_system_setting` + Managed ApiEngine。
59
62
  - `ASPNETCORE_*`、`DOTNET_*` 仅用于 .NET 宿主;构建、安装、测试、MCP、发布脚本可使用自身进程变量,但 API 生产代码不得把它们当业务配置。新增 SaaS 运行字段必须配套独立或既有 Tab、幂等升级、缓存刷新、敏感字段脱敏、子租户不继承和源码扫描测试。
60
63
  - 文件上传的租户业务开关与额度按“当前租户 `sys_osclients` → 代码默认值”解析;平台固定灾难保护、HTTP/Multipart/Form 和反向代理上限不可由租户覆盖,也不要求安装者维护额外上传环境变量。
61
64
  - 类似 MQTT 端口、PressureGuard、V8Limits、OrmLimits、StartupLimits、SecurityGuard 这类影响整进程资源的配置,不能让每个子租户各自抬高全局上限。子租户同名隔离字段只能降低自己的并发、等待时间或资源额度,用于隔离弱租户、试用租户或异常租户。
@@ -9,7 +9,9 @@ description: Microi V8 安全指南。用于审查 DiyToken 与权限、可逆
9
9
 
10
10
  你正在开发 Microi 吾码平台的 V8 引擎代码,必须遵守以下安全规范。
11
11
 
12
- 访问密钥由 `microi_list_my_access_keys`、`microi_create_my_access_key`、`microi_revoke_my_access_key` 管理,只允许当前用户、限期、最小 scope,明文仅创建时返回一次。外部身份回调固定为 `/api/ExternalLogin/Callback`,服务端校验租户、Provider、state、redirect 和回调域名,验证成功后仍签发 DiyToken。
12
+ 访问密钥由 `microi_list_my_access_keys`、`microi_create_my_access_key`、`microi_revoke_my_access_key` 管理,只允许当前用户、限期、最小 scope,明文仅创建时返回一次。外部身份回调固定为 `/api/ExternalLogin/Callback`,服务端校验租户、Provider、state、redirect 和回调域名,验证成功后仍签发 DiyToken。
13
+
14
+ 官方升级资源属于独立控制面。`get-microi-upgrade-resource` 可以匿名读取固定白名单,但 `Publish/PublishBatch` 必须调用仅绑定该 Managed ApiEngineKey 的 `V8.Method.AuthorizeOfficialResourcePublish()`:固定 `iTdos` 官方租户、拒绝访问密钥会话,并从主库复核当前用户、状态和平台管理员角色。禁止只相信 `V8.CurrentUser.Level`,也禁止把这个可信原子复用于普通接口、租户 Hook 或表单事件;资源校验、SHA 乐观锁、事务行锁、写入及回读仍由 Managed V8 编排。
13
15
 
14
16
  <!-- microi-progressive:begin -->
15
17
  <!-- microi-progressive:chunk id=v8-security-000 sha256=d03ee34e72925db55f9022de26dfc251c0d3d69fac52ae6a910d42ddfc142ae7 -->
@@ -35,7 +35,7 @@
35
35
  | `V8.Db`、`V8.DbRead`、`V8.DbTrans` | 主库、只读库、共享事务 |
36
36
  | `V8.DbTrans.FromSql(sql)` | 在平台提供的共享事务内执行参数化 SQL |
37
37
  | `V8.Dbs`、`V8.Dbs.Open(...)` | 已配置的扩展数据库 |
38
- | `V8.MongoDb.*` | MongoDB CRUD |
38
+ | `V8.MongoDb.*` | MongoDB CRUD;`UptFormDataByWhere` / `DelFormDataByWhere` 强制参数化非空 `_Where`,禁止更新 `_id`,绑定当前 V8 租户,且不参与 `V8.DbTrans` |
39
39
  | `V8.DataSourceEngine` | 当前租户数据源引擎对象 |
40
40
  | `V8.DataSourceEngine.Run(...)`、`V8.DataSourceEngine.RunAsync(...)` | 同步/请求内异步运行数据源 |
41
41
  | `V8.ModuleEngine` | 后端模块模型能力;不能绕过用户模块权限 |
@@ -51,19 +51,37 @@
51
51
  | `V8.Method.NewGuid()`、`V8.Method.NewUlid()` | 生成标识 |
52
52
  | `V8.Method.GetTimestamp()` | Unix 秒时间戳 |
53
53
  | `V8.Method.GetCurrentToken(token,osClient)` | 读取当前 Token 对象;不透传前端 |
54
- | `V8.Method.RefreshLoginUser(userId,osClient)` | 刷新用户登录缓存 |
54
+ | `V8.Method.RefreshLoginUser(userId,osClient?,token?)` | 刷新登录投影;租户取当前 V8/已认证 DiyToken(可信宿主须建立用户+租户作用域),显式 OsClient 仅作一致性断言;普通用户仅本人,同租户超级管理员主库复核后可跨用户。第三参仅兼容历史 `microi-init` 的原始 Token,宿主重新验证且只允许 Token 本人;返回对象、访问密钥、空身份和跨租户均拒绝 |
55
55
  | `V8.Method.ClearUserLoginInfo(userId,osClient)` | 管理员吊销用户全部终端 Token |
56
56
  | `V8.Method.GetDirectTableGrantPolicies()` | 读取平台表直连授权策略;仅供可信角色表单事件做最终校验 |
57
57
  | `V8.Method.ConsumeIdentityVerificationTicket({Ticket,Purpose,ActionHash})` | 按当前 DiyToken 用户、租户、用途和操作摘要原子消费一次性 Passkey/TOTP/人脸票据 |
58
- | `V8.Method.GetPrivateFileUrl({FilePathName})` | 签发当前租户短期私有文件代理地址 |
59
- | `V8.Method.Upload(options)` | 受配额限制的上传 |
60
- | `V8.Method.AddSysLog(options)` | 结构化系统日志 |
58
+ | `V8.Method.GetPrivateFileUrl({FilePathName})` | 签发当前租户短期私有文件代理地址 |
59
+ | `V8.Method.ResolveOsClientByDomain(domain)` | 仅允许官方 `platform-os-client-by-domain` 调用;返回最小 OsClient 投影 |
60
+ | `V8.Method.GetPublicSysConfig(lang?)` | 仅允许官方 `platform-sys-config` 调用;返回浏览器安全系统设置投影 |
61
+ | `V8.Method.GetLangBundle(lang?,prefix?)` | 仅允许官方 `platform-lang-bundle` 调用;读取当前租户词条包 |
62
+ | `V8.Method.GetLoginWallpapers()` | 仅允许官方 `platform-login-wallpapers` 调用;固定读取当前租户最多 200 条启用壁纸的 `Id/Name/Category/ImgUrl`,不开放通用匿名表权限 |
63
+ | `V8.Method.GetLegacyInitMenuTree(rawToken, osClient?)` | 仅允许官方 `microi-init` 调用;重新验证请求体原始 DiyToken、拒绝访问密钥与跨租户请求,并按当前角色返回权威菜单树 |
64
+ | `V8.Method.GetAuthorizedPrivateFileUrl(options)` | 仅允许官方 `platform-private-file-url` 调用;重算菜单、行、字段与文件引用授权 |
65
+ | `V8.Method.AuthorizeCurrentUserTenantProvisioning()` | 仅允许官方 `platform-create-tenant` 在 Before Hook 前校验主租户普通登录会话 |
66
+ | `V8.Method.ProvisionCurrentUserTenant(options)` | 仅允许官方 `platform-create-tenant` 调用;所有者、手机号、姓名和密码材料从可信当前用户派生 |
67
+ | `V8.Method.PrepareCurrentUserProfileUpdate(options)` | 仅允许官方 `platform-user-update-profile` 调用;固定当前用户并规范当前租户头像路径 |
68
+ | `V8.Method.ManageSysUserAdmin(options)` | 仅允许官方 `platform-sys-user-admin` 调用;固定身份与租户并执行表权限、角色层级、改密 step-up、内容安全及会话安全边界;授权预检返回规范化 `DataAppend.ChangesPassword` |
69
+ | `V8.Method.AuthorizeOfficialResourcePublish()` | 仅允许官方 `get-microi-upgrade-resource` 的发布分支调用;固定 `iTdos`、拒绝访问密钥并从主库复核平台管理员;不是通用授权 API |
70
+ | `V8.Method.ValidateTenantSystemSettingsOperation(options)` | 仅允许官方 `platform-tenant-system-settings` 调用;拒绝访问密钥、非管理员、Secret/Sensitive Key 和已迁移公开 Key |
71
+ | `V8.Method.GetTenantSystemSettingsSecurityProjection()` | 仅允许官方 `platform-tenant-system-settings` 调用;只返回 Secret 是否已配置与迁移 Key,不返回值或密文 |
72
+ | `V8.Method.Upload(options)` | 受配额限制的上传 |
73
+ | `V8.Method.UploadText(options)` | 直接上传当前租户 UTF-8 文本,避免 Base64 膨胀;仍执行安全文件名、扩展名、HDFS、配额与 256 MB 文本上限 |
74
+ | `V8.Method.AddSysLog(options)` | 结构化系统日志 |
61
75
  | `V8.Method.ParseWhere(where)` | 兼容旧 Where 转换 |
62
76
  | `V8.Method.UpdateBackgroundTask(options)` | 上报已提交单位的后台任务进度 |
63
77
  | `V8.Method.RefreshExtensionDatabases(osClient?)` | 配置表提交后刷新全节点 `V8.Dbs` |
64
78
 
65
- 管理员维护、备份、清库、缓存连接管理等低层方法即使可见,也不能暴露为普通
66
- 或匿名业务 API。
79
+ 管理员维护、备份、清库、缓存连接管理等低层方法即使可见,也不能暴露为普通
80
+ 或匿名业务 API。
81
+
82
+ 以上启动、文件、系统账号和租户设置 `platform-*` 原子都是固定 Managed 接口的可信宿主边界,不是普通业务脚本可复用的快捷方法。官网客户端调用对应 `/apiengine/platform-*` 路由;旧 Controller 只保留旧版本兼容。登录壁纸原子不要求 `diy_wallpaper.IsAnonymousRead=1`,也不允许租户 Hook 参与匿名请求。租户个性化逻辑写入应用声明的 `CreateIfMissing` Hook,禁止直接修改会被官方安装或更新恢复的 Managed 接口。密码、DiyToken、Secret 保存和 Reveal 仍属于 C# 可信边界。
83
+
84
+ 旧客户端兼容路由 `/api/SysUser/UpdateMyDefaultIndexUrl`、`/api/SysUser/UpdateCurrentProfile` 和 `/api/TenantSystemSettings/List` 只固定转发上述 Managed 接口;新客户端不得继续以 Controller 作为业务事实源。
67
85
 
68
86
  ## Base64 与加密
69
87