Forráskód Böngészése

Files uploading and downloading feature. +Static files

Ryan Wright 1 hónapja
szülő
commit
b213622ec1

+ 1 - 0
.gitignore

@@ -37,3 +37,4 @@ build/
 ### Mac OS ###
 .DS_Store
 /src/main/resources/application.properties
+/uploads/

+ 9 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,9 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="IgnoreResultOfCall" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="m_reportAllNonLibraryCalls" value="false" />
+      <option name="callCheckString" value="java.io.File,.*,java.io.InputStream,read|skip|available|markSupported,java.io.Reader,read|skip|ready|markSupported,java.lang.AbstractStringBuilder,capacity|codePointAt|codePointBefore|codePointCount|indexOf|lastIndexOf|offsetByCodePoints|substring|subSequence,java.lang.Boolean,.*,java.lang.Byte,.*,java.lang.Character,.*,java.lang.Double,.*,java.lang.Float,.*,java.lang.Integer,.*,java.lang.Long,.*,java.lang.Math,.*,java.lang.Object,equals|hashCode|toString,java.lang.Short,.*,java.lang.StrictMath,.*,java.lang.String,.*,java.lang.Thread,interrupted,java.math.BigDecimal,.*,java.math.BigInteger,.*,java.net.InetAddress,.*,java.net.URI,.*,java.nio.channels.AsynchronousChannelGroup,.*,java.nio.channels.Channel,isOpen,java.nio.channels.FileChannel,open|map|lock|tryLock|write,java.nio.channels.ScatteringByteChannel,read,java.nio.channels.SocketChannel,open|socket|isConnected|isConnectionPending,java.util.Arrays,.*,java.util.Collections,(?!addAll).*,java.util.List,of,java.util.Map,of|ofEntries|entry,java.util.Set,of,java.util.UUID,.*,java.util.concurrent.BlockingQueue,offer|remove,java.util.concurrent.CountDownLatch,await|getCount,java.util.concurrent.ExecutorService,awaitTermination|isShutdown|isTerminated,java.util.concurrent.ForkJoinPool,awaitQuiescence,java.util.concurrent.Semaphore,tryAcquire|availablePermits|isFair|hasQueuedThreads|getQueueLength|getQueuedThreads,java.util.concurrent.locks.Condition,await|awaitNanos|awaitUntil,java.util.concurrent.locks.Lock,tryLock|newCondition,java.util.regex.Matcher,pattern|toMatchResult|start|end|group|groupCount|matches|find|lookingAt|quoteReplacement|replaceAll|replaceFirst|regionStart|regionEnd|hasTransparentBounds|hasAnchoringBounds|hitEnd|requireEnd,java.util.regex.Pattern,.*,java.util.stream.BaseStream,.*,java.util.stream.DoubleStream,.*,java.util.stream.IntStream,.*,java.util.stream.LongStream,.*,java.util.stream.Stream,.*" />
+    </inspection_tool>
+  </profile>
+</component>

+ 20 - 0
src/main/java/org/example/sweater/config/MvcConfig.java

@@ -0,0 +1,20 @@
+package org.example.sweater.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class MvcConfig implements WebMvcConfigurer {
+    @Value("${upload.path}")
+    private String uploadPath;
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/static/**")
+                .addResourceLocations("classpath:/static/");
+        registry.addResourceHandler("/img/**")
+                .addResourceLocations("file:" + uploadPath + "/");
+    }
+}

+ 1 - 1
src/main/java/org/example/sweater/config/WebSecurityConfig.java

@@ -49,7 +49,7 @@ public class WebSecurityConfig {
     ) throws Exception {
         http
                 .authorizeHttpRequests((requests) -> requests
-                    .requestMatchers("/", "/registration")
+                    .requestMatchers("/", "/registration", "/static/**")
                     .permitAll()
                     .anyRequest()
                     .authenticated()

+ 27 - 1
src/main/java/org/example/sweater/controller/MainController.java

@@ -4,6 +4,7 @@ import jakarta.servlet.http.HttpServletRequest;
 import org.example.sweater.domain.Message;
 import org.example.sweater.domain.User;
 import org.example.sweater.repos.MessagesRepository;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.web.csrf.CsrfToken;
 import org.springframework.stereotype.Controller;
@@ -12,8 +13,13 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
 
 @Controller
 public class MainController {
@@ -23,6 +29,9 @@ public class MainController {
 
     private final MessagesRepository messagesRepository;
 
+    @Value("${upload.path}")
+    private String uploadPath;
+
     @ModelAttribute
     public void addCsrfToken(Model model, HttpServletRequest request) {
         CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
@@ -64,9 +73,26 @@ public class MainController {
             @AuthenticationPrincipal User user,
             @RequestParam String text,
             @RequestParam String tag,
+            @RequestParam("file") MultipartFile file,
             Map<String, Object> model
-    ) {
+    ) throws IOException {
         Message createdMessage = new Message(text, tag, user);
+
+        if (file != null && !Objects.requireNonNull(file.getOriginalFilename()).isEmpty()) {
+            File uploadDir = new File(uploadPath);
+
+            if (!uploadDir.exists()) {
+                uploadDir.mkdir();
+            }
+
+            String uuidFile = UUID.randomUUID().toString();
+            String fileName = uuidFile + "_" + file.getOriginalFilename();
+
+            file.transferTo(new File(uploadDir.getAbsolutePath() + "/" + fileName));
+
+            createdMessage.setFilename(fileName);
+        }
+
         messagesRepository.save(createdMessage);
 
         Iterable<Message> allMessages = messagesRepository.findAll();

+ 10 - 0
src/main/java/org/example/sweater/domain/Message.java

@@ -50,4 +50,14 @@ public class Message {
     public Integer getId() {
         return id;
     }
+
+    private String filename;
+
+    public String getFilename() {
+        return filename;
+    }
+
+    public void setFilename(String filename) {
+        this.filename = filename;
+    }
 }

+ 1 - 0
src/main/resources/application.properties.example

@@ -3,3 +3,4 @@ spring.datasource.username=admin
 spring.datasource.password=admin
 spring.jpa.generate-ddl=true
 spring.freemarker.expose-request-attributes=true
+upload.path=uploads

+ 4 - 0
src/main/resources/static/styles.css

@@ -0,0 +1,4 @@
+body {
+    background: #ddd;
+    color: #111;
+}

+ 8 - 1
src/main/resources/templates/feed.ftl

@@ -7,12 +7,13 @@
         <a href="/user">Users</a>
     </nav>
     <@login.logout />
-    <form method="post">
+    <form method="post" enctype="multipart/form-data">
         <label for="message_text">Message text:</label>
         <input id="message_text" type="text" required name="text" />
         <label for="message_tag">Tag:</label>
         <input id="message_tag" type="text" name="tag" required />
         <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
+        <input type="file" name="file" />
         <button type="submit">Send</button>
     </form>
     <h2>Messages</h2>
@@ -40,6 +41,12 @@
                 <p>${message.text}</p>
                 <i>${message.tag}</i>
                 <strong>${message.authorName}</strong>
+                <#if message.filename??>
+                    <div>
+                        <img alt="Image" src="/img/${message.filename}" />
+                    </div>
+                </#if>
+
                 <form action="/feed/delete" method="post">
                     <input type="hidden" value="${message.id}" name="id" />
                     <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

+ 1 - 1
src/main/resources/templates/parts/common.ftl

@@ -4,7 +4,7 @@
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1">
-    <title>Getting Started: Spring MVC REST</title>
+    <link rel="stylesheet" href="/static/styles.css">
 </head>
 <body>
 <#nested>