diff --git a/.github/workflows/aliyun-backup.yml b/.github/workflows/aliyun-backup.yml index 4267dc56..a0fab4d5 100644 --- a/.github/workflows/aliyun-backup.yml +++ b/.github/workflows/aliyun-backup.yml @@ -1,17 +1,19 @@ -name: 备份到阿里云oss +name: 备份到阿里云OSS on: schedule: - - cron: "0 0 * * 0" # 每周日的00:00执行一次 + - cron: "0 0 * * 0" # 每周日的00:00执行一次(UTC) workflow_dispatch: # 手动触发工作流 concurrency: - group: "pages" - cancel-in-progress: false + group: "aliyun-backup" + cancel-in-progress: true jobs: - publish: + backup: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: 下载代码 uses: actions/checkout@v4 @@ -28,5 +30,25 @@ jobs: - name: 增量上传资源(排除 public 文件夹) run: | - # 查找所有文件,排除 public 文件夹 - find . -path ./public -prune -o -type f -print | xargs -I {} ossutil cp {} oss://${{ secrets.OSS_BUCKET }}/{} --update + # 使用 find -print0 和 xargs -0 安全处理特殊字符 + # 排除 public、.git、node_modules 等不必要的文件 + find . \ + -path ./public -prune -o \ + -path ./.git -prune -o \ + -path ./node_modules -prune -o \ + -path ./resources -prune -o \ + -type f -print0 | xargs -0 -I {} ossutil cp "{}" "oss://${{ secrets.OSS_BUCKET }}/{}" --update + + - name: 验证备份完整性 + run: | + # 统计本地文件数(排除目录) + LOCAL_COUNT=$(find . -path ./public -prune -o -path ./.git -prune -o -path ./node_modules -prune -o -path ./resources -prune -o -type f | wc -l) + echo "本地文件数: $LOCAL_COUNT" + + # 统计 OSS 上的文件数 + REMOTE_COUNT=$(ossutil ls oss://${{ secrets.OSS_BUCKET }}/ --recursive | grep -v "Directory:" | wc -l) + echo "OSS 文件数: $REMOTE_COUNT" + + echo "## 备份统计" >> $GITHUB_STEP_SUMMARY + echo "- **本地文件数**: $LOCAL_COUNT" >> $GITHUB_STEP_SUMMARY + echo "- **OSS 文件数**: $REMOTE_COUNT" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d4e38e4b..b8fc08a1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,19 +6,257 @@ on: - main schedule: - cron: '0 1 * * *' # UTC 01:00 = 北京时间 09:00 + workflow_dispatch: + inputs: + force_font_subset: + description: 'Force font subset regeneration' + type: boolean + default: false + skip_font_subset: + description: 'Skip font subset step' + type: boolean + default: false concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + TZ: Asia/Shanghai + jobs: - deploy: + # ============================================================ + # Job 0: Pre-Flight Checks (前置检查) + # ============================================================ + pre-check: runs-on: ubuntu-latest + timeout-minutes: 2 + outputs: + can_deploy: ${{ steps.check.outputs.can_deploy }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Validate repository + id: repo-check + run: | + if [ "${{ github.repository }}" != "zqlit/blog" ]; then + echo "❌ 只能在主仓库执行部署" + echo "can_deploy=false" >> $GITHUB_OUTPUT + exit 1 + fi + echo "✅ 仓库验证通过" + echo "can_deploy=true" >> $GITHUB_OUTPUT + + - name: Validate environment variables + id: env-check + run: | + REQUIRED_SECRETS=( + "EDGEONE_API_TOKEN" + "UPYUN_BUCKET" + "UPYUN_OPERATOR" + "UPYUN_PASSWORD" + "DOGECLOUD_ACCESS_KEY" + "DOGECLOUD_SECRET_KEY" + "MAIL_USERNAME" + "MAIL_PASSWORD" + ) + + MISSING="" + for SECRET in "${REQUIRED_SECRETS[@]}"; do + if [ -z "${{ secrets[SECRET] }}" ]; then + MISSING="$MISSING $SECRET" + fi + done + + if [ -n "$MISSING" ]; then + echo "❌ 缺少必要的环境变量: $MISSING" + exit 1 + fi + echo "✅ 所有必要环境变量已配置" + + - name: Validate commit (only for push events) + if: github.event_name == 'push' + id: commit-check + run: | + COMMIT_AUTHOR="${{ github.event.head_commit.author.name }}" + COMMIT_MESSAGE="${{ github.event.head_commit.message }}" + + echo "📝 提交作者: $COMMIT_AUTHOR" + echo "📄 提交信息: $COMMIT_MESSAGE" + + if echo "$COMMIT_MESSAGE" | grep -qE "^(feat|fix|chore|docs|style|refactor|test): "; then + echo "✅ 提交信息符合规范" + else + echo "⚠️ 提交信息格式建议: feat/fix/chore/docs/style/refactor/test: description" + fi + + - name: Summary + run: | + echo "## 🔍 前置检查完成" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **仓库**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY + echo "- **分支**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY + echo "- **触发方式**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit SHA**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + + # ============================================================ + # Job 1: Font Subset (字体子集化 - 可选并行任务) + # ============================================================ + subset-fonts: + runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: write - env: - TZ: Asia/Shanghai + needs: pre-check + if: | + needs.pre-check.outputs.can_deploy == 'true' && + inputs.skip_font_subset != 'true' + outputs: + font_updated: ${{ steps.update.outputs.font_updated }} + char_count: ${{ steps.stats.outputs.char_count }} + original_size: ${{ steps.stats.outputs.original_size }} + subset_size: ${{ steps.stats.outputs.subset_size }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: pip install fonttools brotli zopfli + + - name: Generate font subset + id: update + run: | + # 运行字体子集化脚本 + # 会自动扫描: + # 1. 静态文件 (content, layouts, public) + # 2. 本地 JSON 数据 (友链、朋友圈) + # 3. 远程 API (api.usj.cc 的友链、订阅源、朋友圈) + python scripts/subset-font-safe.py + + # 检查是否有变化 + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + + git add themes/Ying/static/font/zql-v2-subset.woff2 themes/Ying/static/font/used_chars.txt + if ! git diff --staged --quiet; then + git commit -m "chore: update font subset [skip ci]" + git push origin HEAD:main + echo "font_updated=true" >> $GITHUB_OUTPUT + echo "✅ 字体子集已更新并推送" + else + echo "font_updated=false" >> $GITHUB_OUTPUT + echo "ℹ️ 字体子集无变化,跳过提交" + fi + + - name: Collect stats + id: stats + if: always() + run: | + CHAR_COUNT=$(wc -m < themes/Ying/static/font/used_chars.txt | tr -d ' ') + ORIGINAL_SIZE=$(stat -c%s themes/Ying/static/font/zql-v2.woff2) + SUBSET_SIZE=$(stat -c%s themes/Ying/static/font/zql-v2-subset.woff2) + echo "char_count=$CHAR_COUNT" >> $GITHUB_OUTPUT + echo "original_size=$ORIGINAL_SIZE" >> $GITHUB_OUTPUT + echo "subset_size=$SUBSET_SIZE" >> $GITHUB_OUTPUT + + - name: Create font subset summary + if: always() + run: | + ORIGINAL_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.original_size }} / 1024" | bc) + SUBSET_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.subset_size }} / 1024" | bc) + REDUCTION_KB=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) / 1024" | bc) + PERCENTAGE=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) * 100 / ${{ steps.stats.outputs.original_size }}" | bc) + + echo "## 🔤 字体子集化" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.update.outputs.font_updated }}" = "true" ]; then + echo "✅ **字体子集已更新**" >> $GITHUB_STEP_SUMMARY + else + echo "ℹ️ **字体子集无变化**" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 📊 统计信息" >> $GITHUB_STEP_SUMMARY + echo "- **字符数:** ${{ steps.stats.outputs.char_count }}" >> $GITHUB_STEP_SUMMARY + echo "- **原始大小:** ${ORIGINAL_SIZE_KB} KB" >> $GITHUB_STEP_SUMMARY + echo "- **子集大小:** ${SUBSET_SIZE_KB} KB" >> $GITHUB_STEP_SUMMARY + echo "- **节省空间:** ${REDUCTION_KB} KB (${PERCENTAGE}%)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 🌐 扫描的数据源" >> $GITHUB_STEP_SUMMARY + echo "- 静态文件: content, layouts, public" >> $GITHUB_STEP_SUMMARY + echo "- 本地 JSON: link_lite.json, friend_circle_data.json" >> $GITHUB_STEP_SUMMARY + echo "- 远程 API: api.usj.cc (links, feeds, articles)" >> $GITHUB_STEP_SUMMARY + + - name: Send font subset notification + if: steps.update.outputs.font_updated == 'true' + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + run: | + ORIGINAL_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.original_size }} / 1024" | bc) + SUBSET_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.subset_size }} / 1024" | bc) + REDUCTION_KB=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) / 1024" | bc) + PERCENTAGE=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) * 100 / ${{ steps.stats.outputs.original_size }}" | bc) + + if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then + MESSAGE="✅ **字体子集化完成** + +📦 项目: ${{ github.repository }} +🌿 分支: ${{ github.ref_name }} + +📊 优化统计: +- 字符数: ${{ steps.stats.outputs.char_count }} +- 原始大小: ${ORIGINAL_SIZE_KB} KB +- 子集大小: ${SUBSET_SIZE_KB} KB +- 节省空间: ${REDUCTION_KB} KB (${PERCENTAGE}%) + +🌐 数据源: +- 静态文件 + 本地JSON + 远程API" + + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d chat_id="${TELEGRAM_CHAT_ID}" \ + -d text="${MESSAGE}" \ + -d parse_mode="Markdown" + fi + + if [ -n "$FEISHU_WEBHOOK_URL" ]; then + curl -s -X POST "${FEISHU_WEBHOOK_URL}" \ + -H "Content-Type: application/json" \ + -d '{ + "msg_type": "text", + "content": { + "text": "✅ **字体子集化完成**\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n\n📊 优化统计:\n- 字符数: ${{ steps.stats.outputs.char_count }}\n- 原始大小: '"${ORIGINAL_SIZE_KB}"' KB\n- 子集大小: '"${SUBSET_SIZE_KB}"' KB\n- 节省空间: '"${REDUCTION_KB}"' KB ('"${PERCENTAGE}"'%)\n\n🌐 数据源: 静态文件 + 本地JSON + 远程API" + } + }' + fi + + # ============================================================ + # Job 2: Build (构建) + # ============================================================ + build: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + outputs: + build_hash: ${{ steps.hash.outputs.build_hash }} + needs: pre-check + if: needs.pre-check.outputs.can_deploy == 'true' + steps: - name: Checkout code uses: actions/checkout@v4 @@ -31,11 +269,6 @@ jobs: hugo-version: '0.128.2' extended: true - - name: Run Pre-Build Scripts - shell: pwsh - run: | - ./scripts/add_draft_to_hidden.ps1 - - name: Setup Node uses: actions/setup-node@v4 with: @@ -45,8 +278,23 @@ jobs: - name: Install Node.js dependencies run: npm ci + - name: Cache Hugo resources + uses: actions/cache@v4 + with: + path: | + resources/_gen + /home/runner/.cache/hugo_cache + key: ${{ runner.os }}-hugo-${{ hashFiles('go.sum', 'package-lock.json', 'hugo.toml', 'config/_default/*.toml') }} + restore-keys: | + ${{ runner.os }}-hugo- + + - name: Run Pre-Build Scripts + shell: pwsh + run: ./scripts/add_draft_to_hidden.ps1 + - name: Optimize Images continue-on-error: true + timeout-minutes: 5 run: node scripts/optimize_images.js - name: Commit Optimized Images @@ -61,9 +309,56 @@ jobs: echo "No changes to commit" fi - - name: Build + - name: Build Hugo site + timeout-minutes: 10 run: rm -rf public && hugo --minify + - name: Generate build hash + id: hash + run: echo "build_hash=$(find public -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}')" >> $GITHUB_OUTPUT + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: hugo-public + path: public/ + retention-days: 1 + + - name: Push Image Optimizations + continue-on-error: true + run: | + git fetch origin main + git stash push -m "temp stash for rebase" || true + git rebase origin/main || true + git stash pop || true + if git log --oneline -1 | grep -q "\[skip ci\]"; then + git push origin HEAD:main + else + echo "No auto-commits to push" + fi + + # ============================================================ + # Job 3: Deploy to EdgeOne Pages (并行) + # ============================================================ + deploy-edgeone: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + needs: build + + steps: + - name: Checkout code (for scripts) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: hugo-public + path: public/ + - name: Deploy to EdgeOne Pages env: EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }} @@ -71,6 +366,23 @@ jobs: chmod +x scripts/deploy_edgeone.sh ./scripts/deploy_edgeone.sh hugo-blog ./public overseas + # ============================================================ + # Job 4: Deploy to UpYun (并行) + # ============================================================ + deploy-upyun: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + needs: build + + steps: + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: hugo-public + path: public/ + - name: Deploy to UpYun uses: her-cat/upyun-deployer@v1.0.3 with: @@ -80,39 +392,84 @@ jobs: dir: 'public' publish_dir: '/' + # ============================================================ + # Job 5: CDN Refresh & Notifications (在所有部署完成后) + # ============================================================ + finalize: + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + needs: [deploy-edgeone, deploy-upyun] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Install Node.js dependencies + run: npm ci + - name: Refresh DogeCloud CDN - continue-on-error: true env: DOGECLOUD_ACCESS_KEY: ${{ secrets.DOGECLOUD_ACCESS_KEY }} DOGECLOUD_SECRET_KEY: ${{ secrets.DOGECLOUD_SECRET_KEY }} CDN_URL_LIST: ${{ secrets.CDN_URL_LIST }} run: node scripts/refresh_cdn.js - - name: Push Image Optimizations - continue-on-error: true - run: | - git fetch origin main - # stash任何未提交的更改,避免rebase失败 - git stash push -m "temp stash for rebase" - git rebase origin/main - # 恢复stash的更改 - git stash pop || true - if ! git log --oneline -1 | grep -q "\[skip ci\]"; then - echo "No auto-commits to push" - else - git push origin HEAD:main - fi - - name: Generate Notification Report continue-on-error: true run: node scripts/generate_notification_report.js + - name: Send Telegram notification + if: secrets.TELEGRAM_BOT_TOKEN && secrets.TELEGRAM_CHAT_ID + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + run: | + MESSAGE="✅ **部署成功** + +📦 项目: ${{ github.repository }} +🌿 分支: ${{ github.ref_name }} +🔗 Commit: ${{ github.sha }} +🕐 时间: $(date +'%Y-%m-%d %H:%M:%S') + +📊 部署目标: +- EdgeOne Pages ✓ +- UpYun ✓ +- CDN 刷新 ✓" + + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d chat_id="${TELEGRAM_CHAT_ID}" \ + -d text="${MESSAGE}" \ + -d parse_mode="Markdown" + + - name: Send Feishu notification + if: secrets.FEISHU_WEBHOOK_URL + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + run: | + curl -s -X POST "${FEISHU_WEBHOOK_URL}" \ + -H "Content-Type: application/json" \ + -d '{ + "msg_type": "text", + "content": { + "text": "✅ **部署成功**\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n🔗 Commit: ${{ github.sha }}\n🕐 时间: '$(date +'%Y-%m-%d %H:%M:%S')'\n\n📊 部署目标:\n- EdgeOne Pages ✓\n- UpYun ✓\n- CDN 刷新 ✓" + } + }' + - name: Get current date (CN) id: date run: echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT - name: Send deployment notification - if: success() uses: dawidd6/action-send-mail@v3 with: server_address: smtp.qq.com @@ -122,6 +479,53 @@ jobs: password: ${{ secrets.MAIL_PASSWORD }} subject: 部署报告 - ${{ steps.date.outputs.date }} to: ${{ secrets.MAIL_USERNAME }} - from: GitHub Actions <${{ secrets.MAIL_USERNAME }}> + from: GitHub Actions <${{ github.event_name }}> body: | ${{ env.DEPLOY_REPORT }} + + # ============================================================ + # Job 6: Failure Notification (失败通知) + # ============================================================ + notify-failure: + runs-on: ubuntu-latest + timeout-minutes: 3 + permissions: + contents: read + needs: [build, deploy-edgeone, deploy-upyun, finalize] + if: failure() + + steps: + - name: Send Telegram failure notification + if: secrets.TELEGRAM_BOT_TOKEN && secrets.TELEGRAM_CHAT_ID + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + run: | + MESSAGE="❌ **部署失败** + +📦 项目: ${{ github.repository }} +🌿 分支: ${{ github.ref_name }} +🔗 Commit: ${{ github.sha }} +🕐 时间: $(date +'%Y-%m-%d %H:%M:%S') + +📝 工作流: ${{ github.workflow }} +🔍 查看详情: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d chat_id="${TELEGRAM_CHAT_ID}" \ + -d text="${MESSAGE}" \ + -d parse_mode="Markdown" + + - name: Send Feishu failure notification + if: secrets.FEISHU_WEBHOOK_URL + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + run: | + curl -s -X POST "${FEISHU_WEBHOOK_URL}" \ + -H "Content-Type: application/json" \ + -d '{ + "msg_type": "text", + "content": { + "text": "❌ **部署失败**\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n🔗 Commit: ${{ github.sha }}\n🕐 时间: '$(date +'%Y-%m-%d %H:%M:%S')'\n\n📝 工作流: ${{ github.workflow }}\n🔍 查看详情: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + }' \ No newline at end of file diff --git a/.github/workflows/subset-fonts.yml b/.github/workflows/subset-fonts.yml deleted file mode 100644 index 65f3fcfb..00000000 --- a/.github/workflows/subset-fonts.yml +++ /dev/null @@ -1,167 +0,0 @@ -# GitHub Actions: 字体子集化自动化 -# 当内容更新时自动提取字符并生成优化的子集字体 - -name: Font Subset Optimization - -on: - # 当推送到main分支且content目录有变更时触发 - push: - branches: - - main - paths: - - 'content/**' - - 'layouts/**' - - # 允许手动触发 - workflow_dispatch: - inputs: - force_rebuild: - description: '强制重新生成子集字体' - required: false - default: 'false' - type: boolean - - # 可选:每周自动运行一次(检查是否需要更新) - schedule: - - cron: '0 2 * * 1' # 每周一凌晨2点 - -# 限制同时运行的工作流数量 -concurrency: - group: font-subset-${{ github.ref }} - cancel-in-progress: true - -jobs: - subset-fonts: - name: Subset Fonts - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: write # 添加写权限 - - # 只在主仓库上运行(fork不运行) - if: github.repository == 'zqlit/blog' - - steps: - # 1. 检出代码 - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # 获取完整历史用于比较 - - # 2. 设置Python环境 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - cache: 'pip' # 使用requirements.txt进行缓存 - - # 3. 安装Python依赖 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install fonttools brotli - - # 4. 检查是否需要更新(可选) - - name: Check if update needed - id: check - run: | - # 检查内容文件是否有变更 - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "needs_update=true" >> $GITHUB_OUTPUT - else - # 比较content目录的变更 - CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD -- content/ layouts/ || echo "") - if [ -n "$CHANGED_FILES" ]; then - echo "needs_update=true" >> $GITHUB_OUTPUT - echo "Changed files: $CHANGED_FILES" - else - echo "needs_update=false" >> $GITHUB_OUTPUT - echo "No content changes detected" - fi - fi - - # 5. 构建Hugo站点(用于提取字符) - - name: Build Hugo site - if: steps.check.outputs.needs_update == 'true' || github.event.inputs.force_rebuild == 'true' - uses: peaceiris/actions-hugo@v2 - with: - hugo-version: '0.128.2' # 固定版本,与本地一致 - extended: true - - - name: Build - if: steps.check.outputs.needs_update == 'true' || github.event.inputs.force_rebuild == 'true' - run: hugo --destination=public --minify - - # 6. 运行字体子集化脚本 - - name: Subset fonts - if: steps.check.outputs.needs_update == 'true' || github.event.inputs.force_rebuild == 'true' - run: | - python scripts/subset-font-safe.py - - # 7. 检查子集字体是否真的优化了 - - name: Verify optimization - if: steps.check.outputs.needs_update == 'true' || github.event.inputs.force_rebuild == 'true' - run: | - ORIGINAL_SIZE=$(stat -f%z themes/Ying/static/font/zql-v2.woff2 2>/dev/null || stat -c%s themes/Ying/static/font/zql-v2.woff2) - SUBSET_SIZE=$(stat -f%z themes/Ying/static/font/zql-v2-subset.woff2 2>/dev/null || stat -c%s themes/Ying/static/font/zql-v2-subset.woff2) - - echo "Original font size: $ORIGINAL_SIZE bytes" - echo "Subset font size: $SUBSET_SIZE bytes" - - # 检查子集字体是否真的更小 - if [ $SUBSET_SIZE -ge $ORIGINAL_SIZE ]; then - echo "⚠️ Warning: Subset font is not smaller than original" - echo "Skipping commit..." - exit 0 - fi - - REDUCTION=$((ORIGINAL_SIZE - SUBSET_SIZE)) - PERCENTAGE=$((REDUCTION * 100 / ORIGINAL_SIZE)) - echo "✅ Font optimized: reduced $REDUCTION bytes ($PERCENTAGE%)" - - # 8. 提交更改(如果有优化) - - name: Commit changes - if: steps.check.outputs.needs_update == 'true' || github.event.inputs.force_rebuild == 'true' - run: | - # 配置git - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - - # 检查是否有变更 - if git diff --quiet; then - echo "No changes to commit" - exit 0 - fi - - # 添加变更的文件 - git add themes/Ying/static/font/zql-v2-subset.* - git add themes/Ying/static/font/used_chars.txt - - # 提交(添加[skip ci]避免触发deploy.yml) - git commit -m "chore: update font subset (automated) [skip ci] - - - Updated font subset based on latest content - - Subset characters: $(wc -c < themes/Ying/static/font/used_chars.txt) chars - - Font size: $(stat -c%s themes/Ying/static/font/zql-v2-subset.woff2 2>/dev/null || echo 'N/A') bytes" - - # 9. 推送更改 - - name: Push changes - if: steps.check.outputs.needs_update == 'true' || github.event.inputs.force_rebuild == 'true' - run: | - git push origin main - - # 10. 创建摘要 - - name: Create summary - if: steps.check.outputs.needs_update == 'true' || github.event.inputs.force_rebuild == 'true' - run: | - echo "## 🔤 Font Subset Optimization" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "✅ Font subset updated successfully!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📊 Statistics" >> $GITHUB_STEP_SUMMARY - echo "- **Characters in subset:** $(wc -c < themes/Ying/static/font/used_chars.txt)" >> $GITHUB_STEP_SUMMARY - echo "- **Subset font size:** $(stat -c%s themes/Ying/static/font/zql-v2-subset.woff2 2>/dev/null || echo 'N/A') bytes" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📝 Triggered by" >> $GITHUB_STEP_SUMMARY - echo "- Event: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY - echo "- Commit: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY diff --git a/scripts/subset-font-safe.py b/scripts/subset-font-safe.py index 63a62442..71c8adf3 100644 --- a/scripts/subset-font-safe.py +++ b/scripts/subset-font-safe.py @@ -50,6 +50,92 @@ def extract_chars_from_files(directories, extensions=('.html', '.md', '.css')): return chars +def extract_chars_from_json(json_files): + """从JSON数据文件中提取字符(用于友链、朋友圈等动态内容)""" + chars = set() + + import json + + for json_file in json_files: + if not os.path.exists(json_file): + print(f"⚠️ JSON文件不存在,跳过: {json_file}") + continue + + print(f"🔍 扫描JSON文件: {json_file}") + + try: + with open(json_file, 'r', encoding='utf-8') as f: + data = json.load(f) + + # 将整个JSON转换为字符串,提取所有字符 + content = json.dumps(data, ensure_ascii=False) + + # 提取中文字符 + chinese_chars = re.findall(r'[一-鿿]', content) + chars.update(chinese_chars) + + # 提取中文标点 + cjk_punct = re.findall(r'[ -〿＀-￯]', content) + chars.update(cjk_punct) + + # 提取英文和数字 + ascii_chars = re.findall(r'[a-zA-Z0-9]', content) + chars.update(ascii_chars) + + print(f" ✅ 从 {json_file} 提取了 {len(chinese_chars)} 个中文字符") + + except Exception as e: + print(f"⚠️ 读取JSON文件失败 {json_file}: {e}") + + return chars + +def fetch_and_extract_chars_from_api(urls): + """从API接口获取数据并提取字符(用于动态内容如友链、朋友圈)""" + chars = set() + + try: + import urllib.request + import json + import ssl + + # 创建不验证SSL的上下文(用于内网或自签名证书) + context = ssl._create_unverified_context() + + for url in urls: + print(f"🌐 尝试从API获取: {url}") + try: + req = urllib.request.Request( + url, + headers={ + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + ) + + with urllib.request.urlopen(req, context=context, timeout=15) as response: + data = response.read().decode('utf-8') + + try: + json_data = json.loads(data) + content = json.dumps(json_data, ensure_ascii=False) + + # 提取中文字符 + chinese_chars = re.findall(r'[一-鿿]', content) + chars.update(chinese_chars) + + print(f" ✅ 从API提取了 {len(chinese_chars)} 个中文字符") + except json.JSONDecodeError: + print(f" ⚠️ 响应不是有效的JSON,跳过") + + except urllib.error.URLError as e: + print(f" ❌ API请求失败: {e}") + except Exception as e: + print(f" ❌ 处理API响应失败: {e}") + + except ImportError: + print("⚠️ urllib不可用,跳过API扫描") + + return chars + def subset_font(input_font_path, output_font_path, chars): """生成子集字体""" print(f"\n📦 加载字体: {input_font_path}") @@ -177,10 +263,50 @@ def main(): print(f"\n🔍 扫描目录: {', '.join(scan_dirs)}") - # 提取字符 + # 提取字符(静态文件) new_chars = extract_chars_from_files(scan_dirs) print(f"\n📝 从文件中提取了 {len(new_chars)} 个唯一字符") + # 扫描动态数据文件(友链、朋友圈等) + json_data_files = [ + "themes/Ying/static/json/link_lite.json", # 友链数据 + "themes/Ying/static/json/friend_circle_data.json", # 朋友圈数据 + "themes/Ying/data/links.yaml", # 友链配置 + "themes/Ying/data/links.yml", # 友链配置(备用) + ] + + print(f"\n{'='*50}") + print(f"🔄 扫描动态数据文件(友链、朋友圈等)") + dynamic_chars = extract_chars_from_json(json_data_files) + print(f"📝 从动态数据中提取了 {len(dynamic_chars)} 个唯一字符") + + # 从API获取动态数据(用于CI环境) + api_urls = [ + "https://api.usj.cc/api/links?all=1", # 友链列表 + "https://api.usj.cc/api/feeds", # 订阅源列表 + "https://api.usj.cc/api/articles?limit=50", # 朋友圈文章 + ] + + # 如果有环境变量,追加额外的API地址 + if os.environ.get('FONT_SUBSET_API_URLS'): + extra_urls = os.environ['FONT_SUBSET_API_URLS'].split(',') + api_urls.extend(extra_urls) + print(f"\n{'='*50}") + print(f"🌐 从环境变量获取额外API地址") + print(f" 额外URLs: {', '.join(extra_urls)}") + + print(f"\n{'='*50}") + print(f"🌐 扫描远程API(友链、朋友圈等)") + print(f" 共 {len(api_urls)} 个API端点") + + api_chars = fetch_and_extract_chars_from_api(api_urls) + print(f"📝 从API提取了 {len(api_chars)} 个唯一字符") + dynamic_chars = dynamic_chars | api_chars + + # 合并静态和动态字符 + new_chars = new_chars | dynamic_chars + print(f"\n📊 合并后总字符数: {len(new_chars)}") + # 合并字符(保留现有字符 + 新提取的字符) all_chars = existing_chars | new_chars print(f"📊 合并后字符数: {len(all_chars)}") diff --git a/themes/Ying/static/font/used_chars.txt b/themes/Ying/static/font/used_chars.txt index 087e899a..f413a28a 100644 --- a/themes/Ying/static/font/used_chars.txt +++ b/themes/Ying/static/font/used_chars.txt @@ -1,2 +1,2 @@ - !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~—、。《》「」一丁七万三上下不与丑专且世丘业东丝丞丢两严丧个中丰串临为主丽举久么义之乌乎乏乐乒乓乘九也习乡书买乱了予争事二于亏云互五井亚些亡交亦产享京亮亲人亿什仁仅今介仍从仓仔他仗付仙代令以仪们仰件价任份仿企伏伐休众优伙会伞传伤伦伪伫估伴伸似但位低住佐佑体何余佛作你佩佬佳使例供依侠侣侧便促俄俗保信俩俭修俱倍倒候借倦债值假偎偏做停健偶偷偿傅傍储催傲傻像僻儿兀允元兄充兆先光克免兑兔党兜入全八公六共关兴其具典养兼冀内册再冒冗写军农冠冤冥冬冰冲决况冷冻净准凉凌减凑凛几凡凭凳凶出击函刀刃分切划列刘则刚创初删判利别刮到制刷券刹刺刻前剔剖剥剧剩剪副割力劝办功加务劣动助努励劲劳势勇勉勒勤勾勿包匆匍匐匕化北匙匠匹区医匾匿十千升午半华协卓单卖南博占卡卢卦卧卫卯印危即却卷卸厂厅历厉压厌厕厘厚原厢厨去县参又及友双反发叒叔叕取受变叙叠口古句另叨只叫召可台史右叶号司叹吃各合吉同名后吐向吓吗吞否吧吨含听启吱吴吵吸吹吾呀呆呈告呕呗员呛呢呦周味呵呼命咄咋和咐咕咙咨咩咪咬咱咳咸咽哀品哄哆哇哈响哒哗哥哦哨哪哭哲唉唐唤唬售唯唾商啊啥啦啸喂善喉喊喜喝喧喵喷喻嗓嗖嗜嗡嗦嗨嗯嗽嘀嘎嘛嘱嘲嘴嘿器噩噪嚣囊四回因团园困围固国图圆圈土圣在地圳场址均坊坎坏坐坑块坚坛坞坟坠坡坦坷垄型垢埋城埔域培基堂堆堑堡堤堪堵塌塑塔塘塞填境墓墙增墨壁士壮声壳壶处备复夏夕外多夜够大天太夫失头夸夹夺奇奈奋奏奖套奢女奶奸她好如妄妇妈妒妙妥妨妹妻始姐姑姓委姨姿威娃娜娱婆媒媚嫁嫉嫌嫖嫩子孔字存季孤学孩宁它守安宋完宏宗官定宛宜宝实宠审客宣室宫宰害宵家容宽宾宿寂寄密寇富寒寓寝察寸对寺寻导寿封射将尉尊小少尔尖尘尝尤尬就尴尸尺尼尽尾局层居屈届屋屎屏屑展属屠屯山岁岂岔岖岗岛岸峨峰崎崩嵌巍川州工左巧巨差己已巴巷巾币市布师希帖帘带帧帮常帽幅幕幢干平年并幸幺幻幼幽广庄庆床序库应底店度座庭庶康庸延建廿开异弃弄弈弊式引弟张弥弯弱弹强归当录形彩彰影彻彼往征径待很律徐徒得徙御循微德心必忆忍忐忑志忘忙忧快念忽怀态怎怒怔怕思怠怡急怦性怨怪怯总怼恋恍恐恒恢恨恩恭息恰恶恼悄悉悍悔悖悚悟患悦您悬悲情惊惑惕惜惠惧惨惫惬惯惰想惹愁愈愉意愚感愣愤愧愿慎慕慢慨憧憨憬憾懂懈懑懒懦懵戏成我或战戚截戳戴户戾房所扇手才扎扑打扔托扣执扩扫扬扰扳批扼找承技抄抉把抑抓投抖抗折抛抢护报抬抱抵抹抽担拆拉拌拍拒拓拔拖拘拙招拜拟拥拦拧拨择括拳拷拼拽拾拿持挂指按挑挖挚挡挣挤挥挨挫振挺捉捏捕捞损捡换捧据捷掂授掉掌掐排探接控推掩措掰掺描提插握揣揭搁搏搓搜搞搬搭携摁摄摆摇摊摔摘摧摩摸撑撒撞撤播撰撸撼擅操擎擦攒支收改攻放政故效敌敏救教敛敞敢散敬数敲整敷文斑斗料斜斥斧斩断斯新方施旁旅族旗无既日旦旧旨早旬时旷明易星映春昨是昵昼显晃晒晕晚晦晨普景晰晶智暂暇暑暖暗暴曦曲更曾替最月有朋服望朝期朦木未末本术朴朵机杀杂权杆李杏材村杜束杠条来杨杭杯東松板极构析枕林枚果枪枫枯架某柒染柔柜查柯柳柴柿栀标栈栋栏树校样核根格栽桂桃框案桌桑档桥桶梁梦梯械梳检棉棒棘棚森棵椅植椒楚楼概榜槛槽樊模横橙欠次欢欣欧欲欺款歉歌止正此步武歪死殉殊残段殿毁毅毋母每毒比毕毛毫氏民气氛氧水永汁求汇汉汕汗江池污汤汪汲沉沙沟没沦沮河油治沾沿泄泉泌法泛泡波泣泥注泪泯泳泽洁洋洒洗洛洞津洪洲活派流浅浇测济浏浑浓浪浮浴海浸涂消涉润涨涩涯液涵淀淋淘淡淫深淳混添清渐渗渠渡温港渲渴游湖湘湾湿溃源溢溯滋滑滚滞满滤滥滴漂漏漓演漫漱潘潜潦潭潮澎澡澳激瀑火灭灯灰灵灾炉炎炒炕炫炮炸点炼烁烂烈烟烤烦烧烫热烽焉焕焖焦然煌煎照煲熄熊熙熟熬熵燥爆爬爱父爷爸爽片版牌牙牛物牲牵特牺犊犯状犹狂狗狠独狱狼猛猜猝猫献猴玄率玉王玛玩环现玻珍珠班球理琐琢瑞璃瓜瓢瓦瓶甚甜生甥用甩田由甲申电男画畅界留略番畴疏疑疗疙疫疮疯疲疼疾病症痍痒痕痘痛痪瘦瘩瘫瘾癌登白百的皇皑皮盆盈益监盒盖盗盘盛盟目盯盲直相盼盾省眉看真眠眩眶眼着睁睛睡督睹睿瞅瞎瞥瞬矛知矩短石矿码砌砍研砖砥破砺础硅硌硬确碌碍碎碑碗碰磁磨示礼社祖祝神票祸禁福离秀私秉秋种科秒租积称移稀程稍税稚稳稿究穷空穿突窃窄窒窗窝立站竞竟章童端竹笑笔符笨第笼等筋筑答策筛筷筹签简算管箭箱篇篝篡簧簿籍米类粉粒粗粘粹粽精糊糕糖糙糟系素索紧累繁纠红纤约级纪纭纯纱纲纳纷纸线练组细织终绍经绑结绕绘给绚络绝统继绩绪续绰绳维综绿缀缆缓编缘缚缝缠缩缭缴缺网罕罗罢罩罪置署羊美羞羡群羽翁翻翼耀老考者而耍耐耗耦耳耸耻耽聊职联聘聚聪聿肃肉肋肌肖肚肝肠股肢肤肥肩肯育肺肿胀背胖胚胜胞胡胧胶胸胺能脂脆脉脏脑脚脱脸脾腊腔腕腹腺腻腾腿膀膏膜膝膨臂臃自臭至致舅舍舒航般良艰色艳节芒芙芦芬芯花芳苍苏苗苛若苦英苹茂范茄茫茶草荐荒荡荣荤荫药荼莓莘莫获菌菜菩菲萌萎营萦萨落著葛葩葫葵蒂蒙蒜蒸蓝蔷蔽蕉蕴薄薅薇薛薪藏虎虐虑虚虫虹虽虾蚁蚂蚊蛇蛊蛋蛙蛮蛳蛾蜂蜕蜜蝇螃融螺蟹血行衍衔街衡衣补表衩衰衷袁袋袍被袭裁裂装裕裙裤裸裹褒襄西要覆见观规视览觉角解触言警计订认讨让讪训议讯记讲许论讽设访诀证评识诈诉诊词译试诗诙诚话诞诠询该详诧语误说诶请诸诺读课谁调谅谈谊谋谏谐谓谔谛谢谨谬谱谴谷豁豆象豫貌贝负贡财责败账货质贩贪贫贬购贯贴贵费贺贼资赋赏赖赘赚赞赢走赴赵赶起趁超越趋趟趣足跃跑距跟跨跪路跳践踏踢踩踪踵蹊蹭蹲躁身躲躺车轨转轮软轰轻载较辅辆辈辉辑输辗辙辛辜辞辟辣辨辱边达迁迅过迎运近返还这进远违连迟迫迭述迷迹追退送适逃逆选逊透逐递途逗通逛逝速造逢逻逼遇遍道遗遣遥遭遮遵避邃那邪邮邹邻郁郎部都鄂配酒酬酱酵酷酸醉醒采釉释里重野量金鉴针钉钓钛钟钢钥钩钮钱钵钻铁铜铠铭银铺链销锁锂锅锐错锚锢键锯锻镇镊镜镳长门闪闭问闲间闷闸闹闻阀阁阅阈阉阔阖队阱防阳阴阵阶阻阿附际陆陈陋陌降限陕陡院除险陪陶陷隆随隐隔隙障难雅集雏雕雨雪零雷雾需震霉霜霞露青静非靠面革鞋鞭韧韩韭音韵页顶项顺须顾顿预领颇颈频颗题颜额颤风飞食餐饥饭饮饰饱饲饺饼饿馆馈馋首香馨马驱驶驻驾驿骂骆验骑骗骚骤骨髌高鬼魂魔鱼鲜鲤鲫鳖鸟鸡鸣鸦鸭鸽鸿鹤麻黄黏黑默黛黢鼓鼠鼻齐齿龄龈龘龙鿿!()+,:;?`~ノ \ No newline at end of file + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~—、。〇《》「」【】一丁七万三上下不与丑专且世丘业东丝丞丢两严丧个中丰串临为主丽举久么义之乌乎乏乐乒乓乘乙九也习乡书买乱了予争事二于亏云互五井亚些亡交亦产享京亮亲人亿什仁仅今介仍从仓仔他仗付仙代令以仪们仰件价任份仿企伏伐休众优伙会伞传伤伦伪伫估伴伸似但位低住佐佑体何余佛作你佩佬佳使例供依侠侣侧便促俄俗保信俩俭修俱倍倒候借倦债值假偎偏做停健偶偷偿傅傍储催傲傻像僵僻儿兀允元兄充兆先光克免兑兔党兜入全八公六共关兴其具典养兼冀内册再冒冗写军农冠冤冥冬冰冲决况冷冻净准凉凌减凑凛几凡凭凳凶出击函刀刃分切划列刘则刚创初删判利别刮到制刷券刹刺刻剁剂前剔剖剥剧剩剪副割力劝办功加务劣动助努励劲劳势勃勇勉勒勤勾勿包匆匍匐匕化北匙匠匹区医匾匿十千升午半华协卓单卖南博占卡卢卦卧卫卯印危即却卷卸厂厅历厉压厌厕厘厚原厢厨去县参又及友双反发叒叔叕取受变叙叠口古句另叨只叫召可台史右叶号司叹吃各合吉同名后吐向吓吗吞吟否吧吨含听启吱吴吵吸吹吾呀呆呈告呕呗员呛呢呦周味呵呼命咄咋和咐咕咙咨咩咪咬咱咳咸咽哀品哄哆哇哈响哒哔哗哟哥哦哨哩哪哭哲唉唐唤唬售唯唱唾商啊啥啦啪啸喂善喉喊喜喝喧喵喷喻嗓嗖嗜嗡嗦嗨嗯嗽嘀嘎嘛嘱嘲嘴嘿器噩噪噶噼嚣囊四回因团园困围固国图圆圈土圣在地圳场圾址均坊坎坏坐坑块坚坛坞坟坠坡坦坷垃垄型垢垮埋城埔域培基堂堆堑堡堤堪堵塌塑塔塘塞填境墓墙增墨壁士壮声壳壶壹处备复夏夕外多夜够大天太夫夯失头夸夹夺奇奈奋奏契奔奖套奢女奶奸她好如妄妇妈妒妙妥妨妹妻始姐姑姓委姨姿威娃娜娱婆婚媒媚嫁嫉嫌嫖嫩子孔孕字存季孤学孩宁它宅守安宋完宏宗官定宛宜宝实宠审客宣室宫宰害宵家容宽宾宿寂寄密寇富寒寓寝察寸对寺寻导寿封射将尉尊小少尔尖尘尚尝尤尬就尴尸尺尼尽尾尿局屁层居屈届屋屎屏屑展属屠屯山岁岂岔岖岗岛岸峨峰崎崩嵌巍川州工左巧巨差己已巴巷巾币市布师希帖帘带帧席帮常帽幅幕幢干平年并幸幺幻幼幽广庄庆床序库应底店庙废度座庭庶康庸延建廿开异弃弄弈弊式引弟张弥弯弱弹强归当录形彩彰影彻彼往征径待很律徐徒徕得徙御循微德心必忆忍忐忑志忘忙忧快念忽怀态怎怒怔怕思怠怡急怦性怨怪怯总怼恋恍恐恒恢恨恩恭息恰恶恼悄悉悍悔悖悚悟患悦您悬悲情惊惑惕惜惠惧惨惫惬惯惰想惹愁愈愉意愚感愣愤愧愿慎慕慢慨憋憧憨憬憾懂懈懑懒懦懵戏成我或战戚截戳戴户戾房所扇手才扎扑扒打扔托扣执扩扫扬扰扳批扼找承技抄抉把抑抓投抖抗折抛抢护报抬抱抵抹抽担拆拉拌拍拒拓拔拖拘拙招拜拟拥拦拧拨择括拳拷拼拽拾拿持挂指按挑挖挚挡挣挤挥挨挫振挺捆捉捏捕捞损捡换捣捧据捷掂授掉掌掐排掘探接控推掩措掰掺描提插握揣揭搁搏搓搜搞搬搭携摁摄摆摇摊摔摘摧摩摸撑撒撞撤播撰撸撼擀擅操擎擦攀攒支收改攻放政故效敌敏救教敛敞敢散敬数敲整敷文斐斑斗料斜斤斥斧斩断斯新方施旁旅旋族旗无既日旦旧旨早旬时旷昂明易星映春昨是昵昼显晃晒晕晚晦晨普景晰晶智暂暇暑暖暗暮暴曦曲更曾替最月有朋服朗望朝期朦木未末本术朴朵机杀杂权杆李杏材村杜束杠条来杨杭杯杰東松板极构析枕林枚果枝枢枪枫枯架某柒染柔柜查柯柳柴柿栀标栈栋栏树栓校样核根格栽桂桃框案桌桑档桥桩桶梁梦梯械梳检棉棒棘棚森棵椅植椒楔楚楼概榜槛槽樊模横橙欠次欢欣欧欲欺款歉歌止正此步武歪死殉殊残段殿毁毅毋母每毒比毕毛毫氏民气氛氧水永汁求汇汉汕汗江池污汤汪汲沉沙沟没沦沮河油治沾沿泄泉泊泌法泛泡波泣泥注泪泯泰泳泽洁洋洒洗洛洞津洪洲活派流浅浇测济浏浑浓浩浪浮浴海浸涂消涉涌润涨涩涯液涵淀淋淘淡淫深淳混淹添清渐渗渠渡渣温港渲渴游湖湘湾湿溃源溢溯滋滑滙滚滞满滤滥滩滴漂漏漓演漫漱潘潜潦潭潮澎澡澳激瀑火灭灯灰灵灼灾炉炎炒炕炖炫炮炸点炼烁烂烈烟烤烦烧烫热烽焉焕焖無焦然煌煎照煲熄熊熙熟熬熵燕燥爆爬爱父爷爸爽片版牌牙牛物牲牵特牺犊犯状犹狂狐狗狠独狱狼猛猜猝猪猫献猴玄率玉王玛玩环现玻珍珠班球理琐琢瑞璃瓜瓢瓦瓶甚甜生甥用甩田由甲申电男画畅界留略番畴疆疏疑疗疙疫疮疯疲疼疾病症痍痒痕痘痛痪瘦瘩瘫瘾癌登白百的皇皑皮盆盈益监盒盖盗盘盛盟目盯盲直相盼盾省眉看真眠眨眩眯眶眼着睁睐睛睡督睹睿瞅瞎瞥瞬矛知矩短石矿码砌砍研砖砥破砺础硅硌硕硬确碌碍碎碑碗碟碰磁磕磨磷示礼社祈祖祝神票祭祸禁福离秀私秉秋种科秒租积称移稀程稍税稚稳稿究穷空穿突窃窄窍窒窗窝立站竞竟章童端竹笑笔符笨第笼等筋筑答策筛筷筹签简算管箭箱篇篝篡簧簿籍米类粉粒粗粘粹粽精糊糕糖糙糟系素索紧累繁纠红纤约级纪纭纯纱纲纳纷纸线练组细织终绍经绑绒结绕绘给绚络绝统继绩绪续绰绳维绷综绿缀缆缓编缘缚缝缠缩缭缴缺网罕罗罢罩罪置署羊美羞羡群羽翁翠翻翼耀老考者而耍耐耗耦耳耸耻耽聊职联聘聚聪聿肃肉肋肌肖肚肝肠股肢肤肥肩肯育肺肾肿胀背胖胚胜胞胡胧胶胸胺能脂脆脉脏脑脚脱脸脾腊腔腕腰腹腺腻腾腿膀膏膜膝膨臂臃自臭至致舅舍舒舞舟航般良艰色艳艾节芒芙芦芬芯花芳芹苍苏苗苛若苦英苹茂范茄茫茶草荐荒荡荣荤荫药荼莓莘莫获菌菜菩菲萊萌萎营萦萨落著葛葩葫葵蒂蒙蒜蒸蓝蔷蔽蕉蕴薄薅薇薛薪藏虎虐虑虚虫虹虽虾蚁蚂蚊蛇蛊蛋蛙蛮蛳蛾蜂蜕蜜蝇螃融螺蟹血行衍衔街衡衣补表衩衫衰衷袁袋袍被袭裁裂装裕裙裤裸裹褒褪襄西要覆见观规视览觉角解触言警计订认讨让讪训议讯记讲许论讽设访诀证评识诈诉诊词译试诗诙诚话诞诠询该详诧语误说诶请诸诺读课谁调谅谈谊谋谏谐谓谔谛谢谨谬谱谴谷豁豆象豫貌贝负贡财责败账货质贩贪贫贬购贯贴贵费贺贼资赋赏赖赘赚赛赞赢走赴赵赶起趁超越趋趟趣足跃跌跑距跟跨跪路跳践踏踢踩踪踵蹈蹊蹭蹲躁身躲躺车轨转轮软轰轻载较辅辆辈辉辐辑输辗辙辛辜辞辟辣辨辱边达迁迄迅过迎运近返还这进远违连迟迪迫迭述迷迹追退送适逃逆选逊透逐递途逗通逛逝速造逢進逻逼遇遍道遗遛遣遥遭遮遵避邀邃那邪邮邹邻郁郎郑部都鄂配酒酬酯酱酵酷酸醉醒采釉释里重野量金鉴针钉钓钛钟钢钥钩钮钱钵钻铁铜铠铭银铺链销锁锂锅锐错锚锢键锯锻镇镊镖镜镳长门闪闭问闲间闷闸闹闻阀阁阅阈阉阔阖队阱防阳阴阵阶阻阿附际陆陈陋陌降限陕陡院除险陪陶陷隆随隐隔隙障难雄雅集雏雕雨雪零雷雾需震霉霓霜霞露霸青静非靠面革鞋鞭韧韩韭音韵页顶项顺须顾顿预领颇颈频颗题颚颜额颤风飞食餐饥饭饮饰饱饲饺饼饿馅馆馈馋首香馨马驯驰驱驶驻驾驿骂骆验骑骗骚骤骨髌高鬼魂魅魔鱼鲜鲤鲫鳖鸟鸡鸣鸦鸭鸽鸿鹤麻黄黏黑默黛黢鼓鼠鼻齐齿龄龈龘龙鿿!()+,:;?`|~ノ \ No newline at end of file diff --git a/themes/Ying/static/font/zql-v2-subset.woff b/themes/Ying/static/font/zql-v2-subset.woff index a14b1a2f..585cb14f 100644 Binary files a/themes/Ying/static/font/zql-v2-subset.woff and b/themes/Ying/static/font/zql-v2-subset.woff differ diff --git a/themes/Ying/static/font/zql-v2-subset.woff2 b/themes/Ying/static/font/zql-v2-subset.woff2 index c78c3d83..3b9605f0 100644 Binary files a/themes/Ying/static/font/zql-v2-subset.woff2 and b/themes/Ying/static/font/zql-v2-subset.woff2 differ