first commit

This commit is contained in:
2026-02-13 12:33:43 +08:00
commit a614102de3
29 changed files with 17053 additions and 0 deletions

69
.dockerignore Normal file
View File

@@ -0,0 +1,69 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
env/
venv/
ENV/
env.bak/
venv.bak/
.env
.venv
# Testing
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.pytest_cache/
htmlcov/
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Project specific
uploads/*
!uploads/.gitkeep
*.log
.git/
README.md
# Docker
Dockerfile
docker-compose.yml
nginx.conf

187
.gitignore vendored Normal file
View File

@@ -0,0 +1,187 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# Application specific
uploads/
logs/
database/
chat_history/

View File

@@ -0,0 +1,45 @@
# 增加内网资产应用跳转及样式美化计划
我将在 `templates/index.html` 中添加一个新的“应用服务”板块包含您指定的6个应用跳转项并对弹出的内网资产页面二级页面进行样式美化。
## 1. 新增应用服务板块
在“台式机资源”下方新增一个 `<div class="asset-section">`,标题为“应用服务”,包含以下应用:
* **it-tools**: 端口 40116 (图标: `fa-toolbox`)
* **langflow**: 端口 7860 (图标: `fa-wind`)
* **n8n**: 端口 5678 (图标: `fa-project-diagram`)
* **certimate**: 端口 8090 (图标: `fa-certificate`)
* **ntfy**: 端口 40265 (图标: `fa-comment-dots`)
* **screego**: 端口 5050 (图标: `fa-desktop`)
* QR-code生成
地址统一为 `6.6.6.66`
## 2. 样式美化 (CSS)
我将更新 JavaScript 中动态注入的 CSS 样式,以美化弹出的二级页面:
* **整体容器**: 优化 `.internal-assets-container` 的滚动体验和间距。
* **列表项**: 增强 `.asset-item` 的视觉效果,添加更明显的悬停 (Hover) 效果和阴影。
* **凭证详情**: 美化 `.asset-credentials` 区域(虽然新应用暂无凭证,但优化现有凭证的展示):
* 添加展开动画 (`slideDown`)。
* 优化账号/密码显示框的样式,使其看起来更像代码块或卡片。
* 调整复制按钮的样式。
## 3. 实现步骤
1. 编辑 `templates/index.html`,在 `modalBody.innerHTML` 的模板字符串中插入新的 HTML 结构。
2. 在同一个文件的 `<style>` 标签内容中JS 字符串内)添加和更新 CSS 规则。

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"idf.pythonInstallPath": "/Applications/miniconda3/bin/python"
}

118
DEPLOY_NOTES.md Normal file
View File

@@ -0,0 +1,118 @@
# 部署脚本优化报告
## 🚨 主要问题修复
### 1. Python环境管理问题
**问题**: Ubuntu 24.04 使用了 PEP 668 外部管理环境策略,禁止直接使用 pip 安装包到系统环境
**解决方案**:
- 使用 Python 虚拟环境 (`python3 -m venv`)
- 所有依赖安装在隔离的虚拟环境中
- 应用启动时自动激活虚拟环境
### 2. 环境重复安装优化
**改进**:
- 添加环境检测功能,避免重复安装已存在的组件
- 智能判断:基础环境 / 虚拟环境 / 完全安装
- 如果服务器已有环境,跳过系统依赖安装
### 3. 文件上传修复
**问题**: `database/*` 被排除,导致 `ip_list.json` 无法上传
**解决方案**:
- 移除了 `database/*` 的排除规则
- `ip_list.json` 现在会正确上传到服务器
## 📁 文件结构改进
```
服务器部署结构:
/home/ubuntu/
├── host_message_venv/ # Python虚拟环境
│ ├── bin/python # 虚拟环境Python解释器
│ ├── bin/pip # 虚拟环境pip
│ └── lib/python3.12/ # 依赖包安装位置
└── host_message/ # 应用代码
├── main.py # 主应用文件
├── database/
│ └── ip_list.json # ✅ 现在会正确上传
├── start_app.sh # 启动脚本(使用虚拟环境)
├── debug_app.sh # 调试脚本
└── logs/
└── supervisor.log # 应用日志
```
## 🔧 技术改进
### 虚拟环境管理
- 创建独立的Python虚拟环境 `/home/ubuntu/host_message_venv`
- 所有依赖安装在虚拟环境中,避免系统污染
- 启动脚本自动激活虚拟环境
### 智能部署流程
```bash
1. 检查环境状态
├── 完全已安装 → 跳过所有安装
├── 部分安装 → 只创建虚拟环境
└── 未安装 → 完整安装流程
2. 虚拟环境管理
├── 检测现有虚拟环境
├── 重新创建(确保干净)
└── 安装所有Python依赖
3. 应用部署
├── 代码解压
├── 启动脚本配置
└── Supervisor进程管理
```
### 错误诊断增强
- 改进的调试脚本,支持虚拟环境
- 详细的环境信息输出
- 更好的错误日志收集
## 🚀 使用指南
### 1. 预检查(推荐)
```bash
./pre_deploy_check.sh
```
### 2. 执行部署
```bash
./deploy.sh
```
### 3. 应用管理
```bash
# 查看状态
ssh ubuntu@6.6.6.86 'sudo supervisorctl status host_message'
# 重启应用
ssh ubuntu@6.6.6.86 'sudo supervisorctl restart host_message'
# 查看日志
ssh ubuntu@6.6.6.86 'tail -f /home/ubuntu/host_message/logs/supervisor.log'
# 调试应用
ssh ubuntu@6.6.6.86 'cd /home/ubuntu/host_message && ./debug_app.sh'
```
## ✅ 问题解决确认
-**Python环境管理**: 使用虚拟环境避免 externally-managed-environment 错误
-**依赖安装**: 所有包正确安装在虚拟环境中
-**文件上传**: ip_list.json 正确上传到服务器
-**环境检测**: 智能跳过已安装的组件
-**进程管理**: 正确的启动和监控配置
-**错误诊断**: 详细的调试信息和日志
## 🎯 预期结果
部署成功后,应用将:
1. 在独立的Python虚拟环境中运行
2. 自动启动并保持运行状态
3. 监听 8888 端口提供服务
4. 包含完整的 ip_list.json 配置
5. 具备自动重启和日志管理功能
访问地址: http://6.6.6.86:8888

42
Dockerfile Normal file
View File

@@ -0,0 +1,42 @@
FROM python:3.12-slim
# 设置工作目录
WORKDIR /app
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# 安装系统依赖
RUN apt-get update && apt-get install -y \
--no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# 复制并安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建上传文件目录和设置权限
RUN mkdir -p uploads && \
chmod 755 uploads
# 创建非root用户安全性改进
RUN useradd --create-home --shell /bin/bash app && \
chown -R app:app /app
USER app
# 暴露端口
EXPOSE 8888
# 健康检查
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8888/health || exit 1
# 启动命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8888", "--proxy-headers", "--forwarded-allow-ips", "*"]

177
README.md Normal file
View File

@@ -0,0 +1,177 @@
# FastAPI 文件共享和聊天应用
这是一个基于FastAPI的文件共享和实时聊天应用支持Docker部署并解决了Docker环境下IP获取错误的问题。
## ✨ 主要功能
- 📁 文件上传和下载
- 💬 实时聊天WebSocket
- 👥 显示在线用户
- 🔍 真实IP地址获取支持Docker/代理环境)
- ❤️ 健康检查
- 🗑️ 文件删除功能
## 🚀 快速开始
### 方式1Docker Compose推荐
```bash
# 使用nginx反向代理推荐支持真实IP
docker-compose up -d
# 访问应用
open http://localhost
# 查看日志
docker-compose logs -f
```
### 方式2仅FastAPI服务
```bash
# 构建并运行
docker-compose up -d web
# 访问应用
open http://localhost:1000
```
### 方式3本地开发
```bash
# 安装依赖
pip install -r requirements.txt
# 开发模式启动
./start.sh dev
# 或直接启动
python main.py
```
## 🔧 IP获取优化
### 问题说明
在Docker环境中传统的 `request.client.host` 会返回Docker内部网络IP如172.x.x.x而不是真实的客户端IP。
### 解决方案
本项目实现了智能IP获取机制
1. **优先级顺序**
- `X-Forwarded-For` 头部
- `X-Real-IP` 头部
- `client.host`排除Docker内部IP
2. **支持的代理场景**
- Nginx反向代理
- Docker网络
- Cloudflare等CDN
- 各种负载均衡器
### 配置示例
#### Nginx配置
```nginx
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;
```
#### Docker启动参数
```bash
uvicorn main:app --proxy-headers --forwarded-allow-ips "*"
```
## 📁 项目结构
```
├── main.py # 主应用文件
├── templates/ # HTML模板
│ ├── index.html # 文件上传页面
│ └── chat.html # 聊天页面
├── uploads/ # 文件存储目录
├── Dockerfile # Docker构建文件
├── docker-compose.yml # Docker编排文件
├── nginx.conf # Nginx配置
├── requirements.txt # Python依赖
├── start.sh # 启动脚本
└── README.md # 说明文档
```
## 🔗 API端点
| 端点 | 方法 | 描述 |
|------|------|------|
| `/` | GET | 主页(文件上传) |
| `/chat` | GET | 聊天页面 |
| `/upload` | POST | 上传文件 |
| `/files` | GET | 获取文件列表 |
| `/files/{filename}` | DELETE | 删除文件 |
| `/get_ip` | GET | 获取客户端IP |
| `/ws` | WebSocket | 聊天WebSocket |
| `/health` | GET | 健康检查 |
| `/online_users` | GET | 获取在线用户 |
## 🐳 Docker部署细节
### 环境变量
```bash
FORWARDED_ALLOW_IPS=* # 允许所有IP转发
PROXY_HEADERS=1 # 启用代理头部
```
### 端口映射
- `1000` - FastAPI应用端口
- `80` - Nginx反向代理端口可选
### 数据持久化
```yaml
volumes:
- ./uploads:/app/uploads # 文件存储持久化
```
## 🛠️ 开发说明
### 本地开发
```bash
# 安装依赖
pip install -r requirements.txt
# 启动开发服务器
./start.sh dev
# 或者
python main.py
```
### 调试IP获取
```python
# 查看请求头
print(request.headers)
# 测试IP获取函数
ip = get_real_client_ip(request=request)
print(f"Client IP: {ip}")
```
## 🚨 常见问题
### Q: Docker中IP显示为172.x.x.x
A: 使用nginx反向代理或确保启动时包含 `--proxy-headers` 参数。
### Q: WebSocket连接失败
A: 检查防火墙设置和代理配置。
### Q: 文件上传失败?
A: 检查uploads目录权限和磁盘空间。
### Q: 健康检查失败?
A: 确保应用正常启动并且端口未被占用。
## 📄 许可证
MIT License
## 🤝 贡献
欢迎提交Issue和Pull Request

648
deploy.sh Normal file
View File

@@ -0,0 +1,648 @@
#!/bin/bash
# ==========================================================================
# 自动部署脚本 - host_message 项目
# 功能:将本地代码打包上传到远程服务器并自动部署
# ==========================================================================
# 配置信息
REMOTE_HOST="6.6.6.86"
REMOTE_USER="ubuntu"
REMOTE_PASS="qweasdzxc1"
REMOTE_DIR="/home/ubuntu/host_message"
LOCAL_DIR="/Users/jeremygan/Desktop/TangledupAI/host_message-main"
APP_PORT=8888
ZIP_NAME="host_message_$(date +%Y%m%d_%H%M%S).zip"
TEMP_ZIP="/tmp/$ZIP_NAME"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# 检查依赖
check_dependencies() {
log_step "检查系统依赖..."
# 检查 sshpass
if ! command -v sshpass &> /dev/null; then
log_error "sshpass 未安装,正在安装..."
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
if command -v brew &> /dev/null; then
brew install sshpass
else
log_error "请先安装 Homebrew然后运行: brew install sshpass"
exit 1
fi
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
sudo apt-get update && sudo apt-get install -y sshpass
else
log_error "不支持的操作系统,请手动安装 sshpass"
exit 1
fi
fi
# 检查 zip
if ! command -v zip &> /dev/null; then
log_error "zip 命令未找到,请安装 zip 工具"
exit 1
fi
log_info "依赖检查完成"
}
# 创建代码包
create_package() {
log_step "创建代码包..."
# 切换到项目目录
cd "$LOCAL_DIR" || {
log_error "无法进入项目目录: $LOCAL_DIR"
exit 1
}
# 删除旧的临时文件
rm -f "$TEMP_ZIP"
# 创建排除文件列表
EXCLUDE_PATTERNS=(
"*.pyc"
"__pycache__/*"
".git/*"
".DS_Store"
"logs/*"
"*.log"
".env"
"venv/*"
".venv/*"
"node_modules/*"
"uploads/*"
"chat_history/*"
)
# 构建排除参数
EXCLUDE_ARGS=""
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
EXCLUDE_ARGS="$EXCLUDE_ARGS -x $pattern"
done
# 创建zip包
log_info "正在打包文件..."
eval "zip -r \"$TEMP_ZIP\" . $EXCLUDE_ARGS"
if [ $? -eq 0 ]; then
log_info "代码包创建成功: $TEMP_ZIP"
log_info "包大小: $(du -h "$TEMP_ZIP" | cut -f1)"
else
log_error "代码包创建失败"
exit 1
fi
}
# 测试SSH连接
test_ssh_connection() {
log_step "测试SSH连接..."
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$REMOTE_USER@$REMOTE_HOST" "echo 'SSH连接测试成功'" 2>/dev/null
if [ $? -eq 0 ]; then
log_info "SSH连接测试成功"
else
log_error "SSH连接失败请检查服务器地址、用户名和密码"
exit 1
fi
}
# 上传代码包
upload_package() {
log_step "上传代码包到服务器..."
# 使用scp上传文件
sshpass -p "$REMOTE_PASS" scp -o StrictHostKeyChecking=no "$TEMP_ZIP" "$REMOTE_USER@$REMOTE_HOST:/tmp/"
if [ $? -eq 0 ]; then
log_info "代码包上传成功"
else
log_error "代码包上传失败"
exit 1
fi
}
# 远程部署
remote_deploy() {
log_step "在远程服务器上执行部署..."
# 创建远程部署脚本
REMOTE_SCRIPT=$(cat <<EOF
#!/bin/bash
# 设置颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() {
echo -e "\${GREEN}[远程INFO]\${NC} \$(date '+%Y-%m-%d %H:%M:%S') - \$1"
}
log_error() {
echo -e "\${RED}[远程ERROR]\${NC} \$(date '+%Y-%m-%d %H:%M:%S') - \$1"
}
log_warn() {
echo -e "\${YELLOW}[远程WARN]\${NC} \$(date '+%Y-%m-%d %H:%M:%S') - \$1"
}
# 检查端口占用并杀死进程
check_and_kill_port() {
log_info \"检查端口 $APP_PORT 是否被占用...\"
# 查找占用端口的进程
PID=\$(lsof -ti:$APP_PORT 2>/dev/null)
if [ ! -z \"\$PID\" ]; then
log_warn \"发现端口 $APP_PORT 被进程 \$PID 占用,正在终止...\"
kill -TERM \$PID
sleep 3
# 检查进程是否还存在
if kill -0 \$PID 2>/dev/null; then
log_warn \"进程仍然存在,强制终止...\"
kill -KILL \$PID
sleep 2
fi
# 再次检查
NEW_PID=\$(lsof -ti:$APP_PORT 2>/dev/null)
if [ ! -z \"\$NEW_PID\" ]; then
log_error \"无法终止占用端口 $APP_PORT 的进程\"
exit 1
else
log_info \"端口 $APP_PORT 已释放\"
fi
else
log_info \"端口 $APP_PORT 未被占用\"
fi
}
# 检查环境是否已安装
check_environment() {
log_info \"检查服务器环境...\"
# 检查Python3
if ! python3 --version &> /dev/null; then
log_info \"Python3 未安装,需要安装...\"
return 1
fi
# 检查是否存在虚拟环境
if [ -d \"/home/ubuntu/host_message_venv\" ]; then
log_info \"发现已存在的虚拟环境\"
return 0
fi
# 检查supervisor
if ! command -v supervisorctl &> /dev/null; then
log_info \"Supervisor 未安装,需要安装...\"
return 1
fi
log_info \"基础环境检查通过,但需要创建虚拟环境\"
return 2
}
# 安装系统依赖
install_dependencies() {
log_info \"安装系统依赖...\"
# 更新包列表
sudo apt-get update -qq
# 安装Python3、pip和相关开发工具
log_info \"安装 Python3 和相关工具...\"
sudo apt-get install -y python3 python3-pip python3-venv python3-dev build-essential python3-full
# 安装supervisor用于进程保活
if ! command -v supervisorctl &> /dev/null; then
log_info \"安装 supervisor...\"
sudo apt-get install -y supervisor
sudo systemctl enable supervisor
sudo systemctl start supervisor
fi
# 安装其他必要工具
sudo apt-get install -y curl wget unzip lsof net-tools
log_info \"系统依赖安装完成\"
}
# 创建和管理虚拟环境
setup_virtual_environment() {
local venv_path=\"/home/ubuntu/host_message_venv\"
log_info \"设置Python虚拟环境...\"
# 如果虚拟环境已存在,询问是否重新创建
if [ -d \"\$venv_path\" ]; then
log_info \"虚拟环境已存在,删除旧环境并重新创建...\"
rm -rf \"\$venv_path\"
fi
# 创建虚拟环境
log_info \"创建新的虚拟环境...\"
python3 -m venv \"\$venv_path\"
if [ \$? -eq 0 ]; then
log_info \"虚拟环境创建成功: \$venv_path\"
else
log_error \"虚拟环境创建失败\"
exit 1
fi
# 激活虚拟环境并升级pip
log_info \"激活虚拟环境并升级pip...\"
source \"\$venv_path/bin/activate\"
pip install --upgrade pip setuptools wheel
log_info \"虚拟环境设置完成\"
}
# 主要部署逻辑
main_deploy() {
# 检查并终止端口进程
check_and_kill_port
# 检查环境状态
check_environment
env_status=\$?
case \$env_status in
0)
log_info "环境已完整安装,跳过依赖安装"
;;
1)
log_info "需要安装基础环境"
install_dependencies
setup_virtual_environment
;;
2)
log_info "基础环境已安装,只需创建虚拟环境"
setup_virtual_environment
;;
esac
# 备份旧代码(如果存在)
if [ -d \"$REMOTE_DIR\" ]; then
log_info \"备份现有代码...\"
sudo mv \"$REMOTE_DIR\" \"${REMOTE_DIR}_backup_\$(date +%Y%m%d_%H%M%S)\" 2>/dev/null || true
fi
# 创建部署目录
log_info \"创建部署目录...\"
sudo mkdir -p \"$REMOTE_DIR\"
sudo chown \$USER:\$USER \"$REMOTE_DIR\"
# 解压代码包
log_info \"解压代码包...\"
cd \"$REMOTE_DIR\"
unzip -q \"/tmp/$ZIP_NAME\"
if [ \$? -eq 0 ]; then
log_info \"代码解压成功\"
else
log_error \"代码解压失败\"
exit 1
fi
# 创建必要的目录
mkdir -p logs uploads chat_history database
# 激活虚拟环境并安装Python依赖
log_info \"激活虚拟环境并安装Python依赖...\"
source \"/home/ubuntu/host_message_venv/bin/activate\"
if [ -f \"requirements.txt\" ]; then
log_info \"发现 requirements.txt安装依赖包...\"
pip install -r requirements.txt
if [ \$? -eq 0 ]; then
log_info \"Python依赖安装成功\"
else
log_error \"Python依赖安装失败\"
# 尝试单独安装每个包
log_info \"尝试单独安装依赖包...\"
while IFS= read -r package; do
if [[ ! \$package =~ ^[[:space:]]*# ]] && [[ ! -z \$package ]]; then
log_info \"安装: \$package\"
pip install \$package || log_warn \"安装 \$package 失败\"
fi
done < requirements.txt
fi
else
log_warn \"未找到 requirements.txt 文件\"
fi
# 验证关键依赖
log_info \"验证Python依赖...\"
python -c \"import fastapi, uvicorn; print('关键依赖验证成功')\" || {
log_error \"关键依赖验证失败,手动安装...\"
pip install fastapi uvicorn
}
# 检查main.py文件
if [ ! -f \"main.py\" ]; then
log_error \"main.py 文件不存在!\"
exit 1
fi
# 测试Python应用是否可以启动
log_info \"测试Python应用...\"
source \"/home/ubuntu/host_message_venv/bin/activate\"
timeout 10 python -c \"
import sys
sys.path.insert(0, '.')
try:
import main
print('应用模块导入成功')
except Exception as e:
print(f'应用模块导入失败: {e}')
sys.exit(1)
\" || log_warn \"应用模块测试失败,但继续部署...\""
# 创建启动脚本
log_info \"创建应用启动脚本...\"
cat > start_app.sh <<SCRIPT_EOF
#!/bin/bash
set -e
# 进入应用目录
cd \"$REMOTE_DIR\"
# 激活虚拟环境
source \"/home/ubuntu/host_message_venv/bin/activate\"
# 设置环境变量
export PYTHONPATH=\"$REMOTE_DIR:\\\$PYTHONPATH\"
# 记录启动信息
echo \"[\$(date)] 应用启动开始...\"
echo \"当前目录: \$(pwd)\"
echo \"Python版本: \$(python --version)\"
echo \"虚拟环境: \$VIRTUAL_ENV\"
# 检查必要文件
if [ ! -f \"main.py\" ]; then
echo \"错误: main.py 文件不存在\"
exit 1
fi
# 启动应用
echo \"[\$(date)] 启动Python应用...\"
python main.py
SCRIPT_EOF
chmod +x start_app.sh
# 创建调试脚本
log_info \"创建调试脚本...\"
cat > debug_app.sh <<DEBUG_EOF
#!/bin/bash
cd \"$REMOTE_DIR\"
echo \"=== 调试信息 ===\"
echo \"当前目录: \$(pwd)\"
# 激活虚拟环境
source \"/home/ubuntu/host_message_venv/bin/activate\" 2>/dev/null || echo \"虚拟环境激活失败\"
echo \"Python版本: \$(python --version 2>/dev/null || python3 --version)\"
echo \"pip版本: \$(pip --version 2>/dev/null || echo '无pip')\"
echo \"虚拟环境: \$VIRTUAL_ENV\"
echo \"文件列表:\"
ls -la
echo \"=== 尝试导入测试 ===\"
python -c \"
import sys
print('Python路径:', sys.path)
try:
import fastapi
print('fastapi版本:', fastapi.__version__)
except ImportError as e:
print('fastapi导入失败:', e)
try:
import uvicorn
print('uvicorn版本:', uvicorn.__version__)
except ImportError as e:
print('uvicorn导入失败:', e)
\" 2>/dev/null || python3 -c \"
import sys
print('Python路径:', sys.path)
try:
import fastapi
print('fastapi版本:', fastapi.__version__)
except ImportError as e:
print('fastapi导入失败:', e)
try:
import uvicorn
print('uvicorn版本:', uvicorn.__version__)
except ImportError as e:
print('uvicorn导入失败:', e)
\"
echo \"=== 检查端口占用 ===\"
netstat -tlnp | grep 8888 || echo '端口8888未被占用'
echo \"=== 尝试启动应用5秒后停止 ===\"
timeout 5 python main.py 2>/dev/null || timeout 5 python3 main.py || echo '应用启动测试完成'
DEBUG_EOF
chmod +x debug_app.sh
# 创建supervisor配置
log_info \"配置进程保活监控...\"
sudo tee /etc/supervisor/conf.d/host_message.conf > /dev/null <<EOF
[program:host_message]
command=$REMOTE_DIR/start_app.sh
directory=$REMOTE_DIR
user=ubuntu
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=$REMOTE_DIR/logs/supervisor.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=3
environment=PATH=\"/home/ubuntu/host_message_venv/bin:/usr/local/bin:/usr/bin:/bin\",PYTHONPATH=\"$REMOTE_DIR\"
startsecs=10
startretries=3
EOF
# 停止可能存在的旧进程
sudo supervisorctl stop host_message 2>/dev/null || true
# 重新加载supervisor配置
sudo supervisorctl reread
sudo supervisorctl update
# 启动应用
log_info \"启动应用...\"
sudo supervisorctl start host_message
# 等待应用启动并检查状态
log_info \"等待应用启动...\"
for i in {1..30}; do
sleep 2
STATUS=\$(sudo supervisorctl status host_message 2>/dev/null || echo "ERROR")
log_info \"\$i 次检查: \$STATUS\"
if echo \"\$STATUS\" | grep -q \"RUNNING\"; then
log_info \"应用启动成功!\"
break
elif echo \"\$STATUS\" | grep -q \"FATAL\\|BACKOFF\"; then
log_error \"应用启动失败: \$STATUS\"
log_error \"查看详细日志:\"
tail -20 \"$REMOTE_DIR/logs/supervisor.log\" 2>/dev/null || echo \"无法读取日志文件\"
# 运行调试脚本
log_info \"运行调试脚本获取详细信息:\"
cd \"$REMOTE_DIR\"
./debug_app.sh 2>&1 || true
# 检查supervisor错误日志
log_info \"检查supervisor错误日志:\"
sudo tail -20 /var/log/supervisor/supervisord.log 2>/dev/null || echo \"无法读取supervisor日志\"
exit 1
fi
if [ \$i -eq 30 ]; then
log_error \"应用启动超时\"
log_error \"最终状态: \$STATUS\"
exit 1
fi
done
# 检查端口监听
log_info \"检查端口监听状态...\"
for i in {1..10}; do
if netstat -tlnp 2>/dev/null | grep -q \":$APP_PORT\" || ss -tlnp 2>/dev/null | grep -q \":$APP_PORT\"; then
log_info \"端口 $APP_PORT 监听正常\"
log_info \"部署完成!您可以通过 http://$REMOTE_HOST:$APP_PORT 访问应用\"
break
else
log_warn \"等待端口 $APP_PORT 开始监听... (\$i/10)\"
sleep 2
fi
if [ \$i -eq 10 ]; then
log_warn \"端口 $APP_PORT 未在监听,请检查应用日志\"
log_info \"当前监听的端口:\"
netstat -tlnp 2>/dev/null | grep LISTEN || ss -tlnp 2>/dev/null | grep LISTEN || echo \"无法获取监听端口信息\"
fi
done
# 清理临时文件
rm -f \"/tmp/$ZIP_NAME\"
log_info \"部署脚本执行完成\"
}
# 执行主要部署逻辑
main_deploy
EOF
)
# 执行远程部署脚本
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" "$REMOTE_SCRIPT"
if [ $? -eq 0 ]; then
log_info "远程部署完成"
else
log_error "远程部署失败"
exit 1
fi
}
# 清理本地临时文件
cleanup() {
log_step "清理临时文件..."
rm -f "$TEMP_ZIP"
log_info "临时文件清理完成"
}
# 显示部署信息
show_deploy_info() {
echo ""
echo "=========================================="
echo " 部署完成信息"
echo "=========================================="
echo "服务器地址: $REMOTE_HOST"
echo "部署目录: $REMOTE_DIR"
echo "应用端口: $APP_PORT"
echo "访问地址: http://$REMOTE_HOST:$APP_PORT"
echo "=========================================="
echo ""
echo "常用管理命令:"
echo "查看应用状态: ssh $REMOTE_USER@$REMOTE_HOST 'sudo supervisorctl status host_message'"
echo "重启应用: ssh $REMOTE_USER@$REMOTE_HOST 'sudo supervisorctl restart host_message'"
echo "停止应用: ssh $REMOTE_USER@$REMOTE_HOST 'sudo supervisorctl stop host_message'"
echo "查看日志: ssh $REMOTE_USER@$REMOTE_HOST 'tail -f $REMOTE_DIR/logs/supervisor.log'"
echo "调试应用: ssh $REMOTE_USER@$REMOTE_HOST 'cd $REMOTE_DIR && ./debug_app.sh'"
echo "=========================================="
# 最后验证部署
log_info "正在验证部署结果..."
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" "
echo '=== 最终验证 ==='
sudo supervisorctl status host_message
echo '=== 端口检查 ==='
netstat -tlnp | grep 8888 || ss -tlnp | grep 8888 || echo '端口8888未监听'
echo '=== 进程检查 ==='
ps aux | grep 'python3 main.py' | grep -v grep || echo '未找到Python进程'
" || log_warn "最终验证失败,请手动检查"
}
# 主函数
main() {
echo "=========================================="
echo " host_message 项目自动部署脚本"
echo "=========================================="
echo "目标服务器: $REMOTE_HOST"
echo "部署目录: $REMOTE_DIR"
echo "开始时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="
echo ""
# 执行部署步骤
check_dependencies
create_package
test_ssh_connection
upload_package
remote_deploy
cleanup
show_deploy_info
log_info "🎉 全部部署任务完成!"
}
# 错误处理
set -e
trap 'log_error "脚本执行过程中发生错误,退出码: $?"' ERR
# 执行主函数
main "$@"

243
deploy_fixed.sh Normal file
View File

@@ -0,0 +1,243 @@
#!/bin/bash
# ==========================================================================
# 自动部署脚本 - host_message 项目
# 功能:将本地代码打包上传到远程服务器并自动部署
# ==========================================================================
# 配置信息
REMOTE_HOST="6.6.6.86"
REMOTE_USER="ubuntu"
REMOTE_PASS="qweasdzxc1"
REMOTE_DIR="/home/ubuntu/host_message"
LOCAL_DIR="/Users/jeremygan/Desktop/TangledupAI/host_message-main"
APP_PORT=8888
ZIP_NAME="host_message_$(date +%Y%m%d_%H%M%S).zip"
TEMP_ZIP="/tmp/$ZIP_NAME"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# 检查依赖
check_dependencies() {
log_step "检查系统依赖..."
# 检查 sshpass
if ! command -v sshpass &> /dev/null; then
log_error "sshpass 未安装,正在安装..."
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
if command -v brew &> /dev/null; then
brew install sshpass
else
log_error "请先安装 Homebrew然后运行: brew install sshpass"
exit 1
fi
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
sudo apt-get update && sudo apt-get install -y sshpass
else
log_error "不支持的操作系统,请手动安装 sshpass"
exit 1
fi
fi
# 检查 zip
if ! command -v zip &> /dev/null; then
log_error "zip 命令未找到,请安装 zip 工具"
exit 1
fi
log_info "依赖检查完成"
}
# 创建代码包
create_package() {
log_step "创建代码包..."
# 切换到项目目录
cd "$LOCAL_DIR" || {
log_error "无法进入项目目录: $LOCAL_DIR"
exit 1
}
# 删除旧的临时文件
rm -f "$TEMP_ZIP"
# 创建排除文件列表
EXCLUDE_PATTERNS=(
"*.pyc"
"__pycache__/*"
".git/*"
".DS_Store"
"logs/*"
"*.log"
".env"
"venv/*"
".venv/*"
"node_modules/*"
"uploads/*"
"chat_history/*"
)
# 构建排除参数
EXCLUDE_ARGS=""
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
EXCLUDE_ARGS="$EXCLUDE_ARGS -x $pattern"
done
# 创建zip包
log_info "正在打包文件..."
eval "zip -r \"$TEMP_ZIP\" . $EXCLUDE_ARGS"
if [ $? -eq 0 ]; then
log_info "代码包创建成功: $TEMP_ZIP"
log_info "包大小: $(du -h "$TEMP_ZIP" | cut -f1)"
else
log_error "代码包创建失败"
exit 1
fi
}
# 测试SSH连接
test_ssh_connection() {
log_step "测试SSH连接..."
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$REMOTE_USER@$REMOTE_HOST" "echo 'SSH连接测试成功'" 2>/dev/null
if [ $? -eq 0 ]; then
log_info "SSH连接测试成功"
else
log_error "SSH连接失败请检查服务器地址、用户名和密码"
exit 1
fi
}
# 上传代码包
upload_package() {
log_step "上传代码包到服务器..."
# 使用scp上传文件
sshpass -p "$REMOTE_PASS" scp -o StrictHostKeyChecking=no "$TEMP_ZIP" "$REMOTE_USER@$REMOTE_HOST:/tmp/"
if [ $? -eq 0 ]; then
log_info "代码包上传成功"
else
log_error "代码包上传失败"
exit 1
fi
}
# 远程部署
remote_deploy() {
log_step "在远程服务器上执行部署..."
# 上传远程部署脚本
sshpass -p "$REMOTE_PASS" scp -o StrictHostKeyChecking=no "remote_deploy_script.sh" "$REMOTE_USER@$REMOTE_HOST:/tmp/"
# 执行远程部署脚本
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" "chmod +x /tmp/remote_deploy_script.sh && /tmp/remote_deploy_script.sh '$ZIP_NAME'"
if [ $? -eq 0 ]; then
log_info "远程部署完成"
else
log_error "远程部署失败"
exit 1
fi
}
# 清理本地临时文件
cleanup() {
log_step "清理临时文件..."
rm -f "$TEMP_ZIP"
log_info "临时文件清理完成"
}
# 显示部署信息
show_deploy_info() {
echo ""
echo "=========================================="
echo " 部署完成信息"
echo "=========================================="
echo "服务器地址: $REMOTE_HOST"
echo "部署目录: $REMOTE_DIR"
echo "应用端口: $APP_PORT"
echo "访问地址: http://$REMOTE_HOST:$APP_PORT"
echo "=========================================="
echo ""
echo "常用管理命令:"
echo "查看应用状态: ssh $REMOTE_USER@$REMOTE_HOST 'sudo supervisorctl status host_message'"
echo "重启应用: ssh $REMOTE_USER@$REMOTE_HOST 'sudo supervisorctl restart host_message'"
echo "停止应用: ssh $REMOTE_USER@$REMOTE_HOST 'sudo supervisorctl stop host_message'"
echo "查看日志: ssh $REMOTE_USER@$REMOTE_HOST 'tail -f $REMOTE_DIR/logs/supervisor.log'"
echo "调试应用: ssh $REMOTE_USER@$REMOTE_HOST 'cd $REMOTE_DIR && ./debug_app.sh'"
echo "=========================================="
# 最后验证部署
log_info "正在验证部署结果..."
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" "
echo '=== 最终验证 ==='
sudo supervisorctl status host_message
echo '=== 端口检查 ==='
netstat -tlnp | grep 8888 || ss -tlnp | grep 8888 || echo '端口8888未监听'
echo '=== 进程检查 ==='
ps aux | grep 'python3 main.py' | grep -v grep || echo '未找到Python进程'
" || log_warn "最终验证失败,请手动检查"
}
# 主函数
main() {
echo "=========================================="
echo " host_message 项目自动部署脚本"
echo "=========================================="
echo "目标服务器: $REMOTE_HOST"
echo "部署目录: $REMOTE_DIR"
echo "开始时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="
echo ""
# 执行部署步骤
check_dependencies
create_package
test_ssh_connection
upload_package
remote_deploy
cleanup
show_deploy_info
log_info "🎉 全部部署任务完成!"
}
# 错误处理
set -e
trap 'log_error "脚本执行过程中发生错误,退出码: $?"' ERR
# 执行主函数
main "$@"

25
docker-compose.yml Normal file
View File

@@ -0,0 +1,25 @@
version: '3.8'
services:
web:
build: .
ports:
- "8888:8888"
volumes:
- ./uploads:/app/uploads
environment:
- FORWARDED_ALLOW_IPS=*
- PROXY_HEADERS=1
restart: unless-stopped
# 可选添加nginx反向代理来更好地处理真实IP
nginx:
image: nginx:alpine
ports:
- "88:88"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- /mnt/server/host_message_files:/mnt/server/host_message_files
depends_on:
- web
restart: unless-stopped

BIN
image/avatar/ag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
image/avatar/azhi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
image/avatar/changfeng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
image/avatar/hongzong.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
image/avatar/liwei.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
image/avatar/server.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
image/avatar/xiaoji.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
image/logo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
image/logo_w.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

1771
main.py Normal file

File diff suppressed because it is too large Load Diff

57
nginx.conf Normal file
View File

@@ -0,0 +1,57 @@
events {
worker_connections 1024;
}
http {
upstream fastapi_backend {
server web:8000;
}
server {
listen 80;
server_name localhost;
# 设置客户端最大体上传大小 (用于文件上传)
client_max_body_size 100M;
# 代理配置
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;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
location / {
proxy_pass http://fastapi_backend;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# WebSocket 特殊处理
location /ws {
proxy_pass http://fastapi_backend;
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;
# WebSocket 超时设置
proxy_read_timeout 86400;
}
# 静态文件处理
location /uploads/ {
proxy_pass http://fastapi_backend;
}
}
}

74
pre_deploy_check.sh Normal file
View File

@@ -0,0 +1,74 @@
#!/bin/bash
# 部署前检查脚本
echo "=========================================="
echo " 部署前环境检查"
echo "=========================================="
# 检查本地环境
echo "1. 检查本地环境..."
echo " - 当前目录: $(pwd)"
echo " - 主文件存在: $(test -f main.py && echo "✅ main.py存在" || echo "❌ main.py不存在")"
echo " - 依赖文件存在: $(test -f requirements.txt && echo "✅ requirements.txt存在" || echo "❌ requirements.txt不存在")"
echo " - zip命令: $(command -v zip >/dev/null && echo "✅ 已安装" || echo "❌ 未安装")"
# 检查sshpass
if command -v sshpass >/dev/null; then
echo " - sshpass: ✅ 已安装"
else
echo " - sshpass: ❌ 未安装"
if [[ "$OSTYPE" == "darwin"* ]]; then
echo " 安装命令: brew install sshpass"
else
echo " 安装命令: sudo apt-get install sshpass"
fi
fi
echo ""
echo "2. 检查目标服务器连接..."
REMOTE_HOST="6.6.6.86"
REMOTE_USER="ubuntu"
REMOTE_PASS="qweasdzxc1"
if command -v sshpass >/dev/null; then
echo " - 测试SSH连接..."
if sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 "$REMOTE_USER@$REMOTE_HOST" "echo 'SSH连接正常'" 2>/dev/null; then
echo " - SSH连接: ✅ 正常"
# 检查服务器系统信息
echo " - 服务器信息:"
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" "
echo ' 操作系统: '$(lsb_release -d 2>/dev/null | cut -f2 || echo 'Unknown')
echo ' Python版本: '$(python3 --version 2>/dev/null || echo '未安装')
echo ' 磁盘空间: '$(df -h / | tail -1 | awk '{print \$4}' || echo '未知') 可用
" 2>/dev/null
else
echo " - SSH连接: ❌ 失败"
echo " 请检查服务器地址、用户名和密码"
fi
else
echo " - SSH连接: ⚠️ 跳过 (sshpass未安装)"
fi
echo ""
echo "3. 检查项目文件..."
echo " - 项目大小: $(du -sh . 2>/dev/null | cut -f1 || echo "未知")"
echo " - 关键文件检查:"
echo " * main.py: $(test -f main.py && echo "✅ 存在" || echo "❌ 缺失")"
echo " * requirements.txt: $(test -f requirements.txt && echo "✅ 存在" || echo "❌ 缺失")"
echo " * database/ip_list.json: $(test -f database/ip_list.json && echo "✅ 存在" || echo "❌ 缺失")"
if [ -f "requirements.txt" ]; then
echo " - Python依赖包:"
while IFS= read -r line; do
if [[ ! $line =~ ^[[:space:]]*# ]] && [[ ! -z $line ]]; then
echo " * $line"
fi
done < requirements.txt
fi
echo ""
echo "=========================================="
echo "检查完成!如果所有项目都显示 ✅,您可以运行:"
echo "./deploy.sh"
echo "=========================================="

415
remote_deploy_script.sh Normal file
View File

@@ -0,0 +1,415 @@
#!/bin/bash
# 设置颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() {
echo -e "${GREEN}[远程INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_error() {
echo -e "${RED}[远程ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_warn() {
echo -e "${YELLOW}[远程WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# 检查端口占用并杀死进程
check_and_kill_port() {
log_info "检查端口 8888 是否被占用..."
# 查找占用端口的进程
PID=$(lsof -ti:8888 2>/dev/null)
if [ ! -z "$PID" ]; then
log_warn "发现端口 8888 被进程 $PID 占用,正在终止..."
kill -TERM $PID
sleep 3
# 检查进程是否还存在
if kill -0 $PID 2>/dev/null; then
log_warn "进程仍然存在,强制终止..."
kill -KILL $PID
sleep 2
fi
# 再次检查
NEW_PID=$(lsof -ti:8888 2>/dev/null)
if [ ! -z "$NEW_PID" ]; then
log_error "无法终止占用端口 8888 的进程"
exit 1
else
log_info "端口 8888 已释放"
fi
else
log_info "端口 8888 未被占用"
fi
}
# 检查环境是否已安装
check_environment() {
log_info "检查服务器环境..."
# 检查Python3
if ! python3 --version &> /dev/null; then
log_info "Python3 未安装,需要安装..."
return 1
fi
# 检查是否存在虚拟环境
if [ -d "/home/ubuntu/host_message_venv" ]; then
log_info "发现已存在的虚拟环境"
return 0
fi
# 检查supervisor
if ! command -v supervisorctl &> /dev/null; then
log_info "Supervisor 未安装,需要安装..."
return 1
fi
log_info "基础环境检查通过,但需要创建虚拟环境"
return 2
}
# 安装系统依赖
install_dependencies() {
log_info "安装系统依赖..."
# 更新包列表
sudo apt-get update -qq
# 安装Python3、pip和相关开发工具
log_info "安装 Python3 和相关工具..."
sudo apt-get install -y python3 python3-pip python3-venv python3-dev build-essential python3-full
# 安装supervisor用于进程保活
if ! command -v supervisorctl &> /dev/null; then
log_info "安装 supervisor..."
sudo apt-get install -y supervisor
sudo systemctl enable supervisor
sudo systemctl start supervisor
fi
# 安装其他必要工具
sudo apt-get install -y curl wget unzip lsof net-tools
log_info "系统依赖安装完成"
}
# 创建和管理虚拟环境
setup_virtual_environment() {
local venv_path="/home/ubuntu/host_message_venv"
log_info "设置Python虚拟环境..."
# 如果虚拟环境已存在,询问是否重新创建
if [ -d "$venv_path" ]; then
log_info "虚拟环境已存在,删除旧环境并重新创建..."
rm -rf "$venv_path"
fi
# 创建虚拟环境
log_info "创建新的虚拟环境..."
python3 -m venv "$venv_path"
if [ $? -eq 0 ]; then
log_info "虚拟环境创建成功: $venv_path"
else
log_error "虚拟环境创建失败"
exit 1
fi
# 激活虚拟环境并升级pip
log_info "激活虚拟环境并升级pip..."
source "$venv_path/bin/activate"
pip install --upgrade pip setuptools wheel
log_info "虚拟环境设置完成"
}
# 主要部署逻辑
main_deploy() {
# 检查并终止端口进程
check_and_kill_port
# 检查环境状态
check_environment
env_status=$?
case $env_status in
0)
log_info "环境已完整安装,跳过依赖安装"
;;
1)
log_info "需要安装基础环境"
install_dependencies
setup_virtual_environment
;;
2)
log_info "基础环境已安装,只需创建虚拟环境"
setup_virtual_environment
;;
esac
# 备份旧代码(如果存在)
if [ -d "/home/ubuntu/host_message" ]; then
log_info "备份现有代码..."
sudo mv "/home/ubuntu/host_message" "/home/ubuntu/host_message_backup_$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
fi
# 创建部署目录
log_info "创建部署目录..."
sudo mkdir -p "/home/ubuntu/host_message"
sudo chown $USER:$USER "/home/ubuntu/host_message"
# 解压代码包
log_info "解压代码包..."
cd "/home/ubuntu/host_message"
unzip -q "/tmp/$1"
if [ $? -eq 0 ]; then
log_info "代码解压成功"
else
log_error "代码解压失败"
exit 1
fi
# 创建必要的目录
mkdir -p logs uploads chat_history database
# 激活虚拟环境并安装Python依赖
log_info "激活虚拟环境并安装Python依赖..."
source "/home/ubuntu/host_message_venv/bin/activate"
if [ -f "requirements.txt" ]; then
log_info "发现 requirements.txt安装依赖包..."
pip install -r requirements.txt
if [ $? -eq 0 ]; then
log_info "Python依赖安装成功"
else
log_error "Python依赖安装失败"
# 尝试单独安装每个包
log_info "尝试单独安装依赖包..."
while IFS= read -r package; do
if [[ ! $package =~ ^[[:space:]]*# ]] && [[ ! -z $package ]]; then
log_info "安装: $package"
pip install $package || log_warn "安装 $package 失败"
fi
done < requirements.txt
fi
else
log_warn "未找到 requirements.txt 文件"
fi
# 验证关键依赖
log_info "验证Python依赖..."
python -c "import fastapi, uvicorn; print('关键依赖验证成功')" || {
log_error "关键依赖验证失败,手动安装..."
pip install fastapi uvicorn
}
# 检查main.py文件
if [ ! -f "main.py" ]; then
log_error "main.py 文件不存在!"
exit 1
fi
# 测试Python应用是否可以启动
log_info "测试Python应用..."
source "/home/ubuntu/host_message_venv/bin/activate"
timeout 10 python -c "
import sys
sys.path.insert(0, '.')
try:
import main
print('应用模块导入成功')
except Exception as e:
print(f'应用模块导入失败: {e}')
sys.exit(1)
" || log_warn "应用模块测试失败,但继续部署..."
# 创建启动脚本
log_info "创建应用启动脚本..."
cat > start_app.sh <<'SCRIPT_EOF'
#!/bin/bash
set -e
# 进入应用目录
cd "/home/ubuntu/host_message"
# 激活虚拟环境
source "/home/ubuntu/host_message_venv/bin/activate"
# 设置环境变量
export PYTHONPATH="/home/ubuntu/host_message:$PYTHONPATH"
# 记录启动信息
echo "[$(date)] 应用启动开始..."
echo "当前目录: $(pwd)"
echo "Python版本: $(python --version)"
echo "虚拟环境: $VIRTUAL_ENV"
# 检查必要文件
if [ ! -f "main.py" ]; then
echo "错误: main.py 文件不存在"
exit 1
fi
# 启动应用
echo "[$(date)] 启动Python应用..."
python main.py
SCRIPT_EOF
chmod +x start_app.sh
# 创建调试脚本
log_info "创建调试脚本..."
cat > debug_app.sh <<'DEBUG_EOF'
#!/bin/bash
cd "/home/ubuntu/host_message"
echo "=== 调试信息 ==="
echo "当前目录: $(pwd)"
# 激活虚拟环境
source "/home/ubuntu/host_message_venv/bin/activate" 2>/dev/null || echo "虚拟环境激活失败"
echo "Python版本: $(python --version 2>/dev/null || python3 --version)"
echo "pip版本: $(pip --version 2>/dev/null || echo '无pip')"
echo "虚拟环境: $VIRTUAL_ENV"
echo "文件列表:"
ls -la
echo "=== 尝试导入测试 ==="
python -c "
import sys
print('Python路径:', sys.path)
try:
import fastapi
print('fastapi版本:', fastapi.__version__)
except ImportError as e:
print('fastapi导入失败:', e)
try:
import uvicorn
print('uvicorn版本:', uvicorn.__version__)
except ImportError as e:
print('uvicorn导入失败:', e)
" 2>/dev/null || python3 -c "
import sys
print('Python路径:', sys.path)
try:
import fastapi
print('fastapi版本:', fastapi.__version__)
except ImportError as e:
print('fastapi导入失败:', e)
try:
import uvicorn
print('uvicorn版本:', uvicorn.__version__)
except ImportError as e:
print('uvicorn导入失败:', e)
"
echo "=== 检查端口占用 ==="
netstat -tlnp | grep 8888 || echo '端口8888未被占用'
echo "=== 尝试启动应用5秒后停止 ==="
timeout 5 python main.py 2>/dev/null || timeout 5 python3 main.py || echo '应用启动测试完成'
DEBUG_EOF
chmod +x debug_app.sh
# 创建supervisor配置
log_info "配置进程保活监控..."
sudo tee /etc/supervisor/conf.d/host_message.conf > /dev/null <<SUPERVISOR_EOF
[program:host_message]
command=/home/ubuntu/host_message/start_app.sh
directory=/home/ubuntu/host_message
user=ubuntu
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/home/ubuntu/host_message/logs/supervisor.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=3
environment=PATH="/home/ubuntu/host_message_venv/bin:/usr/local/bin:/usr/bin:/bin",PYTHONPATH="/home/ubuntu/host_message"
startsecs=10
startretries=3
SUPERVISOR_EOF
# 停止可能存在的旧进程
sudo supervisorctl stop host_message 2>/dev/null || true
# 重新加载supervisor配置
sudo supervisorctl reread
sudo supervisorctl update
# 启动应用
log_info "启动应用..."
sudo supervisorctl start host_message
# 等待应用启动并检查状态
log_info "等待应用启动..."
for i in {1..30}; do
sleep 2
STATUS=$(sudo supervisorctl status host_message 2>/dev/null || echo "ERROR")
log_info "$i 次检查: $STATUS"
if echo "$STATUS" | grep -q "RUNNING"; then
log_info "应用启动成功!"
break
elif echo "$STATUS" | grep -q "FATAL\|BACKOFF"; then
log_error "应用启动失败: $STATUS"
log_error "查看详细日志:"
tail -20 "/home/ubuntu/host_message/logs/supervisor.log" 2>/dev/null || echo "无法读取日志文件"
# 运行调试脚本
log_info "运行调试脚本获取详细信息:"
cd "/home/ubuntu/host_message"
./debug_app.sh 2>&1 || true
# 检查supervisor错误日志
log_info "检查supervisor错误日志:"
sudo tail -20 /var/log/supervisor/supervisord.log 2>/dev/null || echo "无法读取supervisor日志"
exit 1
fi
if [ $i -eq 30 ]; then
log_error "应用启动超时"
log_error "最终状态: $STATUS"
exit 1
fi
done
# 检查端口监听
log_info "检查端口监听状态..."
for i in {1..10}; do
if netstat -tlnp 2>/dev/null | grep -q ":8888" || ss -tlnp 2>/dev/null | grep -q ":8888"; then
log_info "端口 8888 监听正常"
log_info "部署完成!您可以通过 http://6.6.6.86:8888 访问应用"
break
else
log_warn "等待端口 8888 开始监听... ($i/10)"
sleep 2
fi
if [ $i -eq 10 ]; then
log_warn "端口 8888 未在监听,请检查应用日志"
log_info "当前监听的端口:"
netstat -tlnp 2>/dev/null | grep LISTEN || ss -tlnp 2>/dev/null | grep LISTEN || echo "无法获取监听端口信息"
fi
done
# 清理临时文件
rm -f "/tmp/$1"
log_info "部署脚本执行完成"
}
# 执行主要部署逻辑
main_deploy "$1"

7
requirements.txt Normal file
View File

@@ -0,0 +1,7 @@
fastapi==0.109.0
uvicorn[standard]==0.27.0
python-multipart==0.0.6
websockets==12.0
jinja2==3.1.2
aiofiles==23.2.0
python-json-logger==2.0.7

172
start.sh Normal file
View File

@@ -0,0 +1,172 @@
#!/bin/bash
# 定义常量
PORT=8888
LOG_FILE="logs/$(date +%Y%m%d_%H%M%S)_message.log"
PID_FILE="logs/app.pid"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 创建日志目录
mkdir -p "$SCRIPT_DIR/logs"
# 切换到脚本目录
cd "$SCRIPT_DIR" || {
echo "[$(date +%Y-%m-%d\ %H:%M:%S)] 错误: 无法进入目录 $SCRIPT_DIR" | tee -a "$LOG_FILE"
exit 1
}
# 日志输出函数
log_info() {
echo "[$(date +%Y-%m-%d\ %H:%M:%S)] [INFO] $1" | tee -a "$LOG_FILE"
}
log_error() {
echo "[$(date +%Y-%m-%d\ %H:%M:%S)] [ERROR] $1" | tee -a "$LOG_FILE"
}
log_warn() {
echo "[$(date +%Y-%m-%d\ %H:%M:%S)] [WARN] $1" | tee -a "$LOG_FILE"
}
# 检查端口是否被占用
check_port() {
local port=$1
if command -v lsof >/dev/null 2>&1; then
lsof -i :$port >/dev/null 2>&1
elif command -v netstat >/dev/null 2>&1; then
netstat -tuln | grep ":$port " >/dev/null 2>&1
elif command -v ss >/dev/null 2>&1; then
ss -tuln | grep ":$port " >/dev/null 2>&1
else
log_error "无法找到检查端口的命令 (lsof, netstat, ss)"
return 1
fi
}
# 健康检查函数
health_check() {
local url="http://localhost:$PORT"
local timeout=5
if command -v curl >/dev/null 2>&1; then
curl -s --connect-timeout $timeout --max-time $timeout "$url" >/dev/null 2>&1
elif command -v wget >/dev/null 2>&1; then
wget -q --timeout=$timeout --tries=1 -O /dev/null "$url" >/dev/null 2>&1
else
log_warn "无法找到HTTP检查工具 (curl, wget),跳过健康检查"
return 0
fi
}
# 停止现有进程
stop_existing_process() {
log_info "停止现有进程..."
# 从PID文件停止
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
log_info "正在停止进程 PID: $pid"
kill "$pid"
sleep 2
if kill -0 "$pid" 2>/dev/null; then
log_warn "进程仍在运行,强制终止"
kill -9 "$pid"
fi
rm -f "$PID_FILE"
else
log_info "PID文件中的进程已不存在清理PID文件"
rm -f "$PID_FILE"
fi
fi
# 通过端口查找并停止进程
if check_port $PORT; then
log_info "发现端口 $PORT 仍被占用,查找并停止相关进程"
if command -v lsof >/dev/null 2>&1; then
local pids=$(lsof -ti :$PORT)
if [ -n "$pids" ]; then
echo "$pids" | xargs kill -15 2>/dev/null || true
sleep 2
# 如果进程仍在运行,强制杀死
local remaining_pids=$(lsof -ti :$PORT 2>/dev/null)
if [ -n "$remaining_pids" ]; then
echo "$remaining_pids" | xargs kill -9 2>/dev/null || true
fi
fi
fi
fi
}
# 启动应用
start_app() {
log_info "开始启动应用..."
# 后台运行Python程序
nohup python main.py >> "$LOG_FILE" 2>&1 &
local app_pid=$!
# 保存PID
echo $app_pid > "$PID_FILE"
log_info "应用已启动PID: $app_pid"
# 等待应用启动
local max_wait=30
local wait_time=0
while [ $wait_time -lt $max_wait ]; do
if check_port $PORT; then
log_info "端口 $PORT 已开始监听"
break
fi
sleep 1
wait_time=$((wait_time + 1))
done
if [ $wait_time -ge $max_wait ]; then
log_error "应用启动超时,端口 $PORT 未开始监听"
return 1
fi
# 健康检查
sleep 2
if health_check; then
log_info "应用健康检查通过"
return 0
else
log_warn "应用健康检查失败,但端口已监听"
return 0
fi
}
# 主逻辑
main() {
log_info "开始检查应用状态..."
# 检查端口是否被占用
if check_port $PORT; then
log_info "检测到端口 $PORT 已被占用"
# 进行健康检查
if health_check; then
log_info "应用正常运行,端口 $PORT 可正常访问,跳过重启"
exit 0
else
log_warn "端口 $PORT 被占用但健康检查失败,准备重启应用"
stop_existing_process
fi
else
log_info "端口 $PORT 未被占用,准备启动应用"
fi
# 启动应用
if start_app; then
log_info "应用启动成功"
else
log_error "应用启动失败"
exit 1
fi
}
# 执行主函数
main

6058
templates/chat.html Normal file

File diff suppressed because it is too large Load Diff

6359
templates/index.html Normal file

File diff suppressed because it is too large Load Diff

458
templates/login.html Normal file
View File

@@ -0,0 +1,458 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - 局域网聊天室</title>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v6.4.0/css/all.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">
<style>
:root {
--primary-color: #667eea;
--secondary-color: #764ba2;
--background-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--success-color: #4ade80;
--error-color: #ef4444;
--text-dark: #1f2937;
--text-light: #6b7280;
--border-color: #e5e7eb;
--shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--background-gradient);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-container {
background: white;
border-radius: 24px;
box-shadow: var(--shadow);
padding: 48px;
width: 100%;
max-width: 480px;
position: relative;
overflow: hidden;
}
.login-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6px;
background: var(--background-gradient);
}
.login-header {
text-align: center;
margin-bottom: 40px;
}
.login-header h1 {
color: var(--text-dark);
font-size: 2rem;
font-weight: 700;
margin-bottom: 8px;
}
.login-header p {
color: var(--text-light);
font-size: 1rem;
line-height: 1.5;
}
.form-group {
margin-bottom: 24px;
}
label {
display: block;
color: var(--text-dark);
font-weight: 500;
margin-bottom: 8px;
font-size: 0.95rem;
}
.input-wrapper {
position: relative;
}
input[type="text"] {
width: 100%;
padding: 16px 20px 16px 48px;
border: 2px solid var(--border-color);
border-radius: 12px;
font-size: 1rem;
transition: all 0.3s ease;
background: #fafafa;
color: var(--text-dark);
}
input[type="text"]:focus {
outline: none;
border-color: var(--primary-color);
background: white;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.input-icon {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: var(--text-light);
font-size: 1.1rem;
}
.login-btn {
width: 100%;
padding: 16px;
background: var(--background-gradient);
color: white;
border: none;
border-radius: 12px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
}
.login-btn:active {
transform: translateY(0);
}
.login-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.loading-spinner {
display: none;
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
margin-right: 8px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.error-message {
background: #fef2f2;
border: 1px solid #fecaca;
color: var(--error-color);
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 24px;
font-size: 0.9rem;
display: none;
}
.back-link {
text-align: center;
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid var(--border-color);
}
.back-link a {
color: var(--primary-color);
text-decoration: none;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 8px;
transition: color 0.3s ease;
}
.back-link a:hover {
color: var(--secondary-color);
}
.features {
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid var(--border-color);
}
.feature-list {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-top: 16px;
}
.feature-item {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-light);
font-size: 0.9rem;
}
.feature-item i {
color: var(--success-color);
font-size: 1rem;
}
/* 移动端优化 */
@media (max-width: 480px) {
.login-container {
padding: 32px 24px;
border-radius: 16px;
}
.login-header h1 {
font-size: 1.75rem;
}
.feature-list {
grid-template-columns: 1fr;
}
}
/* 动画效果 */
.login-container {
animation: slideUp 0.6s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 输入框动画 */
.input-wrapper {
animation: fadeIn 0.8s ease-out 0.2s both;
}
.login-btn {
animation: fadeIn 0.8s ease-out 0.4s both;
}
.back-link {
animation: fadeIn 0.8s ease-out 0.6s both;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-header">
<h1><i class="bi bi-chat-dots-fill"></i> 局域网聊天室</h1>
<p>请输入您的用户名开始聊天</p>
</div>
<form id="loginForm">
<div class="error-message" id="errorMessage"></div>
<div class="form-group">
<label for="username">用户名</label>
<div class="input-wrapper">
<i class="bi bi-person-fill input-icon"></i>
<input
type="text"
id="username"
name="username"
placeholder="请输入用户名1-20个字符"
maxlength="20"
required
>
</div>
</div>
<button type="submit" class="login-btn" id="loginBtn">
<span class="loading-spinner" id="loadingSpinner"></span>
<span id="btnText">进入聊天室</span>
</button>
</form>
<div class="features">
<div class="feature-list">
<div class="feature-item">
<i class="bi bi-shield-check"></i>
<span>安全可靠</span>
</div>
<div class="feature-item">
<i class="bi bi-lightning-fill"></i>
<span>实时聊天</span>
</div>
<div class="feature-item">
<i class="bi bi-file-earmark-arrow-up"></i>
<span>文件传输</span>
</div>
<div class="feature-item">
<i class="bi bi-people-fill"></i>
<span>群聊私聊</span>
</div>
</div>
</div>
<div class="back-link">
<a href="/">
<i class="bi bi-arrow-left"></i>
返回文件传输
</a>
</div>
</div>
<script>
const loginForm = document.getElementById('loginForm');
const usernameInput = document.getElementById('username');
const loginBtn = document.getElementById('loginBtn');
const loadingSpinner = document.getElementById('loadingSpinner');
const btnText = document.getElementById('btnText');
const errorMessage = document.getElementById('errorMessage');
// 自动聚焦用户名输入框
usernameInput.focus();
// 表单提交处理
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = usernameInput.value.trim();
if (!username) {
showError('请输入用户名');
return;
}
if (username.length > 20) {
showError('用户名长度不能超过20个字符');
return;
}
// 开始登录
setLoading(true);
hideError();
try {
const formData = new FormData();
formData.append('username', username);
const response = await fetch('/login', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok) {
// 登录成功保存session并跳转
localStorage.setItem('session_id', data.session_id);
localStorage.setItem('username', data.username);
// 跳转到聊天页面
window.location.href = '/chat';
} else {
showError(data.detail || '登录失败');
}
} catch (error) {
console.error('登录错误:', error);
showError('网络错误,请稍后重试');
} finally {
setLoading(false);
}
});
// 回车键提交
usernameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
loginForm.dispatchEvent(new Event('submit'));
}
});
// 实时验证用户名长度
usernameInput.addEventListener('input', (e) => {
const value = e.target.value;
if (value.length > 20) {
showError('用户名长度不能超过20个字符');
} else {
hideError();
}
});
function setLoading(loading) {
loginBtn.disabled = loading;
loadingSpinner.style.display = loading ? 'inline-block' : 'none';
btnText.textContent = loading ? '登录中...' : '进入聊天室';
}
function showError(message) {
errorMessage.textContent = message;
errorMessage.style.display = 'block';
usernameInput.focus();
}
function hideError() {
errorMessage.style.display = 'none';
}
// 检查是否已经登录
window.addEventListener('load', async () => {
const sessionId = localStorage.getItem('session_id');
const username = localStorage.getItem('username');
if (sessionId && username) {
try {
const response = await fetch(`/check_session?session_id=${sessionId}`);
const data = await response.json();
if (data.valid) {
// 会话有效,直接跳转到聊天页面
window.location.href = '/chat';
return;
}
} catch (error) {
console.log('会话检查失败:', error);
}
// 清理无效的会话信息
localStorage.removeItem('session_id');
localStorage.removeItem('username');
}
});
</script>
</body>
</html>

125
test_chat.html Normal file
View File

@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html>
<head>
<title>聊天测试</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
#messages { border: 1px solid #ccc; height: 300px; overflow-y: auto; padding: 10px; margin-bottom: 10px; }
#messageInput { width: 300px; padding: 5px; }
button { padding: 5px 10px; margin: 5px; }
.status { color: green; }
.error { color: red; }
</style>
</head>
<body>
<h1>聊天测试</h1>
<div id="status">未连接</div>
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="输入消息...">
<button onclick="sendMessage()">发送</button>
<button onclick="connectWebSocket()">重新连接</button>
<script>
let ws = null;
let myIp = 'unknown';
async function getMyIp() {
try {
const response = await fetch('/get_ip');
const data = await response.json();
myIp = data.ip;
console.log('My IP:', myIp);
document.getElementById('status').innerHTML = `IP: ${myIp} - 准备连接...`;
} catch (error) {
console.error('获取IP失败:', error);
document.getElementById('status').innerHTML = '<span class="error">获取IP失败</span>';
}
}
function connectWebSocket() {
if (ws) {
ws.close();
}
ws = new WebSocket(`ws://${window.location.host}/ws`);
ws.onopen = function() {
console.log('WebSocket连接已建立');
document.getElementById('status').innerHTML = `<span class="status">已连接 - IP: ${myIp}</span>`;
};
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('收到消息:', data);
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('div');
messageElement.innerHTML = `
<strong>${data.ip}:</strong> ${data.message}
<small>(${new Date(data.timestamp).toLocaleTimeString()})</small>
`;
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
};
ws.onclose = function() {
console.log('WebSocket连接已关闭');
document.getElementById('status').innerHTML = '<span class="error">连接已断开</span>';
};
ws.onerror = function(error) {
console.error('WebSocket错误:', error);
document.getElementById('status').innerHTML = '<span class="error">连接错误</span>';
};
}
function sendMessage() {
const messageInput = document.getElementById('messageInput');
const message = messageInput.value.trim();
if (!message || !ws || ws.readyState !== WebSocket.OPEN) {
alert('请输入消息并确保连接正常');
return;
}
const messageData = {
message: message,
timestamp: new Date().getTime(),
type: 'text'
};
try {
ws.send(JSON.stringify(messageData));
messageInput.value = '';
// 显示自己的消息
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('div');
messageElement.innerHTML = `
<strong>我 (${myIp}):</strong> ${message}
<small>(${new Date().toLocaleTimeString()})</small>
`;
messageElement.style.color = 'blue';
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
} catch (error) {
console.error('发送消息失败:', error);
alert('发送消息失败');
}
}
// 回车键发送消息
document.getElementById('messageInput').addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
sendMessage();
}
});
// 页面加载时初始化
window.addEventListener('load', async function() {
await getMyIp();
connectWebSocket();
});
</script>
</body>
</html>