This commit is contained in:
2026-05-06 10:28:16 +08:00
parent 06b3e54a39
commit 20451904bd
5 changed files with 111 additions and 24 deletions
+8
View File
@@ -79,14 +79,20 @@ async def upload_stp(
current_user: User = Depends(get_current_active_user)
):
"""上传STP文件并存储到数据库"""
logger.info(
f"[UPLOAD] 用户={current_user.username}(id={current_user.id}) "
f"文件={file.filename} 材料={material}"
)
if not file.filename.lower().endswith(('.stp', '.step')):
logger.warning(f"[UPLOAD] 拒绝: 不支持的文件类型 - {file.filename}")
raise HTTPException(400, "只支持STP/STEP文件")
task_id = str(uuid.uuid4())
# 保存文件
file_path, file_size = await file_handler.save_uploaded_file(file)
logger.info(f"[UPLOAD] 文件已保存: {file_path} ({file_size} bytes), task_id={task_id}")
# 创建存储集成服务实例
storage_service = StorageIntegrationService()
@@ -98,6 +104,7 @@ async def upload_stp(
original_filename=file.filename,
user_id=current_user.id
)
logger.info(f"[UPLOAD] STP文件已存入RustFS+PG: stp_file.id={stp_file.id}")
# 创建处理任务记录
await storage_service.create_processing_task(db_session, task_id, stp_file.id)
@@ -122,6 +129,7 @@ async def upload_stp(
stp_file.id,
material,
)
logger.info(f"[UPLOAD] 后台处理已调度: task_id={task_id}")
return {
"task_id": task_id,
+9 -6
View File
@@ -32,30 +32,33 @@ async def upload_stp(
current_user: User = Depends(get_current_active_user)
):
"""上传STP文件并存储到数据库"""
logger.info(
f"[UPLOAD] 用户={current_user.username}(id={current_user.id}) "
f"文件={file.filename} 材料={material} "
f"大小={file.size if hasattr(file, 'size') else 'unknown'}"
)
if not file.filename.lower().endswith(('.stp', '.step')):
logger.warning(f"[UPLOAD] 拒绝: 不支持的文件类型 - {file.filename}")
raise HTTPException(400, "只支持STP/STEP文件")
task_id = str(uuid.uuid4())
# 保存文件
file_path, file_size = await file_handler.save_uploaded_file(file)
logger.info(f"[UPLOAD] 文件已保存: {file_path} ({file_size} bytes), task_id={task_id}")
# 创建存储集成服务实例
storage_service = StorageIntegrationService()
# 保存STP文件到RustFS + PostgreSQL
stp_file = await storage_service.save_stp_file(
session=db_session,
file_path=file_path,
original_filename=file.filename,
user_id=current_user.id
)
logger.info(f"[UPLOAD] STP文件已存入RustFS+PG: stp_file.id={stp_file.id}")
# 创建处理任务记录
await storage_service.create_processing_task(db_session, task_id, stp_file.id)
# 创建任务记录(存入 Redis)
task_info = create_task_info(
task_id=task_id,
status=ProcessingStatus.PROCESSING,
@@ -66,11 +69,11 @@ async def upload_stp(
)
await redis_task_manager.set_task(task_id, task_info)
# 后台处理(使用独立数据库会话,避免请求会话关闭问题)
background_tasks.add_task(
processing_service.process_file_with_storage,
task_id, file_path, stp_file.id, material
)
logger.info(f"[UPLOAD] 后台处理已调度: task_id={task_id}")
return {
"task_id": task_id,
+35 -2
View File
@@ -34,17 +34,21 @@ except ImportError as e:
print(f" - {item}")
raise
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import asyncio
import time
from api.auth_routes import router as auth_router
from api.inventory import inventory_router
from utils.logger import setup_logging
from utils.logger import setup_logging, get_logger
from database.init_db import init_database
setup_logging()
logger = get_logger(__name__)
app = FastAPI(
title="Gemold - 模具制造管理系统",
@@ -52,6 +56,35 @@ app = FastAPI(
version="4.0.0"
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
duration = time.time() - start_time
status = response.status_code
if status >= 400:
auth_header = request.headers.get("authorization", "")
token_preview = ""
if auth_header.startswith("Bearer "):
token_raw = auth_header[7:]
token_preview = token_raw[:20] + "..." if len(token_raw) > 20 else token_raw
logger.warning(
f"[HTTP] {request.method} {request.url.path} -> {status} "
f"({duration:.2f}s) "
f"token={token_preview or 'none'}"
)
return response
# 启动时初始化数据库和RustFS
@app.on_event("startup")
async def startup_event():
+33 -11
View File
@@ -1,8 +1,8 @@
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from jose import JWTError, ExpiredSignatureError, jwt
import bcrypt
from fastapi import Depends, HTTPException, status
from fastapi import Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
@@ -11,6 +11,9 @@ from sqlalchemy.orm import selectinload
from config.settings import settings
from database.database import get_db_session
from models.database import User, UserRole
from utils.logger import get_logger
logger = get_logger(__name__)
pwd_context = bcrypt
@@ -45,18 +48,30 @@ async def get_current_user(
if not token:
return None
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无法验证凭据",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
logger.warning(f"[AUTH] Token 中缺少 sub 字段")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token 格式无效:缺少用户标识",
headers={"WWW-Authenticate": "Bearer"},
)
except ExpiredSignatureError:
logger.warning(f"[AUTH] Token 已过期")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="登录已过期,请重新登录",
headers={"WWW-Authenticate": "Bearer"},
)
except JWTError as e:
logger.warning(f"[AUTH] Token 验证失败: {type(e).__name__}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token 无效,请重新登录",
headers={"WWW-Authenticate": "Bearer"},
)
result = await db_session.execute(
select(User).options(selectinload(User.user_roles).selectinload(UserRole.role)).where(User.username == username)
@@ -64,8 +79,14 @@ async def get_current_user(
user = result.scalar_one_or_none()
if user is None:
raise credentials_exception
logger.warning(f"[AUTH] Token 有效但用户不存在: {username}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="用户账户不存在,请重新登录",
headers={"WWW-Authenticate": "Bearer"},
)
if not user.is_active:
logger.warning(f"[AUTH] 用户已被禁用: {username}")
raise HTTPException(status_code=400, detail="用户已被禁用")
return user
@@ -75,6 +96,7 @@ async def get_current_active_user(
current_user: Optional[User] = Depends(get_current_user)
) -> User:
if not current_user:
logger.warning("[AUTH] 未提供认证信息,拒绝访问")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="请先登录",
+22 -1
View File
@@ -918,6 +918,20 @@ const MoldInsightView = {
const uploadFile = async () => {
if (!state.selectedFile) return;
if (!appState.token) {
state.error = '请先登录后再上传文件';
addNotification('请先登录', 'warning');
router.push('/login');
return;
}
if (appState.token === 'demo') {
state.error = '演示模式不支持文件上传,请使用完整账户登录';
addNotification('演示模式不支持上传', 'warning');
return;
}
state.uploading = true;
state.error = "";
state.progress = 0;
@@ -929,9 +943,16 @@ const MoldInsightView = {
try {
const res = await fetch("/api/upload", {
method: "POST",
headers: appState.token ? { 'Authorization': `Bearer ${appState.token}` } : {},
headers: { 'Authorization': `Bearer ${appState.token}` },
body: formData
});
if (res.status === 401) {
clearAuth();
state.error = '登录已过期,请重新登录';
addNotification('登录已过期,请重新登录', 'warning');
router.push('/login');
return;
}
if (!res.ok) throw new Error(`上传失败: ${res.status}`);
const data = await res.json();
state.currentTask = { task_id: data.task_id, status: "processing", filename: data.file_info?.filename };