Scert/doc/项目资源关联模型.md
xuans e2200534d3 完善域名服务器多对多关联与共享路径预检,补齐回归源码及进度文档
支持同项目内域名与服务器双向复用,局部修改和清空不影响其他域名;在绑定保存、服务器编辑和部署前拒绝已知共享路径冲突。

新增仓储、接口、工作流与页面回归源码,同步任务状态和第三版接口交接。按要求先开发后统一测试,本批未运行测试、构建或部署,保留在途任务与主机别名等待办。
2026-09-28 21:51:05 +08:00

190 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 项目资源关联模型
## 十二、资源读取与域名服务器多对多(2026-09-28,源码完成待统一测试)
- `domain_servers` 继续使用 `(domain_id, server_id)` 联合主键,不增加任何单边唯一约束,模式仍为 14,无新增迁移。一个域名关联多台服务器,同一服务器复用于多个域名,双方必须同项目。
- 新绑定接口只替换目标域名的关联。重复服务器标识去重,明确空数组表示清空;不删除服务器、不清理远端证书,不改变其他域名的关联,也不触发 SSH 或推送。
- 活跃超级管理员或同时持有 `certificate.read`、`server.read`、`deployment.execute` 的项目成员可修改绑定。操作者来自会话,项目来自实际域名,既有用户分组和全局管理员身份不自动授权。
- 七个资源 GET 在数据库内按真实资源及账户状态过滤;绑定读取要求双方读取权限。无权和未知详情统一 403;缺少授权依赖为 503,不回退基础仓储,响应禁止缓存。
- 绑定成功审计与关联共用事务,审计或保存失败回滚;失败审计另行按实际资源归属记录,不保存请求正文或敏感凭据。输入最多 100 个服务器标识,限制在去重之前计算。
- 共享服务器默认按域名与证书类型分目录。绑定保存、服务器修改和部署预检比较实际展开、规范化后的证书、证书链、私钥路径;固定路径、被 `..` 抵消的占位符、跨文件或文件目录冲突均拒绝。单域名固定路径保持兼容。
- 部署预检通过事务读取当前绑定及配置并返回已解析的目标路径;不在数据库事务内连接 SSH。它不等于跨主机别名的文件系统占用管理,也不覆盖配置变更与在途任务的完整协调、远端符号链接或解除绑定后的遗留文件识别,这些仍属部署整改待办。
- 真实 SQLite/隔离 MySQL、HTTP、工作流及 Vue 模板回归源码已补齐,尚未执行;不能宣称远端多服务器推送已通过。
## 十一、项目生命周期(2026-09-28,收尾待统一测试)
- 已接入项目创建、编辑、明确确认删除及前端共享忙状态;默认项目不可删除,有资源或成员引用时拒绝删除。
- 项目标识统一为 `^[a-z0-9][a-z0-9_-]{0,35}$`;名称拒绝控制字符,说明允许换行、回车和制表符。非法 ID、停用或撤权、事务审计与失败回滚已补用例。
- 生命周期收尾与读取隔离候选 `709c1ef`、绑定检查点 `f38e290` 已提交推送;历史局部测试不覆盖最新源码,后续集中验证。其他业务资源及排队任务权限尚未完成。
以下第十节及早期记录保留历史实现与验证范围,不代表当前候选仍未开发生命周期,也不表示新代码继承历史通过状态。
> 当前入口批次(2026-09-28 19:54):已新增服务端实时过滤的可见项目分页接口和独立成员页面,管理员导航与普通用户工作台均有入口;账户建议沿用既有用户可见性,不开放全站搜索。见本文件第十节。最后源码后端专项、完整回归、双版本 MySQL 组合、前端测试、三轮专项竞态及双平台构建已通过;条件跳过和验证范围见任务执行清单首节和支持矩阵第二十四节。真实浏览器、项目增删改和全资源权限链仍待验收,下方早期批次未接入界面的描述仅为历史。
## 十、可见项目入口与成员页面
- `GET /api/v1/projects` 通过独立 `ProjectDirectoryRepository` 读取,不直接暴露基础仓储的全量 `ListProjects`。账户状态、合法全局角色、项目成员角色与能力提示在同一 SQL 中判定,用户分组不隐式授予项目权限。
- 有效超级管理员可见全部真实项目;普通管理员、普通用户仅可见自己持有合法项目角色的项目。普通用户担任项目管理员时也可管理成员;其他项目角色只能看到项目基本资料,不能取得成员目录。
- 项目和成员均使用游标分页,支持 5、10、15、30;多个角色不产生重复项目,也不将一个成员拆到不同页。合法空项目列表与认证、权限、存储错误分开。
- `can_manage_members` 仅为界面提示,不是后续请求的授权凭证;所有成员操作继续服务端实时判权。项目和成员响应明确禁止缓存,防止旧身份或旧权限的响应被复用。
- 前端分拆为视图、样式、目录逻辑、成员逻辑与请求封装;普通用户工作台保持独立,不放开全局管理员资源接口。
- 账户建议只读取已有可见用户和本页有效成员;未知目标由本人提供账户 ID。用户管理页显示已有可见账户 ID,不新增账户隐私范围。
- 切换项目、分页和卸载时终止旧读取并递增请求代次;旧成员、账户建议及变更响应不能污染新项目。撤销自己的管理角色后清空成员与账户建议,后续刷新项目重新查询能力。
- 项目创建、编辑、删除,所有业务资源的权限接入,以及排队任务执行、取消、重试重判仍未完成;不能据成员页面上线宣称 P0-07 完成。
> 当前补充(2026-09-28):模式仍为 14。项目成员成功授予/撤销已与审计在同一事务实现,审计失败或未实际写入时回滚成员变化;MySQL 要求成员表和审计表均为 InnoDB。不是失败请求审计,也不等于 Web/API 与任务授权闭环。最终驱动候选的完整后端、双版本 MySQL 组合及 Windows 独立程序恢复已通过;最终证据、竞态结果与未关闭项以执行清单首节及支持矩阵第二十二节为准,下方旧批次数字不替代当前验证。
> 当前模式为 14:已新增项目成员多角色关系、实时判权及原子授予/撤销,见第九节。成员不是用户分组,不向历史账户自动授予默认项目权限。该模块尚未接入完整 Web/API 与任务执行链,不能据此宣称资源隔离完成。第七、八节及下方模式 13 提示是上一批历史记录;本批证据见支持矩阵第二十一节。
> 当前实现更新:第 11、12 号迁移已提交;本批新增第 13 号迁移和任务/审计范围快照,见第七、八节。第五节是项目根实体历史批次,不代表最新资源状态。本批测试及未覆盖范围以执行清单首节、支持矩阵第二十节为准;项目成员授权仍未完成。
> 本批最终验证:SQLite、MySQL 5.7/8.0 项目专项、3 次竞态检测、完整后端及独立 Windows CLI 备份恢复已通过;第五节保留测试启动时状态,最终以执行清单和支持矩阵第十八节为准。P0-06/P0-07 仍为 `[~]`。
> 对应 P0-06、P0-07、P0-10;2026 年 9 月 28 日建立。本文区分已实现模型与后续目标,不以建表代替权限验收。
## 一、确认的需求与约束
- 全部 44 项整改范围保持不变;本轮从缺失的项目根实体开始,逐步接入原定全部核心资源。
- 项目不是用户分组:用户分组的现有展示与成员约束不能被解释为项目授权。
- 资源关联、项目成员授权和 HTTP 接口分别交付;授权闭环前不开放项目管理接口或项目全量列表接口。
- 历史迁移只能增加关联,不删除原任务、服务器、证书、凭据或审计。禁止凭项目种子存在就宣称历史资源已回填。
- 历史资源的默认项目 ID 固定为 `default`;已有默认项目名称和说明在迁移重试中不得覆盖。
- 非默认项目 ID 由后端生成;名称和说明可修改,项目 ID 与创建时间不能通过更新接口改写。
- 所有最终资源引用需要防止跨项目混用;不能仅靠前端筛选保证隔离。
## 二、实体及持久化分批计划
| 实体 | 当前状态 | 后续关系与交付要求 |
|---|---|---|
| `projects` | 已新增模型、三库第 10 号迁移和 SQL 仓储 | 作为项目根实体,统一资源引用;尚无对外接口 |
| `users`、`project_members` | 模式 14 已增加独立项目成员、多角色、实时判权和原子授予/撤销 | 尚待成员管理接口、界面、审计与全部业务授权接入;不能按用户组名称推导项目权限 |
| `domains`、`servers` | 第 11 号迁移已增加项目、所有者、创建人和更新人 | 历史数据回填默认项目,未知身份留空;第 12 号迁移补强 SQLite 替换保护;成员授权仍待完成 |
| `domain_servers` | 已保存项目并校验双方一致性 | 事务失败保持原绑定;三库引用约束已实现,PostgreSQL 当前实库待验 |
| `tasks`、`task_attempts` | 模式 13 任务保存范围快照,尝试读取父任务范围;历史孤儿返回未知 | 已校验新任务/尝试与当前资源一致;项目成员授权、撤权重判待补 |
| `certificates`、`certificate_versions`、`certificate_orders` | 证书业务已有实现;统一实体关系待补 | 项目、域名、订单、版本、任务和签发结果可追溯 |
| `dns_accounts`、`dns_delegations` | DNS 配置业务已有实现;统一实体关系待补 | 项目、所有者、凭据引用、委派及验证任务关联 |
| `deployment_targets`、`deployment_records` | 部署业务已有实现;统一实体关系待补 | 关联项目、服务器、证书版本、任务尝试和回滚记录 |
| `renewal_policies` | 续期业务已有实现;统一实体关系待补 | 关联项目、证书、计划和执行任务 |
| `notification_channels`、`notification_records` | 通知业务已有实现;统一实体关系待补 | 关联项目、事件、任务、通道和发送结果 |
| `audit_logs` | 模式 13 保存范围、任务、操作者;登录、账户、证书流程已接入 | 历史无法确定的身份留空,系统操作单独定义范围;项目级读取授权待补 |
| `backup_records` | 数据库备份恢复已有实现;统一实体关系待补 | 区分系统备份与项目备份,记录范围、校验及恢复任务,不泄露私钥 |
## 三、第 10 号迁移历史记录
仅新增 `projects` 表和默认项目种子,不改写既有业务字段,不将不存在的资源归属伪装成已完成。
字段为 `id`、`name`、`description`、`created_at`、`updated_at`。项目名称允许重名,以 ID 区分;名称最多 128 个字符,说明最多 4096 个字符,拒绝无效编码和危险控制字符。MySQL 项目表显式采用 InnoDB 与 `utf8mb4`,支持中文和四字节字符。
SQLite、PostgreSQL 迁移与版本记录使用同一事务。MySQL 不承诺 DDL 事务回滚:建表和种子写入之后登记版本失败时,保留已执行部分,重试补全版本且不覆盖现有项目资料。
项目仓储提供创建、读取、列表、资料更新和删除。默认项目不能经仓储删除或重复创建;列表默认项目优先。创建/更新时间由仓储生成,更新不覆盖创建时间。第 10 号迁移交付时还没有业务资源引用项目;随后第 11 号迁移已接入域名和服务器引用保护,但不能推及尚未接入的其他资源。
## 四、下一批关联迁移的验收条件
- [x] 域名、服务器和绑定的 MySQL 项目引用采用与项目主键一致的字符集和排序规则,已验证旧表 Latin-1 场景;其他资源需继续遵守。
- [~] 域名、服务器已增加项目及归属字段并回填;任务、审计等核心资源仍待完成。
- [~] 域名/服务器三库约束已实现,SQLite 替换绕过已由模式 12 修复并通过本批回归;所有剩余资源及 PostgreSQL 实库仍需验证。
- [x] 域名服务器绑定的替换在同一事务中校验归属,失败不清空原绑定。
- [~] 域名/服务器 SQL 查询、写入、扫描和接口已接入归属;后台任务及其他资源继续接入,不能仅增加 JSON 字段。
- [ ] 原始证书、凭据、通知和备份状态全部能关联,禁止留下隐式共享全局资源。
- [ ] 两项目实测列表、详情、写入、导出、删除、回滚和撤权后的排队任务,覆盖 P0-07。
- [ ] 三库历史数据迁移、失败重试、备份恢复及独立发行程序验证完成后,再推进实机验收。
## 五、本轮状态
- [x] 项目模型、三库迁移及仓储实现,等待复审。
- [x] SQLite 独立临时文件库读写、关闭重开、迁移回滚重试和历史业务保留测试通过。
- [x] MySQL 5.7/8.0 独立实库专项及完整后端回归已通过,条件跳过与精确计数见支持矩阵第十九节。
- [ ] PostgreSQL 原生实库项目验证、剩余资源归属回填、成员授权、Web/API 和生产验收。
- [~] P0-06、P0-07 总状态均为进行中;第 10 号迁移不是完整统一领域模型交付。
## 六、第 11、12 号迁移的当前边界
第 11 号迁移为域名和服务器新增 `ResourceScope` 四字段,绑定表增加项目。HTTP 创建只取服务端会话身份,更新保留原始创建归属;普通更新不能迁移项目。历史所有者和操作者未知时为空,本地系统操作也不冒充用户。
SQLite 第 11 号迁移安装触发器,第 12 号迁移进一步保护替换冲突的目标行及隐藏行号,插入后检查剩余引用;不依赖 `foreign_keys` 或 `recursive_triggers` 开关。插入检查会读取绑定或资源索引,尚未做大规模性能准入,不据此提升性能整改项。无效历史引用拒绝迁移,不能擅自删除业务数据;触发器建立与版本登记失败必须回滚。
MySQL/PostgreSQL 第 11 号迁移建立项目外键与绑定复合外键,第 12 号只登记统一版本,不声称新增或验证两库特有的替换语义。项目字段和约束不是成员授权,现有跨项目列表、私钥、DNS 等权限门禁仍需单独交付。
## 七、第 13 号迁移:任务与审计关联
- [x] 确认三条实际任务入口:HTTP 申请任务、证书工作流任务和管理证书续期任务;审计另有登录、用户和本地重置等系统入口。
- [x] 已实现业务项目、系统管理证书与历史未知三种范围;管理任务同时校验特殊资源 ID 和任务类型,不创建伪造业务域名。
- [x] 已实现历史保留与可确认关系回填;新增任务通过单条 SQL 从实际域名项目写入快照,拒绝悬空资源和伪造范围。
- [x] 尝试从父任务读取范围,新增尝试校验当前资源仍存在且项目未变;历史孤儿可读,不再新增尝试。
- [x] 审计显式关联任务时核对资源,保留删除后的历史范围;HTTP 身份取服务端验证结果,不接受客户端自报操作者。后台及未知历史不伪造用户。
- [ ] 执行、取消、重试及撤权后的完整项目成员重新判权;当前范围校验不是权限授权。
- [~] SQLite、MySQL 专项和独立程序验证见支持矩阵;PostgreSQL 当前实库、完整三库恢复及生产验证仍未齐备。
历史快照刻意不建立项目外键,以免删除资源或项目时丢失审计与任务。产品仓储约束新建入口,不声称防止任意直接 SQL 篡改。历史审计只回填可证实的任务/资源关系,不按时间猜测所属任务,也不把任务操作者冒充审计操作者。
SQLite 与 PostgreSQL 在迁移事务内回填,失败回滚;MySQL DDL 可能已部分执行,重试跳过既有结构、仅补未知范围,完成后登记模式 13。回填会触发现存更新触发器;触发器拒绝时失败,不绕过。SQLite 已验证回填触发器、索引冲突和版本登记故障;PostgreSQL 此批只有迁移实现,没有实库证据。
## 八、任务与审计接口字段增量
本节说明 Scert 接口;同目录《后端接口说明文档.md》是 Smail 发信集成参考,不向其中混入 Scert 路由。
| 接口 | 新增响应字段 | 含义 |
| --- | --- | --- |
| `GET /api/v1/tasks`、`GET /api/v1/tasks/:id` | `scope_type`、`project_id` | 任务创建时的范围快照;已有 `operator_id` 保存实际 HTTP 发起人,后台任务为空 |
| `GET /api/v1/tasks/:id/attempts` | `scope_type`、`project_id` | 从父任务继承;缺少父任务的历史尝试返回 `unknown` 和空项目 |
| `GET /api/v1/audit-logs` | `scope_type`、`project_id`、`task_id`、`operator_id` | 审计事件范围、明确关联的任务和本次已验证操作者;不可确认的标识为空 |
`scope_type` 为 `project`、`system` 或 `unknown`;这些字段仅表达归属,不是访问令牌。登录审计使用本次认证成功的用户,失败不把请求声明或旧 Cookie 当成已验证身份;更新自身账户、改密导致会话失效后仍保存实际操作者,但不恢复旧会话。既有权限限制继续生效,新增项目成员权限尚未交付。
## 九、第 14 号迁移:成员、角色与实时判权基础
`project_members` 以 `(project_id, user_id, role)` 为主键,记录授予时间;允许一个用户在多个项目内拥有多个角色,不改变现有用户分组数量规则。三库迁移不自动创建成员,不推测历史授权。项目和用户被成员引用时禁止删除或改标识,必须先撤销成员关系;历史任务/审计仍沿用模式 13 的独立快照,不级联删除。
### 9.1 能力矩阵
所有有效项目角色都可读取其项目基本资料,其他能力如下;未列出的操作默认拒绝。
| 项目角色 | 授予的项目能力 |
| --- | --- |
| 项目管理员 `project_admin` | 本模块定义的全部项目能力,包括成员管理、任务取消;仍受普通账户不能读取完整私钥的上限约束 |
| 证书管理员 `certificate_admin` | 证书读取、管理;完整私钥读取还要求登录账户为普通管理员或超级管理员 |
| 部署管理员 `deployment_admin` | 证书元数据读取、服务器读取/管理、部署读取/执行/回滚;不授予证书私钥读取 |
| DNS 管理员 `dns_admin` | DNS 元数据读取、管理、凭据使用;凭据使用不等于导出明文密钥 |
| 审计查看者 `audit_viewer` | 审计及任务读取 |
| 只读用户 `read_only` | 证书、服务器、部署、DNS 元数据及任务读取;不授予审计、凭据使用或任何写操作 |
登录角色 `super_admin` 不是可授予的项目角色。只有当前状态为 `active` 的真实超级管理员可跨项目执行已定义操作;不存在的项目和未知操作即使对超级管理员也拒绝。普通管理员不因登录角色或同组身份获得项目能力。
### 9.2 仓储与并发边界
- `AuthorizeProject` 每次查询实际用户状态、实际项目及当前成员,不缓存会话中的角色或权限。撤销成员、停用账户、普通管理员降为普通用户后,下次判权立即采用新状态。
- `GrantProjectRole`、`RevokeProjectRole` 仅允许有效超级管理员或目标项目的项目管理员;授权条件与成员写入在同一条 SQL 中执行,防止应用层先查后写的间隙。MySQL 撤销使用自连接,不依赖不兼容的同表删除子查询。
- 未知角色、无效目标、禁用操作者及越权变更拒绝;重复授予不会产生重复关系。项目管理员可撤销自己的角色,之后不能自行恢复;有效超级管理员保留恢复能力。
- 判权结果不是长期许可。业务入口仍须从实际资源解析项目,并在执行副作用前重新判权;任务提交、排队、取消、重试及撤权竞态的完整接入尚未完成。
- 成员成功变更与审计已使用同一事务,具体边界见 9.4。成员列表、授予/撤销 HTTP 路由、账户分页与失败请求审计已接入,使用正式会话并在仓储内重判权限,见 9.5。成员管理界面及全部业务资源授权仍未完成;仓储方法不能直接作为无需会话验证的外部入口。
### 9.3 迁移与恢复边界
SQLite 使用外键和额外触发器保护关联,不依赖外键/递归触发器开关。插入前未指定行号时的 `-1` 不误拒绝正常新增,插入及更新后继续检查引用;扩展唯一索引造成的隐式删除也应回滚。邮箱冲突遵循既有非空邮箱唯一索引,多个空邮箱账户的正常新增和更新不受阻。触发器重名或版本登记失败,表与新触发器随事务回滚;不删除旧触发器强行通过。
MySQL 从历史 `users.id` 读取实际字符集和排序规则建立成员外键,不采用可能已变更的库默认值;DDL 后版本登记失败可重试,保留已有成员。重试检查用户字段、角色枚举、主键和双亲外键,残缺同名表拒绝登记版本,不自行删除或修补业务表。PostgreSQL 使用事务迁移与外键;本批未完成其原生实库验证。
Windows 独立产品程序已验证升级前的历史备份以及含两个项目角色的新备份恢复;恢复后重新走正式入口并核对成员授权及跨项目拒绝。此证据不是完整发行包、真实服务重启、三库全部恢复或生产验收。
### 9.4 成员变更与审计事务
- `changeProjectRole` 在同一事务内调用带实时授权谓词的成员写入和成功审计;不调用另开连接的审计接口,避免脱离事务或阻塞单连接 SQLite。
- 审计动作区分 `project_member.grant` 与 `project_member.revoke`,资源为目标账户,记录项目范围、实际操作者和角色;自我撤权后使用刚验证的变更上下文,不重新推测已失去的权限。
- 成员影响行数不是 1、审计写入失败、触发器忽略插入或提交失败均不得返回成功。审计插入未实际写入时回滚已发生的成员变更。
- MySQL 预先检查 `project_members` 与 `audit_logs` 都是 InnoDB;不以 BeginTx 返回成功推断非事务表也能回滚,不擅自修改历史引擎。
- 单连接 SQLite、审计故障、取消上下文、重复操作、自我撤权、历史审计保留及双版本 MySQL 隔离用例是当前验证范围;PostgreSQL 当前实库和生产端到端尚未完成。
- 成功审计不能替代失败请求审计。成员接口的拒绝请求与身份来源现已接入 9.5 所述处理,全部业务入口及排队任务重判仍是后续任务,不能由成员仓储或接口提交宣称完成。
### 9.5 成员接口、分页与失败审计
- `GET /api/v1/projects/:projectID/members` 按账户游标分页,每页允许 5、10、15、30 位成员;同一账户的全部角色合并返回,只包含账户 ID、用户名、状态和角色,不含邮箱、分组或密码摘要。
- `POST` 同路径授予角色;`DELETE /api/v1/projects/:projectID/members/:userID/roles/:role` 撤销角色。真实超级管理员或本项目管理员可操作,普通全局管理员和同组成员不隐式获权。
- 成员查询在同一 SQL 内完成权限门与分页读取;合法空项目返回空数组,无权限和项目不存在统一拒绝。超级管理员不自动产生列表成员。
- 请求身份来自已验证会话,不接受 `actor_id`。会话验证后发生撤权或停用时,仓储仍重新判定;身份快照仅用于本次失败审计,不缓存权限。
- 成功事务失败必须回滚,只有确定的成员唯一键冲突映射为 409;审计触发器等其他失败保持 500,不将宽泛约束错误误报为重复授权。
- 失败审计使用真实项目解析范围,不存在项目记录为未知;不记录请求体或原始数据库错误。独立取消上下文有三秒上限;审计存储失败只写稳定诊断,不伪报已落库。
- [接口交接说明](../.claude/docs/ai/project-members/api-handoff.md) 记录完整请求、响应、错误与前端处理。`doc/后端接口说明文档.md` 是 Smail 参考,不混入本项目接口。
- 本批回归与实库证据见《构建版本与数据库支持矩阵》第二十三节;接口通过不是成员页面、所有资源项目隔离或生产验收通过。