Appearance
H5 发布流水线规划
平台级文档(不限于 vem)。规划"子应用前端 → 合并进主项目 → 构建 H5 → 部署 CDN,平台内全租户共用"的自动化构建。
状态:规划中,接口已落地一部分。创建于 2026-06-15,最近更新 2026-06-15。
1. 背景与目标
我们的产品是可独立交付的多租户 SaaS:同一套 hiapi-cloud 会卖给多个平台/运营商,每个平台一套独立部署(独立 hiapi-cloud-public、独立数据库、独立装了哪些子应用)。主前端是 uniapp/hiapi-cloud-uniapp(下称主项目),各业务子应用(vem / shop / store / public …)各自独立开发,产物发布到中心应用市场(appstore.cloud.adinz.com,跨所有平台共享)。
本期只做 H5。 小程序、APP 等项目多了之后再单独规划(见文末「后续」)。
H5 的发布模型(已与负责人确认):
- 平台内一份产物,全租户共用。 同一平台下不同租户的差异(启用了哪些子应用、装修内容、主题、菜单)全是运行时配置,不进代码、不触发构建。
- 构建是低频运营动作:某平台的运营者在「新增子应用 / 子应用升版」时触发一次,产出该平台自己的 H5。
- 每个平台各自独立构建:用哪些子应用、各自什么版本,是平台级数据(各平台
CloudApplication表),平台之间互不相同。
为什么不用阿里云云效,改用客户服务器上的 k8s 构建
- 产品要交付给 N 个平台/客户,很多部署在客户自有环境(含内网)。中心化的云效流水线服务不了"每个客户独立、各自数据、各自产物"的形态。
- 因此构建能力随产品一起部署到客户的 k8s 集群,在客户服务器上就近构建——读本平台的
CloudApplication、就近部署到本平台的 CDN/静态托管。 - 构建本质仍是几条 shell(node 合并脚本 + pnpm build + 产物上传),用一个 k8s Job(可选常驻 build-controller)承载即可,不需要中心调度系统。
目标:把"拉主项目 → 读本平台子应用清单 → 下载并合并子应用代码 → pnpm build:h5 → 部署本平台 CDN"这条链路,在客户 k8s 集群内用 Job 自动化,运营点一下「发布」即可。
2. 两层构建模型(核心,务必区分)
整套体系有两层完全不同的构建,千万别混:
第 1 层:子应用制品层(已存在,无需改造)
每个子应用仓库(如 hiapi-cloud-vem/vem-web)用 lib 模式 pnpm build:h5,经 @hiapi/hiapi-cloud-components-upload 的 UploadPlugin 在 closeBundle 阶段:
- 把
src/components/<componentDir>(如hiapi-vem)+src/<pageDir>(如subVem)拷进dist/zip/,压成zip.zip; - 解析
src/components/index.ts的widgetExport→components.json(组件模板); - 连同
umd.js(装修预览用)、pages(对外页面列表)、menus(后台菜单)一起 POST 到中心应用市场https://appstore.cloud.adinz.com/api/v1/releases/frontend,按project / version归档。
最终在应用市场 OSS 上形成(参见 hiapi-cloud-nuwa service.go 的 prefix):
hiapi-cloud/<project>/<version>/
components.json 组件模板(入库,供装修选择组件)
pages.json 页面路径(入库)
<project>.umd.js H5 组件预览(后台可视化装修用)
zip.zip 源代码:components/<componentDir>/ + <pageDir>/这一层产出的
umd.js只服务后台可视化装修预览;zip.zip是真正可运行的源码,是第 2 层的输入。这两个用途不要搞混。
第 2 层:H5 运行时层(本文档要做的)
把第 1 层各子应用的 zip.zip 里的源码合并进主项目,做一次普通的 pnpm build:h5(注意:主项目是正常 app 构建,不是 lib 模式),产出真正给终端用户访问的 H5 站点,部署到本平台 CDN,平台内全租户共用。
中心应用市场(OSS) 主项目 hiapi-cloud-uniapp 平台 CDN
┌──────────────┐ ┌───────────────────────────┐ ┌─────────┐
│ vem/0.0.2/ │ │ src/components/hiapi-vem/ │ │ │
│ zip.zip │──解压──▶ │ src/subVem/ │ │ 平台内 │
│ shop/x/ │──合并──▶ │ src/components/hiapi-shop/ │─build│ 一份 H5 │
│ zip.zip │ │ src/subShop/ │──────▶ 全租户 │
│ public/... │ │ (vite 自动接线,无需改配置)│ │ 共用 │
└──────────────┘ └───────────────────────────┘ └─────────┘
▲ 下载哪些 / 哪个版本,由本平台 build-manifest 接口(§6)决定3. 合并契约(Merge Contract)
主项目 vite.config.ts 已做好自动接线,合并因此非常简单——只是放文件,不改配置:
| 机制 | 配置 | 作用 |
|---|---|---|
| 分包自动注册 | subPackages: fg(['src/sub*']) | 任何 src/sub* 目录(如 subVem)自动成为分包 |
| 组件自动导入 | UniHelperComponents({ dirs:['src/components'], directoryAsNamespace:true }) | src/components/hiapi-vem/** 自动 easycom,命名空间隔离 |
| 动态渲染器 | dynamicRendererPlugin({ dirs:'src/components/**/*.vue' }) | 扫描所有组件生成 DynamicRenderer.vue,按装修 schema 的 item.component 渲染 |
| 页面路由 | UniHelperPages 读各页面 <route> 块自动生成 pages.json | 合并页面后路由自动注册,无需手动改 pages.json |
现成样板:主项目已存在
src/components/hiapi-public(public 子应用合并进来的),vem 照此放src/components/hiapi-vem+src/subVem即可。
合并落点(每个子应用)
解压 zip.zip 后,zip 内结构为:
zip/
components/<componentDir>/ → 主项目 src/components/<componentDir>/
components/index.json → 组件模板(入库用,运行时不需要,合并时丢弃或单独收集)
<pageDir>/ → 主项目 src/<pageDir>/合并动作:
src/components/<componentDir>/ ← zip/components/<componentDir>/ (整目录覆盖)
src/<pageDir>/ ← zip/<pageDir>/ (整目录覆盖)注意:子应用源码里的 property.vue(装修属性面板)只在装修后台用,运行时不需要。可在合并时按需剔除(**/property.vue)以减小包体;不剔除也不影响运行,只是多打一点。
强约束(必须在子应用开发期遵守,否则合并构建会炸)
- 依赖白名单:子应用只能使用主项目
package.json已有的依赖 + 共享库(@hiapi/hiapi-cloud-web-basic、wot-design-uni等)。绝不在合并期 merge 子应用自己的 package.json。 这条要在子应用仓库用 CI / lint 卡死,否则生产构建会因缺依赖随机失败,极难排查。 - 命名空间:组件
hiapi-<project>-<widget>、页面路径/sub<Project>/pages/...、组件目录hiapi-<project>、页面目录sub<Project>。靠前缀天然防撞,继续强制。 - 不碰主项目公共文件:子应用不得改
main.ts、根pages.json、App.vue等;一切扩展通过"放自己的目录"完成。
4. 运行时多租户机制(平台内一份产物如何服务所有租户)
H5 在一个平台内是一份产物,租户差异全靠运行时:
- 识别租户:H5 启动时按访问域名(
*.tenant-domain泛域名)或 URL 上的tenantId确定当前租户。 - 拉租户配置:向后端请求该租户的配置 —— 启用了哪些子应用、各页面/首页的装修 schema(JSON)、主题色、菜单(tabBar/导航)。
- 渲染:装修页面把 schema 交给
DynamicRenderer.vue,按item.component渲染对应组件(组件代码已在包里)。 - 功能开关:未启用的子应用,其菜单/入口运行时隐藏;即使包里有代码也访问不到。
因此:装修内容变化、租户启停子应用,都不需要重新构建,改后端配置即生效。只有"平台新增/升级子应用的代码"才需要跑本流水线。
5. 子应用构建清单接口(build-manifest)
决定"本平台这次构建合并哪些子应用、各自什么版本"。来源是本平台的 CloudApplication 表(本平台装了哪些应用、各自已安装的前端版本),再对接中心应用市场补齐 project / componentDir / pageDir(这些目录信息平台本地不存)。
接口
GET /cloud-api/public/app-store/build-manifest
鉴权:无(公开接口,放在 /public/* 路径,不被网关拦截鉴权)实现在
hiapi-cloud-public的cn.hiapi.system.basic.api.pub.AppBuildController(@RequestMapping("/public/app-store"),无@Secured)。 构建流水线跑在客户集群内、无登录态,故必须走公开路径。内部调中心市场批量接口GET https://appstore.cloud.adinz.com/api/v1/market/apps/batch?appIds=...(公开,只回 ONLINE 应用)补齐前端目录信息。 数据是平台级(本平台CloudApplication),不含租户维度、不暴露敏感字段(无 appSecret),可安全公开。
实现逻辑
- 取本平台
CloudApplication中install == 2(安装成功) 的应用。 - 用这些
appId调中心市场批量接口,拿到每个应用的project / componentDir / pageDir。 - 逐条组装:
project / componentDir / pageDir任一为空 ⇒ 视为无前端产物(纯后端应用 / 主宿主应用),跳过;- 构建用版本 = 本平台已安装的前端版本
CloudApplication.frontendVersion(为空回退version)——保证构建结果与本平台当前安装状态一致、可复现,不取市场最新版; zipPath=hiapi-cloud/<project>/<version>/zip.zip(应用市场 OSS 相对路径,与 nuwaservice.go的prefix约定一致)。
响应
jsonc
{
"code": 200,
"data": [
{
"appId": "A-11001",
"name": "无人售卖机",
"project": "hiapi-cloud-vem",
"frontendVersion": "0.0.2",
"componentDir": "hiapi-vem",
"pageDir": "subVem",
"zipPath": "hiapi-cloud/hiapi-cloud-vem/0.0.2/zip.zip"
}
]
}- 流水线拿
zipPath,用自身配置的 应用市场 OSS base(环境变量APPSTORE_OSS_BASE)拼成完整下载地址。当前 base =https://hiapi-cloud.oss-cn-shenzhen.aliyuncs.com,拼出完整地址如https://hiapi-cloud.oss-cn-shenzhen.aliyuncs.com/hiapi-cloud/hiapi-cloud-vem/0.0.2/zip.zip。把 base 留给流水线而非写死在接口,便于不同环境/镜像源切换。 - 范围是"本平台已装的所有有前端的子应用",不带租户维度——H5 平台内一份产物,租户启停是运行时的事。
6. k8s 构建设计(随产品部署在客户集群)
租户后台点「发布 H5」
→ hiapi-cloud-public 写一条发布记录(pending) + 创建一个 k8s Job
(由 Job 模板渲染;或写入队列交给常驻 build-controller)
→ Job 容器执行:
① 读 build-manifest 接口(集群内直连 service)
② 逐个下载 zipPath 的 zip(APPSTORE_OSS_BASE + zipPath)
③ node scripts/merge-subapps.mjs 解压、合并到主项目 src/(见 §7)
④ pnpm install --frozen-lockfile
⑤ pnpm build:h5:production 产物在 dist/build/h5
⑥ 部署到本平台静态托管(nginx 挂载卷 / 平台 OSS / CDN)
⑦ 回调 hiapi-cloud-public 更新发布记录(success/fail + 构建记录)要点(都是为了"在客户服务器、低频、要稳"):
- 构建工具链镜像
hiapi-cloud-h5-builder:<版本>:只装 node22 + pnpm9.9 + git + unzip/rsync/curl 和一个通用 bootstrap(deploy/builder/),不打进 app 源码——主项目仓库变动频繁,代码由 Job 运行时git clone最新拉取(GIT_REPO/GIT_BRANCH,私有仓库用GIT_TOKEN)。镜像因此很稳定,只在 node/pnpm/工具版本变更时才重打。 - install 提速:没源码就没法预烤
node_modules,改用挂载的 pnpm store 卷(PNPM_STORE_DIR,内容寻址、跨构建复用)做热缓存,首次后 install 几乎不联网。 - 触发与并发:低频、单平台,一次发布一个 Job;加平台级锁防并发构建。Job 失败保留日志供后台查看。
- 产物落点可配:每个平台 CDN/静态托管不同,作为环境变量注入 Job。
后端怎么"指挥" k8s 建 Job(A 路线,已实现)
k8s API server 本身就是个 HTTPS 接口,"建一个构建任务" = 往它 POST 一个 Job 的 JSON。后端跑在集群内时,Pod 自动挂好 ServiceAccount 凭证:
/var/run/secrets/kubernetes.io/serviceaccount/token 身份(JWT,放 Authorization: Bearer)
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt 校验 API server 证书
API server: https://kubernetes.default.svc (用 DNS 名以匹配证书 SAN)| 动作 | k8s API |
|---|---|
| 建构建任务 | POST /apis/batch/v1/namespaces/<ns>/jobs + Job JSON |
| 查状态 | GET .../jobs/hiapi-h5-build-<buildNo> 看 .status.succeeded/.failed(本方案不轮询,靠 Job 回调) |
| 看日志 | GET .../pods/<pod>/log |
| 清理 | Job 的 ttlSecondsAfterFinished 自动回收 |
实现:K8sJobClient(复用框架已有的 okhttp,用集群 CA 单建一个 client,不动全局 HttpService)+ CloudH5BuildService.launch() 程序化拼 Job JSON 并 POST。前提是给后端 ServiceAccount 配 RBAC(在 deploy/h5-prereqs.hiapi-cloud.yaml 内:建/查/删 Job、读 Pod 日志)。非集群环境(本地无 token)自动跳过,记录保持 PENDING,便于联调。
- 环境变量(后端 hiapi-cloud-public):
H5_BUILDER_IMAGE(构建镜像 tag,跟产品版本)、H5_BUILD_NAMESPACE(Job 命名空间,缺省读 ServiceAccount 挂载)、K8S_API_BASE(API server 地址,缺省https://kubernetes.default.svc)。 - 环境变量(Job 注入,走 deploy 的 ConfigMap/Secret):
APPSTORE_OSS_BASE(市场 OSS 基址)、BUILD_MANIFEST_URL(本平台接口地址)、PLATFORM_TOKEN(调接口鉴权)、DEPLOY_TARGET(产物上传目标)、CALLBACK_URL(回调发布记录,=/public/app-store/build-callback)、BUILD_ID(= 发布记录buildNo,由后端按 buildNo 注入)。
M3 已落地的后端接口(hiapi-cloud-public)
| 接口 | 路径 | 鉴权 | 作用 |
|---|---|---|---|
| 触发发布 | POST /platform/h5-build/trigger | Platform | 平台级并发锁 → 建发布记录(PENDING)→ 直接调 k8s API 创建 Job(转 BUILDING) |
| 发布记录分页 | POST /platform/h5-build/query | Platform | 后台列表(TablePage),按 status/时间筛选,created 倒序 |
| 发布记录详情 | GET /platform/h5-build/{id} | Platform | 单条详情(含 record 溯源、message) |
| 构建回调 | POST /public/app-store/build-callback | 无(公开) | Job 完成回调:按 buildId(=buildNo)更新状态 success/fail + 写 record |
- 发布记录实体
CloudH5Build(表hiapi_core_h5_build,平台级、不分租户):buildNo(唯一,= Job 名后缀 / BUILD_ID / 回调键)、status(PENDING/BUILDING/SUCCESS/FAILED)、operatorFid、image、record(build-record.json 原文)、message。 - 并发锁:存在 PENDING/BUILDING 记录时拒绝再次触发("已有 H5 构建任务进行中")。
7. 合并脚本设计(scripts/merge-subapps.mjs)
放在主项目仓库(随构建镜像一起),纯 Node,无额外重依赖(用内置 + 已有的 fast-glob/解压库)。
输入:build-manifest 接口返回的清单。流程:
manifest = GET $BUILD_MANIFEST_URL // 本平台子应用清单
for app of manifest:
url = `${APPSTORE_OSS_BASE}/${app.zipPath}`
download url -> tmp/${app.project}.zip
校验(大小/可解压;可加 sha 校验)
unzip -> tmp/${app.project}/
assert exists tmp/.../components/${app.componentDir} // 缺目录即失败,fail fast
assert exists tmp/.../${app.pageDir}
cpSync components/${app.componentDir} -> src/components/${app.componentDir} (先清后拷)
cpSync ${app.pageDir} -> src/${app.pageDir} (先清后拷)
(可选) rm src/${app.pageDir}/**/property.vue, src/components/${app.componentDir}/**/property.vue
收集 app 版本信息
打印合并报告(项目/版本/文件数),写 dist/build-record.json(本份产物含哪些子应用@版本)要点:
- fail fast:任何子应用下载失败 / 目录缺失 → 整个构建失败,绝不"带病构建"。
- 幂等:合并前先清理上次合并落点(
src/components/hiapi-*中清单内的、src/sub*中清单内的),避免残留旧版本。 - 不动主项目自带的、清单之外的公共组件。
- 可复现:
build-record.json记录本次锁定的子应用与版本,随产物发布,便于追溯"线上这份 H5 是用哪些版本拼的"。
8. 需要配合准备的事项(后端 / 运营 / 运维)
- [x] build-manifest 接口:
hiapi-cloud-public公开端AppBuildController.buildManifest(/public/app-store/build-manifest,无鉴权),读CloudApplication+ 中心市场批量接口,已实现。 - [x] 合并脚本:主项目
scripts/merge-subapps.mjs(读清单 → 下载/解压 → 合并 src/ → 写 build-record.json),已本地端到端跑通。 - [x] 构建入口 + 镜像 + Job 模板:
scripts/build-h5.sh、deploy/builder/(工具链镜像 + git clone bootstrap)、deploy/h5-build-job.yaml(参考 Job),已落地。 - [ ] 应用市场 OSS 下载可达:确认
APPSTORE_OSS_BASE + hiapi-cloud/<project>/<version>/zip.zip在构建环境可下载。 - [x] 发布记录 + 触发 + 回调接口:
CloudH5Build记录 +H5BuildController(触发/分页/详情,平台端)+AppBuildController.buildCallback(公开回调)+ 平台级并发锁,已实现。 - [x] 后端直建 k8s Job:
K8sJobClient(集群 CA + ServiceAccount token,POST Job 到 API server)+CloudH5BuildService.launch()程序化拼 Job,已实现。 - [ ] 预置资源 apply:apply
deploy/h5-prereqs.hiapi-cloud.yaml(SA/Role/RoleBinding + ConfigMap/Secret/PVC,一把梭),并把 hiapi-cloud-public 的 Deployment 绑到serviceAccountName: hiapi-cloud-public。 - [ ] 集群内验证:打镜像 + 后端触发,跑通一次发布(建 Job → 构建 → 产物落 CDN → 回调)。
- [ ] 后台「发布 H5」页面:接
POST /platform/h5-build/trigger+TablePage接/platform/h5-build/query展示发布历史。 - [ ] 依赖白名单校验:在各子应用仓库 CI 增加检查(子应用引用的依赖必须 ⊆ 主项目依赖 + 共享库)。
- [ ] 后端租户配置接口:返回某租户的「启用子应用列表 + 各页面装修 schema + 主题 + 菜单」,供 H5 运行时拉取(运行时多租户的基础)。
- [ ] 泛域名 / CDN:规划
*.<域名>或path/tenantId方案,让前端能按访问入口识别租户;各平台 CDN/静态托管配置。
9. 缓存与性能
| 手段 | 收益 |
|---|---|
| 工具链镜像(node+pnpm+git,不含源码) | 镜像稳定、与频繁变动的 app 仓库解耦;代码每次 git clone 最新 |
| 挂载 pnpm store 卷热缓存 | install 几乎不联网,单次构建降到分钟内 |
--frozen-lockfile | 保证依赖确定性、可复现 |
| 产物版本目录 + 软链切换 | 秒级回滚 |
构建频率本身很低(只在子应用增/升版时),性能不是瓶颈,但预烤镜像能让单次发布从"几分钟"降到"一两分钟"。
10. 风险与约束
| 风险 | 说明 | 对策 |
|---|---|---|
| 依赖冲突 | 子应用引入主项目没有的依赖 → 构建炸 | 依赖白名单 + 子应用 CI 校验(§3 强约束 1) |
| 命名/路由碰撞 | 子应用间组件名/页面路径重复 | 强制 hiapi-<project>-* / sub<Project> 命名空间 |
| 带病构建 | 某子应用 zip 损坏仍继续打包 | 合并脚本 fail fast |
| 包体积膨胀 | H5 含本平台所有子应用代码 | H5 无硬上限 + 分包懒加载,可接受;真大了再按需精简 |
| 版本漂移 | 不知道线上这份产物含哪些版本 | 构建用已安装版本(非市场最新)+ 产物写 build-record.json |
| 客户集群资源 | 构建占用客户 k8s 资源 | 低频、单 Job、可限资源配额;构建完即回收 |
11. 落地里程碑
- M0 接口:
build-manifest接口(✅ 已实现)。 - M1 合并脚本:主项目
scripts/merge-subapps.mjs(✅ 已实现并本地跑通;pnpm build:h5在镜像内由build-h5.sh串联)。 - M2 构建镜像 + Job:工具链镜像
deploy/builder/(Dockerfile + entrypoint,git clone 拉源码)+scripts/build-h5.sh入口 +deploy/h5-build-job.yaml+deploy/h5-prereqs.hiapi-cloud.yaml(✅ 已实现;待客户集群内 apply 跑通一次)。 - M3 后端触发 + 发布记录:发布记录表
CloudH5Build+ 触发/分页/详情/回调接口 + 平台级并发锁 + 后端直接调 k8s API 建 Job(K8sJobClient,A 路线)(✅ 已实现;待 apply RBAC/预置资源、集群验证、后台页面)。 - M4 运行时多租户:后端租户配置接口 + 前端按域名/tenantId 拉配置 + DynamicRenderer 渲染,打通"平台内一份产物多租户"。
- M5 子应用 CI 校验:依赖白名单 + 命名规范检查,防止脏制品流入。
12. 后续(本期不做)
- 小程序:必须构建期打包(微信禁远程代码),且需"微信开发者工具 CLI"做 headless 构建/上传——届时用一台装了开发者工具的自有构建机接进同样的 k8s 构建体系。建议按"启用子应用组合"构建,租户下载精简工程后用自己的 AppID 自助上传提审(平台不碰上传密钥)。
- APP:图标/名称/包名/证书因租户而异,需按租户打包;优先"原生壳构建一次 + wgt 热更新"减少重打。
变更记录
| 日期 | 变更 | 备注 |
|---|---|---|
| 2026-06-15 | 创建文档,确定 H5 两层模型、合并契约、清单契约 | 规划,待实现 |
| 2026-06-15 | 架构改为多平台 + 客户服务器 k8s 构建(弃用云效);清单来源改为 hiapi-cloud-public 的 build-manifest 接口(读 CloudApplication + 中心市场补 componentDir/pageDir);接口已实现 | 规划,接口已落地 |
| 2026-06-15 | build-manifest 改为公开接口 /public/app-store/build-manifest(AppBuildController,无鉴权),构建流水线无登录态需走公开路径 | 接口已落地 |
| 2026-06-15 | 落地流水线脚本与镜像:主项目 scripts/merge-subapps.mjs(合并,已本地跑通)、scripts/build-h5.sh(构建入口:合并→install→build:h5→部署→回调)、Dockerfile.builder(预烘焙镜像)、deploy/h5-build-job.yaml(k8s Job + ConfigMap + Secret 模板) | M1/M2 已落地,待集群验证 |
| 2026-06-15 | M3 后端落地:发布记录实体 CloudH5Build + JPA + CloudH5BuildService(触发/并发锁/回调)、平台端 H5BuildController(trigger/query/详情)、公开 AppBuildController.buildCallback | M3 接口已落地 |
| 2026-06-15 | 确定 A 路线并落地:后端直接调 k8s API 建 Job(K8sJobClient 用集群 CA + ServiceAccount token POST Job),弃用 build-controller;新增 deploy/h5-builder-rbac.yaml,deploy/h5-build-job.yaml 改为预置资源 + 参考清单 | A 路线已落地,待集群验证 |
| 2026-06-16 | 镜像不再打进 app 源码:改为工具链镜像(deploy/builder/ Dockerfile + entrypoint)+ Job 运行时 git clone 最新代码;install 改用挂载的 pnpm store 卷热缓存(PNPM_STORE_DIR)。Job 增 pnpm-store PVC,去掉 SKIP_INSTALL;ConfigMap 增 GIT_REPO/GIT_BRANCH/PNPM_STORE_DIR,Secret 增 GIT_TOKEN | 已落地,待集群验证 |
| 2026-06-16 | 收敛 deploy/ 冗余:删 h5-builder-rbac.yaml(已并入 prereqs),h5-build-job.yaml 瘦成纯「参考 Job」;h5-prereqs.hiapi-cloud.yaml 成唯一需 apply 的预置资源文件,GIT_REPO 填 gitee | 文档/部署整理 |