This commit is contained in:
mzl
2026-01-12 15:21:52 +08:00
parent e64adfe628
commit 7c933f4a82
4 changed files with 286 additions and 36 deletions
+43 -6
View File
@@ -74,6 +74,8 @@ async def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request}) return templates.TemplateResponse("index.html", {"request": request})
@app.get("/api/status", response_model=StatusResponse) @app.get("/api/status", response_model=StatusResponse)
async def get_status(): async def get_status():
"""获取当前状态""" """获取当前状态"""
@@ -395,6 +397,7 @@ async def export_users_to_txt():
class LoginStatusResponse(BaseModel): class LoginStatusResponse(BaseModel):
logged_in: bool logged_in: bool
login_time: Optional[str] = None login_time: Optional[str] = None
expire_time: Optional[str] = None
@app.get("/api/login/status", response_model=LoginStatusResponse) @app.get("/api/login/status", response_model=LoginStatusResponse)
@@ -409,9 +412,20 @@ async def get_login_status():
with open(auth_file, 'r', encoding='utf-8') as f: with open(auth_file, 'r', encoding='utf-8') as f:
auth_data = json.load(f) auth_data = json.load(f)
# 检查是否过期
login_time = auth_data.get("login_time")
expire_time = auth_data.get("expire_time")
current_time = datetime.now().isoformat()
if expire_time and current_time > expire_time:
# 登录已过期,清除状态
os.remove(auth_file)
return LoginStatusResponse(logged_in=False)
return LoginStatusResponse( return LoginStatusResponse(
logged_in=auth_data.get("logged_in", False), logged_in=auth_data.get("logged_in", False),
login_time=auth_data.get("login_time") login_time=login_time,
expire_time=expire_time
) )
except Exception: except Exception:
return LoginStatusResponse(logged_in=False) return LoginStatusResponse(logged_in=False)
@@ -419,12 +433,35 @@ async def get_login_status():
@app.post("/api/login/qrcode") @app.post("/api/login/qrcode")
async def generate_qrcode(): async def generate_qrcode():
"""生成登录二维码""" """模拟登录成功"""
# 这里应该调用实际的小红书登录API import uuid
# 暂时返回模拟数据 from datetime import datetime, timedelta
# 生成登录状态(模拟登录成功)
login_id = str(uuid.uuid4())
current_time = datetime.now()
expire_time = current_time + timedelta(hours=24) # 24小时后过期
# 保存登录状态到文件
auth_file = os.path.join(config.DATA_DIR, "auth_state.json")
auth_data = {
"logged_in": True,
"login_time": current_time.isoformat(),
"expire_time": expire_time.isoformat(),
"login_id": login_id,
"platform": "xiaohongshu"
}
with open(auth_file, 'w', encoding='utf-8') as f:
json.dump(auth_data, f, ensure_ascii=False, indent=2)
# 返回登录信息
return { return {
"qr_url": "https://example.com/login/qrcode", "login_id": login_id,
"qr_id": "123456" "login_time": current_time.isoformat(),
"expire_time": expire_time.isoformat(),
"message": "登录成功(模拟)",
"expire_in_hours": 24
} }
+37 -25
View File
@@ -144,6 +144,9 @@ function init() {
// 加载主题 // 加载主题
loadTheme(); loadTheme();
// 加载登录状态
loadAccountStatus();
// 加载初始数据 // 加载初始数据
loadInitialData(); loadInitialData();
@@ -692,42 +695,36 @@ function closeAccountModal() {
// 打开登录二维码模态框 // 打开登录二维码模态框
async function openLoginQrModal() { async function openLoginQrModal() {
elements.loginQrModal.classList.add('show'); elements.loginQrModal.classList.add('show');
elements.qrLoginStatus.textContent = '正在生成二维码...'; elements.qrLoginStatus.textContent = '正在模拟登录...';
try { try {
const response = await fetch('/api/login/qrcode'); const response = await fetch('/api/login/qrcode');
const data = await response.json(); const data = await response.json();
if (response.ok) { if (response.ok) {
// 显示二维码 // 显示登录成功信息
if (data.qr_url) {
// 使用简单的二维码显示(实际项目中可以使用QRCode.js等库)
elements.qrcodeContainer.innerHTML = ` elements.qrcodeContainer.innerHTML = `
<div style="text-align: center;"> <div style="text-align: center; padding: 40px;">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(data.qr_url)}" alt="登录二维码" style="max-width: 100%;"> <div style="font-size: 48px; margin-bottom: 20px;">✅</div>
<h3 style="color: var(--success-color); margin-bottom: 10px;">${data.message}</h3>
<p style="color: var(--text-secondary);">登录状态已保存到本地</p>
${data.expire_in_hours ? `<p style="color: var(--text-muted); font-size: 14px;">有效期:${data.expire_in_hours}小时</p>` : ''}
</div> </div>
`; `;
elements.qrLoginStatus.textContent = '请扫码登录'; elements.qrLoginStatus.textContent = '登录成功!';
// 开始检查登录状态 // 开始检查登录状态
checkLoginStatus(); checkLoginStatus();
// 设置二维码过期倒计时 // 3秒后自动关闭
let countdown = 120; setTimeout(() => {
const countdownInterval = setInterval(() => { closeLoginQrModal();
countdown--; closeAccountModal();
elements.qrExpireTime.textContent = `${countdown}秒`; showToast('登录成功', 'success');
}, 3000);
if (countdown <= 0) {
clearInterval(countdownInterval);
elements.qrLoginStatus.textContent = '二维码已过期,请重新生成';
elements.qrcodeContainer.innerHTML = '<p style="color: var(--error-color);">二维码已过期</p>';
}
}, 1000);
}
} else { } else {
elements.qrcodeContainer.innerHTML = '<p style="color: var(--error-color);">生成二维码失败</p>'; elements.qrcodeContainer.innerHTML = '<p style="color: var(--error-color);">登录失败</p>';
elements.qrLoginStatus.textContent = '生成失败'; elements.qrLoginStatus.textContent = '登录失败';
} }
} catch (error) { } catch (error) {
elements.qrcodeContainer.innerHTML = '<p style="color: var(--error-color);">网络错误</p>'; elements.qrcodeContainer.innerHTML = '<p style="color: var(--error-color);">网络错误</p>';
@@ -751,7 +748,7 @@ async function checkLoginStatus() {
setTimeout(() => { setTimeout(() => {
closeLoginQrModal(); closeLoginQrModal();
closeAccountModal(); closeAccountModal();
updateLoginStatus(true, data.login_time); updateLoginStatus(true, data.login_time, data.expire_time);
showToast('登录成功', 'success'); showToast('登录成功', 'success');
}, 1000); }, 1000);
} else { } else {
@@ -783,7 +780,7 @@ async function logout() {
} }
// 更新登录状态显示 // 更新登录状态显示
function updateLoginStatus(isLoggedIn, loginTime = null) { function updateLoginStatus(isLoggedIn, loginTime = null, expireTime = null) {
const indicator = elements.loginStatusIndicator; const indicator = elements.loginStatusIndicator;
const statusText = elements.loginStatusText; const statusText = elements.loginStatusText;
const modalStatus = elements.modalLoginStatus; const modalStatus = elements.modalLoginStatus;
@@ -797,6 +794,21 @@ function updateLoginStatus(isLoggedIn, loginTime = null) {
if (loginTime) { if (loginTime) {
elements.loginTime.textContent = new Date(loginTime).toLocaleString('zh-CN'); elements.loginTime.textContent = new Date(loginTime).toLocaleString('zh-CN');
} }
// 如果有到期时间,显示剩余时间
if (expireTime) {
const expireDate = new Date(expireTime);
const now = new Date();
const timeLeft = expireDate - now;
if (timeLeft > 0) {
const hours = Math.floor(timeLeft / (1000 * 60 * 60));
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
elements.loginTime.textContent = new Date(loginTime).toLocaleString('zh-CN');
elements.loginTime.innerHTML += `<br><span style="color: var(--warning-color); font-size: 12px;">(${hours}小时${minutes}分钟后过期)</span>`;
}
}
} else { } else {
indicator.className = 'status-indicator offline'; indicator.className = 'status-indicator offline';
statusText.textContent = '未登录'; statusText.textContent = '未登录';
@@ -811,7 +823,7 @@ async function loadAccountStatus() {
try { try {
const response = await fetch('/api/login/status'); const response = await fetch('/api/login/status');
const data = await response.json(); const data = await response.json();
updateLoginStatus(data.logged_in, data.login_time); updateLoginStatus(data.logged_in, data.login_time, data.expire_time);
} catch (error) { } catch (error) {
updateLoginStatus(false); updateLoginStatus(false);
} }
+157
View File
@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录测试</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.btn {
background: #10b981;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 10px;
}
.btn:hover {
background: #059669;
}
.qr-container {
margin: 20px 0;
text-align: center;
}
.status {
padding: 10px;
margin: 10px 0;
border-radius: 5px;
}
.status.success {
background: #d1fae5;
color: #065f46;
}
.status.error {
background: #fee2e2;
color: #991b1b;
}
</style>
</head>
<body>
<div class="container">
<h2>小红书登录测试</h2>
<button class="btn" onclick="generateQR()">生成二维码</button>
<button class="btn" onclick="checkStatus()">检查状态</button>
<button class="btn" onclick="logout()">退出登录</button>
<div id="status" class="status" style="display: none;"></div>
<div id="qrContainer" class="qr-container" style="display: none;">
<p>请使用小红书App扫描二维码登录</p>
<div id="qrcode"></div>
<div id="countdown"></div>
</div>
</div>
<script>
let countdownInterval = null;
async function generateQR() {
try {
const response = await fetch('/api/login/qrcode');
const data = await response.json();
if (response.ok) {
showQR(data.qr_url);
startCountdown();
logStatus('二维码生成成功', 'success');
} else {
logStatus('生成失败: ' + data.detail, 'error');
}
} catch (error) {
logStatus('网络错误: ' + error.message, 'error');
}
}
function showQR(qrUrl) {
const container = document.getElementById('qrcode');
container.innerHTML = `
<div style="background: white; padding: 20px; border-radius: 8px; display: inline-block;">
<img src="https://quickchart.io/qr?size=200x200&text=${encodeURIComponent(qrUrl)}" alt="登录二维码" style="max-width: 200px;">
</div>
`;
document.getElementById('qrContainer').style.display = 'block';
}
function startCountdown() {
let seconds = 120;
const countdownEl = document.getElementById('countdown');
countdownInterval = setInterval(() => {
countdownEl.textContent = `二维码将在 ${seconds} 秒后过期`;
seconds--;
if (seconds < 0) {
clearInterval(countdownInterval);
countdownEl.textContent = '二维码已过期';
document.getElementById('qrContainer').style.display = 'none';
}
}, 1000);
}
async function checkStatus() {
try {
const response = await fetch('/api/login/status');
const data = await response.json();
if (data.logged_in) {
logStatus(`已登录 - 登录时间: ${new Date(data.login_time).toLocaleString()}`, 'success');
} else {
logStatus('未登录', 'error');
}
} catch (error) {
logStatus('检查失败: ' + error.message, 'error');
}
}
async function logout() {
try {
const response = await fetch('/api/logout', { method: 'POST' });
const data = await response.json();
if (response.ok) {
logStatus('已退出登录', 'success');
document.getElementById('qrContainer').style.display = 'none';
clearInterval(countdownInterval);
} else {
logStatus('退出失败: ' + data.detail, 'error');
}
} catch (error) {
logStatus('退出失败: ' + error.message, 'error');
}
}
function logStatus(message, type) {
const statusEl = document.getElementById('status');
statusEl.textContent = message;
statusEl.className = `status ${type}`;
statusEl.style.display = 'block';
}
// 页面加载时检查登录状态
window.onload = checkStatus;
</script>
</body>
</html>
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
# VNC 设置脚本
# 安装 VNC 服务器和相关工具
apt-get update
apt-get install -y \
x11vnc \
xvfb \
firefox \
python3-tk \
python3-pip
# 安装 Python 依赖
pip3 install pyvirtualdisplay selenium webdriver-manager
# 创建 VNC 密码
echo "vncpassword" | x11vnc -storepasswd /etc/vncpasswd
# 创建启动脚本
cat > /start_vnc.sh << 'EOF'
#!/bin/bash
# 设置显示环境
export DISPLAY=:1
# 启动虚拟显示器
Xvfb :1 -screen 0 1920x1080x16 &
XVFB_PID=$!
# 启动 VNC 服务器
x11vnc -display :1 -passwd /etc/vncpasswd -forever -shared -rfbport 5900 &
VNC_PID=$!
# 等待 VNC 启动
sleep 3
echo "VNC 服务器已启动"
echo "VNC 地址: localhost:5900"
echo "VNC 密码: vncpassword"
# 保持脚本运行
wait $XVFB_PID $VNC_PID
EOF
chmod +x /start_vnc.sh