推送
This commit is contained in:
		| @ -0,0 +1,55 @@ | ||||
| package com.yj.earth.business.controller; | ||||
|  | ||||
| import com.yj.earth.common.util.ApiResponse; | ||||
| import com.yj.earth.vo.CsvField; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.Parameter; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| import org.apache.commons.csv.CSVFormat; | ||||
| import org.apache.commons.csv.CSVParser; | ||||
| import org.apache.commons.csv.CSVRecord; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.InputStreamReader; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| @Tag(name = "CSV文件解析") | ||||
| @RestController | ||||
| @RequestMapping("/csv") | ||||
| public class CsvController { | ||||
|     @GetMapping("/parseCsv") | ||||
|     @Operation(summary = "解析CSV文件") | ||||
|     public ApiResponse parseCsv(@Parameter(description = "文件路径") @RequestParam String filePath) { | ||||
|         Map<String, Object> response = new HashMap<>(); | ||||
|         List<CsvField> fieldList = new ArrayList<>(); | ||||
|  | ||||
|         try { | ||||
|             File file = new File(filePath); | ||||
|             try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), "GBK"); | ||||
|                  CSVParser parser = new CSVParser(reader, CSVFormat.DEFAULT)) { | ||||
|                 // 遍历所有行(CSVRecord即一行数据) | ||||
|                 for (CSVRecord record : parser) { | ||||
|                     // 确保每行至少有3列(A、B、C列) | ||||
|                     if (record.size() >= 3) { | ||||
|                         String label = record.get(1).trim(); | ||||
|                         String key = record.get(2).trim(); | ||||
|                         fieldList.add(new CsvField(key, label)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             return ApiResponse.success(fieldList); | ||||
|         } catch (Exception e) { | ||||
|             return ApiResponse.failure(e.getMessage()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,279 @@ | ||||
| package com.yj.earth.business.controller; | ||||
|  | ||||
| import com.alibaba.excel.EasyExcel; | ||||
| import com.alibaba.excel.util.StringUtils; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||
| import com.yj.earth.business.domain.Device; | ||||
| import com.yj.earth.business.service.DeviceService; | ||||
| import com.yj.earth.common.util.ApiResponse; | ||||
| import com.yj.earth.vo.AddDeviceDto; | ||||
| import com.yj.earth.dto.device.ImportDeviceDto; | ||||
| import com.yj.earth.dto.device.UpdateDeviceDto; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.Parameter; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import org.springframework.beans.BeanUtils; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| import org.springframework.util.CollectionUtils; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.net.URLEncoder; | ||||
| import java.util.*; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| @Tag(name = "设备信息管理") | ||||
| @RestController | ||||
| @RequestMapping("/device") | ||||
| public class DeviceController { | ||||
|     @Resource | ||||
|     private DeviceService deviceService; | ||||
|     public static final String TYPE_DAHUA = "大华"; | ||||
|     public static final String TYPE_HIKVISION = "海康"; | ||||
|     // FLV地址拼接规则、key=设备类型、value=地址模板 | ||||
|     public static final Map<String, String> FLV_URL_RULES = new HashMap<String, String>() {{ | ||||
|         put(TYPE_DAHUA, "http://{ip}:{port}/cam/realmonitor?channel={channel}&subtype=0&proto=flv"); | ||||
|         put(TYPE_HIKVISION, "http://{ip}:{port}/Streaming/Channels/{channel}/flv"); | ||||
|     }}; | ||||
|  | ||||
|     /** | ||||
|      * 生成FLV地址 | ||||
|      */ | ||||
|     public static String generateFlvUrl(String type, String ip, Integer port, Integer channel) { | ||||
|         return FLV_URL_RULES.get(type) | ||||
|                 .replace("{ip}", ip) | ||||
|                 .replace("{port}", port.toString()) | ||||
|                 .replace("{channel}", channel.toString()); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/add") | ||||
|     @Operation(summary = "新增设备信息") | ||||
|     public ApiResponse addDevice(@RequestBody AddDeviceDto addDeviceDto) { | ||||
|         Device device = new Device(); | ||||
|         BeanUtils.copyProperties(addDeviceDto, device); | ||||
|  | ||||
|         // 生成FLV地址 | ||||
|         String flvUrl = generateFlvUrl( | ||||
|                 addDeviceDto.getType(), | ||||
|                 addDeviceDto.getIp(), | ||||
|                 addDeviceDto.getPort(), | ||||
|                 addDeviceDto.getChannel() | ||||
|         ); | ||||
|         device.setFlvUrl(flvUrl); | ||||
|  | ||||
|         deviceService.save(device); | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/list") | ||||
|     @Operation(summary = "查询设备信息") | ||||
|     public ApiResponse listDevice(@Parameter(description = "分页数量") Integer pageNum, @Parameter(description = "分页大小") Integer pageSize,@Parameter(description = "设备名称") String cameraName) { | ||||
|         LambdaQueryWrapper<Device> queryWrapper = new LambdaQueryWrapper<>(); | ||||
|         if (StringUtils.isNotBlank(cameraName)) { | ||||
|             queryWrapper.like(Device::getCameraName, cameraName); | ||||
|         } | ||||
|         Page<Device> devicePage = deviceService.page(new Page<>(pageNum, pageSize), queryWrapper); | ||||
|         return ApiResponse.success(devicePage); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/delete") | ||||
|     @Operation(summary = "删除设备信息") | ||||
|     public ApiResponse deleteDevice(@Parameter(description = "设备ID") @RequestParam String id) { | ||||
|         deviceService.removeById(id); | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/getById") | ||||
|     @Operation(summary = "查询设备信息") | ||||
|     public ApiResponse getDevice(@Parameter(description = "设备ID") @RequestParam String id) { | ||||
|         return ApiResponse.success(deviceService.getById(id)); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/update") | ||||
|     @Operation(summary = "更新设备信息") | ||||
|     public ApiResponse updateDevice(@RequestBody UpdateDeviceDto updateDeviceDto) { | ||||
|         Device device = new Device(); | ||||
|         BeanUtils.copyProperties(updateDeviceDto, device); | ||||
|  | ||||
|         // 如果更新了影响FLV URL的字段、重新生成FLV URL | ||||
|         if (updateDeviceDto.getType() != null || updateDeviceDto.getIp() != null || | ||||
|                 updateDeviceDto.getPort() != null || updateDeviceDto.getChannel() != null) { | ||||
|             // 如果有任何一个字段为null、从数据库获取原始值 | ||||
|             Device original = deviceService.getById(updateDeviceDto.getId()); | ||||
|             String type = updateDeviceDto.getType() != null ? updateDeviceDto.getType() : original.getType(); | ||||
|             String ip = updateDeviceDto.getIp() != null ? updateDeviceDto.getIp() : original.getIp(); | ||||
|             Integer port = updateDeviceDto.getPort() != null ? updateDeviceDto.getPort() : original.getPort(); | ||||
|             Integer channel = updateDeviceDto.getChannel() != null ? updateDeviceDto.getChannel() : original.getChannel(); | ||||
|  | ||||
|             device.setFlvUrl(generateFlvUrl(type, ip, port, channel)); | ||||
|         } | ||||
|  | ||||
|         deviceService.updateById(device); | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/import") | ||||
|     @Operation(summary = "导入设备信息") | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public ApiResponse importDevices(@Parameter(description = "设备Excel文件路径") @RequestParam String filePath) { | ||||
|         // 验证文件路径不为空 | ||||
|         if (StringUtils.isBlank(filePath)) { | ||||
|             return ApiResponse.failure("文件路径不能为空"); | ||||
|         } | ||||
|  | ||||
|         // 验证文件是否存在 | ||||
|         File file = new File(filePath); | ||||
|         if (!file.exists() || !file.isFile()) { | ||||
|             return ApiResponse.failure("文件不存在或不是有效文件"); | ||||
|         } | ||||
|  | ||||
|         // 验证文件格式 | ||||
|         String fileName = file.getName(); | ||||
|         if (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls")) { | ||||
|             return ApiResponse.failure("请上传Excel格式文件(.xls或.xlsx)"); | ||||
|         } | ||||
|  | ||||
|         // 验证文件大小 | ||||
|         long fileSize = file.length(); | ||||
|         if (fileSize > 10 * 1024 * 1024) { | ||||
|             return ApiResponse.failure("文件大小不能超过10MB"); | ||||
|         } | ||||
|  | ||||
|         try (InputStream inputStream = new FileInputStream(file)) { | ||||
|             List<ImportDeviceDto> deviceDtoList = EasyExcel.read(inputStream) | ||||
|                     .head(ImportDeviceDto.class) | ||||
|                     .sheet() | ||||
|                     .doReadSync(); | ||||
|  | ||||
|             if (CollectionUtils.isEmpty(deviceDtoList)) { | ||||
|                 return ApiResponse.failure("导入数据为空"); | ||||
|             } | ||||
|  | ||||
|             List<String> errorMessages = validateImportData(deviceDtoList); | ||||
|             if (!errorMessages.isEmpty()) { | ||||
|                 return ApiResponse.failure("导入数据校验失败:" + String.join(";", errorMessages)); | ||||
|             } | ||||
|  | ||||
|             List<String> importIps = deviceDtoList.stream() | ||||
|                     .map(ImportDeviceDto::getIp) | ||||
|                     .collect(Collectors.toList()); | ||||
|  | ||||
|             LambdaQueryWrapper<Device> queryWrapper = new LambdaQueryWrapper<>(); | ||||
|             queryWrapper.in(Device::getIp, importIps); | ||||
|             List<Device> existDevices = deviceService.list(queryWrapper); | ||||
|  | ||||
|             Set<String> existIps = existDevices.stream() | ||||
|                     .map(Device::getIp) | ||||
|                     .collect(Collectors.toSet()); | ||||
|  | ||||
|             List<Device> newDevices = deviceDtoList.stream() | ||||
|                     .filter(dto -> !existIps.contains(dto.getIp())) | ||||
|                     .map(dto -> { | ||||
|                         Device device = new Device(); | ||||
|                         BeanUtils.copyProperties(dto, device); | ||||
|                         // 生成FLV地址 | ||||
|                         String flvUrl = generateFlvUrl( | ||||
|                                 dto.getType(), | ||||
|                                 dto.getIp(), | ||||
|                                 dto.getPort(), | ||||
|                                 dto.getChannel() | ||||
|                         ); | ||||
|                         device.setFlvUrl(flvUrl); | ||||
|                         return device; | ||||
|                     }) | ||||
|                     .collect(Collectors.toList()); | ||||
|  | ||||
|             int total = deviceDtoList.size(); | ||||
|             int skipped = total - newDevices.size(); | ||||
|             int saved = 0; | ||||
|  | ||||
|             if (!newDevices.isEmpty()) { | ||||
|                 boolean saveResult = deviceService.saveBatch(newDevices); | ||||
|                 saved = saveResult ? newDevices.size() : 0; | ||||
|             } | ||||
|  | ||||
|             Map<String, Object> resultMap = new HashMap<>(3); | ||||
|             resultMap.put("total", total); | ||||
|             resultMap.put("saved", saved); | ||||
|             resultMap.put("skipped", skipped); | ||||
|             return ApiResponse.success(resultMap); | ||||
|         } catch (IOException e) { | ||||
|             return ApiResponse.failure("文件读取失败:" + e.getMessage()); | ||||
|         } catch (Exception e) { | ||||
|             return ApiResponse.failure("导入异常:" + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 校验导入数据的合法性 | ||||
|      */ | ||||
|     private List<String> validateImportData(List<ImportDeviceDto> deviceList) { | ||||
|         List<String> errorMessages = new ArrayList<>(); | ||||
|  | ||||
|         for (int i = 0; i < deviceList.size(); i++) { | ||||
|             ImportDeviceDto dto = deviceList.get(i); | ||||
|             int rowNum = i + 2; | ||||
|  | ||||
|             // 校验必填字段 | ||||
|             if (StringUtils.isBlank(dto.getCameraName())) { | ||||
|                 errorMessages.add(String.format("第%d行:设备名称不能为空", rowNum)); | ||||
|             } | ||||
|             if (StringUtils.isBlank(dto.getIp())) { | ||||
|                 errorMessages.add(String.format("第%d行:设备IP不能为空", rowNum)); | ||||
|             } else if (!isValidIp(dto.getIp())) { | ||||
|                 errorMessages.add(String.format("第%d行:设备IP格式不正确", rowNum)); | ||||
|             } | ||||
|             if (dto.getPort() == null || dto.getPort() <= 0 || dto.getPort() > 65535) { | ||||
|                 errorMessages.add(String.format("第%d行:设备端口无效", rowNum)); | ||||
|             } | ||||
|             if (StringUtils.isBlank(dto.getUsername())) { | ||||
|                 errorMessages.add(String.format("第%d行:用户名不能为空", rowNum)); | ||||
|             } | ||||
|             if (StringUtils.isBlank(dto.getType())) { | ||||
|                 errorMessages.add(String.format("第%d行:设备类型不能为空", rowNum)); | ||||
|             } else if (!FLV_URL_RULES.containsKey(dto.getType())) { | ||||
|                 errorMessages.add(String.format("第%d行:不支持的设备类型", rowNum)); | ||||
|             } | ||||
|             if (dto.getChannel() == null || dto.getChannel() <= 0) { | ||||
|                 errorMessages.add(String.format("第%d行:设备通道无效", rowNum)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return errorMessages; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 简单校验IP地址格式 | ||||
|      */ | ||||
|     private boolean isValidIp(String ip) { | ||||
|         if (ip == null || ip.isEmpty()) { | ||||
|             return false; | ||||
|         } | ||||
|         String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\." | ||||
|                 + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." | ||||
|                 + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." | ||||
|                 + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$"; | ||||
|         return ip.matches(regex); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/import/template") | ||||
|     @Operation(summary = "下载导入模板") | ||||
|     public void downloadTemplate(HttpServletResponse response) throws IOException { | ||||
|         // 设置响应头 | ||||
|         response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); | ||||
|         response.setCharacterEncoding("utf-8"); | ||||
|         String fileName = URLEncoder.encode("设备信息导入模板", "UTF-8").replaceAll("\\+", "%20"); | ||||
|         response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); | ||||
|         // 写入模板表头(通过空数据列表触发表头生成) | ||||
|         EasyExcel.write(response.getOutputStream(), ImportDeviceDto.class) | ||||
|                 .sheet("设备信息") | ||||
|                 .doWrite(Collections.emptyList()); | ||||
|     } | ||||
| } | ||||
| @ -10,10 +10,7 @@ import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.Parameter; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import java.util.List; | ||||
| @ -78,7 +75,7 @@ public class PbfInfoController { | ||||
|     } | ||||
|  | ||||
|     @Operation(summary = "获取所有地图文件") | ||||
|     @PostMapping("/list") | ||||
|     @GetMapping("/list") | ||||
|     public ApiResponse list() { | ||||
|         LambdaQueryWrapper<PbfInfo> queryWrapper = new QueryWrapper<PbfInfo>().lambda(); | ||||
|         // 把启用的排在最前面 | ||||
|  | ||||
| @ -0,0 +1,88 @@ | ||||
| package com.yj.earth.business.controller; | ||||
|  | ||||
| import com.yj.earth.common.util.ApiResponse; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.Parameter; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| import lombok.Data; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| import java.sql.*; | ||||
| import java.io.File; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| @Tag(name = "全国POI管理") | ||||
| @RestController | ||||
| @RequestMapping("/poi") | ||||
| public class PoiController { | ||||
|     private static Connection connection; | ||||
|  | ||||
|     static { | ||||
|         try { | ||||
|             Class.forName("org.sqlite.JDBC"); | ||||
|             String dbPath = System.getProperty("user.dir") + File.separator + "poi" + File.separator + "poi.db"; | ||||
|             connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath); | ||||
|         } catch (Exception e) { | ||||
|             throw new RuntimeException("SQLite连接初始化失败", e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/data") | ||||
|     @Operation(summary = "查看数据") | ||||
|     public ApiResponse data(@Parameter(description = "分页页码") Integer pageNum, @Parameter(description = "分页大小") Integer pageSize, @Parameter(description = "名称搜索") String name) { | ||||
|         int offset = (pageNum - 1) * pageSize; | ||||
|         // 构建查询SQL | ||||
|         StringBuilder dataSql = new StringBuilder("SELECT id, name, address, lng, lat FROM tbl_pois WHERE 1=1"); | ||||
|         List<Object> params = new ArrayList<>(); | ||||
|         // 添加名称搜索条件 | ||||
|         if (name != null && !name.trim().isEmpty()) { | ||||
|             dataSql.append(" AND name LIKE ?"); | ||||
|             params.add("%" + name.trim() + "%"); | ||||
|         } | ||||
|         // 添加分页条件 | ||||
|         dataSql.append(" LIMIT ? OFFSET ?"); | ||||
|         try { | ||||
|             // 执行数据查询、获取List | ||||
|             List<Poi> poiList = new ArrayList<>(); | ||||
|             long dataStartTime = System.currentTimeMillis(); | ||||
|             try (PreparedStatement dataPs = connection.prepareStatement(dataSql.toString())) { | ||||
|                 // 设置参数(搜索条件 + 分页参数) | ||||
|                 int paramIndex = 1; | ||||
|                 for (Object param : params) { | ||||
|                     dataPs.setObject(paramIndex++, param); | ||||
|                 } | ||||
|                 dataPs.setInt(paramIndex++, pageSize); | ||||
|                 dataPs.setInt(paramIndex++, offset); | ||||
|                 // 处理结果集、填充List | ||||
|                 try (ResultSet dataRs = dataPs.executeQuery()) { | ||||
|                     while (dataRs.next()) { | ||||
|                         Poi poi = new Poi(); | ||||
|                         poi.setId(dataRs.getLong("id")); | ||||
|                         poi.setName(dataRs.getString("name")); | ||||
|                         poi.setAddress(dataRs.getString("address")); | ||||
|                         poi.setLng(dataRs.getString("lng")); | ||||
|                         poi.setLat(dataRs.getString("lat")); | ||||
|                         poiList.add(poi); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             return ApiResponse.success(poiList); | ||||
|         } catch (SQLException e) { | ||||
|             return ApiResponse.failure("POI数据查询失败:" + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     private static class Poi { | ||||
|         private Long id; | ||||
|         private String name; | ||||
|         private String address; | ||||
|         private String lng; | ||||
|         private String lat; | ||||
|     } | ||||
| } | ||||
| @ -1,21 +1,25 @@ | ||||
| package com.yj.earth.business.controller; | ||||
|  | ||||
| import com.yj.earth.common.util.ApiResponse; | ||||
| import com.yj.earth.datasource.DatabaseManager; | ||||
| import com.yj.earth.dto.system.UpdateSystemServiceDto; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| import org.yaml.snakeyaml.DumperOptions; | ||||
| import org.yaml.snakeyaml.Yaml; | ||||
|  | ||||
| import java.io.*; | ||||
| import java.net.URLEncoder; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Paths; | ||||
| import java.nio.file.StandardCopyOption; | ||||
| import java.sql.SQLException; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| @ -23,35 +27,36 @@ import java.util.Map; | ||||
| @RestController | ||||
| @RequestMapping("/systemService") | ||||
| public class SystemController { | ||||
|     @Value("${server.host}") | ||||
|     private String serverHost; | ||||
|  | ||||
|     @Value("${server.port}") | ||||
|     private Integer serverPort; | ||||
|  | ||||
|     // 配置文件路径(jar包所在目录的application.yml) | ||||
|     private static final String CONFIG_FILE_PATH = "application.yml"; | ||||
|  | ||||
|     // 重启脚本路径(app同级目录) | ||||
|     private static final String WINDOWS_REBOOT_SCRIPT = "../reboot.bat"; | ||||
|     private static final String LINUX_REBOOT_SCRIPT = "../reboot.sh"; | ||||
|  | ||||
|     @Operation(summary = "读取系统服务配置") | ||||
|     @Operation(summary = "读取系统服务端口配置") | ||||
|     @PostMapping("/info") | ||||
|     public ApiResponse readSystemServiceConfig() { | ||||
|         Map<String, Object> map = Map.of("serverHost", serverHost, "serverPort", serverPort); | ||||
|         // 只返回端口信息 | ||||
|         Map<String, Object> map = Map.of("serverPort", serverPort); | ||||
|         return ApiResponse.success(map); | ||||
|     } | ||||
|  | ||||
|     @Operation(summary = "修改系统服务配置并重启") | ||||
|     @PostMapping("/update") | ||||
|     public ApiResponse updateSystemService(@RequestBody UpdateSystemServiceDto updateSystemServiceDto) { | ||||
|     @Operation(summary = "修改系统服务端口配置") | ||||
|     @PostMapping("/updatePort") | ||||
|     public ApiResponse updateSystemPort(@RequestBody UpdateSystemServiceDto updateSystemServiceDto) { | ||||
|         try { | ||||
|             // 检查端口是否为空 | ||||
|             if (updateSystemServiceDto.getServerPort() == null) { | ||||
|                 return ApiResponse.failure("端口号不能为空"); | ||||
|             } | ||||
|  | ||||
|             // 读取并更新YAML配置 | ||||
|             Yaml yaml = new Yaml(); | ||||
|             File configFile = new File(CONFIG_FILE_PATH); | ||||
|             if (!configFile.exists()) { | ||||
|                 return ApiResponse.failure("配置文件不存在"); | ||||
|             } | ||||
|  | ||||
|             // 解析YAML内容 | ||||
|             Map<String, Object> configMap; | ||||
|             try (InputStream in = Files.newInputStream(Paths.get(CONFIG_FILE_PATH))) { | ||||
| @ -60,15 +65,12 @@ public class SystemController { | ||||
|                     configMap = new HashMap<>(); | ||||
|                 } | ||||
|             } | ||||
|             // 更新配置 | ||||
|  | ||||
|             // 更新端口配置 | ||||
|             Map<String, Object> serverMap = (Map<String, Object>) configMap.getOrDefault("server", new HashMap<>()); | ||||
|             if (updateSystemServiceDto.getServerHost() != null && !updateSystemServiceDto.getServerHost().isEmpty()) { | ||||
|                 serverMap.put("host", updateSystemServiceDto.getServerHost()); | ||||
|             } | ||||
|             if (updateSystemServiceDto.getServerPort() != null) { | ||||
|                 serverMap.put("port", updateSystemServiceDto.getServerPort()); | ||||
|             } | ||||
|             serverMap.put("port", updateSystemServiceDto.getServerPort()); | ||||
|             configMap.put("server", serverMap); | ||||
|  | ||||
|             // 保存配置 | ||||
|             DumperOptions options = new DumperOptions(); | ||||
|             options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); | ||||
| @ -77,44 +79,71 @@ public class SystemController { | ||||
|             try (FileWriter writer = new FileWriter(configFile, StandardCharsets.UTF_8)) { | ||||
|                 writeYaml.dump(configMap, writer); | ||||
|             } | ||||
|             // 执行重启脚本 | ||||
|             executeRebootScript(); | ||||
|  | ||||
|             return ApiResponse.success(null); | ||||
|  | ||||
|         } catch (IOException e) { | ||||
|             return ApiResponse.failure("更新配置失败:" + e.getMessage()); | ||||
|             return ApiResponse.failure("更新端口配置失败:" + e.getMessage()); | ||||
|         } catch (Exception e) { | ||||
|             return ApiResponse.failure("操作失败:" + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 执行重启脚本 | ||||
|      */ | ||||
|     private void executeRebootScript() throws IOException, InterruptedException { | ||||
|         String scriptPath; | ||||
|         ProcessBuilder processBuilder; | ||||
|  | ||||
|         // 判断操作系统类型、选择对应的脚本 | ||||
|         if (System.getProperty("os.name").toLowerCase().contains("win")) { | ||||
|             scriptPath = WINDOWS_REBOOT_SCRIPT; | ||||
|             // 检查Windows脚本是否存在 | ||||
|             if (!new File(scriptPath).exists()) { | ||||
|                 throw new FileNotFoundException("Windows重启脚本不存在: " + scriptPath); | ||||
|             } | ||||
|             processBuilder = new ProcessBuilder(scriptPath); | ||||
|         } else { | ||||
|             scriptPath = LINUX_REBOOT_SCRIPT; | ||||
|             // 检查Linux脚本是否存在 | ||||
|             if (!new File(scriptPath).exists()) { | ||||
|                 throw new FileNotFoundException("Linux重启脚本不存在: " + scriptPath); | ||||
|             } | ||||
|             // 给脚本添加执行权限 | ||||
|             new ProcessBuilder("chmod", "+x", scriptPath).start().waitFor(); | ||||
|             processBuilder = new ProcessBuilder(scriptPath); | ||||
|     @Operation(summary = "工程导出") | ||||
|     @GetMapping("/export") | ||||
|     public void exportProject(HttpServletResponse response) { | ||||
|         // 获取SQLite文件绝对路径 | ||||
|         String sqliteFilePath = DatabaseManager.getSqliteDbFilePath(); | ||||
|         // 校验路径与文件有效性 | ||||
|         if (sqliteFilePath == null || sqliteFilePath.isEmpty()) { | ||||
|             handleError(response, HttpStatus.BAD_REQUEST.value(), "SQLite数据库未初始化"); | ||||
|             return; | ||||
|         } | ||||
|         File sqliteFile = new File(sqliteFilePath); | ||||
|         if (!sqliteFile.exists()) { | ||||
|             handleError(response, HttpStatus.NOT_FOUND.value(), "SQLite文件不存在"); | ||||
|             return; | ||||
|         } | ||||
|         if (!sqliteFile.canRead()) { | ||||
|             handleError(response, HttpStatus.FORBIDDEN.value(), "无权限读取SQLite文件"); | ||||
|             return; | ||||
|         } | ||||
|         // 配置下载响应头(让浏览器触发下载而非打开) | ||||
|         response.setContentType("application/octet-stream"); | ||||
|         response.setContentLengthLong(sqliteFile.length()); | ||||
|         // 处理文件名编码 | ||||
|         String fileName = URLEncoder.encode("app.db", StandardCharsets.UTF_8); | ||||
|         response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); | ||||
|         response.setBufferSize(1024 * 8); // 设置响应缓冲区(提升传输效率) | ||||
|         // 读取文件流并写入响应 | ||||
|         try (InputStream inputStream = new BufferedInputStream(new FileInputStream(sqliteFile)); | ||||
|              OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) { | ||||
|             byte[] buffer = new byte[1024 * 8]; | ||||
|             int len; | ||||
|             // 循环读取文件内容并写入响应流 | ||||
|             while ((len = inputStream.read(buffer)) != -1) { | ||||
|                 outputStream.write(buffer, 0, len); | ||||
|             } | ||||
|             // 强制刷新缓冲区、确保所有数据发送到前端 | ||||
|             response.flushBuffer(); | ||||
|         } catch (IOException e) { | ||||
|             handleError(response, HttpStatus.INTERNAL_SERVER_ERROR.value(), "文件下载失败:" + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         // 执行脚本(在新进程中运行、不阻塞当前请求) | ||||
|         processBuilder.start(); | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 返回指定状态码与错误信息 | ||||
|      */ | ||||
|     private void handleError(HttpServletResponse response, int statusCode, String message) { | ||||
|         try { | ||||
|             response.setStatus(statusCode); | ||||
|             response.setContentType("text/plain; charset=utf-8"); | ||||
|             response.getWriter().write(message); | ||||
|             response.getWriter().flush(); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,7 @@ import cn.dev33.satoken.stp.SaTokenInfo; | ||||
| import cn.dev33.satoken.stp.StpUtil; | ||||
| import cn.hutool.crypto.digest.BCrypt; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.toolkit.StringUtils; | ||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||
| import com.yj.earth.annotation.CheckAuth; | ||||
| import com.yj.earth.annotation.EncryptResponse; | ||||
| @ -13,10 +14,7 @@ import com.yj.earth.business.domain.Role; | ||||
| import com.yj.earth.business.domain.User; | ||||
| import com.yj.earth.business.service.RoleService; | ||||
| import com.yj.earth.dto.relation.UserBindOrUnBindRoleDto; | ||||
| import com.yj.earth.dto.user.AddUserDto; | ||||
| import com.yj.earth.dto.user.UpdatePasswordDto; | ||||
| import com.yj.earth.dto.user.UpdateUserDto; | ||||
| import com.yj.earth.dto.user.UserLoginDto; | ||||
| import com.yj.earth.dto.user.*; | ||||
| import com.yj.earth.business.service.UserService; | ||||
| import com.yj.earth.common.util.ApiResponse; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| @ -38,7 +36,6 @@ public class UserController { | ||||
|     @Resource | ||||
|     private RoleService roleService; | ||||
|  | ||||
|     @CheckAuth | ||||
|     @Operation(summary = "新增用户") | ||||
|     @PostMapping("/add") | ||||
|     @RoleAccess(roleNames = "管理员") | ||||
| @ -58,16 +55,6 @@ public class UserController { | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
|  | ||||
|     @CheckAuth | ||||
|     @Operation(summary = "删除用户") | ||||
|     @PostMapping("/delete") | ||||
|     @RoleAccess(roleNames = "管理员") | ||||
|     public ApiResponse delete(@Parameter(description = "用户ID") String id) { | ||||
|         userService.removeById(id); | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
|  | ||||
|     @CheckAuth | ||||
|     @Operation(summary = "更新信息") | ||||
|     @PostMapping("/update") | ||||
|     public ApiResponse update(@RequestBody UpdateUserDto updateUserDto) { | ||||
| @ -77,7 +64,6 @@ public class UserController { | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
|  | ||||
|     @CheckAuth | ||||
|     @Operation(summary = "更新密码") | ||||
|     @PostMapping("/updatePassword") | ||||
|     public ApiResponse updatePassword(@RequestBody UpdatePasswordDto updatePasswordDto) { | ||||
| @ -93,19 +79,29 @@ public class UserController { | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
|  | ||||
|     @CheckAuth | ||||
|     @Operation(summary = "用户详情") | ||||
|     @GetMapping("/getById") | ||||
|     public ApiResponse get(@Parameter(description = "用户ID") String id) { | ||||
|         return ApiResponse.success(userService.getById(id)); | ||||
|     } | ||||
|  | ||||
|     @CheckAuth | ||||
|     @Operation(summary = "用户列表") | ||||
|     @GetMapping("/list") | ||||
|     @RoleAccess(roleNames = "管理员") | ||||
|     public ApiResponse list(@Parameter(description = "分页数量") Integer pageNum, @Parameter(description = "分页大小") Integer pageSize) { | ||||
|         Page<User> userPage = userService.page(new Page<>(pageNum, pageSize)); | ||||
|     public ApiResponse list(@Parameter(description = "分页数量") Integer pageNum, | ||||
|                             @Parameter(description = "分页大小") Integer pageSize, | ||||
|                             @Parameter(description = "搜索字段") String searchKey, | ||||
|                             @Parameter(description = "角色ID") String roleId) { | ||||
|         LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); | ||||
|         // 根据用户名或者昵称进行模糊搜索 | ||||
|         if (StringUtils.isNotBlank(searchKey)) { | ||||
|             wrapper.like(User::getUsername, searchKey).or().like(User::getNickname, searchKey); | ||||
|         } | ||||
|         // 根据角色ID等值搜索 | ||||
|         if (StringUtils.isNotBlank(roleId)) { | ||||
|             wrapper.eq(User::getRoleId, roleId); | ||||
|         } | ||||
|         Page<User> userPage = userService.page(new Page<>(pageNum, pageSize), wrapper); | ||||
|         return ApiResponse.success(userPage); | ||||
|     } | ||||
|  | ||||
| @ -144,4 +140,22 @@ public class UserController { | ||||
|     public ApiResponse getCurrentUserInfo() { | ||||
|         return ApiResponse.success(userService.getById(StpUtil.getLoginIdAsString())); | ||||
|     } | ||||
|  | ||||
|     @Operation(summary = "启用禁用用户数统计") | ||||
|     @GetMapping("/getUserStatusCount") | ||||
|     public ApiResponse getUserStatusCount() { | ||||
|         // 查询状态为1的用户数 | ||||
|         long useUserCount = userService.count(new LambdaQueryWrapper<User>().eq(User::getStatus, 1)); | ||||
|         // 查询状态为0的用户数 | ||||
|         long bindUserCount = userService.count(new LambdaQueryWrapper<User>().eq(User::getStatus, 0)); | ||||
|         return ApiResponse.success(Map.of("useUserCount", useUserCount, "bindUserCount", bindUserCount)); | ||||
|     } | ||||
|  | ||||
|     @Operation(summary = "删除用户") | ||||
|     @PostMapping("/deletes") | ||||
|     @RoleAccess(roleNames = "管理员") | ||||
|     public ApiResponse deletes(@RequestBody List<String> ids) { | ||||
|         userService.removeByIds(ids); | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -4,8 +4,10 @@ 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.RoleAccess; | ||||
| import com.yj.earth.business.domain.PbfInfo; | ||||
| import com.yj.earth.business.domain.WebSource; | ||||
| import com.yj.earth.business.service.ModelLibraryService; | ||||
| import com.yj.earth.business.service.PbfInfoService; | ||||
| import com.yj.earth.business.service.SourceService; | ||||
| import com.yj.earth.business.service.WebSourceService; | ||||
| import com.yj.earth.common.util.ApiResponse; | ||||
| @ -39,6 +41,8 @@ public class WebSourceController { | ||||
|     private ModelLibraryController modelLibraryController; | ||||
|     @Resource | ||||
|     private SourceService sourceService; | ||||
|     @Resource | ||||
|     private PbfInfoService pbfInfoService; | ||||
|     private static final List<String> SUPPORT_EXTENSIONS = Arrays.asList("clt", "mbtiles", "pak", "pbf", "model"); | ||||
|  | ||||
|     @RoleAccess(roleNames = "管理员") | ||||
| @ -102,6 +106,15 @@ public class WebSourceController { | ||||
|         if (webSource.getType().equals("model")) { | ||||
|             modelLibraryController.importModelLibrary(filePath); | ||||
|         } | ||||
|  | ||||
|         // 如果是PBF文件则调用地图服务 | ||||
|         if (webSource.getType().equals("pbf")) { | ||||
|             PbfInfo pbfInfo = new PbfInfo(); | ||||
|             pbfInfo.setName(FileUtil.getName(filePath)); | ||||
|             pbfInfo.setPath(filePath); | ||||
|             pbfInfo.setIsEnable(0); | ||||
|             pbfInfoService.save(pbfInfo); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|  | ||||
							
								
								
									
										45
									
								
								src/main/java/com/yj/earth/business/domain/Device.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/main/java/com/yj/earth/business/domain/Device.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| package com.yj.earth.business.domain; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.FieldFill; | ||||
| import com.baomidou.mybatisplus.annotation.IdType; | ||||
| import com.baomidou.mybatisplus.annotation.TableField; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import java.io.Serializable; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
| import lombok.Getter; | ||||
| import lombok.Setter; | ||||
| import lombok.experimental.Accessors; | ||||
|  | ||||
| @Data | ||||
| public class Device implements Serializable { | ||||
|     @TableId(value = "id", type = IdType.ASSIGN_UUID) | ||||
|     @Schema(description = "主键") | ||||
|     private String id; | ||||
|     @Schema(description = "设备名称") | ||||
|     private String cameraName; | ||||
|     @Schema(description = "设备类型") | ||||
|     private String type; | ||||
|     @Schema(description = "设备ip") | ||||
|     private String ip; | ||||
|     @Schema(description = "设备端口") | ||||
|     private Integer port; | ||||
|     @Schema(description = "设备用户名") | ||||
|     private String username; | ||||
|     @Schema(description = "设备密码") | ||||
|     private String password; | ||||
|     @Schema(description = "设备通道") | ||||
|     private Integer channel; | ||||
|     @Schema(description = "设备FLV地址") | ||||
|     private String flvUrl; | ||||
|     @Schema(description = "设备状态") | ||||
|     private Integer status; | ||||
|     @Schema(description = "创建时间") | ||||
|     @TableField(fill = FieldFill.INSERT) | ||||
|     private LocalDateTime createdAt; | ||||
|     @Schema(description = "更新时间") | ||||
|     @TableField(fill = FieldFill.UPDATE) | ||||
|     private LocalDateTime updatedAt; | ||||
| } | ||||
| @ -25,6 +25,9 @@ public class Role implements Serializable { | ||||
|     @Schema(description = "角色名称") | ||||
|     private String roleName; | ||||
|  | ||||
|     @Schema(description = "状态(0未启用、1启用)") | ||||
|     private Integer status; | ||||
|  | ||||
|     @Schema(description = "角色描述") | ||||
|     private String description; | ||||
|  | ||||
|  | ||||
| @ -41,6 +41,9 @@ public class User implements Serializable { | ||||
|     @Schema(description = "所属角色") | ||||
|     private String roleId; | ||||
|  | ||||
|     @Schema(description = "状态(0未启用、1启用)") | ||||
|     private Integer status; | ||||
|  | ||||
|     @Schema(description = "创建时间") | ||||
|     @TableField(fill = FieldFill.INSERT) | ||||
|     private LocalDateTime createdAt; | ||||
|  | ||||
							
								
								
									
										18
									
								
								src/main/java/com/yj/earth/business/mapper/DeviceMapper.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/main/java/com/yj/earth/business/mapper/DeviceMapper.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| package com.yj.earth.business.mapper; | ||||
|  | ||||
| import com.yj.earth.business.domain.Device; | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| import org.apache.ibatis.annotations.Mapper; | ||||
|  | ||||
| /** | ||||
|  * <p> | ||||
|  *  Mapper 接口 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author 周志雄 | ||||
|  * @since 2025-10-14 | ||||
|  */ | ||||
| @Mapper | ||||
| public interface DeviceMapper extends BaseMapper<Device> { | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,16 @@ | ||||
| package com.yj.earth.business.service; | ||||
|  | ||||
| import com.yj.earth.business.domain.Device; | ||||
| import com.baomidou.mybatisplus.extension.service.IService; | ||||
|  | ||||
| /** | ||||
|  * <p> | ||||
|  *  服务类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author 周志雄 | ||||
|  * @since 2025-10-14 | ||||
|  */ | ||||
| public interface DeviceService extends IService<Device> { | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package com.yj.earth.business.service.impl; | ||||
|  | ||||
| import com.yj.earth.business.domain.Device; | ||||
| import com.yj.earth.business.mapper.DeviceMapper; | ||||
| import com.yj.earth.business.service.DeviceService; | ||||
| import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| /** | ||||
|  * <p> | ||||
|  *  服务实现类 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author 周志雄 | ||||
|  * @since 2025-10-14 | ||||
|  */ | ||||
| @Service | ||||
| public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements DeviceService { | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 ZZX9599
					ZZX9599