Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1463cf0834 | |||
| 3a8d5e50d0 | |||
| 6778f4be32 | |||
| 778b17a723 |
@@ -813,7 +813,7 @@ jobs:
|
|||||||
IMAGE_TAG="${REGISTRY}/${{ matrix.image_name }}:pr-${GITHUB_SHA}"
|
IMAGE_TAG="${REGISTRY}/${{ matrix.image_name }}:pr-${GITHUB_SHA}"
|
||||||
CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:develop"
|
CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:develop"
|
||||||
|
|
||||||
EXTRA_BUILD_ARGS="APP_VERSION=\"${GITHUB_SHA}\""
|
EXTRA_BUILD_ARGS="APP_VERSION=${GITHUB_SHA}"
|
||||||
|
|
||||||
# Worker 与 API/Web 统一走持久 builder(ci-builder-persist),共享宿主机层缓存
|
# Worker 与 API/Web 统一走持久 builder(ci-builder-persist),共享宿主机层缓存
|
||||||
NO_CACHE_FLAG=""
|
NO_CACHE_FLAG=""
|
||||||
@@ -1014,7 +1014,7 @@ jobs:
|
|||||||
PUSHED_TAGS_SUMMARY="${BRANCH_TAG}"
|
PUSHED_TAGS_SUMMARY="${BRANCH_TAG}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
EXTRA_BUILD_ARGS="APP_VERSION=\"${GITHUB_SHA}\""
|
EXTRA_BUILD_ARGS="APP_VERSION=${GITHUB_SHA}"
|
||||||
|
|
||||||
NO_CACHE_FLAG=""
|
NO_CACHE_FLAG=""
|
||||||
for i in 1 2 3; do
|
for i in 1 2 3; do
|
||||||
@@ -1238,11 +1238,12 @@ jobs:
|
|||||||
ACR_PASSWORD: "${{ secrets.ACR_PASSWORD }}"
|
ACR_PASSWORD: "${{ secrets.ACR_PASSWORD }}"
|
||||||
run: |
|
run: |
|
||||||
set -eux
|
set -eux
|
||||||
# CI runner (act_runner) 部署在 116 staging 本机(116.62.226.203 公网 22 未开放),
|
# Staging 业务机 = 47.98.113.167(公网 sshd 端口 22222;内网 VPC 10.0.0.2:22)。
|
||||||
# 默认走 127.0.0.1:22 本机 SSH,避免跨机网络依赖;可通过 secrets 覆盖。
|
# CI runner 已迁移到独立 CI 机器、job 在隔离容器网络内执行,127.0.0.1 会指向 job 容器自身而失败,
|
||||||
staging_host="${STAGING_SSH_HOST:-127.0.0.1}"
|
# 故默认目标必须是 staging 业务机;仍可通过 secrets 覆盖。
|
||||||
|
staging_host="${STAGING_SSH_HOST:-47.98.113.167}"
|
||||||
staging_user="${STAGING_SSH_USER:-root}"
|
staging_user="${STAGING_SSH_USER:-root}"
|
||||||
staging_port="${STAGING_SSH_PORT:-22}"
|
staging_port="${STAGING_SSH_PORT:-22222}"
|
||||||
echo "Host: $staging_host"
|
echo "Host: $staging_host"
|
||||||
echo "Port: $staging_port"
|
echo "Port: $staging_port"
|
||||||
|
|
||||||
@@ -1552,7 +1553,7 @@ jobs:
|
|||||||
IMAGE_TAG="${REGISTRY}/${{ matrix.image_name }}:${TAG_NAME}"
|
IMAGE_TAG="${REGISTRY}/${{ matrix.image_name }}:${TAG_NAME}"
|
||||||
CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:main"
|
CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:main"
|
||||||
|
|
||||||
EXTRA_BUILD_ARGS="APP_VERSION=\"${TAG_NAME}\""
|
EXTRA_BUILD_ARGS="APP_VERSION=${TAG_NAME}"
|
||||||
|
|
||||||
# Docker build 带重试:失败自动重试2次,第2次重试加--no-cache
|
# Docker build 带重试:失败自动重试2次,第2次重试加--no-cache
|
||||||
NO_CACHE_FLAG=""
|
NO_CACHE_FLAG=""
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
/**
|
||||||
|
* 标题工具函数单测 — 提升覆盖率到 50% 阈值以上
|
||||||
|
*/
|
||||||
|
import { describe, it, expect } from "vitest"
|
||||||
|
import {
|
||||||
|
titleStyleConfigToCamel,
|
||||||
|
camelToTitleStyleConfig,
|
||||||
|
buildPresetPreviewSettings,
|
||||||
|
templateToPreviewSettings,
|
||||||
|
} from "@/components/title/utils"
|
||||||
|
import {
|
||||||
|
getFontFamily,
|
||||||
|
getTitlePreset,
|
||||||
|
TITLE_PRESETS,
|
||||||
|
FONT_OPTIONS,
|
||||||
|
} from "@/components/title/constants"
|
||||||
|
import { DEFAULT_TITLE_SETTINGS_FULL } from "@/pages/generate/types"
|
||||||
|
import type { TitleStyleConfig } from "@/components/title/types"
|
||||||
|
import type { TitleTemplate } from "@/components/title/template-types"
|
||||||
|
|
||||||
|
describe("getFontFamily", () => {
|
||||||
|
it("已知字体名返回对应 family 栈", () => {
|
||||||
|
const f = getFontFamily("思源黑体")
|
||||||
|
expect(f).toContain("Noto Sans SC")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("未知字体名回退到思源黑体", () => {
|
||||||
|
const f = getFontFamily("not-exist-font")
|
||||||
|
expect(f).toBe(FONT_OPTIONS[4].family)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("新增书法字体能查到", () => {
|
||||||
|
expect(getFontFamily("马善政毛笔")).toContain("Ma Shan Zheng")
|
||||||
|
expect(getFontFamily("站酷小薇体")).toContain("ZCOOL XiaoWei")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("getTitlePreset", () => {
|
||||||
|
it("合法 key 返回对应 preset", () => {
|
||||||
|
const p = getTitlePreset(TITLE_PRESETS[0].key)
|
||||||
|
expect(p).toBeDefined()
|
||||||
|
expect(p?.key).toBe(TITLE_PRESETS[0].key)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("不存在的 key 返回 undefined", () => {
|
||||||
|
expect(getTitlePreset("__not_exist__")).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("titleStyleConfigToCamel", () => {
|
||||||
|
it("snake_case 字段映射到 camelCase", () => {
|
||||||
|
const cfg: Partial<TitleStyleConfig> = {
|
||||||
|
font: "思源黑体",
|
||||||
|
size: 48,
|
||||||
|
pos_x: 10,
|
||||||
|
pos_y: 20,
|
||||||
|
line_height: 1.4,
|
||||||
|
margin_top: 30,
|
||||||
|
max_chars_per_line: 8,
|
||||||
|
stroke_width: 4,
|
||||||
|
stroke_color: "#000",
|
||||||
|
shadow_offset_x: 2,
|
||||||
|
bg_enabled: true,
|
||||||
|
bg_padding: 12,
|
||||||
|
bg_radius: 6,
|
||||||
|
}
|
||||||
|
const out = titleStyleConfigToCamel(cfg)
|
||||||
|
expect(out.font).toBe("思源黑体")
|
||||||
|
expect(out.size).toBe(48)
|
||||||
|
expect(out.posX).toBe(10)
|
||||||
|
expect(out.posY).toBe(20)
|
||||||
|
expect(out.lineHeight).toBe(1.4)
|
||||||
|
expect(out.marginTop).toBe(30)
|
||||||
|
expect(out.maxCharsPerLine).toBe(8)
|
||||||
|
expect(out.strokeWidth).toBe(4)
|
||||||
|
expect(out.strokeColor).toBe("#000")
|
||||||
|
expect(out.shadowOffsetX).toBe(2)
|
||||||
|
expect(out.bgEnabled).toBe(true)
|
||||||
|
expect(out.bgPadding).toBe(12)
|
||||||
|
expect(out.bgRadius).toBe(6)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("空对象返回空对象", () => {
|
||||||
|
expect(titleStyleConfigToCamel({})).toEqual({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("camelToTitleStyleConfig", () => {
|
||||||
|
it("camelCase 字段映射到 snake_case", () => {
|
||||||
|
const out = camelToTitleStyleConfig({
|
||||||
|
font: "思源宋体",
|
||||||
|
size: 36,
|
||||||
|
posX: 5,
|
||||||
|
posY: 15,
|
||||||
|
strokeWidth: 2,
|
||||||
|
shadowBlur: 8,
|
||||||
|
bgEnabled: false,
|
||||||
|
})
|
||||||
|
expect(out.font).toBe("思源宋体")
|
||||||
|
expect(out.pos_x).toBe(5)
|
||||||
|
expect(out.pos_y).toBe(15)
|
||||||
|
expect(out.stroke_width).toBe(2)
|
||||||
|
expect(out.shadow_blur).toBe(8)
|
||||||
|
expect(out.bg_enabled).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("两种转换在已知字段上往返一致", () => {
|
||||||
|
const camel = {
|
||||||
|
font: "优设标题黑",
|
||||||
|
size: 56,
|
||||||
|
color: "#ffffff",
|
||||||
|
stroke: true,
|
||||||
|
strokeWidth: 6,
|
||||||
|
bold: true,
|
||||||
|
shadow: true,
|
||||||
|
shadowOffsetX: 3,
|
||||||
|
shadowOffsetY: 3,
|
||||||
|
shadowBlur: 10,
|
||||||
|
bgEnabled: true,
|
||||||
|
bgColor: "#000000",
|
||||||
|
bgPadding: 16,
|
||||||
|
bgRadius: 8,
|
||||||
|
} as const
|
||||||
|
const snake = camelToTitleStyleConfig({ ...camel })
|
||||||
|
const back = titleStyleConfigToCamel(snake)
|
||||||
|
for (const k of Object.keys(camel) as (keyof typeof camel)[]) {
|
||||||
|
expect(back[k]).toBe(camel[k])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("buildPresetPreviewSettings", () => {
|
||||||
|
it("未知 preset key 返回 base", () => {
|
||||||
|
const base = { ...DEFAULT_TITLE_SETTINGS_FULL, size: 32 }
|
||||||
|
const out = buildPresetPreviewSettings(base, "__no_such_preset__")
|
||||||
|
expect(out).toBe(base)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("合法 preset 返回固定字号并缩放描边/阴影/padding", () => {
|
||||||
|
const key = TITLE_PRESETS[0].key
|
||||||
|
const out = buildPresetPreviewSettings(DEFAULT_TITLE_SETTINGS_FULL, key, 56)
|
||||||
|
expect(out.size).toBe(56)
|
||||||
|
expect(Array.isArray(out.lineOverrides)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("templateToPreviewSettings", () => {
|
||||||
|
it("按模板 style 合并默认值,固定字号并缩放装饰尺寸", () => {
|
||||||
|
const tpl: TitleTemplate = {
|
||||||
|
id: "tpl-test",
|
||||||
|
name: "测试模板",
|
||||||
|
category: "test",
|
||||||
|
thumbnail_url: "",
|
||||||
|
is_system: true,
|
||||||
|
style: {
|
||||||
|
font: "思源黑体",
|
||||||
|
size: 72,
|
||||||
|
color: "#ffd700",
|
||||||
|
stroke: true,
|
||||||
|
stroke_width: 8,
|
||||||
|
shadow: true,
|
||||||
|
shadow_offset_x: 4,
|
||||||
|
shadow_offset_y: 4,
|
||||||
|
shadow_blur: 12,
|
||||||
|
bg_enabled: false,
|
||||||
|
bg_padding: 24,
|
||||||
|
bg_radius: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const out = templateToPreviewSettings(tpl, 48)
|
||||||
|
expect(out.size).toBe(48)
|
||||||
|
expect(out.color).toBe("#ffd700")
|
||||||
|
expect(out.strokeWidth).toBe(Math.max(1, Math.round((48 / 72) * 8)))
|
||||||
|
expect(out.shadowBlur).toBe(Math.round((48 / 72) * 12))
|
||||||
|
expect(Array.isArray(out.lineOverrides)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("模板未指定 size 时使用默认 size(不触发缩放)", () => {
|
||||||
|
const tpl: TitleTemplate = {
|
||||||
|
id: "tpl-no-size",
|
||||||
|
name: "无字号模板",
|
||||||
|
category: "test",
|
||||||
|
thumbnail_url: "",
|
||||||
|
is_system: false,
|
||||||
|
style: { font: "思源黑体" },
|
||||||
|
}
|
||||||
|
const out = templateToPreviewSettings(tpl, 48)
|
||||||
|
expect(out.size).toBeDefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -56,7 +56,6 @@ services:
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
APP_ENV: ${APP_ENV:-staging}
|
APP_ENV: ${APP_ENV:-staging}
|
||||||
APP_VERSION: ${APP_VERSION:-unknown}
|
|
||||||
GENERATED_FILES_DIR: /app/generated
|
GENERATED_FILES_DIR: /app/generated
|
||||||
GENERATED_FILES_URL_PREFIX: /generated-files
|
GENERATED_FILES_URL_PREFIX: /generated-files
|
||||||
PUBLIC_API_BASE_URL: ${PUBLIC_API_BASE_URL:-https://api.xiaoxiajianji.com}
|
PUBLIC_API_BASE_URL: ${PUBLIC_API_BASE_URL:-https://api.xiaoxiajianji.com}
|
||||||
@@ -113,7 +112,6 @@ services:
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
APP_ENV: ${APP_ENV:-staging}
|
APP_ENV: ${APP_ENV:-staging}
|
||||||
APP_VERSION: ${APP_VERSION:-unknown}
|
|
||||||
WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-4}
|
WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-4}
|
||||||
WORKER_MAX_TASKS_PER_CHILD: ${WORKER_MAX_TASKS_PER_CHILD:-100}
|
WORKER_MAX_TASKS_PER_CHILD: ${WORKER_MAX_TASKS_PER_CHILD:-100}
|
||||||
# #1714 队列隔离:generation 队列独占 worker(默认并发 2),其余并发给转码
|
# #1714 队列隔离:generation 队列独占 worker(默认并发 2),其余并发给转码
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
# SKIP_NOTIFY - 跳过通知 (true/false, 默认 false)
|
# SKIP_NOTIFY - 跳过通知 (true/false, 默认 false)
|
||||||
# CI_NOTIFY_WEBHOOK - 通知 Webhook URL
|
# CI_NOTIFY_WEBHOOK - 通知 Webhook URL
|
||||||
#
|
#
|
||||||
# STAGING_SSH_HOST - Staging 服务器 SSH 地址 (默认 127.0.0.1,CI runner 在 staging 本机)
|
# STAGING_SSH_HOST - Staging 服务器 SSH 地址 (默认 47.98.113.167,CI runner 在独立 CI 机器)
|
||||||
# STAGING_SSH_USER - SSH 用户名 (默认 root)
|
# STAGING_SSH_USER - SSH 用户名 (默认 root)
|
||||||
# STAGING_SSH_PORT - SSH 端口 (默认 22)
|
# STAGING_SSH_PORT - SSH 端口 (默认 22222,公网;内网 VNC 用 10.0.0.2:22)
|
||||||
# STAGING_SSH_KEY - SSH 私钥内容
|
# STAGING_SSH_KEY - SSH 私钥内容
|
||||||
# REGISTRY_TOKEN - Registry Token(回滚时拉取旧镜像需要)
|
# REGISTRY_TOKEN - Registry Token(回滚时拉取旧镜像需要)
|
||||||
#
|
#
|
||||||
@@ -40,9 +40,9 @@ HEALTH_CHECK_TIMEOUT="${HEALTH_CHECK_TIMEOUT:-120}"
|
|||||||
SKIP_ROLLBACK="${SKIP_ROLLBACK:-false}"
|
SKIP_ROLLBACK="${SKIP_ROLLBACK:-false}"
|
||||||
SKIP_NOTIFY="${SKIP_NOTIFY:-false}"
|
SKIP_NOTIFY="${SKIP_NOTIFY:-false}"
|
||||||
|
|
||||||
STAGING_SSH_HOST="${STAGING_SSH_HOST:-127.0.0.1}"
|
STAGING_SSH_HOST="${STAGING_SSH_HOST:-47.98.113.167}"
|
||||||
STAGING_SSH_USER="${STAGING_SSH_USER:-root}"
|
STAGING_SSH_USER="${STAGING_SSH_USER:-root}"
|
||||||
STAGING_SSH_PORT="${STAGING_SSH_PORT:-22}"
|
STAGING_SSH_PORT="${STAGING_SSH_PORT:-22222}"
|
||||||
|
|
||||||
REGISTRY="${REGISTRY:-git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas}"
|
REGISTRY="${REGISTRY:-git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas}"
|
||||||
REGISTRY_USER="${REGISTRY_USER:-xiaoxia}"
|
REGISTRY_USER="${REGISTRY_USER:-xiaoxia}"
|
||||||
|
|||||||
Reference in New Issue
Block a user