---
id: FEAT-025
title: user-ban
domain: moderation
status: done
since_version: 0.5.2
---

# FEAT-025 — 封号 / 冻结 / 解封

## User Story

As a **admin**, I want to **封锁诈骗 / 骚扰用户**, so that **他们不能继续骚扰其他用户**。

## Acceptance Criteria

- [x] `xq admin user ban <id>` 硬封（banned：清 sessions + 禁发信）
- [x] `xq admin user ban <id> --freeze` 软封（frozen：冷静期，能登录不能发信）
- [x] `xq admin user unban <id>` 解封（active）
- [x] `xq admin user list --status banned/frozen/active`
- [x] `xq admin user info <id>` 详情（status + reason + updated_at）
- [x] banned 用户现有 session 立即失效（`sessions` 表该 user_id 所有行清空）
- [x] banned / frozen 用户 `mail.send` 抛 `user_banned` / `user_frozen` 错
- [x] 只 admin（env `XIANGQIN_ADMIN_USER_IDS` 白名单）可调
- [x] status_reason 留档（审计用）

## Tests

- `test/test_admin.py::test_ban_user_updates_status`
- `test/test_admin.py::test_ban_user_kills_existing_sessions`
- `test/test_admin.py::test_unban_restores_active`
- `test/test_admin.py::test_freeze_user`
- `test/test_admin.py::test_ban_user_not_found`
- `test/test_admin.py::test_list_users_by_status`
- `test/test_admin.py::test_banned_user_cannot_send` / `test_frozen_user_cannot_send`
- `test/test_admin.py::test_is_admin_from_env` / `test_require_admin_raises_403`

## CLI / API

- CLI: `xq admin user {info,ban,unban,list}`
- API: `POST/GET /admin/user/{id}` + `/ban` + `/unban`，`GET /admin/users?status=X`
- 核心：`src/xiangqin/admin.py::{ban,unban,freeze}_user` / `assert_user_active`

## 依赖 / 被依赖

- 依赖：`FEAT-001/002` session + env `XIANGQIN_ADMIN_USER_IDS`
- 被依赖：FEAT-029 `confirm --ban-target` / FEAT-014 send 前置拦截

## 风险

- **banned 不可逆 session 清**：用户如果手里还有 cli session 本地 token，banned 后下次调 API 立即 401
- **status_reason 不对外暴露**：普通用户 profile.show 看不到
- **未来加 audit_log**：当前只留 status_updated_at，没记"谁封的"（admin_id 传入了但没入 db）—— 下阶段补 admin_actions 表
