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

SpringBoot 接口怎么传数据库中的 BLOB 图片给前端

  •  1
     
  •   sandman511 · 2020-09-25 09:18:08 +08:00 · 3915 次点击
    这是一个创建于 1549 天前的主题,其中的信息可能已经有所发展或是发生改变。

    单张或者多张的 求大佬讲个方案 现在是能传 但想要个最佳方案。。虽然数据库存图片就离谱。。。。

    26 条回复    2020-09-26 21:00:11 +08:00
    RedBeanIce
        1
    RedBeanIce  
       2020-09-25 09:20:42 +08:00
    都 springboot 了,数据库还存图片的二进制吗?
    myCupOfTea
        2
    myCupOfTea  
       2020-09-25 09:22:56 +08:00
    数据库存图片确实离谱
    muskill
        3
    muskill  
       2020-09-25 09:22:59 +08:00
    存 base64 呢
    lower
        4
    lower  
       2020-09-25 09:27:28 +08:00
    现在的方案是什么样的呢?
    napsterwu
        5
    napsterwu  
       2020-09-25 09:27:49 +08:00 via iPhone   ❤️ 1
    blob 直接当成文件下载送出去,前端直接用 img src 拿
    monkeyWie
        6
    monkeyWie  
       2020-09-25 09:28:00 +08:00
    提供一个接口,然后以字节流的方式写出图片不就行了吗,注意设置好正确的 Content-Type 就行
    yazinnnn
        7
    yazinnnn  
       2020-09-25 09:44:40 +08:00
    说实话,数据库存图片挺扯淡的
    fhsan
        8
    fhsan  
       2020-09-25 09:49:35 +08:00
    http body stream
    charten
        9
    charten  
       2020-09-25 09:56:19 +08:00   ❤️ 1
    我从前端的角度谈一下,我们这边请求 blob 实际上就是请求二进制流,跟普通的请求没什么区别。你只要把图片的二进制数据放进 body 就可以了。另外,如果有请求多张的情况,可以跟前端约定好一个二进制格式,比如以某个字符,比如 0x3b 之类的作为文件的开头,后面加 4 个字节用于表示图片的二进制长度:
    0x3b 0x00 0x00 0x11 0xff <图片数据....4607 个字节>
    0x3b 0x00 0x00 0x22 0xff <图片数据...8959 个字节>
    ...
    securityCoding
        10
    securityCoding  
       2020-09-25 10:02:57 +08:00
    不要在错误的路上越走越远 , 掉头吧
    wysnylc
        11
    wysnylc  
       2020-09-25 10:16:40 +08:00
    调头,OSS
    justlikemaki
        12
    justlikemaki  
       2020-09-25 10:34:31 +08:00
    敏感图片你们也存 oss ?
    crclz
        13
    crclz  
       2020-09-25 10:54:05 +08:00   ❤️ 1
    @GetMapping("{id}")
    public ResponseEntity<byte[]> getFile(@PathVariable long id) {
    var blob = blobRepository.findById(id).orElse(null);
    return ResponseEntity.ok()
    .contentType(MediaType.IMAGE_JPEG)
    .body(blob.getData());
    }
    spacebound
        14
    spacebound  
       2020-09-25 11:19:15 +08:00
    @monkeyWie +1,前端直接不用处理,调接口就行了
    0x666666
        15
    0x666666  
       2020-09-25 11:28:03 +08:00
    都 0202 年了,微服务都用上了,数据库还存图片?
    tanranran
        16
    tanranran  
       2020-09-25 11:28:44 +08:00
    base64
    radiocontroller
        17
    radiocontroller  
       2020-09-25 11:30:32 +08:00
    专业的事交给专业的服务
    zliea
        18
    zliea  
       2020-09-25 11:32:45 +08:00
    InputStreamResource resource = new InputStreamResource(gridFSDBFile.getInputStream(),
    gridFSDBFile.getFilename());

    return (ResponseEntity<Resource>) ResponseEntity
    .ok()
    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + gridFSDBFile.getFilename() + "\"")
    .contentLength(gridFSDBFile.getLength())
    .contentType(MediaType.APPLICATION_OCTET_STREAM)
    .body(resource);
    zliea
        19
    zliea  
       2020-09-25 11:33:37 +08:00   ❤️ 1
    如果是 byte[], 可以使用 ByteArrayResource
    zjsxwc
        20
    zjsxwc  
       2020-09-25 11:46:19 +08:00
    还不如图片加密后文件保存本地,mysql 存图片解密密码,需要的时候通过密码和加密后的文件得到原图返回给客户端
    hejingyuan199
        21
    hejingyuan199  
       2020-09-25 15:27:58 +08:00
    虽然我不知道如何回答。
    但我想附和一下,我也是把图片存数据库的。

    我现在的项目是个测试性项目,不是正规使用。
    我们的数据库供所有开发人员同时使用的。

    不同的开发人员有不同的功能,功能互不干涉,但是共用一个数据库和表。
    如果图片存在服务器本地,数据库只存图片名称,那大家各自在测试时都拿不到图片。
    所以我把图片转成 Base64 字符串存进数据库了。
    还有的表里存着 Blob 类型的用户上传的附件(文件或图片)。

    一直看到大家说大文件不应该存数据库,
    也许未来转为生产环境时候再改吧。

    如果数据库存着 Base64 字符串,往前端发送应该比较简单了吧。
    Blob 我看到我们的这边用的是,
    (代码风格很渣,随便接受 Critics )

    /***** Student Chose One Application*****/
    @RequestMapping(value="/history/{ticketNum}", method=RequestMethod.GET)
    public String historyApplication(@PathVariable String ticketNum, Model model, HttpServletRequest request) {
    String search = (String) request.getSession().getAttribute("searchTicketNum");
    Optional<Application> app = applicationService.findApplication(ticketNum);
    if (app.isPresent()) {
    model.addAttribute("applicationObj", app.get());
    Optional<User> user = AIBTuserdetailsService.findByUserName(app.get().getCreatedBy());
    model.addAttribute("userObj",user.get());
    model.addAttribute("urlTicketNum", ticketNum);

    model.addAttribute("documents", applicationService.getDocumentUrl(ticketNum, app.get().getId()));
    --------------------------------------------------------------------------------------------------
    //调用 blob 类型的文件

    model.addAttribute("logs", applicationService.getAuditLog(app.get().getId()));
    //Optional<Course> course = courseService.findByCourseid(user.get().getCurrentCourseCode());
    //if(course.isPresent()) {
    // model.addAttribute("course", course.get().getCourseName());}
    //else {
    // model.addAttribute("course", "Not found");
    //}
    }
    model.addAttribute("applicationList", applicationService.findApplications(search));
    return "student/history";
    }

    In ApplicationService.java

    public Map<String, String> getDocumentUrl(String ticketNum, int applicationId) {
    Map<String, String> result = new HashMap<>();
    for (String fileName : documentRepository.findAllFileNameByApplicationId(applicationId)) {
    ---------------------------------------------------------------------------------
    //get document out from Database

    if (!fileName.isEmpty()) {
    try {
    result.put(fileName, ticketNum + "/" + URLEncoder.encode(fileName, String.valueOf(StandardCharsets.UTF_8)));
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    }
    }
    }
    if (result.isEmpty()) {
    return null;
    } else {
    return result;
    }
    }

    In DocumentRepository.java

    public interface DocumentRepository extends JpaRepository<Document, Integer> {
    Optional<Document> findByFileNameAndApplicationId(String fileName, int applicationId);
    @Query(value="select file_name from document where application_id =:application_id", nativeQuery=true)
    List<String> findAllFileNameByApplicationId(@Param("application_id") int applicationId);
    }


    //This is parts of Class Document

    @Entity
    @Table(name = "Document")
    public class Document {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private int applicationId;
    private String fileName;
    private String fileType;
    @Lob
    private byte[] data;
    ...
    }

    仅供参考
    这个流程似乎就是前面一些 V 友说的以文件下载方式传到前端的。

    前端是这样的:
    <div class="three">
    <p>
    <b>Attachment:</b>
    </p>
    <div class="links">
    <a target="_blank" th:each="element : ${documents}"
    th:href="@{/student/downloadFile/{url}(url=${element.value})}"><img th:src="@{/image/qubiezhen.png}">[[${element.key}]]</a>
    </div>
    </div>
    hejingyuan199
        22
    hejingyuan199  
       2020-09-25 15:33:30 +08:00
    @hejingyuan199

    补上最关键的数据传输

    @RequestMapping(value="/downloadFile/{ticketNum}/{fileName}", method=RequestMethod.GET)
    public ResponseEntity<Resource> downloadFile(@PathVariable String ticketNum, @PathVariable String fileName) {
    // Load file from database
    Optional<Document> doc = applicationService.getDocument(ticketNum, fileName, Boolean.FALSE);

    if (doc.isPresent()) {
    return ResponseEntity.ok()
    .contentType(MediaType.parseMediaType(doc.get().getFileType()))
    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + doc.get().getFileName() + "\"")
    .body(new ByteArrayResource(doc.get().getData()));
    } else {
    return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
    }
    bxd5812127
        23
    bxd5812127  
       2020-09-25 16:15:34 +08:00
    后端给前端提供两个接口,一个拿所有图片的 id,一个根据 id 拿一个图片,前端根据 id 排队拿图片有没有搞头,拿完一个在拿一个。
    THESDZ
        24
    THESDZ  
       2020-09-25 16:34:35 +08:00
    后端应该提供的能力是 <img src="url"/> 的这个 url,内部实现可以直接通过 resp 传输流
    hyperbin
        25
    hyperbin  
       2020-09-26 11:56:53 +08:00 via Android
    @charten 怎么保证图片里没有 0x3b ?
    charten
        26
    charten  
       2020-09-26 21:00:11 +08:00
    @hyperbin 不用保证呀,又不是拿 0x3b 去当分割符,0x3b 只是一个标识符,标识符一个块的开始,这个标识符可以 0x00 到 0xff 的任意一个字符,关键是紧跟着的后四位字节,代表了这个块的长度,你真正要拿图片要通过这个长度去拿,就是 slice 方法。当然也可以根据这个长度跳过去拿下一张图片。psd 文件的二进制格式基本上都是这样的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   880 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 19:59 · PVG 03:59 · LAX 11:59 · JFK 14:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.