推送
This commit is contained in:
		
							
								
								
									
										14
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								pom.xml
									
									
									
									
									
								
							| @ -194,6 +194,20 @@ | ||||
|             <artifactId>gdal</artifactId> | ||||
|             <version>3.11.4</version> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Excel --> | ||||
|         <dependency> | ||||
|             <groupId>com.alibaba</groupId> | ||||
|             <artifactId>easyexcel</artifactId> | ||||
|             <version>3.3.2</version> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- CSV --> | ||||
|         <dependency> | ||||
|             <groupId>org.apache.commons</groupId> | ||||
|             <artifactId>commons-csv</artifactId> | ||||
|             <version>1.9.0</version> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
|  | ||||
|     <build> | ||||
|  | ||||
							
								
								
									
										226
									
								
								src/main/java/com/yj/earth/FastFileEncryptor.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								src/main/java/com/yj/earth/FastFileEncryptor.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,226 @@ | ||||
| package com.yj.earth; | ||||
|  | ||||
| import javax.crypto.Cipher; | ||||
| import javax.crypto.SecretKey; | ||||
| import javax.crypto.SecretKeyFactory; | ||||
| import javax.crypto.spec.IvParameterSpec; | ||||
| import javax.crypto.spec.PBEKeySpec; | ||||
| import javax.crypto.spec.SecretKeySpec; | ||||
| import java.io.*; | ||||
| import java.security.spec.KeySpec; | ||||
| import java.util.Random; | ||||
|  | ||||
| /** | ||||
|  * 文件快速加密解密工具类(仅加密文件开头部分,兼顾保护与效率) | ||||
|  */ | ||||
| public class FastFileEncryptor { | ||||
|     // 加密算法参数 | ||||
|     private static final String ALGORITHM = "AES"; | ||||
|     private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding"; | ||||
|     private static final int KEY_SIZE = 256; // 需JCE无限制权限包 | ||||
|     private static final int IV_LENGTH = 16; // AES块大小 | ||||
|     private static final int ITERATION_COUNT = 65536; | ||||
|     private static final int SALT_LENGTH = 16; | ||||
|     // 只加密文件开头的1MB(可根据需求调整,建议512KB~2MB) | ||||
|     private static final int ENCRYPTED_LENGTH = 1024 * 1024; // 1MB | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 加密文件(仅加密开头部分) | ||||
|      * @param sourceFilePath 源文件路径 | ||||
|      * @param encryptedFilePath 加密后文件路径 | ||||
|      * @param password 加密密码 | ||||
|      * @throws Exception 加密过程异常 | ||||
|      */ | ||||
|     public static void encryptFile(String sourceFilePath, String encryptedFilePath, String password) throws Exception { | ||||
|         File sourceFile = new File(sourceFilePath); | ||||
|         File encryptedFile = new File(encryptedFilePath); | ||||
|  | ||||
|         // 验证源文件 | ||||
|         if (!sourceFile.exists() || !sourceFile.isFile()) { | ||||
|             throw new FileNotFoundException("源文件不存在或不是文件: " + sourceFilePath); | ||||
|         } | ||||
|         // 检查磁盘空间 | ||||
|         if (!checkDiskSpace(encryptedFilePath, sourceFile.length())) { | ||||
|             throw new IOException("目标路径磁盘空间不足,至少需要 " + sourceFile.length() + " 字节"); | ||||
|         } | ||||
|  | ||||
|         // 生成盐值和IV向量 | ||||
|         byte[] salt = generateRandomBytes(SALT_LENGTH); | ||||
|         byte[] iv = generateRandomBytes(IV_LENGTH); | ||||
|         SecretKey secretKey = generateSecretKey(password, salt); | ||||
|         Cipher cipher = Cipher.getInstance(TRANSFORMATION); | ||||
|         cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); | ||||
|  | ||||
|         try ( | ||||
|                 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile)); | ||||
|                 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(encryptedFile)) | ||||
|         ) { | ||||
|             // 写入元数据:盐值(16) + IV(16) + 加密长度(4) | ||||
|             bos.write(salt); | ||||
|             bos.write(iv); | ||||
|             bos.write(intToBytes(ENCRYPTED_LENGTH)); | ||||
|             bos.flush(); | ||||
|  | ||||
|             // 加密开头指定长度的字节 | ||||
|             byte[] encryptBuffer = new byte[ENCRYPTED_LENGTH]; | ||||
|             int actualRead = bis.read(encryptBuffer); // 实际读取的字节数(可能文件小于1MB) | ||||
|             if (actualRead > 0) { | ||||
|                 byte[] encryptedBytes = cipher.doFinal(encryptBuffer, 0, actualRead); | ||||
|                 bos.write(encryptedBytes); | ||||
|             } | ||||
|  | ||||
|             // 直接复制剩余未加密的字节 | ||||
|             byte[] copyBuffer = new byte[1024 * 1024]; // 1MB缓冲区加速复制 | ||||
|             int bytesRead; | ||||
|             while ((bytesRead = bis.read(copyBuffer)) != -1) { | ||||
|                 bos.write(copyBuffer, 0, bytesRead); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 解密文件(仅解密开头部分) | ||||
|      * @param encryptedFilePath 加密文件路径 | ||||
|      * @param decryptedFilePath 解密后文件路径 | ||||
|      * @param password 解密密码 | ||||
|      * @throws Exception 解密过程异常 | ||||
|      */ | ||||
|     public static void decryptFile(String encryptedFilePath, String decryptedFilePath, String password) throws Exception { | ||||
|         File encryptedFile = new File(encryptedFilePath); | ||||
|         File decryptedFile = new File(decryptedFilePath); | ||||
|  | ||||
|         // 验证加密文件 | ||||
|         if (!encryptedFile.exists() || !encryptedFile.isFile()) { | ||||
|             throw new FileNotFoundException("加密文件不存在或不是文件: " + encryptedFilePath); | ||||
|         } | ||||
|         // 检查磁盘空间(减去元数据字节数) | ||||
|         long requiredSize = encryptedFile.length() - SALT_LENGTH - IV_LENGTH - 4; | ||||
|         if (!checkDiskSpace(decryptedFilePath, requiredSize)) { | ||||
|             throw new IOException("目标路径磁盘空间不足,至少需要 " + requiredSize + " 字节"); | ||||
|         } | ||||
|  | ||||
|         try ( | ||||
|                 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(encryptedFile)); | ||||
|                 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(decryptedFile)) | ||||
|         ) { | ||||
|             // 读取元数据:盐值、IV、加密长度 | ||||
|             byte[] salt = new byte[SALT_LENGTH]; | ||||
|             byte[] iv = new byte[IV_LENGTH]; | ||||
|             byte[] lengthBytes = new byte[4]; | ||||
|             if (bis.read(salt) != SALT_LENGTH || bis.read(iv) != IV_LENGTH || bis.read(lengthBytes) != 4) { | ||||
|                 throw new IOException("加密文件格式错误,元数据不完整"); | ||||
|             } | ||||
|             int encryptedLength = bytesToInt(lengthBytes); | ||||
|  | ||||
|             // 生成密钥并解密开头部分 | ||||
|             SecretKey secretKey = generateSecretKey(password, salt); | ||||
|             Cipher cipher = Cipher.getInstance(TRANSFORMATION); | ||||
|             cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); | ||||
|  | ||||
|             // 读取加密的开头数据(预留填充空间) | ||||
|             byte[] encryptedBytes = new byte[encryptedLength + 16]; // 最多额外16字节填充 | ||||
|             int actualEncryptedRead = bis.read(encryptedBytes); | ||||
|             if (actualEncryptedRead <= 0) { | ||||
|                 throw new IOException("加密文件内容不完整"); | ||||
|             } | ||||
|             // 解密并写入(只取原始加密长度的字节,去除填充) | ||||
|             byte[] decryptedBytes = cipher.doFinal(encryptedBytes, 0, actualEncryptedRead); | ||||
|             bos.write(decryptedBytes, 0, Math.min(decryptedBytes.length, encryptedLength)); | ||||
|  | ||||
|             // 直接复制剩余未加密的字节 | ||||
|             byte[] copyBuffer = new byte[1024 * 1024]; | ||||
|             int bytesRead; | ||||
|             while ((bytesRead = bis.read(copyBuffer)) != -1) { | ||||
|                 bos.write(copyBuffer, 0, bytesRead); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 生成随机字节数组(盐值或IV) | ||||
|      */ | ||||
|     private static byte[] generateRandomBytes(int length) { | ||||
|         byte[] bytes = new byte[length]; | ||||
|         new Random().nextBytes(bytes); | ||||
|         return bytes; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 基于密码和盐值生成AES密钥 | ||||
|      */ | ||||
|     private static SecretKey generateSecretKey(String password, byte[] salt) throws Exception { | ||||
|         SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); | ||||
|         KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_SIZE); | ||||
|         return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), ALGORITHM); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 检查目标路径磁盘空间 | ||||
|      */ | ||||
|     private static boolean checkDiskSpace(String targetPath, long requiredSize) { | ||||
|         File file = new File(targetPath); | ||||
|         File parent = file.getParentFile(); | ||||
|         if (parent == null) { | ||||
|             parent = new File(System.getProperty("user.dir")); | ||||
|         } | ||||
|         return parent.getFreeSpace() >= requiredSize; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * int转4字节数组(大端序) | ||||
|      */ | ||||
|     private static byte[] intToBytes(int value) { | ||||
|         return new byte[]{ | ||||
|                 (byte) (value >> 24), | ||||
|                 (byte) (value >> 16), | ||||
|                 (byte) (value >> 8), | ||||
|                 (byte) value | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 4字节数组转int(大端序) | ||||
|      */ | ||||
|     private static int bytesToInt(byte[] bytes) { | ||||
|         return ((bytes[0] & 0xFF) << 24) | | ||||
|                 ((bytes[1] & 0xFF) << 16) | | ||||
|                 ((bytes[2] & 0xFF) << 8) | | ||||
|                 (bytes[3] & 0xFF); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // 测试示例 | ||||
|     public static void main(String[] args) { | ||||
|         try { | ||||
|             // 测试参数(替换为实际文件路径) | ||||
|             String sourceFile = "E:\\yjearth\\poi\\poi.db";       // 原始大文件 | ||||
|             String encryptedFile = "E:\\yjearth\\poi\\poi_enc.db"; // 加密后文件 | ||||
|             String decryptedFile = "E:\\yjearth\\poi\\poi_dec.db"; // 解密后文件 | ||||
|             String password = "MySecretPassword123!";              // 密码 | ||||
|  | ||||
|             // 加密(仅加密开头1MB,速度极快) | ||||
|             System.out.println("开始加密文件..."); | ||||
|             long encryptStart = System.currentTimeMillis(); | ||||
|             encryptFile(sourceFile, encryptedFile, password); | ||||
|             long encryptEnd = System.currentTimeMillis(); | ||||
|             System.out.println("加密完成!耗时:" + (encryptEnd - encryptStart) + "ms,加密文件:" + encryptedFile); | ||||
|  | ||||
|             // 解密(仅解密开头1MB,速度极快) | ||||
|             System.out.println("开始解密文件..."); | ||||
|             long decryptStart = System.currentTimeMillis(); | ||||
|             decryptFile(encryptedFile, decryptedFile, password); | ||||
|             long decryptEnd = System.currentTimeMillis(); | ||||
|             System.out.println("解密完成!耗时:" + (decryptEnd - decryptStart) + "ms,解密文件:" + decryptedFile); | ||||
|  | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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 { | ||||
|  | ||||
| } | ||||
| @ -28,9 +28,10 @@ public class ServerInitService { | ||||
|     private RoleService roleService; | ||||
|     @Resource | ||||
|     private RoleSourceService roleSourceService; | ||||
|  | ||||
|     public void init() { | ||||
|         // 查询数据库所有需要加载的资源 | ||||
|         List<Source> list =sourceService.list(new LambdaQueryWrapper<Source>() | ||||
|         List<Source> list = sourceService.list(new LambdaQueryWrapper<Source>() | ||||
|                 .eq(Source::getSourceType, "terrain") | ||||
|                 .or().eq(Source::getSourceType, "layer") | ||||
|                 .or().eq(Source::getSourceType, "tileset")); | ||||
| @ -48,18 +49,20 @@ public class ServerInitService { | ||||
|  | ||||
|     public void checkDefaultUserAndRole() { | ||||
|         // 查询角色表和用户表是否有数据 | ||||
|         if(roleService.count() == 0 && userService.count() == 0) { | ||||
|         if (roleService.count() == 0 && userService.count() == 0) { | ||||
|             log.info("初始化默认用户角色数据"); | ||||
|             // 新增一个管理员角色 | ||||
|             Role adminRole = new Role(); | ||||
|             adminRole.setRoleName("管理员"); | ||||
|             adminRole.setDescription("系统管理员"); | ||||
|             adminRole.setStatus(1); | ||||
|             adminRole.setIsSuper(1); | ||||
|             roleService.save(adminRole); | ||||
|             // 新增一个默认角色 | ||||
|             Role defaultRole = new Role(); | ||||
|             defaultRole.setRoleName("默认角色"); | ||||
|             defaultRole.setDescription("系统默认角色"); | ||||
|             defaultRole.setStatus(1); | ||||
|             defaultRole.setIsSuper(0); | ||||
|             roleService.save(defaultRole); | ||||
|             // 新增一个用户 | ||||
| @ -67,6 +70,7 @@ public class ServerInitService { | ||||
|             user.setUsername("admin"); | ||||
|             user.setPassword(BCrypt.hashpw("admin123", BCrypt.gensalt())); | ||||
|             user.setNickname("管理员"); | ||||
|             user.setStatus(1); | ||||
|             user.setRoleId(adminRole.getId()); | ||||
|             user.setPhone("13888888888"); | ||||
|             userService.save(user); | ||||
|  | ||||
| @ -34,7 +34,7 @@ public class CodeUtil { | ||||
|         } | ||||
|  | ||||
|         // 传入需要生成代码的表名 | ||||
|         Generation("pbf_info"); | ||||
|         Generation("device"); | ||||
|     } | ||||
|  | ||||
|     public static void Generation(String... tableName) { | ||||
|  | ||||
| @ -8,6 +8,7 @@ import org.json.JSONArray; | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| @ -15,34 +16,22 @@ import java.util.Map; | ||||
|  | ||||
| import static org.gdal.ogr.ogrConstants.*; | ||||
|  | ||||
| /** | ||||
|  * GDAL地理数据转JSON工具类 | ||||
|  */ | ||||
| import org.gdal.gdal.gdal; | ||||
| import org.gdal.ogr.*; | ||||
| import org.gdal.osr.SpatialReference; | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONObject; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * GDAL地理数据转JSON工具类 | ||||
|  */ | ||||
| public class GdalJsonConverter { | ||||
|  | ||||
|     static { | ||||
|         // 初始化GDAL | ||||
|         // 动态获取项目根目录下的lib文件夹路径 | ||||
|         String projectRoot = System.getProperty("user.dir"); | ||||
|  | ||||
|         // 拼接lib目录路径(项目根目录/lib) | ||||
|         String projLibPath = projectRoot + File.separator + "lib"; | ||||
|  | ||||
|         // 设置PROJ库路径(指向包含proj.db的lib目录) | ||||
|         gdal.SetConfigOption("PROJ_LIB", projLibPath); | ||||
|         gdal.AllRegister(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将地理数据文件转换为指定结构的JSON | ||||
|      * @param filePath 文件绝对路径 | ||||
|      * @return JSON字符串 | ||||
|      */ | ||||
|     public static String convertToJson(String filePath) { | ||||
|         return convertToJson(filePath, ""); | ||||
| @ -50,11 +39,8 @@ public class GdalJsonConverter { | ||||
|  | ||||
|     /** | ||||
|      * 将地理数据文件转换为指定结构的JSON | ||||
|      * @param filePath 文件绝对路径 | ||||
|      * @param targetCrs 目标坐标系(如:"EPSG:4326"),为空则不转换 | ||||
|      * @return JSON字符串 | ||||
|      */ | ||||
|     public static String convertToJson(String filePath, String targetCrs) { | ||||
|     private static String convertToJson(String filePath, String targetCrs) { | ||||
|         try { | ||||
|             // 打开数据源 | ||||
|             DataSource dataSource = ogr.Open(filePath, 0); | ||||
| @ -80,7 +66,7 @@ public class GdalJsonConverter { | ||||
|             result.put("list", listArray); | ||||
|             dataSource.delete(); | ||||
|  | ||||
|             return result.toString(2); // 缩进2个空格,美化输出 | ||||
|             return result.toString(2); | ||||
|  | ||||
|         } catch (Exception e) { | ||||
|             throw new RuntimeException("转换失败: " + e.getMessage(), e); | ||||
| @ -88,7 +74,7 @@ public class GdalJsonConverter { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 创建FeatureCollection对象 | ||||
|      * 创建 FeatureCollection 对象 | ||||
|      */ | ||||
|     private static JSONObject createFeatureCollection(Layer layer, String layerName, String targetCrs) throws JSONException { | ||||
|         JSONObject featureCollection = new JSONObject(); | ||||
| @ -108,10 +94,10 @@ public class GdalJsonConverter { | ||||
|         // 重置图层读取位置 | ||||
|         layer.ResetReading(); | ||||
|  | ||||
|         // 获取图层定义,用于字段信息 | ||||
|         // 获取图层定义、用于字段信息 | ||||
|         FeatureDefn layerDefn = layer.GetLayerDefn(); | ||||
|  | ||||
|         // 如果需要坐标转换,创建坐标转换对象 | ||||
|         // 如果需要坐标转换、创建坐标转换对象 | ||||
|         SpatialReference sourceSrs = layer.GetSpatialRef(); | ||||
|         SpatialReference targetSrs = null; | ||||
|         CoordinateTransformation coordTrans = null; | ||||
| @ -164,7 +150,7 @@ public class GdalJsonConverter { | ||||
|                     return proj4.trim(); | ||||
|                 } | ||||
|  | ||||
|                 // 如果PROJ4为空,尝试获取WKT | ||||
|                 // 如果PROJ4为空、尝试获取WKT | ||||
|                 String wkt = srs.ExportToWkt(); | ||||
|                 if (wkt != null && !wkt.trim().isEmpty()) { | ||||
|                     return wkt.trim(); | ||||
| @ -186,8 +172,7 @@ public class GdalJsonConverter { | ||||
|     /** | ||||
|      * 创建单个Feature对象 | ||||
|      */ | ||||
|     private static JSONObject createFeature(Feature feature, FeatureDefn layerDefn, | ||||
|                                             CoordinateTransformation coordTrans, boolean transformCoords) throws JSONException { | ||||
|     private static JSONObject createFeature(Feature feature, FeatureDefn layerDefn, CoordinateTransformation coordTrans, boolean transformCoords) throws JSONException { | ||||
|         JSONObject featureObj = new JSONObject(); | ||||
|         featureObj.put("type", "Feature"); | ||||
|  | ||||
| @ -220,7 +205,7 @@ public class GdalJsonConverter { | ||||
|                     geomToUse = transformedGeom; | ||||
|                 } catch (Exception e) { | ||||
|                     System.err.println("坐标转换失败: " + e.getMessage()); | ||||
|                     // 如果转换失败,使用原始几何 | ||||
|                     // 如果转换失败、使用原始几何 | ||||
|                     geomToUse = geometry; | ||||
|                 } | ||||
|             } | ||||
| @ -233,7 +218,7 @@ public class GdalJsonConverter { | ||||
|                 geomToUse.delete(); | ||||
|             } | ||||
|         } else { | ||||
|             // 如果没有几何信息,创建空的MultiPolygon | ||||
|             // 如果没有几何信息、创建空的MultiPolygon | ||||
|             featureObj.put("geometry", createEmptyMultiPolygon()); | ||||
|         } | ||||
|  | ||||
| @ -452,11 +437,9 @@ public class GdalJsonConverter { | ||||
|                     layerInfo.put("name", layer.GetName()); | ||||
|                     layerInfo.put("featureCount", layer.GetFeatureCount()); | ||||
|                     layerInfo.put("geometryType", ogr.GeometryTypeToName(layer.GetGeomType())); | ||||
|  | ||||
|                     // 添加坐标系信息 | ||||
|                     String crs = getLayerCrs(layer); | ||||
|                     layerInfo.put("crs", crs != null ? crs : "未知"); | ||||
|  | ||||
|                     layers.add(layerInfo); | ||||
|                 } | ||||
|                 info.put("layers", layers); | ||||
|  | ||||
| @ -171,11 +171,11 @@ public class SdkUtil { | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             log.error("sdk配置文件中未配置server.port"); | ||||
|             log.error("sdk 配置文件中未配置 server.port"); | ||||
|         } catch (IOException e) { | ||||
|             log.error("读取sdk配置文件失败", e); | ||||
|             log.error("读取 sdk 配置文件失败", e); | ||||
|         } catch (ClassCastException e) { | ||||
|             log.error("sdk配置文件中server.port格式错误、应为数字类型", e); | ||||
|             log.error("sdk 配置文件中 server.port 格式错误、应为数字类型", e); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
| @ -53,6 +53,7 @@ public class DatabaseManager { | ||||
|         classes.add(BusinessConfig.class); | ||||
|         classes.add(WebSource.class); | ||||
|         classes.add(PbfInfo.class); | ||||
|         classes.add(Device.class); | ||||
|         ENTITY_CLASSES = Collections.unmodifiableList(classes); | ||||
|     } | ||||
|  | ||||
| @ -192,10 +193,6 @@ public class DatabaseManager { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void createTablesForEntities(DatabaseType dbType, Class entityClass, Connection connection) throws SQLException { | ||||
|         createTableIfNotExists(connection, dbType, entityClass); | ||||
|     } | ||||
|  | ||||
|     private static void createTableIfNotExists(Connection connection, DatabaseType dbType, Class<?> entityClass) throws SQLException { | ||||
|         String tableName = getUnderlineName(entityClass.getSimpleName()); | ||||
|  | ||||
|  | ||||
							
								
								
									
										34
									
								
								src/main/java/com/yj/earth/design/Device.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/main/java/com/yj/earth/design/Device.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| package com.yj.earth.design; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| @Data | ||||
| public class Device { | ||||
|     @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 = "创建时间") | ||||
|     private LocalDateTime createdAt; | ||||
|     @Schema(description = "更新时间") | ||||
|     private LocalDateTime updatedAt; | ||||
| } | ||||
| @ -13,6 +13,8 @@ public class Role { | ||||
|     private String roleName; | ||||
|     @Schema(description = "角色描述") | ||||
|     private String description; | ||||
|     @Schema(description = "状态(0未启用、1启用)") | ||||
|     private Integer status; | ||||
|     @Schema(description = "是否超级管理员") | ||||
|     private Integer isSuper; | ||||
|     @Schema(description = "创建时间") | ||||
|  | ||||
| @ -6,7 +6,7 @@ import lombok.Data; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| @Data | ||||
| public class User{ | ||||
| public class User { | ||||
|     @Schema(description = "主键") | ||||
|     private String id; | ||||
|     @Schema(description = "用户名") | ||||
| @ -19,6 +19,8 @@ public class User{ | ||||
|     private String nickname; | ||||
|     @Schema(description = "手机号") | ||||
|     private String phone; | ||||
|     @Schema(description = "状态(0未启用、1启用)") | ||||
|     private Integer status; | ||||
|     @Schema(description = "所属角色") | ||||
|     private String roleId; | ||||
|     @Schema(description = "创建时间") | ||||
|  | ||||
							
								
								
									
										23
									
								
								src/main/java/com/yj/earth/dto/device/ImportDeviceDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/main/java/com/yj/earth/dto/device/ImportDeviceDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| package com.yj.earth.dto.device; | ||||
|  | ||||
|  | ||||
| import com.alibaba.excel.annotation.ExcelProperty; | ||||
| import lombok.Data; | ||||
|  | ||||
| @Data | ||||
| public class ImportDeviceDto { | ||||
|     @ExcelProperty(value = "名称", index = 0) | ||||
|     private String cameraName; | ||||
|     @ExcelProperty(value = "设备IP", index = 1) | ||||
|     private String ip; | ||||
|     @ExcelProperty(value = "设备端口", index = 2) | ||||
|     private Integer port; | ||||
|     @ExcelProperty(value = "用户名", index = 3) | ||||
|     private String username; | ||||
|     @ExcelProperty(value = "密码", index = 4) | ||||
|     private String password; | ||||
|     @ExcelProperty(value = "设备类型", index = 5) | ||||
|     private String type; | ||||
|     @ExcelProperty(value = "通道号", index = 6) | ||||
|     private Integer channel; | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/main/java/com/yj/earth/dto/device/UpdateDeviceDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/main/java/com/yj/earth/dto/device/UpdateDeviceDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| package com.yj.earth.dto.device; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| @Data | ||||
| public class UpdateDeviceDto { | ||||
|     @Schema(description = "设备ID") | ||||
|     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; | ||||
| } | ||||
| @ -5,8 +5,6 @@ import lombok.Data; | ||||
|  | ||||
| @Data | ||||
| public class UpdateSystemServiceDto { | ||||
|     @Schema(description = "服务地址") | ||||
|     private String serverHost; | ||||
|     @Schema(description = "服务端口") | ||||
|     private Integer serverPort; | ||||
| } | ||||
|  | ||||
| @ -8,13 +8,12 @@ import lombok.Data; | ||||
| public class UpdateUserDto { | ||||
|     @Schema(description = "主键") | ||||
|     private String id; | ||||
|  | ||||
|     @Schema(description = "头像") | ||||
|     private String avatar; | ||||
|  | ||||
|     @Schema(description = "昵称") | ||||
|     private String nickname; | ||||
|  | ||||
|     @Schema(description = "手机号") | ||||
|     private String phone; | ||||
|     @Schema(description = "用户状态") | ||||
|     private Integer status; | ||||
| } | ||||
|  | ||||
							
								
								
									
										12
									
								
								src/main/java/com/yj/earth/dto/user/UpdateUserStatusDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/main/java/com/yj/earth/dto/user/UpdateUserStatusDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| package com.yj.earth.dto.user; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| @Data | ||||
| public class UpdateUserStatusDto { | ||||
|     @Schema(description = "用户ID") | ||||
|     private String id; | ||||
|     @Schema(description = "用户状态") | ||||
|     private Integer status; | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/main/java/com/yj/earth/params/Gdb.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/main/java/com/yj/earth/params/Gdb.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| package com.yj.earth.params; | ||||
|  | ||||
| import com.yj.earth.annotation.SourceType; | ||||
| import lombok.Data; | ||||
|  | ||||
| @Data | ||||
| @SourceType("gdb") | ||||
| public class Gdb { | ||||
|     private String richText; | ||||
|     private String sourcePath; | ||||
| } | ||||
							
								
								
									
										22
									
								
								src/main/java/com/yj/earth/vo/AddDeviceDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/main/java/com/yj/earth/vo/AddDeviceDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| package com.yj.earth.vo; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| @Data | ||||
| public class AddDeviceDto { | ||||
|     @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; | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/main/java/com/yj/earth/vo/CsvField.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/main/java/com/yj/earth/vo/CsvField.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| package com.yj.earth.vo; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
|  | ||||
| @Data | ||||
| @AllArgsConstructor | ||||
| public class CsvField { | ||||
|     @Schema(description = "字段名称") | ||||
|     private String key; | ||||
|     @Schema(description = "字段描述") | ||||
|     private String label; | ||||
| } | ||||
| @ -1,5 +1,5 @@ | ||||
| server: | ||||
|   host: 192.168.110.25 | ||||
|   host: 192.168.110.137 | ||||
|   port: 8848 | ||||
|  | ||||
| sdk: | ||||
|  | ||||
							
								
								
									
										25
									
								
								src/main/resources/mapper/DeviceMapper.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/main/resources/mapper/DeviceMapper.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||||
| <mapper namespace="com.yj.earth.business.mapper.DeviceMapper"> | ||||
|  | ||||
|     <!-- 通用查询映射结果 --> | ||||
|     <resultMap id="BaseResultMap" type="com.yj.earth.business.domain.Device"> | ||||
|         <id column="id" property="id" /> | ||||
|         <result column="camera_name" property="cameraName" /> | ||||
|         <result column="type" property="type" /> | ||||
|         <result column="ip" property="ip" /> | ||||
|         <result column="port" property="port" /> | ||||
|         <result column="user_name" property="userName" /> | ||||
|         <result column="password" property="password" /> | ||||
|         <result column="channel" property="channel" /> | ||||
|         <result column="flv_url" property="flvUrl" /> | ||||
|         <result column="created_at" property="createdAt" /> | ||||
|         <result column="updated_at" property="updatedAt" /> | ||||
|     </resultMap> | ||||
|  | ||||
|     <!-- 通用查询结果列 --> | ||||
|     <sql id="Base_Column_List"> | ||||
|         id, camera_name, type, ip, port, user_name, password, channel, flv_url, created_at, updated_at | ||||
|     </sql> | ||||
|  | ||||
| </mapper> | ||||
		Reference in New Issue
	
	Block a user
	 ZZX9599
					ZZX9599