This commit is contained in:
2025-11-20 15:10:08 +08:00
parent 5a77756c33
commit ca13d078a3
12 changed files with 216 additions and 203 deletions

View File

@ -4,11 +4,12 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>路径规划系统</title> <title>路径规划系统</title>
<!-- 仅保留必要CDN -->
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script> integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.8/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios@1.6.8/dist/axios.min.js"></script>
<!-- Tailwind配置 --> <!-- Tailwind配置 -->
@ -38,25 +39,32 @@
.content-auto { .content-auto {
content-visibility: auto; content-visibility: auto;
} }
.map-height { .map-height {
height: 100vh; /* 调整为占满视口高度 */ height: 100vh; /* 调整为占满视口高度 */
} }
.sidebar-height { .sidebar-height {
height: 100vh; /* 调整为占满视口高度 */ height: 100vh; /* 调整为占满视口高度 */
} }
.scrollbar-hide { .scrollbar-hide {
-ms-overflow-style: none; -ms-overflow-style: none;
scrollbar-width: none; scrollbar-width: none;
} }
.scrollbar-hide::-webkit-scrollbar { .scrollbar-hide::-webkit-scrollbar {
display: none; display: none;
} }
.custom-marker .fa { .custom-marker .fa {
font-size: 14px; font-size: 14px;
} }
.input-error { .input-error {
@apply border-danger focus:ring-danger/50 focus:border-danger; @apply border-danger focus:ring-danger/50 focus:border-danger;
} }
.btn-disabled { .btn-disabled {
@apply bg-gray-300 text-gray-500 cursor-not-allowed hover:bg-gray-300; @apply bg-gray-300 text-gray-500 cursor-not-allowed hover:bg-gray-300;
} }
@ -100,7 +108,8 @@
<!-- 起点移除默认value、默认无数据 --> <!-- 起点移除默认value、默认无数据 -->
<div class="space-y-1"> <div class="space-y-1">
<label class="block text-sm font-medium text-gray-700">起点 <span class="text-danger">*</span></label> <label class="block text-sm font-medium text-gray-700">起点 <span
class="text-danger">*</span></label>
<div class="flex space-x-2"> <div class="flex space-x-2">
<div class="flex-1 space-y-0.5"> <div class="flex-1 space-y-0.5">
<input type="text" id="startLat" placeholder="纬度" <input type="text" id="startLat" placeholder="纬度"
@ -114,7 +123,8 @@
maxlength="10"> maxlength="10">
<span id="startLngError" class="text-danger text-xs hidden">请输入有效经度</span> <span id="startLngError" class="text-danger text-xs hidden">请输入有效经度</span>
</div> </div>
<button id="setStartBtn" class="p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200" <button id="setStartBtn"
class="p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200"
title="在地图上选择起点"> title="在地图上选择起点">
<i class="fa fa-map-marker text-danger"></i> <i class="fa fa-map-marker text-danger"></i>
</button> </button>
@ -123,7 +133,8 @@
<!-- 终点 --> <!-- 终点 -->
<div class="space-y-1"> <div class="space-y-1">
<label class="block text-sm font-medium text-gray-700">终点 <span class="text-danger">*</span></label> <label class="block text-sm font-medium text-gray-700">终点 <span
class="text-danger">*</span></label>
<div class="flex space-x-2"> <div class="flex space-x-2">
<div class="flex-1 space-y-0.5"> <div class="flex-1 space-y-0.5">
<input type="text" id="endLat" placeholder="纬度" <input type="text" id="endLat" placeholder="纬度"
@ -137,7 +148,8 @@
maxlength="10"> maxlength="10">
<span id="endLngError" class="text-danger text-xs hidden">请输入有效经度</span> <span id="endLngError" class="text-danger text-xs hidden">请输入有效经度</span>
</div> </div>
<button id="setEndBtn" class="p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200" <button id="setEndBtn"
class="p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200"
title="在地图上选择终点"> title="在地图上选择终点">
<i class="fa fa-flag text-success"></i> <i class="fa fa-flag text-success"></i>
</button> </button>
@ -160,7 +172,8 @@
<!-- 交通方式 --> <!-- 交通方式 -->
<div class="space-y-1"> <div class="space-y-1">
<label class="block text-sm font-medium text-gray-700">交通方式 <span class="text-danger">*</span></label> <label class="block text-sm font-medium text-gray-700">交通方式 <span
class="text-danger">*</span></label>
<select id="profile" <select id="profile"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all bg-white"> class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all bg-white">
<option value="car">驾车</option> <option value="car">驾车</option>
@ -241,14 +254,14 @@
<script> <script>
// 大整数转字符串处理(保留原逻辑) // 大整数转字符串处理(保留原逻辑)
axios.defaults.transformResponse = [ axios.defaults.transformResponse = [
function(data) { function (data) {
if (typeof data !== 'string') return data; if (typeof data !== 'string') return data;
const bigIntRegex = /(\s*"[^"]*"\s*:\s*)(\d{16,})(\s*)/g; const bigIntRegex = /(\s*"[^"]*"\s*:\s*)(\d{16,})(\s*)/g;
return data.replace(bigIntRegex, (match, keyPart, bigInt, endPart) => { return data.replace(bigIntRegex, (match, keyPart, bigInt, endPart) => {
return `${keyPart}"${bigInt}"${endPart}`; return `${keyPart}"${bigInt}"${endPart}`;
}); });
}, },
function(parsedData) { function (parsedData) {
try { try {
return JSON.parse(parsedData); return JSON.parse(parsedData);
} catch (e) { } catch (e) {
@ -265,7 +278,7 @@
let routeLine = null; let routeLine = null;
let waypointCount = 0; let waypointCount = 0;
const API_BASE_URL = "http://127.0.0.1:8848"; const API_BASE_URL = "http://127.0.0.1:8848";
const DEFAULT_CENTER = { lat: 30.6570, lng: 104.0650 }; // 成都默认坐标 const DEFAULT_CENTER = {lat: 30.6570, lng: 104.0650}; // 成都默认坐标
// 地图初始化 // 地图初始化
function initMap() { function initMap() {
@ -353,7 +366,7 @@
}).addTo(map); }).addTo(map);
startMarker.on('dragend', (e) => { startMarker.on('dragend', (e) => {
const { lat, lng } = e.target.getLatLng(); const {lat, lng} = e.target.getLatLng();
setStartPoint(lat, lng); setStartPoint(lat, lng);
}); });
} }
@ -383,7 +396,7 @@
}).addTo(map); }).addTo(map);
endMarker.on('dragend', (e) => { endMarker.on('dragend', (e) => {
const { lat, lng } = e.target.getLatLng(); const {lat, lng} = e.target.getLatLng();
setEndPoint(lat, lng); setEndPoint(lat, lng);
}); });
} }
@ -436,7 +449,7 @@
waypointDiv.querySelector('.set-waypoint-btn').addEventListener('click', () => { waypointDiv.querySelector('.set-waypoint-btn').addEventListener('click', () => {
const id = parseInt(waypointDiv.dataset.id); const id = parseInt(waypointDiv.dataset.id);
map.once('click', (e) => { map.once('click', (e) => {
const { lat, lng } = e.latlng; const {lat, lng} = e.latlng;
latInput.value = lat.toFixed(6); latInput.value = lat.toFixed(6);
lngInput.value = lng.toFixed(6); lngInput.value = lng.toFixed(6);
latInput.dispatchEvent(new Event('input')); latInput.dispatchEvent(new Event('input'));
@ -457,7 +470,7 @@
}).addTo(map); }).addTo(map);
waypointMarkers[id].on('dragend', (e) => { waypointMarkers[id].on('dragend', (e) => {
const { lat, lng } = e.target.getLatLng(); const {lat, lng} = e.target.getLatLng();
latInput.value = lat.toFixed(6); latInput.value = lat.toFixed(6);
lngInput.value = lng.toFixed(6); lngInput.value = lng.toFixed(6);
latInput.dispatchEvent(new Event('input')); latInput.dispatchEvent(new Event('input'));
@ -480,7 +493,7 @@
// 地图点击处理 // 地图点击处理
function handleMapClick(e) { function handleMapClick(e) {
const { lat, lng } = e.latlng; const {lat, lng} = e.latlng;
const startLat = document.getElementById('startLat').value; const startLat = document.getElementById('startLat').value;
const endLat = document.getElementById('endLat').value; const endLat = document.getElementById('endLat').value;
@ -632,7 +645,7 @@
const lat = parseFloat(item.querySelector('.waypoint-lat').value); const lat = parseFloat(item.querySelector('.waypoint-lat').value);
const lng = parseFloat(item.querySelector('.waypoint-lng').value); const lng = parseFloat(item.querySelector('.waypoint-lng').value);
if (!isNaN(lat) && !isNaN(lng) && validateCoord(lat, 'lat') && validateCoord(lng, 'lng')) { if (!isNaN(lat) && !isNaN(lng) && validateCoord(lat, 'lat') && validateCoord(lng, 'lng')) {
waypoints.push({ lat, lng }); waypoints.push({lat, lng});
} }
}); });
@ -644,8 +657,8 @@
try { try {
const response = await axios.post( const response = await axios.post(
`${API_BASE_URL}/graphhopper/route`, `${API_BASE_URL}/graphhopper/route`,
{ startLat, startLng, endLat, endLng, profile, waypoints }, {startLat, startLng, endLat, endLng, profile, waypoints},
{ headers: { 'Content-Type': 'application/json' } } {headers: {'Content-Type': 'application/json'}}
); );
if (response.data.code !== 200 || !response.data.data) { if (response.data.code !== 200 || !response.data.data) {
@ -671,16 +684,16 @@
if (routeLine) map.removeLayer(routeLine); if (routeLine) map.removeLayer(routeLine);
const latLngs = routeData.pathPoints.map(point => [point.lat, point.lng]); const latLngs = routeData.pathPoints.map(point => [point.lat, point.lng]);
const lineStyles = { const lineStyles = {
car: { color: '#165DFF', weight: 5, opacity: 0.8, dashArray: '' }, car: {color: '#165DFF', weight: 5, opacity: 0.8, dashArray: ''},
bike: { color: '#00B42A', weight: 4, opacity: 0.8, dashArray: '5,5' }, bike: {color: '#00B42A', weight: 4, opacity: 0.8, dashArray: '5,5'},
foot: { color: '#4b0c35', weight: 3, opacity: 0.8, dashArray: '2,2' } foot: {color: '#4b0c35', weight: 3, opacity: 0.8, dashArray: '2,2'}
}; };
routeLine = L.polyline(latLngs, lineStyles[document.getElementById('profile').value]) routeLine = L.polyline(latLngs, lineStyles[document.getElementById('profile').value])
.addTo(map) .addTo(map)
.bindPopup(`<div class="text-sm"><p>距离:${routeData.distanceKm.toFixed(2)} 公里</p><p>时间:${routeData.timeMinutes} 分钟</p></div>`); .bindPopup(`<div class="text-sm"><p>距离:${routeData.distanceKm.toFixed(2)} 公里</p><p>时间:${routeData.timeMinutes} 分钟</p></div>`);
map.fitBounds(routeLine.getBounds(), { padding: [50, 50], maxZoom: 14 }); map.fitBounds(routeLine.getBounds(), {padding: [50, 50], maxZoom: 14});
} }
// 事件绑定:移除起点按钮弹窗 // 事件绑定:移除起点按钮弹窗

View File

@ -150,10 +150,10 @@ public class GraphHopperController {
public ApiResponse calculateRoute(@RequestBody RouteRequest request) { public ApiResponse calculateRoute(@RequestBody RouteRequest request) {
// 区分未加载地图和加载中两种状态 // 区分未加载地图和加载中两种状态
if (isLoading.get()) { if (isLoading.get()) {
return ApiResponse.failure("地图正在加载中请稍后再试"); return ApiResponse.failure("地图正在加载中请稍后再试");
} }
if (!isLoaded.get() || currentHopper == null) { if (!isLoaded.get() || currentHopper == null) {
return ApiResponse.failure("地图未加载请先加载地图"); return ApiResponse.failure("地图未加载请先加载地图");
} }
try { try {
// 构建路径点列表 // 构建路径点列表

View File

@ -33,6 +33,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
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.ArrayList;
@ -200,29 +201,16 @@ public class IconLibraryController {
@Operation(summary = "添加图标文件") @Operation(summary = "添加图标文件")
@PostMapping("/addIconFile") @PostMapping("/addIconFile")
public ApiResponse addIconFile(@RequestParam("files") MultipartFile[] files, @RequestParam("iconTypeId") @Parameter(description = "图标类型ID") String typeId) throws IOException, SQLException, IllegalAccessException, InstantiationException { public ApiResponse addIconFile(@RequestParam("filePaths") List<String> filePaths, @RequestParam("iconTypeId") String typeId) throws Exception {
// 获取当前启用的图标库 // 获取当前启用的图标库路径
String iconPath = getIconLibrary(); String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
// 校验类型是否存在 // 循环处理每个绝对路径对应的文件
if (!isIconTypeExist(iconPath, typeId)) { for (String filePath : filePaths) {
return ApiResponse.failure("图标类型不存在:" + typeId); File file = new File(filePath);
}
// 循环处理每个文件
for (MultipartFile file : files) {
if (file.isEmpty()) {
continue; // 跳过空文件
}
// 解析文件名与后缀 // 解析文件名与后缀
String originalFileName = file.getOriginalFilename(); String originalFileName = file.getName();
if (originalFileName == null) {
continue;
}
String fileSuffix = FileUtil.extName(originalFileName); String fileSuffix = FileUtil.extName(originalFileName);
String fileNameWithoutSuffix = FileUtil.mainName(originalFileName); String fileNameWithoutSuffix = FileUtil.mainName(originalFileName);
@ -235,10 +223,10 @@ public class IconLibraryController {
params.add(typeId); params.add(typeId);
params.add(fileNameWithoutSuffix); params.add(fileNameWithoutSuffix);
params.add(fileSuffix); params.add(fileSuffix);
params.add(file.getBytes()); params.add(Files.readAllBytes(file.toPath()));
params.add(LocalDateTime.now().toString()); params.add(LocalDateTime.now().toString());
System.out.println(insertSql);
// 执行数据库插入
SQLiteUtil.executeUpdate(iconPath, insertSql, params); SQLiteUtil.executeUpdate(iconPath, insertSql, params);
} }
@ -391,7 +379,7 @@ public class IconLibraryController {
private String getIconLibrary() { private String getIconLibrary() {
LambdaQueryWrapper<IconLibrary> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<IconLibrary> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(IconLibrary::getIsEnable, 1); // 1=启用、0=未启用 queryWrapper.eq(IconLibrary::getIsEnable, 1);
IconLibrary library = iconLibraryService.getOne(queryWrapper); IconLibrary library = iconLibraryService.getOne(queryWrapper);
return library == null ? null : library.getPath(); return library == null ? null : library.getPath();
} }

View File

@ -1,21 +1,32 @@
package com.yj.earth.business.controller; package com.yj.earth.business.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.fasterxml.jackson.databind.ObjectMapper;
import com.yj.earth.business.domain.Matter; import com.yj.earth.business.domain.Matter;
import com.yj.earth.business.domain.Source;
import com.yj.earth.business.service.MatterService; import com.yj.earth.business.service.MatterService;
import com.yj.earth.business.service.SourceService;
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.dto.matter.AddMatterDto; import com.yj.earth.dto.matter.AddMatterDto;
import com.yj.earth.dto.matter.UpdateMatterDto; import com.yj.earth.dto.matter.UpdateMatterDto;
import com.yj.earth.params.Point;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional;
@Tag(name = "物资数据管理") @Tag(name = "物资数据管理")
@ -24,6 +35,8 @@ import java.util.List;
public class MatterController { public class MatterController {
@Resource @Resource
private MatterService matterService; private MatterService matterService;
@Resource
private SourceService sourceService;
@PostMapping("/add") @PostMapping("/add")
@Operation(summary = "添加物资") @Operation(summary = "添加物资")
@ -71,10 +84,122 @@ public class MatterController {
public ApiResponse list(@Parameter(description = "分页数量") @RequestParam(value = "pageNum") Integer pageNum, public ApiResponse list(@Parameter(description = "分页数量") @RequestParam(value = "pageNum") Integer pageNum,
@Parameter(description = "分页大小") @RequestParam(value = "pageSize") Integer pageSize, @Parameter(description = "分页大小") @RequestParam(value = "pageSize") Integer pageSize,
@Parameter(description = "物资名称") @RequestParam(value = "name", required = false) String name) { @Parameter(description = "物资名称") @RequestParam(value = "name", required = false) String name) {
LambdaQueryWrapper<Matter> queryWrapper = new LambdaQueryWrapper<>();
Map<String, Integer> goodsTotalMap = getGoodCountMap();
// 分页查询物资表的数据
LambdaQueryWrapper<Matter> matterWrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(name)) { if (StringUtils.isNotBlank(name)) {
queryWrapper.like(Matter::getName, name); matterWrapper.like(Matter::getName, name);
} }
return ApiResponse.success(matterService.page(new Page<>(pageNum, pageSize), queryWrapper)); Page<Matter> matterPage = matterService.page(new Page<>(pageNum, pageSize), matterWrapper);
// 用统计出的数量替换分页结果中的数量
if (!CollectionUtils.isEmpty(matterPage.getRecords())) {
matterPage.getRecords().forEach(matter -> {
String matterName = matter.getName();
Integer count = goodsTotalMap.get(matterName);
if (count != null) {
matter.setNum(count);
}
});
}
return ApiResponse.success(matterPage);
}
@Operation(summary = "物资统计")
@PostMapping("/statistics")
public ApiResponse statistics(@Parameter(description = "点ID列表") @RequestBody List<String> pointIdList) {
// 构建Source表的查询条件
LambdaQueryWrapper<Source> sourceWrapper = new LambdaQueryWrapper<>();
// 根据ID列表查询
sourceWrapper.in(Source::getId, pointIdList);
// 执行查询、获取所有符合条件的Source列表
List<Source> sourceList = sourceService.list(sourceWrapper);
// 初始化物资统计Map用于存储 "物资名称 -> 总数量" 的映射关系
Map<String, Integer> goodsTotalMap = new HashMap<>();
if (!CollectionUtils.isEmpty(sourceList)) {
for (Source source : sourceList) {
try {
Point point = JsonUtil.jsonToObject(source.getParams(), Point.class);
if (point == null) continue;
Optional.ofNullable(point.getAttribute())
.map(Point.Attribute::getGoods)
.map(Point.Attribute.Goods::getContent)
.ifPresent(contentList -> {
// 遍历物资列表中的每个物资项
for (Point.Attribute.Goods.GoodsContent goodsContent : contentList) {
if (goodsContent == null) continue;
// 处理物资名称
String goodsName = Optional.ofNullable(goodsContent.getName())
.orElse("未知物资");
// 处理物资数量
int count = Optional.ofNullable(goodsContent.getCnt())
.map(Integer::valueOf)
.orElse(0);
// 累加物资总数量
goodsTotalMap.merge(goodsName, count, Integer::sum);
}
});
} catch (Exception e) {
}
}
}
// 返回最终的物资统计Map
return ApiResponse.success(goodsTotalMap);
}
private Map<String, Integer> getGoodCountMap() {
// 构建Source表的查询条件
LambdaQueryWrapper<Source> sourceWrapper = new LambdaQueryWrapper<>();
sourceWrapper.eq(Source::getSourceType, "point");
// 执行查询、获取所有符合条件的Source列表
List<Source> sourceList = sourceService.list(sourceWrapper);
// 初始化物资统计Map用于存储 "物资名称 -> 总数量" 的映射关系
Map<String, Integer> goodsTotalMap = new HashMap<>();
if (!CollectionUtils.isEmpty(sourceList)) {
for (Source source : sourceList) {
try {
Point point = JsonUtil.jsonToObject(source.getParams(), Point.class);
if (point == null) continue;
Optional.ofNullable(point.getAttribute())
.map(Point.Attribute::getGoods)
.map(Point.Attribute.Goods::getContent)
.ifPresent(contentList -> {
// 遍历物资列表中的每个物资项
for (Point.Attribute.Goods.GoodsContent goodsContent : contentList) {
if (goodsContent == null) continue;
// 处理物资名称
String goodsName = Optional.ofNullable(goodsContent.getName())
.orElse("未知物资");
// 处理物资数量
int count = Optional.ofNullable(goodsContent.getCnt())
.map(Integer::valueOf)
.orElse(0);
// 累加物资总数量
goodsTotalMap.merge(goodsName, count, Integer::sum);
}
});
} catch (Exception e) {
}
}
}
// 返回最终的物资统计Map
return goodsTotalMap;
} }
} }

View File

@ -33,6 +33,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
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.ArrayList;
@ -200,29 +201,16 @@ public class MilitaryLibraryController {
@Operation(summary = "添加军标文件") @Operation(summary = "添加军标文件")
@PostMapping("/addMilitaryFile") @PostMapping("/addMilitaryFile")
public ApiResponse addMilitaryFile(@RequestParam("files") MultipartFile[] files, @RequestParam("militaryTypeId") @Parameter(description = "军标类型ID") String typeId) throws IOException, SQLException, IllegalAccessException, InstantiationException { public ApiResponse addMilitaryFile(@RequestParam("filePaths") List<String> filePaths,
// 获取当前启用的军标库 @RequestParam("militaryTypeId") @Parameter(description = "军标类型ID") String typeId) throws IOException, SQLException {
String militaryPath = getMilitaryLibrary(); String militaryPath = getMilitaryLibrary();
if (militaryPath == null) {
return ApiResponse.failure("请先创建或导入军标库");
}
// 校验类型是否存在 // 循环处理每个绝对路径对应的文件
if (!isMilitaryTypeExist(militaryPath, typeId)) { for (String filePath : filePaths) {
return ApiResponse.failure("军标类型不存在:" + typeId); File file = new File(filePath);
}
// 循环处理每个文件
for (MultipartFile file : files) {
if (file.isEmpty()) {
continue; // 跳过空文件
}
// 解析文件名与后缀 // 解析文件名与后缀
String originalFileName = file.getOriginalFilename(); String originalFileName = file.getName();
if (originalFileName == null) {
continue;
}
String fileSuffix = FileUtil.extName(originalFileName); String fileSuffix = FileUtil.extName(originalFileName);
String fileNameWithoutSuffix = FileUtil.mainName(originalFileName); String fileNameWithoutSuffix = FileUtil.mainName(originalFileName);
@ -235,7 +223,7 @@ public class MilitaryLibraryController {
params.add(typeId); params.add(typeId);
params.add(fileNameWithoutSuffix); params.add(fileNameWithoutSuffix);
params.add(fileSuffix); params.add(fileSuffix);
params.add(file.getBytes()); params.add(Files.readAllBytes(file.toPath()));
params.add(LocalDateTime.now().toString()); params.add(LocalDateTime.now().toString());
SQLiteUtil.executeUpdate(militaryPath, insertSql, params); SQLiteUtil.executeUpdate(militaryPath, insertSql, params);
@ -243,7 +231,6 @@ 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) {

View File

@ -16,6 +16,7 @@ import com.yj.earth.dto.modelLibrary.AddModelTypeDto;
import com.yj.earth.dto.modelLibrary.CreateModelLibraryDto; import com.yj.earth.dto.modelLibrary.CreateModelLibraryDto;
import com.yj.earth.dto.modelLibrary.DragModelTypeDto; import com.yj.earth.dto.modelLibrary.DragModelTypeDto;
import com.yj.earth.dto.modelLibrary.UpdateModelTypeNameDto; import com.yj.earth.dto.modelLibrary.UpdateModelTypeNameDto;
import com.yj.earth.vo.IconDataVo;
import com.yj.earth.vo.ModelDataVo; import com.yj.earth.vo.ModelDataVo;
import com.yj.earth.vo.ModelTypeVo; import com.yj.earth.vo.ModelTypeVo;
import com.yj.earth.vo.ModelVo; import com.yj.earth.vo.ModelVo;
@ -36,6 +37,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
@ -165,25 +167,24 @@ public class ModelLibraryController {
@Operation(summary = "添加模型文件") @Operation(summary = "添加模型文件")
@PostMapping("/addModelFile") @PostMapping("/addModelFile")
public ApiResponse addModelFile(@RequestParam("files") MultipartFile[] files, @Parameter(description = "模型类型ID") @RequestParam("modelTypeId") String modelTypeId) throws IOException, SQLException { public ApiResponse addModelFile(
@Parameter(description = "绝对文件路径列表") @RequestParam("filePaths") List<String> filePaths,
@Parameter(description = "模型类型ID") @RequestParam("modelTypeId") String modelTypeId) throws IOException, SQLException {
// 获取最新的模型库路径 // 获取最新的模型库路径
String modelPath = getModelLibrary(); String modelPath = getModelLibrary();
if (modelPath == null) { if (modelPath == null) {
return ApiResponse.failure("请先创建或导入模型库"); return ApiResponse.failure("请先创建或导入模型库");
} }
// 循环处理每个上传的文件
for (MultipartFile file : files) { // 简化校验:仅判断列表是否为空
// 跳过空文件 if (filePaths == null || filePaths.isEmpty()) {
if (file.isEmpty()) { return ApiResponse.failure("文件路径列表不能为空");
continue; }
}
// 获取文件信息 // 循环处理每个文件路径
String fileName = file.getOriginalFilename(); for (String filePath : filePaths) {
if (fileName == null) { File file = new File(filePath);
continue;
}
String fileSuffix = FileUtil.extName(fileName);
String fileNameWithoutSuffix = FileUtil.mainName(fileName);
// 构建插入SQL // 构建插入SQL
String sql = "INSERT INTO model " + String sql = "INSERT INTO model " +
@ -194,9 +195,9 @@ public class ModelLibraryController {
String modelId = UUID.fastUUID().toString(true); String modelId = UUID.fastUUID().toString(true);
params.add(modelId); params.add(modelId);
params.add(modelTypeId); params.add(modelTypeId);
params.add(fileNameWithoutSuffix); params.add(file.getName().split("\\.")[0]);
params.add(fileSuffix); params.add(file.getName().substring(file.getName().lastIndexOf(".") + 1));
params.add(file.getBytes()); params.add(Files.readAllBytes(file.toPath()));
params.add(LocalDateTime.now()); params.add(LocalDateTime.now());
// 执行插入操作 // 执行插入操作

View File

@ -34,7 +34,7 @@ public class PoiController {
Class.forName("org.sqlite.JDBC"); Class.forName("org.sqlite.JDBC");
String dbPath = System.getProperty("user.dir") + File.separator + "poi" + File.separator + "poi.db"; String dbPath = System.getProperty("user.dir") + File.separator + "poi" + File.separator + "poi.db";
connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath); connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath);
logger.info("POI数据库连接初始化成功路径:{}", dbPath); logger.info("POI数据库连接初始化成功路径:{}", dbPath);
} }
} }

View File

@ -50,6 +50,7 @@ public class TsSourceController {
public ApiResponse update(@RequestBody UpdateTsSourceDto updateTsSourceDto) { public ApiResponse update(@RequestBody UpdateTsSourceDto updateTsSourceDto) {
TsSource tsSource = new TsSource(); TsSource tsSource = new TsSource();
BeanUtils.copyProperties(updateTsSourceDto, tsSource); BeanUtils.copyProperties(updateTsSourceDto, tsSource);
tsSourceService.updateById(tsSource);
return ApiResponse.success(null); return ApiResponse.success(null);
} }

View File

@ -49,14 +49,16 @@ public class JsonUtil {
} }
} }
/**
* 将 JSON 字符串转换为指定类型的对象
*/
public static <T> T jsonToObject(String json, Class<T> clazz) { public static <T> T jsonToObject(String json, Class<T> clazz) {
if (json == null || json.trim().isEmpty()) { if (json == null || json.trim().isEmpty()) {
return null; return null;
} }
return objectMapper.convertValue(json, clazz); try {
return objectMapper.readValue(json, clazz);
} catch (Exception e) {
return null;
}
} }
/** /**

View File

@ -90,7 +90,7 @@ public class SQLiteUtil {
stmt.execute("PRAGMA busy_timeout=2000;"); // 忙等待超时2秒避免瞬时并发锁等待 stmt.execute("PRAGMA busy_timeout=2000;"); // 忙等待超时2秒避免瞬时并发锁等待
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
throw new RuntimeException("初始化SQLite数据源失败路径" + dbFilePath + "+ 原因是:" + e.getMessage()); throw new RuntimeException("请检查路径是否存在");
} }
return dataSource; return dataSource;

View File

@ -141,11 +141,11 @@ public class DatabaseManager {
public static String getSqliteDbFilePath() { public static String getSqliteDbFilePath() {
if (!isPathInitialized || FOLDER_NAME == null) { if (!isPathInitialized || FOLDER_NAME == null) {
throw new IllegalStateException("数据库路径尚未初始化请先调用 initializePath 方法或等待 @PostConstruct 完成"); throw new IllegalStateException("数据库路径尚未初始化请先调用 initializePath 方法或等待 @PostConstruct 完成");
} }
if (sqliteDbFilePath == null) { if (sqliteDbFilePath == null) {
// 如果没有预先创建则动态构建路径 // 如果没有预先创建则动态构建路径
Path appDir = Paths.get(FOLDER_NAME); Path appDir = Paths.get(FOLDER_NAME);
Path dbFile = appDir.resolve(DB_FILE_NAME); Path dbFile = appDir.resolve(DB_FILE_NAME);
sqliteDbFilePath = dbFile.toAbsolutePath().toString(); sqliteDbFilePath = dbFile.toAbsolutePath().toString();
@ -208,7 +208,7 @@ public class DatabaseManager {
// 直接使用配置的绝对路径 // 直接使用配置的绝对路径
if (FOLDER_NAME == null || FOLDER_NAME.trim().isEmpty()) { if (FOLDER_NAME == null || FOLDER_NAME.trim().isEmpty()) {
throw new IOException("server.path 配置为空无法创建SQLite数据库文件"); throw new IOException("server.path 配置为空无法创建SQLite数据库文件");
} }
Path appDir = Paths.get(FOLDER_NAME); Path appDir = Paths.get(FOLDER_NAME);
@ -484,10 +484,10 @@ public class DatabaseManager {
DatabaseManager.initializePath(serverPath); DatabaseManager.initializePath(serverPath);
log.info("已提前初始化数据库路径: {}", serverPath); log.info("已提前初始化数据库路径: {}", serverPath);
} else { } else {
log.warn("未找到 server.path 配置将等待 @PostConstruct 初始化"); log.warn("未找到 server.path 配置将等待 @PostConstruct 初始化");
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("提前初始化数据库路径失败将等待 @PostConstruct 初始化: {}", e.getMessage()); log.warn("提前初始化数据库路径失败将等待 @PostConstruct 初始化: {}", e.getMessage());
} }
} }
} }

View File

@ -1,104 +0,0 @@
package com.yj.earth.word;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTShd;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STShd;
import java.io.FileOutputStream;
import java.io.IOException;
public class GenerateDocx {
public static void main(String[] args) {
// 创建文档对象
XWPFDocument document = new XWPFDocument();
try {
// 添加标题段落
XWPFParagraph titlePara = document.createParagraph();
titlePara.setAlignment(ParagraphAlignment.CENTER);
XWPFRun titleRun = titlePara.createRun();
titleRun.setText("POI 生成 Docx 示例");
// 字体大小
titleRun.setFontSize(20);
// 加粗
titleRun.setBold(true);
// 字体
titleRun.setFontFamily("宋体");
// 添加正文段落
XWPFParagraph contentPara1 = document.createParagraph();
contentPara1.setAlignment(ParagraphAlignment.LEFT);
XWPFRun run1 = contentPara1.createRun();
run1.setText("这是使用 Apache POI 生成的 docx 文档。");
run1.setFontSize(12);
run1.setFontFamily("微软雅黑");
// 换行
XWPFParagraph contentPara2 = document.createParagraph();
XWPFRun run2 = contentPara2.createRun();
run2.setText("支持设置文本样式,如:");
run2.setFontSize(12);
// 文本样式示例(同一行不同样式)
XWPFRun run3 = contentPara2.createRun();
run3.setText(" 加粗 ");
run3.setBold(true);
XWPFRun run4 = contentPara2.createRun();
run4.setText(" 斜体 ");
run4.setItalic(true);
XWPFRun run5 = contentPara2.createRun();
run5.setText(" 下划线 ");
run5.setUnderline(UnderlinePatterns.SINGLE); // 单下划线
XWPFRun run6 = contentPara2.createRun();
run6.setText(" 红色 ");
run6.setColor("FF0000"); // 红色
// 添加表格
int rows = 3; // 3行
int cols = 3; // 3列
XWPFTable table = document.createTable(rows, cols);
table.setWidth("100%"); // 表格宽度
// 设置表头
XWPFTableRow headerRow = table.getRow(0);
headerRow.getCell(0).setText("ID");
headerRow.getCell(1).setText("名称");
headerRow.getCell(2).setText("描述");
// 表头背景色(浅灰色)
for (XWPFTableCell cell : headerRow.getTableCells()) {
CTShd cTShd = cell.getCTTc().addNewTcPr().addNewShd();
cTShd.setVal(STShd.CLEAR);
cTShd.setFill("D9D9D9"); // 浅灰色
}
// 设置表格内容
XWPFTableRow row1 = table.getRow(1);
row1.getCell(0).setText("1");
row1.getCell(1).setText("POI");
row1.getCell(2).setText("处理 Office 文档的 Java 库");
XWPFTableRow row2 = table.getRow(2);
row2.getCell(0).setText("2");
row2.getCell(1).setText("Docx");
row2.getCell(2).setText("Word 2007+ 格式");
// 保存文档到本地
FileOutputStream out = new FileOutputStream("poi-demo.docx");
document.write(out);
out.close();
System.out.println("docx 生成成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
document.close(); // 关闭文档,释放资源
} catch (IOException e) {
e.printStackTrace();
}
}
}
}