切换主题
项目模板与内容模板
平台提供两类「模板」,用途不同、互不影响:
- 项目模板(
project_template)是一整套项目配置骨架,把「代码获取 / 构建 / 部署 / 任务变量」沉淀下来,新建项目时一键套用,避免重复配置。 - 内容模板(
content_template)是一段可复用的文件内容(如停部启脚本),部署时由执行端在目标机生成指定文件,供多个项目的部署阶段引用。
两者都不落明文凭据:项目模板对所有登录用户共享,因此写入时会剔除敏感值;内容模板本身只描述文件内容,不含凭据。
项目模板
定位与用途
项目模板与项目共用同一套 ProjectConfig 结构(git / build / deploy / env),但不参与执行、不归属分组、不产生任务。它有两条使用路径:
- 项目 → 模板:项目列表「另存为模板」,把已有项目配置整体复制成模板(凭据剔除)。
- 模板 → 项目:项目表单「套用模板」,把模板配置一次性填充到当前表单。保存后项目与模板再无运行期关联——模板后续修改不会影响已建项目(不引入继承,也不做版本 / 差异计算)。
- 模板 → 新建项目页:标记为「快速开始」(
quick_start=1)的模板出现在「新建项目」页的快速开始区域,登录用户点击即一键填充。
数据结构
模板实体返回的主要字段:
| 字段 | 说明 |
|---|---|
id | 主键 |
name | 模板名称(唯一,重复插入返回冲突) |
quickStart | 是否出现在「新建项目」页的快速开始区域 |
sortNo | 快速开始列表中的升序排序值(数值小的在前) |
config | 模板配置 JSON,结构同 project.config(git / build / deploy / env 等;写入前剔除凭据) |
createdAt / updatedAt | 创建 / 更新时间 |
内置「快速开始」模板
backend/mysql_schema.sql 与 backend/mysql_upgrade.sql 会写入 4 条 INSERT IGNORE 内置模板(同名已存在则跳过,可重复执行)。它们都是与语言无关的通用骨架,不绑定任何环境资源 / 镜像仓库 / 目标服务器(依赖具体环境,套用后自行选择):
| 名称 | sort_no | 构建步骤 | 部署条目(要点) |
|---|---|---|---|
| Go 服务 | 10 | 单步骤 go:go build -o bin/app ./cmd/app,产物 bin/* | 覆盖部署 bin/app → app |
| Vue / Node 前端 | 20 | 单步骤 frontend:npm ci + npm run build,dist 目录 | dist/ 直接落地、清空后放入 |
| Java 服务 | 30 | 单步骤 java:clean package -DskipTests,产物 target/*.jar | 覆盖部署为 app.jar |
| Docker 镜像 | 40 | 单步骤 docker:Dockerfile,imageTag=latest,交付方式 tar | 无部署条目 |
内置模板由 admin 修改、删除或另建。这些模板就是「快速开始」区域的数据来源,取代了早期写死在前端的常量预设。
管理:列表、详情、新建 / 编辑与排序
模板列表(/templates,所有登录用户可访问):支持按名称关键字模糊搜索 + 分页(每页 20 / 50 / 100),列为 ID / 模板名称 / 快速开始(开启时显示标记与 sortNo)/ 构建步骤 / 代码 / 更新时间。列表按 id 倒序(新建在前)。
模板详情(/templates/:id,所有登录用户只读):头部展示名称、快速开始状态与创建 / 更新时间;随后以只读方式逐项展示配置(代码获取 / 构建 / 部署 / 任务变量),其中密码位置标注「模板不保存凭据」,敏感任务变量值显示为 ••••••。admin 额外显示「编辑模板」入口。
模板表单(/templates/new、/templates/:id/edit,仅 admin):
- 模板名称:必填。
- 快速开始:开关;开启后出现「排序」输入(
sortNo,非快速开始时保存为 0,建议排序值留间隔如 10 / 20 / 30 便于以后插入)。 - Tab 为代码获取 / 构建 / 部署 / 任务变量,复用项目配置的组件。没有 Webhook Tab;且「代码获取」关闭了「测试连接」——Git 连通性测试依赖已保存的项目与执行机,模板不具备该条件。
操作权限与提示:新建 / 编辑 / 删除仅 admin;删除时提示「已套用该模板的项目不受影响」(模板为一次性套用)。
排序口径:模板管理列表按 id DESC;快速开始查询(quickStart=1)按 ORDER BY sort_no, id 升序返回。
数据权限与凭据处理
模板全局共享:登录用户即可读取(GET /api/templates、GET /api/templates/:id 挂在 authed 路由,不进入 sys_user_scope 数据权限体系);新建 / 修改 / 删除仅 admin。
因为所有登录用户都能读到模板内容,写入模板时统一调用 sanitizeTemplateConfig 剔除敏感值:
| 字段 | 处理 |
|---|---|
git.password | 清空 |
webhook.secret | 清空 |
webhook.key | 清空(Webhook 端点标识不随模板复用) |
env[].value(仅 masked=true 的条目) | 清空,只保留 masked 标记 |
套用模板后这些字段为空,需在项目上补充(Git 密码、掩码变量值);模板表单、模板详情页与项目表单均有对应提示。
「另存为模板」(项目列表操作列,仅 admin):读取项目配置 → normalizeProjectConfig 归一化历史缺省项 → sanitizeTemplateConfig 剔除凭据 → 落库。名称留空取项目名(项目名也为空时用「项目模板」),重名自动加「-模板」后缀(-模板2、-模板3…)。结果模板默认 quick_start=0。
「套用模板」(项目表单页头按钮):选择模板后一次性填充各 Tab,模板带来的 masked 变量视为未设置;对已保存的项目会先确认「将覆盖当前表单配置,保存后才会生效」。
快速开始(新建项目页)
「新建项目」页的快速开始区域由数据库模板驱动:
- 数据来源:
GET /api/templates?quickStart=1&limit=200&offset=0,后端按sort_no, id升序返回。登录用户即可读;admin 在模板表单里用「快速开始」开关 + 「排序」维护。 - 卡片摘要:由模板配置实时归纳,展示构建步骤类型、以及模板显式绑定的环境资源 / 镜像仓库 / 目标服务器名称(取不到时回退
#ID);无构建步骤时显示「不构建(直接上传仓库文件)」。 - 一键填充:套用整份配置后跳到「构建」Tab(无构建步骤时跳「代码获取」),并提示核对仓库地址与凭据。
- 边界:模板绑定的目标服务器(可能有多个)中,不在当前项目分组可选范围内的会在填充后逐个移除并提示(分组限制见 项目与项目组)。
内容模板
定位
内容模板定义一个文件的内容(如应用停部启脚本、构建辅助脚本),部署时由执行端在部署阶段最前面在目标机上生成该文件——位于备份与「部署前(停止)」步骤之前,因此停止脚本本身也可以由模板生成。
模板按项目分组授权:一个模板可分配给多个分组,项目只能选用已分配给其所属分组的模板(保存项目时校验)。
数据结构与分组
content_template(模板本体)
| 字段 | 说明 |
|---|---|
id | 主键 |
name | 模板名称(唯一) |
description | 说明(可选) |
fileName | 生成的文件名(如 app.sh;不能包含路径分隔符,也不能是 . / ..) |
dir | 目标目录,留空表示项目远端目录(即 {{ .Deploy.RemoteDir }});目录不存在时自动创建。本身也支持模板语法 |
content | 文件内容,用 Go 模板语法渲染(见 模板语法) |
permissions | 文件权限,八进制 3~4 位(如 0755),留空默认 0755 |
owner / group | 属主 / 属组(可选,留空保持执行端默认,填写后由部署用户执行 chown) |
groupIds | 已分配的项目分组 ID 列表 |
createdAt / updatedAt | 创建 / 更新时间 |
content_template_group(分配关系):template_id + group_id 的多对多关系表,主键为两者组合,另在 group_id 上建索引。保存模板时按提交内容先删后插整体重置该模板的分组分配;删除模板时同时清理分配关系。列表读取时用 GROUP_CONCAT 一次性取出分组 ID。
模板语法
内容模板的生成文件名、目标目录、文件内容三处统一用 Go 标准库 text/template 渲染,语法为 {{ ... }}。渲染发生在部署阶段最前面、在目标机生成文件时;渲染失败(语法错误 / 执行错误)会直接令任务失败。
与部署步骤脚本的区别
部署步骤脚本(停止 / 部署 / 启动)用的是 ${...} 部署占位符(如 ${remoteDir}),不是本节的模板语法(见 构建与部署)。内容模板里的 ${VAR} / $VAR 一律原样写入文件,交由 shell 在执行该脚本时展开,两者不要混用。
可用数据
| 写法 | 含义 |
|---|---|
{{ .Project.ID }} / {{ .Project.Name }} | 项目 ID / 名称 |
{{ .Vars.KEY }} | 远端环境变量(资源 → 分组 → 项目 → 触发临时变量合并后的结果);内置变量可省略 CICD_ 前缀,如 {{ .Vars.BRANCH }} 等价于 {{ .Vars.CICD_BRANCH }} |
{{ .Deploy.RemoteDir }} | 项目远端部署目录(等价于旧 ${remoteDir}) |
{{ .Deploy.LogDir }} | 日志目录(等价于旧 ${logDir}) |
{{ .Deploy.Image }} | Docker 镜像引用(等价于旧 ${image}) |
{{ .Deploy.Container }} | 容器名(由镜像推导,等价于旧 ${container}) |
{{ .Deploy.TempDir }} | 目标机临时目录(本次任务为 {tempDir}/cicd-{taskId}) |
{{ .RemoteFile }} / {{ .RemoteFileAbs }} | 主条目(第一个文件类产物)的文件名 / 落地绝对路径(等价于旧 ${remoteFile} / ${remoteFileAbs}) |
{{ range .Env }} | 全部远端环境变量,元素含 {{ .Key }} / {{ .Value }},按变量名排序 |
{{ range .Targets }} | 本次部署目标,元素含 .Name / .Mode / .NodeName / .Host / .Port / .Username / .DeployUser(不含任何凭据) |
{{ range .Artifacts }} | 启用中的部署条目,元素含 .Name / .Source / .Type / .TargetDir / .TargetName / .Put / .Mode / .Permissions / .Owner / .Group |
控制结构与函数
| 写法 | 说明 |
|---|---|
{{ if 条件 }}…{{ else if 条件 }}…{{ else }}…{{ end }} | 条件分支;条件可用 eq / ne / lt / gt / and / or / not 等 |
{{ range 集合 }}…{{ end }} | 遍历 .Env / .Targets / .Artifacts,循环内 . 为当前元素 |
{{ default "兜底值" 值 }} | 值为空(空串 / 0 / false / 空集合)时取兜底值,否则取原值 |
{{ contains 值 子串 }} | 是否包含子串 |
{{ hasPrefix 值 前缀 }} / {{ hasSuffix 值 后缀 }} | 是否以某前缀 / 后缀开头结尾 |
{{ upper 值 }} / {{ lower 值 }} / {{ trim 值 }} | 转大写 / 转小写 / 去首尾空白 |
{{"{{"}} | 需要写入字面量 {{ 时使用(避免被当作模板动作) |
示例一:Java 应用启停脚本(完整)
生成 app.sh(目录留空即项目远端目录,权限 0755),部署脚本里用 sh app.sh stop / sh app.sh start 调用:
bash
#!/bin/bash
# 用法:sh app.sh {start|stop|restart|status}
# 说明:{{ .RemoteFileAbs }} 是模板语法,生成文件时替换为主条目(jar)绝对路径;
# ${JAR}、$! 等是 shell 变量,模板不处理,原样写入文件交由 shell 展开。
set -u
JAR="{{ .RemoteFileAbs }}"
DIR="$(dirname "${JAR}")"
PID="${DIR}/app.pid"
# JVM 参数:优先用项目变量 JAVA_OPTS,未配置时按环境取默认值
{{ if eq .Vars.APP_ENV "prod" }}
JAVA_OPTS="{{ default "-Xms1g -Xmx2g -Dspring.profiles.active=prod -Dfile.encoding=UTF-8" .Vars.JAVA_OPTS }}"
{{ else }}
JAVA_OPTS="{{ default "-Xms256m -Xmx512m -Dspring.profiles.active=dev -Dfile.encoding=UTF-8" .Vars.JAVA_OPTS }}"
{{ end }}
pid_of() {
[ -f "${PID}" ] || return 1
p="$(cat "${PID}" 2>/dev/null)"
[ -n "${p}" ] || return 1
kill -0 "${p}" 2>/dev/null || return 1
echo "${p}"
}
# 部署步骤是 su - / sudo -u 起的非交互 shell,PATH 里通常没有 java:先查 PATH,再从常见 JDK 目录兜底。
find_java() {
jb="$(command -v java 2>/dev/null || true)"
if [ -z "${jb}" ]; then
for c in /usr/local/*/bin/java /usr/lib/jvm/*/bin/java /opt/*/bin/java /opt/*/*/bin/java; do
[ -x "${c}" ] && { jb="${c}"; break; }
done
fi
echo "${jb}"
}
start() {
if p="$(pid_of)"; then
echo "已在运行:pid=${p}"
return 0
fi
[ -f "${JAR}" ] || { echo "启动失败:找不到 ${JAR}"; return 1; }
jb="$(find_java)"
[ -n "${jb}" ] || { echo "启动失败:找不到 java(PATH=${PATH})"; return 1; }
cd "${DIR}" || return 1
echo "启动 ${JAR}"
nohup "${jb}" ${JAVA_OPTS} -jar "${JAR}" > /dev/null 2>&1 &
echo $! > "${PID}"
sleep 5
if p="$(pid_of)"; then
echo "启动成功:pid=${p}"
else
echo "启动失败"
rm -f "${PID}"
return 1
fi
}
stop() {
if ! p="$(pid_of)"; then
echo "未在运行"
rm -f "${PID}"
return 0
fi
echo "停止 ${JAR}:pid=${p}"
kill "${p}" 2>/dev/null || true
for _ in $(seq 1 30); do
kill -0 "${p}" 2>/dev/null || break
sleep 1
done
if kill -0 "${p}" 2>/dev/null; then
echo "优雅停止超时,强制结束"
kill -9 "${p}" 2>/dev/null || true
fi
rm -f "${PID}"
echo "已停止"
return 0
}
status() {
if p="$(pid_of)"; then
echo "运行中:pid=${p}"
else
echo "未运行"
fi
}
case "${1:-}" in
start) start ;;
stop) stop ;;
restart) stop && start ;;
status) status ;;
*) echo "用法: $0 {start|stop|restart|status}"; exit 2 ;;
esac1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
配置效果:项目变量里设了 APP_ENV=prod 时,生成的文件固定为生产 JVM 参数;未配置 APP_ENV(取到空串)走 else 分支(开发参数);JAVA_OPTS 变量存在则覆盖对应默认值。
示例二:systemd 单元文件(文件名 / 目录也走模板)
文件名填 {{ .Vars.APP_NAME }}.service、目录填 /etc/systemd/system,内容:
ini
[Unit]
Description={{ .Project.Name }}
After=network.target
[Service]
User={{ default "app" .Vars.DEPLOY_USER }}
WorkingDirectory={{ .Deploy.RemoteDir }}
ExecStart={{ .RemoteFileAbs }}
Restart=on-failure
[Install]
WantedBy=multi-user.target1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
示例三:遍历环境变量生成 env 文件
bash
# {{ .Project.Name }} 运行时环境变量(生成路径:{{ .Deploy.RemoteDir }}/app.env)
APP_ENV={{ .Vars.APP_ENV }}
{{ range .Env -}}
{{ .Key }}={{ .Value }}
{{ end -}}1
2
3
4
5
2
3
4
5
{{ range .Env }}会输出合并后的全部远端变量(含内置CICD_*);只想挑个别变量时直接用{{ .Vars.KEY }}即可。模板在部署前渲染,因此生成的文件与本次任务最终生效的变量一致。
在项目配置中引用
项目在部署配置中通过 config.deploy.scripts[] 引用内容模板:
| 字段 | 说明 |
|---|---|
templateId | 引用的内容模板 ID |
fileName | 覆盖模板默认文件名(留空沿用模板) |
dir | 覆盖模板默认目录(留空沿用模板) |
permissions | 覆盖模板默认权限(留空沿用模板) |
owner / group | 覆盖模板默认属主 / 属组(留空沿用模板) |
引用约束:
- 只有当项目启用部署(
deploy.enabled非 false)时才校验;关闭部署时允许保留历史引用。 - 所选模板必须已分配给项目所属分组,否则保存项目返回 400(提示「内容模板「X」未分配给项目分组「Y」」)。
- 项目表单中,内容模板下拉只列出已分配给当前分组的模板;切换分组时,不在新分组可选范围内的脚本引用会被清空并提示。
- 模板正文由后端在生成任务执行计划时读取,项目里只保存引用与覆盖项。
管理与校验
内容模板列表(/content-templates,登录即可读):按名称关键字搜索,可按项目分组筛选(后端 groupId 参数只返回分配给该分组的模板);列为 ID / 模板名称(含说明)/ 生成文件(文件名 + 目录,空目录显示为「(项目远端目录)」)/ 权限·属主 / 分配分组 / 更新时间;未分配分组显示「未分配(项目无法选用)」。
新建 / 编辑(/content-templates/new、/content-templates/:id/edit,仅 admin)字段:模板名称、说明、生成文件名、目标目录、权限、属主 / 属组、分配分组(多选)、文件内容。
详情(/content-templates/:id,登录即可读):列表中的模板名称与行内「详情」按钮进入;展示全部字段(含分配分组、创建 / 更新时间)与完整文件内容,并提供「预览」「版本」入口,admin 另有「编辑模板」。
校验规则(后端 contentTemplateRequest.build):
- 模板名称、生成文件名必填;文件名不得含
/、\,不得为./..。 - 权限留空取
0755,格式必须匹配八进制 3~4 位(^[0-7]{3,4}$)。 - 分配的分组必须存在,否则返回 400。
删除:仅 admin。内容模板被项目按 ID 引用,删除后已选用该模板的项目在下次部署时会因模板不存在而失败(列表删除确认框会提示该风险)。
配置复制
项目列表的「复制」(POST /api/projects/:id/copy,仅 admin)会新建一个配置副本,行为与范围:
- 名称:在原名后加
-副本,重名继续加序号(-副本2、-副本3…)。 - 范围:配置整体复制(
git/build/deploy/env),含凭据密文(沿用原项目的加密值);分组、启用状态一并沿用。 - Webhook:
config.webhook.key清空并由服务端重新生成端点标识,避免副本与原项目共用同一入口。 - 复制走与导入相同的落库逻辑(引用校验、名称去重)。
复制 ≠ 另存为模板
「复制」产出的是新项目(保留凭据、归属原分组);「另存为模板」产出的是全局共享的模板(剔除凭据、不归属分组)。前者见本节,后者见「项目模板」。
导入导出
项目模板与内容模板各自提供基于 JSON 文件的导入导出(均为 admin),与项目导入导出共用 decodeExport 格式校验、importReport 结果结构、uniqueName 去重逻辑。
导出结构
导出为 JSON 信封(前端以 Blob 下载,文件名形如 templates-all-20260925-143012.json)。三类文件的信封与记录字段:
| 类型 | kind | 记录字段 | 接口 |
|---|---|---|---|
| 项目 | cicd-projects | projects | POST /api/projects/export |
| 项目模板 | cicd-templates | templates | POST /api/templates/export |
| 内容模板 | cicd-content-templates | contentTemplates | POST /api/content-templates/export |
version 当前为 1(exportVersion)。ids 为空表示导出全部,非空表示导出选中。
项目模板导出条目(refs 携带引用名称,供导入端重新匹配;quickStart / sortNo 仅在为真 / 非零时序列化):
json
{
"kind": "cicd-templates",
"version": 1,
"exportedAt": "2026-09-25 14:30:12",
"templates": [
{
"name": "Go 服务",
"quickStart": true,
"sortNo": 10,
"config": { "git": {}, "build": {}, "deploy": {}, "env": [] },
"refs": {
"jdkName": "", "goName": "Go 1.22", "nodeName": "",
"dockerName": "", "registryName": "", "targetNames": ["web-01"]
}
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
内容模板导出条目(分组以名称携带):
json
{
"kind": "cicd-content-templates",
"version": 1,
"exportedAt": "2026-09-25 14:30:12",
"contentTemplates": [
{
"name": "应用启停脚本",
"description": "Spring Boot 启停",
"fileName": "app.sh",
"dir": "",
"content": "#!/bin/bash\nset -e\n...",
"permissions": "0755",
"owner": "",
"group": "",
"groupNames": ["默认分组"]
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
导入校验
decodeExport 的校验顺序为:体积 → JSON 合法性 → (信封)kind / version → 裸数组类型指纹 → 条目结构。
- 体积:上限 16 MB(
exportMaxBytes),超限直接报「导入文件过大」,不做截断解析(Content-Length与实际读取字节双重判定,兼容 chunked 请求)。 - 信封:
kind必填且必须与目标类型匹配;version必填、且不得高于当前支持版本,否则提示「请先升级服务端」;对应记录字段为空视为无可导入记录。 - 裸数组(兼容手工拼装):做类型指纹校验,防止不同类型的文件互相误导入——项目条目必须含
enabled;项目模板条目必须不含enabled;内容模板条目必须含fileName且不含enabled。 - 条目结构:模板走
normalizeProjectConfig归一化(步骤类型合法、名称 / 子目录去空白、枚举补默认值、步骤允许为空);内容模板走contentTemplateRequest.build校验(名称 / 文件名非空、文件名不含路径分隔符、权限格式)。
引用匹配与冲突处理
导入时内部引用按名称在本环境重新匹配:
- 项目模板:环境资源 / 镜像仓库 / 目标服务器按
refs中的名称查找并绑定本环境 ID;名称未命中时回退仍存在的原 ID;仍不存在则置空并写入告警。 - 内容模板:
groupNames中的分组按名称匹配本环境分组,未命中的分组跳过并告警。 - 重名:模板导入重名自动加
-导入后缀(-导入2、-导入3…),同批次连续生成也不会冲突。 - 不阻断整批:单条记录配置不完整或引用未命中只跳过该条 / 置空该字段并记入告警,其余记录继续导入。返回结构为
{ created, total, warnings },前端逐条展示。
凭据处理与引用保护
- 项目模板、内容模板都不含凭据,导出文件可直接跨环境流转。为防手工构造的文件把凭据写进全局共享的模板,模板导入时还会再调用一次
sanitizeTemplateConfig(git.password、webhook.secret、webhook.key、掩码任务变量值均被清空)。导入模板时quickStart/sortNo原样写入。 - 项目导出含凭据密文(
config.git.password、config.webhook.key、掩码任务变量值等原样导出,不脱敏),仅可在使用相同auth.encryptKey的环境之间流转;跨密钥环境导入后凭据不可用,需重新填写。前端在下载前会弹确认框提示该风险。 - 引用保护:内部引用一律以名称随文件携带,导入时按名称在本环境重新匹配、并对仍存在的原 ID 做回退,避免因环境间 ID 不一致导致错误绑定;名称与 ID 都解析不到时置空并告警,不静默指向错误对象。
项目模板以
quick_start=1标记出现在新建项目页;导入的文件会原样还原该标记与排序值,导入后仍可在模板里调整。
升级说明
全新安装执行 backend/mysql_schema.sql 即包含全部相关表与内置数据;已有环境需手动执行增量脚本(程序不会自动执行 DDL):
bash
mysql -h<host> -P<port> -u<user> -p <database> < backend/mysql_upgrade.sql1
脚本中与本页相关的语句:
- 20260927 项目模板:
CREATE TABLE IF NOT EXISTS project_template (...)(纯新增表,无需数据迁移)。 - 20260927 快速开始:
ALTER TABLE project_template ADD COLUMN quick_start TINYINT(1) NOT NULL DEFAULT 0 ... , ADD COLUMN sort_no INT NOT NULL DEFAULT 0 ...——带默认值,存量模板自动为「不开快速开始」,无需数据迁移。INSERT IGNORE写入 4 条内置快速开始模板(同名已存在则跳过,可重复执行)。
- 20260928 内容模板:
CREATE TABLE IF NOT EXISTS content_template (...)与CREATE TABLE IF NOT EXISTS content_template_group (...)(纯新增表,无需数据迁移)。
不执行增量脚本的后果:
project_template缺失:该表已列入store.requiredTables(启动必查表),缺表时管理端启动即报错并提示缺失的表名与建表命令,服务无法启动。缺quick_start/sort_no两列时模板读取会报错。content_template/content_template_group缺失:不影响管理端启动(未列入启动必查表),但「内容模板」页的列表 / 详情加载会失败,项目保存时的内容模板引用校验也会报错,涉及脚本文件部署的项目无法配置内容模板。