Files
QihuAI Design 96be3fdeb3
CI / gate (push) Has been cancelled
phase1-final
2026-07-27 22:27:56 +08:00

369 lines
11 KiB
Python

from app.api.chat_service import get_user_balance
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import and_, desc, asc
from pydantic import BaseModel, Field
from typing import Optional
import uuid
from datetime import datetime, date
from ..database import get_db
from ..models import (
User, LoginLog, ApiKey, MembershipSubscription, AppPurchase, MarketApp,
SmsConfig, gen_uuid
)
from ..auth import get_current_user, hash_password, verify_password
from ..middleware.log_service import LogService
from ..sms_client import send_sms_text
router = APIRouter()
class UpdateProfileRequest(BaseModel):
username: Optional[str] = None
email: Optional[str] = None
phone: Optional[str] = None
avatar: Optional[str] = None
class UpdatePasswordRequest(BaseModel):
old_password: str
new_password: str = Field(min_length=6, max_length=100)
class CreateApiKeyRequest(BaseModel):
name: str = Field(min_length=1, max_length=100)
class UpdateApiKeyRequest(BaseModel):
status: str # enabled, disabled, revoked
@router.get("/balance")
async def get_balance(user=Depends(get_current_user)):
balance = await get_user_balance(user["id"])
return {"code": 0, "data": balance, "message": "ok"}
@router.get("/")
def get_profile(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
# 获取会员信息
membership = db.query(MembershipSubscription).filter(
MembershipSubscription.user_id == current_user.id
).first()
membership_info = None
if membership:
membership_info = {
"id": membership.id,
"plan_id": membership.plan_id,
"status": membership.status,
"start_at": str(membership.start_at) if membership.start_at else None,
"expire_at": str(membership.end_at) if membership.end_at else None,
"is_expired": membership.end_at < datetime.now() if membership.end_at else False,
}
return {
"code": 0,
"data": {
"id": current_user.id,
"username": current_user.username,
"email": current_user.email,
"phone": current_user.phone,
"avatar": current_user.avatar,
"role": current_user.role,
"status": current_user.status,
"membership": membership_info,
"created_at": str(current_user.created_at) if current_user.created_at else None,
"updated_at": str(current_user.updated_at) if current_user.updated_at else None,
},
"message": "ok",
}
@router.put("/")
def update_profile(
req: UpdateProfileRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
# 从当前会话中重新获取用户对象
db_user = db.query(User).filter(User.id == current_user.id).first()
if not db_user:
raise HTTPException(status_code=404, detail="用户不存在")
if req.username is not None and req.username != db_user.username:
# 检查用户名是否被占用
existing = db.query(User).filter(User.username == req.username).first()
if existing:
raise HTTPException(status_code=400, detail="用户名已被占用")
db_user.username = req.username
if req.email is not None and req.email != db_user.email:
existing = db.query(User).filter(User.email == req.email).first()
if existing:
raise HTTPException(status_code=400, detail="邮箱已被占用")
db_user.email = req.email
if req.phone is not None:
db_user.phone = req.phone
if req.avatar is not None:
db_user.avatar = req.avatar
db_user.updated_at = datetime.now()
LogService.log_operation(
user_id=db_user.id, username=db_user.username, action="update_profile",
target="user", target_id=db_user.id,
detail=f"用户 {db_user.username} 更新了个人信息",
)
db.commit()
db.refresh(db_user)
return {
"code": 0,
"data": {
"id": db_user.id,
"username": db_user.username,
"email": db_user.email,
"phone": db_user.phone,
"avatar": db_user.avatar,
},
"message": "更新成功",
}
@router.put("/password")
def update_password(
req: UpdatePasswordRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
db_user = db.query(User).filter(User.id == current_user.id).first()
if not db_user:
raise HTTPException(status_code=404, detail="用户不存在")
if not verify_password(req.old_password, db_user.password_hash):
raise HTTPException(status_code=400, detail="原密码错误")
db_user.password_hash = hash_password(req.new_password)
db_user.updated_at = datetime.now()
# 密码修改成功后发送短信通知(若已配置短信且用户绑定手机号)
try:
sms_cfg = db.query(SmsConfig).first()
if sms_cfg and sms_cfg.provider and db_user.phone:
send_sms_text(sms_cfg, db_user.phone, "您的登录密码已修改,如非本人操作请尽快联系客服。")
except Exception as sms_err:
import logging
logging.getLogger("aiflow").warning(f"密码修改短信通知失败(不影响修改结果): {sms_err}")
LogService.log_operation(
user_id=db_user.id, username=db_user.username, action="change_password",
target="user", target_id=db_user.id,
detail=f"用户 {db_user.username} 修改了密码",
)
db.commit()
return {"code": 0, "message": "密码修改成功"}
@router.get("/login-logs")
def get_login_logs(
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
query = db.query(LoginLog).filter(LoginLog.user_id == current_user.id)
total = query.count()
logs = (
query.order_by(desc(LoginLog.login_at))
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
result = []
for log_item in logs:
result.append({
"id": log_item.id,
"ip": log_item.ip,
"user_agent": log_item.user_agent,
"login_at": str(log_item.login_at) if log_item.login_at else None,
})
return {
"code": 0,
"data": {"total": total, "page": page, "page_size": page_size, "items": result},
"message": "ok",
}
@router.get("/api-keys")
def get_api_keys(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
keys = (
db.query(ApiKey)
.filter(ApiKey.user_id == current_user.id)
.order_by(desc(ApiKey.created_at))
.all()
)
result = []
for k in keys:
preview = None
if k.api_key and len(k.api_key) > 12:
preview = k.api_key[:8] + "****" + k.api_key[-4:]
result.append({
"id": k.id,
"name": k.name,
"key_preview": preview or "****",
"status": k.status,
"last_used_at": str(k.last_used_at) if k.last_used_at else None,
"created_at": str(k.created_at) if k.created_at else None,
"expire_at": str(k.expired_at) if k.expired_at else None,
})
return {"code": 0, "data": result, "message": "ok"}
@router.post("/api-keys")
def create_api_key(
req: CreateApiKeyRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
# 生成API密钥
key_value = f"sk-{gen_uuid().replace('-', '')}"
api_key = ApiKey(
id=gen_uuid(),
user_id=current_user.id,
name=req.name,
api_key=key_value,
secret=f"sec-{gen_uuid().replace('-', '')}",
status="active",
created_at=datetime.now(),
)
db.add(api_key)
LogService.log_operation(
user_id=current_user.id, username=current_user.username, action="create_api_key",
target="api_key", target_id=api_key.id,
detail=f"用户 {current_user.username} 创建了API密钥 {api_key.name}",
)
db.commit()
db.refresh(api_key)
return {
"code": 0,
"data": {
"id": api_key.id,
"name": api_key.name,
"api_key": key_value, # 仅创建时返回完整key
"status": api_key.status,
"created_at": str(api_key.created_at),
},
"message": "API密钥创建成功,请妥善保管",
}
@router.put("/api-keys/{key_id}")
def update_api_key(
key_id: str,
req: UpdateApiKeyRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
api_key = db.query(ApiKey).filter(
and_(ApiKey.id == key_id, ApiKey.user_id == current_user.id)
).first()
if not api_key:
raise HTTPException(status_code=404, detail="API密钥不存在")
if req.status not in ("enabled", "disabled", "revoked"):
raise HTTPException(status_code=400, detail="无效的状态值")
api_key.status = req.status
LogService.log_operation(
user_id=current_user.id, username=current_user.username, action="update_api_key",
target="api_key", target_id=api_key.id,
detail=f"用户 {current_user.username} 更新了API密钥 {api_key.name} 状态为 {req.status}",
)
db.commit()
return {"code": 0, "data": {"id": api_key.id, "status": api_key.status}, "message": "更新成功"}
@router.get("/membership")
def get_membership(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
membership = db.query(MembershipSubscription).filter(
MembershipSubscription.user_id == current_user.id
).first()
if not membership:
return {
"code": 0,
"data": {"has_membership": False, "status": None},
"message": "ok",
}
is_expired = membership.end_at < datetime.now() if membership.end_at else False
return {
"code": 0,
"data": {
"has_membership": True,
"id": membership.id,
"plan_id": membership.plan_id,
"status": "expired" if is_expired else membership.status,
"start_at": str(membership.start_at) if membership.start_at else None,
"expire_at": str(membership.end_at) if membership.end_at else None,
"is_expired": is_expired,
},
"message": "ok",
}
@router.get("/purchased-apps")
def get_purchased_apps(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
purchases = (
db.query(AppPurchase)
.filter(AppPurchase.user_id == current_user.id)
.order_by(desc(AppPurchase.created_at))
.all()
)
result = []
for p in purchases:
app = db.query(MarketApp).filter(MarketApp.id == p.app_id).first()
result.append({
"id": p.id,
"app_id": p.app_id,
"app_name": app.name if app else "unknown",
"app_icon": app.icon if app else None,
"app_type": app.type if app else None,
"price": float(p.price if p.price and app else 0),
"purchased_at": str(p.created_at) if p.created_at else None,
})
return {"code": 0, "data": result, "message": "ok"}