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 %}

二,测试效果 :

image

 

posted @ 2026-07-21 14:19  刘宏缔的架构森林  阅读(0)  评论(0)    收藏  举报