Implementing a Snapchat-like Self-Destructing Image Feature with Spring Boot and MySQL
This article presents a step‑by‑step guide on building a Snapchat‑style “burn after reading” image sharing system using Spring Boot, MySQL, Thymeleaf, and optional cloud storage, covering background analysis, architecture design, environment setup, code implementation, optimization, testing, and deployment.
1. Background and Requirement Analysis
Privacy protection and information security have become increasingly important. With the widespread use of social media and instant‑messaging tools, many users want to share temporary content that leaves no trace. The self‑destructing image feature meets this privacy demand and enhances security.
1.1 Current State of Internet Privacy Protection
As the internet matures, users are more aware of privacy risks. Frequent data‑leak incidents on social platforms have driven users to seek ways to limit the lifespan of shared images, a need that applies to personal, enterprise, educational, and other scenarios.
1.2 Requirements for Self‑Destructing Images
The feature should include the following requirements:
Upload and Storage : Users can upload images, which must be stored securely.
Expiration Mechanism : Images are automatically deleted after being viewed.
User‑Friendly Interface : Provide a simple and intuitive UI for uploading and viewing.
Feedback Mechanism : Show status messages such as upload success or failure.
Security : Ensure uploaded images cannot be accessed illegally.
2. System Architecture Design
2.1 Technology Stack
The system uses the following stack:
Backend : Spring Boot – rapid development of RESTful APIs with good extensibility.
Database : MySQL – relational database suitable for structured data storage and queries.
Frontend : Thymeleaf + HTML/CSS/JavaScript – template engine for dynamic page generation.
File Storage : Local file system or cloud storage (e.g., AWS S3) for flexible file handling.
2.2 System Architecture Diagram
+------------------+
| User Interface |
| (Thymeleaf) |
+--------+---------+
|
+--------v---------+
| Spring Boot |
| Controller |
+--------+---------+
|
+--------v---------+
| Service Layer |
| (Business) |
+--------+---------+
|
+--------v---------+
| Data Access Layer|
| (MySQL/JPA) |
+--------+---------+
|
+--------v---------+
| File Storage |
| (Local/Cloud) |
+------------------+3. Environment Setup
3.1 Create Spring Boot Project
Use Spring Initializr (start.spring.io) to generate a new project with the following dependencies:
Spring Web – for building RESTful APIs.
Spring Data JPA – simplifies data‑access layer development.
MySQL Driver – connects to MySQL.
Thymeleaf – generates dynamic web pages.
Import the generated project into an IDE (IntelliJ IDEA or Eclipse) and ensure it compiles and runs.
3.2 Database Configuration
Create a new MySQL database, for example image_sharing_db :
CREATE DATABASE image_sharing_db;Configure the connection in 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 Dependencies
Ensure the following dependency is present in pom.xml :
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>3.4 File Storage Directory
Create a folder named uploads in the project root and grant write permissions so that uploaded images can be saved.
4. Feature Implementation
4.1 Data Model Design
Create an Image entity to store image metadata such as 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; // Image ID
private String filename; // File name
private LocalDateTime uploadTime; // Upload time
private LocalDateTime expirationTime; // Expiration time
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getFilename() { return filename; }
public void setFilename(String filename) { this.filename = filename; }
public LocalDateTime getUploadTime() { return uploadTime; }
public void setUploadTime(LocalDateTime uploadTime) { this.uploadTime = uploadTime; }
public LocalDateTime getExpirationTime() { return expirationTime; }
public void setExpirationTime(LocalDateTime expirationTime) { this.expirationTime = expirationTime; }
}4.2 Data Access Layer
Create a repository interface ImageRepository that extends JpaRepository for basic CRUD operations.
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ImageRepository extends JpaRepository
{
}4.3 Controller Implementation
Implement ImageController to handle image upload, view, and burn requests.
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;
/**
* By zhangT
*/
@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"; // return upload page view
}
@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);
// Save file info to DB
Image image = new Image();
image.setFilename(filename);
image.setUploadTime(LocalDateTime.now());
image.setExpirationTime(LocalDateTime.now().plusMinutes(1)); // expire after 1 minute
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"; // return upload page view
}
@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 Design
4.4.1 Upload Page
The upload.html page allows users to upload an image, preview it, and trigger the burn effect.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Self‑Destructing Image</title>
<style>
body {font-family: Arial, sans-serif; display: flex; flex-direction: column; align-items: center;}
.upload-container {margin-top: 50px; text-align: center;}
.preview-container img {max-width: 100%; height: auto; margin-top: 10px;}
.message {color: green; font-weight: bold; margin-top: 10px;}
.burn {animation: burn 1s forwards;}
@keyframes burn {from {opacity: 1;} to {opacity: 0; transform: scale(1.5);}}
#imageContainer {position: relative;}
#burnEffect {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(255,0,0,0.5); display: none; z-index: 10;}
</style>
</head>
<body>
<div class="upload-container">
<h1>Self‑Destructing Image</h1>
<form action="/images/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" required>
<button type="submit">Upload</button>
</form>
<p th:text="${message}" class="message"></p>
</div>
<div id="imageContainer" class="preview-container" th:if="${imageUrl}">
<h2>Image Preview</h2>
<img th:src="${imageUrl}" alt="Uploaded Image">
<div id="burnEffect"></div>
</div>
<button id="burnButton">Burn After Reading</button>
<script>
document.getElementById("burnButton").onclick = function() {
const burnEffect = document.getElementById("burnEffect");
burnEffect.style.display = "block";
burnEffect.classList.add("burn");
setTimeout(function() {
document.querySelector("img").style.display = "none";
burnEffect.style.display = "none";
alert("Image has been burned and cannot be recovered.");
}, 2000);
};
// Optional server‑side burn request (commented out)
// const imageId = /*[[${imageId}]]*/ 0;
// fetch(`/burn/${imageId}`, {method: 'POST'}).then(...);
</script>
</body>
</html>4.5 Error Handling
Use a global exception handler to capture runtime errors and return an error view.
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"; // return error page view
}
}5. System Optimization
5.1 Performance Optimization
Image Compression : Compress images on upload using libraries such as Thumbnailator.
Asynchronous Processing : Offload image handling to async methods or message queues using @Async.
5.2 Security
Filename Safety : Rename uploaded files with UUIDs to avoid collisions and security issues.
File Type Checking : Verify that the uploaded file is an image (e.g., content type starts with "image/").
String contentType = file.getContentType();
if (!contentType.startsWith("image/")) {
model.addAttribute("message", "Please upload a valid image file.");
return "upload";
}5.3 Logging
Integrate SLF4J and Logback to record upload, view, and error events.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ImageController {
private static final Logger logger = LoggerFactory.getLogger(ImageController.class);
// Example log
logger.info("Image uploaded successfully: {}", filename);
}6. Testing and Deployment
6.1 Effect Verification
The following GIF demonstrates the burn‑after‑reading effect:
6.2 Deployment
Containerize the Spring Boot application with Docker for consistent deployment across environments.
FROM openjdk:11-jre-slim
VOLUME /tmp
COPY target/image-sharing-app.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]7. Conclusion
This demo illustrates how to build a secure, user‑friendly self‑destructing image sharing platform using Spring Boot and MySQL, covering requirement analysis, architecture design, implementation, optimization, testing, and deployment.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.