远程升级

This commit is contained in:
attiya 2025-11-17 16:00:56 +08:00
parent 2d072b01ff
commit b1c02a8e43
11 changed files with 244 additions and 30 deletions

View File

@ -0,0 +1,110 @@
package com.cdzy.common.utils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
public class RemoteFileUtils {
/**
* 获取远程文件并返回File对象
*/
public static File getRemoteFile(String fileUrl) throws IOException {
return getRemoteFile(fileUrl, null);
}
/**
* 获取远程文件并保存到指定路径
*/
public static File getRemoteFile(String fileUrl, String localPath) throws IOException {
validateUrl(fileUrl);
URL url = new URL(fileUrl);
File outputFile;
if (localPath != null && !localPath.trim().isEmpty()) {
outputFile = new File(localPath);
// 确保目录存在
outputFile.getParentFile().mkdirs();
} else {
// 使用临时文件
String extension = getFileExtension(fileUrl);
String prefix = "remote_file_" + System.currentTimeMillis() + "_";
String suffix = extension != null ? "." + extension : ".tmp";
Path tempPath = Files.createTempFile(prefix, suffix);
outputFile = tempPath.toFile();
// 程序退出时删除临时文件
outputFile.deleteOnExit();
}
// 下载文件
try (InputStream in = url.openStream();
FileOutputStream out = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[8192];
int bytesRead;
long totalBytes = 0;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
totalBytes += bytesRead;
}
}
return outputFile;
}
/**
* 获取文件扩展名
*/
private static String getFileExtension(String url) {
if (url == null || url.isEmpty()) {
return null;
}
int lastDotIndex = url.lastIndexOf('.');
int lastSlashIndex = Math.max(url.lastIndexOf('/'), url.lastIndexOf('\\'));
if (lastDotIndex > lastSlashIndex && lastDotIndex < url.length() - 1) {
String extension = url.substring(lastDotIndex + 1);
// 移除可能的查询参数
int paramIndex = extension.indexOf('?');
if (paramIndex != -1) {
extension = extension.substring(0, paramIndex);
}
return extension;
}
return null;
}
/**
* 验证URL格式
*/
private static void validateUrl(String url) {
if (url == null || url.trim().isEmpty()) {
throw new IllegalArgumentException("URL cannot be null or empty");
}
if (!url.startsWith("http://") && !url.startsWith("https://")) {
throw new IllegalArgumentException("URL must start with http:// or https://");
}
}
/**
* 获取文件信息
*/
public static void printFileInfo(File file) {
if (file != null && file.exists()) {
System.out.println("File name: " + file.getName());
System.out.println("File path: " + file.getAbsolutePath());
System.out.println("File size: " + file.length() + " bytes");
System.out.println("Is temporary: " + file.getName().startsWith("remote_file_"));
}
}
}

View File

@ -0,0 +1,39 @@
package com.cdzy.operations.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author attiya
* @since 2025-03-20
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer序列化值
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
// 设置键和值的序列化器
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}

View File

@ -2,6 +2,7 @@ package com.cdzy.operations.controller;
import com.cdzy.common.model.request.PageParam;
import com.cdzy.common.model.response.JsonResult;
import com.cdzy.operations.enums.CommandType;
import com.cdzy.operations.model.dto.EbikeEcuInOverview;
import com.cdzy.operations.model.entity.EbikeEcuInfo;
import com.cdzy.operations.model.vo.EbikeEcuInfoBatchVo;
@ -158,8 +159,19 @@ public class EbikeEcuInfoController {
*/
@GetMapping("executeCommand")
public JsonResult<?> executeCommand(String ecuSn,String bikeCode,@NotNull(message = "命令编码不能为空") String commandCode) {
boolean findBike = ebikeEcuInfoService.executeCommand(ecuSn,bikeCode,commandCode);
return JsonResult.success(findBike);
boolean result = ebikeEcuInfoService.executeCommand(ecuSn,bikeCode,commandCode,null);
return JsonResult.success(result);
}
/**
* 远程升级
*
* @return 执行结构
*/
@GetMapping("upgrade")
public JsonResult<?> upgrade(String ecuSn,String bikeCode,@NotNull(message = "远程文件地址不能为空") String url) {
boolean result = ebikeEcuInfoService.executeCommand(ecuSn,bikeCode, CommandType.UPGRADE,url);
return JsonResult.success(result);
}

View File

@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 退款审核 控制层
* @author yanglei
* @since 2025-11-14 16:58
*/

View File

@ -45,4 +45,9 @@ public interface CommandType {
* 关闭电源
*/
String POWER_OFF = "POWER_OFF";
/**
* 升级
*/
String UPGRADE = "UPGRADE";
}

View File

@ -73,4 +73,11 @@ public interface CommandService{
* @return 执行结果
*/
boolean powerOff(EbikeEcuInfo ebikeEcuInfo);
/**
* 远程升级
* @param ebikeEcuInfo 中控信息
* @return 执行结果
*/
boolean upgrade(EbikeEcuInfo ebikeEcuInfo,String url);
}

View File

@ -115,7 +115,7 @@ public interface EbikeEcuInfoService extends IService<EbikeEcuInfo> {
* @param commandCode 命令编码
* @return 执行结果
*/
boolean executeCommand(String ecuSn, String bikeCode, String commandCode);
boolean executeCommand(String ecuSn, String bikeCode, String commandCode,String url);
/**
* 根据车辆编号获取中控信息
@ -124,4 +124,11 @@ public interface EbikeEcuInfoService extends IService<EbikeEcuInfo> {
*/
EbikeEcuInfo getEcu(String bikeCode);
/**
* 远程升级
* @param ebikeEcuInfo 中控信息
* @return 结果
*/
boolean upgrade(EbikeEcuInfo ebikeEcuInfo,String url);
}

View File

@ -7,7 +7,6 @@ import com.cdzy.operations.enums.EcuBrand;
import com.cdzy.operations.model.entity.EbikeEcuInfo;
import com.cdzy.operations.service.CommandService;
import com.cdzy.operations.utils.CommandUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -41,8 +40,8 @@ public class CommandServiceImpl implements CommandService {
case EcuBrand.GUANG_HE_TONG:
String command;
try {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.FIND_BIKE);
} catch (JsonProcessingException e) {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.FIND_BIKE,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
return submitTaskAndWait(topic, taskId, command);
@ -58,8 +57,8 @@ public class CommandServiceImpl implements CommandService {
case EcuBrand.GUANG_HE_TONG:
String command;
try {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.GPS);
} catch (JsonProcessingException e) {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.GPS,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
return submitTaskAndWait(topic, taskId, command);
@ -75,8 +74,8 @@ public class CommandServiceImpl implements CommandService {
case EcuBrand.GUANG_HE_TONG:
String command;
try {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.OPEN_BATTERY_LOCK);
} catch (JsonProcessingException e) {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.OPEN_BATTERY_LOCK,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
return submitTaskAndWait(topic, taskId, command);
@ -92,8 +91,8 @@ public class CommandServiceImpl implements CommandService {
case EcuBrand.GUANG_HE_TONG:
String command;
try {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.CLOSE_BATTERY_LOCK);
} catch (JsonProcessingException e) {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.CLOSE_BATTERY_LOCK,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
return submitTaskAndWait(topic, taskId, command);
@ -109,8 +108,8 @@ public class CommandServiceImpl implements CommandService {
case EcuBrand.GUANG_HE_TONG:
String command;
try {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.LOCK);
} catch (JsonProcessingException e) {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.LOCK,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
return submitTaskAndWait(topic, taskId, command);
@ -126,8 +125,8 @@ public class CommandServiceImpl implements CommandService {
case EcuBrand.GUANG_HE_TONG:
String command;
try {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.UNLOCK);
} catch (JsonProcessingException e) {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.UNLOCK,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
return submitTaskAndWait(topic, taskId, command);
@ -148,8 +147,8 @@ public class CommandServiceImpl implements CommandService {
case EcuBrand.GUANG_HE_TONG:
String command;
try {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.OPEN_HELMET);
} catch (JsonProcessingException e) {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.OPEN_HELMET,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
return submitTaskAndWait(topic, taskId, command);
@ -165,8 +164,25 @@ public class CommandServiceImpl implements CommandService {
case EcuBrand.GUANG_HE_TONG:
String command;
try {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.POWER_OFF);
} catch (JsonProcessingException e) {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.POWER_OFF,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
return submitTaskAndWait(topic, taskId, command);
default:
throw new EbikeException("该品牌中控暂未接入");
}
}
@Override
public boolean upgrade(EbikeEcuInfo ebikeEcuInfo,String url) {
String taskId = createTaskId();
switch (ebikeEcuInfo.getEcuBrand()) {
case EcuBrand.GUANG_HE_TONG:
String command;
try {
command = CommandUtil.guang_he_tong(ebikeEcuInfo.getEcuSn(), taskId, CommandType.UPGRADE,url);
} catch (Exception e) {
throw new RuntimeException(e);
}
return submitTaskAndWait(topic, taskId, command);
@ -192,7 +208,7 @@ public class CommandServiceImpl implements CommandService {
sendMessageToQueue(topic, taskData);
// 等待结果5秒超时
return future.orTimeout(5, TimeUnit.SECONDS).join();
return future.orTimeout(10, TimeUnit.SECONDS).join();
} catch (Exception e) {
pendingRequests.remove(taskId);

View File

@ -164,6 +164,11 @@ public class EbikeEcuInfoServiceImpl extends ServiceImpl<EbikeEcuInfoMapper, Ebi
return commandService.powerOff(ebikeEcuInfo);
}
@Override
public boolean upgrade(EbikeEcuInfo ebikeEcuInfo, String url) {
return commandService.upgrade(ebikeEcuInfo, url);
}
@Override
public boolean checkSnOrBikeCode(String ecuSn, String bikeCode) {
try {
@ -180,7 +185,7 @@ public class EbikeEcuInfoServiceImpl extends ServiceImpl<EbikeEcuInfoMapper, Ebi
}
@Override
public boolean executeCommand(String ecuSn, String bikeCode, String commandCode) {
public boolean executeCommand(String ecuSn, String bikeCode, String commandCode, String url) {
check(ecuSn, bikeCode);
EbikeEcuInfo ebikeEcuInfo;
if (StringUtil.hasText(ecuSn)) {
@ -197,6 +202,7 @@ public class EbikeEcuInfoServiceImpl extends ServiceImpl<EbikeEcuInfoMapper, Ebi
case CommandType.UNLOCK -> unLock(ebikeEcuInfo);
case CommandType.OPEN_HELMET -> openHelmet(ebikeEcuInfo);
case CommandType.POWER_OFF -> powerOff(ebikeEcuInfo);
case CommandType.UPGRADE -> upgrade(ebikeEcuInfo, url);
default -> throw new EbikeException("该命令不存在/未接入");
};
}

View File

@ -1,11 +1,15 @@
package com.cdzy.operations.utils;
import cn.hutool.core.io.FileUtil;
import com.cdzy.common.ex.EbikeException;
import com.cdzy.common.utils.RemoteFileUtils;
import com.cdzy.operations.enums.CommandType;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.File;
import java.io.IOException;
/**
* @author attiya
* @since 2025-10-30
@ -16,7 +20,7 @@ public class CommandUtil {
private static final String prefix = "ecu/cmd/cdzybms/";
public static String guang_he_tong(String ecuSn, String taskId, String code) throws JsonProcessingException {
public static String guang_he_tong(String ecuSn, String taskId, String code,String url) throws IOException {
String topic = prefix + ecuSn;
ObjectNode jsonNode = mapper.createObjectNode();
jsonNode.put("topic", topic);
@ -63,6 +67,13 @@ public class CommandUtil {
ObjectNode objectNode_100 = mapper.readValue(command_100, ObjectNode.class);
jsonNode.put("command", objectNode_100.toString());
break;
case CommandType.UPGRADE:
File tempFile = RemoteFileUtils.getRemoteFile(url);
long size = FileUtil.size(tempFile);
String command_35 = "{\"tid\":\""+taskId+"\",\"c\":35,\"param\":{\"len\":"+size+",\"url\":\""+url+"\"}}";
ObjectNode objectNode_35 = mapper.readValue(command_35, ObjectNode.class);
jsonNode.put("command", objectNode_35.toString());
break;
default:
throw new EbikeException("该命令暂未接入");
}

View File

@ -1,17 +1,17 @@
package com.cdzy.operations;
import com.cdzy.operations.utils.CommandUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest
class EbikeOperationsApplicationTests {
@Test
void contextLoads() throws JsonProcessingException {
System.out.println(CommandUtil.guang_he_tong("2370171956","2370171956","LOCK"));
void contextLoads() throws IOException {
System.out.println("http://47.109.141.125:9000/operations-objects/test/gps_bass.bin".getBytes().length);
}
}