This commit is contained in:
ZZX9599
2025-10-22 11:44:18 +08:00
parent 0b6de3de4b
commit 663235eb7e
33 changed files with 1099 additions and 129 deletions

14
pom.xml
View File

@ -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>

View 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();
}
}
}

View File

@ -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());
}
}
}

View File

@ -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());
}
}

View File

@ -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();
// 把启用的排在最前面

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
/**

View 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;
}

View File

@ -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;

View File

@ -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;

View 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> {
}

View File

@ -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> {
}

View File

@ -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 {
}

View File

@ -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);

View File

@ -34,7 +34,7 @@ public class CodeUtil {
}
// 传入需要生成代码的表名
Generation("pbf_info");
Generation("device");
}
public static void Generation(String... tableName) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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());

View 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;
}

View File

@ -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 = "创建时间")

View File

@ -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 = "创建时间")

View 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;
}

View 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;
}

View File

@ -5,8 +5,6 @@ import lombok.Data;
@Data
public class UpdateSystemServiceDto {
@Schema(description = "服务地址")
private String serverHost;
@Schema(description = "服务端口")
private Integer serverPort;
}

View File

@ -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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@ -1,5 +1,5 @@
server:
host: 192.168.110.25
host: 192.168.110.137
port: 8848
sdk:

View 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>