2308
This commit is contained in:
1 parent
450a28c81d
commit
0be8aa9668
7 files changed
+588
-203
No files matched your search
@@ -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
|
||||
+431
-27
@@ -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 }}"
|
||||
}
|
||||
}'
|
||||
@@ -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
|
||||
+127
-1
@@ -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)}")
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
|
||||
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~—、。《》「」一丁七万三上下不与丑专且世丘业东丝丞丢两严丧个中丰串临为主丽举久么义之乌乎乏乐乒乓乘九也习乡书买乱了予争事二于亏云互五井亚些亡交亦产享京亮亲人亿什仁仅今介仍从仓仔他仗付仙代令以仪们仰件价任份仿企伏伐休众优伙会伞传伤伦伪伫估伴伸似但位低住佐佑体何余佛作你佩佬佳使例供依侠侣侧便促俄俗保信俩俭修俱倍倒候借倦债值假偎偏做停健偶偷偿傅傍储催傲傻像僻儿兀允元兄充兆先光克免兑兔党兜入全八公六共关兴其具典养兼冀内册再冒冗写军农冠冤冥冬冰冲决况冷冻净准凉凌减凑凛几凡凭凳凶出击函刀刃分切划列刘则刚创初删判利别刮到制刷券刹刺刻前剔剖剥剧剩剪副割力劝办功加务劣动助努励劲劳势勇勉勒勤勾勿包匆匍匐匕化北匙匠匹区医匾匿十千升午半华协卓单卖南博占卡卢卦卧卫卯印危即却卷卸厂厅历厉压厌厕厘厚原厢厨去县参又及友双反发叒叔叕取受变叙叠口古句另叨只叫召可台史右叶号司叹吃各合吉同名后吐向吓吗吞否吧吨含听启吱吴吵吸吹吾呀呆呈告呕呗员呛呢呦周味呵呼命咄咋和咐咕咙咨咩咪咬咱咳咸咽哀品哄哆哇哈响哒哗哥哦哨哪哭哲唉唐唤唬售唯唾商啊啥啦啸喂善喉喊喜喝喧喵喷喻嗓嗖嗜嗡嗦嗨嗯嗽嘀嘎嘛嘱嘲嘴嘿器噩噪嚣囊四回因团园困围固国图圆圈土圣在地圳场址均坊坎坏坐坑块坚坛坞坟坠坡坦坷垄型垢埋城埔域培基堂堆堑堡堤堪堵塌塑塔塘塞填境墓墙增墨壁士壮声壳壶处备复夏夕外多夜够大天太夫失头夸夹夺奇奈奋奏奖套奢女奶奸她好如妄妇妈妒妙妥妨妹妻始姐姑姓委姨姿威娃娜娱婆媒媚嫁嫉嫌嫖嫩子孔字存季孤学孩宁它守安宋完宏宗官定宛宜宝实宠审客宣室宫宰害宵家容宽宾宿寂寄密寇富寒寓寝察寸对寺寻导寿封射将尉尊小少尔尖尘尝尤尬就尴尸尺尼尽尾局层居屈届屋屎屏屑展属屠屯山岁岂岔岖岗岛岸峨峰崎崩嵌巍川州工左巧巨差己已巴巷巾币市布师希帖帘带帧帮常帽幅幕幢干平年并幸幺幻幼幽广庄庆床序库应底店度座庭庶康庸延建廿开异弃弄弈弊式引弟张弥弯弱弹强归当录形彩彰影彻彼往征径待很律徐徒得徙御循微德心必忆忍忐忑志忘忙忧快念忽怀态怎怒怔怕思怠怡急怦性怨怪怯总怼恋恍恐恒恢恨恩恭息恰恶恼悄悉悍悔悖悚悟患悦您悬悲情惊惑惕惜惠惧惨惫惬惯惰想惹愁愈愉意愚感愣愤愧愿慎慕慢慨憧憨憬憾懂懈懑懒懦懵戏成我或战戚截戳戴户戾房所扇手才扎扑打扔托扣执扩扫扬扰扳批扼找承技抄抉把抑抓投抖抗折抛抢护报抬抱抵抹抽担拆拉拌拍拒拓拔拖拘拙招拜拟拥拦拧拨择括拳拷拼拽拾拿持挂指按挑挖挚挡挣挤挥挨挫振挺捉捏捕捞损捡换捧据捷掂授掉掌掐排探接控推掩措掰掺描提插握揣揭搁搏搓搜搞搬搭携摁摄摆摇摊摔摘摧摩摸撑撒撞撤播撰撸撼擅操擎擦攒支收改攻放政故效敌敏救教敛敞敢散敬数敲整敷文斑斗料斜斥斧斩断斯新方施旁旅族旗无既日旦旧旨早旬时旷明易星映春昨是昵昼显晃晒晕晚晦晨普景晰晶智暂暇暑暖暗暴曦曲更曾替最月有朋服望朝期朦木未末本术朴朵机杀杂权杆李杏材村杜束杠条来杨杭杯東松板极构析枕林枚果枪枫枯架某柒染柔柜查柯柳柴柿栀标栈栋栏树校样核根格栽桂桃框案桌桑档桥桶梁梦梯械梳检棉棒棘棚森棵椅植椒楚楼概榜槛槽樊模横橙欠次欢欣欧欲欺款歉歌止正此步武歪死殉殊残段殿毁毅毋母每毒比毕毛毫氏民气氛氧水永汁求汇汉汕汗江池污汤汪汲沉沙沟没沦沮河油治沾沿泄泉泌法泛泡波泣泥注泪泯泳泽洁洋洒洗洛洞津洪洲活派流浅浇测济浏浑浓浪浮浴海浸涂消涉润涨涩涯液涵淀淋淘淡淫深淳混添清渐渗渠渡温港渲渴游湖湘湾湿溃源溢溯滋滑滚滞满滤滥滴漂漏漓演漫漱潘潜潦潭潮澎澡澳激瀑火灭灯灰灵灾炉炎炒炕炫炮炸点炼烁烂烈烟烤烦烧烫热烽焉焕焖焦然煌煎照煲熄熊熙熟熬熵燥爆爬爱父爷爸爽片版牌牙牛物牲牵特牺犊犯状犹狂狗狠独狱狼猛猜猝猫献猴玄率玉王玛玩环现玻珍珠班球理琐琢瑞璃瓜瓢瓦瓶甚甜生甥用甩田由甲申电男画畅界留略番畴疏疑疗疙疫疮疯疲疼疾病症痍痒痕痘痛痪瘦瘩瘫瘾癌登白百的皇皑皮盆盈益监盒盖盗盘盛盟目盯盲直相盼盾省眉看真眠眩眶眼着睁睛睡督睹睿瞅瞎瞥瞬矛知矩短石矿码砌砍研砖砥破砺础硅硌硬确碌碍碎碑碗碰磁磨示礼社祖祝神票祸禁福离秀私秉秋种科秒租积称移稀程稍税稚稳稿究穷空穿突窃窄窒Line truncated
|
||||
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~—、。〇《》「」【】一丁七万三上下不与丑专且世丘业东丝丞丢两严丧个中丰串临为主丽举久么义之乌乎乏乐乒乓乘乙九也习乡书买乱了予争事二于亏云互五井亚些亡交亦产享京亮亲人亿什仁仅今介仍从仓仔他仗付仙代令以仪们仰件价任份仿企伏伐休众优伙会伞传伤伦伪伫估伴伸似但位低住佐佑体何余佛作你佩佬佳使例供依侠侣侧便促俄俗保信俩俭修俱倍倒候借倦债值假偎偏做停健偶偷偿傅傍储催傲傻像僵僻儿兀允元兄充兆先光克免兑兔党兜入全八公六共关兴其具典养兼冀内册再冒冗写军农冠冤冥冬冰冲决况冷冻净准凉凌减凑凛几凡凭凳凶出击函刀刃分切划列刘则刚创初删判利别刮到制刷券刹刺刻剁剂前剔剖剥剧剩剪副割力劝办功加务劣动助努励劲劳势勃勇勉勒勤勾勿包匆匍匐匕化北匙匠匹区医匾匿十千升午半华协卓单卖南博占卡卢卦卧卫卯印危即却卷卸厂厅历厉压厌厕厘厚原厢厨去县参又及友双反发叒叔叕取受变叙叠口古句另叨只叫召可台史右叶号司叹吃各合吉同名后吐向吓吗吞吟否吧吨含听启吱吴吵吸吹吾呀呆呈告呕呗员呛呢呦周味呵呼命咄咋和咐咕咙咨咩咪咬咱咳咸咽哀品哄哆哇哈响哒哔哗哟哥哦哨哩哪哭哲唉唐唤唬售唯唱唾商啊啥啦啪啸喂善喉喊喜喝喧喵喷喻嗓嗖嗜嗡嗦嗨嗯嗽嘀嘎嘛嘱嘲嘴嘿器噩噪噶噼嚣囊四回因团园困围固国图圆圈土圣在地圳场圾址均坊坎坏坐坑块坚坛坞坟坠坡坦坷垃垄型垢垮埋城埔域培基堂堆堑堡堤堪堵塌塑塔塘塞填境墓墙增墨壁士壮声壳壶壹处备复夏夕外多夜够大天太夫夯失头夸夹夺奇奈奋奏契奔奖套奢女奶奸她好如妄妇妈妒妙妥妨妹妻始姐姑姓委姨姿威娃娜娱婆婚媒媚嫁嫉嫌嫖嫩子孔孕字存季孤学孩宁它宅守安宋完宏宗官定宛宜宝实宠审客宣室宫宰害宵家容宽宾宿寂寄密寇富寒寓寝察寸对寺寻导寿封射将尉尊小少尔尖尘尚尝尤尬就尴尸尺尼尽尾尿局屁层居屈届屋屎屏屑展属屠屯山岁岂岔岖岗岛岸峨峰崎崩嵌巍川州工左巧巨差己已巴巷巾币市布师希帖帘带帧席帮常帽幅幕幢干平年并幸幺幻幼幽广庄庆床序库应底店庙废度座庭庶康庸延建廿开异弃弄弈弊式引弟张弥弯弱弹强归当录形彩彰影彻彼往征径待很律徐徒徕得徙御循微德心必忆忍忐忑志忘忙忧快念忽怀态怎怒怔怕思怠怡急怦性怨怪怯总怼恋恍恐恒恢恨恩恭息恰恶恼悄悉悍悔悖悚悟患悦您悬悲情惊惑惕惜惠惧惨惫惬惯惰想惹愁愈愉意愚感愣愤愧愿慎慕慢慨憋憧憨憬憾懂懈懑懒懦懵戏成我或战戚截戳戴户戾房所扇手才扎扑扒打扔托扣执扩扫扬扰扳批扼找承技抄抉把抑抓投抖抗折抛抢护报抬抱抵抹抽担拆拉拌拍拒拓拔拖拘拙招拜拟拥拦拧拨择括拳拷拼拽拾拿持挂指按挑挖挚挡挣挤挥挨挫振挺捆捉捏捕捞损捡换捣捧据捷掂授掉掌掐排掘探接控推掩措掰掺描提插握揣揭搁搏搓搜搞搬搭携摁摄摆摇摊摔摘摧摩摸撑撒撞撤播撰撸撼擀擅操擎擦攀攒支收改攻放政故效敌敏救教敛敞敢散敬数敲整敷文斐斑斗料斜斤斥斧斩断斯新方施旁旅旋族旗无既日旦旧旨早旬时旷昂明易星映春昨是昵昼显晃晒晕晚晦晨普景晰晶智暂暇暑暖暗暮暴曦曲更曾替最月有朋服朗望朝期朦木未末本术朴朵机杀杂权杆李杏材村杜束杠条来杨杭杯杰東松板极构析枕林枚果枝枢枪枫枯架某柒染柔柜查柯柳柴柿栀标栈栋栏树栓校样核根格栽桂桃框案桌桑档桥桩桶梁梦梯械梳检棉棒棘棚森棵椅植椒楔楚楼概榜槛槽樊模横橙欠次欢欣欧欲欺款歉歌止正此步武歪死殉殊残段殿毁毅毋母每毒比毕毛毫氏民气氛氧水永汁求汇汉汕汗江池污汤汪汲沉沙沟没沦沮河油治沾沿泄泉泊泌法泛泡波泣泥注泪泯泰泳泽洁洋洒洗洛洞津洪洲活派流浅浇测济浏浑浓浩浪浮浴海浸涂消涉涌润涨涩涯液涵淀淋淘淡淫深淳混淹添清渐渗渠渡渣温港渲渴游湖湘湾湿溃源溢溯滋滑滙滚滞满滤滥滩滴漂漏漓演漫漱潘潜潦潭潮澎澡澳激瀑火灭灯灰灵灼灾炉炎炒炕炖炫炮炸点炼烁烂烈烟烤烦烧烫热烽焉焕焖無焦然煌煎照煲熄熊熙熟熬熵燕燥爆爬爱父爷爸爽片版牌牙牛物牲牵特牺犊犯状犹狂狐狗狠独狱狼猛猜猝猪猫献猴玄率玉王玛玩环现玻珍珠班球理琐琢瑞璃瓜瓢瓦瓶甚甜生甥用甩田由甲申电男画畅界留略番畴疆疏疑疗疙疫疮疯疲疼疾病症痍痒痕痘痛痪瘦瘩瘫瘾癌登白百的皇皑皮盆盈益监盒盖盗盘盛盟目盯盲直相盼盾省眉看真眠眨眩眯眶眼着睁睐睛睡督Line truncated
|
||||
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user