本文目录导读:

我来帮你编写一个自动打包并上传的脚本,以下是几种常见场景的示例:
Python项目打包上传到PyPI
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
自动打包并上传到PyPI的脚本
"""
import os
import sys
import subprocess
import shutil
from pathlib import Path
def clean_build():
"""清理之前的构建文件"""
dirs_to_clean = ['build', 'dist', '*.egg-info']
for dir_pattern in dirs_to_clean:
for path in Path('.').glob(dir_pattern):
if path.is_dir():
shutil.rmtree(path)
print(f"清理目录: {path}")
def bump_version(version_type='patch'):
"""自动更新版本号"""
# 使用bump2version或手动更新
try:
subprocess.run(['bump2version', version_type], check=True)
print(f"版本已更新 ({version_type})")
except subprocess.CalledProcessError:
print("请安装bump2version: pip install bump2version")
sys.exit(1)
def build_package():
"""构建包"""
print("开始构建包...")
try:
subprocess.run([sys.executable, '-m', 'build'], check=True)
print("包构建成功!")
except subprocess.CalledProcessError:
print("构建失败")
sys.exit(1)
def upload_to_pypi(repository='pypi'):
"""上传到PyPI"""
print(f"上传到 {repository}...")
# 检查是否有twine
try:
subprocess.run(['twine', '--version'], check=True, capture_output=True)
except subprocess.CalledProcessError:
print("请安装twine: pip install twine")
sys.exit(1)
try:
if repository == 'testpypi':
cmd = ['twine', 'upload', '--repository', 'testpypi', 'dist/*']
else:
cmd = ['twine', 'upload', 'dist/*']
subprocess.run(cmd, check=True)
print(f"上传到 {repository} 成功!")
except subprocess.CalledProcessError:
print(f"上传到 {repository} 失败")
sys.exit(1)
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description='自动打包并上传Python包')
parser.add_argument('--bump', choices=['major', 'minor', 'patch'],
help='更新版本号类型')
parser.add_argument('--test', action='store_true',
help='上传到TestPyPI')
parser.add_argument('--skip-clean', action='store_true',
help='跳过清理步骤')
args = parser.parse_args()
# 1. 更新版本(可选)
if args.bump:
bump_version(args.bump)
# 2. 清理
if not args.skip_clean:
clean_build()
# 3. 构建
build_package()
# 4. 上传
if args.test:
upload_to_pypi('testpypi')
else:
upload_to_pypi()
print("打包上传完成!")
if __name__ == '__main__':
main()
Docker镜像打包上传
#!/bin/bash
# docker-build-and-push.sh
# Docker镜像自动打包上传脚本
set -e # 遇到错误立即退出
# 配置
DOCKER_REGISTRY="registry.example.com"
IMAGE_NAME="myapp"
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "latest")
DOCKERFILE="Dockerfile"
CONTEXT="."
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查依赖
check_dependencies() {
if ! command -v docker &> /dev/null; then
print_error "Docker未安装"
exit 1
fi
}
# 登录到仓库
login_registry() {
if [ -n "$DOCKER_REGISTRY" ]; then
print_info "登录到 $DOCKER_REGISTRY..."
# 优先使用环境变量,如果没有则交互式输入
if [ -n "$DOCKER_USERNAME" ] && [ -n "$DOCKER_PASSWORD" ]; then
echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" -u "$DOCKER_USERNAME" --password-stdin
else
docker login "$DOCKER_REGISTRY"
fi
fi
}
# 构建Docker镜像
build_image() {
local full_image_name="${DOCKER_REGISTRY:+$DOCKER_REGISTRY/}${IMAGE_NAME}:${TAG}"
print_info "构建镜像: $full_image_name"
# 构建参数
BUILD_ARGS=""
if [ -f ".env" ]; then
BUILD_ARGS="--env-file .env"
fi
docker build \
$BUILD_ARGS \
-t "$full_image_name" \
-f "$DOCKERFILE" \
"$CONTEXT"
# 同时打上latest标签
local latest_image="${DOCKER_REGISTRY:+$DOCKER_REGISTRY/}${IMAGE_NAME}:latest"
docker tag "$full_image_name" "$latest_image"
print_info "镜像构建成功: $full_image_name"
}
# 推送镜像
push_image() {
local full_image_name="${DOCKER_REGISTRY:+$DOCKER_REGISTRY/}${IMAGE_NAME}:${TAG}"
local latest_image="${DOCKER_REGISTRY:+$DOCKER_REGISTRY/}${IMAGE_NAME}:latest"
print_info "推送镜像: $full_image_name"
docker push "$full_image_name"
# 如果tag不是latest,也推送latest
if [ "$TAG" != "latest" ]; then
print_info "推送镜像: $latest_image"
docker push "$latest_image"
fi
print_info "镜像推送成功!"
}
# 清理本地镜像
cleanup() {
print_info "清理构建缓存..."
docker builder prune -f
}
# 使用说明
usage() {
echo "用法: $0 [选项]"
echo "选项:"
echo " -r, --registry URL Docker仓库地址"
echo " -n, --name NAME 镜像名称"
echo " -t, --tag TAG 镜像标签 (默认: git tag 或 latest)"
echo " -f, --file FILE Dockerfile路径 (默认: Dockerfile)"
echo " -c, --context DIR 构建上下文 (默认: .)"
echo " --no-clean 不清理构建缓存"
echo " -h, --help 显示帮助信息"
}
# 主函数
main() {
# 解析参数
while [[ $# -gt 0 ]]; do
case $1 in
-r|--registry)
DOCKER_REGISTRY="$2"
shift 2
;;
-n|--name)
IMAGE_NAME="$2"
shift 2
;;
-t|--tag)
TAG="$2"
shift 2
;;
-f|--file)
DOCKERFILE="$2"
shift 2
;;
-c|--context)
CONTEXT="$2"
shift 2
;;
--no-clean)
NO_CLEAN=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
print_error "未知参数: $1"
usage
exit 1
;;
esac
done
check_dependencies
login_registry
build_image
push_image
if [ "$NO_CLEAN" != "true" ]; then
cleanup
fi
print_info "完成!"
}
main "$@"
Node.js项目打包上传到npm
#!/usr/bin/env node
// publish.js - 自动打包并上传到npm
const { execSync } = require('child_process');
const fs = require('fs-extra');
const path = require('path');
const readline = require('readline');
// 颜色输出
const colors = {
green: (msg) => `\x1b[32m${msg}\x1b[0m`,
red: (msg) => `\x1b[31m${msg}\x1b[0m`,
yellow: (msg) => `\x1b[33m${msg}\x1b[0m`,
blue: (msg) => `\x1b[34m${msg}\x1b[0m`
};
function runCommand(cmd, options = {}) {
console.log(colors.blue(`执行: ${cmd}`));
try {
execSync(cmd, { stdio: 'inherit', ...options });
} catch (error) {
console.error(colors.red(`命令执行失败: ${cmd}`));
process.exit(1);
}
}
async function confirmPrompt(message) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(`${message} (y/n) `, (answer) => {
rl.close();
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
});
});
}
async function publish() {
const args = process.argv.slice(2);
const isDryRun = args.includes('--dry-run');
const shouldBumpVersion = args.includes('--bump');
const registry = process.env.npm_config_registry || 'https://registry.npmjs.org';
console.log(colors.green('=== npm 包自动发布脚本 ==='));
console.log(`仓库地址: ${registry}`);
console.log(`模拟运行: ${isDryRun ? '是' : '否'}`);
console.log(''); // 空行
// 1. 检查是否有未提交的更改
try {
const status = execSync('git status --porcelain').toString();
if (status.trim()) {
console.log(colors.yellow('警告: 存在未提交的更改:'));
console.log(status);
if (!isDryRun) {
const answer = await confirmPrompt(colors.yellow('是否继续?'));
if (!answer) {
console.log('发布取消');
process.exit(0);
}
}
}
} catch (error) {
console.error(colors.red('Git检查失败'));
process.exit(1);
}
// 2. 更新版本号(可选)
if (shouldBumpVersion && !isDryRun) {
console.log(colors.green('更新版本号...'));
runCommand('npm version patch', { stdio: 'pipe' });
}
// 3. 清理和构建
console.log(colors.green('清理和构建...'));
// 清理
const dirsToClean = ['dist', 'build', '.next', 'out'];
dirsToClean.forEach(dir => {
if (fs.existsSync(dir)) {
fs.removeSync(dir);
console.log(`清理目录: ${dir}`);
}
});
// 运行构建
const packageJson = fs.readJsonSync('package.json');
if (packageJson.scripts.build) {
console.log(colors.green('运行构建脚本...'));
runCommand('npm run build');
} else {
console.log(colors.yellow('未找到构建脚本,跳过构建'));
}
// 4. 检查dist目录
const distDir = packageJson.main || packageJson.module || 'dist';
if (!fs.existsSync(distDir)) {
console.log(colors.yellow(`警告: ${distDir} 目录不存在`));
if (!isDryRun) {
const answer = await confirmPrompt('是否继续发布?');
if (!answer) {
console.log('发布取消');
process.exit(0);
}
}
}
// 5. 登录npm(如果需要)
try {
const whoami = execSync('npm whoami').toString().trim();
console.log(colors.green(`已登录: ${whoami}`));
} catch {
console.log(colors.yellow('未登录npm'));
if (!isDryRun) {
runCommand('npm login');
}
}
// 6. 发布
console.log(colors.green('发布到npm...'));
if (isDryRun) {
console.log(colors.yellow('模拟运行,不会实际发布'));
runCommand('npm pack --dry-run');
} else {
// 检查是否发布到私有仓库
const isPrivate = packageJson.private;
if (isPrivate) {
console.log(colors.yellow('这是私有包,将发布到私有仓库'));
}
runCommand('npm publish');
}
console.log(colors.green('\n=== 发布完成! ==='));
// 7. 创建git tag(可选)
if (!isDryRun) {
const tagVersion = execSync('node -e "console.log(require(\'./package.json\').version)"').toString().trim();
try {
execSync(`git tag v${tagVersion}`);
execSync('git push --tags');
console.log(colors.green(`已创建Git标签: v${tagVersion}`));
} catch (error) {
console.log(colors.yellow('Git标签创建失败,请手动创建'));
}
}
}
// 帮助信息
function showHelp() {
console.log(`
用法: node publish.js [选项]
选项:
--dry-run 模拟运行,不实际发布
--bump 自动更新版本号 (patch)
--help 显示帮助信息
`);
}
if (process.argv.includes('--help')) {
showHelp();
} else {
publish().catch(console.error);
}
通用部署脚本(适用于任何类型的项目)
#!/bin/bash
# deploy.sh - 通用部署脚本
set -e
# 配置
PROJECT_NAME="my-project"
BUILD_DIR="./build"
DEPLOY_SERVER="user@example.com"
DEPLOY_PATH="/var/www/${PROJECT_NAME}"
BACKUP_PATH="/var/www/backups/${PROJECT_NAME}"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
# 检查必要的命令
check_requirements() {
local commands=("git" "tar" "ssh" "rsync")
for cmd in "${commands[@]}"; do
if ! command -v $cmd &> /dev/null; then
log_error "需要安装 $cmd"
exit 1
fi
done
}
# 获取 Git 信息
get_git_info() {
COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
DEPLOY_DATE=$(date +%Y%m%d_%H%M%S)
VERSION="${BRANCH_NAME}-${COMMIT_HASH}-${DEPLOY_DATE}"
log_info "部署版本: $VERSION"
}
# 预处理:运行测试
run_tests() {
if [ -f "package.json" ] && grep -q '"test"' package.json; then
log_step "运行测试..."
npm test || {
log_error "测试失败"
exit 1
}
fi
if [ -f "pytest.ini" ] || [ -f "setup.cfg" ] && grep -q "\[tool:pytest\]" setup.cfg; then
log_step "运行Python测试..."
python -m pytest || {
log_error "测试失败"
exit 1
}
fi
}
# 构建项目
build_project() {
log_step "构建项目..."
# 根据项目类型选择构建方式
if [ -f "package.json" ]; then
log_info "检测到 Node.js 项目"
npm ci --production
if grep -q '"build"' package.json; then
npm run build
fi
elif [ -f "Makefile" ]; then
log_info "使用 Makefile 构建"
make build
elif [ -f "Dockerfile" ]; then
log_info "检测到 Docker 项目"
docker build -t "${PROJECT_NAME}:${VERSION}" .
fi
log_info "构建完成"
}
# 创建部署包
create_deploy_package() {
log_step "创建部署包..."
DEPLOY_PACKAGE="/tmp/${PROJECT_NAME}-${VERSION}.tar.gz"
# 排除不需要的文件
tar -czf "$DEPLOY_PACKAGE" \
--exclude=".git" \
--exclude="node_modules" \
--exclude="__pycache__" \
--exclude=".env" \
--exclude="*.log" \
.
log_info "部署包创建成功: $DEPLOY_PACKAGE"
}
# 备份当前版本
backup_current() {
log_step "备份当前版本..."
ssh "$DEPLOY_SERVER" "mkdir -p $BACKUP_PATH"
local backup_name="${DEPLOY_DATE}_${COMMIT_HASH}"
ssh "$DEPLOY_SERVER" "if [ -d $DEPLOY_PATH/current ]; then \
cp -r $DEPLOY_PATH/current $BACKUP_PATH/$backup_name; \
log_info '备份完成'; \
fi"
}
# 部署到服务器
deploy_to_server() {
log_step "部署到服务器..."
# 上传部署包
scp "$DEPLOY_PACKAGE" "$DEPLOY_SERVER:/tmp/"
# 在服务器上解压并更新
ssh "$DEPLOY_SERVER" << EOF
set -e
# 创建部署目录
mkdir -p $DEPLOY_PATH/releases/$VERSION
# 解压部署包
tar -xzf /tmp/${PROJECT_NAME}-${VERSION}.tar.gz \
-C $DEPLOY_PATH/releases/$VERSION
# 创建或更新符号链接
ln -sfn $DEPLOY_PATH/releases/$VERSION $DEPLOY_PATH/current
# 复制配置文件
if [ -f $DEPLOY_PATH/shared/.env ]; then
cp $DEPLOY_PATH/shared/.env $DEPLOY_PATH/current/
fi
# 重启服务
if command -v systemctl &> /dev/null; then
systemctl restart $PROJECT_NAME || true
elif command -v supervisorctl &> /dev/null; then
supervisorctl restart $PROJECT_NAME || true
elif [ -f $DEPLOY_PATH/current/restart.sh ]; then
cd $DEPLOY_PATH/current && ./restart.sh
fi
# 清理旧的部署包
rm /tmp/${PROJECT_NAME}-${VERSION}.tar.gz
echo "部署完成"
EOF
log_info "部署到服务器成功"
}
# 健康检查
health_check() {
log_step "健康检查..."
# 等待服务启动
sleep 5
if curl -f http://$DEPLOY_SERVER/health &> /dev/null; then
log_info "健康检查通过"
else
log_warn "健康检查失败,可能需要手动检查"
fi
}
# 清理旧的发布版本
cleanup_old_releases() {
log_step "清理旧版本..."
# 保留最近的5个版本
ssh "$DEPLOY_SERVER" "cd $DEPLOY_PATH/releases && \
ls -t | tail -n +6 | xargs -I {} rm -rf {} 2>/dev/null || true"
# 清理本地临时文件
rm -f "$DEPLOY_PACKAGE"
log_info "清理完成"
}
# 回滚到上一个版本
rollback() {
log_step "执行回滚..."
ssh "$DEPLOY_SERVER" << EOF
local previous=\$(ls -t $DEPLOY_PATH/releases/ | sed -n '2p')
if [ -n "\$previous" ]; then
ln -sfn $DEPLOY_PATH/releases/\$previous $DEPLOY_PATH/current
echo "回滚到版本: \$previous"
else
echo "没有可回滚的版本"
exit 1
fi
EOF
health_check
}
# 主函数
main() {
local action="${1:-deploy}"
echo "================================================"
echo " 部署脚本 - $PROJECT_NAME"
echo " 操作: $action"
echo "================================================"
check_requirements
case "$action" in
deploy)
get_git_info
run_tests || true # 允许测试失败继续
build_project
create_deploy_package
backup_current
deploy_to_server
health_check
cleanup_old_releases
log_info "部署完成!"
;;
rollback)
rollback
;;
status)
ssh "$DEPLOY_SERVER" "cd $DEPLOY_PATH && ls -l current && echo '部署路径:' && readlink -f current"
;;
*)
echo "用法: $0 {deploy|rollback|status}"
exit 1
;;
esac
}
main "$@"
使用方法
Python脚本:
# 上传到PyPI python publish.py # 上传到TestPyPI python publish.py --test # 自动更新patch版本 python publish.py --bump patch
Docker脚本:
# 基本使用 chmod +x docker-build-and-push.sh ./docker-build-and-push.sh # 指定自定义参数 ./docker-build-and-push.sh -r myregistry.com -n myapp -t v1.0.0 # 模拟运行 ./docker-build-and-push.sh --dry-run
npm脚本:
# 发布到npm node publish.js # 模拟运行 node publish.js --dry-run # 自动更新版本号 node publish.js --bump
通用部署脚本:
# 部署 chmod +x deploy.sh ./deploy.sh deploy # 回滚 ./deploy.sh rollback # 查看部署状态 ./deploy.sh status
配置建议
-
环境变量管理:
# .env 文件 DOCKER_USERNAME=your_username DOCKER_PASSWORD=your_password NPM_TOKEN=your_npm_token
-
GitHub Actions CI/CD: 创建
.github/workflows/deploy.yml配置文件 -
使用release-it自动化:
npm install -g release-it release-it --dry-run
这些脚本可以根据你的具体需求进行修改和扩展,记得先在一个测试环境中验证脚本的正确性,然后再用于生产环境。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播