- DevOps
【免费下载链接】concourse
Concourse is a container-based automation system written in Go. It's mostly used for CI/CD.
Concourse 是一个用 Go 编写的、基于容器的自动化系统,主要用于 CI/CD(持续集成与持续交付)。这份贡献指南面向想要为 Concourse 提交代码、参与社区治理,或希望在本地完整跑通"源码构建—运行调试—编写测试—提交 PR"全流程的开发者。读完本文,你将掌握 Concourse 的仓库结构、本地开发环境的搭建方法、web/worker容器与flyCLI 的构建调试手段,以及单元测试、testflight、Elm 测试、Kubernetes 端到端测试等多层测试体系的运行方式。
一、贡献流程与社区治理
Concourse 项目采用开放治理模型(open governance model):项目职责被划分给多个自组织团队,每个团队在仓库的teams/目录下描述自己的职责(governance 仓库,不在本代码库内)。其中:
- maintainers(维护者)团队:负责 issue 与 PR 积压事项以及本仓库内的全部代码;
- core(核心)团队:与 maintainers 协作,通过RFC 流程确保 Concourse 产品与其设计原则保持一致。
参与贡献的第一步(可选)是注册成为 individual contributor,注册后可以获得三项便利:
- 在 Discord 中获得
@contributors角色,可在#dev频道交流; - 获得 Concourse 仓库 issue 与 PR 的 triage(分流整理)权限;
- 获得为你的 PR 重新触发构建的能力。
如果你打算实现新功能,建议先提交 RFC 征求反馈——并非每个改动都需要,但提前征询意见能节省大量返工时间。除了写代码,还有很多其他贡献方式:在 Discussions 和 Discord 中帮助他人、对 RFC 提供反馈、triage Issues、改进文档等。
所有社区互动都必须遵守 CODE_OF_CONDUCT.md,并且所有贡献者都必须在 Linux Foundation 的 EasyCLA 完成注册后才能合并代码。
二、开发环境依赖
要在本地构建、测试和运行 Concourse,需要先安装以下工具:
| 依赖 | 最低版本(原文档要求) |
|---|---|
go | v1.16+ |
git | v2.11+ |
yarn | v4+ |
docker compose | 需支持 compose v2 语法 |
postgresql | 用于本地数据库操作 |
几点基于当前仓库的补充说明:
- 原文档要求 Go v1.16+,但当前仓库 go.mod 头部声明了
go 1.27.1,模块名为github.com/concourse/concourse/v8,实际构建时请使用足够新的 Go 工具链; - 仓库使用 yarn 4 管理前端依赖,package.json 中声明了
"packageManager": "yarn@4.14.1",安装依赖前请确保 yarn 版本为 4.x; - 注意:Concourse 使用 Go module 系统,因此不要把仓库 clone 到
$GOPATH目录下,否则模块解析会出问题。
三、从源码构建并本地运行 Concourse
在仓库根目录依次执行以下命令,即可完成构建并启动本地集群:
$ yarn install $ yarn build $ docker build -t concourse/concourse:local . $ docker compose up -d说明:
yarn build会构建 web UI 静态资源(其内部依次调用build-less、build-elm、build-js,见 package.json 的scripts字段);docker build -t concourse/concourse:local .使用仓库根目录的 Dockerfile 构建本地镜像。该 Dockerfile 明确标注仅用于本地开发,并非官方concourse/concourse镜像的构建方式;它基于concourse/dev基础镜像,先单独下载 Go module(避免每次改动都重下),再编译./cmd/init/init.c(containerd 用的 init 可执行文件)与concourse主二进制,并设置了CONCOURSE_WEB_PUBLIC_DIR=/src/web/public,让本地构建的 web 资源能自动生效;- 启动后 Concourse 运行在 http://localhost:8080。
docker-compose.yml 定义了三个服务:
db:PostgreSQL 数据库,宿主机端口映射为6543:5432,库名concourse、用户dev、密码dev(POSTGRES_HOST_AUTH_METHOD默认trust),并带有健康检查;web:以command: web启动 ATC 与 TSA,暴露8080:8080,挂载./hack/keys作为签名密钥目录,并注入CONCOURSE_ADD_LOCAL_USER: test:test,guest:guest与CONCOURSE_MAIN_TEAM_LOCAL_USER: test等本地用户配置;worker:以command: worker启动,privileged: true,使用CONCOURSE_RUNTIME: containerd运行时,通过 TSA 密钥(CONCOURSE_TSA_PUBLIC_KEY、CONCOURSE_TSA_WORKER_PRIVATE_KEY)向web:2222注册。
密钥文件由 hack/keys/setup 脚本生成,其核心逻辑是调用concourse generate-key生成三类密钥:
concourse generate-key -t rsa -b 1024 -f session_signing_key concourse generate-key -t ssh -b 1024 -f tsa_host_key concourse generate-key -t ssh -b 1024 -f worker_key cp worker_key.pub authorized_worker_keys构建flyCLI 并接入本地实例
$ go install ./fly该命令会把fly可执行文件安装到$GOPATH/bin,请确保该目录在$PATH中。
接着可以跑通一个完整的示例流水线:
1. 登录本地 Concourse(target 命名为dev):
$ fly -t dev login -c http://localhost:8080 -u test -p test(test用户正是由docker-compose.yml中的CONCOURSE_ADD_LOCAL_USER创建的本地用户。)
2. 创建每分钟触发一次 hello world 任务的示例流水线:
$ git clone git@github.com:concourse/examples.git $ fly -t dev set-pipeline -p example -c examples/pipelines/time-triggered.yml(concourse/examples仓库提供了一系列示例流水线,time-triggered.yml是一个按时间触发器执行的 hello world 示例。)
3. 取消暂停该流水线:
$ fly -t dev unpause-pipeline -p example四、仓库结构与模块划分
Concourse 的源码以monorepo形式组织:服务端组件是 Go 代码,web UI 是 Elm 与 Less 代码。顶层目录沿用了早期各组件独立成仓时的"航空管制"风格命名:
| 目录 | 职责说明 |
|---|---|
| atc | Concourse 的"大脑":流水线调度、构建跟踪、资源检查、web UI/API 服务端,是concourse web的一半 |
| fly | flyCLI 的全部命令实现 |
| web | web UI 的 Elm 源码与资源,构建后被嵌入concourse可执行文件并由 ATC 的 web 服务提供 |
| go-concourse | 访问 ATC API 的 Go 客户端库,fly内部使用。原文档明确警告:不要在外部项目里使用它,否则会与你的go.mod纠缠不清 |
| skymarshal | 把 Dex 适配为 ATC 可嵌入的认证组件,同时包含fly与concourse web的认证 flag 定义 |
| tsa | 自研 SSH 服务器,负责安全认证与注册 worker,是concourse web的另一半 |
| worker | concourse worker的库代码:向 TSA 注册、周期性回收容器/卷等 |
| cmd | 胶水代码,把 ATC、TSA、Garden 组装成单个concourseCLI。入口见 cmd/concourse/main.go,其中通过CONCOURSE_前缀把环境变量映射为命令行 flag |
| testflight | 验收测试套件,覆盖流水线与fly功能,针对单个 Concourse 部署运行,可在本地开发机执行 |
| topgun | 覆盖运维级功能与 Concourse 运行时的技术细节,使用 BOSH 与 K8s 部署自己的集群测试后销毁,只能在 CI 中运行 |
| integration | 与topgun类似,但用docker compose拉起 Concourse 集群,可在本地运行 |
五、改动后如何重新构建验证
每次改动后,可以用下面的命令重建并重建web与worker容器:
$ docker compose up --build -d该命令可以在原docker compose up仍在运行的情况下,于另一个终端中执行。
如果改动涉及底层开发镜像本身(例如 Go 版本升级),需要先拉取最新的concourse/dev镜像,让本地容器基于新镜像构建:
$ docker pull concourse/dev $ docker compose up --build -d修改仓库外依赖(以dex为例)
如果修改的依赖不在本仓库内(如dex),需要先在go.mod中通过replace指令指向你的 fork:
# 推送到你 dex fork 的 sample 分支后,拉取该模块修订版并得到版本号 $ go mod download -json github.com/your-user/dex@sample | jq '.Version' go: finding github.com/your-user/dex sample "v0.0.0-20210401171635-fe97ad5e1a6d" # 用该版本更新 go.mod,加入 replace 指令 $ echo 'replace github.com/concourse/dex => github.com/your-user/dex v0.0.0-20210401171635-fe97ad5e1a6d' \ > ./go.mod # 执行常规构建 $ docker compose up --build -d注意:原文档中的示例是把echo ... > ./go.mod直接覆盖文件,实际开发中请用编辑器把 replace 指令追加到 go.mod 的require块之后,避免误删原有内容。
六、Web UI 开发
Concourse 后端是 Go,web UI 则是Elm + Less。改动 web 目录后,执行以下命令重建 web 资源:
$ yarn install $ yarn build本地构建的新资源会自动同步到web容器,无需重启容器——这是因为 Dockerfile 把/src设为 VOLUME,并将CONCOURSE_WEB_PUBLIC_DIR指向挂载目录/src/web/public。
针对 Elm 代码,仓库还提供了几个便捷脚本(见 package.json):
- 静态分析:
yarn analyse,由elm-analyse驱动,报告未使用的 import、变量与潜在优化点;加-s会在localhost:3000起一个便于浏览的服务器,甚至支持部分自动修复; - 格式化:
yarn format,按官方 Elm Style Guide 用elm-format统一代码风格; - 基准测试:
yarn benchmark,在web/elm下编译并运行 benchmarks; - 测试:
yarn test,在web/elm下运行elm-test。
七、本地调试:用dlv附加运行中的实例
Concourse 正在本地运行时,可以附加dlv脚本封装整个流程:
# 跟踪正在运行的 web 实例 $ ./hack/trace web # 跟踪正在运行的 worker 实例 $ ./hack/trace worker脚本的底层原理(见 hack/trace 源码):先用docker exec $container_name pidof concourse拿到容器内进程 PID,再用 hack/dlv/Dockerfile 构建一个内含 dlv 的镜像,最后以--pid=container:$container_name方式运行容器并执行dlv attach $trace_pid。
若想附加 IDE 调试器,可用--listen指定端口,dlv 将以 headless 模式监听该端口:
$ ./hack/trace web --listen 2345 $ ./hack/trace worker --listen 2345随后在 IDE 中配置远程调试参数:
- host:
localhost - port:
2345
以 GoLand 为例:进入Run | Edit Configurations… | + | Go Remote,填入上述参数即可。
八、本地基础设施:Jaeger、Vault、Prometheus 与 Postgres
1. 分布式追踪(Jaeger)
仓库在 hack/overrides/jaeger.yml 提供了 docker-compose override 文件,除 Jaeger 外还一并带上了 Elasticsearch(存储 span)与 Kibana(可视化)。它同时为web/worker注入CONCOURSE_TRACING_SERVICE_NAME与CONCOURSE_TRACING_OTLP_ADDRESS: jaeger:4317环境变量,把 Concourse 的 OTLP 追踪数据导向 Jaeger。启动方式:
$ docker compose \ -f ./docker-compose.yml \ -f ./hack/overrides/jaeger.yml \ up -d运行后可访问 Jaeger UI(localhost:25000映射到容器 16686 端口)查看 trace。追踪相关的后端实现见 tracing 目录(支持 noop、jaeger、otlp、stackdriver、honeycomb 等)。
2. 本地 Vault
Vault 的本地环境准备分两步:
- 确保安装了
certstrap; - 运行
./hack/vault/setup,并按其输出逐步操作。
hack/vault/setup 脚本会依次:用 certstrap 生成 CA 与双向证书(vault-ca、vault、concourse三套)、打印三个后续步骤的提示(用 vault override 启动 compose、导出VAULT_CACERT、运行./hack/vault/init)。
hack/vault/init 脚本则完成初始化与认证配置:执行vault operator init生成 unseal keys 与 root token、unseal 三次、写入concourse策略(hack/vault/config/concourse-policy.hcl)、启用 cert 认证后端并把concourse证书绑定到该策略,最后生成 token 文件。
Vault 服务本身通过 hack/overrides/vault.yml 注入,它给web挂载证书目录并配置CONCOURSE_VAULT_URL、CONCOURSE_VAULT_AUTH_BACKEND: cert、CONCOURSE_VAULT_CA_CERT等环境变量,同时拉起hashicorp/vault容器(端口8200:8200)。更完整的用法可参考 Vault 凭据管理器文档 下的实现。
3. 本地 Prometheus
与 Jaeger 类似,hack/overrides/prometheus.yml 在基础栈上叠加 Prometheus:为web开启CONCOURSE_PROMETHEUS_BIND_IP/CONCOURSE_PROMETHEUS_BIND_PORT(9100 端口)暴露指标端点,并配置 Prometheus 以 5s 间隔抓取web:9100:
$ docker compose \ -f ./docker-compose.yml \ -f ./hack/overrides/prometheus.yml \ up -d之后打开 http://localhost:9090 即可查询并绘制concourse_前缀的 Prometheus 指标。
小贴士:仓库还提供了 hack/dc 脚本,它会自动把
vault、prometheus、jaeger三个 override 与基础docker-compose.yml合并后透传参数,方便一次性带上全部基础设施。
4. 连接本地 Postgres
以docker compose up启动后,可用如下参数连接db节点:
- host:
localhost - port:
6543 - username:
dev - password:(空,
trust认证) - database:
concourse
仓库提供了 hack/db 工具脚本:优先使用pgcli,否则回退到psql:
$ ./hack/db重置数据库则需要先停掉所有服务,删除db容器再启动:
$ docker compose stop $ docker compose rm db $ docker compose start九、数据库迁移(Migrations)
Concourse 的数据库迁移文件位于 atc/db/migration/migrations,使用 Concourse 自研迁移库生成。文件名格式固定为:
<migration_version>_<migration_name>.(up|down).(sql|go)其中迁移版本号是文件创建时刻的 Unix 时间戳,以保证迁移始终按时间顺序执行。打开该目录可以看到真实样例,例如最早的1510262030_initial_schema.up.sql、Go 类型迁移1516643303_update_auth_providers.up.go等。
快捷方式:create-migration脚本
$ ./atc/scripts/create-migration my_migration_name # 或生成 Go 迁移 $ ./atc/scripts/create-migration my_migration_name go该脚本(见 atc/scripts/create-migration)实际执行的是:用go build -tags 'postgres'编译./db/migration/cli为migrate可执行文件,然后调用migrate generate -d ./db/migration/migrations/ -n"$1" -t ${2-"sql"}(默认 SQL 类型)。
手动方式:使用迁移 CLI
- 构建 CLI:
$ cd atc/db/migration $ go build -o mig ./cli- 运行
generate子命令,参数为迁移名称、文件类型(SQL 或 Go),以及可选的输出目录(默认./migrations):
$ ./mig generate -n my_migration_name -t sql该命令会生成两个文件:
1510262030_my_migration_name.down.sql 1510262030_my_migration_name.up.sqlCLI 的底层实现见 atc/db/migration/cli/command/command.go:GenerateCommand提供-d/--directory(默认migrations)、-n/--name、-t/--type三个 flag,类型仅支持sql与go;SQL 迁移生成两个空文件,Go 迁移则基于模板生成含Up_<timestamp>()/Down_<timestamp>()空函数骨架的文件。
生成好 up/down 迁移文件后,在其中填充具体的数据库变更语句。concourse web启动时会扫描atc/db/migration/migrations目录并按顺序执行新迁移。
十、测试你的改动
任何新功能或 bug 修复都应有测试覆盖。没有测试的 PR 几乎不可能被合并,尤其是较大的功能改动。Concourse 的测试体系分为多个层次:
| 测试套件 | 位置/命令 | 说明 |
|---|---|---|
| 单元测试 | foo_test.go与foo.go同目录 | 任何贡献都应尽量编写 |
testflight | testflight | "核心 Concourse"验收测试,覆盖流水线逻辑与fly execute;大多数通过流水线或fly暴露的功能都应在其中加测试 |
| Elm 测试 | web/elm/tests | 隔离测试 web UI 的 Elm 函数,web/elm/src/<module>.elm对应web/elm/tests/<module>Tests.elm,推荐对update与view做较彻底的覆盖 |
web/wats | web/wats | 针对 web UI 的验收测试,像testflight一样跑在真实 Concourse 集群上,仍处于早期阶段 |
topgun | topgun | 更重量级,覆盖对运维更可见的行为,通常不要求 PR 新增该套件 |
安装测试工具链
Concourse 的 Go 测试使用Ginkgo作为框架与套件运行器。按原文档安装 ginkgo CLI:
$ go get github.com/onsi/ginkgo/ginkgo(若使用 v2,则对应go install github.com/onsi/ginkgo/v2/ginkgo。)
单元测试的 fake 由Counterfeiter生成。如果你新增或修改了接口,需要在接口所在目录重新生成 fake:
$ go generate ./...运行单元测试
Concourse 代码量很大,建议只运行你正在改动的组件的测试。在目标包目录内执行:
$ ginkgo -r -p-r:递归运行当前工作目录下所有包;-p:包内所有用例并行执行。
也可以直接把包路径作为参数传入,而无需cd。
注意:直接运行go test ./...会失败——测试目前假设同一时刻只有一个包在跑(这是ginkgo的默认行为),而go test默认并行执行每个包,为测试服务器分配端口之类的用例会互相冲突。
想跑全代码库的单元测试,可执行如下命令(跳过所有集成测试与 linux 专属测试):
ginkgo -r -p -race -flake-attempts=3 \ -skip-package ./integration,testflight,topgun,./worker/integration,./worker/runtime/integration,./worker/baggageclaim如果你在非 Linux 平台,部分测试必须在 Linux 上才能通过,推荐改用 Docker 方式运行(也顺便覆盖 linux 专属测试):
docker run --privileged -v .:/src -it --entrypoint '/bin/bash' concourse/unit进入容器后(代码挂载在/src):
cd /src go install github.com/onsi/ginkgo/v2/ginkgo ginkgo -r -p -race -flake-attempts=3 \ -skip-package ./integration,testflight,topgunElm 测试、分析与格式化
- 测试:在仓库根目录执行
yarn test,或在web/elm目录执行elm-test,整套跑起来很快,可以每次改动都跑一遍; - 静态分析:
yarn analyse; - 格式化:
yarn format。
运行testflight验收测试
testflight针对真实运行的 Concourse。默认目标就是localhost:8080,即docker compose up起来的实例。已经起好集群后,按单元测试同样的方式运行:
$ ginkgo -r -p testflight因为testflight会实际执行真实负载,如果机器核心数超过 8 个,建议限制并行度:
$ ginkgo -r --nodes=4 testflight运行web/watsweb 验收测试
在web/wats目录下执行yarn test,它用 puppeteer 驱动 headless Chromium。一个实用技巧是:测试失败时,失败瞬间的截图会保存在web/wats/failure.png。已知这些测试在默认的并行模式下存在问题(源于上游依赖),可改用yarn test --serial串行执行规避。
运行 Kubernetes 端到端测试
Kubernetes 相关测试都是端到端的,位于topgun/k8s,需要可访问的真实 Kubernetes 集群(通过配置好的~/.kube/config授权)。本地起集群推荐用kind——只需要docker和kindCLI。若希望高并发运行测试,建议配置多个节点,例如:
kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: worker - role: worker - role: worker - role: worker集群起来后,还需要配置 Tiller(这些测试仍使用 Helm 2):
kubectl create serviceaccount \ --namespace kube-system \ tiller kubectl create clusterrolebinding \ tiller-cluster-rule \ --clusterrole=cluster-admin \ --serviceaccount=kube-system:tiller helm init \ --service-account tiller \ --upgrade测试依赖以下环境变量:
CONCOURSE_IMAGE_TAG或CONCOURSE_IMAGE_DIGEST:部署到 k8s 集群时使用的 Concourse 镜像 tag 或 digest;CONCOURSE_IMAGE_NAME:部署使用的镜像名;HELM_CHARTS_DIR:helm/charts 仓库 clone 的路径,用于定义 Concourse 依赖的 postgres chart;CONCOURSE_CHART_DIR:Concourse Helm chart 副本所在路径。
设置完毕后,进入topgun/k8s运行 Ginkgo:
# 串行运行 ginkgo . # 以 16 并发运行 ginkgo -nodes=16 .关于topgun的说明
topgun套件相当重量级,目前不期望大多数贡献者运行或修改它,外部贡献者跑起来也比较困难。要运行topgun,需要有一个正在运行的BOSH director(唯一要求是能访问它创建的实例)。本地搭建可借助 concourse-bosh-release 仓库的 dev 脚本:
# clone concourse-bosh-release 仓库 git clone https://github.com/concourse/concourse-bosh-release cbr && cd $_ # 运行 setup 脚本(需先安装 BOSH CLI) ./dev/vbox/setupsetup会创建 BOSH director(别名为vbox)、上传测试所需的 releases 以及 BOSH Lite stemcell。之后进入对应套件运行测试:
# 获取 concourse 仓库并进入 git clone https://github.com/concourse/concourse concourse && cd $_ # 进入要工作的 topgun 套件目录 cd ./topgun/$SUITE # 运行该套件测试 BOSH_ENVIRONMENT=vbox ginkgo -v .十一、提交代码:commit 规范与 PR 要求
一次理想 commit 的结构
原文档给出了一个示例 commit 的解剖:
i ii iii | | | web: structure: add var declarations Since scripts are run in module mode, they follow the "strict mode" semantics. Variables must be declared prior to being assigned (e.g. - iv cannot have `x = 1` without declaring x (using var, let, or const) concourse/concourse#5131 -------------------------------------------- v Signed-off-by: Aidan Oldershaw <aoldershaw@pivotal.io> -------------- vi各部分含义:
- component changed:被改动的组件名(对应上文"Developing Concourse"中的目录划分,如
web、atc、fly); - structure vs behaviour:标注本次改动属于结构变更还是行为变更;
- 简短、祈使语气的变更描述(如
add var declarations); - 讲述一个完整故事的消息正文——解释为什么做这个改动,而不只是改了什么;
- 关联的 issue 编号(本示例为
concourse/concourse#5131); - sign-off 行(见下文"签署你的工作")。
结构与行为分离(Structure and Behaviour)
理想情况下每个 PR 都应该很小,但代码库庞大,复杂的改动有时无法避免。为了减轻评审负担,社区总结了几个实用做法:
- 聚焦 commit,使每次提交只改动单一组件;
- 把结构变更与行为变更隔离开,并在 commit 标题中明确标注类型——更好的是把同类型 commit 连续成块提交;
- 在 commit 消息中给出清晰的文字论证。改动的动机往往需要翻查大量资料才能弄清,如果不在 commit 消息里写明白,diff 会显得非常晦涩。
提交前的准备工作
在 fork 上开发并推送分支后,提交前请确保:
- 运行
goimports保证代码符合代码风格规范(可选:参考 Go style guide); - 所有改动都有充分的测试覆盖(详见"Testing your changes"一节);
- 所有贡献者都已在 Linux Foundation 的 EasyCLA 注册;
- 相关文档应在另一个关联 PR 中更新——如果你对技术写作没有信心,可以跳过这一步。
完成后就可以提交 pull request。
结语:把贡献落到行动
从安装 Go、yarn、docker compose 等开发依赖,到yarn build && docker compose up拉起一套本地 Concourse;从用hack/trace附加 dlv 断点调试web/worker,到用迁移 CLI 生成数据库迁移;再到用ginkgo运行单元测试、testflight验收测试与 Elm 测试——这条完整的开发链路都沉淀在仓库的 CONTRIBUTING.md 与配套脚本中。无论你想提交一个 bug 修复、一个新功能(记得先考虑 RFC),还是只想改善文档,都可以按本文的路径快速进入状态,并用规范的单组件 commit 与充分的测试覆盖,让 PR 更容易被维护者接受。
- DevOps
【免费下载链接】concourse
Concourse is a container-based automation system written in Go. It's mostly used for CI/CD.
相关推荐
PouchDB 贡献指南:从本地构建、测试、提交 PR 到版本发布的完整开发流程
PouchDB 贡献指南:从本地构建、测试、提交 PR 到版本发布的完整开发流程 本指南以仓库根目录的 CONTRIBUTING.md https://link
数据库数据同步IPython 贡献指南实战:从提 Issue、发 PR 到本地测试与文档构建的完整工作流
IPython 贡献指南实战:从提 Issue、发 PR 到本地测试与文档构建的完整工作流 本指南以 IPython 官方仓库的 CONTRIBUTING.md
开发工具CLIVoid 开源贡献实战指南:从开发者模式调试到本地构建与提交 PR
Void 开源贡献实战指南:从开发者模式调试到本地构建与提交 PR Void 是一款开源的 AI 代码编辑器,其大部分业务代码集中在 src/vs/workbe
代码编辑器开发工具AI Agent人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考