auto backup: 2026-06-01 08:10:02
This commit is contained in:
parent
734068fef9
commit
3da7fc67ac
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,8 @@
|
|||||||
"""轻量 CORS 代理服务 - 转发推送请求到目标 API + 单组件重新生成"""
|
"""轻量 CORS 代理服务 - 转发推送请求到目标 API + 单组件重新生成"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import cgi
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
@ -13,6 +15,102 @@ from pathlib import Path
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
UPLOAD_DIR = os.path.expanduser("~/.openclaw/workspace-xiaoban/tmp/uploads")
|
||||||
|
|
||||||
|
UPLOAD_HTML = r"""<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>上传文件到大麦</title>
|
||||||
|
<style>
|
||||||
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;padding:20px}
|
||||||
|
.container{max-width:600px;margin:0 auto;text-align:center}
|
||||||
|
h1{font-size:24px;margin:16px 0 4px}
|
||||||
|
.sub{color:#94a3b8;margin-bottom:24px;font-size:14px}
|
||||||
|
.upload-box{background:#1e293b;border:2px dashed #475569;border-radius:16px;padding:40px 20px;margin-bottom:16px}
|
||||||
|
.upload-box p{font-size:15px;color:#94a3b8;margin-bottom:16px}
|
||||||
|
.btn{display:inline-block;background:#6366f1;color:#fff;border:none;padding:12px 32px;border-radius:8px;font-size:16px;cursor:pointer;transition:background .2s}
|
||||||
|
.btn:active{background:#4f46e5}
|
||||||
|
.btn-wrap{position:relative;overflow:hidden}
|
||||||
|
.btn-wrap input[type=file]{position:absolute;left:0;top:0;width:100%;height:100%;opacity:0;cursor:pointer}
|
||||||
|
#progress{display:none;margin:12px 0}
|
||||||
|
.bar-bg{background:#334155;border-radius:8px;height:8px;overflow:hidden}
|
||||||
|
.bar{background:linear-gradient(90deg,#6366f1,#a855f7);height:100%;width:0;transition:width .3s}
|
||||||
|
#status{font-size:13px;color:#94a3b8;margin-bottom:8px}
|
||||||
|
#result{margin:12px 0;font-size:14px;line-height:1.8}
|
||||||
|
.success{color:#4ade80}
|
||||||
|
.error{color:#f87171}
|
||||||
|
.recent{margin-top:24px;text-align:left;max-height:180px;overflow-y:auto}
|
||||||
|
.recent h3{font-size:14px;color:#64748b;margin-bottom:8px}
|
||||||
|
.recent li{font-size:12px;color:#94a3b8;padding:4px 0;border-bottom:1px solid #1e293b}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>📤 上传文件到大麦</h1>
|
||||||
|
<p class="sub">点击下方按钮选择文件,支持多选</p>
|
||||||
|
<div class="upload-box">
|
||||||
|
<p>📁 选择视频、压缩包等文件上传</p>
|
||||||
|
<div class="btn-wrap">
|
||||||
|
<span class="btn">选择文件</span>
|
||||||
|
<input type="file" id="file" multiple>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="progress">
|
||||||
|
<p id="status">上传中...</p>
|
||||||
|
<div class="bar-bg"><div class="bar" id="bar"></div></div>
|
||||||
|
</div>
|
||||||
|
<div id="result"></div>
|
||||||
|
<div class="recent" id="recent"><h3>📋 最近上传</h3><ul id="filelist"></ul></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('file').addEventListener('change', function(){
|
||||||
|
if(this.files.length) upload(this.files);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function upload(files){
|
||||||
|
var progress = document.getElementById('progress');
|
||||||
|
var bar = document.getElementById('bar');
|
||||||
|
var status = document.getElementById('status');
|
||||||
|
var result = document.getElementById('result');
|
||||||
|
progress.style.display='block'; bar.style.width='0'; result.innerHTML='';
|
||||||
|
|
||||||
|
var ok=0, fail=0;
|
||||||
|
for(var i=0;i<files.length;i++){
|
||||||
|
var f = files[i];
|
||||||
|
status.textContent = '正在上传: ' + f.name + ' (' + (f.size/1024/1024).toFixed(1) + 'MB)...';
|
||||||
|
bar.style.width = ((i/files.length)*100)+'%';
|
||||||
|
var fd = new FormData(); fd.append('file', f);
|
||||||
|
try{
|
||||||
|
var r = await fetch('/upload', {method:'POST', body:fd});
|
||||||
|
var t = await r.json();
|
||||||
|
if(r.ok){ ok++; } else { fail++; result.innerHTML+='<div class="error">❌ '+f.name+': '+(t.error||'失败')+'</div>'; }
|
||||||
|
}catch(e){ fail++; result.innerHTML+='<div class="error">❌ '+f.name+': 网络错误</div>'; }
|
||||||
|
}
|
||||||
|
bar.style.width='100%';
|
||||||
|
if(fail===0) result.innerHTML+='<div class="success">✅ 全部 '+ok+' 个文件上传成功!</div>';
|
||||||
|
else result.innerHTML+='<div class="success">✅ '+ok+' 个成功</div><div class="error">❌ '+fail+' 个失败</div>';
|
||||||
|
status.textContent='上传完成!';
|
||||||
|
loadFiles();
|
||||||
|
setTimeout(function(){ progress.style.display='none'; result.innerHTML=''; }, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadFiles(){
|
||||||
|
try{
|
||||||
|
var r = await fetch('/upload/files');
|
||||||
|
var files = await r.json();
|
||||||
|
document.getElementById('filelist').innerHTML = files.map(function(f){
|
||||||
|
return '<li>📄 '+f.name+' <span style="color:#64748b">'+(f.size/1024/1024).toFixed(1)+'MB · '+f.time+'</span></li>';
|
||||||
|
}).join('');
|
||||||
|
}catch(e){}
|
||||||
|
}
|
||||||
|
loadFiles();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
SCRIPTS_DIR = Path(__file__).resolve().parent
|
SCRIPTS_DIR = Path(__file__).resolve().parent
|
||||||
LOG_DIR = PROJECT_ROOT / 'logs'
|
LOG_DIR = PROJECT_ROOT / 'logs'
|
||||||
@ -131,6 +229,10 @@ class ProxyHandler(BaseHTTPRequestHandler):
|
|||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
if self.path == '/health':
|
if self.path == '/health':
|
||||||
self._send_json(200, {'status': 'ok'})
|
self._send_json(200, {'status': 'ok'})
|
||||||
|
elif self.path == '/upload':
|
||||||
|
self._serve_upload_page()
|
||||||
|
elif self.path == '/upload/files':
|
||||||
|
self._list_uploaded_files()
|
||||||
else:
|
else:
|
||||||
self.send_error(404)
|
self.send_error(404)
|
||||||
|
|
||||||
@ -139,6 +241,8 @@ class ProxyHandler(BaseHTTPRequestHandler):
|
|||||||
self._handle_push()
|
self._handle_push()
|
||||||
elif self.path == '/api/regenerate':
|
elif self.path == '/api/regenerate':
|
||||||
self._handle_regenerate()
|
self._handle_regenerate()
|
||||||
|
elif self.path == '/upload':
|
||||||
|
self._handle_upload()
|
||||||
else:
|
else:
|
||||||
self.send_error(404)
|
self.send_error(404)
|
||||||
|
|
||||||
@ -221,6 +325,61 @@ class ProxyHandler(BaseHTTPRequestHandler):
|
|||||||
self._send_json(code, result)
|
self._send_json(code, result)
|
||||||
logger.info(f'[{client_ip}] Regenerate done: status={code}')
|
logger.info(f'[{client_ip}] Regenerate done: status={code}')
|
||||||
|
|
||||||
|
def _serve_upload_page(self):
|
||||||
|
html = UPLOAD_HTML.encode('utf-8')
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||||
|
self.send_header('Content-Length', str(len(html)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(html)
|
||||||
|
|
||||||
|
def _list_uploaded_files(self):
|
||||||
|
import time as _time
|
||||||
|
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||||
|
files = []
|
||||||
|
for name in sorted(os.listdir(UPLOAD_DIR), reverse=True):
|
||||||
|
path = os.path.join(UPLOAD_DIR, name)
|
||||||
|
if os.path.isfile(path):
|
||||||
|
st = os.stat(path)
|
||||||
|
files.append({
|
||||||
|
'name': name,
|
||||||
|
'size': st.st_size,
|
||||||
|
'time': _time.strftime('%m-%d %H:%M', _time.localtime(st.st_mtime))
|
||||||
|
})
|
||||||
|
self._send_json(200, files[:20])
|
||||||
|
|
||||||
|
def _handle_upload(self):
|
||||||
|
content_type = self.headers.get('Content-Type', '')
|
||||||
|
if 'multipart/form-data' not in content_type:
|
||||||
|
self._send_json(400, {'error': '需要 multipart/form-data'})
|
||||||
|
return
|
||||||
|
|
||||||
|
form = cgi.FieldStorage(
|
||||||
|
fp=self.rfile,
|
||||||
|
headers=self.headers,
|
||||||
|
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': content_type}
|
||||||
|
)
|
||||||
|
|
||||||
|
file_item = form['file']
|
||||||
|
if file_item is None or not file_item.filename:
|
||||||
|
self._send_json(400, {'error': '未选择文件'})
|
||||||
|
return
|
||||||
|
|
||||||
|
safe_name = os.path.basename(file_item.filename)
|
||||||
|
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||||
|
path = os.path.join(UPLOAD_DIR, safe_name)
|
||||||
|
|
||||||
|
base, ext = os.path.splitext(safe_name)
|
||||||
|
counter = 1
|
||||||
|
while os.path.exists(path):
|
||||||
|
path = os.path.join(UPLOAD_DIR, f"{base}_{counter}{ext}")
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
with open(path, 'wb') as f:
|
||||||
|
f.write(file_item.file.read())
|
||||||
|
|
||||||
|
self._send_json(200, {'ok': True, 'filename': os.path.basename(path)})
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
logger.debug(f'{self.client_address[0]} - {format % args}')
|
logger.debug(f'{self.client_address[0]} - {format % args}')
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user