初始提交: Qihu-AIFlow v2.0.1 完整项目
- 前端 React+TypeScript (源码+构建产物) - 后端 Python FastAPI - 数据库迁移 001-015 - Docker部署配置 - 微信ClawBot API频道绑定 - 项目文档 README.md
This commit is contained in:
commit
78ff261b5a
199 files changed
+69126
No files matched your search
@@ -0,0 +1,351 @@
|
||||
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,
|
||||
gen_uuid
|
||||
)
|
||||
from ..auth import get_current_user, hash_password, verify_password
|
||||
from ..middleware.log_service import LogService
|
||||
|
||||
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("/")
|
||||
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()
|
||||
|
||||
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"}
|
||||
Reference in new issue
Block a user