跳转至

后端-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"

改动 1count_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()

改动 2get_all_active_sessions()(line 376):交叉验证 DB,logged_in 基于 DB 而非内存

改动 3trigger_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_slotsget_all_active_sessionstrigger_qr_login)此前未做此检查