feat(微信订阅): 接入歌曲排期模板并加固部署流程

订阅消息(小程序侧唯一能主动触达用户的通道):
- 新增 server/utils/wechatSubscribeFields.ts:按微信字段类型裁剪值(thing≤20、
  phrase 必须 5 个汉字、time/date/character_string 各自上限),空白压成一行,
  北京时间 time 由 playTime 的开始时间生成,解析不出退化为当天 00:00。
- wechatSubscribeService 内置 SONG_SELECTED 模板(站长新给的那个模板 ID),
  并转发上述纯函数;notificationService.createSongSelectedNotification 现在会带上
  playDate/playTime/sequence,三个排期接口(schedule / publish / bulk-publish)补传 sequence。
- 新增 test/wechatSubscribeFields.test.mjs(10 例,node --test 全绿)。
- test-subscribe 管理接口支持 vars 覆盖并返回真实发送值,方便站长核对字段名。

部署流程(Gitea Actions 容器里没有 buildx 插件,旧流程在此硬失败):
- 每个 job 各自安装/探测 docker-buildx(v0.19.3,走 gh-proxy 再退直连),
  不把 `buildx version` 当唯一依据;装不上时降级为普通 `docker build` 继续发布,
  不再因为缺插件整条流水线失败。

注意:SONG_SELECTED 的字段名(thing2/date4/time1/character_string3/phrase5)是按常见
命名推断的,需在公众平台后台核对;不一致时用环境变量覆盖,模板 ID 变更后老用户需重新授权。
This commit is contained in:
2026-10-11 00:04:14 +08:00
parent ebf1c3a127
commit 3eff710d5d
31 changed files with 659 additions and 189 deletions
+172 -23
View File
@@ -519,7 +519,63 @@ jobs:
echo "环境准备完成"
cat /etc/os-release 2>/dev/null | head -4 || true
docker --version
docker buildx version || echo "buildx 未安装"
docker buildx version 2>&1 | head -1 || echo "buildx 子命令不可用"
- name: 确保 Docker Buildx 插件可用(缺失则装官方插件,否则回退 docker build)
run: |
set -euxo pipefail
# 症状:`docker buildx create --name xxx` 报
# unknown flag: --name / See 'docker --help'
# 打出的是 docker 的总帮助 → 说明 `buildx` 子命令没落到真插件上,docker CLI 把
# `--name` 当自己的顶层 flag 解析了。这里先探一次并按需装官方插件,不能等 build-image
# 作业才炸(各 job 是独立容器,装了也得在该 job 里再装一次)。
ensure_buildx() {
# 不用 `docker buildx version` 当唯一依据:这种“子命令没落到真插件上”的坏状态里
# 它仍可能返回 0。优先相信插件文件在位,其次才信命令。
if [ -x "${HOME}/.docker/cli-plugins/docker-buildx" ] \
|| [ -x /usr/local/lib/docker/cli-plugins/docker-buildx ] \
|| { docker buildx version >/dev/null 2>&1 && docker buildx create --help 2>/dev/null | grep -q -- '--name'; }; then
echo "[buildx] 插件可用:$(docker buildx version 2>&1 | head -1)"
return 0
fi
echo "[buildx] 未发现可用插件,从官方 release 安装"
ARCH="$(uname -m)"
case "${ARCH}" in
x86_64|amd64) BUILDX_ARCH=amd64 ;;
aarch64|arm64) BUILDX_ARCH=arm64 ;;
*) echo "[buildx] 不支持的架构 ${ARCH},跳过安装"; return 1 ;;
esac
BUILDX_VER="v0.19.3"
BUILDX_BIN="buildx-${BUILDX_VER}.linux-${BUILDX_ARCH}"
BUILDX_DIR="${HOME}/.docker/cli-plugins"
mkdir -p "${BUILDX_DIR}"
if curl -fsSL --retry 3 --connect-timeout 20 -o "${BUILDX_DIR}/docker-buildx" \
"${GH_PROXY_PREFIX}https://github.com/docker/buildx/releases/download/${BUILDX_VER}/${BUILDX_BIN}" \
|| curl -fsSL --retry 3 --connect-timeout 20 -o "${BUILDX_DIR}/docker-buildx" \
"https://github.com/docker/buildx/releases/download/${BUILDX_VER}/${BUILDX_BIN}"; then
chmod +x "${BUILDX_DIR}/docker-buildx"
echo "[buildx] 已安装到 ${BUILDX_DIR}/docker-buildx"
else
rm -f "${BUILDX_DIR}/docker-buildx"
echo "[buildx] 下载失败(GitHub 与 ${GH_PROXY_PREFIX} 都不通)"
fi
if ! docker buildx version >/dev/null 2>&1 && command -v sudo >/dev/null 2>&1; then
echo "[buildx] 当前 docker 不认 ~/.docker/cli-plugins,改写系统插件目录"
sudo mkdir -p /usr/local/lib/docker/cli-plugins
sudo cp -f "${BUILDX_DIR}/docker-buildx" /usr/local/lib/docker/cli-plugins/docker-buildx 2>/dev/null || true
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx 2>/dev/null || true
fi
[ -x "${BUILDX_DIR}/docker-buildx" ] || [ -x /usr/local/lib/docker/cli-plugins/docker-buildx ]
}
if ensure_buildx && docker buildx create --help 2>/dev/null | grep -q -- '--name'; then
echo "[buildx] 自检通过(create 支持 --name)"
echo "BUILDX_INSTALLED=yes" >> "$GITHUB_ENV"
else
echo "[buildx] 自检未通过,镜像构建作业将自动回退 docker build(不阻塞部署)"
echo "BUILDX_INSTALLED=no" >> "$GITHUB_ENV"
fi
- name: 检出代码
run: |
@@ -669,7 +725,61 @@ jobs:
BRANCH="${GITHUB_REF_NAME:-main}"
git clone --depth 1 -b "${BRANCH}" "${REPO_URL}" .
- name: 初始化 Docker Buildx builder (为本作业重新创建)
- name: 确保 Docker Buildx 插件可用(本作业容器内独立安装)
run: |
set -euxo pipefail
# 每个 job 跑在独立容器里,meta-warmup 装的插件这里看不到,必须自己再确保一次。
# 症状回顾:`docker buildx create --name xxx` 报 unknown flag: --name(打出的是 docker
# 总帮助)→ buildx 子命令没落到真插件上。装完仍不可用则不报错,交给后面的 docker build 兜底。
# 判定同 meta-warmup:优先看插件文件是否在位,不把 `docker buildx version` 当唯一依据。
if [ -x "${HOME}/.docker/cli-plugins/docker-buildx" ] \
|| [ -x /usr/local/lib/docker/cli-plugins/docker-buildx ] \
|| { docker buildx version >/dev/null 2>&1 && docker buildx create --help 2>/dev/null | grep -q -- '--name'; }; then
echo "[buildx] 插件已可用:$(docker buildx version 2>&1 | head -1)"
echo "BUILDX_USABLE=yes" >> "$GITHUB_ENV"
exit 0
fi
echo "[buildx] 未发现可用插件,从官方 release 安装"
ARCH="$(uname -m)"
case "${ARCH}" in
x86_64|amd64) BUILDX_ARCH=amd64 ;;
aarch64|arm64) BUILDX_ARCH=arm64 ;;
*) echo "[buildx] 不支持的架构 ${ARCH}"; echo "BUILDX_USABLE=no" >> "$GITHUB_ENV"; exit 0 ;;
esac
BUILDX_VER="v0.19.3"
BUILDX_BIN="buildx-${BUILDX_VER}.linux-${BUILDX_ARCH}"
BUILDX_DIR="${HOME}/.docker/cli-plugins"
mkdir -p "${BUILDX_DIR}"
if curl -fsSL --retry 3 --connect-timeout 20 -o "${BUILDX_DIR}/docker-buildx" \
"${GH_PROXY_PREFIX}https://github.com/docker/buildx/releases/download/${BUILDX_VER}/${BUILDX_BIN}" \
|| curl -fsSL --retry 3 --connect-timeout 20 -o "${BUILDX_DIR}/docker-buildx" \
"https://github.com/docker/buildx/releases/download/${BUILDX_VER}/${BUILDX_BIN}"; then
chmod +x "${BUILDX_DIR}/docker-buildx"
echo "[buildx] 已安装到 ${BUILDX_DIR}/docker-buildx"
else
rm -f "${BUILDX_DIR}/docker-buildx"
echo "[buildx] 下载失败(GitHub 与 ${GH_PROXY_PREFIX} 都不通)"
fi
if ! docker buildx version >/dev/null 2>&1 && command -v sudo >/dev/null 2>&1; then
sudo mkdir -p /usr/local/lib/docker/cli-plugins
sudo cp -f "${BUILDX_DIR}/docker-buildx" /usr/local/lib/docker/cli-plugins/docker-buildx 2>/dev/null || true
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx 2>/dev/null || true
fi
if [ -x "${BUILDX_DIR}/docker-buildx" ] || [ -x /usr/local/lib/docker/cli-plugins/docker-buildx ]; then
if docker buildx create --help 2>/dev/null | grep -q -- '--name'; then
echo "[buildx] 自检通过"
echo "BUILDX_USABLE=yes" >> "$GITHUB_ENV"
else
echo "[buildx] 插件在位但 create 仍不认识 --name,改用 docker build 兜底"
echo "BUILDX_USABLE=no" >> "$GITHUB_ENV"
fi
else
echo "[buildx] 安装未成功,改用 docker build 兜底"
echo "BUILDX_USABLE=no" >> "$GITHUB_ENV"
fi
- name: 初始化 Docker Buildx builder (为本作业重新创建,不可用则回退 docker build)
env:
REGISTRY_USER: ${{ secrets.REGISTRY_USER || github.actor }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN || github.token }}
@@ -708,17 +818,35 @@ jobs:
done
[ "${PULLED}" = "1" ] || { echo "ERROR: 无法拉取 buildkitd 基础镜像"; exit 1; }
printf '%s' "${REGISTRY_TOKEN}" | docker login "${GITEA_HOST}" -u "${REGISTRY_USER}" --password-stdin
# buildx 插件不可用时(`docker buildx create --name` 报 unknown flag: --name,
# 说明子命令没落到真插件上),不再硬失败,转用普通 docker build 继续部署。
if [ "${BUILDX_USABLE:-no}" != "yes" ] \
|| ! docker buildx version >/dev/null 2>&1 \
|| ! docker buildx create --help 2>/dev/null | grep -q -- '--name'; then
echo "[buildx] 本作业不可用,跳过 builder 初始化,将由 docker build 回退构建"
echo "BUILDX_USABLE=no" >> "$GITHUB_ENV"
rm -f "${BKK_CFG}"
exit 0
fi
docker buildx rm "${BUILDER_NAME}" 2>/dev/null || true
docker buildx create \
--name "${BUILDER_NAME}" \
--driver docker-container \
--use \
--config "${BKK_CFG}" \
--buildkitd-flags '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host' \
--driver-opt network=host \
--driver-opt "image=${BUILDKIT_IMAGE}"
if docker buildx create \
--name "${BUILDER_NAME}" \
--driver docker-container \
--use \
--config "${BKK_CFG}" \
--buildkitd-flags '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host' \
--driver-opt network=host \
--driver-opt "image=${BUILDKIT_IMAGE}" \
&& docker buildx inspect --bootstrap; then
echo "[buildx] builder ${BUILDER_NAME} 就绪,使用 buildx 构建(含分层缓存)"
echo "BUILDX_USABLE=yes" >> "$GITHUB_ENV"
else
echo "[buildx] builder 创建或启动失败,回退 docker build(不使用分层缓存)"
echo "BUILDX_USABLE=no" >> "$GITHUB_ENV"
fi
rm -f "${BKK_CFG}"
docker buildx inspect --bootstrap
- name: 复用 meta-warmup 结果(与 Preflight 并发省 ~5min)
run: |
@@ -777,18 +905,39 @@ jobs:
echo "使用 BuildKit 分层缓存加速(默认不写 --no-cache)"
fi
docker buildx build \
--file ./Dockerfile \
--platform linux/amd64 \
--load \
--provenance=false \
"${BUILD_ARGS[@]}" \
--tag "${IMG_FULL}" \
--tag "${IMG_LATEST}" \
--label "org.opencontainers.image.source=${OCS_SOURCE}" \
--label "org.opencontainers.image.revision=${OCS_REVISION}" \
--label "org.opencontainers.image.created=${OCS_CREATED}" \
.
# BUILDX_USABLE 由「初始化 Docker Buildx builder」步骤写入 GITHUB_ENV:
# yes → buildx(分层缓存 + --load 打进本地 daemon)
# no → 普通 docker build 兜底(无分层缓存;依赖 daemon.json 的 registry-mirrors
# 或 Dockerfile 里的 DOCKER_MIRROR build-arg 拉基础镜像)
# 注意:--provenance 只有 buildx 认,回退时必须去掉。
if [ "${BUILDX_USABLE:-yes}" = "yes" ]; then
echo "[build] 使用 docker buildx build"
docker buildx build \
--file ./Dockerfile \
--platform linux/amd64 \
--load \
--provenance=false \
"${BUILD_ARGS[@]}" \
--tag "${IMG_FULL}" \
--tag "${IMG_LATEST}" \
--label "org.opencontainers.image.source=${OCS_SOURCE}" \
--label "org.opencontainers.image.revision=${OCS_REVISION}" \
--label "org.opencontainers.image.created=${OCS_CREATED}" \
.
else
echo "[build] buildx 不可用,回退 docker build(生效的 mirror 由 daemon.json 决定)"
docker info 2>/dev/null | sed -n '/Registry Mirrors/,/^$/p' || true
docker build \
--file ./Dockerfile \
--platform linux/amd64 \
"${BUILD_ARGS[@]}" \
--tag "${IMG_FULL}" \
--tag "${IMG_LATEST}" \
--label "org.opencontainers.image.source=${OCS_SOURCE}" \
--label "org.opencontainers.image.revision=${OCS_REVISION}" \
--label "org.opencontainers.image.created=${OCS_CREATED}" \
.
fi
docker image inspect "${IMG_FULL}" >/dev/null
docker image inspect "${IMG_LATEST}" >/dev/null