模型库

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

@ -29,6 +29,12 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- MybatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>

View File

@ -61,13 +61,13 @@ public class AuthAspect {
}
}
// 授权有效继续执行原方法
// 授权有效继续执行原方法
return point.proceed();
}
/**
* 读取授权文件内容
* @return 授权文件内容如果文件不存在或读取失败则返回null
* @return 授权文件内容如果文件不存在或读取失败则返回null
*/
private String readLicenseFile() {
try {

View File

@ -70,4 +70,8 @@ public class AuthGenerator {
throw new RuntimeException("授权信息加密失败", e);
}
}
public static void main(String[] args) {
System.out.println(generateAuth("标准版", 1000, 30, "DAC653349FD15F1E6DB2F9322AD628F4"));
}
}

View File

@ -4,17 +4,22 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.lang.Rational;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.GpsDirectory;
import com.yj.earth.business.domain.FileInfo;
import com.yj.earth.business.service.FileInfoService;
import com.yj.earth.common.util.ApiResponse;
@ -46,41 +51,22 @@ public class FileInfoController {
@Resource
private FileInfoService fileInfoService;
@Value("${file.upload.path}")
private String uploadPath;
// 获取项目根目录
private String getProjectRootPath() {
return System.getProperty("user.dir");
}
// 获取完整的上传目录路径
private String getFullUploadPath() {
// 拼接项目根目录和配置的上传路径
return getProjectRootPath() + File.separator + uploadPath;
}
@Operation(summary = "文件上传")
@PostMapping("/upload")
public ApiResponse uploadFiles(@Parameter(description = "上传的文件数组", required = true) @RequestParam("files") MultipartFile[] files) throws IOException {
// 校验文件数组是否为空
if (files == null || files.length == 0) {
return ApiResponse.failure("上传文件不能为空");
}
// 获取完整的上传目录路径
String fullUploadPath = getFullUploadPath();
String fullUploadPath = fileInfoService.getFullUploadPath();
List<FileInfoVo> fileInfoVoList = new ArrayList<>();
// 遍历处理每个文件
for (MultipartFile file : files) {
// 跳过空文件
if (file.isEmpty()) {
continue;
}
// 获取原始文件名和后缀
String originalFilename = file.getOriginalFilename();
String fileSuffix = FileUtil.extName(originalFilename);
@ -106,7 +92,7 @@ public class FileInfoController {
// 查询有没有文件名一样并且 MD5 也一样的数据
LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FileInfo::getFileName, originalFilename).eq(FileInfo::getFileMd5, fileMd5);
if (fileInfoService.count(queryWrapper) > 0) { // 修复了此处的bug、添加了queryWrapper参数
if (fileInfoService.count(queryWrapper) > 0) {
return ApiResponse.failure("已存在文件名相同且内容完全一致的文件");
}
@ -116,7 +102,7 @@ public class FileInfoController {
fileInfo.setFileSuffix(fileSuffix);
fileInfo.setContentType(contentType);
fileInfo.setFileSize(file.getSize());
fileInfo.setFilePath(uniqueFileName); // 只保存相对文件名、不保存完整路径
fileInfo.setFilePath(uniqueFileName);
fileInfo.setFileMd5(fileMd5);
// 保存文件信息并获取ID
@ -152,7 +138,7 @@ public class FileInfoController {
}
// 构建完整文件路径
String fullPath = getFullUploadPath() + File.separator + fileInfo.getFilePath();
String fullPath = fileInfoService.getFullUploadPath() + File.separator + fileInfo.getFilePath();
File file = new File(fullPath);
// 校验文件是否存在
@ -185,7 +171,7 @@ public class FileInfoController {
}
// 构建完整文件路径
String fullPath = getFullUploadPath() + File.separator + fileInfo.getFilePath();
String fullPath = fileInfoService.getFullUploadPath() + File.separator + fileInfo.getFilePath();
File file = new File(fullPath);
// 校验文件是否存在
@ -204,100 +190,59 @@ public class FileInfoController {
}
}
public String handleLocationImageUpload(MultipartFile file) {
@Operation(summary = "本地文件预览")
@GetMapping("/previewLocal")
public void previewLocalFile(@Parameter(description = "本地文件绝对路径") @RequestParam String fileAbsolutePath, HttpServletResponse response) {
Path targetFilePath = null;
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());
// 检查文件是否已存在
LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FileInfo::getFileName, originalFilename)
.eq(FileInfo::getFileMd5, fileMd5);
if (fileInfoService.count(queryWrapper) > 0) {
throw new IllegalStateException("已存在相同的图片文件");
}
// 提取图片元数据(使用已保存的文件,避免使用临时文件)
Map<String, Object> 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);
fileInfoService.save(fileInfo);
// 构建并返回结果
Map<String, Object> result = new HashMap<>();
result.put("previewUrl", "/fileInfo/preview/" + fileInfo.getId());
result.put("downloadUrl", "/fileInfo/download/" + fileInfo.getId());
result.put("metadata", metadata);
return JsonUtil.mapToJson(result);
} catch (IOException e) {
throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
}
}
// 标准化路径
targetFilePath = Paths.get(fileAbsolutePath).toRealPath();
/**
* 计算文件的 MD5
*/
private String calculateFileMd5(File file) throws IOException {
try (InputStream is = new FileInputStream(file)) {
return DigestUtils.md5DigestAsHex(is);
}
}
// 校验文件合法性:是否存在、是否为普通文件
BasicFileAttributes fileAttr = Files.readAttributes(targetFilePath, BasicFileAttributes.class);
if (!fileAttr.isRegularFile()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
fileInfoService.writeResponseMessage(response, "目标路径不是有效的文件");
return;
}
/**
* 提取图片的EXIF元数据包括定位信息
*/
private Map<String, Object> extractImageMetadata(InputStream inputStream) {
try {
Map<String, Object> result = new HashMap<>();
Metadata metadata = ImageMetadataReader.readMetadata(inputStream);
// 设置预览响应头
String fileName = targetFilePath.getFileName().toString();
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
String contentType = Files.probeContentType(targetFilePath);
// 遍历所有元数据目录
for (Directory directory : metadata.getDirectories()) {
String directoryName = directory.getName();
Map<String, String> directoryTags = new HashMap<>();
// 对于文本类型文件、指定字符编码
if (contentType != null && contentType.startsWith("text/")) {
response.setContentType(contentType + "; charset=UTF-8");
} else {
response.setContentType(contentType != null ? contentType : "application/octet-stream");
}
// 提取当前目录下的所有标签
for (com.drew.metadata.Tag tag : directory.getTags()) {
directoryTags.put(tag.getTagName(), tag.getDescription());
}
// 存储当前目录的所有标签
if (!directoryTags.isEmpty()) {
result.put(directoryName, directoryTags);
response.setContentLengthLong(fileAttr.size());
// 关键修改将attachment改为inline实现预览
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
// 写入文件流
try (InputStream inputStream = Files.newInputStream(targetFilePath);
OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[1024 * 8];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
outputStream.flush();
}
return result;
} catch (ImageProcessingException | IOException e) {
return Collections.emptyMap();
} catch (NoSuchFileException e) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
fileInfoService.writeResponseMessage(response, "文件不存在:" + fileAbsolutePath);
} catch (SecurityException e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
fileInfoService.writeResponseMessage(response, "访问拒绝:无权限读取该文件");
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
fileInfoService.writeResponseMessage(response, "预览失败:" + e.getMessage());
}
}
}

View File

@ -1,65 +0,0 @@
package com.yj.earth.business.controller;
import com.yj.earth.business.service.ModelService;
import com.yj.earth.business.service.ModelTypeService;
import com.yj.earth.common.util.ApiResponse;
import com.yj.earth.datasource.DatabaseManager;
import com.yj.earth.design.Model;
import com.yj.earth.design.ModelType;
import com.yj.earth.dto.model.CreateModelFileDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
@Tag(name = "模型相关")
@RestController
@RequestMapping("/model")
public class ModelController {
@Resource
private ModelService modelService;
@Resource
private ModelTypeService modelTypeService;
@Operation(summary = "创建模型库")
@PostMapping("/createModelFile")
public ApiResponse createModelFile(@RequestBody CreateModelFileDto createModelFileDto) {
try {
// 获取参数并处理文件路径
String folderPath = createModelFileDto.getFolderPath();
String modelFileName = createModelFileDto.getModelFileName() + ".model";
// 创建文件夹(如果不存在)
File folder = new File(folderPath);
if (!folder.exists()) {
boolean folderCreated = folder.mkdirs();
if (!folderCreated) {
return ApiResponse.failure("无法创建文件夹: " + folderPath);
}
}
// 构建完整文件路径
String filePath = folderPath + File.separator + modelFileName;
// 加载 SQLite 驱动并创建数据库文件
Class.forName("org.sqlite.JDBC");
// SQLite会自动创建不存在的数据库文件
try (Connection connection = DriverManager.getConnection("jdbc:sqlite:" + filePath)) {
if (connection != null) {
// 初始化表结构
DatabaseManager.createTablesForEntities(DatabaseManager.DatabaseType.SQLITE, ModelType.class, connection);
DatabaseManager.createTablesForEntities(DatabaseManager.DatabaseType.SQLITE, Model.class, connection);
return ApiResponse.success(null);
}
}
} catch (Exception e) {
return ApiResponse.failure("创建模型库失败: " + e.getMessage());
}
return ApiResponse.failure("未知错误");
}
}

View File

@ -1,12 +0,0 @@
package com.yj.earth.business.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "模型类型管理")
@RestController
@RequestMapping("/modelType")
public class ModelTypeController {
}

View File

@ -2,20 +2,8 @@ package com.yj.earth.business.controller;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yj.earth.annotation.CheckAuth;
import com.yj.earth.business.domain.FileInfo;
import com.yj.earth.business.domain.Role;
import com.yj.earth.business.domain.Source;
import com.yj.earth.business.service.*;
import com.yj.earth.common.service.SourceDataGenerator;
@ -24,6 +12,7 @@ import com.yj.earth.common.util.ApiResponse;
import com.yj.earth.common.util.JsonUtil;
import com.yj.earth.common.util.MapUtil;
import com.yj.earth.dto.source.*;
import com.yj.earth.params.Point;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -34,9 +23,6 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import static com.yj.earth.common.constant.GlobalConstant.DIRECTORY;
@ -62,12 +48,6 @@ public class SourceController {
@Resource
private FileInfoService fileInfoService;
@Resource
private FileInfoController fileInfoControllerl;
@Value("${file.upload.path}")
private String uploadPath;
@PostMapping("/addDirectory")
@Operation(summary = "新增目录资源")
public ApiResponse addDirectory(@RequestBody AddDirectoryDto addDirectoryDto) {
@ -223,23 +203,75 @@ public class SourceController {
public ApiResponse uploadLocationImage(@RequestParam("ids") @Parameter(description = "上传定位图片ID列表") List<String> ids,
@RequestParam(value = "parentId", required = false) @Parameter(description = "父节点ID") String parentId,
@RequestParam(value = "treeIndex", required = false) @Parameter(description = "树状索引") Integer treeIndex,
@RequestParam(value = "params", required = false) @Parameter(description = "参数") String params,
@RequestParam(value = "sourceType", required = false) @Parameter(description = "资源类型") String sourceType,
@RequestParam("files") @Parameter(description = "带有定位的图片文件", required = true) MultipartFile[] files) {
// 验证并转换参数
sourceParamsValidator.validateAndConvert(
sourceType,
JsonUtil.jsonToMap(params)
);
List<Source> sources = new ArrayList<>();
for (int i = 0; i < files.length; i++) {
String detail = fileInfoControllerl.handleLocationImageUpload(files[i]);
Map<String, Object> dataMap = fileInfoService.handleLocationImageUpload(files[i]);
// 构建并保存资源对象
Source source = new Source();
source.setId(ids.get(i));
source.setSourceName(files[i].getOriginalFilename());
source.setParentId(parentId);
source.setSourceType("locationImage");
source.setSourceType(sourceType);
source.setTreeIndex(treeIndex);
source.setDetail(detail);
// 转换为对象
Point point = JsonUtil.mapToObject(JsonUtil.jsonToMap(params), Point.class);
point.setId(ids.get(i));
Point.Position position = new Point.Position();
point.setName(files[i].getOriginalFilename());
point.getLabel().setText(files[i].getOriginalFilename());
Object lonObj = dataMap.get("lon");
if (lonObj != null && lonObj instanceof Double) {
position.setLng((Double) lonObj);
}
Object latObj = dataMap.get("lat");
if (latObj != null && latObj instanceof Double) {
position.setLat((Double) latObj);
}
Object altObj = dataMap.get("alt");
if (altObj != null && altObj instanceof Double) {
position.setAlt((Double) altObj);
}
point.setPosition(position);
if ("linkImage".equals(sourceType)) {
// 设置地址
List<Point.Attribute.Link.LinkContent> list = new ArrayList<>();
Point.Attribute.Link.LinkContent linkContent = new Point.Attribute.Link.LinkContent();
linkContent.setName("带定位照片");
linkContent.setUrl(dataMap.get("url").toString());
list.add(linkContent);
point.getAttribute().getLink().setContent(list);
} else {
List<Point.Attribute.Vr.VrContent> list = new ArrayList<>();
Point.Attribute.Vr.VrContent vrContent = new Point.Attribute.Vr.VrContent();
vrContent.setName("带全景照片");
vrContent.setUrl(dataMap.get("url").toString());
list.add(vrContent);
point.getAttribute().getVr().setContent(list);
}
// 将 vrImage 转化为 JSON
source.setParams(JsonUtil.toJson(point));
source.setIsShow(SHOW);
sourceService.save(source);
// 添加资源到该用户的角色下
roleSourceService.addRoleSource(userService.getById(StpUtil.getLoginIdAsString()).getRoleId(), source.getId());
sources.add(source);
}
return ApiResponse.success(null);
return ApiResponse.success(sources);
}
@GetMapping("/type")

View File

@ -38,7 +38,7 @@ public class TacticalCalculationController {
// 同向而行:时间 = 距离 / (速度差)
double speedDiff = Math.abs(input.getSpeed1() - input.getSpeed2());
if (speedDiff <= 0) {
throw new IllegalArgumentException("同向而行时速度不能相等或后方速度小于前方");
throw new IllegalArgumentException("同向而行时速度不能相等或后方速度小于前方");
}
meetTimeHours = input.getInitialDistance() / speedDiff;
} else {

View File

@ -1,49 +1,34 @@
package com.yj.earth.business.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class Model implements Serializable {
private static final long serialVersionUID = 1L;
@NoArgsConstructor
@AllArgsConstructor
public class Model {
@Schema(description = "主键")
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
@Schema(description = "模型类型ID")
private String modelTypeId;
@Schema(description = "模型名称")
private String modelName;
@Schema(description = "模型类型")
private String modelType;
@Schema(description = "海报类型")
private String posterType;
@Schema(description = "海报")
private byte[] poster;
@Schema(description = "数据")
private byte[] data;
@Schema(description = "视图")
@Schema(description = "海报数据")
private String poster;
@Schema(description = "模型数据")
private String data;
@Schema(description = "模型视图")
private String view;
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@TableField(fill = FieldFill.UPDATE)
@Schema(description = "更新时间")
private LocalDateTime updatedAt;
}

View File

@ -1,35 +1,20 @@
package com.yj.earth.business.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class ModelType implements Serializable {
private static final long serialVersionUID = 1L;
public class ModelType {
@Schema(description = "主键")
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
@Schema(description = "模型类型名称")
private String name;
@Schema(description = "模型类型父级ID")
@Schema(description = "父级节点ID")
private String parentId;
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@Schema(description = "更新时间")
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updatedAt;
}

View File

@ -1,18 +0,0 @@
package com.yj.earth.business.mapper;
import com.yj.earth.business.domain.Model;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author 周志雄
* @since 2025-09-16
*/
@Mapper
public interface ModelMapper extends BaseMapper<Model> {
}

View File

@ -1,18 +0,0 @@
package com.yj.earth.business.mapper;
import com.yj.earth.business.domain.ModelType;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author 周志雄
* @since 2025-09-16
*/
@Mapper
public interface ModelTypeMapper extends BaseMapper<ModelType> {
}

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 {
}

View File

@ -11,9 +11,6 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ApiResponse handleException(Exception e) {
if (!e.getMessage().contains("No static resource")) {
log.error("全局异常处理:{}", e.getMessage());
}
return ApiResponse.failure(e.getMessage());
}

View File

@ -23,10 +23,10 @@ public class SaTokenConfig implements WebMvcConfigurer {
excludePathPatterns.add("/v3/api-docs/**");
excludePathPatterns.add("/fileInfo/download/**");
excludePathPatterns.add("/fileInfo/preview/**");
excludePathPatterns.add("/fileInfo/previewLocal/**");
excludePathPatterns.add("/data/clt/**");
excludePathPatterns.add("/data/mbtiles/**");
excludePathPatterns.add("/data/pak/**");
excludePathPatterns.add("/**");
// 注册 Sa-Token 拦截器
registry.addInterceptor(new SaInterceptor(handle -> {

View File

@ -44,7 +44,6 @@ public class ServerInitService {
public void checkDefaultData() {
checkDefaultUserAndRole();
checkDefaultSource();
}
public void checkDefaultUserAndRole() {
@ -73,98 +72,4 @@ public class ServerInitService {
userService.save(user);
}
}
public void checkDefaultSource() {
if(sourceService.count() == 0) {
// 查询管理员角色
Role adminRole = roleService.getOne(new LambdaQueryWrapper<Role>().eq(Role::getRoleName, "管理员"));
log.info("初始化默认资源数据");
Source source1 = new Source();
source1.setId(UUID.fastUUID().toString(true));
source1.setSourceName("倾斜模型");
source1.setSourceType("directory");
source1.setTreeIndex(0);
source1.setIsShow(1);
sourceService.save(source1);
roleSourceService.addRoleSource(adminRole.getId(), source1.getId());
Source source2 = new Source();
source2.setId(UUID.fastUUID().toString(true));
source2.setSourceName("人工模型");
source2.setSourceType("directory");
source2.setTreeIndex(0);
source2.setIsShow(1);
sourceService.save(source2);
roleSourceService.addRoleSource(adminRole.getId(), source2.getId());
Source source3 = new Source();
source3.setId(UUID.fastUUID().toString(true));
source3.setSourceName("卫星底图");
source3.setSourceType("directory");
source3.setTreeIndex(0);
source3.setIsShow(1);
sourceService.save(source3);
roleSourceService.addRoleSource(adminRole.getId(), source3.getId());
Source source4 = new Source();
source4.setId(UUID.fastUUID().toString(true));
source4.setSourceName("地形");
source4.setSourceType("directory");
source4.setTreeIndex(0);
source4.setIsShow(1);
sourceService.save(source4);
roleSourceService.addRoleSource(adminRole.getId(), source4.getId());
Source source5 = new Source();
source5.setId(UUID.fastUUID().toString(true));
source5.setSourceName("在线图源");
source5.setSourceType("directory");
source5.setTreeIndex(0);
source5.setIsShow(1);
sourceService.save(source5);
roleSourceService.addRoleSource(adminRole.getId(), source5.getId());
Source source6 = new Source();
source6.setId(UUID.fastUUID().toString(true));
source6.setSourceName("卫星图");
source6.setSourceType("arcgisWximagery");
source6.setParentId(source5.getId());
source6.setTreeIndex(0);
source6.setIsShow(1);
sourceService.save(source6);
roleSourceService.addRoleSource(adminRole.getId(), source6.getId());
Source source7 = new Source();
source7.setId(UUID.fastUUID().toString(true));
source7.setSourceName("暗黑地图");
source7.setSourceType("arcgisBlueImagery");
source7.setParentId(source5.getId());
source7.setTreeIndex(0);
source7.setIsShow(1);
sourceService.save(source7);
roleSourceService.addRoleSource(adminRole.getId(), source7.getId());
Source source8 = new Source();
source8.setId(UUID.fastUUID().toString(true));
source8.setSourceName("路网图");
source8.setSourceType("gdlwImagery");
source8.setParentId(source5.getId());
source8.setTreeIndex(0);
source8.setIsShow(1);
sourceService.save(source8);
roleSourceService.addRoleSource(adminRole.getId(), source8.getId());
Source source9 = new Source();
source9.setId(UUID.fastUUID().toString(true));
source9.setSourceName("矢量图");
source9.setSourceType("gdslImagery");
source9.setParentId(source5.getId());
source9.setTreeIndex(0);
source9.setIsShow(1);
sourceService.save(source9);
roleSourceService.addRoleSource(adminRole.getId(), source9.getId());
}
}
}

View File

@ -34,7 +34,7 @@ public class CodeUtil {
}
// 传入需要生成代码的表名
Generation("model");
Generation("business_config");
}
public static void Generation(String... tableName) {

View File

@ -0,0 +1,28 @@
package com.yj.earth.common.util;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class FileUtil {
/**
* 将本地文件转换为 MultipartFile
*/
public static MultipartFile convertToMultipartFile(File file) {
try (FileInputStream inputStream = new FileInputStream(file)) {
return new MockMultipartFile(
"files",
file.getName(),
MediaType.APPLICATION_OCTET_STREAM_VALUE,
inputStream
);
} catch (IOException e) {
return null;
}
}
}

View File

@ -47,4 +47,43 @@ public class JsonUtil {
return new HashMap<>(0);
}
}
/**
* 将 JSON 字符串转换为指定类型的对象
*/
public static <T> T jsonToObject(String json, Class<T> clazz) {
if (json == null || json.trim().isEmpty()) {
return null;
}
return objectMapper.convertValue(json, clazz);
}
/**
* 将 Map 转换为指定类型的对象
*/
public static <T> T mapToObject(Map<String, Object> map, Class<T> clazz) {
if (map == null || clazz == null) {
return null;
}
try {
// 使用ObjectMapper将Map转换为指定类型对象
return objectMapper.convertValue(map, clazz);
} catch (IllegalArgumentException e) {
log.error("Map转对象失败、目标类型: {}, Map内容: {}", clazz.getName(), map, e);
return null;
}
}
/**
* 将任意 Object 转化为 JSON
*/
public static String toJson(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.error("对象转JSON失败", e);
return null;
}
}
}

View File

@ -0,0 +1,334 @@
package com.yj.earth.common.util;
import java.sql.*;
import java.util.*;
import java.lang.reflect.*;
import java.util.Date;
import java.time.LocalDateTime; // 新增导入
import java.time.format.DateTimeFormatter; // 新增导入
import java.time.format.DateTimeParseException; // 新增导入
public class SQLiteUtil {
// 加载 SQLite JDBC 驱动
static {
try {
Class.forName("org.sqlite.JDBC");
} catch (ClassNotFoundException e) {
throw new RuntimeException("无法加载SQLite JDBC驱动", e);
}
}
// 统一日期格式匹配数据库TEXT字段存储的格式yyyy-MM-dd'T'HH:mm:ss.SSS
private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
/**
* 根据数据库文件绝对路径获取连接
*/
public static Connection getConnection(String dbFilePath) throws SQLException {
String url = "jdbc:sqlite:" + dbFilePath;
return DriverManager.getConnection(url);
}
/**
* 执行查询并返回单个对象
*/
public static <T> T queryForObject(String dbFilePath, String sql, List<Object> params, Class<T> clazz) throws SQLException, IllegalAccessException, InstantiationException {
List<T> results = queryForList(dbFilePath, sql, params, clazz);
return results.isEmpty() ? null : results.get(0);
}
/**
* 执行查询并返回对象列表
*/
public static <T> List<T> queryForList(String dbFilePath, String sql, List<Object> params, Class<T> clazz)
throws SQLException, IllegalAccessException, InstantiationException {
List<T> resultList = new ArrayList<>();
// 使用try-with-resources确保资源自动关闭
try (Connection conn = getConnection(dbFilePath);
PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) {
// 执行查询
try (ResultSet rs = pstmt.executeQuery()) {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
// 处理结果集
while (rs.next()) {
T obj = clazz.newInstance();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
Object value = rs.getObject(i);
// 设置对象属性(自动处理类型转换)
setFieldValue(obj, columnName, value);
}
resultList.add(obj);
}
}
}
return resultList;
}
/**
* 执行增删改SQL语句
*/
public static int executeUpdate(String dbFilePath, String sql, List<Object> params) throws SQLException {
try (Connection conn = getConnection(dbFilePath);
PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) {
return pstmt.executeUpdate();
}
}
/**
* 创建并设置参数化的PreparedStatement关键处理LocalDateTime转String
*/
private static PreparedStatement createPreparedStatement(Connection conn, String sql, List<Object> params)
throws SQLException {
PreparedStatement pstmt = conn.prepareStatement(sql);
if (params != null && !params.isEmpty()) {
int index = 1;
for (Object value : params) {
// 新增LocalDateTime类型转为String、适配SQLite的TEXT字段
if (value instanceof LocalDateTime) {
String dateStr = ((LocalDateTime) value).format(LOCAL_DATE_TIME_FORMATTER);
pstmt.setObject(index++, dateStr);
}
// 新增byte[]类型显式指定为BLOB避免SQLite自动转换异常
else if (value instanceof byte[]) {
pstmt.setBytes(index++, (byte[]) value);
} else {
pstmt.setObject(index++, value);
}
}
}
return pstmt;
}
/**
* 通过反射设置对象的字段值
*/
private static void setFieldValue(Object obj, String columnName, Object value) {
try {
Field field = findField(obj.getClass(), columnName);
if (field != null) {
field.setAccessible(true);
Object convertedValue = convertValue(field.getType(), value);
field.set(obj, convertedValue);
}
} catch (IllegalAccessException e) {
System.err.println("警告: 无法设置字段 " + columnName + " 的值 - " + e.getMessage());
}
}
/**
* 查找字段、支持下划线命名转驼峰命名如created_at → createdAt
*/
private static Field findField(Class<?> clazz, String columnName) {
// 1. 直接匹配字段名(如数据库列名与字段名一致)
try {
return clazz.getDeclaredField(columnName);
} catch (NoSuchFieldException e) {
// 2. 下划线转驼峰后匹配如created_at → createdAt
String camelCaseName = underscoreToCamelCase(columnName);
try {
return clazz.getDeclaredField(camelCaseName);
} catch (NoSuchFieldException e1) {
// 3. 递归查找父类(支持继承场景)
if (clazz.getSuperclass() != null) {
return findField(clazz.getSuperclass(), columnName);
}
return null;
}
}
}
/**
* 下划线命名转驼峰命名(工具方法)
*/
private static String underscoreToCamelCase(String str) {
if (str == null || str.isEmpty()) {
return str;
}
StringBuilder sb = new StringBuilder();
boolean nextUpperCase = false;
for (char c : str.toCharArray()) {
if (c == '_') {
nextUpperCase = true;
} else {
if (nextUpperCase) {
sb.append(Character.toUpperCase(c));
nextUpperCase = false;
} else {
// 修正首字母小写如CREATED_AT → createdAt、而非CreatedAt
sb.append(Character.toLowerCase(c));
}
}
}
return sb.toString();
}
/**
* 核心类型转换新增LocalDateTime解析、优化BLOB适配
*/
private static Object convertValue(Class<?> targetType, Object value) {
if (value == null) {
return null;
}
// 类型已匹配、直接返回
if (targetType.isInstance(value)) {
return value;
}
// 1. 数字类型转换int/long/double等
if (targetType == Integer.class || targetType == int.class) {
return ((Number) value).intValue();
} else if (targetType == Long.class || targetType == long.class) {
return ((Number) value).longValue();
} else if (targetType == Double.class || targetType == double.class) {
return ((Number) value).doubleValue();
} else if (targetType == Float.class || targetType == float.class) {
return ((Number) value).floatValue();
}
// 2. 布尔类型转换(支持数字/字符串转布尔)
else if (targetType == Boolean.class || targetType == boolean.class) {
if (value instanceof Number) {
return ((Number) value).intValue() != 0;
} else if (value instanceof String) {
return "true".equalsIgnoreCase((String) value);
}
}
// 3. 字符串类型转换所有类型转String
else if (targetType == String.class) {
return value.toString();
}
// 4. 日期类型转换java.util.Date
else if (targetType == Date.class) {
if (value instanceof Timestamp) {
return new Date(((Timestamp) value).getTime());
} else if (value instanceof LocalDateTime) {
// 支持LocalDateTime转Date如需
return Date.from(((LocalDateTime) value).atZone(java.time.ZoneId.systemDefault()).toInstant());
}
}
// 5. 新增LocalDateTime类型转换SQLite TEXT → Java LocalDateTime
else if (targetType == LocalDateTime.class) {
if (value instanceof String) {
try {
// 解析数据库存储的ISO格式字符串如"2025-09-18T17:30:27.143"
return LocalDateTime.parse((String) value, LOCAL_DATE_TIME_FORMATTER);
} catch (DateTimeParseException e) {
System.err.println("警告: 日期解析失败、字符串=" + value + "、格式应为yyyy-MM-dd'T'HH:mm:ss.SSS - " + e.getMessage());
return null;
}
} else if (value instanceof Timestamp) {
// 兼容Timestamp类型如其他数据库迁移场景
return ((Timestamp) value).toLocalDateTime();
}
}
// 6. 新增byte[]类型转换SQLite BLOB → Java byte[]
else if (targetType == byte[].class && value instanceof Blob) {
Blob blob = (Blob) value;
try {
return blob.getBytes(1, (int) blob.length());
} catch (SQLException e) {
System.err.println("警告: BLOB转byte[]失败 - " + e.getMessage());
return null;
}
}
// 无法转换时返回原始值(避免崩溃、打印警告)
System.err.println("警告: 不支持的类型转换、目标类型=" + targetType.getName() + "、原始值类型=" + value.getClass().getName());
return value;
}
/**
* 执行DDL语句CREATE, ALTER, DROP等
*/
private static void executeDDL(String dbFilePath, String sql, List<Object> params) throws SQLException {
try (Connection conn = getConnection(dbFilePath);
PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) {
pstmt.execute();
}
}
/**
* 执行无参数的DDL语句
*/
public static void executeDDL(String dbFilePath, String sql) {
try {
executeDDL(dbFilePath, sql, null);
} catch (SQLException e) {
throw new RuntimeException("执行DDL语句失败、SQL=" + sql, e);
}
}
/**
* 执行查询并返回count结果
*/
public static int queryForCount(String dbFilePath, String sql, List<Object> params) throws SQLException {
try (Connection conn = getConnection(dbFilePath);
PreparedStatement pstmt = createPreparedStatement(conn, sql, params);
ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return rs.getInt(1);
}
return 0;
}
}
/**
* 执行无参数的查询并返回count结果
*/
public static int queryForCount(String dbFilePath, String sql) throws SQLException {
return queryForCount(dbFilePath, sql, null);
}
/**
* 初始化数据库表model_type + model
*/
public static void initialization(String modelPath) {
// 创建模型类型表
String sql = """
CREATE TABLE "model_type" (
"id" TEXT,
"name" TEXT,
"parent_id" TEXT,
"created_at" TEXT,
"updated_at" TEXT,
PRIMARY KEY ("id")
);
""";
executeDDL(modelPath, sql);
// 创建模型表
sql = """
CREATE TABLE "model" (
"id" TEXT,
"model_type_id" TEXT,
"model_name" TEXT,
"model_type" TEXT,
"poster_type" TEXT,
"poster" TEXT,
"data" TEXT,
"view" TEXT,
"created_at" TEXT,
"updated_at" TEXT,
PRIMARY KEY ("id")
);
""";
executeDDL(modelPath, sql);
}
}

View File

@ -47,8 +47,8 @@ public class DatabaseManager {
classes.add(Source.class);
classes.add(RoleSource.class);
classes.add(FileInfo.class);
classes.add(ModelType.class);
classes.add(Model.class);
classes.add(ModelLibrary.class);
classes.add(BusinessConfig.class);
ENTITY_CLASSES = Collections.unmodifiableList(classes);
}
@ -292,7 +292,7 @@ public class DatabaseManager {
}
}
private static Path getRecommendedCacheDirectory() {
public static Path getRecommendedCacheDirectory() {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
String appData = System.getenv("APPDATA");

View File

@ -0,0 +1,24 @@
package com.yj.earth.design;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class BusinessConfig {
@Schema(description = "主键")
private String id;
@Schema(description = "")
private String key;
@Schema(description = "")
private String value;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "更新时间")
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,22 @@
package com.yj.earth.design;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ModelLibrary {
@Schema(description = "主键")
private String id;
@Schema(description = "模型库路径")
private String path;
@Schema(description = "模型库名称")
private String name;
@Schema(description = "是否启用")
private Integer isEnable;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "更新时间")
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,13 @@
package com.yj.earth.dto.businessConfig;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class AddBusinessConfigDto {
@Schema(description = "")
private String key;
@Schema(description = "")
private String value;
}

View File

@ -0,0 +1,12 @@
package com.yj.earth.dto.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class AddModelTypeDto {
@Schema(description = "模型类型名称")
private String name;
@Schema(description = "父级节点ID")
private String parentId;
}

View File

@ -4,9 +4,9 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class CreateModelFileDto {
public class CreateModelLibraryDto {
@Schema(description = "模型库文件名称")
private String modelFileName;
private String name;
@Schema(description = "生成文件夹路径")
private String folderPath;
private String path;
}

View File

@ -0,0 +1,134 @@
package com.yj.earth.params;
import com.yj.earth.annotation.SourceType;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@SourceType("linkImage")
public class LinkImage {
private String id;
private boolean show;
private String name;
private Position position;
private int heightMode;
private boolean scaleByDistance;
private int near;
private int far;
private Billboard billboard;
private Label label;
private Attribute attribute;
private String richTextContent;
private CustomView customView;
@Data
@NoArgsConstructor
public static class Position {
private double lng;
private double lat;
private double alt;
}
@Data
@NoArgsConstructor
public static class Billboard {
private boolean show;
private String image;
private String defaultImage;
private int scale;
}
@Data
@NoArgsConstructor
public static class Label {
private String text;
private boolean show;
private int fontFamily;
private int fontSize;
private String color;
}
@Data
@NoArgsConstructor
public static class Attribute {
private Link link;
private Vr vr;
private Camera camera;
private Isc isc;
private Goods goods;
@Data
@NoArgsConstructor
public static class Link {
private List<LinkContent> content;
@Data
@NoArgsConstructor
public static class LinkContent {
private String name;
private String url;
}
}
@Data
@NoArgsConstructor
public static class Vr {
private List<VrContent> content;
@Data
@NoArgsConstructor
public static class VrContent {
private String name;
private String url;
}
}
@Data
@NoArgsConstructor
public static class Camera {
private List<Object> content;
}
@Data
@NoArgsConstructor
public static class Isc {
private List<Object> content;
}
@Data
@NoArgsConstructor
public static class Goods {
private List<GoodsContent> content;
@Data
@NoArgsConstructor
public static class GoodsContent {
private String id;
private String name;
private String cnt;
}
}
}
@Data
@NoArgsConstructor
public static class CustomView {
private Orientation orientation;
private RelativePosition relativePosition;
@Data
@NoArgsConstructor
public static class Orientation {
private double heading;
private double pitch;
private double roll;
}
@Data
@NoArgsConstructor
public static class RelativePosition {
private double lng;
private double lat;
private double alt;
}
}
}

View File

@ -2,9 +2,12 @@ package com.yj.earth.params;
import com.yj.earth.annotation.SourceType;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@SourceType("point")
public class Point {
private String id;
@ -22,6 +25,7 @@ public class Point {
private CustomView customView;
@Data
@NoArgsConstructor
public static class Position {
private double lng;
private double lat;
@ -29,6 +33,7 @@ public class Point {
}
@Data
@NoArgsConstructor
public static class Billboard {
private boolean show;
private String image;
@ -37,6 +42,7 @@ public class Point {
}
@Data
@NoArgsConstructor
public static class Label {
private String text;
private boolean show;
@ -46,6 +52,7 @@ public class Point {
}
@Data
@NoArgsConstructor
public static class Attribute {
private Link link;
private Vr vr;
@ -54,9 +61,11 @@ public class Point {
private Goods goods;
@Data
@NoArgsConstructor
public static class Link {
private List<LinkContent> content;
@Data
@NoArgsConstructor
public static class LinkContent {
private String name;
private String url;
@ -64,9 +73,11 @@ public class Point {
}
@Data
@NoArgsConstructor
public static class Vr {
private List<VrContent> content;
@Data
@NoArgsConstructor
public static class VrContent {
private String name;
private String url;
@ -74,19 +85,23 @@ public class Point {
}
@Data
@NoArgsConstructor
public static class Camera {
private List<Object> content;
}
@Data
@NoArgsConstructor
public static class Isc {
private List<Object> content;
}
@Data
@NoArgsConstructor
public static class Goods {
private List<GoodsContent> content;
@Data
@NoArgsConstructor
public static class GoodsContent {
private String id;
private String name;
@ -96,10 +111,12 @@ public class Point {
}
@Data
@NoArgsConstructor
public static class CustomView {
private Orientation orientation;
private RelativePosition relativePosition;
@Data
@NoArgsConstructor
public static class Orientation {
private double heading;
private double pitch;
@ -107,6 +124,7 @@ public class Point {
}
@Data
@NoArgsConstructor
public static class RelativePosition {
private double lng;
private double lat;

View File

@ -0,0 +1,12 @@
package com.yj.earth.params;
import com.yj.earth.annotation.SourceType;
import lombok.Data;
@Data
@SourceType("terrain")
public class Terrain {
private String id;
private String name;
private boolean show;
}

View File

@ -0,0 +1,134 @@
package com.yj.earth.params;
import com.yj.earth.annotation.SourceType;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@SourceType("vrImage")
public class VrImage {
private String id;
private boolean show;
private String name;
private Position position;
private int heightMode;
private boolean scaleByDistance;
private int near;
private int far;
private Billboard billboard;
private Label label;
private Attribute attribute;
private String richTextContent;
private CustomView customView;
@Data
@NoArgsConstructor
public static class Position {
private double lng;
private double lat;
private double alt;
}
@Data
@NoArgsConstructor
public static class Billboard {
private boolean show;
private String image;
private String defaultImage;
private int scale;
}
@Data
@NoArgsConstructor
public static class Label {
private String text;
private boolean show;
private int fontFamily;
private int fontSize;
private String color;
}
@Data
@NoArgsConstructor
public static class Attribute {
private Link link;
private Vr vr;
private Camera camera;
private Isc isc;
private Goods goods;
@Data
@NoArgsConstructor
public static class Link {
private List<LinkContent> content;
@Data
@NoArgsConstructor
public static class LinkContent {
private String name;
private String url;
}
}
@Data
@NoArgsConstructor
public static class Vr {
private List<VrContent> content;
@Data
@NoArgsConstructor
public static class VrContent {
private String name;
private String url;
}
}
@Data
@NoArgsConstructor
public static class Camera {
private List<Object> content;
}
@Data
@NoArgsConstructor
public static class Isc {
private List<Object> content;
}
@Data
@NoArgsConstructor
public static class Goods {
private List<GoodsContent> content;
@Data
@NoArgsConstructor
public static class GoodsContent {
private String id;
private String name;
private String cnt;
}
}
}
@Data
@NoArgsConstructor
public static class CustomView {
private Orientation orientation;
private RelativePosition relativePosition;
@Data
@NoArgsConstructor
public static class Orientation {
private double heading;
private double pitch;
private double roll;
}
@Data
@NoArgsConstructor
public static class RelativePosition {
private double lng;
private double lat;
private double alt;
}
}
}

View File

@ -0,0 +1,40 @@
package com.yj.earth.vo;
import com.yj.earth.business.domain.ModelType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ModelTypeVo {
@Schema(description = "主键")
private String id;
@Schema(description = "模型类型名称")
private String name;
@Schema(description = "父级节点ID")
private String parentId;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "更新时间")
private LocalDateTime updatedAt;
@Schema(description = "子节点列表")
private List<ModelTypeVo> children = new ArrayList<>();
public ModelTypeVo(ModelType modelType) {
this.id = modelType.getId();
this.name = modelType.getName();
this.parentId = modelType.getParentId();
this.createdAt = modelType.getCreatedAt();
this.updatedAt = modelType.getUpdatedAt();
}
}

View File

@ -0,0 +1,9 @@
package com.yj.earth.vo;
import com.yj.earth.business.domain.Model;
import lombok.Data;
@Data
public class ModelVo extends Model {
private String modelTypeName;
}

View File

@ -38,10 +38,6 @@ encrypt:
aes:
key: "ah62ks8dj7dh3yd6"
file:
upload:
path: upload
graphhopper:
graphLocation: ./target/graphhopper
profiles:

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yj.earth.business.mapper.ModelMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.yj.earth.business.domain.Model">
<id column="id" property="id" />
<result column="model_type_id" property="modelTypeId" />
<result column="model_name" property="modelName" />
<result column="model_type" property="modelType" />
<result column="poster_type" property="posterType" />
<result column="poster" property="poster" />
<result column="data" property="data" />
<result column="view" property="view" />
<result column="created_at" property="createdAt" />
<result column="updated_at" property="updatedAt" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, model_type_id, model_name, model_type, poster_type, poster, data, view, created_at, updated_at
</sql>
</mapper>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yj.earth.business.mapper.ModelTypeMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.yj.earth.business.domain.ModelType">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="parent_id" property="parentId" />
<result column="created_at" property="createdAt" />
<result column="updated_at" property="updatedAt" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, name, parent_id, created_at, updated_at
</sql>
</mapper>