照片展示

This commit is contained in:
2025-07-30 10:09:35 +08:00
parent c02e3421ad
commit 923e877759
11 changed files with 657 additions and 78 deletions

View File

@@ -8,6 +8,7 @@ public class FlashSaleSystemApplication {
public static void main(String[] args) {
SpringApplication.run(FlashSaleSystemApplication.class, args);
System.out.println("http://localhost:8080");
}
}

View File

@@ -1,5 +1,6 @@
package com.org.flashsalesystem.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
@@ -16,6 +17,12 @@ import org.springframework.web.servlet.view.JstlView;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${flashsale.upload.path}")
private String uploadPath;
@Value("${flashsale.upload.url-prefix}")
private String urlPrefix;
/**
* JSP视图解析器
*/
@@ -56,6 +63,10 @@ public class WebConfig implements WebMvcConfigurer {
registry.addResourceHandler("/favicon.ico")
.addResourceLocations("classpath:/META-INF/resources/");
// 添加上传文件的静态资源映射
registry.addResourceHandler(urlPrefix + "**")
.addResourceLocations("file:" + uploadPath);
}
/**

View File

@@ -1,12 +1,14 @@
package com.org.flashsalesystem.controller;
import com.org.flashsalesystem.service.AdminService;
import com.org.flashsalesystem.service.FileUploadService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.Map;
@@ -23,6 +25,9 @@ public class AdminController {
@Autowired
private AdminService adminService;
@Autowired
private FileUploadService fileUploadService;
/**
* 获取仪表盘统计数据
*/
@@ -449,4 +454,38 @@ public class AdminController {
return ResponseEntity.badRequest().body(response);
}
}
/**
* 上传商品图片
*/
@Operation(summary = "上传商品图片")
@PostMapping("/products/upload-image")
public ResponseEntity<Map<String, Object>> uploadProductImage(@RequestParam("file") MultipartFile file) {
try {
String imageUrl = fileUploadService.uploadProductImage(file);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "图片上传成功");
response.put("imageUrl", imageUrl);
return ResponseEntity.ok(response);
} catch (IllegalArgumentException e) {
log.error("图片上传失败 - 参数错误", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", e.getMessage());
return ResponseEntity.badRequest().body(response);
} catch (Exception e) {
log.error("图片上传失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "图片上传失败: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
}

View File

@@ -0,0 +1,203 @@
package com.org.flashsalesystem.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
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.time.format.DateTimeFormatter;
import java.util.UUID;
/**
* 文件上传服务类
* 处理商品图片等文件的上传
*/
@Service
@Slf4j
public class FileUploadService {
private static final String IMAGE_DIR = "products/";
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
// 允许的图片格式
private static final String[] ALLOWED_EXTENSIONS = {
"jpg", "jpeg", "png", "gif", "webp"
};
@Value("${flashsale.upload.path}")
private String uploadPath;
@Value("${flashsale.upload.url-prefix}")
private String urlPrefix;
/**
* 初始化上传目录
*/
@PostConstruct
public void init() {
try {
// 创建上传根目录
Path rootPath = Paths.get(uploadPath);
if (!Files.exists(rootPath)) {
Files.createDirectories(rootPath);
log.info("创建上传根目录: {}", uploadPath);
}
// 创建商品图片目录
Path productPath = Paths.get(uploadPath + IMAGE_DIR);
if (!Files.exists(productPath)) {
Files.createDirectories(productPath);
log.info("创建商品图片目录: {}", productPath);
}
} catch (IOException e) {
log.error("初始化上传目录失败", e);
throw new RuntimeException("初始化上传目录失败", e);
}
}
/**
* 上传商品图片
*
* @param file 上传的文件
*
* @return 图片访问URL
*/
public String uploadProductImage(MultipartFile file) {
// 检查文件是否为空
if (file == null || file.isEmpty()) {
throw new IllegalArgumentException("上传文件不能为空");
}
// 检查文件大小
if (file.getSize() > MAX_FILE_SIZE) {
throw new IllegalArgumentException("文件大小不能超过10MB");
}
// 获取文件扩展名
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || !originalFilename.contains(".")) {
throw new IllegalArgumentException("文件名格式不正确");
}
String extension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase();
// 检查文件格式
if (!isAllowedExtension(extension)) {
throw new IllegalArgumentException("不支持的图片格式,仅支持: jpg, jpeg, png, gif, webp");
}
try {
// 生成新的文件名
String newFileName = generateFileName(extension);
// 按日期创建子目录
String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String relativePath = IMAGE_DIR + dateDir + "/" + newFileName;
// 创建目标目录
Path targetDir = Paths.get(uploadPath + IMAGE_DIR + dateDir);
if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir);
}
// 保存文件
Path targetPath = Paths.get(uploadPath + relativePath);
file.transferTo(targetPath.toFile());
// 返回访问URL
String imageUrl = urlPrefix + relativePath;
log.info("文件上传成功: {} -> {}", originalFilename, imageUrl);
return imageUrl;
} catch (IOException e) {
log.error("文件上传失败", e);
throw new RuntimeException("文件上传失败: " + e.getMessage());
}
}
/**
* 删除商品图片
*
* @param imageUrl 图片URL
*/
public void deleteProductImage(String imageUrl) {
if (imageUrl == null || imageUrl.isEmpty()) {
return;
}
// 只处理本地上传的图片
if (!imageUrl.startsWith(urlPrefix)) {
log.warn("非本地图片,跳过删除: {}", imageUrl);
return;
}
try {
// 从URL中提取相对路径
String relativePath = imageUrl.substring(urlPrefix.length());
Path filePath = Paths.get(uploadPath + relativePath);
if (Files.exists(filePath)) {
Files.delete(filePath);
log.info("删除图片成功: {}", imageUrl);
} else {
log.warn("图片文件不存在: {}", filePath);
}
} catch (IOException e) {
log.error("删除图片失败: {}", imageUrl, e);
}
}
/**
* 生成唯一的文件名
*
* @param extension 文件扩展名
*
* @return 新文件名
*/
private String generateFileName(String extension) {
return UUID.randomUUID().toString().replace("-", "") + "." + extension;
}
/**
* 检查文件扩展名是否允许
*
* @param extension 扩展名
*
* @return 是否允许
*/
private boolean isAllowedExtension(String extension) {
for (String allowed : ALLOWED_EXTENSIONS) {
if (allowed.equalsIgnoreCase(extension)) {
return true;
}
}
return false;
}
/**
* 获取文件的MIME类型
*
* @param extension 文件扩展名
*
* @return MIME类型
*/
public String getMimeType(String extension) {
switch (extension.toLowerCase()) {
case "jpg":
case "jpeg":
return "image/jpeg";
case "png":
return "image/png";
case "gif":
return "image/gif";
case "webp":
return "image/webp";
default:
return "application/octet-stream";
}
}
}