Files
OJ2/docker/clear-sessions.sh
T
xuyueandClaude Opus 5.5 793848b06f
Deploy / deploy (push) Canceled after 0s
refactor(登录): 登录态从 Cookie 换成 access token + refresh token
- access token 是 15 分钟的 JWT,走 Authorization: Bearer;refresh token 7 天,每次换都轮换
- 退出登录、改密码、禁用账号仍然立刻生效:每个请求核对 Redis 会话和库里新加的 user.token_version
- 在服务器后台改密码 / 禁用,机房那边登着的也会一起下线(原来两边 Redis 分开,管不到)
- WebSocket 握手借子协议带 token;JWT 密钥存在本站 Redis,部署不用加配置
- 去掉 COOKIE_SECURE;上线后所有人要重新登录一次

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:22:52 +08:00

94 lines
4.1 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# 运维动作:清掉所有会话,强制全员重新登录。
#
# 会话是 `auth-session:<sid>`(见 apps/api/src/auth/session.ts),删掉之后 access token
# 下一个请求就不认、refresh token 也换不出新的,不用等 token 自然过期。
# 顺带清掉 cookie 时代留下的 `session:*` / `user-sessions:*`(换成 token 之后没人读了,
# 不清的话也会在 7 天内自己过期)。
#
# 只想让某一个人下线不用跑这个:后台改密码 / 禁用账号就会把他所有设备踢下线。
#
# ## 两个站点都要跑
#
# 机房和服务器**共用一个数据库,但各有各的 Redis**(见 compose.school.yml 头部)。
# 会话存在各自的 Redis 里,只清一边等于只解决一半。
#
# ## 为什么不是 FLUSHALL
#
# 同一个 Redis 里还装着 BullMQ 的判题队列(`bull:*`)。FLUSHALL 会把还在队列里的提交
# 一起丢掉,那些 submission 会永远停在 PENDING,只能超管逐条重判 —— 而重判还会把
# user_profile 的反范式计数带偏(见 apps/api/src/scripts/recount.ts)。
# 这里只删下面列出来的几种会话键。
#
# ## 用法
#
# docker/clear-sessions.sh # 容器名默认 oj-redis
# CONTAINER=oj2-redis docker/clear-sessions.sh # 本机 dev
# YES=1 docker/clear-sessions.sh # 跳过确认
#
# `sh docker/clear-sessions.sh` 会用 dash 跑(Debian 的 /bin/sh 就是 dash),而下面
# 那行的 pipefail 是 bash 专有的,一上来就报 `Illegal option -o pipefail`。
# 这行必须在 set 之前,且只能用 dash 也认的语法。deploy.sh 里是同一道垫片。
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
set -euo pipefail
CONTAINER="${CONTAINER:-oj-redis}"
# redis 带口令时(服务器那台),redis-cli 靠 REDISCLI_AUTH 认证。没显式给就从
# docker/.env 里读;机房和本机 dev 的 redis 不带口令,读不到就留空,不影响。
if [ -z "${REDIS_PASSWORD:-}" ] && [ -f "$(dirname "$0")/.env" ]; then
REDIS_PASSWORD=$(grep -E '^REDIS_PASSWORD=' "$(dirname "$0")/.env" | tail -1 | cut -d= -f2- || true)
fi
# 口令为空时连 -e 都不能带:空的 REDISCLI_AUTH 会让 redis-cli 发一条 AUTH "",
# 在不带口令的 redis 上报一行 AUTH failed(实测,虽然随后命令照常执行)。
EXEC=(docker exec)
[ -z "${REDIS_PASSWORD:-}" ] || EXEC+=(-e REDISCLI_AUTH="$REDIS_PASSWORD")
EXEC+=("$CONTAINER" redis-cli)
cli() { "${EXEC[@]}" "$@"; }
say() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
die() { printf '\n\033[1;31m❌ %s\033[0m\n\n' "$*" >&2; exit 1; }
[ "$(cli ping 2>/dev/null)" = PONG ] \
|| die "连不上容器 $CONTAINER 里的 redis(用 CONTAINER=... 指定容器名;带口令的话检查 REDIS_PASSWORD)"
# 用 SCAN 而不是 KEYS:KEYS 会阻塞住整个 Redis,而这上面还挂着判题队列和所有人的
# 会话读写。xargs 分批是因为单条 DEL 传太多 key 会顶到命令行长度上限;每批 DEL 返回
# 删掉的个数,累加起来就是总数。
purge() {
cli --scan --pattern "$1" 2>/dev/null \
| xargs -r -n 400 "${EXEC[@]}" del \
| awk '{ sum += $1 } END { print sum + 0 }'
}
count() { cli --scan --pattern "$1" 2>/dev/null | wc -l; }
PATTERNS=('auth-session:*' 'auth-contest:*' 'session:*' 'user-sessions:*')
say "容器 $CONTAINER"
for pattern in "${PATTERNS[@]}"; do
printf ' %-16s %s\n' "$pattern" "$(count "$pattern")"
done
before_bull=$(count 'bull:*')
printf ' %-16s %s(不动)\n' 'bull:*' "$before_bull"
if [ "${YES:-}" != "1" ]; then
read -rp $'\n 删掉上面的会话、让所有人重新登录?[y/N] ' answer
[ "$answer" = "y" ] || [ "$answer" = "Y" ] || die "已取消,什么都没做"
fi
say "清理"
for pattern in "${PATTERNS[@]}"; do
ok "$(printf '%-16s' "$pattern") 删掉 $(purge "$pattern") 个"
done
after_bull=$(count 'bull:*')
[ "$after_bull" = "$before_bull" ] \
|| die "判题队列的键数变了($before_bull → $after_bull),这不该发生,请人工检查"
ok "bull:* 仍是 $after_bull 个,判题队列没被动过"
say "完成 —— 另一个站点的 Redis 也要跑一遍"