后端-Session-过期状态阻止重新登录
问题现象
- 页面
https://news.queding.hk/#/public-account-auth中"退出"按钮被禁用(灰色不可点击) - 爬取日志显示所有 51 个公众号"登录失效",公众号新文章数为 0
- 无法重新扫码登录,点击"扫码登录"后直接返回 success 状态,不弹出二维码
- 当日采集报告:
docs/day/2026-6-20-day.md,122 分钟白白等待全部返回"登录失效"
核心问题
modules/wechat/wechat_auth.py 中的 count_active_slots() 和 get_all_active_sessions() 只检查了内存状态(status == "success" 且有 token),没有验证数据库中 session 的 expires_at 是否已过期。
死锁路径:
1. 微信 session 自然过期(Cookie/Token 在公众号平台侧失效)
2. 内存状态 _login_states[slot] 仍为 success,永不被主动清除
3. count_active_slots() 返回 1 → 前端 Auth.vue:257 判定 totalActive <= 1 → 禁用"退出"按钮
4. trigger_qr_login() 在 wechat_auth.py:80 检测到内存 success → 直接返回,不生成二维码
5. 结果:不能退出(保护逻辑),也不能重新登录(误判已登录)
涉及文件:modules/wechat/wechat_auth.py(3 个函数)
解决方法
永久修复(已提交 c67accc)
修改 modules/wechat/wechat_auth.py 三处,将活跃状态判定源从内存改为数据库:
# 服务器部署
ssh news "cd /www/news && git pull origin main && systemctl restart news-backend"
# 若 git pull 不可用,直接 SCP 文件:
scp modules/wechat/wechat_auth.py news:/www/news/modules/wechat/wechat_auth.py
ssh news "systemctl restart news-backend"
改动 1 — count_active_slots()(line 489):改为查 DB,过滤 expires_at > now()
def count_active_slots() -> int:
db = SyncSessionLocal()
try:
return db.query(AuthSession).filter(
AuthSession.status == 1,
AuthSession.expires_at > datetime.now(),
).count()
finally:
db.close()
改动 2 — get_all_active_sessions()(line 376):交叉验证 DB,logged_in 基于 DB 而非内存
改动 3 — trigger_qr_login()(line 80):内存状态为 success 时先查 DB 确认有效;若已过期则重置内存状态后继续生成二维码
紧急手动绕过(无需部署代码)
直接在服务器数据库中将过期 session 标记为无效:
ssh news "cd /www/news && venv/bin/python -c \"
from src.core.database import SyncSessionLocal
from src.core.models import AuthSession
from datetime import datetime
db = SyncSessionLocal()
db.query(AuthSession).filter(
AuthSession.status == 1,
AuthSession.expires_at < datetime.now()
).update({'status': 0}, synchronize_session=False)
db.commit()
db.close()
print('Done')
\""
下次防错措施
快速定位
# 检查当前 session 状态(内存 vs DB 一致性)
ssh news "cd /www/news && venv/bin/python -c \"
from src.core.database import SyncSessionLocal
from src.core.models import AuthSession
from datetime import datetime
db = SyncSessionLocal()
try:
sessions = db.query(AuthSession).order_by(AuthSession.slot_number).all()
for s in sessions:
expired = s.expires_at and s.expires_at < datetime.now()
print(f'slot={s.slot_number} status={s.status} expires={s.expires_at} expired={expired}')
finally:
db.close()
\""
# 查看 API 返回的 totalActive
curl -s -H 'Authorization: Bearer <token>' https://news.queding.hk/api/wechat/auth/slots | python -m json.tool
流程改进
- 看门狗脚本:添加 crontab 定期检查 session 过期情况,过期时自动标记 DB 为失效
- 日志告警:当日 51/51 公众号全部"登录失效"是异常信号,应触发告警而非静默继续
# 建议的 crontab 检查
*/30 * * * * cd /www/news && venv/bin/python -c "
from datetime import datetime
from core.database import SyncSessionLocal
from core.models import AuthSession
db = SyncSessionLocal()
expired = db.query(AuthSession).filter(
AuthSession.status == 1,
AuthSession.expires_at < datetime.now()
).count()
if expired:
print(f'WARN: {expired} expired sessions still active')
db.close()
"
设计原则
- DB 为真相源:任何判断"当前是否登录"的逻辑都必须以 DB
expires_at字段为准,内存状态仅作缓存加速用 - 在
restore_session_from_db()中已正确过滤expires_at > now,但其余消费端(count_active_slots、get_all_active_sessions、trigger_qr_login)此前未做此检查