Files
zcglxt/app/crud/file_management.py
Claude e71181f0a3 fix: 修复多个关键问题
- 修复前端路由守卫:未登录时不显示提示,直接跳转登录页
- 修复API拦截器:401错误不显示提示,直接跳转
- 增强验证码显示:图片尺寸从120x40增加到200x80
- 增大验证码字体:从28号增加到48号
- 优化验证码字符:排除易混淆的0和1
- 减少干扰线:从5条减少到3条,添加背景色优化
- 增强登录API日志:添加详细的调试日志
- 增强验证码生成和验证日志
- 优化异常处理和错误追踪

影响文件:
- src/router/index.ts
- src/api/request.ts
- app/services/auth_service.py
- app/api/v1/auth.py
- app/schemas/user.py

测试状态:
- 前端构建通过
- 后端语法检查通过
- 验证码显示效果优化完成

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-25 00:26:21 +08:00

236 lines
7.6 KiB
Python

"""
文件管理CRUD操作
"""
from typing import List, Optional, Dict, Any, Tuple
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, func, desc
from datetime import datetime, timedelta
from app.models.file_management import UploadedFile
class CRUDUploadedFile:
"""上传文件CRUD操作"""
def create(self, db: Session, *, obj_in: Dict[str, Any]) -> UploadedFile:
"""创建文件记录"""
db_obj = UploadedFile(**obj_in)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
return db_obj
def get(self, db: Session, id: int) -> Optional[UploadedFile]:
"""根据ID获取文件"""
return db.query(UploadedFile).filter(
and_(
UploadedFile.id == id,
UploadedFile.is_deleted == 0
)
).first()
def get_by_share_code(self, db: Session, share_code: str) -> Optional[UploadedFile]:
"""根据分享码获取文件"""
now = datetime.utcnow()
return db.query(UploadedFile).filter(
and_(
UploadedFile.share_code == share_code,
UploadedFile.is_deleted == 0,
or_(
UploadedFile.share_expire_time.is_(None),
UploadedFile.share_expire_time > now
)
)
).first()
def get_multi(
self,
db: Session,
*,
skip: int = 0,
limit: int = 20,
keyword: Optional[str] = None,
file_type: Optional[str] = None,
uploader_id: Optional[int] = None,
start_date: Optional[str] = None,
end_date: Optional[str] = None
) -> Tuple[List[UploadedFile], int]:
"""获取文件列表"""
query = db.query(UploadedFile).filter(UploadedFile.is_deleted == 0)
# 关键词搜索
if keyword:
query = query.filter(
or_(
UploadedFile.original_name.like(f"%{keyword}%"),
UploadedFile.file_name.like(f"%{keyword}%")
)
)
# 文件类型筛选
if file_type:
query = query.filter(UploadedFile.file_type == file_type)
# 上传者筛选
if uploader_id:
query = query.filter(UploadedFile.uploader_id == uploader_id)
# 日期范围筛选
if start_date:
start = datetime.strptime(start_date, "%Y-%m-%d")
query = query.filter(UploadedFile.upload_time >= start)
if end_date:
end = datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)
query = query.filter(UploadedFile.upload_time < end)
# 获取总数
total = query.count()
# 分页
items = query.order_by(desc(UploadedFile.upload_time)).offset(skip).limit(limit).all()
return items, total
def update(self, db: Session, *, db_obj: UploadedFile, obj_in: Dict[str, Any]) -> UploadedFile:
"""更新文件记录"""
for field, value in obj_in.items():
setattr(db_obj, field, value)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
return db_obj
def delete(self, db: Session, *, db_obj: UploadedFile, deleter_id: int) -> UploadedFile:
"""软删除文件"""
db_obj.is_deleted = 1
db_obj.deleted_at = datetime.utcnow()
db_obj.deleted_by = deleter_id
db.add(db_obj)
db.commit()
db.refresh(db_obj)
return db_obj
def delete_batch(self, db: Session, *, file_ids: List[int], deleter_id: int) -> int:
"""批量删除文件"""
now = datetime.utcnow()
count = db.query(UploadedFile).filter(
and_(
UploadedFile.id.in_(file_ids),
UploadedFile.is_deleted == 0
)
).update({
"is_deleted": 1,
"deleted_at": now,
"deleted_by": deleter_id
}, synchronize_session=False)
db.commit()
return count
def increment_download_count(self, db: Session, *, file_id: int) -> int:
"""增加下载次数"""
file_obj = self.get(db, file_id)
if file_obj:
file_obj.download_count = (file_obj.download_count or 0) + 1
db.add(file_obj)
db.commit()
return file_obj.download_count
return 0
def generate_share_code(self, db: Session, *, file_id: int, expire_days: int = 7) -> str:
"""生成分享码"""
import secrets
import string
file_obj = self.get(db, file_id)
if not file_obj:
return None
# 生成随机分享码
alphabet = string.ascii_uppercase + string.ascii_lowercase + string.digits
share_code = ''.join(secrets.choice(alphabet) for _ in range(16))
# 设置过期时间
expire_time = datetime.utcnow() + timedelta(days=expire_days)
# 更新文件记录
self.update(db, db_obj=file_obj, obj_in={
"share_code": share_code,
"share_expire_time": expire_time
})
return share_code
def get_statistics(
self,
db: Session,
*,
uploader_id: Optional[int] = None
) -> Dict[str, Any]:
"""获取文件统计信息"""
# 基础查询
query = db.query(UploadedFile).filter(UploadedFile.is_deleted == 0)
if uploader_id:
query = query.filter(UploadedFile.uploader_id == uploader_id)
# 总文件数和总大小
total_stats = query.with_entities(
func.count(UploadedFile.id).label('count'),
func.sum(UploadedFile.file_size).label('size')
).first()
# 文件类型分布
type_dist = query.with_entities(
UploadedFile.file_type,
func.count(UploadedFile.id).label('count')
).group_by(UploadedFile.file_type).all()
type_distribution = {file_type: count for file_type, count in type_dist}
# 今日上传数
today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
upload_today = query.filter(UploadedFile.upload_time >= today_start).count()
# 本周上传数
week_start = today_start - timedelta(days=today_start.weekday())
upload_this_week = query.filter(UploadedFile.upload_time >= week_start).count()
# 本月上传数
month_start = today_start.replace(day=1)
upload_this_month = query.filter(UploadedFile.upload_time >= month_start).count()
# 上传排行
uploader_ranking = query.with_entities(
UploadedFile.uploader_id,
func.count(UploadedFile.id).label('count')
).group_by(UploadedFile.uploader_id).order_by(desc('count')).limit(10).all()
# 转换为人类可读的文件大小
total_size = total_stats.size or 0
total_size_human = self._format_size(total_size)
return {
"total_files": total_stats.count or 0,
"total_size": total_size,
"total_size_human": total_size_human,
"type_distribution": type_distribution,
"upload_today": upload_today,
"upload_this_week": upload_this_week,
"upload_this_month": upload_this_month,
"top_uploaders": [{"uploader_id": uid, "count": count} for uid, count in uploader_ranking]
}
@staticmethod
def _format_size(size_bytes: int) -> str:
"""格式化文件大小"""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if size_bytes < 1024.0:
return f"{size_bytes:.2f} {unit}"
size_bytes /= 1024.0
return f"{size_bytes:.2f} PB"
# 创建CRUD实例
uploaded_file = CRUDUploadedFile()