Мне нужна помощь в сохранении MultipartFile весной MVC на сетевой диск SMB

Я пытаюсь сохранить MultipartFile (загруженный в контроллер Spring MVC) на сервер Linux в сети (требуется аутентификация). Я попробовал smb с помощью jcifs, но производительность очень низкая.

Может ли кто-нибудь указать мне альтернативный способ сделать это? Я искал везде в течение 2 дней и не смог найти решение, которое работает.

Сервер приложений работает под управлением Linux.

Обновлено: это код, который я использую. Код работает, но работает очень, очень плохо

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbFile;
import jcifs.smb.SmbFileInputStream;
import jcifs.smb.SmbFileOutputStream;

@Component
public class FileUploadUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(FileUploadUtil.class);

    @Value("${dr.fileuser}")
    private String user;

    @Value("${dr.filepwd}")
    private String pass;

    @Value("${dr.sharePath}")
    private String drSharePath;

    @Value("${dr.fileLocation}")
    private String drFileLocation;

    @Value("${dr.serverName}")
    private String drServerName;


    /**
     * @param uploadedFile
     * @param filename
     * @param discId: this is a generated id, used to associate these files with a database record.
     * @return
     * @throws WW_Exception
     * @throws IOException
     */
    public String writeFileToServer(MultipartFile uploadedFile, String filename, Integer discId) throws WW_Exception, IOException   {

        NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication("",user, pass);

        String destDir = StringUtils.cleanPath("smb://" + drServerName + drSharePath + drFileLocation + discId + "/");

        LOGGER.info("FILE Destination DIR: {}", destDir);
        try{
            //create directory structure
            SmbFile sfileDir = new SmbFile(destDir, auth);
            if (!sfileDir.exists()) {
                sfileDir.mkdirs();
            }

            String destFilePath = StringUtils.cleanPath(destDir + filename);
            LOGGER.info("FILE Destination PATH: {}", destFilePath);
            SmbFile sfile = new SmbFile(destFilePath, auth);
            try (SmbFileOutputStream fos = new SmbFileOutputStream(sfile)){
                fos.write(uploadedFile.getBytes());
            }

            return destFilePath.replace("smb:", "");

        } catch(Exception e){
            throw e;
        }

    }

    /**
     * @param drId: this is a generated id, used to associate these files with a database record.
     * @param origFilePath
     * @return
     * @throws IOException
     */
    public String copyFileFromServer(Integer drId, String origFilePath) throws  IOException   {

        LOGGER.info("FILE to get: {}",origFilePath);

        NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication("",user, pass);

        String[] imagePathInfo = origFilePath.split("/");
        String destFilePath = StringUtils.cleanPath("/weblogs/tmp/" + drId + "/" + imagePathInfo[imagePathInfo.length-1]);

        File destDir = new File(StringUtils.cleanPath("/weblogs/tmp/" + drId + "/"));
        if (!destDir.exists()) {
            destDir.mkdirs();
        }

        SmbFile origFile = new SmbFile(origFilePath,auth);
        try(InputStream in = new SmbFileInputStream(origFile)) {
             Files.copy(in, Paths.get(destFilePath), StandardCopyOption.REPLACE_EXISTING);
        }

        return destFilePath;

    }
}

Привет @Jorge, у тебя есть root-доступ на рассматриваемом сервере?

Paul Warren 20.04.2019 01:19

Кроме того, вы пытаетесь связать этот загруженный контент с объектами Spring Data или чем-то подобным или просто сохраняете файл по известному пути на сервере для последующего извлечения?

Paul Warren 20.04.2019 01:20

Привет @PaulWarren, я могу получить root-доступ к серверу. Я просто пытаюсь сохранить файл для последующего поиска. Прямо сейчас использование jcifs занимает около 8 секунд для хранения файла размером 8 МБ, что слишком долго. Спасибо!

Jorge 20.04.2019 04:40
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
3
1 919
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Пожалуйста, проверьте, где вы пытаетесь сохранить MultipartFile, этот файл имеет разрешение на чтение и запись на Linux-машине.

Привет @iragond, я только что обновил пост. Код не дает сбоев, он просто работает очень плохо.

Jorge 20.04.2019 17:34
Ответ принят как подходящий

@ Хорхе,

Основываясь на ваших комментариях выше, поскольку у вас есть root-доступ к серверу Linux, все, что вам нужно сделать, это обычное старое монтирование ядра. Вам нужно будет убедиться, что вы установили клиент cifs (при условии, что Ubuntu):

$ sudo apt install -y cifs-utils

Затем вы сможете смонтировать свою долю с помощью чего-то вроде:

$ SMB_USERNAME=<your username>
$ SMB_PASSWORD=<your password>
$ SMB_SERVER = "//<your host>/<your share>"
$ sudo mount -t cifs -o username=${SMB_USERNAME},password=${SMB_PASSWORD} \
       "${SMB_SERVER}" /mnt

Тогда вариант 1 будет состоять в том, чтобы изменить ваш код для чтения и записи файлов в смонтированный каталог; то есть /mnt в этом случае.

Или, вариант 2, будет использовать проект сообщества под названием Весенний контент. Это обеспечивает абстракцию над хранилищем для обработки ресурсов и может внедрить код контроллера и службы для вас, чтобы вам не нужно было писать его самостоятельно. Одним из поддерживаемых модулей хранения является модуль хранилища файловой системы, и вы должны настроить его для чтения и записи файлов в ваш локальный каталог /mnt, который на самом деле является вашим удаленным общим ресурсом.

Итак, если вы добавили Spring Content в свой проект, вы можете удалить весь код своего контроллера и не беспокоиться о деталях реализации. Кроме того, поскольку Spring Content является абстракцией, в будущем вы также можете перейти на любой другой носитель данных, поддерживаемый Spring Content; С3 например. Добавление будет выглядеть примерно так:

pom.xml (assuming maven. Spring boot starters also available)

    <!-- Java API -->
    <!-- just change this depdendency if you want to store somewhere else -->
    <dependency>
        <groupId>com.github.paulcwarren</groupId>
        <artifactId>spring-content-fs</artifactId>
        <version>0.7.0</version>
    </dependency>
    <!-- REST API -->
    <dependency>
        <groupId>com.github.paulcwarren</groupId>
        <artifactId>spring-content-rest</artifactId>
        <version>0.7.0</version>
    </dependency>

StoreConfig.java

@Configuration
@EnableFilesystemStores
@Import(RestConfiguration.class)
public class StoreConfig {

    @Bean
    FileSystemResourceLoader fileSystemResourceLoader() throws IOException {
        return new FileSystemResourceLoader(new File("/mnt").getAbsolutePath());
    }

}

FileStore.java

  @StoreRestResource(path = "files")
  public interface FileStore extends Store<String> {
  }

Вот и все. FileStore — это, по сути, универсальный Spring ResourceLoader. Зависимость spring-content-fs заставит Spring Content внедрить реализацию на основе файловой системы, поэтому вам не нужно беспокоиться о ее реализации самостоятельно. Более того, зависимость spring-content-rest приведет к тому, что Spring Content также внедрит реализацию, если @Controller перенаправляет HTTP-запросы в метод FileStore.

Таким образом, теперь у вас будет полнофункциональная (POST, PUT, GET, DELETE) файловая служба на основе REST в /files, которая будет использовать ваш FileStore для извлечения (и хранения) файлов в /mnt; то есть на вашем удаленном SMB-сервере.

Так:

GET /files/some/file.csv

будет скачивать file.csv из /path/to/your/files/some/.

А также...

curl --upload-file some-other-file.csv /files/some-other-file.csv

загрузит some-other-file.csv и сохранит его в /mnt/ на вашем сервере.

А также:

curl /files/some-other-file.csv

получит его снова.

ХТН

Введенный контроллер также поддерживает потоковое видео, если это полезно.

спасибо, @PaulWarren... Я попробую и опубликую результаты.

Jorge 21.04.2019 16:18

есть ли способ указать подкаталог во время загрузки файла с помощью Spring Content? Некоторые из наших загрузок основаны на идентификаторе строки и должны поместить все связанные файлы в один и тот же подкаталог.

Jorge 26.04.2019 21:39

@ Хорхе, да. если вы загрузили в /files/a/b/some-other-file.csv, то он будет сохранен в ``/mnt/a/b/some-other-file.csv`

Paul Warren 26.04.2019 23:01

Другие вопросы по теме