新项目:从 goat init 到第一个模型
创建 goatcms-child 项目、配置 .env、运行 herd/dev.goat、创建第一个实体以及最佳实践。
新建项目:从 goat init 到第一个模型
本指南将带你基于 goatcms-child 启动模板创建项目、配置 .env、通过 herd/dev.goat 启动环境,并添加第一个自定义实体。
要求
PATH中的 Goat CLI(goat),- Docker(已启动),
- Git,
- Python 3 和
qtc——仅用于child/*.sh脚本, - Go 1.25+——可选,用于在主机上运行测试。
PostgreSQL、Node.js 和后端的 Go 编译器均在容器中运行。
1. 创建项目
goat init ./my-app \
--template goatcms-child \
--name my-app \
--git-repo https://github.com/acme/my-app.git| 标志 | 含义 |
--template | 内置模板。goatcms-child 是一个小型应用启动模板:登录、管理面板、页面、示例 note 模型、Angular 前端。默认的 goatcms 是一个包含博客、文档和商店的完整服务。 |
--name | 项目名称(字母、数字、.、_、-)。必填。 |
--git-repo | 仓库地址。必填;将被设置为 origin。 |
--default-language、--supported-languages | 应用语言(默认 en 和 pl,en,de)。 |
--terminal-prefix | 终端变量前缀(默认 APP)。 |
目标目录必须为空或不存在。goat init 会复制模板、执行 git init 并添加 origin——不会构建或下载任何内容。
goatcms-child启动模板目前保留原始 Go 模块路径(code.pozoga.eu/spozoga/goatcms.com)和二进制文件名goatcms。项目无需修改即可运行;如果你想更改它们,请在项目变大之前立即在go.mod、--go-prefix中的herd/_model.goat,以及child/中的导入路径中完成修改。
立即创建第一次提交——这样后续每次重新生成时都能在 git diff 中清晰查看。
2. 配置 .env
cd my-app
cp .env.example .env至少填写 GOAT_JWT_SECRET_KEY,例如使用以下命令生成的值:
openssl rand -hex 32最重要的变量:
| 变量 | 示例 | 说明 |
GOAT_DEV_APP_PORT | 8091 | 开发环境中的应用端口。 |
GOAT_HOST | :8091 | 监听地址;应与端口保持一致。 |
GOAT_URL_BASE | http://localhost:8091 | 公开地址——用于邮件中的链接和 SEO。 |
GOAT_DOMAIN | localhost | 环境域名(不含协议和端口)。 |
GOAT_DB_MAIN_HOST / PORT | localhost / 55432 | 开发数据库。每个项目使用不同端口。 |
GOAT_DB_MAIN_NAME / USER / PASS | starter | 所创建 PostgreSQL 容器的凭据。 |
GOAT_JWT_SECRET_KEY | 随机十六进制值 | 必填。更改后会使会话失效。 |
GOAT_DIR_TMP、GOAT_DIR_DATA、GOAT_DIR_DIST、GOAT_DIR_SHARED_TMP | ./data 等 | 工作目录;保留默认值。 |
GOAT_SMTP_* | 空 | 可选。未配置 SMTP 时不会发送邮件。 |
GOAT_AI_OPENAI_API_KEY、GOAT_AI_OPENAI_MODEL | 空、gpt-4o-mini | 可选——在面板中启用 AI 助手。 |
由 Goat 脚本执行的提交和推送所需凭据请保存在 .private.env 中(格式:.example.private.env)。
.env 和 .private.env 绝不能提交到仓库。若同时运行多个项目,请一并修改 GOAT_DEV_APP_PORT、GOAT_HOST、GOAT_URL_BASE 和 GOAT_DB_MAIN_PORT。
更多信息:[环境配置](/doc/zh/environment-configuration)。
3. 通过 herd/dev.goat 启动项目
goat run:script --path herd/dev.goat脚本读取 .env 并启动 child/dev/runtime.goat,其会:
- 执行
re—— 从herd/_modules.goat获取模块到.goat/modules/,并生成goatapp/, - 并行启动三个任务:
- 数据库 —— 在
GOAT_DB_MAIN_PORT上运行 PostgreSQL 17,数据存储在.cache/postgres, - 后端 —— 在 Go 容器中构建二进制文件,等待数据库,执行
db:migrate、child:db:migrate,加载herd/fixture.goat,并启动serve, - 前端 —— 安装依赖并根据
herd/dev/frontends.json启动 Angular 监视器(管理面板、child-app、foundation 元素)。
首次启动需要几分钟(Docker 镜像、npm 依赖、首次 Angular 构建)。请等待监视器完成首次构建后再打开前端。
启动后:
| 地址 | 内容 |
http://localhost:8091/ | 公开页面(SSR),波兰语版本位于 /pl |
http://localhost:8091/app/ | 管理面板 |
http://localhost:8091/child/ | 项目自定义 Angular 应用 |
fixture 中的开发账户:admin / starter-dev-123 和 user / starter-dev-123。将实例提供给他人之前,请先修改密码。
实用说明:
- 数据库会在多次启动之间保留;仅执行缺失的迁移。每次后端启动时都会加载 fixture,因此它们必须是幂等的。
herd/dev.goat仅在启动时生成一次代码。修改模型后,请停止脚本(Ctrl+C)并重新运行。- 如果希望在模型、模板和后端变更后自动重启,请使用 supervisor:
bash child/setup.sh(仅一次),然后执行bash child/dev.sh。Supervisor 使用自己的goat-starter-<port>容器并占用同一端口——不要同时运行两种变体(docker stop goat-starter-55432)。 - “端口已被占用”错误几乎总是意味着另一个项目或另一个开发变体占用了同一端口。
4. 各部分所在位置
herd/_model.goat 模型:实体、角色、模块、仪表盘
herd/_modules.goat 框架模块(goatcore、goatcms)
herd/fixtures/ 由 herd/fixture.goat 加载的示例数据
child/app/ 你的 Go 代码:布局、路由、服务、命令、迁移
child/web/ 你的 Angular 应用(/child/)
child/static/raw/ 你的静态资源(主题 CSS、图片)
goatapp/ 生成的代码——绝不要手动编辑划分原则:由模型决定的内容在 herd/ 中描述;应用特有的内容在 child/ 中编写。goatapp/ 中的所有内容都会在下一次 goat re 时被覆盖。
5. 第一个自定义实体
在 herd/_model.goat 中添加一个分配给用户的任务实体:
entity:add --name=task --label=title --base=base_entity --doc=<<DOCEOF
A task assigned to a team member.
DOCEOF --properties=<<PROPERTIESEOF
def --write=admin,manager --read=admin,manager,user
add --name=title --type=short_text --required --doc="Short task name."
add --name=description --type=short_text
add --name=due_at --type=date_time
add --name=done --type=bool
PROPERTIESEOF --relations=<<RELATIONSEOF
def --onremove
add --name=assignee --to=user --doc="User responsible for the task."
RELATIONSEOF --system=<<ENTITYSYSTEMEOF
module:add --name=crud --property:list:list="title,due_at,done" --property:list:persist="title,description,due_at,done"
module:add --name=crud_cli --property:list:list="title,done" --property:list:persist="title,description,due_at,done"
module:add --name=harness --property:list:list="title,done" --property:list:persist="title,description,due_at,done"
ENTITYSYSTEMEOF在仪表盘导航中添加链接(位于 app:module:add --name=dashboard 的 --navigation 块中):
link:add --label="Tasks" --entity=task模块提供的功能:
crud—— 管理面板/app/中的 API 和表单,crud_cli——crud:task:*命令,包括用于 fixture 的crud:task:persist,harness—— 此实体的 AI 助手工具(如果不希望向 AI 提供数据,请跳过),seo和ssr—— 仅用于公开内容(示例:page实体)。
然后:
goat re # regeneracja goatapp/
git diff # przejrzyj zmiany w herd/ i child/在现有数据库上生成模型迁移,检查 SQL 并重启 herd/dev.goat:
goat run:script --path=@goatcms/herd/db/migration_generate.goat迁移会进入 child/app/migrations/sql/0000_goatmigrations/。检查类型转换、删除数据的操作以及表锁。应用的手写 SQL 请放在 0001_childigrations/ 中。
最后添加示例数据,例如 herd/fixtures/tasks/fixture.goat:
crud:task:persist --title="Prepare release notes" --done=false并在 herd/fixture.goat 中将其挂接:
run:script --path herd/fixtures/tasks/fixture.goat完整模型语法:[数据模型与代码生成](/doc/zh/data-model-and-code-generation)。
最佳实践
模型
- 从模型开始。在
herd/_model.goat中描述实体、字段、关系、权限和列列表,而不是在手写代码中描述。 - 为每个实体和字段添加
--doc。这些描述会进入代码、API 和 AI 助手。 - 在
def --write=... --read=...中显式设置权限。不要依赖默认值。 - 在适用时使用语义类型(
web_slug、seo_description、language、image、block_content)而非通用的short_text——它们可提供验证和合适的控件。 - 将公共字段提取到基类(
entity:base:add),并通过entity:extended继承,例如content→page。 - 在模型中声明唯一性(
--unique或unique:add --fields=lang,slug)——幂等 fixturepersist依赖于此。 - 以小步骤修改模型:一次修改 →
goat re→git diff→ 测试 → 提交。
代码
- 绝不要编辑
goatapp/。如果生成的代码不合适,请修改模型或herd/gen/中的模板。 - 在
child/中编写业务逻辑、自定义路由和服务,并使用完整包路径导入 foundation。 - 提交
herd/和child/;goatapp/是生成的,并被 Git 忽略。
数据库和数据
- 不要更改已应用迁移的名称或内容——请新增迁移。
- 仅通过带有唯一键的
crud:*:persist编写 fixture,以便重复运行时得到相同状态。 - 开发用 fixture(账户、密码)绝不能进入生产环境。
环境
- 一个项目——在
.env中使用一套端口。 - 密钥仅存放于
.env/.private.env,绝不放入仓库。 - 提交前运行测试:
goat run:script --path=herd/test.goat。
后续步骤
- [项目架构](/doc/zh/project-architecture)
- [数据模型与代码生成](/doc/zh/data-model-and-code-generation)
- [数据库与迁移](/doc/zh/database-and-migrations)
- [测试数据与 fixture](/doc/zh/test-data-and-fixtures)
- [harness 模块:AI 助手](/doc/zh/harness-module)