@microi.net/cli 5.2.7 → 5.2.9
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.
- package/.codebuddy-plugin/marketplace.json +2 -2
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.workbuddy-plugin/marketplace.json +2 -2
- package/.workbuddy-plugin/plugin.json +1 -1
- package/assets/build-meta.json +4 -4
- package/cordis.patch.yml +1 -1
- package/package.json +1 -1
- package/scripts/microi-cli.js +37 -29
- package/scripts/microi-skills.meta.json +204 -204
- package/skills/.microi-skills-version.json +2 -2
- package/skills/.progressive-disclosure-manifest.json +15 -15
- package/skills/ai-engine/SKILL.md +11 -3
- package/skills/app-store/SKILL.md +29 -12
- package/skills/message-notification/SKILL.md +12 -1
- package/skills/message-notification/references/contracts.md +1 -0
- package/skills/microi-ai-app-auth.js +4 -3
- package/skills/microi-client-frontend/references/progressive-01-3-/345/212/250/346/200/201/346/214/211/351/222/256/347/263/273/347/273/237.md +1 -1
- package/skills/microi-client-frontend/references/progressive-02-8-/350/277/220/350/241/214/346/227/266/351/253/230/351/242/221/345/235/221/345/244/215/347/233/230.md +1 -1
- package/skills/microi-frontend-sdk/SKILL.md +1 -1
- package/skills/microi-mobile-app-quality/SKILL.md +1 -1
- package/skills/microi-system-delivery/references/progressive-02-/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/345/277/205/351/241/273/350/246/206/347/233/226/347/232/204/345/235/221.md +1 -1
- package/skills/microi-uniapp-frontend/SKILL.md +1 -1
- package/skills/microi.v8.js +75 -21
- package/skills/v8-file-upload/SKILL.md +10 -7
- package/skills/v8-file-upload/references/progressive-01-/345/205/254/346/234/211/346/241/266-vs-/347/247/201/346/234/211/346/241/266.md +1 -1
- package/skills/v8-file-upload/references/progressive-02-office-/346/226/207/344/273/266/345/234/250/347/272/277/347/274/226/350/276/221/347/211/210/346/234/254/345/217/267/350/247/204/345/210/231.md +1 -1
- package/skills/v8-mongodb/SKILL.md +58 -18
- package/skills/v8-saas-multi-tenant/SKILL.md +3 -0
- package/skills/v8-security/SKILL.md +3 -1
- package/skills/v8-utilities/references/platform-http-routes.md +1 -1
- package/skills/v8-utilities/references/server-api-index.md +25 -7
|
@@ -38,7 +38,7 @@ description: Microi AI 引擎、MiniMax 图片/音乐/视频生成与预览、
|
|
|
38
38
|
|
|
39
39
|
`Chat/ChatStream` 虽声明 GET/POST,含问题、附件和会话上下文的业务调用默认使用 POST,避免敏感内容进入 URL 日志。Controller 和 `V8.AI` 必须从可信执行上下文覆盖当前用户和 `OsClient`,并清除客户端提交的 `ApiKey/Endpoint`。所谓打字机效果必须来自真实 SSE 增量块;`microi_chat` 只返回最终结果,不能被描述成逐 token MCP 流。
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
安全数据分析的新客户端调用 `/apiengine/platform-ai-runtime` 并固定 `Action=NL2SQL`;旧 `/api/Ai/NL2SQL` 只保留兼容转发。最终都由租户/用户绑定的 `V8.AI.NL2SQL` 从当前用户权限生成表白名单;任何通用包装接口都不得接受客户端自报的表名作为授权。
|
|
42
42
|
|
|
43
43
|
## 代码分层
|
|
44
44
|
|
|
@@ -46,8 +46,13 @@ AI 业务统一实现在 `Microi.Server/Microi.AI`。`Microi.Server/Microi.net.A
|
|
|
46
46
|
|
|
47
47
|
- Controller、Hub 只调用 `IMicroiAI`、`AiProxyService`、`SubscriptionService`、`AiWorkflowService` 等 `Microi.AI` 门面,不直接查询 `mic_ai` / `mic_sub_order`,不接触上游密钥和向量基础设施。
|
|
48
48
|
- “授权 + 执行”必须由领域门面原子完成,不能让接口层先生成可伪造的 `AllowedTables` 或遗漏某一步。
|
|
49
|
-
- AI 的 Qdrant/Ollama/Embedding 配置、Schema 初始化和其它生命周期任务由 `AddMicroiAI()` 在模块内自注册;API 的 `Program.cs` 只负责调用模块注册。
|
|
50
|
-
- `Microi.Core` 只承载跨模块契约和模型。新增入口时先扩展 AI 领域服务,再添加薄 Controller/Hub 适配,禁止复制业务流程。
|
|
49
|
+
- AI 的 Qdrant/Ollama/Embedding 配置、Schema 初始化和其它生命周期任务由 `AddMicroiAI()` 在模块内自注册;API 的 `Program.cs` 只负责调用模块注册。
|
|
50
|
+
- `Microi.Core` 只承载跨模块契约和模型。新增入口时先扩展 AI 领域服务,再添加薄 Controller/Hub 适配,禁止复制业务流程。
|
|
51
|
+
- AI 账户统一入口是 Managed 接口引擎 `/apiengine/platform-ai-account`;套餐、订阅和订单由 V8 编排。支付宝回调 C# 只做可信租户选择/归一化、该租户配置验签和一次性 Managed 调用;`CompletePayment` 在同一接口引擎事务内完成订单条件认领、金额校验、订阅续期/新建与平台/供应商 API Key 分配。供应商协议、密钥隔离、分布式额度原子、任务句柄和受控媒体落盘仍由绑定原子完成。
|
|
52
|
+
- 非流式 `UpdateConversationTitle / RecognizeIntent / Chat / NL2SQL / NL2V8EngineSync` 统一进入 Managed `/apiengine/platform-ai-runtime`,旧 Controller 同名路由只做兼容转发;SSE/流式、Provider Proxy、媒体、文件和策略元数据保持原生。`platform-ai-runtime` 只调用绑定当前租户和当前用户的 `V8.AI` 原子,不接收客户端伪造的租户、用户、Endpoint、ApiKey、表白名单或管理员标记。
|
|
53
|
+
- AI 官方应用只有一个应用级租户扩展点 `platform-ai-custom-hook`,策略为 `CreateIfMissing`。`mci_ai_data_assistant` 与 `platform-ai-runtime` 只传 `Stage`、`SourceApiEngineKey`、`Action`;支付动作最多增加 `EventId`、`Provider`,账户其它动作只增加白名单资源元数据。Hook 禁止接收订单号、交易号、金额、标题、SQL、模型、附件、提示词或问题原文及其摘要、回答、供应商/平台密钥、供应商任务号、文件句柄或媒体内容。匿名发现接口不调用 Hook,`/v1/usage` 继续由 C# 凭据网关直接校验,使 `Authorization` 永不进入 V8。
|
|
54
|
+
- 9 张 `mic_sub_*` 订阅表与 `mci_ai_token_account`、`mci_ai_token_log` 由官方 `app.microi.ai-engine` 应用升级提供,必须同时声明 DDL、PhysicalColumns、DiyTables、DiyFields;`PromptPreview` 属于日志表正式字段。`mci_ai_token_recharge` 当前不在该运行时实体闭包内,禁止根据线上残留自行扩包。运行时发现缺失时失败关闭,禁止在请求路径自动建表、加列或修改业务 Schema。
|
|
55
|
+
- `Sys_User.AiApiKey` 归官方 `app.microi.sys_user`,由系统账号包同时维护建表 DDL、PhysicalColumns 和隐藏只读 DiyField;AI助手包不得重复声明 `Sys_User`。缺列错误必须提示升级系统账号应用,不能误导用户升级 AI助手。
|
|
51
56
|
|
|
52
57
|
## 模型与密钥
|
|
53
58
|
|
|
@@ -73,6 +78,9 @@ AI 业务统一实现在 `Microi.Server/Microi.AI`。`Microi.Server/Microi.net.A
|
|
|
73
78
|
## 跨端 AI 助手与商城交付
|
|
74
79
|
|
|
75
80
|
- PC 与移动端复用 `mci_ai_data_assistant`。`Bootstrap` 返回的 `Enabled`、`Models`、`AllowedDomains` 和 `Prompts` 是跨端共同事实源;快捷问题来自启用的 `mci_ai_data_domain.PromptExamples`,前端不能维护另一套固定文案。
|
|
81
|
+
- 官方 AI助手应用归 `app.microi.ai-engine`。v6.3.6 的接口引擎必须严格只有 Managed `mci_ai_data_assistant`、Managed `platform-ai-account`、Managed `platform-ai-runtime` 和 CreateIfMissing `platform-ai-custom-hook` 四项,并声明 `V8.Method.ManageAiPlatform`、`V8.Method.RequireManagedProtocolContext` 与所需 `V8.AI` 能力;后端缺少任一能力时安装预检失败关闭。
|
|
82
|
+
- `ai_app_list`、`ai_app_detail`、`ai_app_get_file`、`ai_app_save_file`、`ai_app_create`、`ai_app_build`、`ai_app_preview`、`ai_app_download_source_zip`、`ai_app_download_build_zip`、`ai_app_moveobject_probe`、`ai_app_moveobject_exec_probe`、`ai_app_publish_store` 这 12 个 `ai_app_*` 归 `app.microi.store` 维护。AI助手包移除它们的资源选择和所有权声明,但不会删除目标租户既有运行时接口记录;既有记录继续由应用商城包升级。
|
|
83
|
+
- v6.3.6 还必须保持 AI助手包的 DDL、PhysicalColumns、DiyTables、DiyFields、DataSets 全层不再包含重复的 `mci_ai_app_version`、`mci_ai_app_file`、`sys_microistore`,这些表交回 Store/SaaS 既有平台包维护;当前仅 AI助手包拥有的 `app_mic_aiapp`、`mci_ai_app` 继续保留。
|
|
76
84
|
- 普通角色必须匹配启用的 `mci_ai_role_policy`。只有后端可信的 `V8.CurrentUser.Level >= 9999` 可以在新安装租户缺少角色策略时获得安全兜底:从目标租户动态读取已启用业务域和模型,范围为 `All`,仍保持 `AllowRawSql=false`、敏感字段默认关闭。不得相信客户端提交的 Level、角色名或账号名。
|
|
77
85
|
- 当租户要求“所有角色均可使用 AI 助手”时,必须为 `sys_role` 中每个目标角色建立显式启用策略;受限角色使用 `Self`/`Department` 与最小业务域,管理角色才可使用经确认的 `All`。禁止把“人人可打开助手”实现成普通角色默认全库可读。
|
|
78
86
|
- `Sys_Config.DisableAiAssistant` 是负向开关:缺失、空值或 `0/false` 都显示 AI 助手,只有显式 `1/true` 才关闭图标。商城升级应复用旧 `IsShowAiAssistant` 的字段元数据 Id 就地改名;兼容读取可以保留旧物理列,但旧字段元数据必须在 PC 与移动端隐藏,禁止同时暴露正向、负向两个开关。关闭该开关前后都要做策略覆盖验收:回读 `sys_role` 与 `mci_ai_role_policy`,断言每个目标角色都有唯一启用策略,`AllowedDomains`、`AllowedModels` 均非空且模型仍处于启用状态;再至少用超级管理员、普通员工和客户身份分别调用 `Bootstrap`,确认 `Enabled=true` 且返回范围符合角色。仅看到入口图标不算可用。
|
|
@@ -22,12 +22,12 @@ description: Microi 应用商城开发、打包、安装和升级规范。用于
|
|
|
22
22
|
- `Microi.Upgrade` 只保留应用商城/安装器启动前必需的核心物理兼容或协议迁移,并要求持久化版本门、共享租约、幂等、失败不推进版本。禁止每次启动对每个租户重跑不断增长的历史迁移清单。
|
|
23
23
|
- 平台通用缺陷的交付证据必须分开记录:源码修复、官方母版资源、商城包发布后回读、目标租户后台安装任务、目标租户资源回读和真实 UI/接口验收;其中任一步未完成都不能笼统称为“已发布并安装”。
|
|
24
24
|
- 任何计划让全部吾码租户通过“安装/更新官方应用”获得的标准字段、布局、菜单、页面或种子数据,必须先在官方 `microi_itdos` 主租户创建并回读,再从该主租户按精确菜单/表资源导出新版包、单调递增应用版本并发布。包正文以 `PackageHdfsPath + PackageSha256 + PackageSize` 为事实源;`AppPakcet` 只作旧版读取兼容,验证完成后应为空。禁止先只在客户/子租户补字段,再用本地手工 JSON 冒充官方母版;客户验证应发生在官方包发布之后。
|
|
25
|
-
- 同一标准能力若同时属于基础 SaaS
|
|
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
|
|
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#
|
|
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
|
-
-
|
|
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,17 @@ 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
|
+
应用包中的 `sys_apiengine.Id` 是跨应用共享物理表的稳定主键,必须在全部官方应用范围内全局唯一;不能只检查单包内 Key/Id。发布前必须同时扫描全部官方包的 `Id` 与 `ApiEngineKey`,任一跨包重复都应阻断发布和离线包生成。
|
|
109
|
+
|
|
110
|
+
系统聊天门面 `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、伪造租户/发送人必须失败关闭。
|
|
111
|
+
|
|
112
|
+
`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`。
|
|
113
|
+
|
|
114
|
+
`platform-chat-runtime` 必须保持 `StopHttp=1`、`AllowAnonymous=0`。SignalR Hub 在 DiyToken 与租户核验后,通过宿主一次性可信协议作用域调用,并携带宿主生成的权威当前用户快照;V8 对 `_InvokeType=Client` 的调用必须先执行 `V8.Method.RequireManagedProtocolContext()` 原子消费。不得为修复 Hub 误报“禁止 HTTP 调用”而开放 `StopHttp`,也不得接受 Param 中的信任布尔值、用户或租户覆盖;接口引擎内部 `Server` 嵌套调用保持原有语义。
|
|
115
|
+
|
|
116
|
+
租户个性化仅写入 `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
117
|
|
|
108
118
|
## 最低验收
|
|
109
119
|
|
|
@@ -113,3 +123,4 @@ await V8.Notification.MarkRead({ All: true });
|
|
|
113
123
|
4. 用户只能查询和标记自己的通知;危险链接、超长正文、跨租户接收人和匿名调用被拒绝。
|
|
114
124
|
5. 公众号/服务号发送主体与小程序跳转目标分别验证,不把 `MiniProgramAppId` 当作模板发送主体。
|
|
115
125
|
6. 源码定向测试、后端编译、远端 MCP 回读、真实浏览器点击和商城安装/校验分别报告;未执行的生产发布不得写成已上线。
|
|
126
|
+
7. 聊天空 Token/访问密钥/伪造租户失败关闭;相同 `RequestId` 并发只有一份 Mongo 事实,不同载荷冲突拒绝;已持久后 SignalR/After Hook 失败仍返回成功并可回读。
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
| 两节点同时发送 | 唯一索引只有一个 claim 成功,两节点都不崩溃 |
|
|
95
95
|
| 写入后节点退出 | 日志仍可查询;发送状态可审计、可补偿 |
|
|
96
96
|
| SignalR/Redis 短故障 | 业务写入不回滚,客户端列表回读恢复 |
|
|
97
|
+
| SignalR Hub 调用聊天运行时 | `StopHttp=1` 保持关闭;仅固定 Key/租户/权威用户绑定的一次性宿主上下文可通过,V8 原子消费一次 |
|
|
97
98
|
| 事务回滚 | 不产生实时通知,业务日志随事务回滚 |
|
|
98
99
|
| 离线用户 | 下次打开通知中心能看到并标记已读 |
|
|
99
100
|
| 越权读取/已读 | 其它用户 Id 无效,记录不改变 |
|
|
@@ -363,9 +363,9 @@ async function jsonRequest(path, options = {}) {
|
|
|
363
363
|
|
|
364
364
|
async function loadSysConfig() {
|
|
365
365
|
const resolved = runtime();
|
|
366
|
-
const result = await jsonRequest(
|
|
366
|
+
const result = await jsonRequest(`/apiengine/platform-sys-config?OsClient=${encodeURIComponent(resolved.osClient)}`, {
|
|
367
367
|
method: 'POST',
|
|
368
|
-
headers: { 'Content-Type': 'application/json', osclient: resolved.osClient, did: V8.getDid() },
|
|
368
|
+
headers: { 'Content-Type': 'application/json', osclient: resolved.osClient, did: V8.getDid(), apiengine: '1' },
|
|
369
369
|
body: JSON.stringify({ OsClient: resolved.osClient, _SearchEqual: { IsEnable: 1 } })
|
|
370
370
|
});
|
|
371
371
|
if (!result.body || Number(result.body.Code) !== 1) {
|
|
@@ -475,12 +475,13 @@ async function validateCachedLogin() {
|
|
|
475
475
|
if (validatedSessionToken === token && authenticatedIdentity()) return cachedUser;
|
|
476
476
|
|
|
477
477
|
const resolved = runtime();
|
|
478
|
-
const result = await jsonRequest(
|
|
478
|
+
const result = await jsonRequest(`/apiengine/platform-current-user?OsClient=${encodeURIComponent(resolved.osClient)}`, {
|
|
479
479
|
method: 'POST',
|
|
480
480
|
headers: {
|
|
481
481
|
'Content-Type': 'application/json',
|
|
482
482
|
osclient: resolved.osClient,
|
|
483
483
|
did: V8.getDid(),
|
|
484
|
+
apiengine: '1',
|
|
484
485
|
Token: token,
|
|
485
486
|
Authorization: `Bearer ${token}`
|
|
486
487
|
},
|
|
@@ -76,7 +76,7 @@ Page 模式要特别注意:
|
|
|
76
76
|
- 匿名公有存储场景只允许当前 `OsClient` 目录下的 `filePathName`;接口响应文件场景用 `fileUrl` 接收当前平台正式 `ApiBase`,或由同端口本地后端读取的 loopback `/apiengine/...`,并要求 URL 显式携带当前 `OsClient`。两种场景都拒绝私有文件、跨租户路径、路径穿越和任意第三方域名。
|
|
77
77
|
- `fileUrl` 路径没有文件扩展名时必须同时传 `fileName` 或 `fileType`。组件先调用 `/api/HDFS/PrepareOfficePreviewFromUrl`,由后端严格校验当前平台、当前 `OsClient` 和单层 `/apiengine/{key}`,再把响应文件透明缓存到当前租户公有对象存储;OnlyOffice 使用返回的公网静态地址。开发环境 loopback 只允许同端口本地后端读取,不能简单替换 origin,也不能把该接口扩展成通用 URL 代理。
|
|
78
78
|
- `canEdit` 不能直接作为授权结果。最终允许编辑必须同时满足有效登录态;匿名即使传 `canEdit=1` 也强制使用 OnlyOffice `mode:'view'` 和 `permissions.edit=false`。
|
|
79
|
-
- 私有文件调用 `/
|
|
79
|
+
- 私有文件调用 `/apiengine/platform-private-file-url` 时传 `ForOfficePreview:true` 和权威资源上下文,让远程 OnlyOffice 使用租户公网 `ApiBase` 的审计代理地址,避免 `localhost` 导致“下载失败”。
|
|
80
80
|
- `layout/index.vue` 仅在“路由要求隐藏外壳且当前无有效登录用户”时隐藏 Sidebar/Navbar/TagsView;登录用户打开同一路由仍保留正常系统布局。
|
|
81
81
|
- 不要把匿名路由简单加入全局白名单后跳过组件鉴权;过期 Token 要清理,公有文件校验失败必须停止创建 OnlyOffice 配置。
|
|
82
82
|
|
|
@@ -160,7 +160,7 @@ DiyCommon.FormEngine.AddFormData("table_name", { Field: "value" }, function (res
|
|
|
160
160
|
|
|
161
161
|
文件同步、跨平台导入等需要登录另一套 Microi API 的前端工具,也必须复用同一验证码契约:
|
|
162
162
|
|
|
163
|
-
- 用户填写远程 `ApiBase` 和 `OsClient` 后,先请求远程 `/
|
|
163
|
+
- 用户填写远程 `ApiBase` 和 `OsClient` 后,先请求远程 `/apiengine/platform-sys-config?OsClient=<OsClient>`,并让 Query、`osclient` Header 与 Body 三处租户一致;按 `isEnabledFlag(EnableCaptcha)` 判断是否需要验证码,不能先盲目调用登录接口。
|
|
164
164
|
- 需要验证码时,自动请求远程 `/api/Captcha/GetCaptcha?OsClient=<OsClient>`,读取响应头 `captchaid` 并显示验证码图片;用户输入后,远程 `/api/SysUser/login` 必须同时提交 `_CaptchaId/_CaptchaValue`。
|
|
165
165
|
- 远程地址或租户变化时清空旧验证码和 Token;登录失败时刷新验证码。未开启验证码时不得显示验证码输入,也不得提交空验证码字段。
|
|
166
166
|
- 远程响应头必须通过 CORS 暴露 `captchaid` 和 `authorization`;前端还应兼容登录响应体中的 Token,避免只依赖响应头。
|
|
@@ -78,7 +78,7 @@ SDK 或项目请求模块必须提供登录所需的系统配置和验证码薄
|
|
|
78
78
|
|
|
79
79
|
要求:
|
|
80
80
|
- 提供 `isEnabledFlag(value)` 或等价工具,统一判断 `Sys_Config.EnableCaptcha`。它必须把 `true`、`1`、`'true'`、`'1'` 识别为开启,把 `false`、`0`、`'false'`、`'0'`、空值识别为关闭。
|
|
81
|
-
- 提供 `getSysConfig()`,内部调用 `V8.GetSysConfig(true)` 或 `/
|
|
81
|
+
- 提供 `getSysConfig()`,内部调用 `V8.GetSysConfig(true)` 或 `/apiengine/platform-sys-config`,并保持 Query、Header、Body 中的当前租户 `OsClient` 一致。
|
|
82
82
|
- 提供 `getCaptcha()`,内部调用 `GET /api/Captcha/GetCaptcha`,`responseType:'arraybuffer'`,从响应头读取 `captchaid`,返回 `{ CaptchaId, ImageSrc }`。
|
|
83
83
|
- 提供账号登录封装时,只有在页面传入验证码时才追加 `_CaptchaId/_CaptchaValue`;不要在未开启验证码时提交空字段。
|
|
84
84
|
- PC Vue、UniApp H5、微信小程序和 App 的账号密码登录都必须使用同一套验证码判断和登录参数契约。
|
|
@@ -84,7 +84,7 @@ description: Microi 移动端质量门禁,适用于 UniApp/H5/微信小程序
|
|
|
84
84
|
PC 端、H5、App、微信小程序或任何自定义前端只要调用 `/api/SysUser/login`、`/api/SysUser/Login` 或 `V8.Login(param)`,都必须先读取 `Sys_Config` 的 `EnableCaptcha` 配置,并按配置决定是否展示和提交图形验证码。
|
|
85
85
|
|
|
86
86
|
要求:
|
|
87
|
-
- 启动登录页时调用 `/
|
|
87
|
+
- 启动登录页时调用 `/apiengine/platform-sys-config` 或项目 SDK 的 `V8.GetSysConfig(true)`,读取当前租户启用状态;旧 Controller 路由只作兼容转发。
|
|
88
88
|
- `EnableCaptcha` 可能是 `1`、`true`、`'1'`、`'true'`,也可能是大小写不同的字符串。必须使用统一的 `isEnabledFlag(value)` 或等价函数判断,不能直接 `!!value`,否则字符串 `'0'` 会被误判为开启。
|
|
89
89
|
- 开启验证码时,登录表单必须显示验证码输入框和验证码图片;验证码图片通过 `GET /api/Captcha/GetCaptcha` 获取,读取响应头 `captchaid`,提交登录时附加 `_CaptchaId` 和 `_CaptchaValue`。
|
|
90
90
|
- 登录失败、验证码错误、网络错误后必须刷新验证码并清空验证码输入;验证码未填写时前端直接阻止提交并提示用户。
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
### 资源与隐私文件
|
|
103
103
|
|
|
104
104
|
- 公开图片走 FileServer/CDN。
|
|
105
|
-
-
|
|
105
|
+
- 私有图片必须先通过 `/apiengine/platform-private-file-url` 携带权威资源上下文换取短期签名 URL;签发失败时禁止回退裸路径。
|
|
106
106
|
- 上传接口必须通用,不能在平台 HDFS 控制器写某项目“只允许支付凭证或实名认证图片”这类硬编码。
|
|
107
107
|
- iOS Safari 上传后要验证表单字段不丢失,上传组件不能重置整张表单。
|
|
108
108
|
|
|
@@ -63,7 +63,7 @@ Microi 标准小程序必须采用“平台内核 + 版本化元数据 + Profile
|
|
|
63
63
|
- 登录、接口引擎、FormEngine、上传等所有请求头必须做大小写不敏感去重;`osclient` 只能发送一个运行期值,例如 `demo`,禁止同时传 `OsClient` 与 `osclient` 导致网络面板出现 `demo, demo`。
|
|
64
64
|
- 账号密码登录只有在同时拿到成功码、有效 token 和有效用户 `Id` 时才算成功。Microi 登录 token 可能在响应头 `authorization`,用户信息可能在响应体 `Data`;两者任一缺失都要清理 SDK token、本地用户缓存和 session。
|
|
65
65
|
- 恢复本地会话时必须重新校验 token 和用户 `Id`,禁止出现页面显示 `admin` 但状态仍是“未登录”的半登录状态。
|
|
66
|
-
- 账号密码登录调用 `/api/SysUser/login`、`/api/SysUser/Login` 或 `V8.Login(param)` 前,必须读取 `/
|
|
66
|
+
- 账号密码登录调用 `/api/SysUser/login`、`/api/SysUser/Login` 或 `V8.Login(param)` 前,必须读取 `/apiengine/platform-sys-config` 或 `V8.GetSysConfig(true)`,并根据 `Sys_Config.EnableCaptcha` 决定是否显示图形验证码。判断函数必须兼容 `1`、`true`、`'1'`、`'true'`,不要直接 `!!cfg.EnableCaptcha`。
|
|
67
67
|
- 开启验证码时,页面必须通过 `GET /api/Captcha/GetCaptcha` 获取验证码图片,读取响应头 `captchaid`,提交账号登录时传 `_CaptchaId` 和 `_CaptchaValue`;登录失败后清空输入并刷新验证码。未开启验证码时不显示验证码,不传空验证码字段。
|
|
68
68
|
- 微信小程序手机号快捷登录必须使用 `<button open-type="getPhoneNumber">`,通过 `@getphonenumber` 获取 `detail.code`,并重新调用 `uni.login({ provider:'weixin' })` 获取新的 `LoginCode`。前端不能假设能直接拿到手机号明文。
|
|
69
69
|
- H5/App 可提供手机号输入兜底,但必须确认后端接口支持 `Phone` 登录;微信小程序优先走 `Code + LoginCode`。
|
package/skills/microi.v8.js
CHANGED
|
@@ -209,13 +209,30 @@ function normalizeClientUploadPath(value) {
|
|
|
209
209
|
return parts.join('/');
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
function normalizeFileUrlData(data, assetUrl, fallback = '') {
|
|
212
|
+
function normalizeFileUrlData(data, assetUrl, fallback = '') {
|
|
213
213
|
const raw = Array.isArray(data) ? (data[0] || '') : (data || '');
|
|
214
214
|
if (typeof raw === 'string') return assetUrl(raw || fallback);
|
|
215
215
|
const url = raw.Url || raw.FileUrl || raw.FileURL || raw.PreviewUrl || raw.PreviewURL || raw.FullUrl || '';
|
|
216
216
|
const path = raw.Path || raw.FilePathName || raw.FilePath || raw.FullPath || '';
|
|
217
217
|
return assetUrl(url || path || fallback);
|
|
218
|
-
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isLocalPackagedAsset(path) {
|
|
221
|
+
return /^(?:\.\/)?\/?static\//i.test(String(path || ''));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function hasPublicUploadFlag(value) {
|
|
225
|
+
if (!value || typeof value !== 'object') return false;
|
|
226
|
+
const raw = Array.isArray(value) ? (value[0] || {}) : value;
|
|
227
|
+
return raw.Limit === false || raw.Limit === 0 || String(raw.Limit).toLowerCase() === 'false' ||
|
|
228
|
+
raw.IsPrivate === false || raw.Private === false || raw.Public === true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isKnownPublicUploadPath(path) {
|
|
232
|
+
// Ordinary form uploads such as /xjy/img and /xjy/file are private by default.
|
|
233
|
+
// They must use the Managed signer instead of being mistaken for CDN assets.
|
|
234
|
+
return /^\/?(?:public|mci-public|xjy\/xjy\/miniapp-assets|xjy\/miniapp\/share)\//i.test(String(path || ''));
|
|
235
|
+
}
|
|
219
236
|
|
|
220
237
|
function getHeaderValue(headers, key) {
|
|
221
238
|
if (!headers) return '';
|
|
@@ -918,11 +935,12 @@ export function createMicroiV8(options = {}) {
|
|
|
918
935
|
}
|
|
919
936
|
|
|
920
937
|
// 资源地址统一过滤占位图,并兼容 HDFS 私有文件、FileServer 和绝对地址。
|
|
921
|
-
function assetUrl(value) {
|
|
938
|
+
function assetUrl(value) {
|
|
922
939
|
const picked = extractUploadPath(value);
|
|
923
940
|
if (!picked || isBlockedAsset(picked)) return '';
|
|
924
941
|
if (/^(https?:|data:|blob:|file:)/i.test(picked)) return picked;
|
|
925
|
-
if (
|
|
942
|
+
if (isLocalPackagedAsset(picked)) return picked;
|
|
943
|
+
if (/^\/?file\//i.test(picked)) return joinUrl(config.apiBase, picked);
|
|
926
944
|
if (/^\//.test(picked) || /^[a-z0-9_-]+\//i.test(picked)) return joinUrl(config.fileServer || config.apiBase, picked);
|
|
927
945
|
return picked;
|
|
928
946
|
}
|
|
@@ -931,23 +949,59 @@ export function createMicroiV8(options = {}) {
|
|
|
931
949
|
return config.blockedAssetPattern ? config.blockedAssetPattern.test(String(value || '')) : false;
|
|
932
950
|
}
|
|
933
951
|
|
|
934
|
-
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
952
|
+
function hasPrivateFileAccessContext(options = {}) {
|
|
953
|
+
const resourceKind = String(options.resourceKind || options.ResourceKind || 'FormField').trim();
|
|
954
|
+
const resourceId = String(options.resourceId || options.ResourceId || '').trim();
|
|
955
|
+
const formEngineKey = String(options.formEngineKey || options.FormEngineKey || '').trim();
|
|
956
|
+
const formDataId = String(options.formDataId || options.FormDataId || '').trim();
|
|
957
|
+
const fieldId = String(options.fieldId || options.FieldId || '').trim();
|
|
958
|
+
const sysMenuId = String(options.sysMenuId || options.SysMenuId || options.menuId || options.MenuId || '').trim();
|
|
959
|
+
const formFieldReady = Boolean(formEngineKey && formDataId && fieldId && sysMenuId);
|
|
960
|
+
|
|
961
|
+
if (resourceKind === 'UserAvatar' || resourceKind === 'MenuImportTemplate' || resourceKind === 'DeptImportTemplate') {
|
|
962
|
+
return Boolean(resourceId);
|
|
963
|
+
}
|
|
964
|
+
if (resourceKind === 'FileManagerObject') return Boolean(resourceId && sysMenuId);
|
|
965
|
+
if (resourceKind === 'FormFieldDerivedPreview') {
|
|
966
|
+
return formFieldReady && Boolean(options.originalFilePathName || options.OriginalFilePathName);
|
|
967
|
+
}
|
|
968
|
+
return resourceKind === 'FormField' && formFieldReady;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
async function resolveFileUrl(filePathName, options = {}) {
|
|
972
|
+
const path = extractUploadPath(filePathName);
|
|
973
|
+
if (!path || isBlockedAsset(path)) return '';
|
|
974
|
+
if (/^(https?:|blob:|data:|file:)/i.test(path)) return assetUrl(path);
|
|
975
|
+
if (isLocalPackagedAsset(path) || options.private === false || hasPublicUploadFlag(filePathName) || isKnownPublicUploadPath(path)) {
|
|
976
|
+
return assetUrl(path);
|
|
977
|
+
}
|
|
978
|
+
// 私有对象没有权威资源上下文时直接失败关闭,禁止以裸路径换取签名。
|
|
979
|
+
if (!hasPrivateFileAccessContext(options)) return '';
|
|
980
|
+
|
|
981
|
+
async function requestPrivateFileUrl() {
|
|
982
|
+
try {
|
|
983
|
+
const body = await apiEngineRun('platform-private-file-url', {
|
|
984
|
+
OsClient: config.osClient,
|
|
985
|
+
FilePathName: path,
|
|
986
|
+
FormEngineKey: options.formEngineKey || options.FormEngineKey,
|
|
987
|
+
FormDataId: options.formDataId || options.FormDataId,
|
|
988
|
+
FieldId: options.fieldId || options.FieldId,
|
|
989
|
+
SysMenuId: options.sysMenuId || options.SysMenuId || options.menuId || options.MenuId,
|
|
990
|
+
ResourceKind: options.resourceKind || options.ResourceKind,
|
|
991
|
+
ResourceId: options.resourceId || options.ResourceId,
|
|
992
|
+
OriginalFilePathName: options.originalFilePathName || options.OriginalFilePathName,
|
|
993
|
+
_TableChildAuth: options.tableChildAuth || options._TableChildAuth,
|
|
994
|
+
HDFS: options.hdfs || options.HDFS
|
|
995
|
+
}, {
|
|
996
|
+
checkCode: false,
|
|
997
|
+
silentError: true
|
|
998
|
+
});
|
|
999
|
+
if (body && body.Code === 1 && body.Data) return normalizeFileUrlData(body.Data, assetUrl, path);
|
|
946
1000
|
} catch (e) {}
|
|
947
1001
|
return '';
|
|
948
1002
|
}
|
|
949
1003
|
|
|
950
|
-
return (await
|
|
1004
|
+
return (await requestPrivateFileUrl()) || '';
|
|
951
1005
|
}
|
|
952
1006
|
|
|
953
1007
|
// 文件上传同时支持 uni.uploadFile 与浏览器 fetch/FormData。
|
|
@@ -1237,7 +1291,7 @@ export function createMicroiV8(options = {}) {
|
|
|
1237
1291
|
|
|
1238
1292
|
function legacyGetCurrentUser(refresh, callback) {
|
|
1239
1293
|
if (refresh) {
|
|
1240
|
-
legacyPost('/
|
|
1294
|
+
legacyPost('/apiengine/platform-current-user', {}, (result) => {
|
|
1241
1295
|
if (result && result.Code) legacySetCurrentUser(result.Data || {});
|
|
1242
1296
|
if (typeof callback === 'function') callback(result);
|
|
1243
1297
|
});
|
|
@@ -1484,7 +1538,7 @@ export function createMicroiV8(options = {}) {
|
|
|
1484
1538
|
// 旧版前端 V8 依赖的后端接口路径,保留原名称以减少迁移成本。
|
|
1485
1539
|
const legacyApi = {
|
|
1486
1540
|
MicroiInit: '/apiengine/microi-init',
|
|
1487
|
-
GetSysConfig: '/
|
|
1541
|
+
GetSysConfig: '/apiengine/platform-sys-config',
|
|
1488
1542
|
Login: '/api/SysUser/login',
|
|
1489
1543
|
AddFormData: '/api/FormEngine/addFormData',
|
|
1490
1544
|
AddFormDataBatch: '/api/FormEngine/addFormDataBatch',
|
|
@@ -1508,10 +1562,10 @@ export function createMicroiV8(options = {}) {
|
|
|
1508
1562
|
UploadAnonymous: '/api/HDFS/uploadAnonymous',
|
|
1509
1563
|
UniappUpload: '/api/HDFS/UniappUpload',
|
|
1510
1564
|
UniappUploadAnonymous: '/api/HDFS/uniappUploadAnonymous',
|
|
1511
|
-
GetCurrentUser: '/
|
|
1565
|
+
GetCurrentUser: '/apiengine/platform-current-user',
|
|
1512
1566
|
GetDateTimeNow: '/api/os/getDateTimeNow',
|
|
1513
1567
|
AddSysLog: '/apiengine/platform-client-log',
|
|
1514
|
-
GetOsClientByDomain: '/
|
|
1568
|
+
GetOsClientByDomain: '/apiengine/platform-os-client-by-domain',
|
|
1515
1569
|
ApiEngine: {}
|
|
1516
1570
|
};
|
|
1517
1571
|
|
|
@@ -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
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
-
-
|
|
181
|
+
- 远程目标登录后必须调用文件柜能力探针 `mci_file_sync_capability` 检查同步协议版本,并使用其 `Data.FileManagerSysMenuId` 作为目标租户权威文件柜菜单;禁止硬编码发布端菜单 Id。接口不存在、未返回菜单 Id、返回 404/非标准结果或协议版本过低时,提示目标平台更新【文件柜】应用,不得继续同步。
|
|
179
182
|
- 验收至少覆盖:登录成功显示身份、退出后 Token 清空、历史连接一键重连、删除连接、密文落库、服务重启后仍可解密、目标平台缺少能力接口时的升级提示。
|
|
180
183
|
|
|
181
184
|
<!-- /microi-progressive:chunk -->
|
|
@@ -133,7 +133,7 @@ var url = V8.Method.GetPrivateFileUrl({
|
|
|
133
133
|
// 后端审计代理 URL,过期不可访问;真实对象存储签名 URL 不会返回前端
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
- 普通客户端调用 `/
|
|
136
|
+
- 普通客户端调用 `/apiengine/platform-private-file-url` 时,不能只提交 `FilePathName`,必须同时提交 `FormEngineKey`、`FormDataId`、`FieldId`、`SysMenuId`。服务端校验菜单、菜单绑定表、记录数据范围、字段归属以及字段值确实引用该路径后,才签发临时票据;旧 Controller 路由仅作兼容转发,新代码禁止继续引用。
|
|
137
137
|
- `FieldId` 必须属于目标表,且组件为 `FileUpload`、`ImgUpload` 或 `RichText`;`SysMenuId` 必须是当前用户真实拥有、并绑定目标表的菜单。RichText 还必须在真实 `img/video/source.src` 或 `a.href` 中精确引用请求路径,普通文字、`data-*` 和脚本标签不算授权依据。
|
|
138
138
|
- 普通用户禁止通过该入口直接取得私有文件 `Byte` / `Stream`。签发失败时不能回退裸路径、真实对象存储签名地址或公有 URL。
|
|
139
139
|
- 私有文件访问必须经过后端短期票据代理:签发链接时记录当前登录用户,实际 `GET/HEAD` 打开或下载时再记录一次访问行为;支持 `Range` 流式响应,并对同一次分片请求做短时去重,不能把文件完整读入内存。
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
- OnlyOffice 文档服务器会在服务端再次下载文件。浏览器可以下载但 OnlyOffice 提示“下载失败”时,优先检查生成地址是否为 `localhost/127.0.0.1/内网域名`。
|
|
19
19
|
- OnlyOffice 可能先对文档地址发起 `HEAD` 探测。响应文件接口除了 `GET 200`,还必须让 `HEAD` 返回相同的 `Content-Type/Content-Length/Content-Disposition`,不能返回 `405`。
|
|
20
|
-
- 私有文件在线预览调用
|
|
20
|
+
- 私有文件在线预览调用 `/apiengine/platform-private-file-url` 时传 `ForOfficePreview:true` 和权威资源上下文。审计代理应优先使用租户系统配置的公网 `ApiBase`,但仍把真实对象存储签名地址保存在共享 Redis ticket 中,禁止直接返回真实签名地址;旧 Controller 路由只作兼容转发。
|
|
21
21
|
- `/online-office` 可以匿名访问。公有存储模式只允许当前 `OsClient` 目录下的 `filePathName`;接口模式通过 `fileUrl` 接收当前平台正式 `ApiBase`,或由同端口本地后端读取的 loopback `/apiengine/...` 响应文件地址,并要求 URL 显式携带当前 `OsClient`。两种模式都拒绝跨租户、路径穿越和任意第三方 URL;`isPrivate=1` 必须登录。
|
|
22
22
|
- 匿名接口引擎预览不要求 V8 代码先上传 HDFS:接口开启【响应文件】和【允许匿名】后,完整地址 URL 编码传给 `fileUrl`,同时传 `fileName/fileType`。页面通过匿名安全中转让当前后端限域读取源文件,并以确定性路径缓存到当前租户公有对象存储;OnlyOffice 只接收公网 `FileServer` 地址。loopback 仅允许同端口本地后端访问,禁止开放任意 URL 代理。
|
|
23
23
|
- `canEdit` 只是前端请求参数,不是权限。最终编辑条件必须是“有效登录态 && canEdit=true”;匿名始终只读,不能因 URL 参数放开编辑。
|