模型库

This commit is contained in:
ZZX9599
2025-09-22 17:13:22 +08:00
parent adf375648b
commit 521efbafac
40 changed files with 1177 additions and 523 deletions

View File

@ -2,8 +2,16 @@ package com.yj.earth.business.service;
import com.yj.earth.business.domain.FileInfo;
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
public interface FileInfoService extends IService<FileInfo> {
// 根据文件ID获取文件绝对路径
String getFileAbsolutePath(String id);
Map<String, Object> handleLocationImageUpload(MultipartFile file);
String uploadWithPreview(MultipartFile file);
void writeResponseMessage(HttpServletResponse response, String message);
String getFullUploadPath();
}

View File

@ -1,16 +0,0 @@
package com.yj.earth.business.service;
import com.yj.earth.business.domain.Model;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author 周志雄
* @since 2025-09-16
*/
public interface ModelService extends IService<Model> {
}

View File

@ -1,16 +0,0 @@
package com.yj.earth.business.service;
import com.yj.earth.business.domain.ModelType;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author 周志雄
* @since 2025-09-16
*/
public interface ModelTypeService extends IService<ModelType> {
}

View File

@ -1,21 +1,38 @@
package com.yj.earth.business.service.impl;
import cn.hutool.core.io.FileUtil;
import com.drew.imaging.ImageMetadataReader;
import com.drew.lang.Rational;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.GpsDirectory;
import com.yj.earth.business.domain.FileInfo;
import com.yj.earth.business.mapper.FileInfoMapper;
import com.yj.earth.business.service.FileInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yj.earth.datasource.DatabaseManager;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Service
public class FileInfoServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> implements FileInfoService {
@Value("${file.upload.path}")
private String uploadPath;
private static final String DEFAULT_UPLOAD_PATH = "upload";
public String getFileAbsolutePath(String id) {
@ -26,7 +43,7 @@ public class FileInfoServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> i
}
// 构建完整文件路径
String fullPath = uploadPath + File.separator + fileInfo.getFilePath();
String fullPath = DEFAULT_UPLOAD_PATH + File.separator + fileInfo.getFilePath();
File file = new File(fullPath);
// 校验文件是否存在
@ -37,4 +54,187 @@ public class FileInfoServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> i
// 获取并返回绝对路径
return file.getAbsolutePath();
}
public Map<String, Object> handleLocationImageUpload(MultipartFile file) {
// 构建并返回结果
Map<String, Object> result = new HashMap<>();
try {
// 校验文件是否为空
if (file.isEmpty()) {
throw new IllegalArgumentException("上传文件不能为空");
}
// 获取文件基本信息
String originalFilename = file.getOriginalFilename();
String fileSuffix = FileUtil.extName(originalFilename);
String contentType = file.getContentType();
// 验证是否为图片文件
if (contentType == null || !contentType.startsWith("image/")) {
throw new IllegalArgumentException("请上传图片文件");
}
// 获取完整的上传目录路径
Path fullUploadPath = Paths.get(getFullUploadPath());
// 生成唯一文件名
String uniqueFileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + fileSuffix;
// 创建文件存储目录
Files.createDirectories(fullUploadPath);
// 构建完整文件路径并保存文件
Path destFilePath = fullUploadPath.resolve(uniqueFileName);
// 先将文件保存到目标位置
file.transferTo(destFilePath);
// 计算文件MD5使用已保存的文件
String fileMd5 = calculateFileMd5(destFilePath.toFile());
// 提取图片元数据(使用已保存的文件、避免使用临时文件)
Map<String, Double> metadata;
try (InputStream is = Files.newInputStream(destFilePath)) {
metadata = extractImageMetadata(is);
}
// 保存文件信息到数据库
FileInfo fileInfo = new FileInfo();
fileInfo.setFileName(originalFilename);
fileInfo.setFileSuffix(fileSuffix);
fileInfo.setContentType(contentType);
fileInfo.setFileSize(file.getSize());
fileInfo.setFilePath(uniqueFileName);
fileInfo.setFileMd5(fileMd5);
this.save(fileInfo);
result.put("url", "/fileInfo/preview/" + fileInfo.getId());
result.put("lon", metadata.get("lon"));
result.put("lat", metadata.get("lat"));
result.put("alt", metadata.get("alt"));
} catch (IOException e) {
throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
}
return result;
}
public String uploadWithPreview(MultipartFile file) {
FileInfo fileInfo = new FileInfo();
// 构建并返回结果
Map<String, Object> result = new HashMap<>();
try {
// 校验文件是否为空
if (file.isEmpty()) {
throw new IllegalArgumentException("上传文件不能为空");
}
// 获取文件基本信息
String originalFilename = file.getOriginalFilename();
String fileSuffix = FileUtil.extName(originalFilename);
String contentType = file.getContentType();
// 获取完整的上传目录路径
Path fullUploadPath = Paths.get(getFullUploadPath());
// 生成唯一文件名
String uniqueFileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + fileSuffix;
// 创建文件存储目录
Files.createDirectories(fullUploadPath);
// 构建完整文件路径并保存文件
Path destFilePath = fullUploadPath.resolve(uniqueFileName);
// 先将文件保存到目标位置
file.transferTo(destFilePath);
// 计算文件MD5使用已保存的文件
String fileMd5 = calculateFileMd5(destFilePath.toFile());
// 保存文件信息到数据库
fileInfo.setFileName(originalFilename);
fileInfo.setFileSuffix(fileSuffix);
fileInfo.setContentType(contentType);
fileInfo.setFileSize(file.getSize());
fileInfo.setFilePath(uniqueFileName);
fileInfo.setFileMd5(fileMd5);
this.save(fileInfo);
} catch (IOException e) {
throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
}
return "/fileInfo/preview/" + fileInfo.getId();
}
public void writeResponseMessage(HttpServletResponse response, String message) {
try {
response.setContentType("text/plain; charset=UTF-8");
response.getWriter().write(message);
response.getWriter().flush();
} catch (IOException e) {
}
}
public String getFullUploadPath() {
// 拼接项目根目录和配置的上传路径
return DatabaseManager.getRecommendedCacheDirectory() + File.separator + DEFAULT_UPLOAD_PATH;
}
/**
* 提取图片的EXIF元数据
*/
public Map<String, Double> extractImageMetadata(InputStream inputStream) {
Map<String, Double> result = new HashMap<>(3);
try {
Metadata metadata = ImageMetadataReader.readMetadata(inputStream);
// 获取GPS相关元数据目录图片的经纬度和高度通常存储在GPS目录中
GpsDirectory gpsDirectory = metadata.getFirstDirectoryOfType(GpsDirectory.class);
if (gpsDirectory == null) {
return result;
}
// 解析纬度(度分秒转十进制)
Double latitude = parseLatitudeOrLongitude(
gpsDirectory.getRationalArray(GpsDirectory.TAG_LATITUDE),
gpsDirectory.getString(GpsDirectory.TAG_LATITUDE_REF)
);
// 解析经度(度分秒转十进制)
Double longitude = parseLatitudeOrLongitude(
gpsDirectory.getRationalArray(GpsDirectory.TAG_LONGITUDE),
gpsDirectory.getString(GpsDirectory.TAG_LONGITUDE_REF)
);
// 解析高度考虑海拔参考、0表示海平面以上、1表示以下
Double altitude = null;
if (gpsDirectory.containsTag(GpsDirectory.TAG_ALTITUDE)) {
Rational altitudeRational = gpsDirectory.getRational(GpsDirectory.TAG_ALTITUDE);
if (altitudeRational != null) {
altitude = altitudeRational.doubleValue();
// 处理海拔参考(是否在海平面以下)
if (gpsDirectory.containsTag(GpsDirectory.TAG_ALTITUDE_REF) &&
gpsDirectory.getInt(GpsDirectory.TAG_ALTITUDE_REF) == 1) {
altitude = -altitude;
}
}
}
// 存入结果 Map
result.put("lat", latitude);
result.put("lon", longitude);
result.put("alt", altitude);
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}
/**
* 将GPS的度分秒格式转换为十进制坐标
*/
public Double parseLatitudeOrLongitude(Rational[] degreesMinutesSeconds, String ref) {
if (degreesMinutesSeconds == null || degreesMinutesSeconds.length != 3 || ref == null) {
return null;
}
// 度分秒转十进制:度 + 分/60 + 秒/3600
double degrees = degreesMinutesSeconds[0].doubleValue();
double minutes = degreesMinutesSeconds[1].doubleValue();
double seconds = degreesMinutesSeconds[2].doubleValue();
double value = degrees + (minutes / 60) + (seconds / 3600);
// 根据方向参考调整正负(南纬/西经为负)
if (ref.equalsIgnoreCase("S") || ref.equalsIgnoreCase("W")) {
value = -value;
}
return value;
}
/**
* 计算文件的 MD5
*/
public String calculateFileMd5(File file) throws IOException {
try (InputStream is = new FileInputStream(file)) {
return DigestUtils.md5DigestAsHex(is);
}
}
}

View File

@ -1,20 +0,0 @@
package com.yj.earth.business.service.impl;
import com.yj.earth.business.domain.Model;
import com.yj.earth.business.mapper.ModelMapper;
import com.yj.earth.business.service.ModelService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* </p>
*
* @author 周志雄
* @since 2025-09-16
*/
@Service
public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements ModelService {
}

View File

@ -1,20 +0,0 @@
package com.yj.earth.business.service.impl;
import com.yj.earth.business.domain.ModelType;
import com.yj.earth.business.mapper.ModelTypeMapper;
import com.yj.earth.business.service.ModelTypeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* </p>
*
* @author 周志雄
* @since 2025-09-16
*/
@Service
public class ModelTypeServiceImpl extends ServiceImpl<ModelTypeMapper, ModelType> implements ModelTypeService {
}