Implementing a View‑Once Image Feature with Spring Boot and MySQL
This guide explains how to design and build a privacy‑focused, view‑once image sharing system using Spring Boot, MySQL, Thymeleaf, and optional cloud storage, covering requirements analysis, architecture, environment setup, code implementation, security considerations, performance optimizations, testing, and deployment.
Introduction
“View‑once” (Snapchat‑like) functionality automatically deletes a message or image after it has been read, protecting user privacy and preventing unauthorized access to sensitive content.
1. Background and Requirement Analysis
With growing concerns about information security and privacy, users increasingly demand temporary sharing of images that leave no trace after viewing. This feature satisfies personal privacy needs and can be applied in enterprise communication, social platforms, and online education.
1.1 Current State of Internet Privacy Protection
Frequent data leaks on social media have heightened users' awareness of privacy, prompting demand for mechanisms that restrict access to uploaded images after they are viewed.
1.2 Requirements for View‑Once Images
Upload & Storage : Users can upload images, which must be stored securely.
Expiration Mechanism : Images are automatically deleted after being viewed.
User‑Friendly Interface : Simple UI for uploading and previewing images.
Feedback Mechanism : System provides upload success/failure messages.
Security : Prevent illegal access to stored images.
2. System Architecture Design
2.1 Technology Stack
Backend : Spring Boot – rapid development of RESTful APIs with good scalability.
Database : MySQL – relational storage for image metadata.
Frontend : Thymeleaf + HTML/CSS/JavaScript – template engine for dynamic pages.
File Storage : Local file system or cloud storage (e.g., AWS S3).
2.2 Architecture Diagram
+------------------+
| User Interface |
| (Thymeleaf) |
+--------+---------+
|
+--------v---------+
| Spring Boot |
| Controller Layer |
+--------+---------+
|
+--------v---------+
| Service Layer |
| (Business Logic) |
+--------+---------+
|
+--------v---------+
| Data Access Layer |
| (MySQL/JPA) |
+--------+---------+
|
+--------v---------+
| File Storage |
| (Local/Cloud) |
+------------------+3. Environment Setup
3.1 Create Spring Boot Project
Use Spring Initializr (https://start.spring.io/) and add dependencies: Spring Web, Spring Data JPA, MySQL Driver, Thymeleaf.
3.2 Database Configuration
Create a MySQL database named image_sharing_db :
CREATE DATABASE image_sharing_db;Configure application.properties :
# MySQL database configuration
spring.datasource.url=jdbc:mysql://localhost:3306/image_sharing_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect3.3 Add Maven Dependencies
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>3.4 File Storage Directory
Create an uploads folder in the project root with write permissions to store uploaded images.
4. Feature Implementation
4.1 Data Model
Define an Image entity with fields for filename, upload time, and expiration time:
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String filename;
private LocalDateTime uploadTime;
private LocalDateTime expirationTime;
// getters and setters omitted for brevity
}4.2 Data Access Layer
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ImageRepository extends JpaRepository<Image, Long> {}4.3 Controller
package com.example.demo.controller;
import com.example.demo.entity.Image;
import com.example.demo.repository.ImageRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;
@Controller
@RequestMapping("/images")
public class ImageController {
private static final String UPLOAD_DIR = "src/main/resources/static/uploads/";
@Autowired
private ImageRepository imageRepository;
@GetMapping("/upload")
public String uploadPage() {
return "upload";
}
@PostMapping("/upload")
public String uploadImage(@RequestParam("file") MultipartFile file, Model model) {
String filename = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
Path path = Paths.get(UPLOAD_DIR + filename);
try {
Files.createDirectories(path.getParent());
file.transferTo(path);
Image image = new Image();
image.setFilename(filename);
image.setUploadTime(LocalDateTime.now());
image.setExpirationTime(LocalDateTime.now().plusMinutes(1));
Image savedImage = imageRepository.save(image);
model.addAttribute("message", "Image_Uploaded_Successfully.");
model.addAttribute("imageUrl", "/uploads/" + filename);
model.addAttribute("imageId", savedImage.getId());
} catch (IOException e) {
model.addAttribute("message", "Failed to upload image: " + e.getMessage());
}
return "upload";
}
@PostMapping("/burn/{id}")
public ResponseEntity
burnImage(@PathVariable Long id) {
Optional
imageOptional = imageRepository.findById(id);
if (imageOptional.isPresent()) {
Image image = imageOptional.get();
Path path = Paths.get("src/main/resources/static/uploads/" + image.getFilename());
try {
Files.deleteIfExists(path);
imageRepository.delete(image);
return ResponseEntity.ok("Image burned successfully");
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to burn image");
}
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Image not found");
}
}4.4 User Interface
The upload.html page provides a file input, upload button, preview area, and a “burn” button that triggers a CSS animation and optionally sends a server request to delete the image.
4.5 Error Handling
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleRuntimeException(RuntimeException e, Model model) {
model.addAttribute("message", "An error occurred: " + e.getMessage());
return "error";
}
}5. System Optimization
5.1 Performance
Image Compression : Use libraries like Thumbnailator to reduce file size before storage.
Asynchronous Processing : Apply @Async or message queues to avoid blocking requests.
5.2 Security
Filename Safety : Rename files with UUIDs to prevent collisions and path traversal.
File Type Validation : String contentType = file.getContentType(); if (!contentType.startsWith("image/")) { model.addAttribute("message", "Please upload a valid image file."); return "upload"; }
5.3 Logging
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ImageController {
private static final Logger logger = LoggerFactory.getLogger(ImageController.class);
// Example logging
logger.info("Image uploaded successfully: {}", filename);
}6. Testing and Deployment
6.1 Verification
A GIF demonstration shows the image disappearing after the burn button is clicked.
6.2 Docker Deployment
FROM openjdk:11-jre-slim
VOLUME /tmp
COPY target/image-sharing-app.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]Conclusion
This demo illustrates how to build a secure, easy‑to‑use view‑once image sharing platform with Spring Boot and MySQL, covering requirement analysis, architecture design, implementation, optimization, testing, and containerized deployment.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.