ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GitHub Actions CI/CD 最佳实践:从工作流结构、安全加固到部署策略的完整指南(awesome-copilot 实战验证)

GitHub Actions CI/CD 最佳实践:从工作流结构、安全加固到部署策略的完整指南(awesome-copilot 实战验证) GitHub Actions CI/CD 最佳实践从工作流结构、安全加固到部署策略的完整指南awesome-copilot 实战验证【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot本指南以 awesome-copilot 仓库中的 github-actions-ci-cd-best-practices.instructions.md 为核心骨架系统讲解如何用 GitHub Actions 构建健壮、安全、高效的 CI/CD 流水线。文中每一个原则都会结合本仓库.github/workflows/下真实运行的工作流进行验证例如 validate-plugins.yml、deploy-website.yml、publish.yml 等读完后你将掌握工作流结构设计、Jobs 编排、Action 版本锁定、密钥管理、OIDC 云认证、缓存与矩阵并行化、分层测试以及多种部署与回滚策略的完整落地方法。一、工作流结构让.github/workflows/*.yml清晰、模块化、可复用1.1 命名与触发器on工作流文件应使用一致、描述性的命名如build-and-test.yml、deploy-prod.yml并完整理解全部触发器类型触发器用途push推送到分支/标签时触发适合构建、单元测试pull_requestPR 创建/更新时触发适合审查与验证workflow_dispatch手动触发可携带inputs参数适合受控部署schedulecron 定时任务适合夜间回归、依赖扫描repository_dispatch外部事件触发适合跨仓库联动workflow_call可复用工作流抽象通用 CI/CD 模式本仓库 codespell.yml 展示了多事件触发与最小权限的组合同时监听push和pull_request均限main分支仅声明permissions: contents: read。而 validate-plugins.yml 则演示了更精细的paths过滤——只有在plugins/**、extensions/**、eng/validate-plugins.mjs或工作流自身变更时才触发验证避免无关改动白白消耗 CI 资源on: pull_request: branches: [main] paths: - plugins/** - extensions/** - eng/validate-plugins.mjs - .github/workflows/validate-plugins.yml1.2 并发控制concurrency用concurrency防止同一分支或分组同时运行、避免资源竞争与浪费。注意cancel-in-progress的两种语义对发布类工作流通常不要取消进行中的运行让生产部署跑完对验证类工作流则可设为true快速取消旧任务。仓库中两种用法并存deploy-website.ymlPages 部署group: pages、cancel-in-progress: false注释明确说明“不希望取消进行中的生产部署”publish.yml发布 distribution 分支cancel-in-progress: true因为同一时刻只需一个发布任务。1.3 权限声明安全默认值在工作流级定义permissions作为安全默认必要时在作业级覆盖。这是最小权限原则的第一道闸门详见第三章第 3 节。1.4 Pro Tip用workflow_call抽象复用对多仓库的复杂项目用可复用工作流workflow_call抽象通用 CI/CD 模式显著减少重复。为手动触发提供workflow_dispatch与inputs是受控部署的推荐姿势。二、Jobs 编排独立的阶段、明确的依赖、高效的数据传递2.1runs-on/needs/outputs/if四大要素runs-onubuntu-latest最常用windows-latest、macos-latest用于跨平台self-hosted用于特殊硬件与内网资源。needs定义作业依赖Job BneedsJob A 时只在 A 成功后运行。outputs跨作业传数据例如构建作业输出 artifact 路径、部署作业消费它实现关注点分离。if条件基于分支名、提交信息、事件类型或前一作业状态做条件执行如if: success()、if: failure()、if: always()。仓库 validate-agentic-workflows-pr.yml 是needs与if的教科书示例compile-workflows作业needs: check-forbidden-files确保先做禁用文件检查随后用if: failure()仅在编译失败时调用marocchino/sticky-pull-request-comment在 PR 上贴出错误说明——失败路径也走完整反馈闭环。2.2 条件部署与输出传递示例继承原文jobs: build: runs-on: ubuntu-latest outputs: artifact_path: ${{ steps.package_app.outputs.path }} steps: - name: Checkout code uses: actions/checkout34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Setup Node.js uses: actions/setup-node3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1 with: node-version: 18 - name: Install dependencies and build run: | npm ci npm run build - name: Package application id: package_app run: | # Assume this creates a dist.zip file zip -r dist.zip dist echo pathdist.zip $GITHUB_OUTPUT - name: Upload build artifact uses: actions/upload-artifactbbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: my-app-build path: dist.zip deploy-staging: runs-on: ubuntu-latest needs: build if: github.ref refs/heads/develop || github.ref refs/heads/main environment: staging steps: - name: Download build artifact uses: actions/download-artifact3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: my-app-build - name: Deploy to Staging run: | unzip dist.zip echo Deploying ${{ needs.build.outputs.artifact_path }} to staging... # Add actual deployment commands here注意${{ needs.build.outputs.artifact_path }}的引用方式以及outputs必须先由上游作业显式声明本例为steps.package_app.outputs.path而echo path... $GITHUB_OUTPUT是写入 job 输出的现代写法。三、Steps 与 Actions原子化步骤 不可变版本锁定3.1 为什么必须锁定完整 Commit SHA这是供应链安全的核心。标签v4和分支main是可变引用——攻击者一旦获得某 Action 仓库的写权限可以静默把标签移动到恶意提交从而在你的工作流中执行任意代码。而Commit SHA 不可变、无法被重定向。规范做法uses: actions/checkout34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1完整 SHA 注释版本号# v4.3.1兼顾安全与可读性永远避免main、latest或裸大版本标签v4这一点对本仓库尤为关键——仓库大量使用第三方 Action 做 PR 评论、质量门禁可逐一核对例如 validate-plugins.yml 中的actions/checkout、actions/setup-node、actions/github-script全部为完整 SHA 引用。3.2 步骤要素name/run/env/withname每个步骤必须有描述性名称日志可读、便于调试run复杂逻辑用多行脚本|组合命令用减少 Docker 层数env在步骤或作业级定义环境变量严禁硬编码敏感数据with显式给 Action 传输入动态值用${{ }}表达式。3.3 使用前的安全审计使用第三方 Action 前要审计优先可信来源actions/官方组织尽可能审查其源码用 Dependabot 跟踪 Action 版本更新绝不使用可变 tag/分支引用这正是前面反复强调的供应链攻击防御要点。四、安全最佳实践从密钥到供应链的七层防线4.1 密钥管理Secret ManagementGitHub Secrets存储敏感信息的主要机制静态加密仅在传入 runner 时解密Environment Secrets按环境隔离可绑定人工审批与分支条件自动掩码GitHub Actions 会自动遮蔽日志中的密钥但依然不要直接打印掩码也可能有遗漏。环境密钥 审批示例继承原文jobs: deploy: runs-on: ubuntu-latest environment: name: production url: https://prod.example.com steps: - name: Deploy to production env: PROD_API_KEY: ${{ secrets.PROD_API_KEY }} run: ./deploy-script.sh4.2 OpenID ConnectOIDC云认证用 OIDC 替代长期静态凭证GitHub 签发 JWT换取云厂商短期临时凭证显著缩小攻击面。核心要素短期凭证临时令牌 短 TTL即使泄露影响面也极小信任策略Trust Policies需在云侧AWS IAM 角色、Azure AD 应用注册、GCP Service Account配置信任 GitHub OIDC issuer联邦身份现代云部署的关键模式。OIDC 的典型体现是 Pages 部署。看 deploy-website.yml 的权限声明——id-token: write正是 OIDC 令牌获取的必需项配合pages: write与官方actions/deploy-pagesAction 完成免密钥部署permissions: contents: read pages: write id-token: write使用云厂商 OIDC Action 时同样必须锁定完整 SHA如aws-actions/configure-aws-credentialsSHA # v4.x.x。4.3GITHUB_TOKEN最小权限默认GITHUB_TOKEN权限过宽应显式收缩。原则全局默认contents: read仅在绝对必要时按需授权。继承原文示例permissions: contents: read # Default is write, explicitly set to read-only for security pull-requests: write # Only if workflow needs to update PRs checks: write # For updating checks jobs: lint: permissions: contents: read # This job only needs to read code, override workflow default steps: - uses: actions/checkout34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - run: npm run lint仓库落实得极为彻底codespell.yml 与 pr-risk-scan.yml 全程仅contents: read而 validate-plugins.yml 因需要在 PR 上写评论/删评论才在contents: read基础上追加pull-requests: write——权限与需求精确对应。4.4 依赖审查与软件成分分析SCA在 CI 早期集成依赖漏洞/许可扫描工具包括dependency-review-action、Snyk、Trivy、Mend。持续维护依赖清单、理解传递依赖并针对新漏洞设置告警是软件供应链安全的关键一环。4.5 静态应用安全测试SASTShift Left尽早发现并修复漏洞成本最低工具CodeQLGitHub Advanced Security、SonarQube、BanditPython、ESLint 安全插件JS/TS自动强制发现关键漏洞时使构建失败或拦截 PR形成“安全默认”姿态还可将安全 linter 加入 pre-commit 钩子提供更早反馈。4.6 密钥扫描与凭据泄漏防护开启 GitHub 内置 Secret Scanning用git-secrets等 pre-commit 钩子阻止密钥本地入库密钥只在运行期传入所需环境绝不进入构建产物即便有掩码也要复盘工作流日志排查意外暴露。4.7 不可变基础设施与镜像签名可复现构建相同代码必须产出完全一致的镜像镜像签名用 Notary 或 Cosign 对镜像做密码学签名验证来源与完整性部署门禁只允许签名镜像进入生产环境并在部署阶段做校验。五、优化与性能缓存、矩阵、自托管与快速检出5.1 依赖与构建缓存设计高命中率的缓存键是提速关键缓存键基于文件哈希如hashFiles(**/package-lock.json)、hashFiles(**/requirements.txt)仅在依赖变化时失效restore-keys回退到更旧但兼容的缓存缓存作用域按仓库与分支隔离。继承原文的 Monorepo 高级缓存示例- name: Cache Node.js modules uses: actions/cache668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: | ~/.npm ./node_modules # For monorepos, cache specific project node_modules key: ${{ runner.os }}-node-${{ hashFiles(**/package-lock.json) }}-${{ github.run_id }} restore-keys: | ${{ runner.os }}-node-${{ hashFiles(**/package-lock.json) }}- ${{ runner.os }}-node-提示缓存键越“动态”越容易 miss。想制造“每次全新”的缓存时可在 key 中加入github.run_id如上例但默认场景应让 key 只随依赖变化。5.2 矩阵策略并行化用strategy.matrix在多种配置Node 版本、OS、Python 版本、浏览器类型下并行测试include/exclude精调组合fail-fast: true默认快速失败反馈fail-fast: false跑完全部组合以获得完整测试报告。继承原文的多版本、多 OS 测试矩阵jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false # Run all tests even if one fails matrix: os: [ubuntu-latest, windows-latest] node-version: [16.x, 18.x, 20.x] browser: [chromium, firefox] steps: - uses: actions/checkout34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - uses: actions/setup-node3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1 with: node-version: ${{ matrix.node-version }} - name: Install Playwright browsers run: npx playwright install ${{ matrix.browser }} - name: Run tests run: npm test5.3 自托管 Runner适用场景特殊硬件GPU、访问私有网络资源、超大构建缓存、GitHub 托管 runner 成本过高时。注意这是双刃剑必须自行加固 runner 机器、管理访问控制、及时打补丁、限制网络访问用runner groups分组管理规划伸缩策略手动或自动扩缩容。5.4 快速检出与浅克隆fetch-depth: 1大多数 CI 构建只需最新提交1足够0拉全量历史大仓库极慢仅发布打 tag、深度提交分析、git blame等场景才需要submodules: false非必需不检出子模块lfs: false不需要大文件时不拉 LFS超大仓库可考虑 Git partial clone--filterblob:none/--filtertree:0。仓库中两处fetch-depth恰好构成对比deploy-website.yml 注释写明fetch-depth: 0 # Full history needed for git-based last updated dates站点“最后更新日期”依赖完整 Git 历史因此必须全量而 publish.yml 同样需要fetch-depth: 0以基于分支历史做发布。反观 pr-risk-scan.yml它要用git diff origin/${{ github.base_ref }}...HEAD计算变更文件所以也取了全量历史——这提醒我们fetch-depth: 0的取舍取决于该作业是否真的需要历史/基准比较。5.5 用 Artifacts 做作业间/工作流间通信actions/upload-artifact上传作业产物自动压缩可后续下载actions/download-artifact按名下载全部或指定 artifactretention-days控制存储成本与合规按重要性和监管要求设置用途构建产物、测试报告JUnit XML/HTML、覆盖率报告、安全扫描结果、生成的文档、静态站点构建产物限制artifact 上传后不可变单个可达数 GB但要注意存储成本。仓库实践pr-risk-scan.yml 用actions/upload-artifact上传pr-risk-results/目录并设置retention-days: 1中间产物短留即可deploy-website.yml 则用actions/upload-pages-artifact把./website/dist上传给后续 deploy 作业消费——构建与部署严格分离保证部署的就是刚构建测试的那份产物。六、CI/CD 中的分层测试6.1 单元测试每次push/pull_request都跑速度最快、数量最多强烈推荐并行化接入覆盖率工具Istanbul、Coverage.py、JaCoCo并设最低阈值——追求“有意义的测试”而非纯行覆盖用actions/upload-artifact发布 JUnit XML 报告或接入与 GitHub Checks/Annotations 集成的报告 Action用 mock/stub 隔离被测单元。语言侧框架可选 Jest、Vitest、Pytest、Go testing、JUnit、NUnit、XUnit、RSpec。6.2 集成测试用作业内services拉起临时数据库、消息队列、外部 API 等 Docker 容器依赖得到一致、隔离的环境平衡“mock 外部服务”与“真实轻量实例”——测真实集成点时优先真实实例规划测试数据管理保证可重复、可清理集成测试比单元测试慢可放在 PR 合并而非每次 push 时跑并置于单元测试之后、E2E 之前。本仓库即依赖该模式验证其 Node 生态根 package.json 中plugin:validate、skill:validate等脚本被 validate-plugins.yml 等 CI 作业调用先npm ci安装依赖、再跑npm run plugin:validate属于典型的“依赖就绪后执行校验”的集成化步骤。6.3 端到端E2E测试工具Cypress、Playwright、Selenium最佳实践优先对与生产高度一致的 staging 环境运行而非直接跑在 CI抗抖动显式等待、稳健选择器、失败重试、谨慎的测试数据管理flaky 会侵蚀信任视觉回归可集成 Applitools、Percy失败取证失败时捕获截图与视频便于排障。6.4 性能与压测工具JMeter、k6、Locust、Gatling、Artillery按语言与复杂度选择频率低于单元/集成测试夜间、每周或重大功能合并时阈值定义响应时间、吞吐、错误率阈值超限即构建失败基线对比与历史基线比对检测性能回退在模拟生产负载模式的专用环境运行。6.5 测试报告与可见性用 GitHub Checks/Annotations 在 PR 内联反馈测试成败将 JUnit XML、HTML 报告、覆盖率、录屏、截图上传为 artifact 长期留存对接外部看板SonarQube、Allure、TestRail看聚合趋势在 README 挂工作流状态徽章Status Badges一眼可见 CI/CD 健康度。七、高级部署策略7.1 Staging 环境部署尽量在基础设施、数据、配置、安全上镜像生产通过环境保护规则防误部署、强制人工审批、限制可部署分支合并到develop、release/*等分支后自动部署 staging并做冒烟测试与部署后校验定期从生产刷新必要时脱敏staging 数据保证测试场景真实。7.2 生产环境部署多层人工审批、安全签字、变更管理流程——GitHub Environments 原生支持 required reviewers回滚能力快速可靠地恢复到上一稳定版本部署期可观测性部署期间与刚部署后密切监控看板、告警、链路追踪渐进式发布蓝绿、金丝雀、暗启动紧急部署通道为 hotfix 提供绕过非必要审批但保留安全校验的快速管线。7.3 部署类型选型继承原文类型机制适用与要点Rolling Update逐步替换旧实例大多数场景、无状态应用用maxSurge超出期望副本数的新实例上限与maxUnavailable可下线旧实例上限控制发布速度与可用性Blue/Green新旧环境并行流量整体切换零停机、秒级回滚需双环境 流量路由LB/Ingress/DNSCanary先向 5%–10% 用户放量受控爆炸半径用 Service MeshIstio/Linkerd或支持流量切分的 Ingress 做指标分析Dark Launch / Feature Flags代码已上线、功能按用户切换解耦“部署”与“发布”用 LaunchDarkly、Split.io、UnleashA/B Testing多版本并行给不同用户段基于用户行为与业务指标对比常配合 feature flags 与分析平台7.4 回滚策略与事故响应自动化回滚基于监控告警错误率突增、高延迟或部署后健康检查失败自动触发版本化产物保留可立即部署的历史构建产物、镜像与基础设施状态Runbook编写清晰、可执行的回滚手册并定期演练缩短 MTTR无指责复盘PIR分析根因、沉淀教训、落实预防措施沟通计划事故与回滚期间向利益相关方同步。八、工作流评审检查清单可直接用于 PR Review结构与设计name清晰、描述性、唯一on触发器匹配用途push/pull_request/workflow_dispatch/schedulepath/branch 过滤是否有效关键工作流或共享资源设置了concurrency全局permissions最小权限默认contents: read作业级按需覆盖通用模式用workflow_call复用减少重复作业与步骤命名有意义组织逻辑清晰。作业与步骤作业代表独立阶段build/lint/test/deployneeds依赖关系正确outputs高效用于跨作业通信if条件用于环境化部署、分支化操作所有uses锁定完整 SHA 并带版本注释可变 tag 会被静默重定向到恶意提交run命令高效组合、清理临时文件、多行脚本格式规范env作用域恰当且不硬编码敏感数据长任务设置timeout-minutes防止挂起。安全敏感数据只经secrets上下文访问绝不硬编码、绝不打日志云认证优先 OIDC消除长期凭证GITHUB_TOKEN权限显式收缩contents: read基线集成 SCAdependency-review-action、Snyk集成 SASTCodeQL、SonarQube关键漏洞阻断构建开启 Secret Scanning建议本地 pre-commit 钩子容器镜像签名Notary/Cosign并在部署中校验自托管 runner 遵循加固规范、限制网络访问。性能优化actions/cache覆盖依赖与构建产物缓存key/restore-keys用hashFiles设计以最大化命中strategy.matrix并行测试/构建多环境、多版本非必要不取全量历史fetch-depth: 1用 artifact 传数据而非重复构建/拉取大文件用 Git LFS 管理并优化检出。测试策略单元测试有专用作业且置于管线早期集成测试用services提供依赖在单元测试之后运行E2E 测试优先打 staging具备抗抖动措施关键应用接入性能/压测并设阈值测试报告收集、上传为 artifact、接入 Checks/Annotations覆盖率跟踪并设最低阈值。部署与可靠性staging/production 用 GitHubenvironment规则审批、reviewers、分支限制敏感生产部署配置人工审批回滚策略清晰且可自动化kubectl rollout undo、恢复上一稳定镜像部署类型与关键度/风险容忍度匹配实现部署后健康检查与冒烟测试对临时故障有重试机制如网络抖动。可观测性日志足够排查失败应用日志走 STDOUT/STDERR采集并暴露应用/基础设施指标如 Prometheus对关键工作流失败、部署问题、生产异常配置告警微服务接入分布式追踪OpenTelemetry、Jaegerartifactretention-days按需配置以管理存储与合规。九、常见问题排查Deep Dive9.1 工作流不触发或步骤意外跳过核对触发器on块与期望事件精确匹配branches/tags/paths过滤正确注意paths-ignore和branches-ignore优先级更高workflow_dispatch需工作流文件在默认分支且inputs正确检查if条件逐级审查 workflow/job/step 的if用always()加调试步骤打印${{ toJson(github) }}、${{ toJson(job) }}、${{ toJson(steps) }}观察求值状态检查concurrency查看运行记录的 Concurrency 页签确认是否有旧运行阻塞同组新运行分支保护确认无规则阻止工作流运行或要求未通过的 check。9.2 权限错误Resource not accessible by integration/Permission deniedGITHUB_TOKEN权限审查 workflow/job 两级permissions全局默认contents: read仅按需授予写权限如pull-requests: write、packages: write密钥访问确认密钥配置在仓库/组织/环境三级中正确位置使用环境密钥时作业必须能访问该环境、无未决审批secrets.MY_API_KEY名称精确匹配OIDC 配置复查云侧信任策略AWS IAM 角色、Azure AD 应用注册、GCP Service Account是否正确信任 GitHub OIDC issuer且角色具备目标资源所需权限。9.3 缓存问题Cache not found/Cache miss/Cache creation failed验证缓存键key/restore-keys只在依赖真实变化时改变过动态的 key 必然 miss用restore-keys提供回退检查path保存与恢复的路径与依赖安装/产物生成目录严格一致缓存前确认路径存在调试缓存用actions/cache/restore加lookup-only: true观察尝试的 key 与 miss 原因查看日志中Cache hit/Cache miss与关联 key体积与限制留意仓库缓存大小上限过大缓存会被频繁驱逐。9.4 长耗时工作流或超时剖析耗时用运行汇总定位最长作业/步骤优化步骤合并run命令减少 Docker 层用完即清理临时文件只装必要依赖充分利用缓存为所有显著依赖与构建产物配置actions/cache矩阵并行把测试/构建拆成可并行单元用strategy.matrix并发跑升级 runner重任务考虑更大规格托管 runner 或自托管拆分工作流复杂长流程拆成互相触发的小工作流或复用workflow_call。9.5 CI 中 flaky 测试本地过、CI 挂测试隔离每个测试独立清理资源如数据库记录消除竞态集成/E2E 用显式等待元素可见、API 响应替代任意sleep外部服务操作加重试环境一致性CI 与本地对齐 Node/Python/数据库版本用 Dockerservices保证一致依赖稳健选择器E2E 用data-testid而非脆弱的 CSS/XPath失败取证配置失败时截图与录屏隔离复现对持续 flaky 的用例单独反复运行定位非确定性根因。9.6 部署后应用不可用日志复盘查kubectl logs、应用日志、服务器日志中的错误与告警配置校验核对注入的环境变量、ConfigMap、Secret 是否与目标环境匹配部署前做配置预检依赖检查确认运行期依赖库、框架、外部服务已正确打入镜像或安装部署后健康检查部署后立即做自动化冒烟测试与健康检查失败即触发回滚网络连通性检查新环境内组件间连通防火墙、安全组、K8s NetworkPolicy立即回滚生产部署失败或劣化时立刻回滚恢复服务再到非生产环境排查。十、结语把最佳实践落到本仓库的每一行 YAMLGitHub Actions 是自动化的强大平台但安全与效率不会自动发生。本文档从工作流结构、Jobs 编排、Action 版本锁定到密钥管理、OIDC、最小权限、SCA/SAST、缓存与矩阵并行、分层测试、渐进式部署与回滚构成了完整的工程化闭环。本仓库本身就是最佳实践的“活标本”validate-plugins.yml 与 codespell.yml 示范了paths过滤与contents: read最小权限deploy-website.yml 示范了 OIDCid-token: write、concurrency与 artifact 化的构建/部署分离publish.yml 示范了并发取消、跨分支发布与gh workflow run联动validate-agentic-workflows-pr.yml 示范了needs、if: failure()与 PR 反馈闭环。在编写或评审任何 CI/CD 工作流时都可以对照第八节检查清单逐项自检——持续度量、持续加固才能实现更快、更安全、更自信的发布。【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表