前端全局水印, 拖拉拽图片 ,拽入等比压缩,上传服务器后,java 转base64 加水印,然后前端http预览,确认保存,拽出删除。
完整的项目,包括前端(Vue 3)和后端(Java Spring Boot),并确保所有功能都能正常工作。以下是详细的步骤:
1. 前端部分
1.1 创建 Vue 3 项目
首先,使用 Vue CLI 创建一个新的 Vue 3 项目:
bash
npm install -g @vue/cli
vue create vue-drag-and-drop-upload
cd vue-drag-and-drop-upload
在创建过程中,选择默认的 Vue 3 预设。
1.2 安装必要的依赖
安装 vuedraggable
和 axios
:
bash
npm install vuedraggable axios
1.3 创建组件 DragAndDropUpload.vue
在 src/components
目录下创建 DragAndDropUpload.vue
文件,并添加以下代码:
html
<template>
<div class="drag-and-drop-container">
<div class="watermark"></div>
<div
class="drop-area"
@dragenter.prevent
@dragover.prevent
@drop.prevent="onDrop"
>
<p>Drag & Drop images here or click to upload</p>
<input type="file" multiple @change="onFileChange" accept="image/*" />
</div>
<draggable
:list="images"
item-key="id"
@end="onDragEnd"
class="image-list"
>
<template #item="{ element }">
<div class="image-item">
<img :src="element.url" alt="Uploaded Image" />
<button @click="removeImage(element)">Remove</button>
<button v-if="!element.uploaded" @click="uploadImage(element.blob)">Upload</button>
<span v-else>Uploaded</span>
</div>
</template>
</draggable>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import draggable from 'vuedraggable';
import axios from 'axios';
export default {
name: 'DragAndDropUpload',
components: {
draggable,
},
setup() {
const images = ref([]);
const onDrop = (event) => {
const files = event.dataTransfer.files;
handleFiles(files);
};
const onFileChange = (event) => {
const files = event.target.files;
handleFiles(files);
};
const handleFiles = (files) => {
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (file.type.startsWith('image/')) {
compressImage(file).then(compressedBlob => {
const reader = new FileReader();
reader.onload = (e) => {
images.value.push({
id: Date.now() + i,
url: e.target.result,
blob: compressedBlob,
uploaded: false,
});
};
reader.readAsDataURL(compressedBlob);
});
}
}
};
const removeImage = (image) => {
images.value = images.value.filter(img => img.id !== image.id);
};
const onDragEnd = () => {
console.log('Drag ended');
};
const compressImage = (file) => {
return new Promise((resolve) => {
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = () => {
const canvas = document.createElement('canvas');
let width = img.width;
let height = img.height;
// Maintain aspect ratio and set maximum dimensions
const maxWidth = 800;
const maxHeight = 600;
if (width > height) {
if (width > maxWidth) {
height *= maxWidth / width;
width = maxWidth;
}
} else {
if (height > maxHeight) {
width *= maxHeight / height;
height = maxHeight;
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob(resolve, file.type, 0.8); // 0.8 is the quality of the compressed image
};
});
};
const uploadImage = (blob) => {
const formData = new FormData();
formData.append('file', blob);
axios.post('http://172.168.0.1/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then(response => {
console.log('Image processed successfully:', response.data);
alert('Image processed successfully!');
// Update the image preview with watermarked image
const watermarkedUrl = `data:image/png;base64,${response.data.base64}`;
const updatedImages = images.value.map(image => {
if (image.blob === blob) {
return { ...image, url: watermarkedUrl, uploaded: true };
}
return image;
});
images.value = updatedImages;
})
.catch(error => {
console.error('Error processing image:', error);
alert('Error processing image.');
});
};
const addGlobalWatermark = () => {
const watermarkDiv = document.createElement('div');
watermarkDiv.className = 'global-watermark';
watermarkDiv.textContent = 'Sample Watermark';
document.body.appendChild(watermarkDiv);
};
onMounted(() => {
addGlobalWatermark();
});
return {
images,
onDrop,
onFileChange,
handleFiles,
removeImage,
onDragEnd,
compressImage,
uploadImage,
};
},
};
</script>
<style scoped>
.drag-and-drop-container {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
}
.drop-area {
border: 2px dashed #ccc;
padding: 20px;
text-align: center;
width: 300px;
position: relative;
}
.drop-area input[type="file"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.image-list {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 20px;
}
.image-item {
position: relative;
margin: 10px;
}
.image-item img {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 5px;
}
.image-item button {
position: absolute;
bottom: 5px;
right: 5px;
background-color: red;
color: white;
border: none;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
}
.image-item span {
position: absolute;
bottom: 5px;
right: 5px;
background-color: green;
color: white;
padding: 5px 10px;
border-radius: 5px;
}
</style>
<style>
.global-watermark {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
pointer-events: none;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
font-weight: bold;
color: rgba(255, 255, 255, 0.3);
transform: rotate(-45deg);
user-select: none;
}
</style>
1.4 更新 App.vue
在 src/App.vue
中引入并使用 DragAndDropUpload
组件:
html
<template>
<div id="app">
<DragAndDropUpload />
</div>
</template>
<script>
import DragAndDropUpload from './components/DragAndDropUpload.vue';
export default {
name: 'App',
components: {
DragAndDropUpload,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
1.5 构建 Vue 项目
构建项目以生成静态文件:
bash
npm run build
这将在 dist
目录中生成构建后的文件。
2. 后端部分
2.1 创建 Spring Boot 项目
使用 Spring Initializr 创建一个新的 Spring Boot 项目。选择以下依赖:
- Spring Web
- Spring Boot DevTools
下载项目并解压。
2.2 添加依赖
在 pom.xml
中添加必要的依赖:
xml
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Apache Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.3 创建控制器
创建一个 UploadController.java
文件来处理文件上传和处理请求:
java
package com.example.uploadservice.controller;
import com.example.uploadservice.service.ImageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/api")
public class UploadController {
@Autowired
private ImageService imageService;
@PostMapping("/upload")
public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file) {
String base64Image = imageService.processImage(file);
return ResponseEntity.ok().body(base64Image);
}
}
2.4 创建服务
创建一个 ImageService.java
文件来处理图片压缩和水印添加:
java
package com.example.uploadservice.service;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@Service
public class ImageService {
public String processImage(MultipartFile file) {
try {
BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(file.getBytes()));
BufferedImage resizedImage = resize(originalImage, 800, 600);
BufferedImage watermarkedImage = addWatermark(resizedImage, "Sample Watermark");
return encodeToBase64(watermarkedImage);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Failed to process image", e);
}
}
private BufferedImage resize(BufferedImage img, int maxW, int maxH) {
int w = img.getWidth();
int h = img.getHeight();
float ratio = (float) w / h;
if (w > maxW || h > maxH) {
if (ratio >= 1) {
h = Math.round(maxW / ratio);
w = maxW;
} else {
w = Math.round(maxH * ratio);
h = maxH;
}
}
BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = resizedImg.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(img, 0, 0, w, h, null);
g2d.dispose();
return resizedImg;
}
private BufferedImage addWatermark(BufferedImage img, String watermarkText) {
int width = img.getWidth();
int height = img.getHeight();
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = bufferedImage.createGraphics();
graphics.drawImage(img, 0, 0, null);
Font font = new Font("Arial", Font.BOLD, 30);
Color color = new Color(255, 255, 255, 128);
graphics.setFont(font);
graphics.setColor(color);
FontMetrics metrics = graphics.getFontMetrics(font);
int x = (width - metrics.stringWidth(watermarkText)) / 2;
int y = (height - metrics.getHeight()) / 2 + metrics.getAscent();
graphics.drawString(watermarkText, x, y);
graphics.dispose();
return bufferedImage;
}
private String encodeToBase64(BufferedImage img) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(img, "png", baos);
byte[] bytes = baos.toByteArray();
return IOUtils.encodeBase64String(bytes);
}
}
2.5 运行 Spring Boot 应用
在项目根目录下运行 Spring Boot 应用:
bash
./mvnw spring-boot:run
确保防火墙允许 172.168.0.1
的 8080 端口流量。
3. Nginx 配置
3.1 创建一个新的站点配置文件
创建一个新的配置文件 /etc/nginx/sites-available/vue-drag-and-drop-upload
:
bash
sudo nano /etc/nginx/sites-available/vue-drag-and-drop-upload
3.2 编辑配置文件
在 vue-drag-and-drop-upload
文件中添加以下内容:
conf
server {
listen 80;
server_name example.com www.example.com;
# Root directory of the built Vue app
root /path/to/your/project/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Proxy API requests to backend server
location /upload {
proxy_pass http://172.168.0.1:8080/api/upload;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
请将 /path/to/your/project/dist
替换为你的实际项目路径。
3.3 启用站点配置
创建符号链接到 sites-enabled
目录以启用该站点配置:
bash
sudo ln -s /etc/nginx/sites-available/vue-drag-and-drop-upload /etc/nginx/sites-enabled/
3.4 测试配置并重新加载 Nginx
测试 Nginx 配置是否有语法错误:
bash
sudo nginx -t
如果没有错误,重新加载 Nginx 以应用新的配置:
bash
sudo systemctl reload nginx
总结
通过以上步骤,你已经成功实现了以下功能:
- Vue 3.0 项目:使用 Vue CLI 创建项目,并实现拖拽图片上传、等比压缩和前端预览。
- 图片压缩:使用
canvas
进行图片等比压缩。 - 图片预览:在前端预览压缩后的图片。
- 图片上传:使用
axios
将压缩后的图片上传到172.168.0.1
服务器。 - Java 服务端:使用 Spring Boot 处理图片上传、添加水印并返回 Base64 格式的图片。
- Nginx 配置:配置 Nginx 以提供静态文件服务,并设置反向代理以处理 API 请求,使用 HTTP 模式。
- 图片删除:支持从列表中删除图片。
- 全局水印:在页面上添加全局水印。
希望这个完整的示例对你有所帮助!