fastapi: 完整的文件上传,前端网络库用jquery
一,代码
资源model
# -*- coding: utf-8 -*-
#
# 附件管理
#
# app/models/assets.py
from app.models.mybasemodel import MyBaseModel
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import String, event, BigInteger, text, select, exists
from app.utils.MakeCoder import MakeCoder
# 文件资源模型
class Asset(MyBaseModel):
__tablename__ = "assets"
# 编号
serial_no: Mapped[str] = mapped_column(String(50),nullable=False,unique=True,default='', server_default="''", comment='编号')
# 文件标题
title: Mapped[str] = mapped_column(String(200), nullable=False, default='', server_default="''",comment='标题')
# 文件名
file_name: Mapped[str] = mapped_column(String(200), nullable=False, default='', server_default="''",comment='原文件名')
# 文件路径
file_path: Mapped[str] = mapped_column(String(200), nullable=False, default='', server_default="''",comment='文件路径')
# 完整路径
full_path: Mapped[str] = mapped_column(String(500), nullable=False, default='', server_default="''",comment='完整路径')
# 文件后缀
ext: Mapped[str] = mapped_column(String(200), nullable=False, default='', server_default="''", comment='文件后缀')
# 文件大小
size: Mapped[BigInteger] = mapped_column(BigInteger, nullable=False, default=0, server_default=text('0'), comment='文件大小')
# 宽
width: Mapped[BigInteger] = mapped_column(BigInteger, nullable=False, default=0, server_default=text('0'), comment='宽')
# 高
height: Mapped[BigInteger] = mapped_column(BigInteger, nullable=False, default=0, server_default=text('0'), comment='高')
# 用户ID
user_id: Mapped[BigInteger] = mapped_column(BigInteger, nullable=False, default=0, server_default=text('0'), comment='用户ID') # 用户ID;
@staticmethod
def on_before_insert(mapper, connection, target):
while True:
# 自动生成编号
sn = MakeCoder.make_serial_no('A')
stmt = select(exists().where(target.__class__.serial_no == sn))
# 利用事件自带的同步 connection 执行查询
is_exists = connection.scalar(stmt)
if not is_exists:
target.serial_no = sn
break
# 监听事件
event.listen(Asset, 'before_insert', Asset.on_before_insert)
接口:
@router.post("/upload")
async def upload_single_file(
request: Request,
file: UploadFile = File(...),
# 挂载强制登录依赖,未登录的用户根本进不来
current_user: dict = Depends(admin_login_required),
db: AsyncSession = Depends(get_db)
):
# 拼接本地保存路径
upload_dir = '/data/file/fast/images'
# 得到目录
now = datetime.datetime.now()
formatted_date = now.strftime("%Y%m%d")
dir1 = upload_dir+"/"+formatted_date
# 创建目录1
ensure_dir(dir1)
formatted_time = now.strftime("%H%M")
dir2 = dir1 + "/" + formatted_time
# 创建目录1
ensure_dir(dir2)
dir_path = formatted_date+'/'+formatted_time
full_path = "/"+dir_path+"/"+file.filename
file_path = upload_dir+full_path
# 打开本地文件并将上传的文件内容复制进去
with open(file_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
# 得到用户的信息
user_id = getattr(request.state, "id", 0)
# 得到文件的信息
file_info = get_file_info(file_path)
info = file_info['data']
# 得到目录
# 创建新文件对象
new_asset = Asset(
title='',
file_name=info['file_name'],
file_path=dir_path,
full_path=full_path,
ext=info['ext'],
size=info['size'],
width=info['width'],
height=info['height'],
status=1,
user_id=user_id,
)
# 保存到assets表中
try:
# 添加到数据库
db.add(new_asset)
await db.commit() # 提交事务
await db.refresh(new_asset) # 刷新,获取数据库生成的字段(如id)
except Exception as err:
await db.rollback()
stack = traceback.format_exc()
mylogger.bind(log_type="system").info(f'注册用户失败: {err}')
mylogger.bind(log_type="system").exception(stack)
raise HTTPException(
status_code=400,
detail="注册用户失败"
)
return JSONResponse({"status_code": 200, "msg": "文件上传成功", "data": {
"id": new_asset.id,
}})
def ensure_dir(directory):
"""确保目录存在,如果不存在则创建"""
if not os.path.exists(directory):
os.makedirs(directory)
html页面:
{% extends "layout/admin.html" %}
{% block title %}{{ title }}{% endblock %}
<script src="{{ url_for('static', path='js/jquery/jquery.form.min.js') }}"></script>
<script src="{{ url_for('static', path='js/jquery/jquery.validate.min.js') }}"></script>
<script type="text/javascript">
</script>
{% block layout_js %}
<script type="text/javascript">
function select_avatar() {
document.getElementById('uploadInput').click();
}
imagePreview = document.getElementById('imagePreview')
document.getElementById('uploadInput').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
// 文件读取成功后触发
reader.onload = function(e) {
imagePreview.src = e.target.result; // e.target.result 就是 Base64 URL
imagePreview.style.display = 'block';
};
// 将文件读取为 DataURL (Base64)
reader.readAsDataURL(file);
var formData = new FormData();
formData.append('file', file); // 将文件添加到FormDat
var upload_url = "/admin/assets/upload";
//上传选中的图片
$.ajax({
url:upload_url,
type: "post",
data: formData,
contentType: false,
processData: false,
success: function(data){
console.log('success');
console.log(data.data.id)
alert('上傳成功!:'+data.data.id);
},
error: function(xhr, status, error) {
console.error('文件上传失败');
console.error(error); // 输出错误信息
}
});
}
});
</script>
{% endblock %}
<!-- 填充核心内容块 -->
{% block content %}
<div>当前用户:{{current_user.username}}:</div>
<form id="info-form" action="{{ url_for('post_info') }}" method="POST">
<div class="col-sm-3">
<label class="col-sm-2 col-form-label text-center">头像</label>
<div class="col-sm-10">
<input id="thumb_id" name="thumb_id" value="" type="hidden">
<div class="btn btn-primary" id="thumb-uploader" onclick="select_avatar()">
<i class="bi bi-upload"></i> 上传图片
</div><br/>
<span class="form-text">
<i class="bi bi-info-circle"></i> 1:1 方形图片,用于展示话题列表时显示的头像
</span>
<div id="thumb-uploader-list" class="upload-list mt-3">
<input type="file" id="uploadInput" accept="image/*" style="display:none;">
{% if current_user.avatar_url %}
<img id="imagePreview" src="{{ current_user.avatar_url }}" alt="图片预览" style="max-width: 200px;max-height: 200px; ">
{% else %}
<img id="imagePreview" src="{{ current_user.avatar_url }}" alt="图片预览" style="max-width: 200px;max-height: 200px; display: none;">
{% endif %}
</div>
</div>
</div>
<div class="col-sm-3">
<label class="form-label" for="system_name">昵称</label>
<input type="text" class="form-control" name="system_name" value="{{ current_user.nickname }}" placeholder="">
</div>
<button class="w-100 btn btn-lg btn-primary" type="submit">保存</button>
</form>
{% endblock %}
{% block templates %}
{% endblock %}
二,测试效果 :

浙公网安备 33010602011771号