模型库、矢量文件
This commit is contained in:
7
pom.xml
7
pom.xml
@ -175,6 +175,13 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- DBCP2 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-dbcp2</artifactId>
|
||||
<version>2.10.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- YAML -->
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
|
||||
@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
|
||||
@Tag(name = "业务配置管理")
|
||||
@CheckAuth
|
||||
@RestController
|
||||
|
||||
@ -4,21 +4,27 @@ import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.yj.earth.annotation.CheckAuth;
|
||||
import com.yj.earth.business.domain.MilitaryLibrary;
|
||||
import com.yj.earth.business.domain.MilitaryType;
|
||||
import com.yj.earth.business.domain.MilitaryLibrary; // 需自定义:对应军标库配置表(同ModelLibrary结构)
|
||||
import com.yj.earth.business.domain.MilitaryType; // 需自定义:对应military_type表实体
|
||||
import com.yj.earth.business.service.FileInfoService;
|
||||
import com.yj.earth.business.service.MilitaryLibraryService;
|
||||
import com.yj.earth.business.service.MilitaryLibraryService; // 需自定义:军标库Service(同ModelLibraryService逻辑)
|
||||
import com.yj.earth.common.util.ApiResponse;
|
||||
import com.yj.earth.common.util.FileCommonUtil;
|
||||
import com.yj.earth.common.util.SQLiteUtil;
|
||||
import com.yj.earth.dto.militaryLibrary.AddMilitaryTypeDto;
|
||||
import com.yj.earth.dto.militaryLibrary.CreateMilitaryLibraryDto;
|
||||
import com.yj.earth.dto.militaryLibrary.DragMilitaryTypeDto;
|
||||
import com.yj.earth.dto.militaryLibrary.UpdateMilitaryTypeNameDto;
|
||||
import com.yj.earth.vo.MilitaryTypeVo;
|
||||
import com.yj.earth.vo.MilitaryVo;
|
||||
import com.yj.earth.dto.militaryLibrary.AddMilitaryTypeDto; // 需自定义:添加军标类型DTO
|
||||
import com.yj.earth.dto.militaryLibrary.CreateMilitaryLibraryDto; // 需自定义:创建军标库DTO
|
||||
import com.yj.earth.dto.militaryLibrary.DragMilitaryTypeDto; // 需自定义:拖动军标类型DTO
|
||||
import com.yj.earth.dto.militaryLibrary.UpdateMilitaryTypeNameDto; // 需自定义:修改军标类型名称DTO
|
||||
import com.yj.earth.vo.MilitaryDataVo;
|
||||
import com.yj.earth.vo.MilitaryTypeVo; // 需自定义:军标类型树形VO(同ModelTypeVo结构)
|
||||
import com.yj.earth.vo.MilitaryVo; // 需自定义:军标列表VO(剔除海报相关字段)
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@ -26,9 +32,12 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@ -38,44 +47,52 @@ import java.util.stream.Collectors;
|
||||
@RestController
|
||||
@RequestMapping("/militaryLibrary")
|
||||
public class MilitaryLibraryController {
|
||||
|
||||
@Resource
|
||||
private MilitaryLibraryService militaryLibraryService;
|
||||
@Resource
|
||||
private FileInfoService fileInfoService;
|
||||
|
||||
@Operation(summary = "创建军标库")
|
||||
@PostMapping("/createMilitaryLibrary")
|
||||
public ApiResponse createMilitaryLibrary(@RequestBody CreateMilitaryLibraryDto createMilitaryLibraryDto) {
|
||||
public ApiResponse createMilitaryLibrary(@RequestBody CreateMilitaryLibraryDto createDto) {
|
||||
try {
|
||||
// 参数校验
|
||||
String folderPath = createMilitaryLibraryDto.getPath();
|
||||
String militaryName = createMilitaryLibraryDto.getName();
|
||||
// 处理路径、组合为完整军标库文件路径
|
||||
// 参数校验与路径处理
|
||||
String folderPath = createDto.getPath();
|
||||
String libraryName = createDto.getName();
|
||||
if (!StringUtils.hasText(folderPath) || !StringUtils.hasText(libraryName)) {
|
||||
return ApiResponse.failure("路径和名称不能为空");
|
||||
}
|
||||
|
||||
// 构建完整军标库文件路径(SQLite文件)
|
||||
File parentDir = new File(folderPath);
|
||||
File militaryFile = new File(parentDir, militaryName);
|
||||
File militaryFile = new File(parentDir, libraryName);
|
||||
String militaryPath = militaryFile.getAbsolutePath().replace("\\", "/");
|
||||
// 检查父目录是否存在、不存在则创建
|
||||
|
||||
// 父目录不存在则创建
|
||||
if (!parentDir.exists()) {
|
||||
boolean mkdirsSuccess = parentDir.mkdirs();
|
||||
if (!mkdirsSuccess) {
|
||||
return ApiResponse.failure("创建父目录失败:" + folderPath);
|
||||
}
|
||||
}
|
||||
// 检查军标库文件是否已存在
|
||||
|
||||
// 校验军标库文件是否已存在
|
||||
if (militaryFile.exists()) {
|
||||
if (militaryFile.isDirectory()) {
|
||||
return ApiResponse.failure("同名目录已存在、无法创建文件:" + militaryPath);
|
||||
return ApiResponse.failure("同名目录已存在,无法创建文件:" + militaryPath);
|
||||
}
|
||||
return ApiResponse.failure("军标库文件已存在:" + militaryPath);
|
||||
}
|
||||
// 创建军标库文件
|
||||
boolean createSuccess = militaryFile.createNewFile();
|
||||
if (!createSuccess) {
|
||||
|
||||
// 创建SQLite文件
|
||||
boolean createFileSuccess = militaryFile.createNewFile();
|
||||
if (!createFileSuccess) {
|
||||
return ApiResponse.failure("创建军标库文件失败:" + militaryPath);
|
||||
}
|
||||
// 添加军标库信息
|
||||
addMilitaryLibrary(militaryPath);
|
||||
|
||||
// 初始化军标库表结构
|
||||
SQLiteUtil.initializationMilitary(militaryPath);
|
||||
// 添加军标库配置到数据库
|
||||
addMilitaryLibrary(militaryPath);
|
||||
return ApiResponse.success(null);
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.failure("创建军标库失败:" + e.getMessage());
|
||||
@ -84,304 +101,441 @@ public class MilitaryLibraryController {
|
||||
|
||||
@Operation(summary = "导入军标库")
|
||||
@PostMapping("/importMilitaryLibrary")
|
||||
public ApiResponse importMilitaryLibrary(@RequestParam("militaryPath") @Parameter(description = "军标库路径") String militaryPath) {
|
||||
addMilitaryLibrary(militaryPath);
|
||||
return ApiResponse.success(null);
|
||||
public ApiResponse importMilitaryLibrary(
|
||||
@RequestParam("militaryPath") @Parameter(description = "军标库SQLite文件路径") String militaryPath) {
|
||||
try {
|
||||
// 校验路径是否存在
|
||||
File militaryFile = new File(militaryPath);
|
||||
if (!militaryFile.exists() || !militaryFile.isFile()) {
|
||||
return ApiResponse.failure("军标库文件不存在:" + militaryPath);
|
||||
}
|
||||
// 添加到配置表并启用
|
||||
addMilitaryLibrary(militaryPath);
|
||||
return ApiResponse.success(null);
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.failure("导入军标库失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "添加军标类型")
|
||||
@PostMapping("/addMilitaryType")
|
||||
public ApiResponse addMilitaryType(@RequestBody AddMilitaryTypeDto addMilitaryTypeDto) throws SQLException, IllegalAccessException, InstantiationException {
|
||||
public ApiResponse addMilitaryType(@RequestBody AddMilitaryTypeDto addDto) throws SQLException, IllegalAccessException, InstantiationException {
|
||||
// 获取当前启用的军标库路径
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
// 检查父级是否存在
|
||||
String parentId = addMilitaryTypeDto.getParentId();
|
||||
if (parentId != null) {
|
||||
String sql = "SELECT * FROM military_type WHERE id = ?";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(parentId);
|
||||
MilitaryType militaryType = SQLiteUtil.queryForObject(militaryPath, sql, params, MilitaryType.class);
|
||||
if (militaryType == null) {
|
||||
return ApiResponse.failure("父级军标类型不存在");
|
||||
|
||||
// 校验父级类型(若有)
|
||||
String parentId = addDto.getParentId();
|
||||
if (StringUtils.hasText(parentId)) {
|
||||
String checkParentSql = "SELECT id FROM military_type WHERE id = ?";
|
||||
List<Object> parentParams = new ArrayList<>();
|
||||
parentParams.add(parentId);
|
||||
MilitaryType parentType = SQLiteUtil.queryForObject(
|
||||
militaryPath, checkParentSql, parentParams, MilitaryType.class
|
||||
);
|
||||
if (parentType == null) {
|
||||
return ApiResponse.failure("父级军标类型不存在:" + parentId);
|
||||
}
|
||||
}
|
||||
String sql = "INSERT INTO military_type " +
|
||||
|
||||
// 插入军标类型
|
||||
String insertSql = "INSERT INTO military_type " +
|
||||
"(id, name, parent_id, tree_index, created_at) " +
|
||||
"VALUES (?, ?, ?, ?, ?)";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(UUID.fastUUID().toString(true));
|
||||
params.add(addMilitaryTypeDto.getName());
|
||||
params.add(addMilitaryTypeDto.getParentId());
|
||||
params.add(addDto.getName());
|
||||
params.add(parentId);
|
||||
params.add(0);
|
||||
params.add(LocalDateTime.now());
|
||||
SQLiteUtil.executeUpdate(militaryPath, sql, params);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "删除军标类型")
|
||||
@PostMapping("/deleteMilitaryType")
|
||||
public ApiResponse deleteMilitaryType(@Parameter(description = "军标类型ID") @RequestParam("militaryTypeId") String militaryTypeId) throws SQLException {
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
String sql = "DELETE FROM military_type WHERE id = ?";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(militaryTypeId);
|
||||
SQLiteUtil.executeUpdate(militaryPath, sql, params);
|
||||
params.add(LocalDateTime.now().toString());
|
||||
SQLiteUtil.executeUpdate(militaryPath, insertSql, params);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "修改军标类型名称")
|
||||
@PostMapping("/updateMilitaryTypeName")
|
||||
public ApiResponse updateMilitaryTypeName(@RequestBody UpdateMilitaryTypeNameDto updateMilitaryTypeNameDto) throws SQLException {
|
||||
public ApiResponse updateMilitaryTypeName(@RequestBody UpdateMilitaryTypeNameDto updateDto)
|
||||
throws SQLException {
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
String sql = "UPDATE military_type SET name = ? WHERE id = ?";
|
||||
|
||||
// 执行更新
|
||||
String updateSql = "UPDATE military_type SET name = ?, updated_at = ? WHERE id = ?";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(updateMilitaryTypeNameDto.getName());
|
||||
params.add(updateMilitaryTypeNameDto.getId());
|
||||
SQLiteUtil.executeUpdate(militaryPath, sql, params);
|
||||
params.add(updateDto.getName());
|
||||
params.add(LocalDateTime.now().toString());
|
||||
params.add(updateDto.getId());
|
||||
SQLiteUtil.executeUpdate(militaryPath, updateSql, params);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "军标类型列表")
|
||||
@GetMapping("/militaryTypeList")
|
||||
@Operation(summary = "删除军标类型")
|
||||
@PostMapping("/deleteMilitaryType")
|
||||
public ApiResponse deleteMilitaryType(
|
||||
@RequestParam("militaryTypeId") @Parameter(description = "军标类型ID") String typeId)
|
||||
throws SQLException {
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
|
||||
// 执行删除
|
||||
String deleteSql = "DELETE FROM military_type WHERE id = ?";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(typeId);
|
||||
|
||||
SQLiteUtil.executeUpdate(militaryPath, deleteSql, params);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "军标类型树形列表")
|
||||
@GetMapping("/militaryTypeTree")
|
||||
public ApiResponse militaryTypeTree() throws SQLException, IllegalAccessException, InstantiationException {
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
// 查询所有军标类型
|
||||
String sql = """
|
||||
SELECT id, name, parent_id as parentId,
|
||||
tree_index as treeIndex, created_at as createdAt,
|
||||
updated_at as updatedAt FROM military_type ORDER BY tree_index ASC
|
||||
""";
|
||||
List<MilitaryType> militaryTypes = SQLiteUtil.queryForList(militaryPath, sql, null, MilitaryType.class);
|
||||
// 转换为树形结构
|
||||
List<MilitaryTypeVo> treeList = buildMilitaryTypeTree(militaryTypes);
|
||||
List<MilitaryTypeVo> treeList = militaryTypeList();
|
||||
return ApiResponse.success(treeList);
|
||||
}
|
||||
|
||||
@Operation(summary = "拖动军标类型树")
|
||||
@PostMapping("/dragMilitaryType")
|
||||
public ApiResponse dragMilitaryType(@RequestBody DragMilitaryTypeDto dragMilitaryTypeDto) throws SQLException {
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
|
||||
// 动态构建SQL和参数
|
||||
List<String> updateFields = new ArrayList<>();
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
// 判断 parentId 是否存在
|
||||
if (dragMilitaryTypeDto.getParentId() != null) {
|
||||
updateFields.add("parent_id = ?");
|
||||
params.add(dragMilitaryTypeDto.getParentId());
|
||||
}
|
||||
|
||||
// 判断 treeIndex 是否存在
|
||||
if (dragMilitaryTypeDto.getTreeIndex() != null) {
|
||||
updateFields.add("tree_index = ?");
|
||||
params.add(dragMilitaryTypeDto.getTreeIndex());
|
||||
}
|
||||
|
||||
// 构建完整 SQL
|
||||
String sql = "UPDATE military_type SET " + String.join(", ", updateFields) + " WHERE id = ?";
|
||||
params.add(dragMilitaryTypeDto.getId());
|
||||
SQLiteUtil.executeUpdate(militaryPath, sql, params);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "添加军标文件")
|
||||
@PostMapping("/addMilitaryFile")
|
||||
public ApiResponse addMilitaryFile(@RequestParam("files") MultipartFile[] files, @Parameter(description = "军标类型ID") @RequestParam("militaryTypeId") String militaryTypeId) throws IOException, SQLException {
|
||||
// 获取最新的军标库路径
|
||||
public ApiResponse addMilitaryFile(@RequestParam("files") MultipartFile[] files, @RequestParam("militaryTypeId") @Parameter(description = "军标类型ID") String typeId) throws IOException, SQLException, IllegalAccessException, InstantiationException {
|
||||
// 获取当前启用的军标库
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
// 循环处理每个上传的文件
|
||||
|
||||
// 校验类型是否存在
|
||||
if (!isMilitaryTypeExist(militaryPath, typeId)) {
|
||||
return ApiResponse.failure("军标类型不存在:" + typeId);
|
||||
}
|
||||
|
||||
// 循环处理每个文件
|
||||
for (MultipartFile file : files) {
|
||||
// 跳过空文件
|
||||
if (file.isEmpty()) {
|
||||
continue;
|
||||
continue; // 跳过空文件
|
||||
}
|
||||
// 获取文件信息
|
||||
String fileName = file.getOriginalFilename();
|
||||
if (fileName == null) {
|
||||
continue;
|
||||
}
|
||||
String fileSuffix = FileUtil.extName(fileName);
|
||||
String fileNameWithoutSuffix = FileUtil.mainName(fileName);
|
||||
|
||||
// 构建插入SQL
|
||||
String sql = "INSERT INTO military " +
|
||||
"(id, military_type_id, military_name, military_type, data, created_at) " +
|
||||
// 解析文件名与后缀
|
||||
String originalFileName = file.getOriginalFilename();
|
||||
if (originalFileName == null) {
|
||||
continue;
|
||||
}
|
||||
String fileSuffix = FileUtil.extName(originalFileName);
|
||||
String fileNameWithoutSuffix = FileUtil.mainName(originalFileName);
|
||||
|
||||
// 插入军标数据
|
||||
String insertSql = "INSERT INTO military " +
|
||||
"(id, military_type_id, military_name, military_type, military_data, created_at) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?)";
|
||||
|
||||
String url = fileInfoService.uploadWithPreview(file);
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(UUID.fastUUID().toString(true));
|
||||
params.add(militaryTypeId);
|
||||
params.add(typeId);
|
||||
params.add(fileNameWithoutSuffix);
|
||||
params.add(fileSuffix);
|
||||
params.add(url);
|
||||
params.add(LocalDateTime.now());
|
||||
params.add(file.getBytes());
|
||||
params.add(LocalDateTime.now().toString());
|
||||
|
||||
// 执行插入操作
|
||||
SQLiteUtil.executeUpdate(militaryPath, sql, params);
|
||||
SQLiteUtil.executeUpdate(militaryPath, insertSql, params);
|
||||
}
|
||||
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据军标类型查看军标列表")
|
||||
@Operation(summary = "获取军标文件数据")
|
||||
@GetMapping("/data/military/{militaryId}/{fileSuffix}")
|
||||
public ResponseEntity<byte[]> getMilitaryData(@PathVariable("militaryId") @Parameter(description = "军标ID") String militaryId, @PathVariable("fileSuffix") @Parameter(description = "军标文件后缀") String fileSuffix) {
|
||||
try {
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
// 查询军标二进制数据
|
||||
String querySql = "SELECT military_name, military_data FROM military WHERE id = ?";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(militaryId);
|
||||
MilitaryDataVo dataVo = SQLiteUtil.queryForObject(
|
||||
militaryPath, querySql, params, MilitaryDataVo.class
|
||||
);
|
||||
if (dataVo == null || dataVo.getMilitaryData() == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
// 构建响应头【支持中文文件名+预览】
|
||||
String originalFileName = dataVo.getMilitaryName() + "." + fileSuffix;
|
||||
String encodedFileName = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8.name());
|
||||
MediaType contentType = FileCommonUtil.getImageMediaType(fileSuffix);
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(contentType)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"inline; filename=\"" + encodedFileName + "\"")
|
||||
.body(dataVo.getMilitaryData());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "根据类型查询军标列表")
|
||||
@PostMapping("/militaryList")
|
||||
public ApiResponse militaryList(@Parameter(description = "军标类型ID") @RequestParam("militaryTypeId") String militaryTypeId) throws SQLException, IllegalAccessException, InstantiationException {
|
||||
// 获取最新的军标库
|
||||
public ApiResponse getMilitaryList(@RequestParam("militaryTypeId") @Parameter(description = "军标类型ID") String typeId) throws SQLException, IllegalAccessException, InstantiationException {
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
// 多表联查、查询所有的军标
|
||||
String sql = """
|
||||
SELECT
|
||||
military.id,
|
||||
military.military_type_id as militaryTypeId,
|
||||
military.military_name as militaryName,
|
||||
military.military_type as militaryType,
|
||||
military.data,
|
||||
military.view,
|
||||
military.created_at as createdAt,
|
||||
military.updated_at as updatedAt,
|
||||
military_type.name as militaryTypeName from
|
||||
military JOIN military_type ON military.military_type_id = military_type.id
|
||||
WHERE military.military_type_id = ?
|
||||
""";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(militaryTypeId);
|
||||
List<MilitaryVo> militaryVos = SQLiteUtil.queryForList(militaryPath, sql, params, MilitaryVo.class);
|
||||
return ApiResponse.success(militaryVos);
|
||||
|
||||
// 获取当前类型及所有子类型ID(递归)
|
||||
List<String> typeIdList = getMilitaryTypeIdsWithChildren(typeId);
|
||||
if (typeIdList == null || typeIdList.isEmpty()) {
|
||||
return ApiResponse.success(new ArrayList<>());
|
||||
}
|
||||
|
||||
// 构建IN条件(处理SQL注入风险)
|
||||
String idsWithQuotes = typeIdList.stream()
|
||||
.map(id -> "'" + id + "'")
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
// 多表联查
|
||||
String querySql = """
|
||||
SELECT
|
||||
m.id,
|
||||
m.military_type_id as militaryTypeId,
|
||||
m.military_name as militaryName,
|
||||
m.military_type as militaryType,
|
||||
m.created_at as createdAt,
|
||||
m.updated_at as updatedAt,
|
||||
t.name as militaryTypeName
|
||||
FROM military m
|
||||
JOIN military_type t ON m.military_type_id = t.id
|
||||
WHERE m.military_type_id IN (%s)
|
||||
ORDER BY m.created_at DESC
|
||||
""".replace("%s", idsWithQuotes);
|
||||
|
||||
// 查询并转换为VO
|
||||
List<MilitaryVo> militaryVoList = SQLiteUtil.queryForList(
|
||||
militaryPath, querySql, null, MilitaryVo.class
|
||||
);
|
||||
for (MilitaryVo vo : militaryVoList) {
|
||||
vo.setMilitaryDataUrl("/militaryLibrary/data/military/" + vo.getId() + "/" + vo.getMilitaryType());
|
||||
}
|
||||
return ApiResponse.success(militaryVoList);
|
||||
}
|
||||
|
||||
@Operation(summary = "更新军标信息")
|
||||
@PostMapping("/uploadMilitaryInfo")
|
||||
public ApiResponse uploadMilitaryInfo(
|
||||
@Parameter(description = "军标ID") @RequestParam("militaryId") String militaryId,
|
||||
@Parameter(description = "军标名称") @RequestParam(value = "militaryName", required = false) String militaryName)
|
||||
@Operation(summary = "更新军标名称")
|
||||
@PostMapping("/updateMilitaryInfo")
|
||||
public ApiResponse updateMilitaryInfo(
|
||||
@RequestParam("militaryId") @Parameter(description = "军标ID") String militaryId,
|
||||
@RequestParam("militaryName") @Parameter(description = "新军标名称") String militaryName)
|
||||
throws SQLException {
|
||||
|
||||
// 获取最新的军标库路径
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
|
||||
// 如果没有需要更新的字段、直接返回成功
|
||||
if (!StringUtils.hasText(militaryName)) {
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
// 构建更新SQL和参数
|
||||
String sql = "UPDATE military SET updated_at = ?, military_name = ? WHERE id = ?";
|
||||
String updateSql = "UPDATE military SET military_name = ?, updated_at = ? WHERE id = ?";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(LocalDateTime.now());
|
||||
params.add(militaryName);
|
||||
params.add(LocalDateTime.now().toString());
|
||||
params.add(militaryId);
|
||||
|
||||
// 执行更新
|
||||
SQLiteUtil.executeUpdate(militaryPath, sql, params);
|
||||
SQLiteUtil.executeUpdate(militaryPath, updateSql, params);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "删除军标")
|
||||
@PostMapping("/deleteMilitary")
|
||||
public ApiResponse deleteMilitary(@Parameter(description = "军标ID") @RequestParam("militaryId") String militaryId) throws SQLException {
|
||||
public ApiResponse deleteMilitary(@RequestParam("militaryId") @Parameter(description = "军标ID") String militaryId) throws SQLException {
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
String sql = "DELETE FROM military WHERE id = ?";
|
||||
|
||||
// 执行删除
|
||||
String deleteSql = "DELETE FROM military WHERE id = ?";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(militaryId);
|
||||
SQLiteUtil.executeUpdate(militaryPath, sql, params);
|
||||
return ApiResponse.success(null);
|
||||
|
||||
SQLiteUtil.executeUpdate(militaryPath, deleteSql, params);
|
||||
return ApiResponse.success("删除军标成功");
|
||||
}
|
||||
|
||||
private List<MilitaryTypeVo> buildMilitaryTypeTree(List<MilitaryType> militaryTypes) {
|
||||
List<MilitaryTypeVo> treeNodes = militaryTypes.stream()
|
||||
.map(militaryType -> new MilitaryTypeVo(militaryType))
|
||||
.collect(Collectors.toList());
|
||||
// 构建节点ID到节点的映射
|
||||
Map<String, MilitaryTypeVo> nodeMap = treeNodes.stream()
|
||||
.collect(Collectors.toMap(MilitaryTypeVo::getId, node -> node));
|
||||
// 根节点列表
|
||||
List<MilitaryTypeVo> rootNodes = new ArrayList<>();
|
||||
// 为每个节点添加子节点
|
||||
for (MilitaryTypeVo node : treeNodes) {
|
||||
String parentId = node.getParentId();
|
||||
|
||||
@Operation(summary = "拖动调整军标类型层级")
|
||||
@PostMapping("/dragMilitaryType")
|
||||
public ApiResponse dragMilitaryType(@RequestBody List<DragMilitaryTypeDto> dragDtoList)
|
||||
throws SQLException, IllegalAccessException, InstantiationException {
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return ApiResponse.failure("请先创建或导入军标库");
|
||||
}
|
||||
|
||||
// 批量更新层级和排序
|
||||
for (DragMilitaryTypeDto dto : dragDtoList) {
|
||||
String updateSql = "UPDATE military_type " +
|
||||
"SET parent_id = ?, tree_index = ?, updated_at = ? " +
|
||||
"WHERE id = ?";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(dto.getParentId());
|
||||
params.add(dto.getTreeIndex());
|
||||
params.add(LocalDateTime.now().toString());
|
||||
params.add(dto.getId());
|
||||
|
||||
SQLiteUtil.executeUpdate(militaryPath, updateSql, params);
|
||||
}
|
||||
|
||||
// 返回更新后的树形列表
|
||||
List<MilitaryTypeVo> treeList = militaryTypeList();
|
||||
return ApiResponse.success(treeList);
|
||||
}
|
||||
|
||||
private String getMilitaryLibrary() {
|
||||
LambdaQueryWrapper<MilitaryLibrary> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(MilitaryLibrary::getIsEnable, 1); // 1=启用,0=未启用
|
||||
MilitaryLibrary library = militaryLibraryService.getOne(queryWrapper);
|
||||
return library == null ? null : library.getPath();
|
||||
}
|
||||
|
||||
private void addMilitaryLibrary(String militaryPath) {
|
||||
// 所有已存在的军标库设置为「未启用」
|
||||
List<MilitaryLibrary> existLibraries = militaryLibraryService.list();
|
||||
for (MilitaryLibrary library : existLibraries) {
|
||||
library.setIsEnable(0);
|
||||
militaryLibraryService.updateById(library);
|
||||
}
|
||||
|
||||
// 检查路径是否已存在(存在则启用,不存在则新增)
|
||||
LambdaQueryWrapper<MilitaryLibrary> pathWrapper = new LambdaQueryWrapper<>();
|
||||
pathWrapper.eq(MilitaryLibrary::getPath, militaryPath);
|
||||
MilitaryLibrary existLibrary = militaryLibraryService.getOne(pathWrapper);
|
||||
|
||||
if (existLibrary != null) {
|
||||
existLibrary.setIsEnable(1);
|
||||
militaryLibraryService.updateById(existLibrary);
|
||||
} else {
|
||||
MilitaryLibrary newLibrary = new MilitaryLibrary();
|
||||
newLibrary.setId(UUID.fastUUID().toString(true));
|
||||
newLibrary.setPath(militaryPath);
|
||||
newLibrary.setName(FileUtil.mainName(militaryPath));
|
||||
newLibrary.setIsEnable(1);
|
||||
newLibrary.setCreatedAt(LocalDateTime.now());
|
||||
militaryLibraryService.save(newLibrary);
|
||||
}
|
||||
}
|
||||
|
||||
private List<MilitaryTypeVo> militaryTypeList() throws SQLException, IllegalAccessException, InstantiationException {
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
String querySql = """
|
||||
SELECT
|
||||
id, name, parent_id as parentId,
|
||||
tree_index as treeIndex, created_at as createdAt,
|
||||
updated_at as updatedAt
|
||||
FROM military_type
|
||||
ORDER BY tree_index ASC
|
||||
""";
|
||||
List<MilitaryType> typeList = SQLiteUtil.queryForList(militaryPath, querySql, null, MilitaryType.class);
|
||||
|
||||
// 构建树形结构
|
||||
return buildMilitaryTypeTree(typeList);
|
||||
}
|
||||
|
||||
private List<MilitaryTypeVo> buildMilitaryTypeTree(List<MilitaryType> typeList) {
|
||||
// 转换为VO
|
||||
List<MilitaryTypeVo> voList = typeList.stream()
|
||||
.map(militaryType -> new MilitaryTypeVo(militaryType)).collect(Collectors.toList());
|
||||
|
||||
// 构建ID→VO的映射(方便快速查找父级)
|
||||
Map<String, MilitaryTypeVo> voMap = voList.stream()
|
||||
.collect(Collectors.toMap(MilitaryTypeVo::getId, vo -> vo));
|
||||
|
||||
// 组装树形结构
|
||||
List<MilitaryTypeVo> rootList = new ArrayList<>();
|
||||
for (MilitaryTypeVo vo : voList) {
|
||||
String parentId = vo.getParentId();
|
||||
if (parentId == null || parentId.isEmpty()) {
|
||||
// 没有父节点的是根节点
|
||||
rootNodes.add(node);
|
||||
rootList.add(vo);
|
||||
} else {
|
||||
// 找到父节点并添加为子节点
|
||||
MilitaryTypeVo parentNode = nodeMap.get(parentId);
|
||||
if (parentNode != null) {
|
||||
parentNode.getChildren().add(node);
|
||||
MilitaryTypeVo parentVo = voMap.get(parentId);
|
||||
if (parentVo != null) {
|
||||
parentVo.getChildren().add(vo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rootNodes;
|
||||
// 排序
|
||||
rootList.sort(Comparator.comparingInt(MilitaryTypeVo::getTreeIndex));
|
||||
sortMilitaryTypeChildren(rootList);
|
||||
|
||||
return rootList;
|
||||
}
|
||||
|
||||
private String getMilitaryLibrary() {
|
||||
// 获取启用的军标库
|
||||
LambdaQueryWrapper<MilitaryLibrary> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(MilitaryLibrary::getIsEnable, 1);
|
||||
MilitaryLibrary militaryLibrary = militaryLibraryService.getOne(queryWrapper);
|
||||
if (militaryLibrary == null) {
|
||||
return null;
|
||||
}
|
||||
return militaryLibrary.getPath();
|
||||
}
|
||||
|
||||
private void addMilitaryLibrary(String militaryPath) {
|
||||
// 查询系统所有的军标库
|
||||
List<MilitaryLibrary> militaryLibraries = militaryLibraryService.list();
|
||||
// 遍历并更新状态
|
||||
for (MilitaryLibrary militaryLibrary : militaryLibraries) {
|
||||
// 设置启用状态为0
|
||||
militaryLibrary.setIsEnable(0);
|
||||
militaryLibraryService.updateById(militaryLibrary);
|
||||
}
|
||||
|
||||
// 检查相同路径的军标库是否已存在
|
||||
LambdaQueryWrapper<MilitaryLibrary> pathWrapper = new LambdaQueryWrapper<>();
|
||||
pathWrapper.eq(MilitaryLibrary::getPath, militaryPath);
|
||||
MilitaryLibrary existingMilitary = militaryLibraryService.getOne(pathWrapper);
|
||||
// 若存在相同路径的军标库、不做处理、仅仅更新状态为显示
|
||||
if (existingMilitary != null) {
|
||||
existingMilitary.setIsEnable(1);
|
||||
militaryLibraryService.updateById(existingMilitary);
|
||||
private void sortMilitaryTypeChildren(List<MilitaryTypeVo> voList) {
|
||||
if (voList == null || voList.isEmpty()) {
|
||||
return;
|
||||
} else {
|
||||
// 新增军标库
|
||||
MilitaryLibrary newMilitary = new MilitaryLibrary();
|
||||
File file = FileUtil.file(militaryPath);
|
||||
newMilitary.setId(UUID.fastUUID().toString(true));
|
||||
newMilitary.setPath(militaryPath);
|
||||
newMilitary.setName(FileUtil.extName(file));
|
||||
newMilitary.setIsEnable(1);
|
||||
militaryLibraryService.save(newMilitary);
|
||||
}
|
||||
// 排序当前层级
|
||||
voList.sort(Comparator.comparingInt(MilitaryTypeVo::getTreeIndex));
|
||||
// 递归排序子层级
|
||||
for (MilitaryTypeVo vo : voList) {
|
||||
sortMilitaryTypeChildren(vo.getChildren());
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getMilitaryTypeIdsWithChildren(String typeId)
|
||||
throws SQLException, IllegalAccessException, InstantiationException {
|
||||
List<String> idList = new ArrayList<>();
|
||||
if (!StringUtils.hasText(typeId)) {
|
||||
return idList;
|
||||
}
|
||||
|
||||
String militaryPath = getMilitaryLibrary();
|
||||
if (militaryPath == null) {
|
||||
return idList;
|
||||
}
|
||||
|
||||
// 校验类型是否存在
|
||||
if (!isMilitaryTypeExist(militaryPath, typeId)) {
|
||||
return idList;
|
||||
}
|
||||
|
||||
// 递归收集ID(包含自身)
|
||||
recursiveGetMilitaryTypeChildren(militaryPath, typeId, idList);
|
||||
return idList;
|
||||
}
|
||||
|
||||
private void recursiveGetMilitaryTypeChildren(String militaryPath, String currentId, List<String> idList)
|
||||
throws SQLException, IllegalAccessException, InstantiationException {
|
||||
// 先添加当前ID
|
||||
idList.add(currentId);
|
||||
|
||||
// 查询直接子类型
|
||||
String querySql = "SELECT id FROM military_type WHERE parent_id = ? ORDER BY tree_index ASC";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(currentId);
|
||||
List<MilitaryType> childList = SQLiteUtil.queryForList(
|
||||
militaryPath, querySql, params, MilitaryType.class
|
||||
);
|
||||
|
||||
// 递归查询子类型的子类型
|
||||
if (childList != null && !childList.isEmpty()) {
|
||||
for (MilitaryType child : childList) {
|
||||
recursiveGetMilitaryTypeChildren(militaryPath, child.getId(), idList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMilitaryTypeExist(String militaryPath, String typeId)
|
||||
throws SQLException, IllegalAccessException, InstantiationException {
|
||||
String checkSql = "SELECT id FROM military_type WHERE id = ?";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(typeId);
|
||||
List<MilitaryType> existList = SQLiteUtil.queryForList(
|
||||
militaryPath, checkSql, params, MilitaryType.class
|
||||
);
|
||||
return existList != null && !existList.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,12 +313,17 @@ public class ModelLibraryController {
|
||||
// 循环遍历数据
|
||||
for (ModelVo modelVo : modelVos) {
|
||||
if (modelVo.getModelType() != null) {
|
||||
modelVo.setModelDataUrl("/modelLibrary/data/model/" + modelVo.getId() + "/" + modelVo.getModelType());
|
||||
String processedModelType = trimDot(modelVo.getModelType());
|
||||
modelVo.setModelDataUrl("/modelLibrary/data/model/" + modelVo.getId() + "/" + processedModelType);
|
||||
modelVo.setModelType(processedModelType);
|
||||
}
|
||||
if (modelVo.getPosterType() != null) {
|
||||
modelVo.setPosterDataUrl("/modelLibrary/data/poster/" + modelVo.getId() + "/" + modelVo.getPosterType());
|
||||
String processedPosterType = trimDot(modelVo.getPosterType());
|
||||
modelVo.setPosterDataUrl("/modelLibrary/data/poster/" + modelVo.getId() + "/" + processedPosterType);
|
||||
modelVo.setPosterType(processedPosterType);
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse.success(modelVos);
|
||||
}
|
||||
|
||||
@ -566,4 +571,12 @@ public class ModelLibraryController {
|
||||
// 若查询结果非空、说明分类存在
|
||||
return existTypes != null && !existTypes.isEmpty();
|
||||
}
|
||||
|
||||
private String trimDot(String str) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
return str.startsWith(".") ? str.substring(1) : str;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -20,9 +20,7 @@ public class Military {
|
||||
@Schema(description = "军标类型")
|
||||
private String militaryType;
|
||||
@Schema(description = "军标数据")
|
||||
private String data;
|
||||
@Schema(description = "军标视图")
|
||||
private String view;
|
||||
private byte[] militaryData;
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdAt;
|
||||
@Schema(description = "更新时间")
|
||||
|
||||
@ -7,6 +7,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
|
||||
@Data
|
||||
@Component
|
||||
public class ServerConfig {
|
||||
|
||||
@ -29,20 +29,20 @@ public class FileCommonUtil {
|
||||
public static MediaType getImageMediaType(String suffix) {
|
||||
String lowerSuffix = suffix.toLowerCase();
|
||||
switch (lowerSuffix) {
|
||||
case ".jpg":
|
||||
case ".jpeg":
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
return MediaType.IMAGE_JPEG;
|
||||
case ".glb":
|
||||
case "glb":
|
||||
return MediaType.valueOf("model/gltf-binary");
|
||||
case ".png":
|
||||
case "png":
|
||||
return MediaType.IMAGE_PNG;
|
||||
case ".gif":
|
||||
case "gif":
|
||||
return MediaType.IMAGE_GIF;
|
||||
case ".bmp":
|
||||
case "bmp":
|
||||
return MediaType.valueOf("image/bmp");
|
||||
case ".webp":
|
||||
case "webp":
|
||||
return MediaType.valueOf("image/webp");
|
||||
case ".svg":
|
||||
case "svg":
|
||||
return MediaType.valueOf("image/svg+xml");
|
||||
default:
|
||||
return MediaType.APPLICATION_OCTET_STREAM;
|
||||
|
||||
@ -1,274 +1,249 @@
|
||||
package com.yj.earth.common.util;
|
||||
|
||||
import cn.hutool.core.lang.UUID;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.UUID;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class SQLiteConverter {
|
||||
// SQLite JDBC驱动
|
||||
private static final String JDBC_DRIVER = "org.sqlite.JDBC";
|
||||
// 源数据库和目标数据库路径
|
||||
private String sourceDbPath;
|
||||
private String targetDbPath;
|
||||
// 批处理大小、可根据内存情况调整
|
||||
private static final int BATCH_SIZE = 100;
|
||||
|
||||
// 原始数据库路径
|
||||
private static final String ORIGINAL_DB_PATH = "jdbc:sqlite:D:\\YJEarth.model";
|
||||
public SQLiteConverter(String sourceDbPath, String targetDbPath) {
|
||||
this.sourceDbPath = sourceDbPath;
|
||||
this.targetDbPath = targetDbPath;
|
||||
}
|
||||
|
||||
// 新数据库路径
|
||||
private static final String NEW_DB_PATH = "jdbc:sqlite:E:\\通用模型库.model";
|
||||
public void convert() {
|
||||
Connection sourceConn = null;
|
||||
Connection targetConn = null;
|
||||
try {
|
||||
// 加载驱动
|
||||
Class.forName(JDBC_DRIVER);
|
||||
// 连接源数据库和目标数据库
|
||||
sourceConn = DriverManager.getConnection("jdbc:sqlite:" + sourceDbPath);
|
||||
targetConn = DriverManager.getConnection("jdbc:sqlite:" + targetDbPath);
|
||||
// 禁用自动提交、以便在出现错误时可以回滚
|
||||
targetConn.setAutoCommit(false);
|
||||
// 创建目标表结构
|
||||
createTargetTables(targetConn);
|
||||
// 复制并转换数据
|
||||
copyModeTypesData(sourceConn, targetConn);
|
||||
copyModelsData(sourceConn, targetConn);
|
||||
// 为model表添加索引
|
||||
createModelTableIndexes(targetConn);
|
||||
// 提交事务
|
||||
targetConn.commit();
|
||||
System.out.println("数据库转换成功!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
if (targetConn != null) {
|
||||
targetConn.rollback();
|
||||
System.out.println("转换失败、已回滚操作!");
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
// 关闭连接
|
||||
try {
|
||||
if (sourceConn != null) sourceConn.close();
|
||||
if (targetConn != null) targetConn.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createTargetTables(Connection conn) throws SQLException {
|
||||
System.out.println("开始创建目标表结构...");
|
||||
Statement stmt = conn.createStatement();
|
||||
String sql = """
|
||||
CREATE TABLE "model_type" (
|
||||
"id" TEXT,
|
||||
"name" TEXT,
|
||||
"parent_id" TEXT,
|
||||
"tree_index" INTEGER,
|
||||
"created_at" TEXT,
|
||||
"updated_at" TEXT,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
""";
|
||||
stmt.execute(sql);
|
||||
sql = """
|
||||
CREATE TABLE "model" (
|
||||
"id" TEXT,
|
||||
"model_type_id" TEXT,
|
||||
"model_name" TEXT,
|
||||
"model_type" TEXT,
|
||||
"model_data" BLOB,
|
||||
"poster_type" TEXT,
|
||||
"poster_data" BLOB,
|
||||
"created_at" TEXT,
|
||||
"updated_at" TEXT,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
""";
|
||||
stmt.execute(sql);
|
||||
stmt.close();
|
||||
System.out.println("目标表结构创建完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 model 表的每个字段创建索引
|
||||
*/
|
||||
private void createModelTableIndexes(Connection conn) throws SQLException {
|
||||
System.out.println("开始为创建索引...");
|
||||
Statement stmt = conn.createStatement();
|
||||
|
||||
String sql = """
|
||||
CREATE INDEX idx_model_covering ON model(
|
||||
model_type_id,
|
||||
id,
|
||||
model_name,
|
||||
model_type,
|
||||
poster_type,
|
||||
created_at,
|
||||
updated_at
|
||||
);
|
||||
""";
|
||||
stmt.execute(sql);
|
||||
|
||||
stmt.close();
|
||||
System.out.println("model表索引创建完成");
|
||||
}
|
||||
|
||||
private int getTotalRecords(Connection conn, String tableName) throws SQLException {
|
||||
PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(*) AS total FROM " + tableName);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
int total = rs.next() ? rs.getInt("total") : 0;
|
||||
rs.close();
|
||||
stmt.close();
|
||||
return total;
|
||||
}
|
||||
|
||||
private void copyModeTypesData(Connection sourceConn, Connection targetConn) throws SQLException {
|
||||
int totalRecords = getTotalRecords(sourceConn, "mode_types");
|
||||
System.out.println("开始转换 mode_types 表数据、共" + totalRecords + "条记录");
|
||||
PreparedStatement sourceStmt = sourceConn.prepareStatement("SELECT * FROM mode_types");
|
||||
ResultSet rs = sourceStmt.executeQuery();
|
||||
PreparedStatement targetStmt = targetConn.prepareStatement(
|
||||
"INSERT INTO model_type (id, name, parent_id, tree_index, created_at, updated_at) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
int count = 0;
|
||||
|
||||
while (rs.next()) {
|
||||
targetStmt.setString(1, rs.getString("type_id"));
|
||||
targetStmt.setString(2, rs.getString("type_name"));
|
||||
targetStmt.setString(3, rs.getString("p_id"));
|
||||
targetStmt.setInt(4, 0);
|
||||
targetStmt.setObject(5, LocalDateTime.now());
|
||||
targetStmt.setObject(6, LocalDateTime.now());
|
||||
|
||||
// 添加到批处理
|
||||
targetStmt.addBatch();
|
||||
count++;
|
||||
|
||||
// 每达到批处理大小或最后一条记录时执行批处理
|
||||
if (count % BATCH_SIZE == 0 || count == totalRecords) {
|
||||
targetStmt.executeBatch();
|
||||
displayProgress(count, totalRecords, "mode_types 表");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("\n成功转换 mode_types 表数据:" + count + "条记录");
|
||||
rs.close();
|
||||
sourceStmt.close();
|
||||
targetStmt.close();
|
||||
}
|
||||
|
||||
private void copyModelsData(Connection sourceConn, Connection targetConn) throws SQLException {
|
||||
int totalRecords = getTotalRecords(sourceConn, "models");
|
||||
System.out.println("开始转换 models 表数据、共" + totalRecords + "条记录");
|
||||
|
||||
// 对于大字段、使用向前滚动的结果集、避免缓存所有数据
|
||||
PreparedStatement sourceStmt = sourceConn.prepareStatement(
|
||||
"SELECT * FROM models",
|
||||
ResultSet.TYPE_FORWARD_ONLY,
|
||||
ResultSet.CONCUR_READ_ONLY
|
||||
);
|
||||
sourceStmt.setFetchSize(100);
|
||||
ResultSet rs = sourceStmt.executeQuery();
|
||||
PreparedStatement targetStmt = targetConn.prepareStatement(
|
||||
"INSERT INTO model (id, model_type_id, model_name, model_type, model_data, " +
|
||||
"poster_type, poster_data, created_at, updated_at) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
|
||||
int count = 0;
|
||||
while (rs.next()) {
|
||||
targetStmt.setString(1, rs.getString("id"));
|
||||
targetStmt.setString(2, rs.getString("p_id"));
|
||||
targetStmt.setString(3, rs.getString("model_name"));
|
||||
targetStmt.setString(4, rs.getString("model_type"));
|
||||
byte[] dataBytes = rs.getBytes("data");
|
||||
if (dataBytes != null) {
|
||||
targetStmt.setBytes(5, dataBytes);
|
||||
} else {
|
||||
targetStmt.setNull(5, Types.BLOB);
|
||||
}
|
||||
targetStmt.setString(6, rs.getString("poster_type"));
|
||||
byte[] posterBytes = rs.getBytes("poster");
|
||||
if (posterBytes != null) {
|
||||
targetStmt.setBytes(7, posterBytes);
|
||||
}
|
||||
targetStmt.setObject(8, LocalDateTime.now());
|
||||
targetStmt.setObject(9, LocalDateTime.now());
|
||||
|
||||
// 添加到批处理
|
||||
targetStmt.addBatch();
|
||||
count++;
|
||||
// 执行批处理
|
||||
if (count % BATCH_SIZE == 0 || count == totalRecords) {
|
||||
targetStmt.executeBatch();
|
||||
displayProgress(count, totalRecords, "models 表");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("\n成功转换 models 表数据:" + count + "条记录");
|
||||
rs.close();
|
||||
sourceStmt.close();
|
||||
targetStmt.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示进度信息
|
||||
*/
|
||||
private void displayProgress(int current, int total, String tableName) {
|
||||
double percentage = (double) current / total * 100;
|
||||
// 清除当前行并显示进度
|
||||
System.out.printf("\r%s: 已完成 %.1f%% (%d/%d条)", tableName, percentage, current, total);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("===== 开始数据库转换程序 =====");
|
||||
System.out.println("原始数据库: " + ORIGINAL_DB_PATH);
|
||||
System.out.println("目标数据库: " + NEW_DB_PATH + "\n");
|
||||
// 源数据库路径
|
||||
String sourcePath = "F:\\公司通用模型库.model";
|
||||
// 目标数据库路径
|
||||
String targetPath = "F:\\通用模型库.model";
|
||||
|
||||
// 使用try-with-resources自动管理连接资源
|
||||
try (Connection originalConn = DriverManager.getConnection(ORIGINAL_DB_PATH);
|
||||
Connection newConn = DriverManager.getConnection(NEW_DB_PATH)) {
|
||||
System.out.println("开始数据库转换...");
|
||||
System.out.println("源数据库: " + sourcePath);
|
||||
System.out.println("目标数据库: " + targetPath);
|
||||
|
||||
System.out.println("✅ 成功连接到原始数据库");
|
||||
System.out.println("✅ 成功创建并连接到新数据库\n");
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 在新数据库中创建表结构
|
||||
System.out.println("===== 开始创建新表结构 =====");
|
||||
createNewTables(newConn);
|
||||
System.out.println("===== 新表结构创建完成 =====");
|
||||
// 创建转换器并执行转换
|
||||
SQLiteConverter converter = new SQLiteConverter(sourcePath, targetPath);
|
||||
converter.convert();
|
||||
|
||||
// 迁移mode_types表数据到model_type表
|
||||
System.out.println("\n===== 开始迁移mode_types表数据 =====");
|
||||
MigrationResult modeTypesResult = migrateModeTypes(originalConn, newConn);
|
||||
System.out.println("===== mode_types表数据迁移完成 =====");
|
||||
System.out.printf(" 成功: %d 条, 失败: %d 条, 总计: %d 条%n",
|
||||
modeTypesResult.successCount,
|
||||
modeTypesResult.failureCount,
|
||||
modeTypesResult.totalCount);
|
||||
|
||||
// 迁移models表数据到model表
|
||||
System.out.println("\n===== 开始迁移models表数据 =====");
|
||||
MigrationResult modelsResult = migrateModels(originalConn, newConn);
|
||||
System.out.println("===== models表数据迁移完成 =====");
|
||||
System.out.printf(" 成功: %d 条, 失败: %d 条, 总计: %d 条%n",
|
||||
modelsResult.successCount,
|
||||
modelsResult.failureCount,
|
||||
modelsResult.totalCount);
|
||||
|
||||
if (modeTypesResult.failureCount > 0 || modelsResult.failureCount > 0) {
|
||||
System.out.println("\n⚠️ 注意:部分记录迁移失败、已跳过错误记录");
|
||||
}
|
||||
|
||||
System.out.println("\n===== 数据库转换处理完成! =====");
|
||||
|
||||
} catch (SQLException e) {
|
||||
System.err.println("\n❌ 数据库连接错误: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移结果数据类
|
||||
*/
|
||||
private static class MigrationResult {
|
||||
int successCount;
|
||||
int failureCount;
|
||||
int totalCount;
|
||||
|
||||
MigrationResult(int success, int failure, int total) {
|
||||
this.successCount = success;
|
||||
this.failureCount = failure;
|
||||
this.totalCount = total;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在新数据库中创建表结构
|
||||
*/
|
||||
private static void createNewTables(Connection conn) throws SQLException {
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
// 创建model_type表
|
||||
System.out.println("正在创建model_type表...");
|
||||
String createModelTypeTable = "CREATE TABLE IF NOT EXISTS \"model_type\" (" +
|
||||
"\"id\" TEXT," +
|
||||
"\"name\" TEXT," +
|
||||
"\"parent_id\" TEXT," +
|
||||
"\"tree_index\" INTEGER," +
|
||||
"\"created_at\" TEXT," +
|
||||
"\"updated_at\" TEXT," +
|
||||
"PRIMARY KEY (\"id\")" +
|
||||
");";
|
||||
stmt.execute(createModelTypeTable);
|
||||
System.out.println("✅ model_type表创建完成");
|
||||
|
||||
// 创建model表
|
||||
System.out.println("正在创建model表...");
|
||||
String createModelTable = "CREATE TABLE IF NOT EXISTS \"model\" (" +
|
||||
"\"id\" TEXT," +
|
||||
"\"model_type_id\" TEXT," +
|
||||
"\"model_name\" TEXT," +
|
||||
"\"model_type\" TEXT," +
|
||||
"\"poster_type\" TEXT," +
|
||||
"\"poster\" TEXT," +
|
||||
"\"data\" TEXT," +
|
||||
"\"data_bytes\" BLOB," +
|
||||
"\"view\" TEXT," +
|
||||
"\"created_at\" TEXT," +
|
||||
"\"updated_at\" TEXT," +
|
||||
"PRIMARY KEY (\"id\")" +
|
||||
");";
|
||||
stmt.execute(createModelTable);
|
||||
System.out.println("✅ model表创建完成");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移mode_types表数据到model_type表
|
||||
*/
|
||||
private static MigrationResult migrateModeTypes(Connection originalConn, Connection newConn) throws SQLException {
|
||||
// 先获取记录总数、用于显示进度
|
||||
int totalCount;
|
||||
try (PreparedStatement countStmt = originalConn.prepareStatement("SELECT COUNT(*) FROM mode_types");
|
||||
ResultSet countRs = countStmt.executeQuery()) {
|
||||
countRs.next();
|
||||
totalCount = countRs.getInt(1);
|
||||
System.out.println("发现 " + totalCount + " 条mode_types记录待迁移");
|
||||
}
|
||||
|
||||
// 查询原始表数据
|
||||
try (PreparedStatement selectStmt = originalConn.prepareStatement("SELECT * FROM mode_types");
|
||||
ResultSet rs = selectStmt.executeQuery()) {
|
||||
|
||||
// 插入到新表、关闭自动提交以提高性能
|
||||
newConn.setAutoCommit(false);
|
||||
|
||||
try (PreparedStatement insertStmt = newConn.prepareStatement(
|
||||
"INSERT INTO model_type (" +
|
||||
"id, name, parent_id, tree_index, created_at, updated_at" +
|
||||
") VALUES (?, ?, ?, ?, ?, ?)")) {
|
||||
|
||||
int successCount = 0;
|
||||
int failureCount = 0;
|
||||
|
||||
while (rs.next()) {
|
||||
try {
|
||||
// 生成新的UUID作为ID
|
||||
String newId = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
String typeName = rs.getString("type_name");
|
||||
|
||||
// 映射字段
|
||||
insertStmt.setString(1, newId);
|
||||
insertStmt.setString(2, typeName);
|
||||
insertStmt.setString(3, rs.getString("p_id"));
|
||||
insertStmt.setInt(4, 0);
|
||||
insertStmt.setString(5, rs.getString("created_at"));
|
||||
insertStmt.setString(6, rs.getString("updated_at"));
|
||||
|
||||
insertStmt.executeUpdate();
|
||||
successCount++;
|
||||
|
||||
// 每迁移10条记录或最后一批记录时打印进度
|
||||
if (successCount % 10 == 0 || (successCount + failureCount) == totalCount) {
|
||||
printProgress(successCount + failureCount, totalCount);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
// 捕获单条记录的异常、跳过该记录
|
||||
failureCount++;
|
||||
System.err.printf("\n❌ 迁移mode_types记录失败 (序号: %d): %s%n",
|
||||
successCount + failureCount, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
newConn.commit();
|
||||
return new MigrationResult(successCount, failureCount, totalCount);
|
||||
} catch (SQLException e) {
|
||||
newConn.rollback();
|
||||
throw e;
|
||||
} finally {
|
||||
newConn.setAutoCommit(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移models表数据到model表
|
||||
*/
|
||||
private static MigrationResult migrateModels(Connection originalConn, Connection newConn) throws SQLException {
|
||||
// 先获取记录总数、用于显示进度
|
||||
int totalCount;
|
||||
try (PreparedStatement countStmt = originalConn.prepareStatement("SELECT COUNT(*) FROM models");
|
||||
ResultSet countRs = countStmt.executeQuery()) {
|
||||
countRs.next();
|
||||
totalCount = countRs.getInt(1);
|
||||
System.out.println("发现 " + totalCount + " 条models记录待迁移");
|
||||
}
|
||||
|
||||
// 查询原始表数据
|
||||
try (PreparedStatement selectStmt = originalConn.prepareStatement("SELECT * FROM models");
|
||||
ResultSet rs = selectStmt.executeQuery()) {
|
||||
|
||||
// 插入到新表、关闭自动提交以提高性能
|
||||
newConn.setAutoCommit(false);
|
||||
|
||||
try (PreparedStatement insertStmt = newConn.prepareStatement(
|
||||
"INSERT INTO model (" +
|
||||
"id, model_type_id, model_name, model_type, poster_type, " +
|
||||
"poster, data, data_bytes, view, created_at, updated_at" +
|
||||
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) {
|
||||
|
||||
int successCount = 0;
|
||||
int failureCount = 0;
|
||||
|
||||
while (rs.next()) {
|
||||
try {
|
||||
// 生成新的UUID作为ID
|
||||
String newId = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
String modelName = rs.getString("model_name");
|
||||
|
||||
// 映射字段
|
||||
insertStmt.setString(1, newId);
|
||||
insertStmt.setString(2, rs.getString("p_id"));
|
||||
insertStmt.setString(3, modelName);
|
||||
insertStmt.setString(4, rs.getString("model_type"));
|
||||
insertStmt.setString(5, rs.getString("poster_type"));
|
||||
|
||||
// 处理BLOB字段
|
||||
byte[] posterBlob = rs.getBytes("poster");
|
||||
insertStmt.setString(6, posterBlob != null ? new String(posterBlob) : null);
|
||||
insertStmt.setString(7, null);
|
||||
|
||||
byte[] dataBlob = rs.getBytes("data");
|
||||
insertStmt.setBytes(8, dataBlob);
|
||||
insertStmt.setString(9, null);
|
||||
|
||||
insertStmt.setString(10, rs.getString("created_at"));
|
||||
insertStmt.setString(11, rs.getString("updated_at"));
|
||||
|
||||
insertStmt.executeUpdate();
|
||||
successCount++;
|
||||
|
||||
// 每迁移10条记录或最后一批记录时打印进度
|
||||
if (successCount % 10 == 0 || (successCount + failureCount) == totalCount) {
|
||||
printProgress(successCount + failureCount, totalCount);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
// 捕获单条记录的异常、跳过该记录
|
||||
failureCount++;
|
||||
System.err.printf("\n❌ 迁移models记录失败 (序号: %d, 名称: %s): %s%n",
|
||||
successCount + failureCount,
|
||||
rs.getString("model_name"),
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
newConn.commit();
|
||||
return new MigrationResult(successCount, failureCount, totalCount);
|
||||
} catch (SQLException e) {
|
||||
newConn.rollback();
|
||||
throw e;
|
||||
} finally {
|
||||
newConn.setAutoCommit(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印迁移进度
|
||||
*/
|
||||
private static void printProgress(int current, int total) {
|
||||
double percentage = (current * 100.0) / total;
|
||||
System.out.printf("迁移进度: %d/%d (%.1f%%)\r", current, total, percentage);
|
||||
System.out.flush();
|
||||
long endTime = System.currentTimeMillis();
|
||||
double elapsedTime = (endTime - startTime) / 1000.0;
|
||||
System.out.printf("转换完成、耗时: %.2f秒%n", elapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
package com.yj.earth.common.util;
|
||||
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
|
||||
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; // 新增导入
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
/**
|
||||
* 优化版SQLite工具类
|
||||
*/
|
||||
public class SQLiteUtil {
|
||||
|
||||
// 加载 SQLite JDBC 驱动
|
||||
// 加载SQLite JDBC驱动(静态初始化)
|
||||
static {
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
@ -19,140 +24,508 @@ public class SQLiteUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// 统一日期格式(匹配数据库TEXT字段存储的格式:yyyy-MM-dd'T'HH:mm:ss.SSS)
|
||||
// 统一日期格式(LocalDateTime)
|
||||
private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
|
||||
// 连接池缓存:key=数据库文件路径,value=DBCP2数据源(支持多连接复用)
|
||||
private static final Map<String, BasicDataSource> DATA_SOURCE_POOL = new ConcurrentHashMap<>();
|
||||
|
||||
// 字段缓存:缓存类的字段映射(避免反射重复开销)
|
||||
private static final Map<Class<?>, Map<String, Field>> FIELD_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
// ========================== 连接池核心方法 ==========================
|
||||
|
||||
/**
|
||||
* 根据数据库文件绝对路径获取连接
|
||||
* 从DBCP2连接池获取连接(自动复用/创建连接)
|
||||
*
|
||||
* @param dbFilePath 数据库文件绝对路径
|
||||
* @return 线程安全的Connection(使用后需通过try-with-resources自动归还)
|
||||
*/
|
||||
public static Connection getConnection(String dbFilePath) throws SQLException {
|
||||
String url = "jdbc:sqlite:" + dbFilePath;
|
||||
return DriverManager.getConnection(url);
|
||||
if (dbFilePath == null || dbFilePath.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("数据库文件路径不能为空");
|
||||
}
|
||||
// 不存在则创建数据源,存在则直接从池获取连接
|
||||
BasicDataSource dataSource = DATA_SOURCE_POOL.computeIfAbsent(dbFilePath, SQLiteUtil::createDataSource);
|
||||
return dataSource.getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行查询并返回单个对象
|
||||
* 创建DBCP2数据源(配置SQLite性能参数)
|
||||
*
|
||||
* @param dbFilePath 数据库文件路径
|
||||
* @return 配置优化后的BasicDataSource
|
||||
*/
|
||||
public static <T> T queryForObject(String dbFilePath, String sql, List<Object> params, Class<T> clazz) throws SQLException, IllegalAccessException, InstantiationException {
|
||||
private static BasicDataSource createDataSource(String dbFilePath) {
|
||||
BasicDataSource dataSource = new BasicDataSource();
|
||||
// 1. 基础JDBC配置
|
||||
dataSource.setDriverClassName("org.sqlite.JDBC");
|
||||
dataSource.setUrl("jdbc:sqlite:" + dbFilePath);
|
||||
|
||||
// 2. 连接池核心参数(根据并发量调整,SQLite不建议过多连接)
|
||||
dataSource.setMaxTotal(30); // 最大连接数:20-50(根据服务器CPU/内存调整)
|
||||
dataSource.setMaxIdle(15); // 最大空闲连接:保留部分连接避免频繁创建
|
||||
dataSource.setMinIdle(5); // 最小空闲连接:保证基础并发响应速度
|
||||
dataSource.setTimeBetweenEvictionRunsMillis(60000); // 连接检测间隔:1分钟
|
||||
dataSource.setMinEvictableIdleTimeMillis(300000); // 连接空闲超时:5分钟(清理长期空闲连接)
|
||||
|
||||
// 3. 连接有效性验证(避免使用失效连接)
|
||||
dataSource.setTestOnBorrow(true); // 借连接时验证
|
||||
dataSource.setTestOnReturn(false); // 还连接时不验证(减少开销)
|
||||
dataSource.setValidationQuery("SELECT 1"); // 轻量验证SQL(SQLite支持)
|
||||
dataSource.setValidationQueryTimeout(2); // 验证超时:2秒
|
||||
|
||||
// 4. PreparedStatement缓存(减少SQL解析开销)
|
||||
dataSource.setPoolPreparedStatements(true);
|
||||
dataSource.setMaxOpenPreparedStatements(100); // 最大缓存100个PreparedStatement
|
||||
|
||||
// 5. SQLite底层性能优化(关键!提升并发能力)
|
||||
try (Connection conn = dataSource.getConnection();
|
||||
Statement stmt = conn.createStatement()) {
|
||||
stmt.execute("PRAGMA journal_mode=WAL;"); // 启用WAL模式:支持多读者+单写者(核心优化)
|
||||
stmt.execute("PRAGMA cache_size=-20000;"); // 页面缓存20MB(负号表示KB单位,内存足可调大)
|
||||
stmt.execute("PRAGMA synchronous=NORMAL;"); // 同步级别:平衡性能与安全(崩溃最多丢WAL日志)
|
||||
stmt.execute("PRAGMA temp_store=MEMORY;"); // 临时表/索引存内存(减少磁盘IO)
|
||||
stmt.execute("PRAGMA busy_timeout=2000;"); // 忙等待超时:2秒(避免瞬时并发锁等待)
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("初始化SQLite数据源失败(路径:" + dbFilePath + ")", e);
|
||||
}
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭指定数据库的数据源(释放所有连接)
|
||||
*
|
||||
* @param dbFilePath 数据库文件路径
|
||||
*/
|
||||
public static void closeDataSource(String dbFilePath) {
|
||||
if (dbFilePath == null) return;
|
||||
BasicDataSource dataSource = DATA_SOURCE_POOL.remove(dbFilePath);
|
||||
if (dataSource != null) {
|
||||
try {
|
||||
dataSource.close(); // DBCP2会自动关闭所有活跃/空闲连接
|
||||
} catch (SQLException e) {
|
||||
System.err.println("关闭SQLite数据源失败(路径:" + dbFilePath + "):" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭所有数据源(JVM退出时自动调用)
|
||||
*/
|
||||
public static void closeAllDataSources() {
|
||||
for (BasicDataSource dataSource : DATA_SOURCE_POOL.values()) {
|
||||
try {
|
||||
dataSource.close();
|
||||
} catch (SQLException e) {
|
||||
System.err.println("关闭SQLite数据源失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
// 清理缓存(避免内存泄漏)
|
||||
DATA_SOURCE_POOL.clear();
|
||||
FIELD_CACHE.clear();
|
||||
}
|
||||
|
||||
// JVM关闭钩子:确保程序退出时释放所有数据源资源
|
||||
static {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(SQLiteUtil::closeAllDataSources));
|
||||
}
|
||||
|
||||
|
||||
// ========================== 数据查询核心方法 ==========================
|
||||
|
||||
/**
|
||||
* 执行查询并返回单个对象(优化版:连接池+字段缓存)
|
||||
*
|
||||
* @param dbFilePath 数据库路径
|
||||
* @param sql 查询SQL
|
||||
* @param params SQL参数
|
||||
* @param clazz 返回对象类型
|
||||
* @return 单个对象(无结果返回null)
|
||||
*/
|
||||
public static <T> T queryForObject(String dbFilePath, String sql, List<Object> params, Class<T> clazz) throws SQLException {
|
||||
List<T> results = queryForList(dbFilePath, sql, params, clazz);
|
||||
return results.isEmpty() ? null : results.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行查询并返回对象列表
|
||||
* 执行查询并返回对象列表(优化版:预构建字段映射+资源自动回收)
|
||||
*
|
||||
* @param dbFilePath 数据库路径
|
||||
* @param sql 查询SQL
|
||||
* @param params SQL参数
|
||||
* @param clazz 返回对象类型
|
||||
* @return 结果列表(无结果返回空列表)
|
||||
*/
|
||||
public static <T> List<T> queryForList(String dbFilePath, String sql, List<Object> params, Class<T> clazz) throws SQLException, IllegalAccessException, InstantiationException {
|
||||
public static <T> List<T> queryForList(String dbFilePath, String sql, List<Object> params, Class<T> clazz) throws SQLException {
|
||||
List<T> resultList = new ArrayList<>();
|
||||
if (sql == null || sql.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("查询SQL不能为空");
|
||||
}
|
||||
|
||||
// 使用try-with-resources确保资源自动关闭
|
||||
// 预加载字段映射(缓存生效,避免重复反射)
|
||||
Map<String, Field> fieldMap = getFieldMap(clazz);
|
||||
|
||||
// try-with-resources:自动关闭Connection、PreparedStatement、ResultSet
|
||||
try (Connection conn = getConnection(dbFilePath);
|
||||
PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) {
|
||||
PreparedStatement pstmt = createPreparedStatement(conn, sql, params);
|
||||
ResultSet rs = pstmt.executeQuery()) {
|
||||
|
||||
// 执行查询
|
||||
try (ResultSet rs = pstmt.executeQuery()) {
|
||||
ResultSetMetaData metaData = rs.getMetaData();
|
||||
int columnCount = metaData.getColumnCount();
|
||||
ResultSetMetaData metaData = rs.getMetaData();
|
||||
int columnCount = metaData.getColumnCount();
|
||||
// 预构建「列名-字段」映射(一次构建,循环复用)
|
||||
ColumnFieldMapping[] mappings = buildColumnFieldMappings(metaData, columnCount, fieldMap);
|
||||
|
||||
// 处理结果集
|
||||
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);
|
||||
// 处理结果集(反射赋值)
|
||||
while (rs.next()) {
|
||||
try {
|
||||
T obj = clazz.getDeclaredConstructor().newInstance(); // 无参构造器(需确保类有)
|
||||
for (int i = 0; i < columnCount; i++) {
|
||||
ColumnFieldMapping mapping = mappings[i];
|
||||
if (mapping.field != null) {
|
||||
Object value = rs.getObject(i + 1); // ResultSet列索引从1开始
|
||||
setFieldValueOptimized(obj, mapping.field, value);
|
||||
}
|
||||
}
|
||||
resultList.add(obj);
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException |
|
||||
InvocationTargetException e) {
|
||||
throw new SQLException("创建对象实例失败(类型:" + clazz.getName() + ")", e);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
// 异常时关闭当前数据源(避免后续请求使用异常连接)
|
||||
closeDataSource(dbFilePath);
|
||||
throw new SQLException("执行查询失败(SQL:" + sql + ")", e);
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行增删改SQL语句
|
||||
* 执行增删改SQL(优化版:连接池+自动提交)
|
||||
*
|
||||
* @param dbFilePath 数据库路径
|
||||
* @param sql 增删改SQL
|
||||
* @param params SQL参数
|
||||
* @return 影响行数
|
||||
*/
|
||||
public static int executeUpdate(String dbFilePath, String sql, List<Object> params) throws SQLException {
|
||||
if (sql == null || sql.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("执行SQL不能为空");
|
||||
}
|
||||
|
||||
try (Connection conn = getConnection(dbFilePath);
|
||||
PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) {
|
||||
return pstmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
closeDataSource(dbFilePath);
|
||||
throw new SQLException("执行增删改失败(SQL:" + sql + ")", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并设置参数化的PreparedStatement(关键:处理LocalDateTime转String)
|
||||
* 执行计数查询(优化版:轻量结果处理)
|
||||
*
|
||||
* @param dbFilePath 数据库路径
|
||||
* @param sql 计数SQL(如SELECT COUNT(*) ...)
|
||||
* @param params SQL参数
|
||||
* @return 计数结果(无结果返回0)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
public static int queryForCount(String dbFilePath, String sql, List<Object> params) throws SQLException {
|
||||
if (sql == null || sql.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("计数SQL不能为空");
|
||||
}
|
||||
|
||||
try (Connection conn = getConnection(dbFilePath);
|
||||
PreparedStatement pstmt = createPreparedStatement(conn, sql, params);
|
||||
ResultSet rs = pstmt.executeQuery()) {
|
||||
return rs.next() ? rs.getInt(1) : 0;
|
||||
} catch (SQLException e) {
|
||||
closeDataSource(dbFilePath);
|
||||
throw new SQLException("执行计数查询失败(SQL:" + sql + ")", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行DDL语句(创建表/索引等)
|
||||
*
|
||||
* @param dbFilePath 数据库路径
|
||||
* @param sql DDL语句
|
||||
* @param params SQL参数(可选,如动态表名)
|
||||
*/
|
||||
public static void executeDDL(String dbFilePath, String sql, List<Object> params) throws SQLException {
|
||||
if (dbFilePath == null || dbFilePath.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("数据库文件路径不能为空");
|
||||
}
|
||||
if (sql == null || sql.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("DDL语句不能为空");
|
||||
}
|
||||
|
||||
try (Connection conn = getConnection(dbFilePath);
|
||||
PreparedStatement pstmt = createPreparedStatement(conn, sql, params)) {
|
||||
pstmt.execute();
|
||||
} catch (SQLException e) {
|
||||
closeDataSource(dbFilePath);
|
||||
throw new SQLException("执行DDL失败(SQL:" + sql + ",路径:" + dbFilePath + ")", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载:无参数的DDL执行
|
||||
*/
|
||||
public static void executeDDL(String dbFilePath, String sql) throws SQLException {
|
||||
executeDDL(dbFilePath, sql, null);
|
||||
}
|
||||
|
||||
|
||||
// ========================== 工具辅助方法 ==========================
|
||||
|
||||
/**
|
||||
* 创建PreparedStatement(复用Connection,避免重复获取)
|
||||
*
|
||||
* @param conn 已获取的Connection(从连接池来)
|
||||
* @param sql SQL语句
|
||||
* @param params 参数列表
|
||||
* @return 配置好的PreparedStatement
|
||||
*/
|
||||
private static PreparedStatement createPreparedStatement(Connection conn, String sql, List<Object> params) throws SQLException {
|
||||
PreparedStatement pstmt = conn.prepareStatement(sql);
|
||||
// 设置SQL参数(处理特殊类型如LocalDateTime、byte[])
|
||||
if (params != null && !params.isEmpty()) {
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
setPreparedStatementValue(pstmt, i + 1, params.get(i));
|
||||
}
|
||||
}
|
||||
return pstmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射设置对象的字段值
|
||||
* 设置PreparedStatement参数(处理SQLite特殊类型映射)
|
||||
*
|
||||
* @param pstmt PreparedStatement对象
|
||||
* @param index 参数索引(从1开始)
|
||||
* @param value 参数值
|
||||
*/
|
||||
private static void setFieldValue(Object obj, String columnName, Object value) {
|
||||
private static void setPreparedStatementValue(PreparedStatement pstmt, int index, Object value) throws SQLException {
|
||||
if (value == null) {
|
||||
pstmt.setNull(index, Types.NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// 特殊类型处理
|
||||
if (value instanceof LocalDateTime) {
|
||||
// LocalDateTime转字符串(SQLite无原生DateTime类型)
|
||||
String dateStr = ((LocalDateTime) value).format(LOCAL_DATE_TIME_FORMATTER);
|
||||
pstmt.setString(index, dateStr);
|
||||
} else if (value instanceof byte[]) {
|
||||
// 二进制数据(如BLOB)
|
||||
pstmt.setBytes(index, (byte[]) value);
|
||||
} else if (value instanceof java.util.Date) {
|
||||
// Date转Timestamp(兼容SQLite时间处理)
|
||||
pstmt.setTimestamp(index, new Timestamp(((java.util.Date) value).getTime()));
|
||||
} else {
|
||||
// 通用类型(依赖JDBC自动映射)
|
||||
pstmt.setObject(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预构建「列名-字段」映射(支持下划线转驼峰)
|
||||
*
|
||||
* @param metaData 结果集元数据
|
||||
* @param columnCount 列数
|
||||
* @param fieldMap 类字段缓存
|
||||
* @return 映射数组(与列顺序对应)
|
||||
*/
|
||||
private static ColumnFieldMapping[] buildColumnFieldMappings(ResultSetMetaData metaData, int columnCount, Map<String, Field> fieldMap) throws SQLException {
|
||||
ColumnFieldMapping[] mappings = new ColumnFieldMapping[columnCount];
|
||||
for (int i = 0; i < columnCount; i++) {
|
||||
String columnName = metaData.getColumnName(i + 1); // 列名(如military_name)
|
||||
ColumnFieldMapping mapping = new ColumnFieldMapping();
|
||||
mapping.columnName = columnName;
|
||||
|
||||
// 1. 直接匹配字段名(如列名=字段名)
|
||||
Field field = fieldMap.get(columnName);
|
||||
// 2. 下划线转驼峰匹配(如military_name → militaryName)
|
||||
if (field == null) {
|
||||
String camelCaseName = underscoreToCamelCase(columnName);
|
||||
field = fieldMap.get(camelCaseName);
|
||||
}
|
||||
|
||||
mapping.field = field;
|
||||
mappings[i] = mapping;
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的字段缓存(含父类字段,一次反射永久缓存)
|
||||
*
|
||||
* @param clazz 目标类
|
||||
* @return 字段名→Field的映射(不可修改)
|
||||
*/
|
||||
private static Map<String, Field> getFieldMap(Class<?> clazz) {
|
||||
return FIELD_CACHE.computeIfAbsent(clazz, key -> {
|
||||
Map<String, Field> fieldMap = new HashMap<>();
|
||||
// 遍历当前类及所有父类(直到Object)
|
||||
Class<?> currentClass = clazz;
|
||||
while (currentClass != null && currentClass != Object.class) {
|
||||
Field[] fields = currentClass.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
field.setAccessible(true); // 突破访问权限(private字段可赋值)
|
||||
fieldMap.put(field.getName(), field);
|
||||
}
|
||||
currentClass = currentClass.getSuperclass();
|
||||
}
|
||||
return Collections.unmodifiableMap(fieldMap); // 避免外部修改缓存
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化的字段赋值(处理类型转换,避免重复异常捕获)
|
||||
*
|
||||
* @param obj 目标对象
|
||||
* @param field 字段
|
||||
* @param value 待赋值的值
|
||||
*/
|
||||
private static void setFieldValueOptimized(Object obj, Field field, Object value) {
|
||||
if (value == null) return;
|
||||
|
||||
try {
|
||||
Field field = findField(obj.getClass(), columnName);
|
||||
if (field != null) {
|
||||
field.setAccessible(true);
|
||||
Object convertedValue = convertValue(field.getType(), value);
|
||||
Class<?> fieldType = field.getType();
|
||||
// 类型匹配直接赋值(无转换开销)
|
||||
if (fieldType.isInstance(value)) {
|
||||
field.set(obj, value);
|
||||
return;
|
||||
}
|
||||
// 类型不匹配时转换(支持基础类型、时间、二进制等)
|
||||
Object convertedValue = convertValueOptimized(fieldType, value);
|
||||
if (convertedValue != null) {
|
||||
field.set(obj, convertedValue);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
System.err.println("警告: 无法设置字段 " + columnName + " 的值 - " + e.getMessage());
|
||||
System.err.println("警告:字段赋值失败(字段:" + field.getName() + ",值类型:" + value.getClass().getName() + "):" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找字段、支持下划线命名转驼峰命名(如created_at → createdAt)
|
||||
* 优化的类型转换(支持常见SQLite类型→Java类型)
|
||||
*
|
||||
* @param targetType 目标类型(字段类型)
|
||||
* @param value 原始值(ResultSet获取的值)
|
||||
* @return 转换后的值(无法转换返回null)
|
||||
*/
|
||||
private static Field findField(Class<?> clazz, String columnName) {
|
||||
// 1. 直接匹配字段名(如数据库列名与字段名一致)
|
||||
private static Object convertValueOptimized(Class<?> targetType, Object value) {
|
||||
// 基础数值类型转换
|
||||
if (targetType == int.class || targetType == Integer.class) {
|
||||
if (value instanceof Number) return ((Number) value).intValue();
|
||||
if (value instanceof String) return parseInteger((String) value);
|
||||
} else if (targetType == long.class || targetType == Long.class) {
|
||||
if (value instanceof Number) return ((Number) value).longValue();
|
||||
if (value instanceof String) return parseLong((String) value);
|
||||
} else if (targetType == double.class || targetType == Double.class) {
|
||||
if (value instanceof Number) return ((Number) value).doubleValue();
|
||||
if (value instanceof String) return parseDouble((String) value);
|
||||
} else if (targetType == boolean.class || targetType == Boolean.class) {
|
||||
if (value instanceof Number) return ((Number) value).intValue() != 0;
|
||||
if (value instanceof String) return "true".equalsIgnoreCase((String) value) || "1".equals(value);
|
||||
}
|
||||
// 字符串类型
|
||||
else if (targetType == String.class) {
|
||||
return value.toString();
|
||||
}
|
||||
// 时间类型(LocalDateTime)
|
||||
else if (targetType == LocalDateTime.class) {
|
||||
return convertToLocalDateTime(value);
|
||||
}
|
||||
// 时间类型(Date)
|
||||
else if (targetType == java.util.Date.class) {
|
||||
return convertToDate(value);
|
||||
}
|
||||
// 二进制类型(byte[])
|
||||
else if (targetType == byte[].class) {
|
||||
return convertToByteArray(value);
|
||||
}
|
||||
|
||||
// 不支持的类型转换(打印警告)
|
||||
System.err.println("警告:不支持的类型转换(目标类型:" + targetType.getName() + ",原始值类型:" + value.getClass().getName() + ")");
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========================== 类型转换辅助方法 ==========================
|
||||
private static Integer parseInteger(String value) {
|
||||
try {
|
||||
return clazz.getDeclaredField(columnName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
// 2. 下划线转驼峰后匹配(如created_at → createdAt)
|
||||
String camelCaseName = underscoreToCamelCase(columnName);
|
||||
return Integer.parseInt(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println("警告:字符串转Integer失败(值:" + value + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Long parseLong(String value) {
|
||||
try {
|
||||
return Long.parseLong(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println("警告:字符串转Long失败(值:" + value + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Double parseDouble(String value) {
|
||||
try {
|
||||
return Double.parseDouble(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println("警告:字符串转Double失败(值:" + value + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static LocalDateTime convertToLocalDateTime(Object value) {
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return clazz.getDeclaredField(camelCaseName);
|
||||
} catch (NoSuchFieldException e1) {
|
||||
// 3. 递归查找父类(支持继承场景)
|
||||
if (clazz.getSuperclass() != null) {
|
||||
return findField(clazz.getSuperclass(), columnName);
|
||||
}
|
||||
return LocalDateTime.parse((String) value, LOCAL_DATE_TIME_FORMATTER);
|
||||
} catch (DateTimeParseException e) {
|
||||
System.err.println("警告:字符串转LocalDateTime失败(值:" + value + ")");
|
||||
return null;
|
||||
}
|
||||
} else if (value instanceof Timestamp) {
|
||||
return ((Timestamp) value).toLocalDateTime();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static java.util.Date convertToDate(Object value) {
|
||||
if (value instanceof Timestamp) {
|
||||
return new java.util.Date(((Timestamp) value).getTime());
|
||||
} else if (value instanceof LocalDateTime) {
|
||||
return java.util.Date.from(((LocalDateTime) value).atZone(java.time.ZoneId.systemDefault()).toInstant());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static byte[] convertToByteArray(Object value) {
|
||||
if (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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线命名转驼峰命名(工具方法)
|
||||
* 下划线命名转驼峰命名(如military_type → militaryType)
|
||||
*
|
||||
* @param str 下划线字符串
|
||||
* @return 驼峰字符串
|
||||
*/
|
||||
private static String underscoreToCamelCase(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
if (str == null || str.isEmpty()) return str;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean nextUpperCase = false;
|
||||
for (char c : str.toCharArray()) {
|
||||
@ -163,7 +536,6 @@ public class SQLiteUtil {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
nextUpperCase = false;
|
||||
} else {
|
||||
// 修正:首字母小写(如CREATED_AT → createdAt、而非CreatedAt)
|
||||
sb.append(Character.toLowerCase(c));
|
||||
}
|
||||
}
|
||||
@ -171,136 +543,26 @@ public class SQLiteUtil {
|
||||
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;
|
||||
private static class ColumnFieldMapping {
|
||||
String columnName; // 数据库列名
|
||||
Field field; // 对应的Java字段
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行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语句
|
||||
* 初始化模型相关表(model_type + model)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库表
|
||||
*/
|
||||
public static void initializationModel(String modelPath) {
|
||||
// 创建模型类型表
|
||||
public static void initializationModel(String modelPath) throws SQLException {
|
||||
String sql = """
|
||||
CREATE TABLE "model_type" (
|
||||
CREATE TABLE IF NOT EXISTS "model_type" (
|
||||
"id" TEXT,
|
||||
"name" TEXT,
|
||||
"parent_id" TEXT,
|
||||
@ -312,9 +574,8 @@ public class SQLiteUtil {
|
||||
""";
|
||||
executeDDL(modelPath, sql);
|
||||
|
||||
// 创建模型表
|
||||
sql = """
|
||||
CREATE TABLE "model" (
|
||||
CREATE TABLE IF NOT EXISTS "model" (
|
||||
"id" TEXT,
|
||||
"model_type_id" TEXT,
|
||||
"model_name" TEXT,
|
||||
@ -331,12 +592,11 @@ public class SQLiteUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库表
|
||||
* 初始化军标相关表(military_type + military)
|
||||
*/
|
||||
public static void initializationMilitary(String modelPath) {
|
||||
// 创建军标类型表
|
||||
public static void initializationMilitary(String militaryPath) throws SQLException {
|
||||
String sql = """
|
||||
CREATE TABLE "military_type" (
|
||||
CREATE TABLE IF NOT EXISTS "military_type" (
|
||||
"id" TEXT,
|
||||
"name" TEXT,
|
||||
"parent_id" TEXT,
|
||||
@ -346,29 +606,29 @@ public class SQLiteUtil {
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
""";
|
||||
executeDDL(modelPath, sql);
|
||||
executeDDL(militaryPath, sql);
|
||||
|
||||
// 创建军标表
|
||||
sql = """
|
||||
CREATE TABLE "military" (
|
||||
CREATE TABLE IF NOT EXISTS "military" (
|
||||
"id" TEXT,
|
||||
"military_type_id" TEXT,
|
||||
"military_name" TEXT,
|
||||
"military_type" TEXT,
|
||||
"data" TEXT,
|
||||
"view" TEXT,
|
||||
"military_data" BLOB,
|
||||
"created_at" TEXT,
|
||||
"updated_at" TEXT,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
""";
|
||||
executeDDL(modelPath, sql);
|
||||
executeDDL(militaryPath, sql);
|
||||
}
|
||||
|
||||
public static void initializationIcon(String iconPath) {
|
||||
// 创建图标类型表
|
||||
/**
|
||||
* 初始化图标相关表(icon_type + icon)
|
||||
*/
|
||||
public static void initializationIcon(String iconPath) throws SQLException {
|
||||
String sql = """
|
||||
CREATE TABLE "icon_type" (
|
||||
CREATE TABLE IF NOT EXISTS "icon_type" (
|
||||
"id" TEXT,
|
||||
"name" TEXT,
|
||||
"parent_id" TEXT,
|
||||
@ -380,9 +640,8 @@ public class SQLiteUtil {
|
||||
""";
|
||||
executeDDL(iconPath, sql);
|
||||
|
||||
// 创建图标表
|
||||
sql = """
|
||||
CREATE TABLE "icon" (
|
||||
CREATE TABLE IF NOT EXISTS "icon" (
|
||||
"id" TEXT,
|
||||
"icon_type_id" TEXT,
|
||||
"icon_name" TEXT,
|
||||
|
||||
12
src/main/java/com/yj/earth/vo/MilitaryDataVo.java
Normal file
12
src/main/java/com/yj/earth/vo/MilitaryDataVo.java
Normal file
@ -0,0 +1,12 @@
|
||||
package com.yj.earth.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class MilitaryDataVo {
|
||||
@Schema(description = "军标名称")
|
||||
private String militaryName;
|
||||
@Schema(description = "军标数据")
|
||||
private byte[] militaryData;
|
||||
}
|
||||
@ -2,9 +2,29 @@ package com.yj.earth.vo;
|
||||
|
||||
import com.yj.earth.business.domain.Military;
|
||||
import com.yj.earth.business.domain.Model;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class MilitaryVo extends Military {
|
||||
@Schema(description = "军标类型名称")
|
||||
private String militaryTypeName;
|
||||
@Schema(description = "军标数据URL")
|
||||
private String militaryDataUrl;
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
@Schema(description = "军标类型ID")
|
||||
private String militaryTypeId;
|
||||
@Schema(description = "军标名称")
|
||||
private String militaryName;
|
||||
@Schema(description = "军标类型")
|
||||
private String militaryType;
|
||||
@Schema(description = "军标数据")
|
||||
private byte[] militaryData;
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdAt;
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user