V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
9fan
V2EX  ›  程序员

springboot web 大表单多文件接口设计问题

  •  
  •   9fan · 2024-03-11 14:39:58 +08:00 · 2399 次点击
    这是一个创建于 367 天前的主题,其中的信息可能已经有所发展或是发生改变。

    springboot web 项目,现有一个要求,一个创建项目表单中,有十几个字段,其中有些是文件,而且不同文件之间是有不同的,意思是后端必须知道哪个对应哪个文件,业务要求,没办法,现在需要设计这个接口,麻烦问下,有什么好的解决方案。 方案一:使用 POST requestBody body 中让前端把文件作为 byte[]进行传递,应该是可以的

    不知道大家有没有比较高效的方案

    42 条回复    2024-03-12 14:25:47 +08:00
    wuyiccc
        1
    wuyiccc  
       2024-03-11 14:41:59 +08:00
    单独提供文件转 url 接口,表达字段填文件的 url
    9fan
        2
    9fan  
    OP
       2024-03-11 14:44:55 +08:00
    设计系统的只支持上传项目内的文件,不被允许此类调用。意思是系统内的文件上传必须携带项目相关的信息,领导设计如此,反驳几次无法撼动,而且文件上传是他所写,所以协调不了
    thevita
        3
    thevita  
       2024-03-11 14:51:36 +08:00
    嗯,,啥意思,是我没理解对么,“后端必须知道哪个对应哪个文件” 是啥意思,就是后端能上传多个文件?,,

    这本来就可以啊,挑战在哪??
    vagusss
        4
    vagusss  
       2024-03-11 14:53:05 +08:00
    上传接口单独做,返回文件 id 或者路径之类的, 然后最后统一和参数一起提交.
    vagusss
        5
    vagusss  
       2024-03-11 14:57:58 +08:00
    @vagusss 这样你还可以给文件上传搞个进度条之类的, 上传的过程中不影响填写其他表单参数
    cheng6563
        6
    cheng6563  
       2024-03-11 14:58:16 +08:00
    multipart/form-data 不是本来就能传多个文件多个参数。
    unclema
        7
    unclema  
       2024-03-11 15:04:00 +08:00
    主流方案不都是文件服务都是单独去处理吗
    1. 上传到本地文件服务器或 OSS 中去
    2. 上传接口后端处理完成后返回文件地址
    3. 表单提交的时候存储文件 ID 或者这个文件的地址
    4. 预览的时候根据 ID 或地址去拿就行了哇
    后端必须知道哪个对应哪个文件: 每个类型的文件单独一个字段不就可以了吗.
    9fan
        8
    9fan  
    OP
       2024-03-11 15:04:49 +08:00
    @cheng6563 multipart/form-data 是可以上传多个文件,但是文件无法进行业务上的区分。
    @vagusss 如果单独做,那这种临时文件需要单独去开发,去做存储。还要考虑后期定时删除废文件
    我的意思能否有优雅的设计方案
    wxb2dyj
        9
    wxb2dyj  
       2024-03-11 15:08:17 +08:00   ❤️ 2
    说实话,我建议你先学会如何描述问题。你这描述怎么跟领导沟通清楚
    thevita
        10
    thevita  
       2024-03-11 15:09:06 +08:00
    @9fan
    嗯.......

    你这里的 “文件无法进行业务上的区分” 是啥意思, 是上面说的 “哪个对应哪个文件”,没问题啊

    给个 curl 你试试就知道了: curl http://localhost/upload -Ffile1=@log [email protected]

    不知道是不是理解错了,我感觉你被网上的信息误导了
    9fan
        11
    9fan  
    OP
       2024-03-11 15:10:10 +08:00
    @unclema 是的,目前就是每个类型的文件对应一个字段这样做的。主流方案大家都赞同,由于领导个人影响力太强,我们无法更改,前端一直骂娘,一个文件上传调 6 个接口,我们没办法决定
    9fan
        12
    9fan  
    OP
       2024-03-11 15:16:17 +08:00
    目前是 POST http://localhost/project/create (@ModelAttribute ProjectCreateRequest request)

    request {
    field1,
    field2,
    field3,
    ......
    // 领导立项文件
    List<FileDto> file1;
    // 领导会议纪要
    List<FileDto> file2;
    // 其余补充文件
    List<FileDto> file3;
    }

    类似这样的,由于文件太多,且这种会慢,有没有更好的方法
    limaofeng
        13
    limaofeng  
       2024-03-11 15:17:06 +08:00
    @wxb2dyj 真相了
    @9fan 这有啥好纠结的,这么被动,领导设计,你无法撼动,而又没有能力说服领导采纳你的方案(前提你得有),那还有啥好商量的,领导咋说你咋干不就成了。不懂问领导,即使再沙壁的方案,也是正道
    9fan
        14
    9fan  
    OP
       2024-03-11 15:17:21 +08:00
    感觉这下子应该说清楚了,下次还是应该直接上 demo
    zpf124
        15
    zpf124  
       2024-03-11 15:21:28 +08:00
    “是可以上传多个文件,但是文件无法进行业务上的区分”

    你们是不会给 MultipartFile 加 @ RequestParam 或者 @ RequestPart 注解? 还是说不知道 这个注解可以指定 key ?
    9fan
        16
    9fan  
    OP
       2024-03-11 15:22:52 +08:00
    @zpf124 文件个数是不确定的
    xiangyuecn
        17
    xiangyuecn  
       2024-03-11 15:28:53 +08:00
    这不是设计问题,是压根没有设计😅
    limaofeng
        18
    limaofeng  
       2024-03-11 15:31:51 +08:00
    @9fan 一个请求附带 N 多文件,而不是单独上传后的文件 ID 。那这个表单到 controller 一定比 只携带文件 ID 的请求慢。这个没法解决。
    如果文件单独上传。在客户填写表单时,文件已经上传了。而且为了容错。可以支持多个文件同时上传,端点续传等功能。
    但如果非要和业务表单同时上传文件。这个表单就是所有文件要传输的大小。你网络好,文件大的不是很过分,也没啥问题。


    所以你的问题,更本就不是你能解决的。你无法改变方案。

    "文件个数是不确定的" ? 这个影响吗? 真正影响速度慢的是文件大小以及网络。 "文件个数是不确定的" 只影响你后端如何处理这个表单中的文件 key 按什么规则匹配到业务场景
    9fan
        19
    9fan  
    OP
       2024-03-11 15:33:00 +08:00
    @xiangyuecn 你真懂,我们都这么觉得的,小伙子,有无好的方案呀,贡献一波,一看你就是大佬,好让我来喷一下领导,虽然我一直赞同文件服务不管业务,只管上传,以及中间增加过滤规则拦截规则之类的
    9fan
        20
    9fan  
    OP
       2024-03-11 15:39:32 +08:00
    @limaofeng 可否先让后端业务这边单独写个文件暂存的功能返回文件 key ,其实不走文件服务,后续在提交表单时只传与这个表单对应的 key ,这里头可否有问题,比如怎么知道是这个表单的文件 key ,而不是别的表单的文件 key ,后期当前表单提交后,找出临时文件 key 的文件去调用文件服务去上传,最后再删除这个临时表的文件 key 及临时文件。其中的问题应该怎么解决,是不是增加了复杂性了
    doublebu
        21
    doublebu  
       2024-03-11 15:42:15 +08:00
    之前的项目是按 #7 老哥说的一样。
    关于临时文件
    1. 如果实现已经知道是临时文件,可以在保存到 S3 的时候给一个标记,比如都放到 /temp 文件夹中,定时调用 S3 的 API 删除文件夹就可以了
    2. 可以开个定时任务,扫描数据库的文件字段,然后和 S3 文件列表做对比,找不到的就删掉,会麻烦一些
    zpf124
        22
    zpf124  
       2024-03-11 15:46:27 +08:00   ❤️ 1
    @limaofeng
    @9fan

    以这楼主这描述问题的能力想说服领导真的不太现实。
    你要说服领导
    1 、你要明白他为什么这么想
    2 、你得有足够合理的方案能兼容他原有的想法,或者有足够的说服力劝他改变原想法。


    至于上传文件必须有业务信息这部分有时候是合理的,比如我参与的有的系统涉及打包导出功能,所以资源文件的存放一开始就是和业务绑定的 比如 '/data/posts/{postid}/images/cover.jpg','/data/post/{postid}/attachments/a.zip' 因为要复制对应目录就可以导出所有有用资源,所以路径中拼接了 相关的业务 id , 因为需要业务 id 自然需要先创建了业务信息。

    基于此,如果是我,我会提出这些解决方案,我个人倾向 2 > 1 > 3 > 4 。

    1 、能不能先根据 id 生成策略创建一个 业务 id ,进入页面就返回一个 id 给前端, 然后上传文件 和提交业务信息分成两步来,都使用这个 id 。 这样的缺点就是会有冗余废数据,需要额外逻辑去判断和删除,而且原本应该是一个原子性的操作如今拆成几步了可能会有 bug 需要仔细 debug 。

    2 、新写一个临时文件上传接口, 提交业务时创建信息,然后再从临时文件那复制文件到之前的上传接口就好,业务信息也有了, 临时文件目录只需要写个定时任务定期清理就好,甚至不清理也没问题,除了占空间和可能会被人当作分享图床外不会有任何副作用。 缺点除了这个临时目录也会冗余外,就是上传接口要调整一下或者新加一个支持从本地复制接口。

    3 、一个接口支持多文件上传,写起来很简单,但文件如果大了,需要调整服务器允许上传文件上限,并且会严重占用带宽。

    4 、将文件二进制 base64 发送, 除了个别接口只允许上传类似 logo 之类小文件的情况外,一遍不建议这么写。
    limaofeng
        23
    limaofeng  
       2024-03-11 15:48:49 +08:00
    @9fan 文件上传接口要纯粹,就只上传单个文件,并返回 ID 。

    所以你还是回到了 #7 的方案
    如果你想清理过期文件。可以用一张表记录已经上传的文件: 比如 ID , 状态, 上传时间, 文件实际路径。

    再业务表单里面。为文件进行一次确认操作(就是修改其状态)

    然后用个定时任务,定期查询 文件记录表中的 状态。如果超过一定时间未处理的文件。 你就可以删除它。

    这是你要的效果吗?
    seedhk
        24
    seedhk  
       2024-03-11 15:50:20 +08:00
    @9fan #20 这里有个问题就是万一表单提交成功了,临时文件丢了怎么办
    limaofeng
        25
    limaofeng  
       2024-03-11 15:53:28 +08:00
    @seedhk 他描述的临时文件,应该不是存放在系统临时文件夹中的文件。不然就唧唧
    zpf124
        26
    zpf124  
       2024-03-11 16:04:34 +08:00
    @9fan

    那就还是说单个请求太大的问题呗。

    我个人方案是 上传服务增加临时目录,用对象存储的话搞个 temp 桶 或者 /temp 目录都可以, 然后写个定时任务,每隔几个小时清理掉临时目录下所有创建时间大于任务执行间隔的文件。
    这个接口的所有文件都先上传到临时目录, 然后提交时后从临时目录复制一份到正确位置。

    这样临时文件接口也不需要数据库记那些文件被使用了,那些是作废的。
    9fan
        27
    9fan  
    OP
       2024-03-11 16:17:17 +08:00
    @seedhk 应该不可能,应该只有状态为已使用的才会被清理
    @limaofeng 是的,有问题是,如果有多人在提交表单,怎么区分临时表中文件与表单的绑定关系的,如果按照 #20 所说中的第 1 方案让前端生成一个唯一表单 id 来与临时文件表中进行绑定,我觉得可行,但是怎么保证前端生成的一定是唯一的呢,如果交由后端生成,那什么时候该生成什么时候应该返回这个表单 id 呢。还有这个方案还是没有 @zpf124 的 2 方案好,简单好维护且高效,不知意下如何。其实 @zpf124 说的是的,他主要是考虑文件打包的功能,其实系统中文件如果这样设计,那么文件上传在微服务调用会区分多个接口调用,而且对应的下载接口数量也同等,前端已经在报怨了,说系统内文件接口太多,已经不知道调用哪个了,可能是接口设计抽象能力不够吧。还有另一个问题是,项目是部署在乙方自建服务器中的,有可能会有拦截,比如请求大小,文件的校验,这个也是未知的
    9fan
        28
    9fan  
    OP
       2024-03-11 16:30:04 +08:00
    @zpf124 这个还是最好,谢谢了,下次一定描述尽量准确一点。不用记数据库,返回 url,前端就算离开这个表单,最终也是被延后几小时后删除,前端也能正确提交,唯一需要修改的是要不就在提交表单的业务接口去拉文件然后自己调用文件上传,因为是微服务,要么让其提供一个通知临时文件变永久文件的接口,文件 key 还是不变
    limaofeng
        29
    limaofeng  
       2024-03-11 16:31:51 +08:00
    @9fan 无法在继续下去了。

    "系统内文件接口太多" 我看到过很多系统,都只有一个文件上传接口(下载也只有一个)

    "怎么区分临时表中文件与表单的绑定关系",为什么会有这个问题。业务表单 file1 = 后端上传文件后生成的 FileId 。 还需要如何区分。(如果你还是不懂,那会不会是自己太菜,或者单纯只是理解与表达有些障碍)

    "还有另一个问题是,项目是部署在乙方自建服务器中的,有可能会有拦截,比如请求大小,文件的校验,这个也是未知的",这个是另一个问题,混在一起会成为解决问题的噪音。

    如果你已经在维护一座屎山了,请不要尝试任何所谓的"高效的方案",抄袭前任的方案,多问前辈。别想搞个大新闻,不然你就会成为最大的新闻。
    Belmode
        30
    Belmode  
       2024-03-11 16:33:50 +08:00
    你领导也是真的奇葩。
    如果知道字段名对应的文件类型,,,怎么不能区分业务呢?实在不行,加一个普通字段,用来区分业务也好。
    9fan
        31
    9fan  
    OP
       2024-03-11 16:45:00 +08:00
    @limaofeng "怎么区分临时表中文件与表单的绑定关系",为什么会有这个问题。业务表单 file1 = 后端上传文件后生成的 FileId : 针对这个,我想的是多个表单怎么找到自己表单的临时文件 key ,不好意思是我想叉了,我当时想的记住这个表单,下次可以重用并且适时间内表单内文件是存在的,当前这个表单是知道的没问题,但是当他关闭这个表单后,又想重新提交的情况下,其实这种情况是不需要解决的,只需要定时清理无用的临时文件即可
    9fan
        32
    9fan  
    OP
       2024-03-11 16:54:47 +08:00
    @Belmode 希望你不会不会遇到,算球,只是针对技术解决方案,不对领导与别人作出评价。
    @limaofeng 还是应该学习这位老哥的 #13 心态。保命要紧。再贴一波:这有啥好纠结的,这么被动,领导设计,你无法撼动,而又没有能力说服领导采纳你的方案(前提你得有),那还有啥好商量的,领导咋说你咋干不就成了。不懂问领导,即使再沙壁的方案,也是正道
    chendy
        33
    chendy  
       2024-03-11 16:56:03 +08:00
    问题不是很明确
    但是吧,文件可以用参数名区分,一个参数多个文件可以拿到一个文件集合
    而且既然 multipart 了,找个 part 塞个 json 也能传结构复杂的数据
    所以有什么实现不了的呢…
    levintrueno
        34
    levintrueno  
       2024-03-11 17:05:58 +08:00
    我这上传设计是一个单独接口,接收 MultipartFile 数组和一个枚举(区分业务),返回字符串 url 。然后把这个 url 放对应的字段就行。
    tedzhou1221
        35
    tedzhou1221  
       2024-03-11 17:17:08 +08:00
    上传时,按业务类型,强行给文件名称后面追加业务类型??
    例如 : 领导立项文件, 文件名为 xx.pdf , 把文件名改为 xx@@领导立项文件.pdf ,然后后端根据文件名切分
    serical
        36
    serical  
       2024-03-11 17:32:04 +08:00
    这种文件上传接口不都是单独的吗? 上传成功后返回文件唯一 ID, 再提交到后台啊
    wellerman
        37
    wellerman  
       2024-03-11 18:30:24 +08:00
    @9fan #2 后端统一成一个接口,前端选择后就上传。文件上传表里加状态做标记。最后全部提交成功, 文件状态由临时,变成成功,同时可以更新项目 ID 信息。最后状态是临时的,定期清掉。
    koloonps
        38
    koloonps  
       2024-03-11 20:02:21 +08:00
    @9fan 前端上传文件的时候修改 filename 为 uuid,后端去取数据的时候根据这个 uuid 去获取怎么样?
    NewtonLeibniz
        39
    NewtonLeibniz  
       2024-03-11 20:35:01 +08:00 via Android
    上传前端做,后端接受参数就是上传后的地址
    9fan
        40
    9fan  
    OP
       2024-03-12 11:43:03 +08:00
    @wellerman 这个也可以的,与增加临时上传接口本质差不多,你这个更加注重文件上传接口统一成一个的,对前端更加友好
    wellerman
        41
    wellerman  
       2024-03-12 12:49:00 +08:00
    @9fan 那就 multipart/form-data ,用最原始的方式上传。前端生成上传组件包含 input file 和 select 两个组件,select 用于标记这个文件的分类,整个文件列表就是一个二维数组。过去最传统做法,如果后端用 PHP ,前端就是 files[0][file] files[0][type] , files[1][file] files[1][type] 等。
    fengpan567
        42
    fengpan567  
       2024-03-12 14:25:47 +08:00
    区分每个附件对应的输入框?让前端多传个数组参数,有附件就是 1 ,没有就是 0 ,后端按数组下标给对应的字段取值,遇到 0 的就跳过不取。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1058 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 19:07 · PVG 03:07 · LAX 12:07 · JFK 15:07
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.