x
This commit is contained in:
@@ -79,14 +79,20 @@ async def upload_stp(
|
|||||||
current_user: User = Depends(get_current_active_user)
|
current_user: User = Depends(get_current_active_user)
|
||||||
):
|
):
|
||||||
"""上传STP文件并存储到数据库"""
|
"""上传STP文件并存储到数据库"""
|
||||||
|
logger.info(
|
||||||
|
f"[UPLOAD] 用户={current_user.username}(id={current_user.id}) "
|
||||||
|
f"文件={file.filename} 材料={material}"
|
||||||
|
)
|
||||||
|
|
||||||
if not file.filename.lower().endswith(('.stp', '.step')):
|
if not file.filename.lower().endswith(('.stp', '.step')):
|
||||||
|
logger.warning(f"[UPLOAD] 拒绝: 不支持的文件类型 - {file.filename}")
|
||||||
raise HTTPException(400, "只支持STP/STEP文件")
|
raise HTTPException(400, "只支持STP/STEP文件")
|
||||||
|
|
||||||
task_id = str(uuid.uuid4())
|
task_id = str(uuid.uuid4())
|
||||||
|
|
||||||
# 保存文件
|
# 保存文件
|
||||||
file_path, file_size = await file_handler.save_uploaded_file(file)
|
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()
|
storage_service = StorageIntegrationService()
|
||||||
@@ -98,6 +104,7 @@ async def upload_stp(
|
|||||||
original_filename=file.filename,
|
original_filename=file.filename,
|
||||||
user_id=current_user.id
|
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)
|
await storage_service.create_processing_task(db_session, task_id, stp_file.id)
|
||||||
@@ -122,6 +129,7 @@ async def upload_stp(
|
|||||||
stp_file.id,
|
stp_file.id,
|
||||||
material,
|
material,
|
||||||
)
|
)
|
||||||
|
logger.info(f"[UPLOAD] 后台处理已调度: task_id={task_id}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
|
|||||||
@@ -32,30 +32,33 @@ async def upload_stp(
|
|||||||
current_user: User = Depends(get_current_active_user)
|
current_user: User = Depends(get_current_active_user)
|
||||||
):
|
):
|
||||||
"""上传STP文件并存储到数据库"""
|
"""上传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')):
|
if not file.filename.lower().endswith(('.stp', '.step')):
|
||||||
|
logger.warning(f"[UPLOAD] 拒绝: 不支持的文件类型 - {file.filename}")
|
||||||
raise HTTPException(400, "只支持STP/STEP文件")
|
raise HTTPException(400, "只支持STP/STEP文件")
|
||||||
|
|
||||||
task_id = str(uuid.uuid4())
|
task_id = str(uuid.uuid4())
|
||||||
|
|
||||||
# 保存文件
|
|
||||||
file_path, file_size = await file_handler.save_uploaded_file(file)
|
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()
|
storage_service = StorageIntegrationService()
|
||||||
|
|
||||||
# 保存STP文件到RustFS + PostgreSQL
|
|
||||||
stp_file = await storage_service.save_stp_file(
|
stp_file = await storage_service.save_stp_file(
|
||||||
session=db_session,
|
session=db_session,
|
||||||
file_path=file_path,
|
file_path=file_path,
|
||||||
original_filename=file.filename,
|
original_filename=file.filename,
|
||||||
user_id=current_user.id
|
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)
|
await storage_service.create_processing_task(db_session, task_id, stp_file.id)
|
||||||
|
|
||||||
# 创建任务记录(存入 Redis)
|
|
||||||
task_info = create_task_info(
|
task_info = create_task_info(
|
||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
status=ProcessingStatus.PROCESSING,
|
status=ProcessingStatus.PROCESSING,
|
||||||
@@ -66,11 +69,11 @@ async def upload_stp(
|
|||||||
)
|
)
|
||||||
await redis_task_manager.set_task(task_id, task_info)
|
await redis_task_manager.set_task(task_id, task_info)
|
||||||
|
|
||||||
# 后台处理(使用独立数据库会话,避免请求会话关闭问题)
|
|
||||||
background_tasks.add_task(
|
background_tasks.add_task(
|
||||||
processing_service.process_file_with_storage,
|
processing_service.process_file_with_storage,
|
||||||
task_id, file_path, stp_file.id, material
|
task_id, file_path, stp_file.id, material
|
||||||
)
|
)
|
||||||
|
logger.info(f"[UPLOAD] 后台处理已调度: task_id={task_id}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
|
|||||||
+35
-2
@@ -34,17 +34,21 @@ except ImportError as e:
|
|||||||
print(f" - {item}")
|
print(f" - {item}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI, Request
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import time
|
||||||
|
|
||||||
from api.auth_routes import router as auth_router
|
from api.auth_routes import router as auth_router
|
||||||
from api.inventory import inventory_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
|
from database.init_db import init_database
|
||||||
|
|
||||||
setup_logging()
|
setup_logging()
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Gemold - 模具制造管理系统",
|
title="Gemold - 模具制造管理系统",
|
||||||
@@ -52,6 +56,35 @@ app = FastAPI(
|
|||||||
version="4.0.0"
|
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
|
# 启动时初始化数据库和RustFS
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def startup_event():
|
async def startup_event():
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from jose import JWTError, jwt
|
from jose import JWTError, ExpiredSignatureError, jwt
|
||||||
import bcrypt
|
import bcrypt
|
||||||
from fastapi import Depends, HTTPException, status
|
from fastapi import Depends, HTTPException, status, Request
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -11,6 +11,9 @@ from sqlalchemy.orm import selectinload
|
|||||||
from config.settings import settings
|
from config.settings import settings
|
||||||
from database.database import get_db_session
|
from database.database import get_db_session
|
||||||
from models.database import User, UserRole
|
from models.database import User, UserRole
|
||||||
|
from utils.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
pwd_context = bcrypt
|
pwd_context = bcrypt
|
||||||
|
|
||||||
@@ -44,30 +47,48 @@ async def get_current_user(
|
|||||||
) -> Optional[User]:
|
) -> Optional[User]:
|
||||||
if not token:
|
if not token:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
credentials_exception = HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="无法验证凭据",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||||
username: str = payload.get("sub")
|
username: str = payload.get("sub")
|
||||||
if username is None:
|
if username is None:
|
||||||
raise credentials_exception
|
logger.warning(f"[AUTH] Token 中缺少 sub 字段")
|
||||||
except JWTError:
|
raise HTTPException(
|
||||||
raise credentials_exception
|
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(
|
result = await db_session.execute(
|
||||||
select(User).options(selectinload(User.user_roles).selectinload(UserRole.role)).where(User.username == username)
|
select(User).options(selectinload(User.user_roles).selectinload(UserRole.role)).where(User.username == username)
|
||||||
)
|
)
|
||||||
user = result.scalar_one_or_none()
|
user = result.scalar_one_or_none()
|
||||||
|
|
||||||
if user is 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:
|
if not user.is_active:
|
||||||
|
logger.warning(f"[AUTH] 用户已被禁用: {username}")
|
||||||
raise HTTPException(status_code=400, detail="用户已被禁用")
|
raise HTTPException(status_code=400, detail="用户已被禁用")
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
@@ -75,6 +96,7 @@ async def get_current_active_user(
|
|||||||
current_user: Optional[User] = Depends(get_current_user)
|
current_user: Optional[User] = Depends(get_current_user)
|
||||||
) -> User:
|
) -> User:
|
||||||
if not current_user:
|
if not current_user:
|
||||||
|
logger.warning("[AUTH] 未提供认证信息,拒绝访问")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail="请先登录",
|
detail="请先登录",
|
||||||
|
|||||||
+22
-1
@@ -918,6 +918,20 @@ const MoldInsightView = {
|
|||||||
|
|
||||||
const uploadFile = async () => {
|
const uploadFile = async () => {
|
||||||
if (!state.selectedFile) return;
|
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.uploading = true;
|
||||||
state.error = "";
|
state.error = "";
|
||||||
state.progress = 0;
|
state.progress = 0;
|
||||||
@@ -929,9 +943,16 @@ const MoldInsightView = {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch("/api/upload", {
|
const res = await fetch("/api/upload", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: appState.token ? { 'Authorization': `Bearer ${appState.token}` } : {},
|
headers: { 'Authorization': `Bearer ${appState.token}` },
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
clearAuth();
|
||||||
|
state.error = '登录已过期,请重新登录';
|
||||||
|
addNotification('登录已过期,请重新登录', 'warning');
|
||||||
|
router.push('/login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!res.ok) throw new Error(`上传失败: ${res.status}`);
|
if (!res.ok) throw new Error(`上传失败: ${res.status}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
state.currentTask = { task_id: data.task_id, status: "processing", filename: data.file_info?.filename };
|
state.currentTask = { task_id: data.task_id, status: "processing", filename: data.file_info?.filename };
|
||||||
|
|||||||
Reference in New Issue
Block a user