Initial commit: AI Interview System

This commit is contained in:
111
2026-01-23 13:57:48 +08:00
commit 95770afe21
127 changed files with 24686 additions and 0 deletions

21
deploy/Dockerfile.backend Normal file
View File

@@ -0,0 +1,21 @@
# 后端 Python 服务
FROM python:3.11-slim
WORKDIR /app
# 配置 pip 使用阿里云镜像
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ && \
pip config set global.trusted-host mirrors.aliyun.com
# 安装依赖
COPY backend/requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt --timeout 120
# 复制源码
COPY backend/ ./
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -0,0 +1,32 @@
# 前端构建
FROM node:18-alpine as builder
WORKDIR /app
# 安装 pnpm
RUN npm install -g pnpm
# 复制依赖文件
COPY frontend/package.json frontend/pnpm-lock.yaml* ./
# 安装依赖
RUN pnpm install --frozen-lockfile || pnpm install
# 复制源码
COPY frontend/ ./
# 构建
RUN pnpm build
# 生产镜像
FROM nginx:alpine
# 复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制 nginx 配置
COPY deploy/nginx/frontend.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

246
deploy/bt_deploy.py Normal file
View File

@@ -0,0 +1,246 @@
#!/usr/bin/env python3
"""
通过宝塔 API 自动部署 AI 面试系统
"""
import requests
import time
import hashlib
import os
import base64
import json
# 禁用 SSL 警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 宝塔配置
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
# 部署配置
DEPLOY_PATH = "/www/wwwroot/ai-interview"
DOMAIN = "interview.test.ai.ireborn.com.cn"
def bt_api(action, data=None, files=None):
"""调用宝塔 API"""
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
if files:
response = requests.post(url, data=data, files=files, timeout=300, verify=False)
else:
response = requests.post(url, data=data, timeout=60, verify=False)
return response.json()
except requests.exceptions.JSONDecodeError:
return {"status": True, "msg": response.text}
except Exception as e:
return {"status": False, "msg": str(e)}
def create_directory(path):
"""创建目录"""
print(f"📁 创建目录: {path}")
result = bt_api("files?action=CreateDir", {"path": path})
if result.get("status") or "已存在" in str(result.get("msg", "")):
print(" ✅ 目录已就绪")
return True
print(f" ⚠️ {result}")
return True # 继续执行
def upload_file(local_path, remote_dir):
"""上传文件到服务器"""
filename = os.path.basename(local_path)
print(f"📤 上传文件: {filename}")
with open(local_path, 'rb') as f:
files = {'f_path': (filename, f)}
data = {'f_path': remote_dir, 'f_name': filename}
result = bt_api("files?action=upload", data, files)
if result.get("status") or result.get("msg") == "上传成功":
print(f" ✅ 上传成功")
return True
print(f" ❌ 上传失败: {result}")
return False
def exec_shell(command):
"""执行 Shell 命令"""
print(f"🔧 执行命令: {command[:80]}...")
result = bt_api("files?action=ExecShell", {"command": command})
return result
def create_website(domain):
"""创建网站"""
print(f"🌐 创建网站: {domain}")
data = {
"webname": json.dumps({"domain": domain, "domainlist": [], "count": 0}),
"path": f"/www/wwwroot/{domain}",
"type_id": 0,
"type": "PHP",
"version": "00",
"port": "80",
"ps": "AI面试系统",
"ftp": "false",
"sql": "false"
}
result = bt_api("site?action=AddSite", data)
if result.get("status") or result.get("siteStatus"):
print(" ✅ 网站创建成功")
return True
if "已存在" in str(result.get("msg", "")):
print(" ⚠️ 网站已存在")
return True
print(f" 结果: {result}")
return True
def set_proxy(domain, target_url):
"""设置反向代理"""
print(f"🔄 设置反向代理: {domain} -> {target_url}")
# 获取站点 ID
sites_result = bt_api("site?action=GetSitesSort")
site_id = None
if sites_result.get("data"):
for site in sites_result["data"]:
if site.get("name") == domain:
site_id = site.get("id")
break
if not site_id:
print(" ⚠️ 未找到站点 ID尝试继续...")
return True
# 设置代理
proxy_data = {
"sitename": domain,
"proxyname": "ai-interview",
"proxydir": "/",
"proxysite": target_url,
"proxysend": "0",
"cache": "0",
"cacheTime": "1",
"subfilter": "[]",
"type": "1",
"advanced": "0"
}
result = bt_api("site?action=CreateProxy", proxy_data)
print(f" 结果: {result}")
return True
def write_remote_file(path, content):
"""写入远程文件"""
print(f"📝 写入文件: {path}")
# 使用 base64 编码内容
encoded = base64.b64encode(content.encode()).decode()
result = bt_api("files?action=SaveFileBody", {
"path": path,
"data": content,
"encoding": "utf-8"
})
if result.get("status"):
print(" ✅ 文件写入成功")
return True
print(f" 结果: {result}")
return True
def main():
print("=" * 60)
print("🚀 AI 语音面试系统 - 自动部署")
print("=" * 60)
print()
# 1. 创建部署目录
print("\n📦 步骤 1: 创建目录结构")
create_directory(DEPLOY_PATH)
create_directory(f"{DEPLOY_PATH}/frontend")
create_directory(f"{DEPLOY_PATH}/backend")
create_directory(f"{DEPLOY_PATH}/deploy")
create_directory(f"{DEPLOY_PATH}/deploy/nginx")
# 2. 写入配置文件
print("\n📝 步骤 2: 写入配置文件")
# .env 文件
env_content = """COZE_PAT_TOKEN=pat_nd1wU47WyPS9GCIyJ1clnH8h1WOQXGrYELX8w73TnSZaYbFdYD4swIhzcETBUbfT
COZE_BOT_ID=7595113005181386792
COZE_WORKFLOW_A_ID=7597357422713798710
COZE_WORKFLOW_C_ID=7597376294612107318
FILE_SERVER_URL=https://files.test.ai.ireborn.com.cn
FILE_SERVER_TOKEN=ai_interview_2026_secret
"""
write_remote_file(f"{DEPLOY_PATH}/deploy/.env", env_content)
# 3. 上传部署包
print("\n📤 步骤 3: 上传部署包")
tar_path = "/Users/a111/Documents/AgentWD/projects/ai-interview-deploy.tar.gz"
if os.path.exists(tar_path):
upload_file(tar_path, "/www/wwwroot")
else:
print(f" ⚠️ 部署包不存在: {tar_path}")
# 4. 解压并部署
print("\n🔧 步骤 4: 解压部署包")
commands = [
f"cd /www/wwwroot && tar -xzf ai-interview-deploy.tar.gz -C {DEPLOY_PATH}",
f"cd {DEPLOY_PATH}/deploy && cp .env .env.bak 2>/dev/null || true",
]
for cmd in commands:
exec_shell(cmd)
time.sleep(1)
# 5. 启动 Docker
print("\n🐳 步骤 5: 启动 Docker 容器")
docker_commands = [
f"cd {DEPLOY_PATH}/deploy && docker-compose down 2>/dev/null || true",
f"cd {DEPLOY_PATH}/deploy && docker-compose up -d --build",
]
for cmd in docker_commands:
print(f" 执行: {cmd}")
result = exec_shell(cmd)
print(f" 结果: {result}")
time.sleep(3)
# 6. 创建网站和反向代理
print("\n🌐 步骤 6: 配置网站")
create_website(DOMAIN)
time.sleep(2)
set_proxy(DOMAIN, "http://127.0.0.1:3000")
# 7. 检查状态
print("\n🔍 步骤 7: 检查部署状态")
result = exec_shell("docker ps --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'")
print(f" 容器状态: {result}")
print()
print("=" * 60)
print("✅ 部署完成!")
print("=" * 60)
print()
print(f"🌐 访问地址:")
print(f" 用户端: http://{DOMAIN}")
print(f" 管理后台: http://{DOMAIN}/admin")
print()
print("⚠️ 如需 HTTPS请在宝塔面板中为该网站申请 SSL 证书")
print("=" * 60)
if __name__ == "__main__":
main()

132
deploy/build_only.py Normal file
View File

@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""
仅执行构建命令
"""
import requests
import time
import hashlib
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
def bt_api(action, data=None, timeout=60):
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:2000]}
except Exception as e:
return {"status": False, "msg": str(e)}
def run_task(shell_body, task_name, wait_time=30):
# 先清理旧任务
result = bt_api("crontab?action=GetCrontab", {"page": 1, "limit": 100})
if isinstance(result, dict) and result.get("data"):
for task in result["data"]:
if isinstance(task, dict) and ("upload_" in task.get("name", "") or "build_" in task.get("name", "")):
bt_api("crontab?action=DelCrontab", {"id": task["id"]})
result = bt_api("crontab?action=AddCrontab", {
"name": task_name,
"type": "minute-n",
"where1": "1",
"sType": "toShell",
"sBody": shell_body,
})
if not result.get("status") or not result.get("id"):
print(f"创建任务失败: {result}")
return None
cron_id = result["id"]
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=600)
print(f"任务 {cron_id} 启动,等待 {wait_time}s...")
time.sleep(wait_time)
log_result = bt_api("crontab?action=GetLogs", {"id": cron_id})
bt_api("crontab?action=DelCrontab", {"id": cron_id})
return log_result
def main():
print("=" * 60)
print("🐳 构建 Docker 容器")
print("=" * 60)
# 先清理磁盘空间
print("\n🧹 步骤 1: 清理磁盘空间...")
cleanup_script = """#!/bin/bash
echo "清理 Docker..."
docker system prune -af --volumes 2>/dev/null || true
docker builder prune -af 2>/dev/null || true
echo ""
echo "清理宝塔日志..."
rm -rf /www/wwwlogs/*.log 2>/dev/null || true
find /www/server/panel/logs -name "*.log" -mtime +1 -delete 2>/dev/null || true
echo ""
echo "磁盘使用:"
df -h /
"""
result = run_task(cleanup_script, "cleanup", wait_time=30)
if result and result.get("msg"):
print(result["msg"][:1000])
# 构建
print("\n🐳 步骤 2: 构建并启动...")
build_script = f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
echo "目录检查:"
ls -la {DEPLOY_PATH}/frontend/ | head -5
ls -la {DEPLOY_PATH}/backend/ | head -5
echo ""
echo "构建并启动..."
docker-compose up -d --build 2>&1
sleep 20
echo ""
echo "容器状态:"
docker ps -a --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}'
echo ""
echo "后端日志:"
docker logs --tail 30 ai-interview-backend 2>&1
echo ""
echo "测试:"
curl -s http://127.0.0.1:8000/health 2>&1 || echo "后端未响应"
echo ""
curl -s -o /dev/null -w "前端: %{{http_code}}" http://127.0.0.1:3000 2>&1
"""
result = run_task(build_script, "build", wait_time=180)
if result and result.get("msg"):
print("\n📋 构建结果:")
print("-" * 60)
print(result["msg"])
print("\n" + "=" * 60)
print("🌐 http://interview.test.ai.ireborn.com.cn")
print("=" * 60)
if __name__ == "__main__":
main()

124
deploy/check_docker.py Normal file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""
检查 Docker 容器状态
"""
import requests
import time
import hashlib
# 禁用 SSL 警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 宝塔配置
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
def bt_api(action, data=None, timeout=60):
"""调用宝塔 API"""
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:2000]}
except Exception as e:
return {"status": False, "msg": str(e)}
def run_and_get_log(shell_body, task_name):
"""创建计划任务,运行并获取日志"""
# 1. 创建任务
result = bt_api("crontab?action=AddCrontab", {
"name": task_name,
"type": "minute-n",
"where1": "1",
"hour": "",
"minute": "",
"week": "",
"sType": "toShell",
"sBody": shell_body,
"sName": "",
"backupTo": "",
"save": "",
"urladdress": ""
})
if not result.get("status") or not result.get("id"):
print(f"创建任务失败: {result}")
return None
cron_id = result["id"]
print(f"任务创建成功ID: {cron_id}")
# 2. 执行任务
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=120)
print("任务已启动,等待执行...")
time.sleep(10)
# 3. 获取日志
log_result = bt_api("crontab?action=GetLogs", {"id": cron_id})
# 4. 删除任务
bt_api("crontab?action=DelCrontab", {"id": cron_id})
return log_result
def main():
print("=" * 60)
print("🔍 检查 Docker 容器状态")
print("=" * 60)
print()
# 检查命令
check_script = f"""#!/bin/bash
echo "========== Docker 容器状态 =========="
docker ps -a
echo ""
echo "========== Docker Compose 状态 =========="
cd {DEPLOY_PATH}/deploy && docker-compose ps 2>&1 || echo "docker-compose 未运行"
echo ""
echo "========== 后端容器日志 (最后 50 行) =========="
docker logs --tail 50 ai-interview-backend 2>&1 || echo "后端容器不存在"
echo ""
echo "========== 检查端口 =========="
netstat -tlnp | grep -E ':(3000|8000)' || ss -tlnp | grep -E ':(3000|8000)' || echo "端口未监听"
echo ""
echo "========== 测试本地服务 =========="
curl -s http://127.0.0.1:8000/health 2>&1 || echo "后端 8000 端口无响应"
curl -s -o /dev/null -w "前端 3000 端口: HTTP %{{http_code}}" http://127.0.0.1:3000 2>&1 || echo "前端 3000 端口无响应"
"""
result = run_and_get_log(check_script, f"check_docker_{int(time.time())}")
if result:
print("\n📋 执行结果:")
print("-" * 60)
if isinstance(result, dict) and result.get("msg"):
print(result["msg"])
else:
print(result)
print()
print("=" * 60)
if __name__ == "__main__":
main()

133
deploy/cleanup_and_build.py Normal file
View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""
清理宝塔旧任务后再构建
"""
import requests
import time
import hashlib
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
def bt_api(action, data=None, timeout=60):
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:2000]}
except Exception as e:
return {"status": False, "msg": str(e)}
def cleanup_old_tasks():
"""清理所有旧的计划任务"""
print("🧹 清理旧计划任务...")
result = bt_api("crontab?action=GetCrontab", {"page": 1, "limit": 100})
deleted = 0
if isinstance(result, list):
for task in result:
if isinstance(task, dict) and task.get("id"):
name = task.get("name", "")
# 删除我们创建的临时任务
if any(x in name for x in ["upload_", "build_", "cleanup", "restart", "check", "create", "update", "rebuild"]):
bt_api("crontab?action=DelCrontab", {"id": task["id"]})
deleted += 1
print(f" 删除: {name}")
elif isinstance(result, dict) and result.get("data"):
for task in result["data"]:
if isinstance(task, dict) and task.get("id"):
name = task.get("name", "")
if any(x in name for x in ["upload_", "build_", "cleanup", "restart", "check", "create", "update", "rebuild"]):
bt_api("crontab?action=DelCrontab", {"id": task["id"]})
deleted += 1
print(f" 删除: {name}")
print(f" 已删除 {deleted} 个旧任务")
return deleted
def run_task(shell_body, task_name, wait_time=30):
result = bt_api("crontab?action=AddCrontab", {
"name": task_name,
"type": "minute-n",
"where1": "1",
"sType": "toShell",
"sBody": shell_body,
})
if not result.get("status") or not result.get("id"):
print(f" 创建任务失败: {result}")
return None
cron_id = result["id"]
print(f" 任务 {cron_id} 已创建")
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=600)
print(f" 执行中,等待 {wait_time}s...")
time.sleep(wait_time)
log_result = bt_api("crontab?action=GetLogs", {"id": cron_id})
bt_api("crontab?action=DelCrontab", {"id": cron_id})
return log_result
def main():
print("=" * 60)
print("🚀 清理并构建")
print("=" * 60)
# 1. 清理旧任务
cleanup_old_tasks()
time.sleep(2)
# 2. 构建
print("\n🐳 构建 Docker 容器...")
build_script = f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
# 清理空间
docker system prune -af 2>/dev/null || true
# 构建
docker-compose up -d --build 2>&1
sleep 15
echo "容器状态:"
docker ps -a --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}'
echo ""
echo "后端日志:"
docker logs --tail 20 ai-interview-backend 2>&1
echo ""
echo "测试:"
curl -s http://127.0.0.1:8000/health || echo "后端未响应"
curl -s -o /dev/null -w " 前端:%{{http_code}}" http://127.0.0.1:3000
"""
result = run_task(build_script, "deploy", wait_time=180)
if result and result.get("msg"):
print("\n📋 结果:")
print("-" * 60)
print(result["msg"])
print("\n" + "=" * 60)
print("🌐 http://interview.test.ai.ireborn.com.cn")
if __name__ == "__main__":
main()

136
deploy/cleanup_bt.py Normal file
View File

@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""
深度清理宝塔面板
"""
import requests
import time
import hashlib
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
def bt_api(action, data=None, timeout=60):
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:2000]}
except Exception as e:
return {"status": False, "msg": str(e)}
def main():
print("=" * 60)
print("🧹 深度清理宝塔面板")
print("=" * 60)
# 1. 删除所有计划任务
print("\n1. 清理计划任务...")
result = bt_api("crontab?action=GetCrontab", {"page": 1, "limit": 100})
deleted = 0
tasks_to_delete = []
if isinstance(result, list):
tasks_to_delete = result
elif isinstance(result, dict) and result.get("data"):
tasks_to_delete = result["data"]
for task in tasks_to_delete:
if isinstance(task, dict) and task.get("id"):
name = task.get("name", "")
# 删除所有临时任务
bt_api("crontab?action=DelCrontab", {"id": task["id"]})
deleted += 1
print(f" 删除: {name} (ID: {task['id']})")
print(f" 共删除 {deleted} 个任务")
# 2. 清理计划任务日志
print("\n2. 清理任务日志...")
result = bt_api("crontab?action=DelLogs")
print(f" 结果: {result}")
# 3. 清理面板日志
print("\n3. 清理面板日志...")
result = bt_api("system?action=ClearCache")
print(f" 结果: {result}")
# 4. 尝试压缩数据库
print("\n4. 压缩数据库...")
result = bt_api("system?action=ReDatabase")
print(f" 结果: {result}")
time.sleep(3)
# 5. 再次尝试创建任务
print("\n5. 测试创建任务...")
result = bt_api("crontab?action=AddCrontab", {
"name": "test_task",
"type": "minute-n",
"where1": "1",
"sType": "toShell",
"sBody": "echo test",
})
if result.get("status") and result.get("id"):
print(f" ✅ 成功! 任务ID: {result['id']}")
# 删除测试任务
bt_api("crontab?action=DelCrontab", {"id": result["id"]})
# 现在执行构建
print("\n6. 执行构建...")
build_result = bt_api("crontab?action=AddCrontab", {
"name": "deploy_docker",
"type": "minute-n",
"where1": "1",
"sType": "toShell",
"sBody": f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
docker system prune -af 2>/dev/null || true
docker-compose up -d --build 2>&1
sleep 15
docker ps -a
docker logs --tail 20 ai-interview-backend 2>&1
curl -s http://127.0.0.1:8000/health || echo "后端未响应"
""",
})
if build_result.get("id"):
cron_id = build_result["id"]
print(f" 任务 {cron_id} 已创建,开始执行...")
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=600)
print(" 等待 180 秒...")
time.sleep(180)
log_result = bt_api("crontab?action=GetLogs", {"id": cron_id})
bt_api("crontab?action=DelCrontab", {"id": cron_id})
if log_result and log_result.get("msg"):
print("\n📋 构建结果:")
print("-" * 60)
print(log_result["msg"])
else:
print(f" ❌ 仍然失败: {result}")
print("\n" + "=" * 60)
print("🌐 http://interview.test.ai.ireborn.com.cn")
if __name__ == "__main__":
main()

183
deploy/create_files.py Normal file
View File

@@ -0,0 +1,183 @@
#!/usr/bin/env python3
"""
通过宝塔计划任务创建文件
"""
import requests
import time
import hashlib
import os
import base64
# 禁用 SSL 警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 宝塔配置
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
LOCAL_BACKEND = "/Users/a111/Documents/AgentWD/projects/011-ai-interview-2601/backend"
LOCAL_DEPLOY = "/Users/a111/Documents/AgentWD/projects/011-ai-interview-2601/deploy"
LOCAL_FRONTEND = "/Users/a111/Documents/AgentWD/projects/011-ai-interview-2601/frontend"
def bt_api(action, data=None, timeout=60):
"""调用宝塔 API"""
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:1000]}
except Exception as e:
return {"status": False, "msg": str(e)}
def run_task(shell_body, task_name, wait_time=30):
"""创建计划任务并执行"""
result = bt_api("crontab?action=AddCrontab", {
"name": task_name,
"type": "minute-n",
"where1": "1",
"hour": "",
"minute": "",
"week": "",
"sType": "toShell",
"sBody": shell_body,
"sName": "",
"backupTo": "",
"save": "",
"urladdress": ""
})
if not result.get("status") or not result.get("id"):
print(f" 创建任务失败: {result}")
return None
cron_id = result["id"]
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=300)
print(f" 任务 {cron_id} 已启动,等待 {wait_time} 秒...")
time.sleep(wait_time)
log_result = bt_api("crontab?action=GetLogs", {"id": cron_id})
bt_api("crontab?action=DelCrontab", {"id": cron_id})
return log_result
def create_file_via_shell(remote_path, content):
"""通过 shell 命令创建文件"""
# 使用 base64 编码内容以避免特殊字符问题
encoded = base64.b64encode(content.encode('utf-8')).decode('utf-8')
# 确保目录存在,然后写入文件
dir_path = os.path.dirname(remote_path)
shell_script = f"""#!/bin/bash
mkdir -p {dir_path}
echo '{encoded}' | base64 -d > {remote_path}
echo "文件已创建: {remote_path}"
ls -la {remote_path}
"""
return shell_script
def main():
print("=" * 60)
print("📤 创建所有必需文件")
print("=" * 60)
print()
# 读取所有需要上传的文件
files_to_create = []
# 1. requirements.txt
with open(os.path.join(LOCAL_BACKEND, "requirements.txt"), 'r') as f:
files_to_create.append((f"{DEPLOY_PATH}/backend/requirements.txt", f.read()))
# 2. Dockerfile.frontend
with open(os.path.join(LOCAL_DEPLOY, "Dockerfile.frontend"), 'r') as f:
files_to_create.append((f"{DEPLOY_PATH}/deploy/Dockerfile.frontend", f.read()))
# 批量创建文件
print("📁 创建目录结构和文件...")
# 合并所有文件创建命令
all_commands = f"""#!/bin/bash
mkdir -p {DEPLOY_PATH}/backend
mkdir -p {DEPLOY_PATH}/backend/app/routers
mkdir -p {DEPLOY_PATH}/backend/app/services
mkdir -p {DEPLOY_PATH}/deploy/nginx
mkdir -p {DEPLOY_PATH}/frontend
mkdir -p {DEPLOY_PATH}/deploy/uploads
"""
for remote_path, content in files_to_create:
encoded = base64.b64encode(content.encode('utf-8')).decode('utf-8')
all_commands += f"""
echo "创建: {remote_path}"
echo '{encoded}' | base64 -d > {remote_path}
"""
all_commands += f"""
echo ""
echo "文件列表:"
ls -la {DEPLOY_PATH}/backend/
ls -la {DEPLOY_PATH}/deploy/
"""
result = run_task(all_commands, f"create_files_{int(time.time())}", wait_time=15)
if result and result.get("msg"):
print("\n📋 执行结果:")
print(result["msg"][:1500])
# 3. 重新构建 Docker
print("\n🐳 重新构建 Docker 容器...")
rebuild_script = f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
echo "停止旧容器..."
docker-compose down 2>/dev/null || true
sleep 2
echo "重新构建..."
docker-compose up -d --build 2>&1
sleep 15
echo ""
echo "容器状态:"
docker ps --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}'
echo ""
echo "后端容器日志:"
docker logs --tail 30 ai-interview-backend 2>&1
echo ""
echo "测试后端:"
curl -s http://127.0.0.1:8000/health 2>&1 || echo "后端未响应"
"""
result = run_task(rebuild_script, f"rebuild_{int(time.time())}", wait_time=90)
if result and result.get("msg"):
print("\n📋 构建结果:")
print("-" * 60)
print(result["msg"][:3000])
print()
print("=" * 60)
print("✅ 完成!")
print("=" * 60)
if __name__ == "__main__":
main()

81
deploy/deploy.sh Normal file
View File

@@ -0,0 +1,81 @@
#!/bin/bash
# AI 面试系统部署脚本
# 使用方法: bash deploy.sh
set -e
echo "=========================================="
echo "AI 语音面试系统 - Docker 部署"
echo "=========================================="
# 配置
DEPLOY_DIR="/www/wwwroot/ai-interview"
DOMAIN="interview.test.ai.ireborn.com.cn"
# 检查 Docker
if ! command -v docker &> /dev/null; then
echo "❌ Docker 未安装,请先安装 Docker"
exit 1
fi
if ! command -v docker-compose &> /dev/null; then
echo "❌ Docker Compose 未安装,请先安装"
exit 1
fi
echo "✅ Docker 环境检查通过"
# 创建部署目录
echo "📁 创建部署目录..."
mkdir -p $DEPLOY_DIR
cd $DEPLOY_DIR
# 检查 .env 文件
if [ ! -f "deploy/.env" ]; then
echo "⚠️ 未找到 .env 文件,正在创建..."
mkdir -p deploy
cat > deploy/.env << 'EOF'
# Coze 配置
COZE_PAT_TOKEN=pat_nd1wU47WyPS9GCIyJ1clnH8h1WOQXGrYELX8w73TnSZaYbFdYD4swIhzcETBUbfT
COZE_BOT_ID=7595113005181386792
# 工作流 ID
COZE_WORKFLOW_A_ID=7597357422713798710
COZE_WORKFLOW_C_ID=7597376294612107318
# 文件服务器
FILE_SERVER_URL=https://files.test.ai.ireborn.com.cn
FILE_SERVER_TOKEN=ai_interview_2026_secret
EOF
echo "✅ .env 文件已创建"
fi
# 构建并启动
echo "🐳 构建 Docker 镜像..."
cd deploy
docker-compose down 2>/dev/null || true
docker-compose up -d --build
echo "⏳ 等待服务启动..."
sleep 10
# 检查服务状态
echo "🔍 检查服务状态..."
docker-compose ps
echo ""
echo "=========================================="
echo "✅ 部署完成!"
echo "=========================================="
echo ""
echo "访问地址:"
echo " 用户端: http://$DOMAIN"
echo " 管理后台: http://$DOMAIN/admin"
echo " 后端 API: http://$DOMAIN/api"
echo ""
echo "容器状态:"
docker-compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"
echo ""
echo "查看日志: docker-compose logs -f"
echo "=========================================="

39
deploy/docker-compose.yml Normal file
View File

@@ -0,0 +1,39 @@
version: '3.8'
services:
frontend:
build:
context: ..
dockerfile: deploy/Dockerfile.frontend
container_name: ai-interview-frontend
ports:
- "3000:80"
depends_on:
- backend
networks:
- ai-interview-network
restart: unless-stopped
backend:
build:
context: ..
dockerfile: deploy/Dockerfile.backend
container_name: ai-interview-backend
ports:
- "8000:8000"
environment:
- COZE_PAT_TOKEN=${COZE_PAT_TOKEN}
- COZE_BOT_ID=${COZE_BOT_ID}
- COZE_WORKFLOW_A_ID=${COZE_WORKFLOW_A_ID}
- COZE_WORKFLOW_C_ID=${COZE_WORKFLOW_C_ID}
- FILE_SERVER_URL=${FILE_SERVER_URL}
- FILE_SERVER_TOKEN=${FILE_SERVER_TOKEN}
volumes:
- ./uploads:/app/uploads
networks:
- ai-interview-network
restart: unless-stopped
networks:
ai-interview-network:
driver: bridge

11
deploy/env.example Normal file
View File

@@ -0,0 +1,11 @@
# Coze 配置
COZE_PAT_TOKEN=pat_xxx
COZE_BOT_ID=7595113005181386792
# 工作流 ID
COZE_WORKFLOW_A_ID=7597357422713798710
COZE_WORKFLOW_C_ID=7597376294612107318
# 文件服务器
FILE_SERVER_URL=https://files.test.ai.ireborn.com.cn
FILE_SERVER_TOKEN=ai_interview_2026_secret

21
deploy/env.production Normal file
View File

@@ -0,0 +1,21 @@
# AI Interview 后端环境变量
# ============================================
# 部署步骤:
# 1. 复制此文件到服务器: /www/wwwroot/ai-interview/deploy/.env
# 2. 或者在服务器上执行: cp env.production .env
# ============================================
# Coze 配置
COZE_PAT_TOKEN=pat_nd1wU47WyPS9GCIyJ1clnH8h1WOQXGrYELX8w73TnSZaYbFdYD4swIhzcETBUbfT
COZE_BOT_ID=7595113005181386792
# 工作流 ID
COZE_WORKFLOW_A_ID=7597357422713798710
COZE_WORKFLOW_C_ID=7597376294612107318
# 文件服务器
FILE_SERVER_URL=https://files.test.ai.ireborn.com.cn
FILE_SERVER_TOKEN=ai_interview_2026_secret
# CORS 允许的域名(逗号分隔)
CORS_ORIGINS=http://interview.test.ai.ireborn.com.cn,https://interview.test.ai.ireborn.com.cn,http://localhost:5173

211
deploy/fix_deploy.py Normal file
View File

@@ -0,0 +1,211 @@
#!/usr/bin/env python3
"""
修复部署 - 上传配置文件并重启服务
"""
import requests
import time
import hashlib
import json
# 禁用 SSL 警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 宝塔配置
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
# 部署配置
DEPLOY_PATH = "/www/wwwroot/ai-interview"
DOMAIN = "interview.test.ai.ireborn.com.cn"
def bt_api(action, data=None, timeout=60):
"""调用宝塔 API"""
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
return response.json()
except requests.exceptions.JSONDecodeError:
return {"status": True, "msg": response.text[:500]}
except Exception as e:
return {"status": False, "msg": str(e)}
def write_remote_file(path, content):
"""写入远程文件"""
print(f"📝 写入文件: {path}")
result = bt_api("files?action=SaveFileBody", {
"path": path,
"data": content,
"encoding": "utf-8"
})
if result.get("status"):
print(" ✅ 文件写入成功")
return True
print(f" 结果: {result}")
return result.get("status", False)
def exec_shell(command, timeout=300):
"""执行 Shell 命令 - 使用宝塔终端 API"""
print(f"🔧 执行: {command[:80]}...")
# 尝试多个可能的 API 端点
endpoints = [
("deployment?action=ExecCommand", {"command": command}),
("plugin?action=a&s=deployment&name=exec_command", {"command": command}),
("crontab?action=GetDataList", {}), # 先获取计划任务列表
]
# 方法1: 使用 files 接口执行命令(某些宝塔版本支持)
result = bt_api("files?action=ExecCommand", {"command": command}, timeout=timeout)
if result.get("status") or "404" not in str(result.get("msg", "")):
print(f" 结果: {str(result)[:200]}")
return result
# 方法2: 创建临时脚本并执行
script_path = "/tmp/bt_exec_cmd.sh"
write_result = bt_api("files?action=SaveFileBody", {
"path": script_path,
"data": f"#!/bin/bash\n{command}\n",
"encoding": "utf-8"
})
if write_result.get("status"):
# 设置执行权限并运行
result = bt_api("files?action=ExecCommand", {"command": f"chmod +x {script_path} && {script_path}"}, timeout=timeout)
print(f" 结果: {str(result)[:200]}")
return result
print(f" ⚠️ 无法执行命令,请手动在服务器执行")
return {"status": False, "msg": "需要手动执行"}
def restart_nginx():
"""重启 Nginx"""
print("🔄 重启 Nginx...")
result = bt_api("system?action=ServiceAdmin", {
"name": "nginx",
"type": "restart"
})
print(f" 结果: {result}")
return result
def main():
print("=" * 60)
print("🔧 AI 面试系统 - 修复部署")
print("=" * 60)
print()
# 1. 写入 .env 文件
print("\n📝 步骤 1: 写入环境变量文件")
env_content = """# AI Interview 后端环境变量
COZE_PAT_TOKEN=pat_nd1wU47WyPS9GCIyJ1clnH8h1WOQXGrYELX8w73TnSZaYbFdYD4swIhzcETBUbfT
COZE_BOT_ID=7595113005181386792
COZE_WORKFLOW_A_ID=7597357422713798710
COZE_WORKFLOW_C_ID=7597376294612107318
FILE_SERVER_URL=https://files.test.ai.ireborn.com.cn
FILE_SERVER_TOKEN=ai_interview_2026_secret
CORS_ORIGINS=http://interview.test.ai.ireborn.com.cn,https://interview.test.ai.ireborn.com.cn,http://localhost:5173
"""
write_remote_file(f"{DEPLOY_PATH}/deploy/.env", env_content)
# 2. 写入 Nginx 配置
print("\n📝 步骤 2: 写入 Nginx 反向代理配置")
nginx_config = """server {
listen 80;
server_name interview.test.ai.ireborn.com.cn;
index index.html;
access_log /www/wwwlogs/interview.test.ai.ireborn.com.cn.log;
error_log /www/wwwlogs/interview.test.ai.ireborn.com.cn.error.log;
# 前端 - 代理到 Docker 前端容器 (3000 端口)
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# API - 代理到 Docker 后端容器 (8000 端口)
location /api/ {
proxy_pass http://127.0.0.1:8000/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
proxy_connect_timeout 120s;
}
# 健康检查端点
location /health {
proxy_pass http://127.0.0.1:8000/health;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
"""
write_remote_file(f"/www/server/panel/vhost/nginx/{DOMAIN}.conf", nginx_config)
# 3. 写入 Docker 重启脚本
print("\n📝 步骤 3: 写入 Docker 重启脚本")
restart_script = f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
docker-compose down 2>/dev/null || true
sleep 2
docker-compose up -d --build
sleep 5
docker ps
"""
write_remote_file(f"{DEPLOY_PATH}/restart_docker.sh", restart_script)
# 4. 重启 Nginx
print("\n🔄 步骤 4: 重启 Nginx")
restart_nginx()
print()
print("=" * 60)
print("✅ 配置文件已上传!")
print("=" * 60)
print()
print("⚠️ 请在服务器上手动执行以下命令重启 Docker:")
print()
print(f" chmod +x {DEPLOY_PATH}/restart_docker.sh")
print(f" {DEPLOY_PATH}/restart_docker.sh")
print()
print(" 或者:")
print()
print(f" cd {DEPLOY_PATH}/deploy")
print(" docker-compose down && docker-compose up -d --build")
print()
print(f"🌐 配置完成后访问:")
print(f" 用户端: http://{DOMAIN}")
print(f" 管理后台: http://{DOMAIN}/admin")
print(f" API: http://{DOMAIN}/api/health")
print()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,38 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# 前端静态资源
location / {
try_files $uri $uri/ /index.html;
}
# API 代理到后端
location /api/ {
proxy_pass http://backend:8000/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
proxy_connect_timeout 120s;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}

View File

@@ -0,0 +1,68 @@
# interview.test.ai.ireborn.com.cn Nginx 配置
# 宝塔面板配置:网站 -> 添加站点 -> 设置 -> 配置文件 -> 粘贴此内容
server {
listen 80;
listen 443 ssl http2;
server_name interview.test.ai.ireborn.com.cn;
index index.html;
# SSL 证书 (宝塔面板会自动生成,如果没有可以先注释掉 ssl 相关配置)
# ssl_certificate /www/server/panel/vhost/cert/interview.test.ai.ireborn.com.cn/fullchain.pem;
# ssl_certificate_key /www/server/panel/vhost/cert/interview.test.ai.ireborn.com.cn/privkey.pem;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
# ssl_prefer_server_ciphers on;
# ssl_session_timeout 10m;
# ssl_session_cache shared:SSL:10m;
# 日志
access_log /www/wwwlogs/interview.test.ai.ireborn.com.cn.log;
error_log /www/wwwlogs/interview.test.ai.ireborn.com.cn.error.log;
# 前端 - 代理到 Docker 前端容器 (3000 端口)
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# API - 代理到 Docker 后端容器 (8000 端口)
location /api/ {
proxy_pass http://127.0.0.1:8000/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
proxy_connect_timeout 120s;
# CORS 配置
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always;
# 处理 OPTIONS 预检请求
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
add_header Content-Length 0;
return 204;
}
}
# 健康检查端点
location /health {
proxy_pass http://127.0.0.1:8000/health;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}

158
deploy/rebuild.py Normal file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""
上传新的 Dockerfile 并重新构建
"""
import requests
import time
import hashlib
import os
import base64
# 禁用 SSL 警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 宝塔配置
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
def bt_api(action, data=None, timeout=60):
"""调用宝塔 API"""
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:1000]}
except Exception as e:
return {"status": False, "msg": str(e)}
def run_task(shell_body, task_name, wait_time=30):
"""创建计划任务并执行"""
result = bt_api("crontab?action=AddCrontab", {
"name": task_name,
"type": "minute-n",
"where1": "1",
"hour": "",
"minute": "",
"week": "",
"sType": "toShell",
"sBody": shell_body,
"sName": "",
"backupTo": "",
"save": "",
"urladdress": ""
})
if not result.get("status") or not result.get("id"):
print(f" 创建任务失败: {result}")
return None
cron_id = result["id"]
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=600)
print(f" 任务 {cron_id} 已启动,等待 {wait_time} 秒...")
time.sleep(wait_time)
log_result = bt_api("crontab?action=GetLogs", {"id": cron_id})
bt_api("crontab?action=DelCrontab", {"id": cron_id})
return log_result
def main():
print("=" * 60)
print("🐳 更新 Dockerfile 并重新构建")
print("=" * 60)
print()
# 1. 读取新的 Dockerfile
dockerfile_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Dockerfile.backend")
with open(dockerfile_path, 'r') as f:
dockerfile_content = f.read()
# 2. 通过 shell 命令更新文件
encoded = base64.b64encode(dockerfile_content.encode('utf-8')).decode('utf-8')
update_script = f"""#!/bin/bash
echo "更新 Dockerfile.backend..."
echo '{encoded}' | base64 -d > {DEPLOY_PATH}/deploy/Dockerfile.backend
cat {DEPLOY_PATH}/deploy/Dockerfile.backend
"""
print("📝 步骤 1: 更新 Dockerfile...")
result = run_task(update_script, f"update_dockerfile_{int(time.time())}", wait_time=10)
if result and result.get("msg"):
print(result["msg"][:1000])
# 3. 重新构建
print("\n🐳 步骤 2: 重新构建 Docker 容器 (使用阿里云镜像)...")
rebuild_script = f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
echo "停止旧容器..."
docker-compose down 2>/dev/null || true
sleep 2
echo "删除旧镜像..."
docker rmi deploy-backend 2>/dev/null || true
echo "重新构建 (这可能需要几分钟)..."
docker-compose build --no-cache backend 2>&1
echo ""
echo "启动容器..."
docker-compose up -d 2>&1
sleep 10
echo ""
echo "========== 容器状态 =========="
docker ps --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}'
echo ""
echo "========== 后端日志 =========="
docker logs --tail 30 ai-interview-backend 2>&1
echo ""
echo "========== 测试服务 =========="
curl -s http://127.0.0.1:8000/health 2>&1 || echo "后端未响应"
echo ""
curl -s -o /dev/null -w "前端: HTTP %{{http_code}}" http://127.0.0.1:3000 2>&1
"""
result = run_task(rebuild_script, f"rebuild_{int(time.time())}", wait_time=180)
if result and result.get("msg"):
print("\n📋 构建结果:")
print("-" * 60)
print(result["msg"])
print()
print("=" * 60)
print("✅ 完成!")
print("=" * 60)
print()
print("🌐 访问地址:")
print(" http://interview.test.ai.ireborn.com.cn")
print(" http://interview.test.ai.ireborn.com.cn/admin")
if __name__ == "__main__":
main()

147
deploy/restart_docker.py Normal file
View File

@@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""
通过宝塔计划任务重启 Docker 容器
"""
import requests
import time
import hashlib
import json
# 禁用 SSL 警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 宝塔配置
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
def bt_api(action, data=None, timeout=60):
"""调用宝塔 API"""
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:500]}
except Exception as e:
return {"status": False, "msg": str(e)}
def add_crontab(name, shell_body):
"""添加计划任务"""
print(f"📋 添加计划任务: {name}")
result = bt_api("crontab?action=AddCrontab", {
"name": name,
"type": "minute-n",
"where1": "1",
"hour": "",
"minute": "",
"week": "",
"sType": "toShell",
"sBody": shell_body,
"sName": "",
"backupTo": "",
"save": "",
"urladdress": ""
})
print(f" 结果: {result}")
return result
def exec_crontab(cron_id):
"""立即执行计划任务"""
print(f"▶️ 执行计划任务 ID: {cron_id}")
result = bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=300)
print(f" 结果: {result}")
return result
def delete_crontab(cron_id):
"""删除计划任务"""
print(f"🗑️ 删除计划任务 ID: {cron_id}")
result = bt_api("crontab?action=DelCrontab", {"id": cron_id})
print(f" 结果: {result}")
return result
def get_crontab_list():
"""获取计划任务列表"""
result = bt_api("crontab?action=GetCrontab", {"page": 1, "search": ""})
return result
def main():
print("=" * 60)
print("🐳 重启 Docker 容器")
print("=" * 60)
print()
# Docker 重启命令
shell_body = f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
docker-compose down 2>/dev/null || true
sleep 2
docker-compose up -d --build
sleep 5
echo "Docker 容器状态:"
docker ps --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}'
echo ""
echo "测试后端服务:"
curl -s http://127.0.0.1:8000/health || echo "后端未响应"
echo ""
echo "测试前端服务:"
curl -s -o /dev/null -w "HTTP Status: %{{http_code}}" http://127.0.0.1:3000 || echo "前端未响应"
"""
# 1. 添加计划任务
task_name = f"restart_docker_{int(time.time())}"
result = add_crontab(task_name, shell_body)
if result.get("status") and result.get("id"):
cron_id = result["id"]
print(f"\n✅ 计划任务创建成功ID: {cron_id}")
# 2. 立即执行
print("\n🚀 立即执行计划任务...")
exec_result = exec_crontab(cron_id)
# 3. 等待执行完成
print("\n⏳ 等待执行完成...")
time.sleep(30)
# 4. 删除计划任务
delete_crontab(cron_id)
print("\n✅ Docker 重启命令已执行!")
else:
print(f"\n❌ 计划任务创建失败: {result}")
print("\n⚠️ 请手动在服务器执行:")
print(f" cd {DEPLOY_PATH}/deploy")
print(" docker-compose down && docker-compose up -d --build")
print()
print("=" * 60)
print("🌐 访问地址:")
print(" http://interview.test.ai.ireborn.com.cn")
print(" http://interview.test.ai.ireborn.com.cn/admin")
print("=" * 60)
if __name__ == "__main__":
main()

96
deploy/setup-server.sh Normal file
View File

@@ -0,0 +1,96 @@
#!/bin/bash
# AI Interview 服务器部署脚本
# ============================================
# 在服务器上执行此脚本来快速部署
#
# 使用方法:
# chmod +x setup-server.sh
# ./setup-server.sh
# ============================================
set -e
echo "=========================================="
echo "AI Interview 部署脚本"
echo "=========================================="
# 项目目录
PROJECT_DIR="/www/wwwroot/ai-interview"
DEPLOY_DIR="$PROJECT_DIR/deploy"
# 进入部署目录
cd $DEPLOY_DIR
# 1. 复制环境变量文件
if [ ! -f ".env" ]; then
echo "[1/4] 创建 .env 文件..."
cp env.production .env
echo "✅ .env 文件已创建"
else
echo "[1/4] .env 文件已存在,跳过"
fi
# 2. 创建上传目录
echo "[2/4] 创建上传目录..."
mkdir -p $DEPLOY_DIR/uploads
chmod 755 $DEPLOY_DIR/uploads
echo "✅ 上传目录已创建"
# 3. 停止旧容器
echo "[3/4] 停止旧容器..."
docker-compose down 2>/dev/null || true
echo "✅ 旧容器已停止"
# 4. 构建并启动新容器
echo "[4/4] 构建并启动容器..."
docker-compose up -d --build
# 等待服务启动
echo ""
echo "等待服务启动..."
sleep 5
# 检查容器状态
echo ""
echo "=========================================="
echo "容器状态:"
echo "=========================================="
docker-compose ps
# 检查服务健康
echo ""
echo "=========================================="
echo "健康检查:"
echo "=========================================="
# 检查后端
if curl -s http://127.0.0.1:8000/health > /dev/null 2>&1; then
echo "✅ 后端服务正常 (http://127.0.0.1:8000)"
else
echo "❌ 后端服务未响应"
echo "查看后端日志: docker logs ai-interview-backend"
fi
# 检查前端
if curl -s http://127.0.0.1:3000 > /dev/null 2>&1; then
echo "✅ 前端服务正常 (http://127.0.0.1:3000)"
else
echo "❌ 前端服务未响应"
echo "查看前端日志: docker logs ai-interview-frontend"
fi
echo ""
echo "=========================================="
echo "部署完成!"
echo "=========================================="
echo ""
echo "访问地址:"
echo " - 用户端: http://interview.test.ai.ireborn.com.cn"
echo " - 后台: http://interview.test.ai.ireborn.com.cn/admin"
echo " - API: http://interview.test.ai.ireborn.com.cn/api/health"
echo ""
echo "常用命令:"
echo " - 查看日志: docker-compose logs -f"
echo " - 重启服务: docker-compose restart"
echo " - 停止服务: docker-compose down"
echo ""

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""
只启动后端容器
"""
import requests
import time
import hashlib
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
def bt_api(action, data=None, timeout=60):
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:2000]}
except Exception as e:
return {"status": False, "msg": str(e)}
def run_task(shell_body, task_name, wait_time=30):
result = bt_api("crontab?action=AddCrontab", {
"name": task_name,
"type": "minute-n",
"where1": "1",
"sType": "toShell",
"sBody": shell_body,
})
if not result.get("status") or not result.get("id"):
print(f"创建任务失败: {result}")
return None
cron_id = result["id"]
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=300)
print(f"任务 {cron_id} 已启动,等待 {wait_time} 秒...")
time.sleep(wait_time)
log_result = bt_api("crontab?action=GetLogs", {"id": cron_id})
bt_api("crontab?action=DelCrontab", {"id": cron_id})
return log_result
def main():
print("=" * 60)
print("🚀 单独启动后端容器")
print("=" * 60)
# 直接用 docker run 启动后端容器
start_script = f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
# 停止旧容器
docker stop ai-interview-backend 2>/dev/null || true
docker rm ai-interview-backend 2>/dev/null || true
# 读取 .env 文件
source .env 2>/dev/null || true
echo "启动后端容器..."
docker run -d \\
--name ai-interview-backend \\
-p 8000:8000 \\
-e COZE_PAT_TOKEN="$COZE_PAT_TOKEN" \\
-e COZE_BOT_ID="$COZE_BOT_ID" \\
-e COZE_WORKFLOW_A_ID="$COZE_WORKFLOW_A_ID" \\
-e COZE_WORKFLOW_C_ID="$COZE_WORKFLOW_C_ID" \\
-e FILE_SERVER_URL="$FILE_SERVER_URL" \\
-e FILE_SERVER_TOKEN="$FILE_SERVER_TOKEN" \\
-v {DEPLOY_PATH}/deploy/uploads:/app/uploads \\
--restart unless-stopped \\
deploy-backend
sleep 5
echo ""
echo "========== 容器状态 =========="
docker ps --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}'
echo ""
echo "========== 后端日志 =========="
docker logs --tail 30 ai-interview-backend 2>&1
echo ""
echo "========== 测试后端 =========="
curl -s http://127.0.0.1:8000/health 2>&1 || echo "后端未响应"
"""
result = run_task(start_script, f"start_backend_{int(time.time())}", wait_time=20)
if result and result.get("msg"):
print("\n" + result["msg"])
print("\n" + "=" * 60)
print("测试后端 API:")
print(" http://interview.test.ai.ireborn.com.cn/api/health")
print("=" * 60)
if __name__ == "__main__":
main()

100
deploy/start_containers.py Normal file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""
启动 Docker 容器
"""
import requests
import time
import hashlib
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
def bt_api(action, data=None, timeout=60):
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:2000]}
except Exception as e:
return {"status": False, "msg": str(e)}
def run_task(shell_body, task_name, wait_time=30):
result = bt_api("crontab?action=AddCrontab", {
"name": task_name,
"type": "minute-n",
"where1": "1",
"sType": "toShell",
"sBody": shell_body,
})
if not result.get("status") or not result.get("id"):
print(f"创建任务失败: {result}")
return None
cron_id = result["id"]
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=300)
print(f"任务 {cron_id} 已启动,等待 {wait_time} 秒...")
time.sleep(wait_time)
log_result = bt_api("crontab?action=GetLogs", {"id": cron_id})
bt_api("crontab?action=DelCrontab", {"id": cron_id})
return log_result
def main():
print("=" * 60)
print("🚀 启动 Docker 容器")
print("=" * 60)
start_script = f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
echo "当前目录文件:"
ls -la
echo ""
echo "启动容器..."
docker-compose up -d 2>&1
sleep 10
echo ""
echo "========== 容器状态 =========="
docker ps -a --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}'
echo ""
echo "========== 后端日志 =========="
docker logs --tail 30 ai-interview-backend 2>&1
echo ""
echo "========== 测试服务 =========="
curl -s http://127.0.0.1:8000/health 2>&1 || echo "后端未响应"
echo ""
curl -s -o /dev/null -w "前端: HTTP %{{http_code}}" http://127.0.0.1:3000 2>&1
"""
result = run_task(start_script, f"start_{int(time.time())}", wait_time=30)
if result and result.get("msg"):
print("\n" + result["msg"])
print("\n" + "=" * 60)
print("🌐 http://interview.test.ai.ireborn.com.cn")
print("=" * 60)
if __name__ == "__main__":
main()

237
deploy/upload_all.py Normal file
View File

@@ -0,0 +1,237 @@
#!/usr/bin/env python3
"""
上传所有代码到服务器并部署
"""
import requests
import time
import hashlib
import os
import base64
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
LOCAL_PROJECT = "/Users/a111/Documents/AgentWD/projects/011-ai-interview-2601"
def bt_api(action, data=None, timeout=60):
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:1000]}
except Exception as e:
return {"status": False, "msg": str(e)}
def run_task(shell_body, task_name, wait_time=30):
result = bt_api("crontab?action=AddCrontab", {
"name": task_name,
"type": "minute-n",
"where1": "1",
"sType": "toShell",
"sBody": shell_body,
})
if not result.get("status") or not result.get("id"):
print(f" 创建任务失败: {result}")
return None
cron_id = result["id"]
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=600)
print(f" 任务 {cron_id} 启动,等待 {wait_time}s...")
time.sleep(wait_time)
log_result = bt_api("crontab?action=GetLogs", {"id": cron_id})
bt_api("crontab?action=DelCrontab", {"id": cron_id})
return log_result
def upload_file_via_task(remote_path, content):
"""通过计划任务上传文件"""
encoded = base64.b64encode(content.encode('utf-8')).decode('utf-8')
dir_path = os.path.dirname(remote_path)
script = f"""#!/bin/bash
mkdir -p {dir_path}
echo '{encoded}' | base64 -d > {remote_path}
"""
result = bt_api("crontab?action=AddCrontab", {
"name": f"upload_{int(time.time())}",
"type": "minute-n",
"where1": "1",
"sType": "toShell",
"sBody": script,
})
if result.get("status") and result.get("id"):
cron_id = result["id"]
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=60)
time.sleep(2)
bt_api("crontab?action=DelCrontab", {"id": cron_id})
return True
return False
def main():
print("=" * 60)
print("🚀 完整部署 - 上传所有代码")
print("=" * 60)
# 前端文件列表
frontend_files = [
"package.json",
"pnpm-lock.yaml",
"vite.config.ts",
"tsconfig.json",
"tsconfig.node.json",
"tailwind.config.js",
"postcss.config.js",
"index.html",
"src/main.ts",
"src/App.vue",
"src/styles/index.css",
"src/router/index.ts",
"src/api/index.ts",
"src/api/request.ts",
"src/api/candidate.ts",
"src/composables/index.ts",
"src/composables/useRTC.ts",
"src/composables/useCozeRealtime.ts",
"src/types/index.ts",
"src/types/env.d.ts",
"src/layouts/AdminLayout.vue",
"src/layouts/InterviewLayout.vue",
"src/pages/interview/index.vue",
"src/pages/interview/info.vue",
"src/pages/interview/call.vue",
"src/pages/interview/complete.vue",
"src/pages/admin/index.vue",
"src/pages/admin/login.vue",
"src/pages/admin/dashboard.vue",
"src/pages/admin/interviews.vue",
"src/pages/admin/interview-detail.vue",
"src/pages/admin/configs.vue",
"src/pages/admin/layout.vue",
"src/pages/admin/[id].vue",
]
# 后端文件列表
backend_files = [
"main.py",
"requirements.txt",
"app/__init__.py",
"app/config.py",
"app/schemas.py",
"app/routers/__init__.py",
"app/routers/admin.py",
"app/routers/candidate.py",
"app/routers/chat.py",
"app/routers/files.py",
"app/routers/init.py",
"app/routers/room.py",
"app/routers/upload.py",
"app/services/__init__.py",
"app/services/coze_service.py",
]
# 部署配置文件
deploy_files = [
"docker-compose.yml",
"Dockerfile.backend",
"Dockerfile.frontend",
"nginx/frontend.conf",
]
print("\n📤 步骤 1: 上传前端代码...")
for f in frontend_files:
local_path = os.path.join(LOCAL_PROJECT, "frontend", f)
if os.path.exists(local_path):
with open(local_path, 'r', encoding='utf-8') as fp:
content = fp.read()
remote_path = f"{DEPLOY_PATH}/frontend/{f}"
print(f" 📝 {f}")
upload_file_via_task(remote_path, content)
else:
print(f" ⚠️ 跳过: {f}")
print("\n📤 步骤 2: 上传后端代码...")
for f in backend_files:
local_path = os.path.join(LOCAL_PROJECT, "backend", f)
if os.path.exists(local_path):
with open(local_path, 'r', encoding='utf-8') as fp:
content = fp.read()
remote_path = f"{DEPLOY_PATH}/backend/{f}"
print(f" 📝 {f}")
upload_file_via_task(remote_path, content)
print("\n📤 步骤 3: 上传部署配置...")
for f in deploy_files:
local_path = os.path.join(LOCAL_PROJECT, "deploy", f)
if os.path.exists(local_path):
with open(local_path, 'r', encoding='utf-8') as fp:
content = fp.read()
remote_path = f"{DEPLOY_PATH}/deploy/{f}"
print(f" 📝 {f}")
upload_file_via_task(remote_path, content)
print("\n🐳 步骤 4: 构建并启动 Docker...")
build_script = f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
echo "目录结构:"
ls -la {DEPLOY_PATH}/
ls -la {DEPLOY_PATH}/frontend/ | head -10
ls -la {DEPLOY_PATH}/backend/ | head -10
echo ""
echo "停止旧容器..."
docker-compose down 2>/dev/null || true
echo ""
echo "删除旧镜像..."
docker rmi deploy-backend deploy-frontend 2>/dev/null || true
echo ""
echo "构建并启动..."
docker-compose up -d --build 2>&1
sleep 15
echo ""
echo "========== 容器状态 =========="
docker ps -a --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}'
echo ""
echo "========== 后端日志 =========="
docker logs --tail 20 ai-interview-backend 2>&1
echo ""
echo "========== 测试 =========="
curl -s http://127.0.0.1:8000/health 2>&1 || echo "后端未响应"
echo ""
curl -s -o /dev/null -w "前端: %{{http_code}}" http://127.0.0.1:3000 2>&1
"""
result = run_task(build_script, f"build_{int(time.time())}", wait_time=180)
if result and result.get("msg"):
print("\n📋 构建结果:")
print("-" * 60)
print(result["msg"][-3000:])
print("\n" + "=" * 60)
print("🌐 http://interview.test.ai.ireborn.com.cn")
print("=" * 60)
if __name__ == "__main__":
main()

212
deploy/upload_backend.py Normal file
View File

@@ -0,0 +1,212 @@
#!/usr/bin/env python3
"""
上传后端代码到服务器并重新构建
"""
import requests
import time
import hashlib
import os
# 禁用 SSL 警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 宝塔配置
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
LOCAL_BACKEND = "/Users/a111/Documents/AgentWD/projects/011-ai-interview-2601/backend"
def bt_api(action, data=None, timeout=60):
"""调用宝塔 API"""
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:500]}
except Exception as e:
return {"status": False, "msg": str(e)}
def write_remote_file(path, content):
"""写入远程文件"""
print(f"📝 写入: {path}")
result = bt_api("files?action=SaveFileBody", {
"path": path,
"data": content,
"encoding": "utf-8"
})
if result.get("status"):
print(" ✅ 成功")
return True
print(f" ❌ 失败: {result.get('msg', result)}")
return False
def create_directory(path):
"""创建目录"""
result = bt_api("files?action=CreateDir", {"path": path})
return result.get("status") or "已存在" in str(result.get("msg", ""))
def run_task(shell_body, task_name, wait_time=30):
"""创建计划任务并执行"""
result = bt_api("crontab?action=AddCrontab", {
"name": task_name,
"type": "minute-n",
"where1": "1",
"hour": "",
"minute": "",
"week": "",
"sType": "toShell",
"sBody": shell_body,
"sName": "",
"backupTo": "",
"save": "",
"urladdress": ""
})
if not result.get("status") or not result.get("id"):
print(f"创建任务失败: {result}")
return None
cron_id = result["id"]
bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=300)
print(f" 任务 {cron_id} 已启动,等待 {wait_time} 秒...")
time.sleep(wait_time)
log_result = bt_api("crontab?action=GetLogs", {"id": cron_id})
bt_api("crontab?action=DelCrontab", {"id": cron_id})
return log_result
def main():
print("=" * 60)
print("📤 上传后端代码到服务器")
print("=" * 60)
print()
# 1. 创建目录结构
print("📁 步骤 1: 创建目录结构")
dirs = [
f"{DEPLOY_PATH}/backend",
f"{DEPLOY_PATH}/backend/app",
f"{DEPLOY_PATH}/backend/app/routers",
f"{DEPLOY_PATH}/backend/app/services",
]
for d in dirs:
create_directory(d)
print(f"{d}")
# 2. 上传后端代码
print("\n📤 步骤 2: 上传后端代码")
# 定义要上传的文件
files_to_upload = [
("main.py", ""),
("requirements.txt", ""),
("app/__init__.py", "app"),
("app/config.py", "app"),
("app/schemas.py", "app"),
("app/routers/__init__.py", "app/routers"),
("app/routers/admin.py", "app/routers"),
("app/routers/candidate.py", "app/routers"),
("app/routers/chat.py", "app/routers"),
("app/routers/files.py", "app/routers"),
("app/routers/init.py", "app/routers"),
("app/routers/room.py", "app/routers"),
("app/routers/upload.py", "app/routers"),
("app/services/__init__.py", "app/services"),
("app/services/coze_service.py", "app/services"),
]
for local_file, subdir in files_to_upload:
local_path = os.path.join(LOCAL_BACKEND, local_file)
if os.path.exists(local_path):
with open(local_path, 'r', encoding='utf-8') as f:
content = f.read()
if subdir:
remote_path = f"{DEPLOY_PATH}/backend/{subdir}/{os.path.basename(local_file)}"
else:
remote_path = f"{DEPLOY_PATH}/backend/{local_file}"
write_remote_file(remote_path, content)
else:
print(f" ⚠️ 文件不存在: {local_path}")
# 3. 上传 deploy 配置文件
print("\n📤 步骤 3: 上传部署配置")
deploy_files = [
"docker-compose.yml",
"Dockerfile.backend",
"Dockerfile.frontend",
]
for filename in deploy_files:
local_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
if os.path.exists(local_path):
with open(local_path, 'r', encoding='utf-8') as f:
content = f.read()
write_remote_file(f"{DEPLOY_PATH}/deploy/{filename}", content)
# 上传 nginx 配置
nginx_conf_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "nginx", "frontend.conf")
if os.path.exists(nginx_conf_path):
create_directory(f"{DEPLOY_PATH}/deploy/nginx")
with open(nginx_conf_path, 'r', encoding='utf-8') as f:
content = f.read()
write_remote_file(f"{DEPLOY_PATH}/deploy/nginx/frontend.conf", content)
# 4. 重新构建 Docker 容器
print("\n🐳 步骤 4: 重新构建 Docker 容器")
rebuild_script = f"""#!/bin/bash
cd {DEPLOY_PATH}/deploy
echo "停止旧容器..."
docker-compose down 2>/dev/null || true
sleep 2
echo "清理旧镜像..."
docker rmi deploy-backend 2>/dev/null || true
echo "重新构建..."
docker-compose up -d --build
sleep 10
echo "容器状态:"
docker ps --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}'
echo ""
echo "后端日志:"
docker logs --tail 20 ai-interview-backend 2>&1
"""
print(" 🚀 执行重建命令...")
result = run_task(rebuild_script, f"rebuild_docker_{int(time.time())}", wait_time=60)
if result and result.get("msg"):
print("\n📋 执行结果:")
print("-" * 40)
print(result["msg"][:2000])
print()
print("=" * 60)
print("✅ 上传完成!")
print("=" * 60)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""
通过文件 API 写入部署脚本
"""
import requests
import time
import hashlib
import base64
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BT_PANEL = "http://47.107.172.23:8888"
BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq"
DEPLOY_PATH = "/www/wwwroot/ai-interview"
def bt_api(action, data=None, timeout=60):
if data is None:
data = {}
request_time = int(time.time())
request_token = hashlib.md5(
f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode()
).hexdigest()
data['request_time'] = request_time
data['request_token'] = request_token
url = f"{BT_PANEL}/{action}"
try:
response = requests.post(url, data=data, timeout=timeout, verify=False)
try:
return response.json()
except:
return {"status": True, "msg": response.text[:2000]}
except Exception as e:
return {"status": False, "msg": str(e)}
def main():
print("=" * 60)
print("📝 写入部署脚本到服务器")
print("=" * 60)
deploy_script = f"""#!/bin/bash
# AI Interview 部署脚本
cd {DEPLOY_PATH}/deploy
echo "清理 Docker..."
docker system prune -af 2>/dev/null || true
echo "构建并启动..."
docker-compose up -d --build
sleep 15
echo "容器状态:"
docker ps -a
echo "后端日志:"
docker logs --tail 30 ai-interview-backend 2>&1
echo "测试:"
curl -s http://127.0.0.1:8000/health || echo "后端未响应"
"""
# 方法1: 尝试写入已存在的路径
paths_to_try = [
f"{DEPLOY_PATH}/deploy.sh",
f"{DEPLOY_PATH}/deploy/deploy.sh",
"/tmp/deploy.sh",
]
for path in paths_to_try:
print(f"\n尝试写入: {path}")
# 先尝试创建空文件
result = bt_api("files?action=CreateFile", {"path": path})
print(f" 创建文件: {result}")
# 然后写入内容
result = bt_api("files?action=SaveFileBody", {
"path": path,
"data": deploy_script,
"encoding": "utf-8"
})
if result.get("status"):
print(f" ✅ 写入成功!")
print()
print("=" * 60)
print("请在服务器执行:")
print(f" bash {path}")
print("=" * 60)
return
else:
print(f"{result.get('msg', result)}")
print()
print("=" * 60)
print("❌ 所有方法都失败了")
print()
print("请手动在服务器终端执行:")
print()
print(f"cd {DEPLOY_PATH}/deploy")
print("docker system prune -af")
print("docker-compose up -d --build")
print("=" * 60)
if __name__ == "__main__":
main()