Update app.py
Browse files
app.py
CHANGED
|
@@ -30,7 +30,7 @@ app.secret_key = os.getenv('FLASK_SECRET_KEY', os.urandom(24))
|
|
| 30 |
APP_TOKEN = os.getenv('APP_TOKEN', 'admin123')
|
| 31 |
STORAGE_MODE = os.getenv('STORAGE_MODE', 'cloud').lower()
|
| 32 |
|
| 33 |
-
# 并发控制
|
| 34 |
MAX_WORKERS = int(os.getenv('MAX_WORKERS', 5))
|
| 35 |
|
| 36 |
# 图床配置
|
|
@@ -73,7 +73,10 @@ def get_file_list_from_disk():
|
|
| 73 |
mtime = 0
|
| 74 |
time_str = "Unknown"
|
| 75 |
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
files_data.append({
|
| 79 |
'filename': filename,
|
|
@@ -156,6 +159,7 @@ def save_to_local(image_data, original_url, content_type, folder_name):
|
|
| 156 |
unique_name = f"{uuid.uuid4().hex}{ext}"
|
| 157 |
path = os.path.join(save_dir, unique_name)
|
| 158 |
with open(path, 'wb') as f: f.write(image_data)
|
|
|
|
| 159 |
return f"{SITE_DOMAIN}/{LOCAL_IMAGE_FOLDER}/{safe_folder}/{unique_name}"
|
| 160 |
except Exception as e:
|
| 161 |
logger.error(f"Local Save Error: {e}")
|
|
@@ -165,14 +169,11 @@ def save_to_local(image_data, original_url, content_type, folder_name):
|
|
| 165 |
def process_single_image_task(url, filename_no_ext):
|
| 166 |
"""
|
| 167 |
下载并上传单个图片
|
| 168 |
-
返回: (原始URL, 新URL) 或 (原始URL, None)
|
| 169 |
"""
|
| 170 |
-
# 1. 下载
|
| 171 |
img_data, c_type = download_image(url)
|
| 172 |
if not img_data:
|
| 173 |
return url, None
|
| 174 |
|
| 175 |
-
# 2. 上传/保存
|
| 176 |
fname = url.split('/')[-1].split('?')[0] or "image.jpg"
|
| 177 |
new_url = None
|
| 178 |
|
|
@@ -189,52 +190,49 @@ def process_markdown_content(content, filename_no_ext):
|
|
| 189 |
"""
|
| 190 |
pattern = re.compile(r'!\[(.*?)\]\((.*?)\)')
|
| 191 |
|
| 192 |
-
|
| 193 |
-
matches = pattern.findall(content) # 返回 [(alt, url), (alt, url)...]
|
| 194 |
|
| 195 |
-
# 2. 筛选出需要处理的 URL (去重,且必须是 http 开头,且不是本站域名)
|
| 196 |
unique_urls = set()
|
| 197 |
for _, url in matches:
|
| 198 |
if url.startswith(('http://', 'https://')):
|
| 199 |
-
# 如果是本地模式,排除掉已经是本站的链接
|
| 200 |
if STORAGE_MODE == 'local' and SITE_DOMAIN in url:
|
| 201 |
continue
|
| 202 |
unique_urls.add(url)
|
| 203 |
|
| 204 |
logger.info(f"Found {len(matches)} images, {len(unique_urls)} need processing.")
|
| 205 |
|
| 206 |
-
|
| 207 |
-
url_map = {} # 旧URL -> 新URL
|
| 208 |
success_count = 0
|
| 209 |
failed_count = 0
|
| 210 |
|
| 211 |
if unique_urls:
|
| 212 |
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
|
| 213 |
-
# 提交任务
|
| 214 |
future_to_url = {
|
| 215 |
executor.submit(process_single_image_task, url, filename_no_ext): url
|
| 216 |
for url in unique_urls
|
| 217 |
}
|
| 218 |
|
| 219 |
-
# 获取结果
|
| 220 |
for future in as_completed(future_to_url):
|
| 221 |
old_url, new_url = future.result()
|
| 222 |
if new_url:
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
success_count += 1
|
| 225 |
else:
|
| 226 |
failed_count += 1
|
| 227 |
|
| 228 |
-
# 4. 执行替换 (使用 re.sub 和 映射表)
|
| 229 |
def replace_callback(match):
|
| 230 |
alt_text = match.group(1)
|
| 231 |
original_url = match.group(2)
|
| 232 |
|
| 233 |
-
# 如果在映射表中,说明处理成功,替换之
|
| 234 |
if original_url in url_map:
|
| 235 |
return f''
|
| 236 |
|
| 237 |
-
# 否则保持原样
|
| 238 |
return match.group(0)
|
| 239 |
|
| 240 |
new_content = pattern.sub(replace_callback, content)
|
|
@@ -248,7 +246,10 @@ def save_processed_md(content, original_filename):
|
|
| 248 |
with open(save_path, 'w', encoding='utf-8') as f:
|
| 249 |
f.write(content)
|
| 250 |
logger.info(f"Markdown file saved: {save_path}")
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
| 252 |
|
| 253 |
# ================= HTML 模板 (保持不变) =================
|
| 254 |
BASE_TEMPLATE = """
|
|
|
|
| 30 |
APP_TOKEN = os.getenv('APP_TOKEN', 'admin123')
|
| 31 |
STORAGE_MODE = os.getenv('STORAGE_MODE', 'cloud').lower()
|
| 32 |
|
| 33 |
+
# 并发控制
|
| 34 |
MAX_WORKERS = int(os.getenv('MAX_WORKERS', 5))
|
| 35 |
|
| 36 |
# 图床配置
|
|
|
|
| 73 |
mtime = 0
|
| 74 |
time_str = "Unknown"
|
| 75 |
|
| 76 |
+
# URL 编码处理:文件名中可能有空格,下载链接也需要处理
|
| 77 |
+
# 但这里的 filename 是路径参数,通常由浏览器自动编码,这里手动替换空格更保险
|
| 78 |
+
encoded_filename = filename.replace(' ', '%20')
|
| 79 |
+
url = f"{SITE_DOMAIN}/api/download/{encoded_filename}?t={int(mtime)}"
|
| 80 |
|
| 81 |
files_data.append({
|
| 82 |
'filename': filename,
|
|
|
|
| 159 |
unique_name = f"{uuid.uuid4().hex}{ext}"
|
| 160 |
path = os.path.join(save_dir, unique_name)
|
| 161 |
with open(path, 'wb') as f: f.write(image_data)
|
| 162 |
+
# 注意:这里返回的 URL 可能包含中文或空格
|
| 163 |
return f"{SITE_DOMAIN}/{LOCAL_IMAGE_FOLDER}/{safe_folder}/{unique_name}"
|
| 164 |
except Exception as e:
|
| 165 |
logger.error(f"Local Save Error: {e}")
|
|
|
|
| 169 |
def process_single_image_task(url, filename_no_ext):
|
| 170 |
"""
|
| 171 |
下载并上传单个图片
|
|
|
|
| 172 |
"""
|
|
|
|
| 173 |
img_data, c_type = download_image(url)
|
| 174 |
if not img_data:
|
| 175 |
return url, None
|
| 176 |
|
|
|
|
| 177 |
fname = url.split('/')[-1].split('?')[0] or "image.jpg"
|
| 178 |
new_url = None
|
| 179 |
|
|
|
|
| 190 |
"""
|
| 191 |
pattern = re.compile(r'!\[(.*?)\]\((.*?)\)')
|
| 192 |
|
| 193 |
+
matches = pattern.findall(content)
|
|
|
|
| 194 |
|
|
|
|
| 195 |
unique_urls = set()
|
| 196 |
for _, url in matches:
|
| 197 |
if url.startswith(('http://', 'https://')):
|
|
|
|
| 198 |
if STORAGE_MODE == 'local' and SITE_DOMAIN in url:
|
| 199 |
continue
|
| 200 |
unique_urls.add(url)
|
| 201 |
|
| 202 |
logger.info(f"Found {len(matches)} images, {len(unique_urls)} need processing.")
|
| 203 |
|
| 204 |
+
url_map = {}
|
|
|
|
| 205 |
success_count = 0
|
| 206 |
failed_count = 0
|
| 207 |
|
| 208 |
if unique_urls:
|
| 209 |
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
|
|
|
|
| 210 |
future_to_url = {
|
| 211 |
executor.submit(process_single_image_task, url, filename_no_ext): url
|
| 212 |
for url in unique_urls
|
| 213 |
}
|
| 214 |
|
|
|
|
| 215 |
for future in as_completed(future_to_url):
|
| 216 |
old_url, new_url = future.result()
|
| 217 |
if new_url:
|
| 218 |
+
# ====================================================
|
| 219 |
+
# 【核心修改】在这里对 URL 进行编码处理
|
| 220 |
+
# 将空格替换为 %20,保证 Markdown 解析正常
|
| 221 |
+
# ====================================================
|
| 222 |
+
encoded_new_url = new_url.replace(' ', '%20')
|
| 223 |
+
|
| 224 |
+
url_map[old_url] = encoded_new_url
|
| 225 |
success_count += 1
|
| 226 |
else:
|
| 227 |
failed_count += 1
|
| 228 |
|
|
|
|
| 229 |
def replace_callback(match):
|
| 230 |
alt_text = match.group(1)
|
| 231 |
original_url = match.group(2)
|
| 232 |
|
|
|
|
| 233 |
if original_url in url_map:
|
| 234 |
return f''
|
| 235 |
|
|
|
|
| 236 |
return match.group(0)
|
| 237 |
|
| 238 |
new_content = pattern.sub(replace_callback, content)
|
|
|
|
| 246 |
with open(save_path, 'w', encoding='utf-8') as f:
|
| 247 |
f.write(content)
|
| 248 |
logger.info(f"Markdown file saved: {save_path}")
|
| 249 |
+
|
| 250 |
+
# 返回下载链接时,文件名也进行编码,防止下载链接断裂
|
| 251 |
+
encoded_filename = safe_filename.replace(' ', '%20')
|
| 252 |
+
return f"{SITE_DOMAIN}/api/download/{encoded_filename}"
|
| 253 |
|
| 254 |
# ================= HTML 模板 (保持不变) =================
|
| 255 |
BASE_TEMPLATE = """
|