Cách đề xuất để lưu các tệp đã tải lên trong một ứng dụng servlet


121

Tôi đọc ở đây rằng người ta không nên lưu tệp trong máy chủ vì nó không phải là tệp di động, giao dịch và yêu cầu các tham số bên ngoài. Tuy nhiên, do tôi cần giải pháp tmp cho tomcat (7) và tôi có quyền kiểm soát (tương đối) đối với máy chủ mà tôi muốn biết:

  • Nơi tốt nhất để lưu tệp là gì? Tôi có nên lưu nó vào /WEB-INF/uploads(không nên ở đây ) hay nơi nào đó dưới $CATALINA_BASE(xem tại đây ) hay ...? Hướng dẫn JavaEE 6 lấy đường dẫn từ người dùng (: wtf :). NB: Không nên tải xuống tệp bằng bất kỳ cách nào.

  • Tôi có nên thiết lập thông số cấu hình như chi tiết ở đây không? Tôi đánh giá cao một số mã (tôi muốn cung cấp cho nó một đường dẫn tương đối - vì vậy nó ít nhất là Tomcat di động) - có Part.write()vẻ hứa hẹn - nhưng dường như cần một đường dẫn tuyệt đối

  • Tôi muốn giải thích những nhược điểm của phương pháp này so với một cơ sở dữ liệu / kho lưu trữ JCR

Thật không may là FileServlet bởi @BalusC tập trung trên các tập tin tải về, trong khi mình câu trả lời trên các tập tin tải lên bỏ qua phần về nơi để lưu các tập tin.

Giải pháp có thể chuyển đổi dễ dàng để sử dụng DB hoặc triển khai JCR (như jackrabbit ) sẽ thích hợp hơn.


Để biết cách làm cuối cùng của tôi, hãy xem câu trả lời bên dưới
Mr_and_Mrs_D

Câu trả lời:


165

Lưu trữ nó ở bất kỳ đâu trong một vị trí có thể truy cập được ngoại trừ thư mục dự án của IDE hay còn gọi là thư mục triển khai của máy chủ, vì những lý do được đề cập trong câu trả lời cho Hình ảnh đã tải lên chỉ khả dụng sau khi làm mới trang :

  1. Những thay đổi trong thư mục dự án của IDE không được phản ánh ngay lập tức trong thư mục công việc của máy chủ. Có một loại công việc nền trong IDE đảm bảo rằng thư mục công việc của máy chủ được đồng bộ hóa với các bản cập nhật mới nhất (điều này trong thuật ngữ IDE được gọi là "xuất bản"). Đây là nguyên nhân chính của vấn đề mà bạn đang gặp phải.

  2. Trong mã thế giới thực, có những trường hợp lưu trữ các tệp đã tải lên trong thư mục triển khai của ứng dụng web sẽ không hoạt động. Một số máy chủ (theo mặc định hoặc theo cấu hình) không mở rộng tệp WAR đã triển khai vào hệ thống tệp đĩa cục bộ mà thay vào đó là hoàn toàn trong bộ nhớ. Bạn không thể tạo tệp mới trong bộ nhớ mà không chỉnh sửa về cơ bản tệp WAR đã triển khai và triển khai lại.

  3. Ngay cả khi máy chủ mở rộng tệp WAR đã triển khai vào hệ thống tệp đĩa cục bộ, tất cả các tệp mới được tạo sẽ bị mất khi triển khai lại hoặc thậm chí khởi động lại đơn giản, đơn giản vì những tệp mới đó không phải là một phần của tệp WAR ban đầu.

Nó thực sự không quan trọng với tôi hay bất cứ ai khác mà chính xác trên hệ thống tập tin trên đĩa địa phương nó sẽ được lưu lại, miễn là bạn làm không bao giờ sử dụng getRealPath()phương pháp . Sử dụng phương pháp đó trong mọi trường hợp đáng báo động.

Đường dẫn đến vị trí lưu trữ lần lượt có thể được xác định theo nhiều cách. Bạn phải làm tất cả một mình . Có lẽ đây là nguyên nhân gây ra sự nhầm lẫn của bạn bởi vì bằng cách nào đó bạn đã mong đợi rằng máy chủ thực hiện tất cả điều đó một cách tự động. Xin lưu ý rằng @MultipartConfig(location)không phải chỉ định điểm đến upload cuối cùng, nhưng vị trí lưu trữ tạm thời cho kích thước hồ sơ vụ án vượt quá ngưỡng bộ nhớ lưu trữ.

Vì vậy, đường dẫn đến vị trí lưu trữ cuối cùng có thể được xác định theo một trong hai cách sau:

  • Mã cứng:

      File uploads = new File("/path/to/uploads");
  • Biến môi trường thông qua SET UPLOAD_LOCATION=/path/to/uploads:

      File uploads = new File(System.getenv("UPLOAD_LOCATION"));
  • Đối số VM trong quá trình khởi động máy chủ thông qua -Dupload.location="/path/to/uploads":

      File uploads = new File(System.getProperty("upload.location"));
  • *.propertiesmục nhập tệp như upload.location=/path/to/uploads:

      File uploads = new File(properties.getProperty("upload.location"));
  • web.xml <context-param>với tên upload.locationvà giá trị /path/to/uploads:

      File uploads = new File(getServletContext().getInitParameter("upload.location"));
  • Nếu có, hãy sử dụng vị trí do máy chủ cung cấp, ví dụ như trong JBoss AS / WildFly :

      File uploads = new File(System.getProperty("jboss.server.data.dir"), "uploads");

Dù bằng cách nào, bạn có thể dễ dàng tham khảo và lưu tệp như sau:

File file = new File(uploads, "somefilename.ext");

try (InputStream input = part.getInputStream()) {
    Files.copy(input, file.toPath());
}

Hoặc, khi bạn muốn tự động tạo một tên tệp duy nhất để ngăn người dùng ghi đè lên các tệp hiện có trùng tên trùng lặp:

File file = File.createTempFile("somefilename-", ".ext", uploads);

try (InputStream input = part.getInputStream()) {
    Files.copy(input, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}

Cách lấy parttrong JSP / Servlet đã được giải đáp trong Cách tải tệp lên máy chủ bằng JSP / Servlet? và cách lấy parttrong JSF được giải đáp trong Cách tải tệp lên bằng JSF 2.2 <h: inputFile>? Tệp được lưu ở đâu?

Lưu ý: không sử dụng Part#write()vì nó diễn giải đường dẫn liên quan đến vị trí lưu trữ tạm thời được xác định trong @MultipartConfig(location).

Xem thêm:


@MultipartConfig(location)chỉ định vị trí storge tạm thời mà máy chủ sẽ sử dụng khi kích thước tệp vượt quá ngưỡng cho bộ nhớ lưu trữ, không phải vị trí lưu trữ vĩnh viễn nơi cuối cùng bạn muốn nó được lưu trữ. Giá trị này mặc định là đường dẫn được xác định bởi thuộc tính java.io.tmpdirhệ thống. Xem thêm câu trả lời có liên quan này cho lần thử JSF không thành công: stackoverflow.com/questions/18478154/…
BalusC,

1
Cảm ơn - hy vọng tôi không nghe có vẻ ngốc nghếch nhưng trích dẫn này từ Part.write>> Điều này cho phép một triển khai cụ thể sử dụng, ví dụ: đổi tên tệp, nếu có thể, thay vì sao chép tất cả dữ liệu cơ bản, do đó đạt được lợi ích hiệu suất đáng kể cùng với một số phương thức "cắt" (so với bản sao) không xác định từ một số lib apache sẽ giúp tôi tiết kiệm cho tôi những rắc rối khi tự viết các byte - và tạo lại một tệp đã có ở đó (xem thêm ở đây )
Mr_and_Mrs_D

Có, nếu bạn đã sử dụng Servlet 3.0, bạn có thể tận dụng Part#write(). Tôi đã cập nhật câu trả lời với nó.
BalusC

Cảm ơn bạn rất nhiều vì đã cập nhật bài viết - có tài sản như vậy cho Tomcat "jboss.server.data.dir"không?
Mr_and_Mrs_D

1
Không, nó không có.
BalusC

7

Tôi đăng cách làm cuối cùng của mình dựa trên câu trả lời được chấp nhận:

@SuppressWarnings("serial")
@WebServlet("/")
@MultipartConfig
public final class DataCollectionServlet extends Controller {

    private static final String UPLOAD_LOCATION_PROPERTY_KEY="upload.location";
    private String uploadsDirName;

    @Override
    public void init() throws ServletException {
        super.init();
        uploadsDirName = property(UPLOAD_LOCATION_PROPERTY_KEY);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // ...
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        Collection<Part> parts = req.getParts();
        for (Part part : parts) {
            File save = new File(uploadsDirName, getFilename(part) + "_"
                + System.currentTimeMillis());
            final String absolutePath = save.getAbsolutePath();
            log.debug(absolutePath);
            part.write(absolutePath);
            sc.getRequestDispatcher(DATA_COLLECTION_JSP).forward(req, resp);
        }
    }

    // helpers
    private static String getFilename(Part part) {
        // courtesy of BalusC : http://stackoverflow.com/a/2424824/281545
        for (String cd : part.getHeader("content-disposition").split(";")) {
            if (cd.trim().startsWith("filename")) {
                String filename = cd.substring(cd.indexOf('=') + 1).trim()
                        .replace("\"", "");
                return filename.substring(filename.lastIndexOf('/') + 1)
                        .substring(filename.lastIndexOf('\\') + 1); // MSIE fix.
            }
        }
        return null;
    }
}

Ở đâu :

@SuppressWarnings("serial")
class Controller extends HttpServlet {

    static final String DATA_COLLECTION_JSP="/WEB-INF/jsp/data_collection.jsp";
    static ServletContext sc;
    Logger log;
    // private
    // "/WEB-INF/app.properties" also works...
    private static final String PROPERTIES_PATH = "WEB-INF/app.properties";
    private Properties properties;

    @Override
    public void init() throws ServletException {
        super.init();
        // synchronize !
        if (sc == null) sc = getServletContext();
        log = LoggerFactory.getLogger(this.getClass());
        try {
            loadProperties();
        } catch (IOException e) {
            throw new RuntimeException("Can't load properties file", e);
        }
    }

    private void loadProperties() throws IOException {
        try(InputStream is= sc.getResourceAsStream(PROPERTIES_PATH)) {
                if (is == null)
                    throw new RuntimeException("Can't locate properties file");
                properties = new Properties();
                properties.load(is);
        }
    }

    String property(final String key) {
        return properties.getProperty(key);
    }
}

và /WEB-INF/app.properties:

upload.location=C:/_/

HTH và nếu bạn tìm thấy lỗi, hãy cho tôi biết


1
Điều gì sẽ xảy ra nếu tôi muốn một giải pháp SO độc lập, hoạt động trong cả hai trường hợp (win / ux)? Tôi có phải đặt đường dẫn upload.location khác hay có một số gợi ý khác không?
pikimota, 09/07/18
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.