190 lines
7.3 KiB
Python
190 lines
7.3 KiB
Python
"""管理后台服务"""
|
|
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import select, func, delete
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.config import settings
|
|
from app.models.user import User
|
|
from app.models.message import Message
|
|
from app.models.conversation import Conversation
|
|
from app.models.system_config import SystemConfig
|
|
from app.utils.security import verify_password, hash_password, create_access_token
|
|
|
|
|
|
class AdminService:
|
|
def __init__(self, db: AsyncSession = None):
|
|
self.db = db
|
|
|
|
async def init_system_config(self):
|
|
"""初始化系统默认配置"""
|
|
if not self.db:
|
|
return
|
|
defaults = {
|
|
"platform_name": "青叶",
|
|
"announcement": "",
|
|
"max_upload_size_mb": "10",
|
|
"allow_registration": "true",
|
|
"admin_password_hash": hash_password(settings.ADMIN_PASSWORD),
|
|
}
|
|
for key, value in defaults.items():
|
|
result = await self.db.execute(
|
|
select(SystemConfig).where(SystemConfig.key == key)
|
|
)
|
|
if not result.scalars().first():
|
|
self.db.add(SystemConfig(id=str(uuid.uuid4()), key=key, value=value))
|
|
|
|
async def login(self, password: str) -> str | None:
|
|
"""管理员登录(仅密码)"""
|
|
result = await self.db.execute(
|
|
select(SystemConfig).where(SystemConfig.key == "admin_password_hash")
|
|
)
|
|
config = result.scalars().first()
|
|
if not config:
|
|
return None
|
|
|
|
if not verify_password(password, config.value):
|
|
return None
|
|
|
|
# 生成管理员 Token
|
|
token = create_access_token({
|
|
"sub": "admin",
|
|
"username": "admin",
|
|
"is_admin": True,
|
|
})
|
|
return token
|
|
|
|
async def get_dashboard_stats(self) -> dict:
|
|
"""获取仪表盘统计数据"""
|
|
total_users = await self.db.execute(select(func.count(User.id)))
|
|
online_users = await self.db.execute(
|
|
select(func.count(User.id)).where(User.status == "online")
|
|
)
|
|
total_messages = await self.db.execute(select(func.count(Message.id)))
|
|
total_conversations = await self.db.execute(select(func.count(Conversation.id)))
|
|
|
|
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
today_messages = await self.db.execute(
|
|
select(func.count(Message.id)).where(Message.created_at >= today)
|
|
)
|
|
seven_days_ago = datetime.utcnow() - __import__("datetime").timedelta(days=7)
|
|
new_users_7d = await self.db.execute(
|
|
select(func.count(User.id)).where(User.created_at >= seven_days_ago)
|
|
)
|
|
|
|
return {
|
|
"total_users": total_users.scalar() or 0,
|
|
"online_users": online_users.scalar() or 0,
|
|
"total_messages": total_messages.scalar() or 0,
|
|
"today_messages": today_messages.scalar() or 0,
|
|
"total_conversations": total_conversations.scalar() or 0,
|
|
"new_users_7d": new_users_7d.scalar() or 0,
|
|
}
|
|
|
|
async def get_trend_data(self, metric: str, days: int = 7) -> list[dict]:
|
|
"""获取趋势数据"""
|
|
from sqlalchemy import cast, Date
|
|
trends = []
|
|
for i in range(days - 1, -1, -1):
|
|
day = (datetime.utcnow() - __import__("datetime").timedelta(days=i)).date()
|
|
day_start = datetime.combine(day, __import__("datetime").time.min)
|
|
day_end = datetime.combine(day, __import__("datetime").time.max)
|
|
|
|
if metric == "online":
|
|
# 简化:使用当前在线数
|
|
count = await self.db.execute(
|
|
select(func.count(User.id)).where(User.status == "online")
|
|
)
|
|
value = count.scalar() or 0
|
|
elif metric == "messages":
|
|
count = await self.db.execute(
|
|
select(func.count(Message.id)).where(
|
|
Message.created_at >= day_start,
|
|
Message.created_at <= day_end,
|
|
)
|
|
)
|
|
value = count.scalar() or 0
|
|
elif metric == "registrations":
|
|
count = await self.db.execute(
|
|
select(func.count(User.id)).where(
|
|
User.created_at >= day_start,
|
|
User.created_at <= day_end,
|
|
)
|
|
)
|
|
value = count.scalar() or 0
|
|
else:
|
|
value = 0
|
|
|
|
trends.append({"date": day.isoformat(), "value": value})
|
|
return trends
|
|
|
|
async def get_users_list(self, page: int = 1, page_size: int = 20,
|
|
search: str | None = None, status: str | None = None) -> dict:
|
|
"""获取用户列表(管理后台)"""
|
|
query = select(User)
|
|
count_query = select(func.count(User.id))
|
|
|
|
if search:
|
|
query = query.where(User.username.ilike(f"%{search}%"))
|
|
count_query = count_query.where(User.username.ilike(f"%{search}%"))
|
|
if status == "online":
|
|
query = query.where(User.status == "online")
|
|
count_query = count_query.where(User.status == "online")
|
|
elif status == "banned":
|
|
query = query.where(User.is_banned == True)
|
|
count_query = count_query.where(User.is_banned == True)
|
|
|
|
total = (await self.db.execute(count_query)).scalar() or 0
|
|
result = await self.db.execute(
|
|
query.order_by(User.created_at.desc())
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
)
|
|
|
|
users = []
|
|
for u in result.scalars().all():
|
|
users.append({
|
|
"id": u.id,
|
|
"username": u.username,
|
|
"email": u.email,
|
|
"avatar_url": u.avatar_url,
|
|
"status": u.status,
|
|
"is_banned": u.is_banned,
|
|
"banned_reason": u.banned_reason,
|
|
"last_seen_at": u.last_seen_at,
|
|
"created_at": u.created_at,
|
|
})
|
|
|
|
return {"items": users, "total": total, "page": page, "page_size": page_size}
|
|
|
|
async def ban_user(self, user_id: str, is_banned: bool, reason: str | None = None):
|
|
"""封禁/解封用户"""
|
|
result = await self.db.execute(select(User).where(User.id == user_id))
|
|
user = result.scalars().first()
|
|
if not user:
|
|
raise ValueError("用户不存在")
|
|
user.is_banned = is_banned
|
|
user.banned_reason = reason if is_banned else None
|
|
|
|
async def delete_user(self, user_id: str):
|
|
"""删除用户"""
|
|
await self.db.execute(delete(User).where(User.id == user_id))
|
|
|
|
async def get_all_configs(self) -> list[dict]:
|
|
"""获取所有系统配置"""
|
|
result = await self.db.execute(select(SystemConfig))
|
|
return [{"key": c.key, "value": c.value} for c in result.scalars().all()]
|
|
|
|
async def update_configs(self, configs: dict[str, str]):
|
|
"""更新系统配置"""
|
|
for key, value in configs.items():
|
|
result = await self.db.execute(
|
|
select(SystemConfig).where(SystemConfig.key == key)
|
|
)
|
|
config = result.scalars().first()
|
|
if config:
|
|
config.value = value
|
|
config.updated_at = datetime.utcnow()
|