This commit is contained in:
jiangh277
2025-12-24 14:17:19 +08:00
parent 3eb445291f
commit 4c7d59f87b
89 changed files with 3525 additions and 311 deletions

View File

@@ -3,6 +3,7 @@ package com.timeline.file.controller;
import com.timeline.file.service.FileService;
import com.timeline.file.vo.ImageInfoVo;
import com.timeline.common.response.ResponseEntity;
import com.timeline.common.utils.UserContextUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
@@ -32,6 +33,19 @@ public class FileController {
fileService.createBucketIfNotExist();
return "create default bucket success";
}
@PostMapping("/bucket/{userId}")
public ResponseEntity<String> createBucketForUser(@PathVariable String userId) throws Throwable {
fileService.createUserBucket(userId);
return ResponseEntity.success("bucket created for user " + userId);
}
@PostMapping("/bucket/self")
public ResponseEntity<String> createBucketForCurrentUser() throws Throwable {
String userId = UserContextUtils.getCurrentUserId();
fileService.createUserBucket(userId);
return ResponseEntity.success("bucket created for current user");
}
@GetMapping("/get-upload-url/{fileName}")
public ResponseEntity<String> getUploadUrl(@PathVariable String fileName) throws Throwable {
String uploadUrl = fileService.generateUploadUrl(fileName);
@@ -77,7 +91,8 @@ public class FileController {
fileService.saveFileMetadata(imageInfoVo);
// 2. 关联图片和 StoryItem
fileService.associateImageWithStoryItem(imageInfoVo.getInstanceId(), storyItemId, "9999");
String userId = UserContextUtils.getCurrentUserId();
fileService.associateImageWithStoryItem(imageInfoVo.getInstanceId(), storyItemId, userId);
return ResponseEntity.success("上传并绑定成功");
}
@@ -105,9 +120,9 @@ public class FileController {
return ResponseEntity.success("图片已从故事项中移除");
}
@GetMapping("/image/list")
public ResponseEntity<Map> getImagesListByOwnerId(@SpringQueryMap ImageInfoVo imageInfoVo) throws Throwable {
imageInfoVo.setOwnerId("9999");
Map images = fileService.getImagesListByOwnerId(imageInfoVo);
public ResponseEntity<Map<String, Object>> getImagesListByOwnerId(@SpringQueryMap ImageInfoVo imageInfoVo) throws Throwable {
imageInfoVo.setOwnerId(UserContextUtils.getCurrentUserId());
Map<String, Object> images = fileService.getImagesListByOwnerId(imageInfoVo);
return ResponseEntity.success(images);
}
@DeleteMapping("/image/{imageInstanceId}")

View File

@@ -13,6 +13,7 @@ import java.util.Map;
@Service
public interface FileService {
void createBucketIfNotExist() throws Throwable;
void createUserBucket(String userId) throws Throwable;
String generateUploadUrl(String fileName) throws Throwable;
String generateDownloadUrl(String fileName) throws Throwable;
void saveFileMetadata(ImageInfoVo imageInfoVo);
@@ -27,5 +28,5 @@ public interface FileService {
InputStream fetchImageLowRes(String instanceId) throws Throwable;
Map getImagesListByOwnerId(ImageInfoVo imageInfoVo);
Map<String, Object> getImagesListByOwnerId(ImageInfoVo imageInfoVo);
}

View File

@@ -5,6 +5,7 @@ import com.timeline.common.exception.CustomException;
import com.timeline.common.response.ResponseEnum;
import com.timeline.common.utils.CommonUtils;
import com.timeline.common.utils.PageUtils;
import com.timeline.common.utils.UserContextUtils;
import com.timeline.file.config.MinioConfig;
import com.timeline.file.dao.CommonRelationMapper;
import com.timeline.file.dao.FileHashMapper;
@@ -41,12 +42,23 @@ public class FileServiceImpl implements FileService {
private CommonRelationMapper commonRelationMapper;
@Autowired
private FileHashMapper fileHashMapper;
@Autowired
public FileServiceImpl(MinioClient minioClient, MinioConfig minioConfig) {
this.minioClient = minioClient;
this.minioConfig = minioConfig;
}
private String currentUserId() {
String userId = UserContextUtils.getCurrentUserId();
if (userId == null || userId.isEmpty()) {
throw new CustomException(ResponseEnum.UNAUTHORIZED, "未获取到用户身份");
}
return userId;
}
private String userBucket(String userId) {
return (minioConfig.getBucketName() + "-" + userId).toLowerCase();
}
@Override
public void createBucketIfNotExist() throws Throwable {
try {
@@ -61,22 +73,42 @@ public class FileServiceImpl implements FileService {
}
}
@Override
public void createUserBucket(String userId) throws Throwable {
String bucket = userBucket(userId);
try {
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
if (!found) {
log.info("bucket不存在为用户{}创建bucket{}", userId, bucket);
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
}
} catch (MinioException e) {
log.error("MinIO 操作失败:", e);
throw new CustomException(500, "创建bucket失败");
}
}
@Override
public String generateUploadUrl(String fileName) throws Throwable {
String userId = currentUserId();
String bucket = userBucket(userId);
createUserBucket(userId);
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.PUT)
.bucket(minioConfig.getBucketName())
.bucket(bucket)
.object(fileName).build()
);
}
@Override
public String generateDownloadUrl(String fileName) throws Throwable {
String userId = currentUserId();
String bucket = userBucket(userId);
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(minioConfig.getBucketName())
.bucket(bucket)
.object(fileName).build()
);
}
@@ -91,7 +123,7 @@ public class FileServiceImpl implements FileService {
imageInfo.setContentType(imageInfoVo.getContentType());
imageInfo.setSize(imageInfoVo.getSize());
imageInfo.setUploadTime(LocalDateTime.now());
imageInfo.setUserId("9999");
imageInfo.setUserId(currentUserId());
imageInfoMapper.insert(imageInfo);
} catch (Exception e) {
log.error("保存图片元数据失败", e);
@@ -114,15 +146,16 @@ public class FileServiceImpl implements FileService {
} else {
// 不存在其他image_info使用则删除 MinIO 中的对象
log.info("删除 MinIO 中的对象:{}", imageInfo.getObjectKey());
String bucket = userBucket(imageInfo.getUserId());
log.info("删除 MinIO 中的对象:{} from bucket {}", imageInfo.getObjectKey(), bucket);
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucket)
.object(imageInfo.getObjectKey())
.build());
// 删除低分辨率图像
log.info("删除 MinIO 中的低分辨率对象:{}", CommonConstants.LOW_RESOLUTION_PREFIX + imageInfo.getObjectKey());
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucket)
.object(CommonConstants.LOW_RESOLUTION_PREFIX + imageInfo.getObjectKey())
.build());
}
@@ -168,8 +201,18 @@ public class FileServiceImpl implements FileService {
ArrayList<String> urls = new ArrayList<>();
for (String imageInstanceId : imageIds) {
String objectKey = imageInfoMapper.selectObjectKeyById(imageInstanceId);
String url = this.generateDownloadUrl(objectKey);
ImageInfo info = imageInfoMapper.selectByInstanceId(imageInstanceId);
if (info == null) {
continue;
}
String bucket = userBucket(info.getUserId());
String url = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucket)
.object(info.getObjectKey())
.build()
);
urls.add(url);
}
return urls;
@@ -183,6 +226,9 @@ public class FileServiceImpl implements FileService {
String lowResolutionObjectKey = CommonConstants.LOW_RESOLUTION_PREFIX + hash + suffix;
log.info("上传图片的ObjectKey值为{}", objectKey);
List<FileHash> hashByFileHash = fileHashMapper.getFileHashByFileHash(hash);
String userId = currentUserId();
String bucket = userBucket(userId);
createUserBucket(userId);
// 2. 保存元数据到 MySQL
ImageInfo imageInfo = new ImageInfo();
imageInfo.setInstanceId(IdUtils.randomUuidUpper());
@@ -190,14 +236,14 @@ public class FileServiceImpl implements FileService {
imageInfo.setImageName(image.getOriginalFilename());
imageInfo.setContentType(image.getContentType());
imageInfo.setSize(image.getSize());
imageInfo.setUserId("9999");
imageInfo.setUserId(userId);
imageInfo.setUploadTime(LocalDateTime.now());
if (hashByFileHash != null && !hashByFileHash.isEmpty()) {
log.info("当前文件已存在不进行minio文件上传");
} else {
// 1. 上传到 MinIO
minioClient.putObject(PutObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucket)
.object(objectKey)
.stream(image.getInputStream(), image.getSize(), -1)
.contentType(image.getContentType())
@@ -212,7 +258,7 @@ public class FileServiceImpl implements FileService {
ByteArrayInputStream lowResInputStream = new ByteArrayInputStream(lowResOutputStream.toByteArray());
minioClient.putObject(PutObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucket)
.object(lowResolutionObjectKey)
.stream(lowResInputStream, lowResInputStream.available(), -1)
.contentType(image.getContentType())
@@ -236,8 +282,10 @@ public class FileServiceImpl implements FileService {
if (objectKey == null) {
throw new CustomException(ResponseEnum.NOT_FOUND_ERROR);
}
ImageInfo imageInfo = imageInfoMapper.selectByInstanceId(instanceId);
String bucket = userBucket(imageInfo.getUserId());
return minioClient.getObject(GetObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucket)
.object(objectKey)
.build());
}
@@ -249,26 +297,29 @@ public class FileServiceImpl implements FileService {
throw new CustomException(ResponseEnum.NOT_FOUND_ERROR);
}
String lowResObjectKey = CommonConstants.LOW_RESOLUTION_PREFIX + objectKey;
ImageInfo imageInfo = imageInfoMapper.selectByInstanceId(instanceId);
String bucket = userBucket(imageInfo.getUserId());
// 优先返回低分辨率版本,如果不存在则返回原图
if (doesObjectExist(lowResObjectKey)) {
if (doesObjectExist(bucket, lowResObjectKey)) {
return minioClient.getObject(GetObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucket)
.object(lowResObjectKey)
.build());
} else {
log.warn("低分辨率版本不存在,返回原图: {}", objectKey);
return minioClient.getObject(GetObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucket)
.object(objectKey)
.build());
}
}
@Override
public Map getImagesListByOwnerId(ImageInfoVo imageInfoVo) {
public Map<String, Object> getImagesListByOwnerId(ImageInfoVo imageInfoVo) {
HashMap<String, String> map = new HashMap<>();
map.put("ownerId", imageInfoVo.getOwnerId());
Map resultMap = PageUtils.pageQuery(imageInfoVo.getCurrent(), imageInfoVo.getPageSize(), ImageInfoMapper.class, "selectListByOwnerId",
@SuppressWarnings("unchecked")
Map<String, Object> resultMap = (Map<String, Object>) PageUtils.pageQuery(imageInfoVo.getCurrent(), imageInfoVo.getPageSize(), ImageInfoMapper.class, "selectListByOwnerId",
map, "list");
return resultMap;
}
@@ -278,10 +329,10 @@ public class FileServiceImpl implements FileService {
* @param objectKey 对象键
* @return true表示存在false表示不存在
*/
private boolean doesObjectExist(String objectKey) {
private boolean doesObjectExist(String bucket, String objectKey) {
try {
minioClient.statObject(StatObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucket)
.object(objectKey)
.build());
return true;

View File

@@ -1,12 +1,12 @@
spring.application.name=timeline.file
spring.datasource.url=jdbc:mysql://8.137.148.196:33306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.url=jdbc:mysql://59.80.22.43:33306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=your_password
spring.datasource.password=WoCloud@9ol7uj
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MinIO ??
minio.endpoint=http://8.137.148.196:9000
minio.accessKey=dasdqq22211AAdsda2
minio.secretKey=2123sda2AADDsa4
minio.endpoint=http://59.80.22.43:9000
minio.accessKey=9ttSGjvQxek2uKKlhpqI
minio.secretKey=12CaKew53tu94tgyDLoqAwAq32iDuz3SWW0O1hex
minio.bucketName=timeline-test
# MyBatis ??
@@ -20,3 +20,11 @@ server.port=30002
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
# Ensure UTF-8 encoding for logs and web layer to avoid garbled Chinese output
logging.charset.console=UTF-8
logging.charset.file=UTF-8
server.tomcat.uri-encoding=UTF-8
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true