本项目固定使用私服发行的 Core 8.1.0、Module Runtime 9.0.4 和 RPC 9.0.7;精确依赖、锁文件与逐文件校验基线一并提交。 V2 发布与受控开发监听以 [RELEASE-V2.md](RELEASE-V2.md) 为准;历史 V1 操作说明不适用于新 V2 候选。 # COOL SaaS 模块微服务起始项目 这是一个可复制到 `cool-midway-biz/microservices/` 的干净起始项目。 它只包含托管 SaaS 模块微服务运行所需的基础结构,不包含 Demo 商品业务、Pilot、 Examples、测试 Fixture 或任何既有模块的生成契约。 ## 初始化 1. 将本目录的项目文件复制到 `cool-midway-biz/microservices/`,不要复制模板 仓库自身的 `.git`;scaffold 工具也必须移除模板 origin,再由业务项目重新初始化。 2. 修改 `package.json` 中的包名,并按需修改 `src/config/app.default.ts` 中的本地默认端口 `7101`。 3. 在开发环境的模块管理页输入基础版本(例如 `1.0.0`)并锁定 Candidate;平台会分配 Build Revision、`1.0.0-rc.` 候选版本和同名 release 分支。 4. 安装依赖后,用平台给出的精确 Revision 生成并启动开发实例: ```powershell pnpm install pnpm exec cool saas-model generate --module --revision pnpm dev ``` 首次生成会初始化 Git(如果需要)、要求配置 `origin`,并切换到平台指定的 `release/` 分支。CLI 会在项目根目录写入可提交的 `.cool-saas-candidate.json`;其中只有模块、Revision、版本、分支与 checksum,不包含 控制面 token、Runtime ticket 或其他 Secret。 `src/config/app.default.ts` 会直接导入 `src/saas/generated`。因此全新模板在首次生成 完成前不能编译,这是为了避免用占位契约冒充平台锁定的 Build Revision。 `start:local` 会显式开启仅限 `local/test` 的 Runtime bootstrap fallback;本地控制面 启动命令也必须开启同一个 flag。两端未另设 token 时只使用仓库内公开的开发常量, 生产环境始终拒绝这条路径。 ## CLI 生命周期 在微服务根目录执行: ```powershell pnpm exec cool saas-model generate ` --module ` --revision pnpm build ``` 管理端的“生成到本地”仍只负责选择目录并写入生成文件,不会初始化 Git、修改 remote 或切换分支;完整 Candidate 开发流程应使用上面的 CLI generate 命令建立这些 Git 约束。 使用默认输出目录时,CLI 会写入 `src/saas/generated/`,并自动维护 `src/saas/generated/index.ts` 稳定导出入口。 生成后可随时执行 `pnpm exec cool saas-model verify` 检查当前分支、候选锁、生成文件与 控制面的精确 Candidate 是否一致。`pnpm dev` 会先执行同一校验,再调用 `package.json` 中 `coolSaas.lifecycle.dev` 的非递归命令构建并启动原有开发宿主;因此 配置里不要写回 `pnpm dev`。持续开发使用 `pnpm exec cool saas-model dev --watch`,由外层 CLI 捕获源码身份、构建独立快照、排空旧实例并发布新代次。不要用仅重启内层宿主的 watch 替代这个入口;依赖或候选锁变化需要结束当前 watch 后重新启动。 CLI 生成的 `models.ts`、`contracts.ts`、`saas-actions.ts`、`saas-events.ts`、 `module.ts`、`index.ts` 和锁文件属于同一个不可变 Build,请整体保留且不要手工修改。 ## 固化 Candidate 开发联调完成后,停止开发 watch,提交并推送当前候选分支,然后使用外部 builder 配置: ```powershell pnpm run publish --builder-config <外部绝对配置路径> ``` `publish` 脚本执行 `cool saas-model finalize`,不会发布 npm 包或直接晋升 Stable。 Core 8.1.0 先领取平台签名构建任务,从已推送的 Git 提交提取独立源码目录,复制并核对实际 依赖,再按 `coolSaas.lifecycle` 执行测试和构建。源码、启动脚本、依赖在构建后发生变化 都会拒绝固化。CLI 签名保存完整 bundle、推送版本 Tag,并幂等提交平台 freeze。 生命周期子命令不能再次调用 `pnpm dev`、`pnpm publish` 或 `cool saas-model dev/finalize`。 保存 CLI 输出的 frozen deployment directory 及其外部构建回执。目录内包含已经构建的 `dist`、启动器和独立依赖;不要重新安装、构建或修改其中内容。freeze 后原 development 身份不能继续参与路由,V2 候选也不能再用 `pnpm dev` 重建。配置精确候选 Release ID、 BUILD_SHA、独立 Runtime 票据/状态、租户凭证、外部 builder 公钥信任文件和短期制品读取凭证, 从该目录执行 `pnpm run start:frozen`,再由平台的专用 runner 执行自动验收。 管理员查看不可变报告,选择有效的 run 单独批准,随后晋升 Stable。Stable 使用同一份 deployment directory 与独立运行身份执行 `pnpm run start:stable`。已晋升来源 RC 如需继续 运行,也必须保留原制品和有效关联;拒绝、放弃、撤销或关联失效的候选不能重新启动。 完整配置、回执恢复和验收顺序见 [RELEASE-V2.md](RELEASE-V2.md)。 控制面地址与凭证使用 `COOL_SAAS_CONTROL_PLANE_URL`、`COOL_SAAS_TOKEN`,或 CLI 的 `--endpoint`、`--token` 参数。CLI token 不写入候选锁,也不传给测试、构建或开发宿主 子进程。freeze 调用 `/internal/saas/module_release/freeze`,使用 Bearer 凭证和受限的二进制 请求体传输完整 bundle,不依赖浏览器 Cookie。 ## 依赖与发布 `@cool-midway/core`、`@cool-midway/module-runtime` 和 `@cool-midway/rpc` 使用 `https://npm.blendercg.art/` 中已发布的精确版本。安装前通过环境变量提供私服凭证, 不要把 token 写入仓库: ```powershell $env:NODE_AUTH_TOKEN='' pnpm install ``` 本地环境可以使用 Runtime 提供的开发配置。应用开发者在部署描述中只需要关注: ```ini PORT=7101 SAAS_CONTROL_PLANE_URL=https://control-plane.example.com ``` `PORT` 是进程在容器内监听的健康检查端口,不是 Action RPC 的入口。同一镜像的所有 副本都可以监听 `7101`。宿主机可分别映射为 `17101:7101`、`17102:7101`,也可以完全 不发布端口;无论选择哪种方式,容器内配置都保持不变。 仓库当前网关与模块宿主默认连接 Redis `6397`。部署环境如果使用其他 Redis 服务端口, 由编排器同时覆盖双方的 `REDIS_PORT`,不能只修改一侧。独立模块宿主默认采用 `RPC_RUNTIME_MODE=all`,即同一进程同时接收 Action 并运行托管 Outbox/Inbox Worker; 只有明确拆成 `rpc` 与 `worker` 两类副本时才覆盖该值,不能用兼容旧名称 `server` 冒充完整托管宿主。 生产环境由受信部署编排器在应用配置之外完成以下工作: - 签发绑定精确 Build/Release 的 bootstrap ticket,并注入 `BUILD_SHA`、Redis 和 每个进程独立的 Ed25519 私钥及受限公钥信任文件;Release 身份及摘要由控制面权威响应回填。 - 为每个副本分别挂载短期、一次性且绑定模块/Build/Release/制品的 `SAAS_RUNTIME_BOOTSTRAP_TOKEN_FILE`,不能让两个副本共用一个票据。票据签发结果会 同时返回并绑定该副本的 `instanceId`。 - 为每个副本提供独立、可写的 `SAAS_RUNTIME_BOOTSTRAP_STATE_FILE`;其中的实例 UUID、 Runtime Node ID、租约及 fencing 状态由 Runtime 管理,开发者不要手工填写。 - 使用票据签发结果中的 `instanceId` 生成对应的 `SAAS_TENANT_CREDENTIALS_FILE`,再与 票据一起挂载给该副本。源码和 Compose 文件都不能保存 Secret 值。 生产及其他严格环境不接受手工配置 `RPC_INSTANCE_ID`、`SAAS_RUNTIME_NODE_ID` 和 `SAAS_SERVICE_TOKEN_FILE` 来绕过 bootstrap。进程只能通过一次性 ticket 建立新会话, 或恢复 Runtime 已验真且仍有效的独占 state;静态三元组仅作为 local/test 的迁移兼容, 不能用于部署。 `bootstrap.js` 会先执行 Runtime pre-bootstrap,再校验精确 Release 环境,最后启动 Midway。`GET /health/` 读取 `rpcHostReadiness` 的真实快照;bootstrap、Node ID 租约或 RPC 注册任一未就绪时都会返回 HTTP `503`,只有全部就绪才返回 `200`。 仓库根目录的 `docker-compose.microservices.yml` 是同镜像双副本示例。两个副本容器内 均使用 `7101`,并分别映射到宿主机 `17101`、`17102`,每个副本有独立的 bootstrap ticket、tenant credential Secret 和 Runtime state volume。生产环境若只需容器网络 内的 readiness probe,可以用部署 override 清空 `ports`,Action RPC 不受影响: ```yaml services: demo-stable-a: ports: [] demo-stable-b: ports: [] ``` Compose 顶层 Secret 的 `${..._FILE}` 是编排器生成的宿主机文件路径,不是应用配置。 这些源文件在 Linux 上必须限制为服务账户私有权限(例如 `0600`),且不能提交到 仓库。生产平台可等价替换为 Kubernetes/Swarm external Secret,容器内的固定挂载路径 和 `*_FILE` 环境变量无需改变。 `SAAS_CONTROL_PLANE_URL` 必须是容器自身能够访问的地址;容器中的 `127.0.0.1` 指向 容器本身,不能用来表示宿主机或另一个控制面容器。生产环境必须使用 HTTPS。