Skip to content

Commit d542bcb

Browse files
committed
添加模型下载功能并更新依赖说明,优化云端部署配置
1 parent ebbc0ac commit d542bcb

File tree

5 files changed

+24
-194
lines changed

5 files changed

+24
-194
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ venv/
33
logs/
44
.env.local
55
.env
6-
checkpoints
6+
checkpoints
7+
.vercel

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,12 @@ base_model:
115115
cd LOL-DeepWinPredictor
116116
```
117117
2. **安装依赖(建议虚拟环境)**
118-
- 先取消requirements.txt中的`torch`相关注释(该注释是为了避免云端部署依赖安装的错误)
118+
- 先在requirements.txt中加上如下依赖(该操作是为了避免云端部署依赖安装的错误)
119+
```txt
120+
torch==2.3.0+cu121
121+
torch_geometric==2.5.3
122+
```
123+
- 创建虚拟环境
119124
120125
```bash
121126
python -m venv venv

api/app.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import os
3+
import requests
34

45
import numpy as np
56
import torch
@@ -34,6 +35,20 @@
3435
rich_logger.error(f"文件不存在错误: {e}")
3536
hero_list, hero_win_rate, team_list = [], [], []
3637

38+
# 下载模型文件(如果不存在)
39+
MODEL_LOCAL_PATH = os.path.join(STATIC_DIR, 'saved_model', 'BILSTM_Att.pt')
40+
MODEL_REMOTE_URL = 'https://mori.teracloud.jp:443/v2/dav/BILSTM_Att.pt'
41+
if not os.path.exists(MODEL_LOCAL_PATH):
42+
os.makedirs(os.path.dirname(MODEL_LOCAL_PATH), exist_ok=True)
43+
rich_logger.info(f"模型文件不存在,正在从云端下载: {MODEL_REMOTE_URL}")
44+
r = requests.get(MODEL_REMOTE_URL, stream=True)
45+
r.raise_for_status()
46+
with open(MODEL_LOCAL_PATH, 'wb') as f:
47+
for chunk in r.iter_content(chunk_size=8192):
48+
if chunk:
49+
f.write(chunk)
50+
rich_logger.info("模型下载完成!")
51+
3752
# 加载LSTM模型(添加异常处理)
3853
try:
3954
model = BiLSTMModelWithAttention(input_size=32, hidden_size=1024, num_layers=2, output_size=1)
@@ -42,7 +57,7 @@
4257
:param MODEL_PATH: 模型文件路径
4358
:return: None
4459
"""
45-
model.load_state_dict(torch.load(env.MODEL_PATH, map_location=torch.device('cpu'), weights_only=True)) # 确保在CPU上加载,且只加载权重
60+
model.load_state_dict(torch.load(MODEL_LOCAL_PATH, map_location=torch.device('cpu'), weights_only=True)) # 确保在CPU上加载,且只加载权重
4661
model.eval()
4762
except Exception as e:
4863
rich_logger.error(f"模型加载错误: {e}")

requirements.txt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
aiohttp>=3.11.16
22
beautifulsoup4>=4.12.2
3-
boto3==1.37.37
4-
botocore==1.37.37
53
fake_useragent==1.5.1
64
Flask==3.0.3
75
flask_cors==5.0.1
@@ -11,13 +9,8 @@ orjson==3.10.16
119
pandas==2.2.3
1210
pymongo==4.6.1
1311
pymysql==1.1.1
14-
pyspark==3.5.1
1512
python-dotenv>=1.0.1
1613
Requests>=2.32.3
1714
rich>=14.0.0
18-
rocketmq==0.4.4
1915
scikit_learn==1.4.2
20-
# torch==2.3.0+cu121
21-
# torch_geometric==2.5.3
22-
tqdm==4.67.1
2316
urllib3>=2.3.0

tool_utils/file_utils.py

Lines changed: 0 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -5,197 +5,13 @@
55
# @Author :Zhangjinzhao
66
# @Software :PyCharm
77

8-
import mimetypes
9-
import os
10-
import subprocess
118
from pathlib import Path
129

13-
import boto3
14-
from botocore.exceptions import ClientError
15-
1610
from tool_utils.log_utils import RichLogger
1711

1812
rich_logger = RichLogger()
1913

2014

21-
class S3Utils:
22-
def __init__(self):
23-
s3endpoint = os.getenv('S3_ENDPOINT') # 请填入控制台 “Bucket 设置” 页面底部的 “Endpoint” 标签中的信息
24-
s3region = os.getenv('S3_REGION')
25-
s3accessKeyId = os.getenv('S3_ACCESS_KEY') # 请到控制台创建子账户,并为子账户创建相应 accessKey
26-
s3SecretKeyId = os.getenv('S3_SECRET_KEY') # !!切记,创建子账户时,需要手动为其分配具体权限!!
27-
self.bucket = os.getenv('S3_BUCKET') # 请填入控制台 “Bucket 列表” 页面的 “Bucket 名称”
28-
self.s3_client = boto3.client(
29-
's3',
30-
aws_access_key_id=s3accessKeyId,
31-
aws_secret_access_key=s3SecretKeyId,
32-
endpoint_url=s3endpoint,
33-
region_name=s3region
34-
)
35-
36-
@rich_logger
37-
def check_s3_file_exists(self, file_path):
38-
"""
39-
检查文件在 S3 上是否已存在
40-
:param file_path: 本地文件路径
41-
:return: 存在返回 True,不存在返回 False
42-
"""
43-
try:
44-
# 将路径替换为统一的单斜杠
45-
unified_path = file_path.replace("\\", "/")
46-
# 分割路径
47-
parts = unified_path.split("/")
48-
xovideos_indices = [i for i, part in enumerate(parts) if part == "XOVideos"]
49-
50-
videos_index = xovideos_indices[1] # 获取第二个 "XOVideos" 的索引
51-
s3_key = "/".join(parts[videos_index:])
52-
53-
# 检查文件是否存在
54-
try:
55-
self.s3_client.head_object(Bucket=self.bucket, Key=s3_key)
56-
rich_logger.info(f"文件已存在于 S3: {s3_key}")
57-
return True
58-
except ClientError as e:
59-
error_code = e.response['Error'].get('Code', 'Unknown')
60-
if error_code == '404':
61-
rich_logger.info(f"文件不存在于 S3: {s3_key}")
62-
return False
63-
else:
64-
rich_logger.error(f"检查 S3 文件时出错 ({error_code}): {e}")
65-
return False
66-
67-
except Exception as e:
68-
rich_logger.error(f"检查 S3 文件时发生未知错误: {e}")
69-
return False
70-
71-
@rich_logger
72-
def s4_upload_file(self, file_path, delete_on_success=True, delete_on_failure=False):
73-
74-
try:
75-
# 路径合规性检查
76-
if not os.path.isfile(file_path):
77-
rich_logger.error(f"文件不存在或不可读: {file_path}")
78-
return False
79-
80-
# 将路径替换为统一的单斜杠
81-
unified_path = file_path.replace("\\", "/")
82-
# 分割路径
83-
parts = unified_path.split("/")
84-
xovideos_indices = [i for i, part in enumerate(parts) if part == "XOVideos"]
85-
86-
videos_index = xovideos_indices[1] # 获取第二个 "XOVideos" 的索引
87-
s3_key = "/".join(parts[videos_index:])
88-
89-
# 检查文件是否存在
90-
try:
91-
self.s3_client.head_object(Bucket=self.bucket, Key=s3_key)
92-
rich_logger.info(f"S4文件已存在,跳过上传: {s3_key}")
93-
94-
if delete_on_success:
95-
try:
96-
os.remove(file_path)
97-
except OSError as e:
98-
rich_logger.error(f"删除本地文件失败: {e}")
99-
return True
100-
except ClientError as e:
101-
error_code = e.response['Error'].get('Code', 'Unknown')
102-
if error_code != '404':
103-
raise
104-
105-
# 动态检测 MIME 类型
106-
content_type, _ = mimetypes.guess_type(file_path)
107-
extra_args = {
108-
"ContentType": content_type or "application/octet-stream",
109-
"ContentDisposition": "inline"
110-
}
111-
112-
# 记录文件大小
113-
file_size = os.path.getsize(file_path)
114-
size_units = ['B', 'KB', 'MB', 'GB', 'TB']
115-
size_index = 0
116-
while file_size >= 1024 and size_index < len(size_units) - 1:
117-
file_size /= 1024.0
118-
size_index += 1
119-
size_str = f"{int(file_size)} {size_units[size_index]}" if size_index == 0 else f"{file_size:.2f} {size_units[size_index]}".rstrip('0').rstrip('.')
120-
rich_logger.info(f"S4开始上传: {s3_key},大小: {size_str}")
121-
122-
# 执行上传
123-
self.s3_client.upload_file(
124-
Filename=file_path,
125-
Bucket=self.bucket,
126-
Key=s3_key,
127-
ExtraArgs=extra_args
128-
)
129-
rich_logger.info(f"S4上传成功: {s3_key}")
130-
131-
# 上传成功后删除本地文件
132-
if delete_on_success:
133-
try:
134-
os.remove(file_path)
135-
except OSError as e:
136-
rich_logger.error(f"删除本地文件失败: {e}")
137-
return True
138-
139-
except ClientError as e:
140-
error_code = e.response['Error'].get('Code', 'Unknown')
141-
rich_logger.error(f"S4上传失败 ({error_code}): {e}")
142-
if delete_on_failure:
143-
try:
144-
os.remove(file_path)
145-
except OSError as err:
146-
rich_logger.error(f"删除本地文件失败: {err}")
147-
return False
148-
except Exception as e:
149-
rich_logger.exception(f"S4未知错误: {e}")
150-
if delete_on_failure:
151-
try:
152-
os.remove(file_path)
153-
except OSError as err:
154-
rich_logger.error(f"删除本地文件失败: {err}")
155-
return False
156-
157-
@staticmethod
158-
@rich_logger
159-
def ffmpeg_video_streaming(input_file):
160-
"""
161-
使用FFmpeg将视频转换为H.264格式,并进行流优化处理,使其能够流式传输。
162-
163-
参数:
164-
- input_file: 输入的视频文件路径
165-
- output_file: 输出的优化后的视频文件路径
166-
167-
返回:
168-
- 成功: 返回输出文件的路径
169-
- 失败: 返回 None
170-
"""
171-
output_file = input_file.replace('.mp4', 'h264.mp4')
172-
command = [
173-
'ffmpeg',
174-
'-i', input_file,
175-
'-c:v', 'libx264', # 使用H.264编解码器
176-
'-c:a', 'copy', # 保留原始音频
177-
'-movflags', 'faststart', # 使视频文件头放在文件开始处,优化流媒体播放
178-
output_file
179-
]
180-
181-
try:
182-
# 使用 subprocess.run 执行 FFmpeg 命令,并捕获 stdout 和 stderr 以便调试
183-
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
184-
185-
rich_logger.info(f"视频优化和H.264转换完成: {output_file}")
186-
os.remove(input_file) # 删除原视频,保留流式优化视频
187-
os.rename(output_file, input_file)
188-
return input_file
189-
except subprocess.CalledProcessError as e:
190-
# 捕获 FFmpeg 命令的返回码和输出
191-
rich_logger.exception(f"视频优化失败: {output_file}\n错误信息: {e.stderr.decode('utf-8')}")
192-
return None
193-
except Exception as e:
194-
# 捕获其他意外错误
195-
rich_logger.error(f"未知错误: {e}")
196-
return None
197-
198-
19915
class ProjectRootFinder:
20016
PROJECT_MARKERS = {
20117
"Python": ["setup.py", "requirements.txt", "pyproject.toml", "manage.py", "Pipfile", "app.py"],

0 commit comments

Comments
 (0)