支持账号按筛选全量批量操作
This commit is contained in:
+137
-29
@@ -5,8 +5,8 @@ from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session, defer, joinedload
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import User, Account, AuditLog, LoginTask
|
||||
from ..schemas import AccountImport, AccountAssign, AccountTag, AccountOut, BatchAssign
|
||||
from ..models import User, Account, AuditLog, LoginTask, DouyuTask
|
||||
from ..schemas import AccountBulkSelection, AccountBulkTag, AccountImport, AccountAssign, AccountTag, AccountOut, BatchAssign
|
||||
from ..deps import get_current_user, require_permission
|
||||
from ..permissions import user_has_permission
|
||||
from ..services.account_service import (
|
||||
@@ -16,6 +16,82 @@ from ..services.account_service import (
|
||||
router = APIRouter(prefix="/api/accounts", tags=["账号管理"])
|
||||
|
||||
|
||||
def _visible_accounts_query(db: Session, current: User):
|
||||
"""返回当前用户可见的斗鱼账号查询。"""
|
||||
query = db.query(Account)
|
||||
if user_has_permission(current, "account:view_all"):
|
||||
return query
|
||||
if user_has_permission(current, "account:view_assigned"):
|
||||
return query.filter(Account.assigned_to == current.id)
|
||||
raise HTTPException(status_code=403, detail="无权查看账号")
|
||||
|
||||
|
||||
def _filter_accounts_query(
|
||||
db: Session,
|
||||
query,
|
||||
current: User,
|
||||
*,
|
||||
assigned_only: bool = False,
|
||||
tag: str | None = None,
|
||||
has_cookie: bool = False,
|
||||
search: str = "",
|
||||
):
|
||||
"""复用列表筛选条件,供分页列表和全量批量操作保持一致。"""
|
||||
if has_cookie:
|
||||
query = query.filter(Account.id.in_(cookie_account_ids_query(db)))
|
||||
if assigned_only and user_has_permission(current, "account:view_all"):
|
||||
query = query.filter(Account.assigned_to.isnot(None))
|
||||
if tag:
|
||||
query = query.filter(Account.tag == tag)
|
||||
search_text = (search or "").strip()
|
||||
if search_text:
|
||||
pattern = f"%{search_text}%"
|
||||
query = query.filter(or_(
|
||||
Account.username.ilike(pattern),
|
||||
Account.tag.ilike(pattern),
|
||||
Account.remark.ilike(pattern),
|
||||
))
|
||||
return query
|
||||
|
||||
|
||||
def _selected_account_ids(db: Session, current: User, req: AccountBulkSelection) -> list[int]:
|
||||
"""解析批量操作目标:当前筛选全部或显式选择的 ID。"""
|
||||
if req.all_matching:
|
||||
rows = (
|
||||
_filter_accounts_query(
|
||||
db,
|
||||
_visible_accounts_query(db, current),
|
||||
current,
|
||||
assigned_only=req.assigned_only,
|
||||
tag=req.tag,
|
||||
has_cookie=req.has_cookie,
|
||||
search=req.search,
|
||||
)
|
||||
.with_entities(Account.id)
|
||||
.order_by(None)
|
||||
.all()
|
||||
)
|
||||
return [account_id for account_id, in rows]
|
||||
|
||||
seen = set()
|
||||
ids = []
|
||||
for account_id in req.account_ids:
|
||||
if account_id not in seen:
|
||||
seen.add(account_id)
|
||||
ids.append(account_id)
|
||||
if not ids:
|
||||
return []
|
||||
rows = (
|
||||
_visible_accounts_query(db, current)
|
||||
.filter(Account.id.in_(ids))
|
||||
.with_entities(Account.id)
|
||||
.order_by(None)
|
||||
.all()
|
||||
)
|
||||
allowed = {account_id for account_id, in rows}
|
||||
return [account_id for account_id in ids if account_id in allowed]
|
||||
|
||||
|
||||
@router.get("")
|
||||
def list_accounts(
|
||||
assigned_only: bool = Query(False),
|
||||
@@ -29,33 +105,15 @@ def list_accounts(
|
||||
current: User = Depends(get_current_user),
|
||||
):
|
||||
"""列表:按角色返回不同字段和范围。"""
|
||||
query = db.query(Account)
|
||||
|
||||
# 只展示已成功登录过的账号
|
||||
if has_cookie:
|
||||
query = query.filter(Account.id.in_(cookie_account_ids_query(db)))
|
||||
|
||||
# 权限控制:客服只能看分配给自己的
|
||||
if not user_has_permission(current, "account:view_all"):
|
||||
if user_has_permission(current, "account:view_assigned"):
|
||||
query = query.filter(Account.assigned_to == current.id)
|
||||
else:
|
||||
raise HTTPException(status_code=403, detail="无权查看账号")
|
||||
|
||||
if assigned_only and user_has_permission(current, "account:view_all"):
|
||||
query = query.filter(Account.assigned_to.isnot(None))
|
||||
|
||||
if tag:
|
||||
query = query.filter(Account.tag == tag)
|
||||
|
||||
search_text = (search or "").strip()
|
||||
if search_text:
|
||||
pattern = f"%{search_text}%"
|
||||
query = query.filter(or_(
|
||||
Account.username.ilike(pattern),
|
||||
Account.tag.ilike(pattern),
|
||||
Account.remark.ilike(pattern),
|
||||
))
|
||||
query = _filter_accounts_query(
|
||||
db,
|
||||
_visible_accounts_query(db, current),
|
||||
current,
|
||||
assigned_only=assigned_only,
|
||||
tag=tag,
|
||||
has_cookie=has_cookie,
|
||||
search=search,
|
||||
)
|
||||
|
||||
total = None
|
||||
if page is not None:
|
||||
@@ -284,6 +342,27 @@ def batch_tag(
|
||||
return {"message": f"已为 {count} 个账号设置标签", "success": True}
|
||||
|
||||
|
||||
@router.put("/batch-tag-selection")
|
||||
def batch_tag_selection(
|
||||
req: AccountBulkTag,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("account:import")),
|
||||
):
|
||||
"""按显式选择或当前筛选结果批量设置账号标签。"""
|
||||
ids = _selected_account_ids(db, current, req)
|
||||
if not ids:
|
||||
raise HTTPException(status_code=400, detail="请选择账号")
|
||||
tag = (req.tag_value or "").strip()
|
||||
count = (
|
||||
_visible_accounts_query(db, current)
|
||||
.filter(Account.id.in_(ids))
|
||||
.update({Account.tag: tag}, synchronize_session=False)
|
||||
)
|
||||
db.commit()
|
||||
scope = "当前筛选下" if req.all_matching else "选中的"
|
||||
return {"message": f"已为{scope} {count} 个账号设置标签", "success": True, "count": count}
|
||||
|
||||
|
||||
@router.get("/tags/list")
|
||||
def list_tags(
|
||||
db: Session = Depends(get_db),
|
||||
@@ -310,6 +389,7 @@ def batch_delete_accounts(
|
||||
|
||||
# 先删除关联的登录任务
|
||||
db.query(LoginTask).filter(LoginTask.account_id.in_(ids)).delete(synchronize_session=False)
|
||||
db.query(DouyuTask).filter(DouyuTask.account_id.in_(ids)).delete(synchronize_session=False)
|
||||
|
||||
# 删除账号
|
||||
deleted = db.query(Account).filter(Account.id.in_(ids)).delete(synchronize_session=False)
|
||||
@@ -322,6 +402,33 @@ def batch_delete_accounts(
|
||||
return {"message": f"已删除 {deleted} 个账号", "deleted": deleted, "success": True}
|
||||
|
||||
|
||||
@router.post("/batch-delete")
|
||||
def batch_delete_accounts_selection(
|
||||
req: AccountBulkSelection,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("account:delete")),
|
||||
):
|
||||
"""按显式选择或当前筛选结果批量删除账号及其关联任务。"""
|
||||
ids = _selected_account_ids(db, current, req)
|
||||
if not ids:
|
||||
raise HTTPException(status_code=400, detail="请选择账号")
|
||||
|
||||
db.query(LoginTask).filter(LoginTask.account_id.in_(ids)).delete(synchronize_session=False)
|
||||
db.query(DouyuTask).filter(DouyuTask.account_id.in_(ids)).delete(synchronize_session=False)
|
||||
deleted = (
|
||||
_visible_accounts_query(db, current)
|
||||
.filter(Account.id.in_(ids))
|
||||
.delete(synchronize_session=False)
|
||||
)
|
||||
db.add(AuditLog(
|
||||
user_id=current.id, username=current.username,
|
||||
action="account:delete",
|
||||
target=f"{'按筛选' if req.all_matching else '批量'}删除{deleted}个账号",
|
||||
))
|
||||
db.commit()
|
||||
return {"message": f"已删除 {deleted} 个账号", "deleted": deleted, "success": True}
|
||||
|
||||
|
||||
@router.delete("/{account_id}")
|
||||
def delete_account(
|
||||
account_id: int,
|
||||
@@ -334,6 +441,7 @@ def delete_account(
|
||||
|
||||
# 先删除关联的登录任务,避免外键约束失败
|
||||
db.query(LoginTask).filter(LoginTask.account_id == account_id).delete(synchronize_session=False)
|
||||
db.query(DouyuTask).filter(DouyuTask.account_id == account_id).delete(synchronize_session=False)
|
||||
|
||||
db.add(AuditLog(user_id=current.id, username=current.username,
|
||||
action="account:delete", target=acc.username))
|
||||
|
||||
Reference in New Issue
Block a user