This commit is contained in:
fengchuanhn@gmail.com
2026-05-21 16:24:51 +08:00
parent 91fb42bf91
commit 8d5f5f2608
4 changed files with 376 additions and 26 deletions

View File

@@ -4,7 +4,7 @@ from typing import Literal
from fastapi import APIRouter, Query
from app.dependencies import AdminUser, DbSession
from app.dependencies import AgentUser, DbSession
from app.schemas.admin_card_key import (
CardKeyBatchCreateResult,
CardKeyCreate,
@@ -12,14 +12,14 @@ from app.schemas.admin_card_key import (
CardKeyUpdate,
)
from app.schemas.common import ApiResponse, PaginatedData
from app.services import admin_card_key as card_key_service
from app.services import agent_card_key as card_key_service
router = APIRouter(prefix="/agent/card-keys", tags=["管理-卡密"])
@router.get("", response_model=ApiResponse[PaginatedData[CardKeyOut]])
async def list_card_keys(
_admin: AdminUser,
_agent: AgentUser,
db: DbSession,
page: int = Query(1, ge=1),
page_size: int = Query(
@@ -42,8 +42,7 @@ async def list_card_keys(
page_size=page_size,
status=filter_status,
username=username,
oem_id=oem_id,
agent_id=agent_id,
agent_id=_agent.id,
)
return ApiResponse(
ok=True,
@@ -60,7 +59,7 @@ async def list_card_keys(
@router.post("", response_model=ApiResponse[CardKeyBatchCreateResult])
async def create_card_keys(
body: CardKeyCreate,
_admin: AdminUser,
_admin: AgentUser,
db: DbSession,
) -> ApiResponse[CardKeyBatchCreateResult]:
try:
@@ -80,7 +79,7 @@ async def create_card_keys(
async def update_card_key(
card_id: int,
body: CardKeyUpdate,
_admin: AdminUser,
_admin: AgentUser,
db: DbSession,
) -> ApiResponse[CardKeyOut]:
try:
@@ -98,7 +97,7 @@ async def update_card_key(
@router.delete("/{card_id}", response_model=ApiResponse[None])
async def delete_card_key(
card_id: int,
_admin: AdminUser,
_admin: AgentUser,
db: DbSession,
) -> ApiResponse[None]:
try:

View File

@@ -2,37 +2,37 @@
from fastapi import APIRouter, Query
from app.dependencies import OemUser, DbSession
from app.dependencies import AgentUser, DbSession
from app.schemas.admin_user import AdminUserCreate, AdminUserOut, AdminUserUpdate
from app.schemas.common import ApiResponse, PaginatedData
from app.services import admin_user as oem_user_service
from app.services import agent_user as agent_user_service
router = APIRouter(prefix="/agent/users", tags=["管理-用户"])
@router.get("", response_model=ApiResponse[PaginatedData[AdminUserOut]])
async def list_users(
_oem: OemUser,
_agent: AgentUser,
db: DbSession,
page: int = Query(1, ge=1, description="页码,从 1 开始"),
page_size: int = Query(
oem_user_service.DEFAULT_PAGE_SIZE,
agent_user_service.DEFAULT_PAGE_SIZE,
ge=1,
le=oem_user_service.MAX_PAGE_SIZE,
le=agent_user_service.MAX_PAGE_SIZE,
description="每页条数",
),
username: str | None = Query(None, description="用户名模糊搜索"),
agent_id: int | None = Query(None, description="按所属代理用户 ID 筛选"),
role_id: int | None = Query(None, ge=0, le=3, description="按角色筛选"),
) -> ApiResponse[PaginatedData[AdminUserOut]]:
oem_id=_oem.id
users, total = await oem_user_service.list_users(
agent_id=_agent.id
users, total = await agent_user_service.list_users(
db,
page=page,
page_size=page_size,
username=username,
oem_id=oem_id,
agent_id=agent_id,
role_id=role_id,
)
return ApiResponse(
@@ -50,12 +50,12 @@ async def list_users(
@router.post("", response_model=ApiResponse[AdminUserOut])
async def create_user(
body: AdminUserCreate,
oem: OemUser,
agent: AgentUser,
db: DbSession,
) -> ApiResponse[AdminUserOut]:
try:
user = await oem_user_service.create_user(db, body,oem)
except oem_user_service.AdminUserError as exc:
user = await agent_user_service.create_user(db, body,agent)
except agent_user_service.AdminUserError as exc:
return ApiResponse(ok=False, message=exc.message)
return ApiResponse(
@@ -69,17 +69,17 @@ async def create_user(
async def update_user(
user_id: int,
body: AdminUserUpdate,
oem: OemUser,
agent: AgentUser,
db: DbSession,
) -> ApiResponse[AdminUserOut]:
try:
user = await oem_user_service.update_user(
user = await agent_user_service.update_user(
db,
user_id,
body,
actor_id=oem.id,
actor_id=agent.id,
)
except oem_user_service.AdminUserError as exc:
except agent_user_service.AdminUserError as exc:
return ApiResponse(ok=False, message=exc.message)
return ApiResponse(
@@ -92,12 +92,12 @@ async def update_user(
@router.delete("/{user_id}", response_model=ApiResponse[None])
async def delete_user(
user_id: int,
oem: OemUser,
agent: AgentUser,
db: DbSession,
) -> ApiResponse[None]:
try:
await oem_user_service.delete_user(db, user_id, actor_id=admin.id)
except oem_user_service.AdminUserError as exc:
await agent_user_service.delete_user(db, user_id, actor_id=agent.id)
except agent_user_service.AdminUserError as exc:
return ApiResponse(ok=False, message=exc.message)
return ApiResponse(ok=True, message="用户已删除")

View File

@@ -0,0 +1,176 @@
import secrets
import string
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.roles import ROLE_AGENT, ROLE_OEM
from app.models.card_key import CardKey
from app.models.user import User
from app.schemas.admin_card_key import CardKeyCreate, CardKeyUpdate
DEFAULT_PAGE_SIZE = 20
MAX_PAGE_SIZE = 200
_SERIAL_ALPHABET = string.ascii_uppercase + string.digits
_SERIAL_LENGTH = 16
class AdminCardKeyError(Exception):
def __init__(self, message: str) -> None:
self.message = message
super().__init__(message)
def _generate_serial() -> str:
return "".join(secrets.choice(_SERIAL_ALPHABET) for _ in range(_SERIAL_LENGTH))
async def _unique_serial(db: AsyncSession, preferred: str | None = None) -> str:
if preferred:
existing = await db.scalar(
select(CardKey.id).where(CardKey.serial_number == preferred)
)
if existing is not None:
raise AdminCardKeyError("序列号已存在")
return preferred
for _ in range(20):
candidate = _generate_serial()
existing = await db.scalar(
select(CardKey.id).where(CardKey.serial_number == candidate)
)
if existing is None:
return candidate
raise AdminCardKeyError("生成序列号失败,请重试")
async def _validate_assign_ids(
db: AsyncSession,
*,
oem_id: int | None,
agent_id: int | None,
) -> None:
if oem_id is not None and agent_id is not None:
raise AdminCardKeyError("OEM 与代理只能指定其一")
if oem_id is not None:
oem = await db.get(User, oem_id)
if oem is None or oem.role_id != ROLE_OEM:
raise AdminCardKeyError("所选 OEM 不存在")
if agent_id is not None:
agent = await db.get(User, agent_id)
if agent is None or agent.role_id != ROLE_AGENT:
raise AdminCardKeyError("所选代理不存在")
def _list_card_keys_filters(
*,
status: str | None = None,
username: str | None = None,
oem_id: int | None = None,
agent_id: int | None = None,
) -> list:
filters = []
if status == "unused":
filters.append(CardKey.activated_at.is_(None))
elif status == "used":
filters.append(CardKey.activated_at.is_not(None))
if username and username.strip():
filters.append(CardKey.username.ilike(f"%{username.strip()}%"))
if oem_id is not None:
filters.append(CardKey.oem_id == oem_id)
if agent_id is not None:
filters.append(CardKey.agent_id == agent_id)
return filters
async def list_card_keys(
db: AsyncSession,
*,
page: int = 1,
page_size: int = DEFAULT_PAGE_SIZE,
status: str | None = None,
username: str | None = None,
oem_id: int | None = None,
agent_id: int | None = None,
) -> tuple[list[CardKey], int]:
page_size = min(max(page_size, 1), MAX_PAGE_SIZE)
page = max(page, 1)
offset = (page - 1) * page_size
filters = _list_card_keys_filters(
status=status,
username=username,
oem_id=oem_id,
agent_id=agent_id,
)
count_stmt = select(func.count()).select_from(CardKey)
list_stmt = select(CardKey)
for clause in filters:
count_stmt = count_stmt.where(clause)
list_stmt = list_stmt.where(clause)
total = await db.scalar(count_stmt) or 0
result = await db.scalars(
list_stmt.order_by(CardKey.id.desc()).offset(offset).limit(page_size)
)
return list(result.all()), total
async def create_card_keys(db: AsyncSession, body: CardKeyCreate) -> list[CardKey]:
await _validate_assign_ids(db, oem_id=body.oem_id, agent_id=body.agent_id)
created: list[CardKey] = []
for index in range(body.count):
serial = await _unique_serial(
db,
body.serial_number if index == 0 else None,
)
card = CardKey(
serial_number=serial,
duration_days=body.duration_days,
remark=body.remark,
oem_id=body.oem_id,
agent_id=body.agent_id,
)
db.add(card)
created.append(card)
await db.flush()
for card in created:
await db.refresh(card)
return created
async def update_card_key(
db: AsyncSession,
card_id: int,
body: CardKeyUpdate,
) -> CardKey:
card = await db.get(CardKey, card_id)
if card is None:
raise AdminCardKeyError("卡密不存在")
if card.activated_at is not None:
if body.duration_days is not None:
raise AdminCardKeyError("已激活卡密不能修改时长")
elif body.duration_days is not None:
card.duration_days = body.duration_days
if body.remark is not None:
card.remark = body.remark
await db.flush()
await db.refresh(card)
return card
async def delete_card_key(db: AsyncSession, card_id: int) -> None:
card = await db.get(CardKey, card_id)
if card is None:
raise AdminCardKeyError("卡密不存在")
if card.activated_at is not None:
raise AdminCardKeyError("已激活卡密不能删除")
await db.delete(card)

175
app/services/agent_user.py Normal file
View File

@@ -0,0 +1,175 @@
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.roles import ROLE_ADMIN, ROLE_AGENT, ROLE_OEM
from app.core.security import hash_password
from app.models.user import User
from app.schemas.admin_user import AdminUserCreate, AdminUserUpdate
DEFAULT_PAGE_SIZE = 20
MAX_PAGE_SIZE = 200
class AdminUserError(Exception):
def __init__(self, message: str) -> None:
self.message = message
super().__init__(message)
def _list_users_filters(
*,
username: str | None = None,
oem_id: int | None = None,
agent_id: int | None = None,
role_id: int | None = None,
) -> list:
conditions = []
if username and username.strip():
conditions.append(User.username.ilike(f"%{username.strip()}%"))
if oem_id is not None:
conditions.append(User.oem_id == oem_id)
if agent_id is not None:
conditions.append(User.agent_id == agent_id)
if role_id is not None:
conditions.append(User.role_id == role_id)
return conditions
async def list_users(
db: AsyncSession,
*,
page: int = 1,
page_size: int = DEFAULT_PAGE_SIZE,
username: str | None = None,
oem_id: int | None = None,
agent_id: int | None = None,
role_id: int | None = None,
) -> tuple[list[User], int]:
page_size = min(max(page_size, 1), MAX_PAGE_SIZE)
page = max(page, 1)
offset = (page - 1) * page_size
conditions = _list_users_filters(
username=username,
oem_id=oem_id,
agent_id=agent_id,
role_id=role_id,
)
count_stmt = select(func.count()).select_from(User)
list_stmt = select(User).order_by(User.id.desc())
if conditions:
count_stmt = count_stmt.where(*conditions)
list_stmt = list_stmt.where(*conditions)
total = await db.scalar(count_stmt) or 0
result = await db.scalars(list_stmt.offset(offset).limit(page_size))
return list(result.all()), total
async def _validate_owner_ids(
db: AsyncSession,
*,
oem_id: int | None,
agent_id: int | None,
) -> None:
if oem_id is not None:
oem = await db.get(User, oem_id)
if oem is None or oem.role_id != ROLE_OEM:
raise AdminUserError("所选 OEM 不存在")
if agent_id is not None:
agent = await db.get(User, agent_id)
if agent is None or agent.role_id != ROLE_AGENT:
raise AdminUserError("所选代理不存在")
async def create_user(db: AsyncSession, body: AdminUserCreate,oem: User) -> User:
existing = await db.scalar(select(User).where(User.username == body.username))
if existing is not None:
raise AdminUserError("用户名已存在")
if body.phone:
phone_taken = await db.scalar(select(User).where(User.phone == body.phone))
if phone_taken is not None:
raise AdminUserError("手机号已被使用")
await _validate_owner_ids(db, oem_id=oem.id, agent_id=body.agent_id)
user = User(
username=body.username,
password_hash=hash_password(body.password),
phone=body.phone,
role_id=body.role_id,
oem_id=oem.id,
agent_id=body.agent_id,
vip_end_time=body.vip_end_time,
)
db.add(user)
await db.flush()
await db.refresh(user)
return user
async def update_user(
db: AsyncSession,
oem: User,
user_id: int,
body: AdminUserUpdate,
*,
actor_id: int,
) -> User:
user = await db.get(User, user_id)
if user is None:
raise AdminUserError("用户不存在")
body.oem_id=oem.id
if body.username is not None and body.username != user.username:
taken = await db.scalar(select(User).where(User.username == body.username))
if taken is not None:
raise AdminUserError("用户名已存在")
user.username = body.username
if body.phone is not None:
if body.phone != user.phone:
taken = await db.scalar(select(User).where(User.phone == body.phone))
if taken is not None:
raise AdminUserError("手机号已被使用")
user.phone = body.phone
if body.role_id is not None:
if user_id == actor_id and body.role_id != ROLE_ADMIN:
raise AdminUserError("不能修改自己的管理员角色")
user.role_id = body.role_id
if body.oem_id is not None or body.agent_id is not None:
await _validate_owner_ids(
db,
oem_id=body.oem_id if body.oem_id is not None else user.oem_id,
agent_id=body.agent_id if body.agent_id is not None else user.agent_id,
)
if body.oem_id is not None:
user.oem_id = body.oem_id
if body.agent_id is not None:
user.agent_id = body.agent_id
if body.password:
user.password_hash = hash_password(body.password)
if body.clear_vip_end_time:
user.vip_end_time = None
elif body.vip_end_time is not None:
user.vip_end_time = body.vip_end_time
await db.flush()
await db.refresh(user)
return user
async def delete_user(db: AsyncSession, oem: User, user_id: int, *, actor_id: int) -> None:
if user_id == actor_id:
raise AdminUserError("不能删除当前登录账号")
user = await db.get(User, user_id)
if user is None:
raise AdminUserError("用户不存在")
if user.oem_id != oem.id:
raise AdminUserError("无权限删除该用户")
await db.delete(user)