This commit is contained in:
2025-11-28 15:35:06 +08:00
parent 7d8aeedcf2
commit 74bee3e232
18 changed files with 330 additions and 169 deletions

View File

@ -77,7 +77,7 @@ public class AuthGenerator {
public static void main(String[] args) { public static void main(String[] args) {
try { try {
// 生成加密的授权字符串 // 生成加密的授权字符串
String authContent = generateAuth("标准版", 1000, 365, "3A446222D1FE537F6C9EEF5C2AB3F957"); String authContent = generateAuth("标准版", 1000, 365, "8B1FB12E9F8E80109724989E0B25773B");
// 定义授权文件路径(当前目录下的 yjearth.YJ // 定义授权文件路径(当前目录下的 yjearth.YJ
Path licPath = Paths.get("yjearth.YJ"); Path licPath = Paths.get("yjearth.YJ");

View File

@ -19,37 +19,46 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
@Tag(name = "CSV文件解析") @Tag(name = "CSV文件解析")
@RestController @RestController
@RequestMapping("/csv") @RequestMapping("/csv")
public class CsvController { public class CsvController {
@GetMapping("/parseCsv") @GetMapping("/parseCsv")
@Operation(summary = "解析CSV文件") @Operation(summary = "解析CSV文件")
public ApiResponse parseCsv(@Parameter(description = "文件路径") @RequestParam String filePath) { public ApiResponse parseCsv(@Parameter(description = "文件路径") @RequestParam String filePath) {
Map<String, Object> response = new HashMap<>();
List<CsvField> fieldList = new ArrayList<>(); List<CsvField> fieldList = new ArrayList<>();
try { try {
File file = new File(filePath); File file = new File(filePath);
// 验证文件是否存在
if (!file.exists()) {
return ApiResponse.failure("文件不存在");
}
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), "GBK"); try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), "GBK");
CSVParser parser = new CSVParser(reader, CSVFormat.DEFAULT)) { CSVParser parser = new CSVParser(reader, CSVFormat.DEFAULT)) {
// 遍历所有行CSVRecord即一行数据 // 遍历所有行、跳过第一行下标为0
for (CSVRecord record : parser) { for (CSVRecord record : parser) {
// 跳过表头行(第一行)
if (record.getRecordNumber() == 1) {
continue;
}
// 确保每行至少有3列A、B、C列 // 确保每行至少有3列A、B、C列
if (record.size() >= 3) { if (record.size() >= 3) {
String label = record.get(1).trim(); String label = record.get(1).trim();
String key = record.get(2).trim(); String key = record.get(2).trim();
if (!key.isEmpty() && !label.isEmpty()) {
fieldList.add(new CsvField(key, label)); fieldList.add(new CsvField(key, label));
} }
} }
} }
}
return ApiResponse.success(fieldList); return ApiResponse.success(fieldList);
} catch (Exception e) { } catch (Exception e) {
return ApiResponse.failure(e.getMessage()); return ApiResponse.failure("解析失败:" + e.getMessage());
} }
} }
} }

View File

@ -91,7 +91,7 @@ public class DeviceController {
List<Device> deviceList = devicePage.getRecords(); List<Device> deviceList = devicePage.getRecords();
if (CollectionUtils.isEmpty(deviceList)) { if (CollectionUtils.isEmpty(deviceList)) {
// 如果没有数据直接返回空分页 // 如果没有数据直接返回空分页
return ApiResponse.success(devicePage); return ApiResponse.success(devicePage);
} }
@ -105,15 +105,15 @@ public class DeviceController {
// 异步并发检测所有 IP 的在线状态 // 异步并发检测所有 IP 的在线状态
Map<String, Boolean> reachabilityMap = NetUtils.checkReachabilityAsync(ipAddresses); Map<String, Boolean> reachabilityMap = NetUtils.checkReachabilityAsync(ipAddresses);
// 更新设备状态:遍历设备列表根据 IP 设置 status 字段 // 更新设备状态:遍历设备列表根据 IP 设置 status 字段
deviceList.forEach(device -> { deviceList.forEach(device -> {
String ip = device.getIp(); String ip = device.getIp();
if (ip != null) { if (ip != null) {
Boolean isOnline = reachabilityMap.get(ip); Boolean isOnline = reachabilityMap.get(ip);
// 根据 Ping 结果设置 status: 1 为在线0 为离线 // 根据 Ping 结果设置 status: 1 为在线0 为离线
device.setStatus(isOnline != null && isOnline ? 1 : 0); device.setStatus(isOnline != null && isOnline ? 1 : 0);
} else { } else {
// 如果 IP 地址为空直接设置为离线 // 如果 IP 地址为空直接设置为离线
device.setStatus(0); device.setStatus(0);
} }
}); });

View File

@ -36,10 +36,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.*;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Tag(name = "图标数据管理") @Tag(name = "图标数据管理")
@ -194,8 +191,8 @@ public class IconLibraryController {
@Operation(summary = "图标类型树形列表") @Operation(summary = "图标类型树形列表")
@GetMapping("/iconTypeTree") @GetMapping("/iconTypeTree")
public ApiResponse iconTypeTree() throws SQLException, IllegalAccessException, InstantiationException { public ApiResponse iconTypeTree(@Parameter(description = "图标名称") @RequestParam(value = "iconName", required = false) String iconName) throws SQLException, IllegalAccessException, InstantiationException {
List<IconTypeVo> treeList = iconTypeList(); List<IconTypeVo> treeList = iconTypeList(iconName);
return ApiResponse.success(treeList); return ApiResponse.success(treeList);
} }
@ -267,27 +264,30 @@ public class IconLibraryController {
} }
} }
@Operation(summary = "根据类型查询图标列表") @Operation(summary = "根据类型和名称模糊查询图标列表")
@PostMapping("/iconList") @PostMapping("/iconList")
public ApiResponse getIconList(@RequestParam("iconTypeId") @Parameter(description = "图标类型ID") String typeId) throws SQLException, IllegalAccessException, InstantiationException { public ApiResponse getIconList(
@RequestParam("iconTypeId") @Parameter(description = "图标类型ID") String iconTypeId,
@Parameter(description = "图标名称模糊查询") @RequestParam(value = "name", required = false) String name)
throws SQLException, IllegalAccessException, InstantiationException {
String iconPath = getIconLibrary(); String iconPath = getIconLibrary();
if (iconPath == null) { if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库"); return ApiResponse.failure("请先创建或导入图标库");
} }
// 获取当前类型及所有子类型ID递归 // 获取当前类型及所有子类型ID递归
List<String> typeIdList = getIconTypeIdsWithChildren(typeId); List<String> typeIdList = getIconTypeIdsWithChildren(iconTypeId);
if (typeIdList == null || typeIdList.isEmpty()) { if (typeIdList == null || typeIdList.isEmpty()) {
return ApiResponse.success(new ArrayList<>()); return ApiResponse.success(new ArrayList<>());
} }
// 构建IN条件处理SQL注入风险 // 构建IN条件
String idsWithQuotes = typeIdList.stream() String idsWithQuotes = typeIdList.stream()
.map(id -> "'" + id + "'") .map(id -> "'" + id + "'")
.collect(Collectors.joining(",")); .collect(Collectors.joining(","));
// 多表联查 // 构建SQL语句
String querySql = """ StringBuilder sql = new StringBuilder("""
SELECT SELECT
i.id, i.id,
i.icon_type_id as iconTypeId, i.icon_type_id as iconTypeId,
@ -299,16 +299,26 @@ public class IconLibraryController {
FROM icon i FROM icon i
JOIN icon_type t ON i.icon_type_id = t.id JOIN icon_type t ON i.icon_type_id = t.id
WHERE i.icon_type_id IN (%s) WHERE i.icon_type_id IN (%s)
ORDER BY i.created_at DESC """.formatted(idsWithQuotes));
""".replace("%s", idsWithQuotes);
// 查询并转换为VO // 如果传入了名称、则增加模糊查询条件
if (name != null && !name.trim().isEmpty()) {
// 直接拼接SQL、注意SQL注入风险
sql.append(" AND i.icon_name LIKE '%" + name.trim() + "%'");
}
// 统一添加排序条件
sql.append(" ORDER BY i.created_at DESC");
// 执行查询
List<IconVo> iconVoList = SQLiteUtil.queryForList( List<IconVo> iconVoList = SQLiteUtil.queryForList(
iconPath, querySql, null, IconVo.class iconPath, sql.toString(), null, IconVo.class
); );
for (IconVo vo : iconVoList) { for (IconVo vo : iconVoList) {
vo.setIconDataUrl("/iconLibrary/data/icon/" + vo.getId() + "/" + vo.getIconType()); vo.setIconDataUrl("/iconLibrary/data/icon/" + vo.getId() + "/" + vo.getIconType());
} }
return ApiResponse.success(iconVoList); return ApiResponse.success(iconVoList);
} }
@ -373,7 +383,7 @@ public class IconLibraryController {
} }
// 返回更新后的树形列表 // 返回更新后的树形列表
List<IconTypeVo> treeList = iconTypeList(); List<IconTypeVo> treeList = iconTypeList(null);
return ApiResponse.success(treeList); return ApiResponse.success(treeList);
} }
@ -381,7 +391,10 @@ public class IconLibraryController {
LambdaQueryWrapper<IconLibrary> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<IconLibrary> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(IconLibrary::getIsEnable, 1); queryWrapper.eq(IconLibrary::getIsEnable, 1);
IconLibrary library = iconLibraryService.getOne(queryWrapper); IconLibrary library = iconLibraryService.getOne(queryWrapper);
return library == null ? null : library.getPath(); if(library == null){
throw new RuntimeException("请先创建或导入图标库");
}
return library.getPath();
} }
private void addIconLibrary(String iconPath) { private void addIconLibrary(String iconPath) {
@ -411,24 +424,44 @@ public class IconLibraryController {
} }
} }
private List<IconTypeVo> iconTypeList() throws SQLException, IllegalAccessException, InstantiationException { /**
* 查询图标分类树形结构
*/
private List<IconTypeVo> iconTypeList(String iconName) throws SQLException, IllegalAccessException, InstantiationException {
String iconPath = getIconLibrary(); String iconPath = getIconLibrary();
if (iconPath == null) { if (iconPath == null) {
return new ArrayList<>(); return null;
} }
String querySql = """ // 构建基础SQL语句
SELECT StringBuilder sqlBuilder = new StringBuilder("""
id, name, parent_id as parentId, SELECT DISTINCT icon_type.id, icon_type.name, icon_type.parent_id,
tree_index as treeIndex, created_at as createdAt, icon_type.tree_index, icon_type.created_at,
updated_at as updatedAt icon_type.updated_at
FROM icon_type FROM icon_type
ORDER BY tree_index ASC """);
""";
List<IconType> typeList = SQLiteUtil.queryForList(iconPath, querySql, null, IconType.class);
// 构建树形结构 // 如果传入了图标名称、则拼接 JOIN 和 WHERE 条件
return buildIconTypeTree(typeList); if (iconName != null && !iconName.trim().isEmpty()) {
String trimmedName = iconName.trim();
// 直接拼接SQL
sqlBuilder.append(" INNER JOIN icon ON icon_type.id = icon.icon_type_id");
sqlBuilder.append(" WHERE icon.icon_name LIKE '%" + trimmedName + "%'");
}
// 为所有查询都加上统一的排序条件
sqlBuilder.append(" ORDER BY icon_type.tree_index ASC");
// 执行查询、获取符合条件的图标分类列表
List<IconType> iconTypes = SQLiteUtil.queryForList(
iconPath,
sqlBuilder.toString(),
null,
IconType.class
);
// 将扁平的分类列表转换为树形结构
return buildIconTypeTree(iconTypes);
} }
private List<IconTypeVo> buildIconTypeTree(List<IconType> typeList) { private List<IconTypeVo> buildIconTypeTree(List<IconType> typeList) {

View File

@ -204,8 +204,8 @@ public class MilitaryLibraryController {
@Operation(summary = "军标类型树形列表") @Operation(summary = "军标类型树形列表")
@GetMapping("/militaryTypeTree") @GetMapping("/militaryTypeTree")
public ApiResponse militaryTypeTree() throws SQLException, IllegalAccessException, InstantiationException { public ApiResponse militaryTypeTree(@Parameter(description = "军标名称") @RequestParam(value = "militaryName", required = false) String militaryName) throws SQLException, IllegalAccessException, InstantiationException {
List<MilitaryTypeVo> treeList = militaryTypeList(); List<MilitaryTypeVo> treeList = militaryTypeList(militaryName);
return ApiResponse.success(treeList); return ApiResponse.success(treeList);
} }
@ -241,6 +241,7 @@ public class MilitaryLibraryController {
return ApiResponse.success(null); return ApiResponse.success(null);
} }
@Operation(summary = "获取军标文件数据") @Operation(summary = "获取军标文件数据")
@GetMapping("/data/military/{militaryId}/{fileSuffix}") @GetMapping("/data/military/{militaryId}/{fileSuffix}")
public ResponseEntity<byte[]> getMilitaryData(@PathVariable("militaryId") @Parameter(description = "军标ID") String militaryId, @PathVariable("fileSuffix") @Parameter(description = "军标文件后缀") String fileSuffix) { public ResponseEntity<byte[]> getMilitaryData(@PathVariable("militaryId") @Parameter(description = "军标ID") String militaryId, @PathVariable("fileSuffix") @Parameter(description = "军标文件后缀") String fileSuffix) {
@ -275,27 +276,30 @@ public class MilitaryLibraryController {
} }
} }
@Operation(summary = "根据类型查询军标列表") @Operation(summary = "根据类型和名称模糊查询军标列表")
@PostMapping("/militaryList") @PostMapping("/militaryList")
public ApiResponse getMilitaryList(@RequestParam("militaryTypeId") @Parameter(description = "军标类型ID") String typeId) throws SQLException, IllegalAccessException, InstantiationException { public ApiResponse getMilitaryList(
@RequestParam("militaryTypeId") @Parameter(description = "军标类型ID") String militaryTypeId,
@Parameter(description = "军标名称模糊查询") @RequestParam(value = "name", required = false) String name)
throws SQLException, IllegalAccessException, InstantiationException {
String militaryPath = getMilitaryLibrary(); String militaryPath = getMilitaryLibrary();
if (militaryPath == null) { if (militaryPath == null) {
return ApiResponse.failure("请先创建或导入军标库"); return ApiResponse.failure("请先创建或导入军标库");
} }
// 获取当前类型及所有子类型ID递归 // 获取当前类型及所有子类型ID递归
List<String> typeIdList = getMilitaryTypeIdsWithChildren(typeId); List<String> typeIdList = getMilitaryTypeIdsWithChildren(militaryTypeId);
if (typeIdList == null || typeIdList.isEmpty()) { if (typeIdList == null || typeIdList.isEmpty()) {
return ApiResponse.success(new ArrayList<>()); return ApiResponse.success(new ArrayList<>());
} }
// 构建IN条件处理SQL注入风险 // 构建IN条件
String idsWithQuotes = typeIdList.stream() String idsWithQuotes = typeIdList.stream()
.map(id -> "'" + id + "'") .map(id -> "'" + id + "'")
.collect(Collectors.joining(",")); .collect(Collectors.joining(","));
// 多表联查 // 构建SQL语句
String querySql = """ StringBuilder sql = new StringBuilder("""
SELECT SELECT
m.id, m.id,
m.military_type_id as militaryTypeId, m.military_type_id as militaryTypeId,
@ -307,16 +311,26 @@ public class MilitaryLibraryController {
FROM military m FROM military m
JOIN military_type t ON m.military_type_id = t.id JOIN military_type t ON m.military_type_id = t.id
WHERE m.military_type_id IN (%s) WHERE m.military_type_id IN (%s)
ORDER BY m.created_at DESC """.formatted(idsWithQuotes));
""".replace("%s", idsWithQuotes);
// 查询并转换为VO // 如果传入了名称、则增加模糊查询条件
if (name != null && !name.trim().isEmpty()) {
// 直接拼接SQL、注意SQL注入风险
sql.append(" AND m.military_name LIKE '%" + name.trim() + "%'");
}
// 统一添加排序条件
sql.append(" ORDER BY m.created_at DESC");
// 执行查询
List<MilitaryVo> militaryVoList = SQLiteUtil.queryForList( List<MilitaryVo> militaryVoList = SQLiteUtil.queryForList(
militaryPath, querySql, null, MilitaryVo.class militaryPath, sql.toString(), null, MilitaryVo.class
); );
for (MilitaryVo vo : militaryVoList) { for (MilitaryVo vo : militaryVoList) {
vo.setMilitaryDataUrl("/militaryLibrary/data/military/" + vo.getId() + "/" + vo.getMilitaryType()); vo.setMilitaryDataUrl("/militaryLibrary/data/military/" + vo.getId() + "/" + vo.getMilitaryType());
} }
return ApiResponse.success(militaryVoList); return ApiResponse.success(militaryVoList);
} }
@ -407,7 +421,7 @@ public class MilitaryLibraryController {
} }
// 返回更新后的树形列表 // 返回更新后的树形列表
List<MilitaryTypeVo> treeList = militaryTypeList(); List<MilitaryTypeVo> treeList = militaryTypeList(null);
return ApiResponse.success(treeList); return ApiResponse.success(treeList);
} }
@ -415,7 +429,10 @@ public class MilitaryLibraryController {
LambdaQueryWrapper<MilitaryLibrary> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<MilitaryLibrary> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(MilitaryLibrary::getIsEnable, 1); // 1=启用、0=未启用 queryWrapper.eq(MilitaryLibrary::getIsEnable, 1); // 1=启用、0=未启用
MilitaryLibrary library = militaryLibraryService.getOne(queryWrapper); MilitaryLibrary library = militaryLibraryService.getOne(queryWrapper);
return library == null ? null : library.getPath(); if (library == null) {
throw new RuntimeException("请先创建或导入军标库");
}
return library.getPath();
} }
private void addMilitaryLibrary(String militaryPath) { private void addMilitaryLibrary(String militaryPath) {
@ -445,24 +462,44 @@ public class MilitaryLibraryController {
} }
} }
private List<MilitaryTypeVo> militaryTypeList() throws SQLException, IllegalAccessException, InstantiationException { /**
* 查询军标分类树形结构
*/
private List<MilitaryTypeVo> militaryTypeList(String militaryName) throws SQLException, IllegalAccessException, InstantiationException {
String militaryPath = getMilitaryLibrary(); String militaryPath = getMilitaryLibrary();
if (militaryPath == null) { if (militaryPath == null) {
return new ArrayList<>(); return null;
} }
String querySql = """ // 构建基础SQL语句
SELECT StringBuilder sqlBuilder = new StringBuilder("""
id, name, parent_id as parentId, SELECT DISTINCT military_type.id, military_type.name, military_type.parent_id,
tree_index as treeIndex, created_at as createdAt, military_type.tree_index, military_type.created_at,
updated_at as updatedAt military_type.updated_at
FROM military_type FROM military_type
ORDER BY tree_index ASC """);
""";
List<MilitaryType> typeList = SQLiteUtil.queryForList(militaryPath, querySql, null, MilitaryType.class);
// 构建树形结构 // 如果传入了军标名称、则拼接 JOIN 和 WHERE 条件
return buildMilitaryTypeTree(typeList); if (militaryName != null && !militaryName.trim().isEmpty()) {
String trimmedName = militaryName.trim();
// 直接拼接SQL
sqlBuilder.append(" INNER JOIN military ON military_type.id = military.military_type_id");
sqlBuilder.append(" WHERE military.military_name LIKE '%" + trimmedName + "%'");
}
// 为所有查询都加上统一的排序条件
sqlBuilder.append(" ORDER BY military_type.tree_index ASC");
// 执行查询、获取符合条件的军标分类列表
List<MilitaryType> militaryTypes = SQLiteUtil.queryForList(
militaryPath,
sqlBuilder.toString(),
null,
MilitaryType.class
);
// 将扁平的分类列表转换为树形结构
return buildMilitaryTypeTree(militaryTypes);
} }
private List<MilitaryTypeVo> buildMilitaryTypeTree(List<MilitaryType> typeList) { private List<MilitaryTypeVo> buildMilitaryTypeTree(List<MilitaryType> typeList) {

View File

@ -472,7 +472,7 @@ public class ModelLibraryController {
// 如果传入了模型名称、则拼接 JOIN 和 WHERE 条件 // 如果传入了模型名称、则拼接 JOIN 和 WHERE 条件
if (modelName != null && !modelName.trim().isEmpty()) { if (modelName != null && !modelName.trim().isEmpty()) {
String trimmedName = modelName.trim(); String trimmedName = modelName.trim();
// 直接拼接SQL、不做任何转义存在SQL注入风险 // 直接拼接SQL
sqlBuilder.append(" INNER JOIN model ON model_type.id = model.model_type_id"); sqlBuilder.append(" INNER JOIN model ON model_type.id = model.model_type_id");
sqlBuilder.append(" WHERE model.model_name LIKE '%" + trimmedName + "%'"); sqlBuilder.append(" WHERE model.model_name LIKE '%" + trimmedName + "%'");
} }
@ -547,7 +547,7 @@ public class ModelLibraryController {
queryWrapper.eq(ModelLibrary::getIsEnable, 1); queryWrapper.eq(ModelLibrary::getIsEnable, 1);
ModelLibrary modelLibrary = modelLibraryService.getOne(queryWrapper); ModelLibrary modelLibrary = modelLibraryService.getOne(queryWrapper);
if (modelLibrary == null) { if (modelLibrary == null) {
return null; throw new RuntimeException("请先创建或导入模型库");
} }
return modelLibrary.getPath(); return modelLibrary.getPath();
} }

View File

@ -88,24 +88,19 @@ public class RoleController {
if (status != null) { if (status != null) {
queryWrapper.eq(Role::getStatus, status); queryWrapper.eq(Role::getStatus, status);
} }
// 统计当前条件下每个角色名称的数量
List<Role> allMatchedRoles = roleService.list(queryWrapper);
Map<String, Long> roleNameCountMap = allMatchedRoles.stream()
.collect(Collectors.groupingBy(Role::getRoleName, Collectors.counting()));
// 分页查询 // 分页查询
Page<Role> rolePage = roleService.page(new Page<>(pageNum, pageSize), queryWrapper); Page<Role> rolePage = roleService.page(new Page<>(pageNum, pageSize), queryWrapper);
// 转换为 Page<RoleVo> // 转换为 Page<RoleVo>
Page<RoleVo> roleVoPage = (Page<RoleVo>) rolePage.convert(role -> { Page<RoleVo> roleVoPage = (Page<RoleVo>) rolePage.convert(role -> {
LambdaQueryWrapper<User> userQueryWrapper = new LambdaQueryWrapper<>();
userQueryWrapper.eq(User::getRoleId, role.getId());
Long userCount = userService.count(userQueryWrapper);
RoleVo roleVo = new RoleVo(); RoleVo roleVo = new RoleVo();
BeanUtils.copyProperties(role, roleVo); BeanUtils.copyProperties(role, roleVo);
// 设置数量 // 设置数量
roleVo.setCount(roleNameCountMap.getOrDefault(role.getRoleName(), 0L).intValue()); roleVo.setCount(Math.toIntExact(userCount));
return roleVo; return roleVo;
}); });
return ApiResponse.success(roleVoPage); return ApiResponse.success(roleVoPage);
} }

View File

@ -177,7 +177,7 @@ public class SystemController {
source.setSourceType(resultSet.getString("source_type")); source.setSourceType(resultSet.getString("source_type"));
source.setSourcePath(resultSet.getString("source_path")); source.setSourcePath(resultSet.getString("source_path"));
// 关键修改: 读取原始parent_id、而非覆盖为新父节点ID // 读取原始parent_id、而非覆盖为新父节点ID
source.setParentId(resultSet.getString("parent_id")); source.setParentId(resultSet.getString("parent_id"));
Integer treeIndex = resultSet.getObject("tree_index") != null Integer treeIndex = resultSet.getObject("tree_index") != null

View File

@ -50,6 +50,7 @@ public class TsEventController {
public ApiResponse update(@RequestBody UpdateTsEventDto updateTsEventDto) { public ApiResponse update(@RequestBody UpdateTsEventDto updateTsEventDto) {
TsEvent tsEvent = new TsEvent(); TsEvent tsEvent = new TsEvent();
BeanUtils.copyProperties(updateTsEventDto, tsEvent); BeanUtils.copyProperties(updateTsEventDto, tsEvent);
tsEventService.updateById(tsEvent);
return ApiResponse.success(null); return ApiResponse.success(null);
} }

View File

@ -4,9 +4,12 @@ import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yj.earth.business.domain.Source;
import com.yj.earth.business.domain.TsPlan; import com.yj.earth.business.domain.TsPlan;
import com.yj.earth.business.domain.TsSource;
import com.yj.earth.business.domain.User; import com.yj.earth.business.domain.User;
import com.yj.earth.business.service.TsPlanService; import com.yj.earth.business.service.TsPlanService;
import com.yj.earth.business.service.TsSourceService;
import com.yj.earth.business.service.UserService; import com.yj.earth.business.service.UserService;
import com.yj.earth.common.util.ApiResponse; import com.yj.earth.common.util.ApiResponse;
import com.yj.earth.dto.tsPlan.AddTsPlanDto; import com.yj.earth.dto.tsPlan.AddTsPlanDto;
@ -21,6 +24,7 @@ import javax.annotation.Resource;
import java.time.DateTimeException; import java.time.DateTimeException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -36,6 +40,8 @@ public class TsPlanController {
private TsPlanService tsPlanService; private TsPlanService tsPlanService;
@Resource @Resource
private UserService userService; private UserService userService;
@Resource
private TsSourceService tsSourceService;
@PostMapping("/add") @PostMapping("/add")
@Operation(summary = "添加方案") @Operation(summary = "添加方案")
@ -45,9 +51,51 @@ public class TsPlanController {
// 获取当前登录用户ID // 获取当前登录用户ID
tsPlan.setCreatedBy(StpUtil.getLoginIdAsString()); tsPlan.setCreatedBy(StpUtil.getLoginIdAsString());
tsPlanService.save(tsPlan); tsPlanService.save(tsPlan);
// 获取最新得到的方案ID
String tsPlanId = tsPlan.getId();
// 添加方案之后、新增默认资源
createSourceIfNotExists("倾斜模型", "directory", null, 0, 1, tsPlanId);
createSourceIfNotExists("人工模型", "directory", null, 0, 1, tsPlanId);
createSourceIfNotExists("卫星底图", "directory", null, 0, 1, tsPlanId);
createSourceIfNotExists("地形", "directory", null, 0, 1, tsPlanId);
// 创建"在线图源"目录及其子资源
TsSource onlineSource = createSourceIfNotExists("在线图源", "directory", null, 0, 1, tsPlanId);
if (onlineSource != null) {
String onlineSourceId = onlineSource.getId();
createSourceIfNotExists("卫星图", "arcgisWximagery", onlineSourceId, 0, 1, tsPlanId);
createSourceIfNotExists("暗黑地图", "arcgisBlueImagery", onlineSourceId, 0, 1, tsPlanId);
createSourceIfNotExists("路网图", "gdlwImagery", onlineSourceId, 0, 1, tsPlanId);
createSourceIfNotExists("矢量图", "gdslImagery", onlineSourceId, 0, 1, tsPlanId);
}
return ApiResponse.success(null); return ApiResponse.success(null);
} }
private TsSource createSourceIfNotExists(String sourceName, String sourceType, String parentId, int treeIndex, int isShow, String tsPlanId) {
// 检查资源是否已存在通过名称和父ID组合判断唯一性
TsSource existingSource = tsSourceService.getOne(new LambdaQueryWrapper<TsSource>()
.eq(TsSource::getSourceName, sourceName)
.eq(parentId != null, TsSource::getParentId, parentId)
.isNull(parentId == null, TsSource::getParentId));
if (existingSource != null) {
return existingSource;
}
// 不存在则创建新资源
try {
TsSource newSource = new TsSource();
newSource.setId(cn.hutool.core.lang.UUID.fastUUID().toString(true));
newSource.setPlanId(tsPlanId);
newSource.setSourceName(sourceName);
newSource.setSourceType(sourceType);
newSource.setParentId(parentId);
newSource.setTreeIndex(treeIndex);
newSource.setIsShow(isShow);
tsSourceService.save(newSource);
return newSource;
} catch (Exception e) {
return null;
}
}
@Operation(summary = "删除方案") @Operation(summary = "删除方案")
@PostMapping("/delete") @PostMapping("/delete")
public ApiResponse delete(@Parameter(description = "态势方案ID") @RequestParam(required = true) String id) { public ApiResponse delete(@Parameter(description = "态势方案ID") @RequestParam(required = true) String id) {
@ -73,10 +121,10 @@ public class TsPlanController {
@Operation(summary = "查询所有方案") @Operation(summary = "查询所有方案")
@PostMapping("/list") @PostMapping("/list")
public ApiResponse list(@Parameter(description = "分页数量") @RequestParam Integer pageNum, public ApiResponse list(@Parameter(description = "分页数量") @RequestParam(defaultValue = "1") Integer pageNum,
@Parameter(description = "分页大小") @RequestParam Integer pageSize, @Parameter(description = "分页大小") @RequestParam(defaultValue = "10") Integer pageSize,
@Parameter(description = "方案名称") @RequestParam(required = false) String name, @Parameter(description = "方案名称") @RequestParam(required = false) String name,
@Parameter(description = "用户名") @RequestParam(required = false) String username, @Parameter(description = "创建人昵") @RequestParam(required = false) String nickname,
@Parameter(description = "开始时间") @RequestParam(required = false) String startTime, @Parameter(description = "开始时间") @RequestParam(required = false) String startTime,
@Parameter(description = "结束时间") @RequestParam(required = false) String endTime) { @Parameter(description = "结束时间") @RequestParam(required = false) String endTime) {
LambdaQueryWrapper<TsPlan> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<TsPlan> queryWrapper = new LambdaQueryWrapper<>();
@ -86,87 +134,66 @@ public class TsPlanController {
queryWrapper.like(TsPlan::getName, name); queryWrapper.like(TsPlan::getName, name);
} }
// 用户名查询 // 创建人昵称模糊查询
if (StringUtils.isNotBlank(username)) { if (StringUtils.isNotBlank(nickname)) {
LambdaQueryWrapper<User> userQueryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(TsPlan::getCreatedBy,
userQueryWrapper.like(User::getUsername, username); new LambdaQueryWrapper<User>()
List<User> userList = userService.list(userQueryWrapper); .select(User::getId)
.like(User::getNickname, nickname)
// 判断列表是否为空 );
if (userList.isEmpty()) {
// 无匹配用户、直接返回空分页结果
Page<TsPlan> emptyPage = new Page<>(pageNum, pageSize);
emptyPage.setTotal(0);
emptyPage.setRecords(Collections.emptyList());
return ApiResponse.success(emptyPage);
} }
// 提取用户 ID 列表(确保类型匹配) // 时间范围查询
List<String> userIdList = userList.stream()
.map(User::getId)
.filter(Objects::nonNull)
.toList();
// 若 ID 列表为空,也返回空结果
if (userIdList.isEmpty()) {
Page<TsPlan> emptyPage = new Page<>(pageNum, pageSize);
emptyPage.setTotal(0);
emptyPage.setRecords(Collections.emptyList());
return ApiResponse.success(emptyPage);
}
queryWrapper.in(TsPlan::getCreatedBy, userIdList);
}
// 时间条件优化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
if (StringUtils.isNotBlank(startTime)) {
try { try {
if (StringUtils.isNotBlank(startTime)) {
LocalDateTime start = LocalDateTime.parse(startTime, formatter); LocalDateTime start = LocalDateTime.parse(startTime, formatter);
queryWrapper.ge(TsPlan::getCreatedAt, start); queryWrapper.ge(TsPlan::getCreatedAt, start);
} catch (DateTimeException e) {
return ApiResponse.failure("开始时间格式错误,需为 yyyy-MM-dd HH:mm:ss");
}
} }
if (StringUtils.isNotBlank(endTime)) { if (StringUtils.isNotBlank(endTime)) {
try {
LocalDateTime end = LocalDateTime.parse(endTime, formatter); LocalDateTime end = LocalDateTime.parse(endTime, formatter);
queryWrapper.le(TsPlan::getCreatedAt, end); queryWrapper.le(TsPlan::getCreatedAt, end);
} catch (DateTimeException e) {
return ApiResponse.failure("结束时间格式错误,需为 yyyy-MM-dd HH:mm:ss");
} }
} catch (DateTimeParseException e) {
return ApiResponse.failure("时间格式错误、需为 yyyy-MM-dd HH:mm:ss");
} }
// 分页查询方案 // 执行分页查询
Page<TsPlan> page = tsPlanService.page(new Page<>(pageNum, pageSize), queryWrapper); Page<TsPlan> page = tsPlanService.page(new Page<>(pageNum, pageSize), queryWrapper);
// 优化用户名查询 // 关联查询用户信息并封装结果
if (!page.getRecords().isEmpty()) { if (!page.getRecords().isEmpty()) {
// 提取所有创建人 ID
List<String> creatorIds = page.getRecords().stream() List<String> creatorIds = page.getRecords().stream()
.map(TsPlan::getCreatedBy) .map(TsPlan::getCreatedBy)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.distinct() .distinct()
.toList(); .collect(Collectors.toList());
// 批量查询用户
if (!creatorIds.isEmpty()) { if (!creatorIds.isEmpty()) {
List<User> creators = userService.listByIds(creatorIds); List<User> creators = userService.listByIds(creatorIds);
// 构建 ID -> 昵称映射
Map<String, String> creatorNicknameMap = creators.stream() Map<String, String> creatorNicknameMap = creators.stream()
.collect(Collectors.toMap( .collect(Collectors.toMap(
User::getId, User::getId,
User::getNickname, user -> user.getNickname() != null ? user.getNickname() : "未知昵称",
(k1, k2) -> k1)); (k1, k2) -> k1
));
// 填充昵称
page.getRecords().forEach(plan -> { page.getRecords().forEach(plan -> {
String nickname = creatorNicknameMap.getOrDefault(plan.getCreatedBy(), "未知用户"); plan.setCreatedBy(creatorNicknameMap.getOrDefault(plan.getCreatedBy(), "未知用户"));
plan.setCreatedBy(nickname);
}); });
} }
} }
return ApiResponse.success(page); return ApiResponse.success(page);
} }
/**
* 抽取创建空分页结果的方法
*/
private Page<TsPlan> createEmptyPage(Integer pageNum, Integer pageSize) {
Page<TsPlan> emptyPage = new Page<>(pageNum, pageSize);
emptyPage.setTotal(0);
emptyPage.setRecords(Collections.emptyList());
return emptyPage;
}
} }

View File

@ -7,6 +7,7 @@ import com.yj.earth.business.service.TsSourceService;
import com.yj.earth.common.util.ApiResponse; import com.yj.earth.common.util.ApiResponse;
import com.yj.earth.common.util.JsonUtil; import com.yj.earth.common.util.JsonUtil;
import com.yj.earth.common.util.MapUtil; import com.yj.earth.common.util.MapUtil;
import com.yj.earth.dto.tsPlan.BatchUpdateShowStatusDto;
import com.yj.earth.dto.tsSource.AddTsModelSourceDto; import com.yj.earth.dto.tsSource.AddTsModelSourceDto;
import com.yj.earth.dto.tsSource.AddTsSourceDto; import com.yj.earth.dto.tsSource.AddTsSourceDto;
import com.yj.earth.dto.tsSource.UpdateTsSourceDto; import com.yj.earth.dto.tsSource.UpdateTsSourceDto;
@ -54,6 +55,19 @@ public class TsSourceController {
return ApiResponse.success(null); return ApiResponse.success(null);
} }
@Operation(summary = "批量修改资源显示状态")
@PostMapping("/batchUpdateShowStatus")
public ApiResponse batchUpdateShowStatus(@RequestBody List<BatchUpdateShowStatusDto> batchUpdateShowStatusDtoList) {
for (BatchUpdateShowStatusDto batchUpdateShowStatusDto : batchUpdateShowStatusDtoList) {
LambdaQueryWrapper<TsSource> queryWrapper = new LambdaQueryWrapper<>();
TsSource tsSource = new TsSource();
tsSource.setId(batchUpdateShowStatusDto.getId());
tsSource.setIsShow(batchUpdateShowStatusDto.getShow());
tsSourceService.updateById(tsSource);
}
return ApiResponse.success(null);
}
@Operation(summary = "查询某个方案下的态势资源") @Operation(summary = "查询某个方案下的态势资源")
@PostMapping("/query") @PostMapping("/query")
public ApiResponse query(@Parameter(description = "态势方案ID") @RequestParam(required = true) String id) { public ApiResponse query(@Parameter(description = "态势方案ID") @RequestParam(required = true) String id) {

View File

@ -51,6 +51,6 @@ public class Source implements Serializable {
private LocalDateTime createdAt; private LocalDateTime createdAt;
@Schema(description = "更新时间") @Schema(description = "更新时间")
@TableField(fill = FieldFill.UPDATE) @TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
} }

View File

@ -21,6 +21,8 @@ public interface SourceService extends IService<Source> {
String fetchCltDetail(String sourceId); String fetchCltDetail(String sourceId);
String fetchJsonDetail(String sourceId);
String fetchMbtilesDetail(String sourceId); String fetchMbtilesDetail(String sourceId);
String fetchPakDetail(String sourceId); String fetchPakDetail(String sourceId);

View File

@ -61,6 +61,7 @@ public class SourceServiceImpl extends ServiceImpl<SourceMapper, Source> impleme
detailFetchers.put("clt", this::fetchCltDetail); detailFetchers.put("clt", this::fetchCltDetail);
detailFetchers.put("mbtiles", this::fetchMbtilesDetail); detailFetchers.put("mbtiles", this::fetchMbtilesDetail);
detailFetchers.put("pak", this::fetchPakDetail); detailFetchers.put("pak", this::fetchPakDetail);
detailFetchers.put("json", this::fetchJsonDetail);
} }
/** /**
@ -156,6 +157,15 @@ public class SourceServiceImpl extends ServiceImpl<SourceMapper, Source> impleme
return HttpUtil.doGet(url); return HttpUtil.doGet(url);
} }
/**
* 获取 JSON 类型资源详情
*/
@Override
public String fetchJsonDetail(String sourceId) {
String url = buildSdkUrl("/data/clt/json/detail/" + sourceId);
return HttpUtil.doGet(url);
}
/** /**
* 获取 MBTiles 类型资源详情 * 获取 MBTiles 类型资源详情
*/ */
@ -199,12 +209,14 @@ public class SourceServiceImpl extends ServiceImpl<SourceMapper, Source> impleme
return resultMap; return resultMap;
} }
// 创建用于计算总数的查询条件始终排除directory类型 // 创建用于计算总数的查询条件
LambdaQueryWrapper<Source> countQueryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Source> countQueryWrapper = new LambdaQueryWrapper<>();
countQueryWrapper.ne(Source::getSourceType, "directory"); countQueryWrapper.ne(Source::getSourceType, "directory");
// 构建资源查询条件 // 构建资源查询条件
LambdaQueryWrapper<Source> sourceQueryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Source> sourceQueryWrapper = new LambdaQueryWrapper<>();
// 排序字段
sourceQueryWrapper.orderByDesc(Source::getUpdatedAt);
// 公共查询条件 // 公共查询条件
if (sourceType != null) { if (sourceType != null) {
@ -263,7 +275,6 @@ public class SourceServiceImpl extends ServiceImpl<SourceMapper, Source> impleme
// 不分页: 获取全部数据包含所有类型和总数排除directory // 不分页: 获取全部数据包含所有类型和总数排除directory
List<Source> sourceList = sourceService.list(sourceQueryWrapper); List<Source> sourceList = sourceService.list(sourceQueryWrapper);
long total = sourceService.count(countQueryWrapper); long total = sourceService.count(countQueryWrapper);
resultMap.put("list", sourceList); resultMap.put("list", sourceList);
resultMap.put("total", total); resultMap.put("total", total);
} }

View File

@ -121,7 +121,7 @@ public class SQLiteUtil {
try { try {
dataSource.close(); dataSource.close();
} catch (SQLException e) { } catch (SQLException e) {
System.err.println("关闭SQLite数据源失败: " + e.getMessage()); System.err.println("关闭SQLite: " + e.getMessage());
} }
} }
// 清理缓存(避免内存泄漏) // 清理缓存(避免内存泄漏)
@ -199,7 +199,7 @@ public class SQLiteUtil {
} catch (SQLException e) { } catch (SQLException e) {
// 异常时关闭当前数据源(避免后续请求使用异常连接) // 异常时关闭当前数据源(避免后续请求使用异常连接)
closeDataSource(dbFilePath); closeDataSource(dbFilePath);
throw new SQLException("执行查询失败SQL: " + sql + "", e); throw new SQLException("请检查文件是否存在");
} }
return resultList; return resultList;

View File

@ -0,0 +1,14 @@
package com.yj.earth.dto.tsPlan;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
public class BatchUpdateShowStatusDto {
@Schema(description = "资源ID列表")
private String id;
@Schema(description = "显示状态")
private Integer show;
}

View File

@ -54,6 +54,22 @@ public class Path {
@Data @Data
public static class CustomView { public static class CustomView {
private AttackArrow.CustomView.Orientation orientation;
private AttackArrow.CustomView.RelativePosition relativePosition;
@Data
public static class Orientation {
private double heading;
private double pitch;
private double roll;
}
@Data
public static class RelativePosition {
private double lng;
private double lat;
private double alt;
}
} }
@Data @Data

View File

@ -9,6 +9,8 @@ import java.util.List;
@SourceType("roam") @SourceType("roam")
public class Roam { public class Roam {
private String name; private String name;
private String defaultTime;
private String totalTime;
private List<Point> points; private List<Point> points;
private String repeat; private String repeat;