369 lines
11 KiB
Python
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"}
|