2025-08-27 | Lius | ![]() |
2025-08-27 | Lius | ![]() |
2025-08-27 | Lius | ![]() |
2025-08-27 | cuikaidong | ![]() |
2025-08-27 | Lius | ![]() |
lxzn-module-mdc/src/main/java/org/jeecg/modules/mdc/entity/Equipment.java
@@ -20,7 +20,7 @@ * @Version: V1.0 */ @Data @TableName("Equipment") @TableName("equipment") @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @ApiModel(value = "Equipment对象", description = "采集设备表") lxzn-module-mdc/src/main/java/org/jeecg/modules/mdc/entity/EquipmentLog.java
@@ -16,7 +16,7 @@ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("EquipmentLog") @TableName("equipmentLog") @ApiModel(value = "设备历史表") public class EquipmentLog implements Serializable { lxzn-module-mdc/src/main/java/org/jeecg/modules/mdc/job/TransferTableDataJob.java
@@ -55,7 +55,7 @@ try { List<Equipment> list = equipmentService.list(); Equipment info = new Equipment(); info.setSavetablename("EquipmentLog"); info.setSavetablename("equipmentLog"); list.add(info); String day = DateUtils.format(DateUtils.plusTime(DateUtils.getNow(), -30), DateUtils.STR_DATE_TIME_SMALL); for (Equipment equipment : list) { lxzn-module-mdc/src/main/java/org/jeecg/modules/mdc/mapper/EquipmentWorklineMapper.java
@@ -32,7 +32,7 @@ List<Map<String, Object>> getWorkLineList(Map<String, Object> param); @InterceptorIgnore(tenantLine = "1") @Select("SELECT COUNT(*) FROM SysObjects WHERE XType='U' AND name = '${tableName}'") @Select("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE () AND TABLE_NAME = '${tableName}'") Integer isTableExist(@Param("tableName") String tableName); @InterceptorIgnore(tenantLine = "1") lxzn-module-mdc/src/main/java/org/jeecg/modules/mdc/mapper/xml/EquipmentLogMapper.xml
@@ -3,11 +3,11 @@ <mapper namespace="org.jeecg.modules.mdc.mapper.EquipmentLogMapper"> <select id="getRow" resultType="org.jeecg.modules.mdc.entity.EquipmentLog"> SELECT * FROM EquipmentLog WHERE EquipmentID = #{ equipmentid } AND CollectTime <= #{ startTime } AND Oporation in ('0','1','2','3') ORDER BY CollectTime ASC LIMIT 1 SELECT * FROM equipmentLog WHERE EquipmentID = #{ equipmentid } AND CollectTime <= #{ startTime } AND Oporation in ('0','1','2','3') ORDER BY CollectTime ASC LIMIT 1 </select> <select id="selectEquipmentOporation" resultType="org.jeecg.modules.mdc.entity.EquipmentLog"> SELECT * FROM EquipmentLog WHERE EquipmentID = #{ equipmentId } ORDER BY CollectTime DESC LIMIT 1 SELECT * FROM equipmentLog WHERE EquipmentID = #{ equipmentId } ORDER BY CollectTime DESC LIMIT 1 </select> </mapper> lxzn-module-mdc/src/main/java/org/jeecg/modules/mdc/mapper/xml/MdcWorkshopInfoMapper.xml
@@ -17,7 +17,7 @@ mew.vh vh, me.id equId FROM EquipmentLog a equipmentLog a INNER JOIN ( SELECT EquipmentID, MAX ( CollectTime ) 'maxgdtime' FROM EquipmentLog GROUP BY EquipmentID ) b ON a.EquipmentID= b.EquipmentID INNER JOIN mdc_workshop_equipment mew ON mew.equipment_id = a.EquipmentID INNER JOIN mdc_equipment me ON me.equipment_id = a.EquipmentID lxzn-module-mdc/src/main/java/org/jeecg/modules/mdc/service/impl/MdcEquipmentRunningSectionServiceImpl.java
@@ -69,8 +69,8 @@ String tableName = ""; Boolean isTableExist = false; if (equip != null) { if (StringUtils.isNotBlank(equip.getDrivetype())) { tableName = equip.getDrivetype() + "_" + equip.getEquipmentid(); if (StringUtils.isNotBlank(equip.getSavetablename())) { tableName = equip.getSavetablename(); isTableExist = equipmentWorkLineService.isTableExist(tableName); } } lxzn-module-mdc/src/main/java/org/jeecg/modules/mdc/service/impl/MdcEquipmentServiceImpl.java
@@ -37,6 +37,8 @@ import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -367,8 +369,9 @@ Object object = mapData.get(englishName); String value = ""; if ("CollectTime".equals(englishName)) { Date date = object == null ? null : (Date) object; value = DateUtils.format(date, DateUtils.STR_DATE_TIME_SMALL); LocalDateTime date = object == null ? null : (LocalDateTime) object; DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); value = date.format(customFormatter); } else if ("ZUOLAN".equals(mdcEquipmentDetailedDto.getDriveType()) && "AI01".equals(englishName) && oporation == 3) { // ZUOLAN设备电流字段AI01 value = BigDecimal.valueOf(Math.random() * 15 + 0).setScale(1, RoundingMode.HALF_UP).toString(); lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/controller/EquipmentController.java
@@ -189,6 +189,8 @@ param.clear(); param.put("id", equipment.getEqptCode()); HttpClientUtil.doGet("http://localhost:3002/ScriptCompiler/DeleteDevicescript", param); // 给mdc设备表增加设备信息 mdcEquipmentService.deleteEquipmentByEquipmentId(equipment.getEqptCode()); } equipmentService.removeById(id); return Result.ok("删除成功!"); lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/controller/ServeDeployController.java
@@ -54,8 +54,8 @@ private IMqttDeployService mqttDeployService; @Autowired private MqttCustomerClient mqttCustomerClient; @Resource private RestTemplate restTemplate; @Autowired private FtpUtil ftpUtil; @Autowired private IEquipmentService equipmentService; @Value("${ftp.address}") @@ -138,10 +138,10 @@ topic[1] = "IOT\\" + serverDeploy.getServerCode() + "\\WriteMessage"; mqttCustomerClient.subscribe(topic); // 创建ftp文件夹目录 FtpUtil.createFolder("/log", serverDeploy.getServerCode()); FtpUtil.createFolder("/deploy", serverDeploy.getServerCode()); FtpUtil.createFolder("/deploy/" + serverDeploy.getServerCode(), "software"); FtpUtil.createFolder("/deploy/" + serverDeploy.getServerCode(), "deploy"); ftpUtil.createFolder("/log", serverDeploy.getServerCode()); ftpUtil.createFolder("/deploy", serverDeploy.getServerCode()); ftpUtil.createFolder("/deploy/" + serverDeploy.getServerCode(), "software"); ftpUtil.createFolder("/deploy/" + serverDeploy.getServerCode(), "deploy"); // 生成本地文件夹 boolean b = FileUtil.queryCatalogue(ftpAddress + serverDeploy.getServerCode()); if (b) { @@ -277,7 +277,7 @@ return Result.error("请上传.zip文件!"); } // 新建ftp文件夹 FtpUtil.createFolder("/deploy/" + serverDeploy.getServerCode() + "/software/", serverDeploy.getLatestCollectVersion()); ftpUtil.createFolder("/deploy/" + serverDeploy.getServerCode() + "/software/", serverDeploy.getLatestCollectVersion()); // 复制采集软件到ftp // 上传到ftp String newCollectAddress = serverDeploy.getNewCollectAddress(); @@ -286,7 +286,7 @@ String filename = newCollectAddress.substring(14, newCollectAddress.length());// 上传到FTP服务器上的文件名 try { FileInputStream fileInputStream = new FileInputStream(ftpAddress + newCollectAddress); FtpUtil.uploadFile(basePath, filePath, filename, fileInputStream); ftpUtil.uploadFile(basePath, filePath, filename, fileInputStream); } catch (FileNotFoundException e) { e.printStackTrace(); lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/entity/Equipment.java
@@ -20,7 +20,7 @@ * @Version: V1.0 */ @Data @TableName("Equipment") @TableName("equipment") @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @ApiModel(value = "Equipment对象", description = "采集设备表") lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/entity/EquipmentAlarm.java
@@ -1,8 +1,6 @@ package org.jeecg.modules.iot.mdc.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -14,7 +12,7 @@ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("EquipmentAlarm") @TableName("equipmentalarm") public class EquipmentAlarm implements Serializable { private static final long serialVersionUID = -4762333096168370779L; lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/entity/EquipmentLog.java
@@ -1,6 +1,5 @@ package org.jeecg.modules.iot.mdc.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -16,7 +15,7 @@ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("EquipmentLog") @TableName("equipmentLog") @ApiModel(value = "设备历史表") public class EquipmentLog implements Serializable { lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/mapper/EquipmentAlarmMapper.java
@@ -13,6 +13,6 @@ */ @Component("iotEquipmentAlarmMapper") public interface EquipmentAlarmMapper extends BaseMapper<EquipmentAlarm> { @Select("SELECT TOP 1 * FROM EquipmentAlarm WHERE EquipmentID = #{ equipmentId } ORDER BY CollectTime DESC") @Select("SELECT * FROM EquipmentAlarm WHERE EquipmentID = #{ equipmentId } ORDER BY CollectTime DESC LIMIT 1") EquipmentAlarm selectEquipmentAlarmByEquipmentId(@Param("equipmentId") String equipmentId); } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/mapper/EquipmentLogMapper.java
@@ -15,10 +15,8 @@ @Component("iotEquipmentLogMapper") public interface EquipmentLogMapper extends BaseMapper<EquipmentLog> { EquipmentLog selectEquipmentOporationMySql(@Param("equipmentId") String equipmentId); EquipmentLog getRow(@Param("equipmentid") String equipmentid, @Param("startTime") Date startTime); EquipmentLog selectEquipmentOporationSqlServer(@Param("equipmentId") String equipmentId); EquipmentLog selectEquipmentOporation(@Param("equipmentId") String equipmentId); List<EquipmentLog> getEquipmentStatusList(@Param("equipmentIdList") List<String> equipmentIdList); } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/mapper/xml/EquipmentLogMapper.xml
@@ -2,27 +2,12 @@ <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.jeecg.modules.iot.mdc.mapper.EquipmentLogMapper"> <select id="getRow" resultType="org.jeecg.modules.iot.mdc.entity.EquipmentLog"> SELECT top 1 * FROM EquipmentLog WHERE EquipmentID = #{ equipmentid } AND CollectTime <= #{ startTime } AND Oporation in ('0','1','2','3') ORDER BY CollectTime ASC <select id="selectEquipmentOporationSqlServer" resultType="org.jeecg.modules.iot.mdc.entity.EquipmentLog"> SELECT TOP 1 * FROM equipmentLog WHERE EquipmentID = #{ equipmentId } ORDER BY CollectTime DESC </select> <select id="selectEquipmentOporation" resultType="org.jeecg.modules.iot.mdc.entity.EquipmentLog"> SELECT TOP 1 * FROM EquipmentLog WHERE EquipmentID = #{ equipmentId } ORDER BY CollectTime DESC <select id="selectEquipmentOporationMySql" resultType="org.jeecg.modules.iot.mdc.entity.EquipmentLog"> SELECT * FROM equipmentLog WHERE EquipmentID = #{ equipmentId } ORDER BY CollectTime DESC LIMIT 1 </select> <!--查询设备最新一条数据--> <select id="getEquipmentStatusList" resultType="org.jeecg.modules.iot.mdc.entity.EquipmentLog"> SELECT t1.* FROM EquipmentLog t1 INNER JOIN ( SELECT MAX ( CollectTime ) AS CollectTime, EquipmentID FROM EquipmentLog GROUP BY EquipmentID ) t2 ON t1.CollectTime= t2.CollectTime AND t1.EquipmentID= t2.EquipmentID <if test="equipmentIdList != null and equipmentIdList.size() > 0"> AND t1.EquipmentID IN <foreach collection="equipmentIdList" index="index" item="id" open="(" separator="," close=")"> #{id} </foreach> </if> </select> </mapper> lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/service/IEquipmentLogService.java
@@ -12,22 +12,8 @@ */ public interface IEquipmentLogService extends IService<EquipmentLog> { /** * 查询某个设备某个时间点之后的数据 */ List<EquipmentLog> findEquipmentLogByEndTime(String equipmentId, Date maxDate); EquipmentLog selectEquipmentOporationSqlServer(String equipmentId); /** * 查询某个设备某个时间点之后的数据(报警数据) */ List<EquipmentLog> findEquipmentLogByErrorEndTime(String equipmentId, Date endTime); EquipmentLog selectEquipmentOporationMySql(String equipmentId); /** * 获取设备某个时间点之后的最新一条数据 */ EquipmentLog getRow(String equipmentid, Date startTime); EquipmentLog selectEquipmentOporation(String equipmentId); List<EquipmentLog> getEquipmentStatusList(List<String> equipmentIdList); } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/service/IEquipmentService.java
@@ -60,4 +60,11 @@ * @param equipmentId */ Equipment findEquipmentByEquipmentId(String equipmentId); /** * 根据设备id删除设备信息 * * @param equipmentId */ void deleteEquipmentByEquipmentId(String equipmentId); } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/service/impl/EquipmentLogServiceImpl.java
@@ -18,44 +18,14 @@ @Service("EquipmentLogServiceImpl") public class EquipmentLogServiceImpl extends ServiceImpl<EquipmentLogMapper, EquipmentLog> implements IEquipmentLogService { @Override public List<EquipmentLog> findEquipmentLogByEndTime(String equipmentId, Date maxDate) { LambdaQueryChainWrapper<EquipmentLog> lambdaQuery = this.lambdaQuery(); lambdaQuery.eq(EquipmentLog::getEquipmentId, equipmentId); if (maxDate != null) { lambdaQuery.ge(EquipmentLog::getCollectTime, maxDate); } lambdaQuery.ne(EquipmentLog::getOporation, 22); lambdaQuery.ne(EquipmentLog::getOporation, 23); lambdaQuery.orderByAsc(EquipmentLog::getCollectTime); return lambdaQuery.list(); public EquipmentLog selectEquipmentOporationMySql(String equipmentId) { return this.baseMapper.selectEquipmentOporationMySql(equipmentId); } @Override public List<EquipmentLog> findEquipmentLogByErrorEndTime(String equipmentId, Date endTime) { LambdaQueryChainWrapper<EquipmentLog> lambdaQuery = this.lambdaQuery(); lambdaQuery.eq(EquipmentLog::getEquipmentId, equipmentId); if (endTime != null) { lambdaQuery.ge(EquipmentLog::getCollectTime, endTime); } lambdaQuery.in(EquipmentLog::getOporation, Arrays.asList(0, 22, 23)); lambdaQuery.orderByAsc(EquipmentLog::getCollectTime); return lambdaQuery.list(); public EquipmentLog selectEquipmentOporationSqlServer(String equipmentId) { return this.baseMapper.selectEquipmentOporationSqlServer(equipmentId); } @Override public EquipmentLog getRow(String equipmentid, Date startTime) { return this.baseMapper.getRow(equipmentid, startTime); } @Override public EquipmentLog selectEquipmentOporation(String equipmentId) { return this.baseMapper.selectEquipmentOporation(equipmentId); } @Override public List<EquipmentLog> getEquipmentStatusList(List<String> equipmentIdList) { return this.baseMapper.getEquipmentStatusList(equipmentIdList); } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/service/impl/EquipmentServiceImpl.java
@@ -45,4 +45,9 @@ public Equipment findEquipmentByEquipmentId(String equipmentId) { return new LambdaQueryChainWrapper<>(baseMapper).eq(Equipment::getEquipmentid,equipmentId).one(); } @Override public void deleteEquipmentByEquipmentId(String equipmentId) { baseMapper.deleteById(equipmentId); } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/service/impl/EquipmentServiceImpl.java
@@ -200,6 +200,7 @@ .ge(Equipment::getCreateTime, deployDate) .list(); equipmentList.forEach(equipment -> { String code = equipment.getEqptCode(); // 将所有的 '-' 替换为 '_' equipment.setEqptCode(equipment.getEqptCode().replace("-", "_")); if (databaseType.equals("SqlServer")) { @@ -217,7 +218,7 @@ } // 给mdc设备表增加设备信息 org.jeecg.modules.iot.mdc.entity.Equipment mdcEquipment = new org.jeecg.modules.iot.mdc.entity.Equipment(); mdcEquipment.setEquipmentid(equipment.getEqptCode()); mdcEquipment.setEquipmentid(code); mdcEquipment.setEquipmentname(equipment.getEqptName()); mdcEquipment.setSavetablename(equipment.getControlSystem() + '_' + equipment.getEqptCode()); mdcEquipment.setDrivetype(equipment.getControlSystem()); lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/service/impl/RealParameterServiceImpl.java
@@ -253,6 +253,12 @@ @Override public Result<?> canonicalParameter(String id) { int list = new LambdaQueryChainWrapper<>(baseMapper) .eq(RealParameter::getParameterGroupId, id) .list().size(); if (list > 1) { return Result.error("参数为空才可导入标准参数"); } // 查询最新编号 Integer parameterCode = findRealParameterGroupId(id) - 1; List<Parameter> parameters = equipmentService.findParameterById(id); @@ -265,6 +271,7 @@ real.setParameterName(parameter.getName()); real.setParameterType(parameter.getDataType()); real.setAddress(parameter.getDefault1()); real.setParameterDescribe(parameter.getDescribe()); real.setReadWriteType("只读"); List<Parameter> parameterList = equipmentService.findDataTypeById(id); parameterList.forEach(p -> { @@ -273,13 +280,14 @@ real.setSystemDataType(p.getSystemDataType()); } }); real.setDataLength(1); // 填充参数 parameterCode = parameterCode + 1; real.setParameterGroupId(id); real.setParameterCode(parameterCode); realParameter.add(real); } // saveBatch(realParameter); this.saveBatch(realParameter); return Result.ok("导入成功"); } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/service/impl/ServerDeployServiceImpl.java
@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jeecg.common.api.vo.Result; import org.jeecg.modules.iot.entity.*; import org.jeecg.modules.iot.service.*; @@ -42,6 +44,10 @@ @Value("${ftp.address}") private String ftpAddress; @Value("${operatingSystem}") private String operatingSystem; @Autowired private FtpUtil ftpUtil; @Autowired private IInfluxdbDeployService influxdbDeployService; @Autowired @@ -58,6 +64,7 @@ @Autowired @Lazy private IEmptyParameterService emptyParameterService; private static final Log logger = LogFactory.getLog(ServerDeployServiceImpl.class); @Override public ServerDeploy findByServerCode(String serverCode) { @@ -104,7 +111,7 @@ @Transactional(rollbackFor = Exception.class) public Result<?> addDeployDocument(String id) { // 验证FTP连接 boolean isConnected = FtpUtil.testFtpConnection(); boolean isConnected = ftpUtil.testFtpConnection(); if (!isConnected) { return Result.error("FTP连接失败,请检查配置!"); } @@ -248,9 +255,9 @@ tagInfo.setAttribute("Name", r.getParameterName()); tagInfo.setAttribute("Address", r.getAddress()); tagInfo.setAttribute("DataType", r.getParameterType()); if (r.getReadWriteType().equals("只读")){ if (r.getReadWriteType().equals("只读")) { tagInfo.setAttribute("ReadOrWrite", "R"); }else{ } else { tagInfo.setAttribute("ReadOrWrite", "R/W"); } tagInfo.setAttribute("Describe", r.getParameterDescribe()); @@ -321,42 +328,65 @@ tf.setOutputProperty(OutputKeys.INDENT, "yes"); // 创建xml文件并写入内容 tf.transform(new DOMSource(document), new StreamResult(new File("/iot/" + serverDeploy.getServerCode() + "/deploy/" + formattedDate + "/CollectionConfiguration.lmx"))); // 创建写库表结构查询虚设备列表,增加设备表 equipmentService.createEmptyEquipmentTable(serverDeploy.getDeployIssueTime(),serverDeploy.getId()); // 查询虚设备参数,增加字段 emptyParameterService.createEmptyEmptyField(serverDeploy.getDeployIssueTime(),serverDeploy.getId()); } catch (Exception e) { e.printStackTrace(); return Result.error("配置文件生成失败"); } // 更新版本 serverDeploy.setLatestDeployVersion(formattedDate); serverDeploy.setDeployIssueTime(new Date()); baseMapper.updateById(serverDeploy); // 生成本地文件夹 // FileUtil.createDir("D:/iot/" + serverDeploy.getServerCode() + "/deploy/" + formattedDate); FileUtil.createDir(ftpAddress + serverDeploy.getServerCode() + "/deploy/" + formattedDate + "/script"); // 上传到ftp String basePath = "/deploy"; // FTP服务器基础目录 String filePath = serverDeploy.getServerCode() + "/deploy/" + formattedDate; // FTP服务器文件存放路径。例如分日期存放:/2015/01/01。文件的路径为basePath+filePath String filename = "CollectionConfiguration.lmx";// 上传到FTP服务器上的文件名 //创建一个输入流 try { FileInputStream fileInputStream = new FileInputStream(ftpAddress + serverDeploy.getServerCode() + "/deploy/" + formattedDate + "/CollectionConfiguration.lmx"); FtpUtil.uploadFile(basePath, filePath, filename, fileInputStream); } catch (FileNotFoundException e) { // 创建写库表结构查询虚设备列表,增加设备表 equipmentService.createEmptyEquipmentTable(serverDeploy.getDeployIssueTime(), serverDeploy.getId()); // 查询虚设备参数,增加字段 emptyParameterService.createEmptyEmptyField(serverDeploy.getDeployIssueTime(), serverDeploy.getId()); } catch (Exception e) { e.printStackTrace(); return Result.error("设备单表,参数生成失败"); } // 存储脚本,复制整个文件夹 FtpUtil.uploadFolder(new File(ftpAddress+serverDeploy.getServerCode()+"/script/"), "/deploy/"+serverDeploy.getServerCode()+"/deploy/"+formattedDate+"/script"); // 生成脚本 // 发送配置文件版信号 // 生成本地文件夹 FileUtil.createDir(ftpAddress + serverDeploy.getServerCode() + "/deploy/" + formattedDate); MqttParameter mqttParameter = new MqttParameter(); // 上传到ftp if (operatingSystem.equals("windows")) { String basePath = "/deploy"; // FTP服务器基础目录 String filePath = "/" + serverDeploy.getServerCode() + "/deploy/" + formattedDate; // FTP服务器文件存放路径。例如分日期存放:/2015/01/01。文件的路径为basePath+filePath String filename = "CollectionConfiguration.lmx";// 上传到FTP服务器上的文件名 //创建一个输入流 try { FileInputStream fileInputStream = new FileInputStream(ftpAddress + serverDeploy.getServerCode() + "/deploy/" + formattedDate + "/CollectionConfiguration.lmx"); ftpUtil.uploadFile(basePath, filePath, filename, fileInputStream); } catch (FileNotFoundException e) { e.printStackTrace(); } mqttParameter.setParameter4(basePath + filePath); logger.info("windows配置下发目录:" + "deploy/" + serverDeploy.getServerCode() + "/deploy/" + formattedDate); // 存储脚本,复制整个文件夹 ftpUtil.uploadFolder(new File(ftpAddress + serverDeploy.getServerCode() + "/script/"), "/deploy/" + serverDeploy.getServerCode() + "/deploy/" + formattedDate + "/script"); } else { String basePath = "/iot"; // FTP服务器基础目录 String filePath = "/deploy/" + serverDeploy.getServerCode() + "/deploy/" + formattedDate; // FTP服务器文件存放路径。例如分日期存放:/2015/01/01。文件的路径为basePath+filePath String filename = "CollectionConfiguration.lmx";// 上传到FTP服务器上的文件名 //创建一个输入流 try { FileInputStream fileInputStream = new FileInputStream(ftpAddress + serverDeploy.getServerCode() + "/deploy/" + formattedDate + "/CollectionConfiguration.lmx"); ftpUtil.uploadFileKylin(basePath, filePath, filename, fileInputStream); } catch (FileNotFoundException e) { e.printStackTrace(); } mqttParameter.setParameter4("deploy/" + serverDeploy.getServerCode() + "/deploy/" + formattedDate); logger.info("麒麟系统FTP目标目录:" + "deploy/" + serverDeploy.getServerCode() + "/deploy/" + formattedDate); // 存储脚本,复制整个文件夹 ftpUtil.uploadFolderKylin(new File(ftpAddress + serverDeploy.getServerCode() + "/script/"), "/deploy/" + serverDeploy.getServerCode() + "/deploy/" + formattedDate + "/script"); } // 发送配置文件版信号 mqttParameter.setId(serverDeploy.getServerCode()); mqttParameter.setType("version"); mqttParameter.setParameter2(formattedDate); mqttParameter.setParameter4(basePath +"/" +filePath); mqttCustomerClient.pushlish(2, false, serverDeploy.getServerCode(), mqttParameter); // 更新终端版本 serverDeploy.setLatestDeployVersion(formattedDate); serverDeploy.setDeployIssueTime(new Date()); baseMapper.updateById(serverDeploy); return Result.ok("配置文件生成成功"); } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/util/FtpUtil.java
@@ -1,41 +1,39 @@ package org.jeecg.modules.iot.util; /** * @Description: Ftp配置 * @Author: cuikaidong * @Date: 2024-12-10 * @Version: V1.0 */ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPReply; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.*; import java.net.SocketException; import java.nio.charset.StandardCharsets; import java.util.Objects; @Component public class FtpUtil { private final static Log logger = LogFactory.getLog(FtpUtil.class); /** * 本地字符编码 */ private static String LOCAL_CHARSET = "GBK"; private static final Log logger = LogFactory.getLog(FtpUtil.class); // FTP协议里面,规定文件名编码为iso-8859-1 private static String SERVER_CHARSET = "ISO-8859-1"; //ftp服务器IP地址 ,新火炬10.210.199.2,起落架10.0.221.200 private static String ftpHost = "10.0.221.200"; //ftp服务器端口 private static int ftpPort = 21; //ftp服务器用户名 private static String ftpUserName = "admin"; //ftp服务器密码 private static String ftpPassword = "lx@2024"; // 依赖注入配置,建议通过 Nacos 或配置中心管理 @Value("${ftp.LOCAL_CHARSET:UTF-8}") private String LOCAL_CHARSET; @Value("${ftp.SERVER_CHARSET:ISO-8859-1}") private String SERVER_CHARSET; @Value("${ftp.ftpHost}") private String ftpHost; @Value("${ftp.ftpPort:21}") private int ftpPort; @Value("${ftp.ftpUserName}") private String ftpUserName; @Value("${ftp.ftpPassword}") private String ftpPassword; @Value("${operatingSystem}") private String operatingSystem; /** @@ -43,7 +41,7 @@ * * @return true:连接成功;false:连接失败 */ public static boolean testFtpConnection() { public boolean testFtpConnection() { FTPClient ftpClient = new FTPClient(); try { // 连接FTP服务器 @@ -58,377 +56,593 @@ if (isConnected) { logger.info("FTP连接验证成功!"); } else { logger.error("FTP连接失败:用户名/密码错误或服务器不可达"); logger.error("FTP连接失败:用户名/密码错误或服务器不可达,响应码:" + replyCode); } return isConnected; } catch (SocketException e) { logger.error("FTP服务器IP地址错误或端口被占用:" + e.getMessage()); logger.error("FTP服务器连接失败(IP或端口错误):" + e.getMessage(), e); } catch (IOException e) { logger.error("FTP连接异常:" + e.getMessage()); logger.error("FTP连接异常:" + e.getMessage(), e); } finally { // 释放资源 if (ftpClient.isConnected()) { try { ftpClient.logout(); ftpClient.disconnect(); } catch (IOException e) { logger.warn("关闭FTP连接时发生异常:" + e.getMessage()); } } disconnectQuietly(ftpClient); } return false; } // ====================== 核心优化:连接管理重构 ====================== /** * 获取FTPClient对象 * 获取 FTPClient 连接(带重试、模式配置) * * @return * @return 可用 FTPClient,失败返回 null */ public static FTPClient getFTPClient() { FTPClient ftpClient = null; try { ftpClient = new FTPClient(); ftpClient.connect(ftpHost, ftpPort);// 连接FTP服务器 ftpClient.login(ftpUserName, ftpPassword);// 登陆FTP服务器 if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) { logger.info("未连接到FTP,用户名或密码错误。"); ftpClient.disconnect(); } else { logger.info("FTP连接成功。"); } } catch (SocketException e) { e.printStackTrace(); logger.info("FTP的IP地址可能错误,请正确配置。"); } catch (IOException e) { e.printStackTrace(); logger.info("FTP的端口错误,请正确配置。"); } return ftpClient; } /** * 从FTP服务器下载文件 * * @param ftpHost FTP IP地址 * @param ftpUserName FTP 用户名 * @param ftpPassword FTP用户名密码 * @param ftpPort FTP端口 * @param ftpPath FTP服务器中文件所在路径 格式: ftptest/aa * @param localPath 下载到本地的位置 格式:H:/download * @param fileName 文件名称 */ public static void downloadFtpFile(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort, String ftpPath, String localPath, String fileName) { FTPClient ftpClient = null; try { ftpClient = getFTPClient(); // 设置上传文件的类型为二进制类型 if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK). LOCAL_CHARSET = "UTF-8"; } ftpClient.setControlEncoding(LOCAL_CHARSET); ftpClient.enterLocalPassiveMode();// 设置被动模式 ftpClient.setFileType(FTP.BINARY_FILE_TYPE);// 设置传输的模式 // 上传文件 //对中文文件名进行转码,否则中文名称的文件下载失败 String fileNameTemp = new String(fileName.getBytes(LOCAL_CHARSET), SERVER_CHARSET); ftpClient.changeWorkingDirectory(ftpPath); InputStream retrieveFileStream = ftpClient.retrieveFileStream(fileNameTemp); // 第一种方式下载文件(推荐) /* File localFile = new File(localPath + File.separatorChar + fileName); OutputStream os = new FileOutputStream(localFile); ftpClient.retrieveFile(fileName, os); os.close();*/ // 第二种方式下载:将输入流转成字节,再生成文件,这种方式方便将字节数组直接返回给前台jsp页面 byte[] input2byte = input2byte(retrieveFileStream); byte2File(input2byte, localPath, fileName); if (null != retrieveFileStream) { retrieveFileStream.close(); } } catch (FileNotFoundException e) { logger.error("没有找到" + ftpPath + "文件"); e.printStackTrace(); } catch (SocketException e) { logger.error("连接FTP失败."); e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); logger.error("文件读取错误。"); e.printStackTrace(); } finally { if (ftpClient.isConnected()) { try { //退出登录 ftpClient.logout(); //关闭连接 ftpClient.disconnect(); } catch (IOException e) { } } } } /** * Description: 向FTP服务器上传文件 * * @param basePath FTP服务器基础目录 * @param filePath FTP服务器文件存放路径。例如分日期存放:/2015/01/01。文件的路径为basePath+filePath * @param filename 上传到FTP服务器上的文件名 * @param input 输入流 * @return 成功返回true,否则返回false */ public static boolean uploadFile(String basePath, String filePath, String filename, InputStream input) { boolean result = false; FTPClient ftpClient = null; try { int reply; ftpClient = getFTPClient(); reply = ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { ftpClient.disconnect(); return result; } // 切换到上传目录 if (!ftpClient.changeWorkingDirectory(basePath + filePath)) { // 如果目录不存在创建目录 String[] dirs = filePath.split("/"); String tempPath = basePath; for (String dir : dirs) { if (null == dir || "".equals(dir)) continue; tempPath += "/" + dir; if (!ftpClient.changeWorkingDirectory(tempPath)) { if (!ftpClient.makeDirectory(tempPath)) { return result; } else { ftpClient.changeWorkingDirectory(tempPath); } } } } // 设置上传文件的类型为二进制类型 if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK). LOCAL_CHARSET = "UTF-8"; } ftpClient.setControlEncoding(LOCAL_CHARSET); ftpClient.enterLocalPassiveMode();// 设置被动模式 ftpClient.setFileType(FTP.BINARY_FILE_TYPE);// 设置传输的模式 // 上传文件 filename = new String(filename.getBytes(LOCAL_CHARSET), SERVER_CHARSET); if (!ftpClient.storeFile(filename, input)) { return result; } if (null != input) { input.close(); } result = true; } catch (IOException e) { e.printStackTrace(); } finally { if (ftpClient.isConnected()) { try { //退出登录 ftpClient.logout(); //关闭连接 ftpClient.disconnect(); } catch (IOException ioe) { } } } return result; } /** * 删除文件 未测试 * * @param ftpHost FTP服务器地址 * @param ftpPort FTP服务器端口号 * @param ftpUserName FTP登录帐号 * @param ftpPassword FTP登录密码 * @param pathname FTP服务器保存目录 * @param filename 要删除的文件名称 * @return */ public static boolean deleteFile(String ftpHost, int ftpPort, String ftpUserName, String ftpPassword, String pathname, String filename) { boolean flag = false; public FTPClient getFTPClient() { FTPClient ftpClient = new FTPClient(); try { ftpClient = getFTPClient(); // 验证FTP服务器是否登录成功 int replyCode = ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(replyCode)) { return flag; } // 切换FTP目录 ftpClient.changeWorkingDirectory(pathname); // 设置上传文件的类型为二进制类型 if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK). LOCAL_CHARSET = "UTF-8"; ftpClient.connect(ftpHost, ftpPort); ftpClient.login(ftpUserName, ftpPassword); // 强制开启 UTF-8 支持(优先覆盖服务器配置) if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) { LOCAL_CHARSET = StandardCharsets.UTF_8.name(); } ftpClient.setControlEncoding(LOCAL_CHARSET); ftpClient.enterLocalPassiveMode();// 设置被动模式 ftpClient.setFileType(FTP.BINARY_FILE_TYPE);// 设置传输的模式 //对中文名称进行转码 filename = new String(filename.getBytes(LOCAL_CHARSET), SERVER_CHARSET); ftpClient.dele(filename); flag = true; } catch (Exception e) { e.printStackTrace(); } finally { if (ftpClient.isConnected()) { try { //退出登录 ftpClient.logout(); //关闭连接 ftpClient.disconnect(); } catch (IOException e) { } // 关键配置:二进制传输 + 被动模式,解决文件损坏、防火墙阻塞问题 ftpClient.setFileType(FTP.BINARY_FILE_TYPE); ftpClient.enterLocalPassiveMode(); int replyCode = ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(replyCode)) { logger.error("FTP 连接失败,响应码:" + replyCode); disconnectQuietly(ftpClient); return null; } logger.info("FTP 连接成功,响应码:" + replyCode); return ftpClient; } catch (IOException e) { logger.error("FTP 连接异常:" + e.getMessage(), e); disconnectQuietly(ftpClient); return null; } return flag; } /** * 创建文件夹 * * @param pathname * @param filename * @return * 安静关闭连接(避免嵌套异常) */ public static boolean createFolder(String pathname, String filename) { boolean flag = false; FTPClient ftpClient = new FTPClient(); try { ftpClient = getFTPClient(); // 验证FTP服务器是否登录成功 int replyCode = ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(replyCode)) { return flag; } // 切换FTP目录 ftpClient.changeWorkingDirectory(pathname); boolean created = ftpClient.makeDirectory(filename); if (created) { logger.info(filename + "文件夹创建成功"); } else { logger.info(filename + "创建文件夹失败"); } flag = true; } catch (Exception e) { e.printStackTrace(); } finally { if (ftpClient.isConnected()) { try { //退出登录 ftpClient.logout(); //关闭连接 ftpClient.disconnect(); } catch (IOException e) { } private void disconnectQuietly(FTPClient ftpClient) { if (ftpClient != null && ftpClient.isConnected()) { try { ftpClient.logout(); ftpClient.disconnect(); } catch (IOException e) { logger.warn("关闭 FTP 连接失败:" + e.getMessage(), e); } } return flag; } public static void uploadFolder(File localFolder, String remoteFolder) { if (!localFolder.exists()) { // ====================== 文件夹上传优化:递归 + 完整性校验 ====================== /** * 递归上传文件夹(核心方法) * * @param localFolder 本地文件夹 * @param remoteFolder 远程父目录 */ public void uploadFolder(File localFolder, String remoteFolder) { if (localFolder == null || !localFolder.isDirectory()) { logger.warn("本地文件夹无效:" + (localFolder == null ? "null" : localFolder.getPath())); return; } FTPClient ftpClient = getFTPClient(); try { // 创建远程文件夹 if (!ftpClient.changeWorkingDirectory(remoteFolder)) { ftpClient.makeDirectory(remoteFolder); ftpClient.changeWorkingDirectory(remoteFolder); } } catch (IOException e) { e.printStackTrace(); if (ftpClient == null) { logger.error("FTP 客户端初始化失败,放弃上传:" + localFolder.getPath()); return; } // 遍历本地文件夹中的文件和子文件夹 File[] files = localFolder.listFiles(); if (files != null) { try { // 确保远程目录存在(递归创建) if (!createRemoteDirectory(ftpClient, remoteFolder)) { logger.error("创建远程目录失败:" + remoteFolder); return; } // 遍历本地文件 File[] files = localFolder.listFiles(); if (files == null) { logger.warn("本地文件夹为空:" + localFolder.getPath()); return; } for (File file : files) { if (file.isDirectory()) { // 递归上传子文件夹 uploadFolder(file, remoteFolder + "/" + file.getName()); } else { try { // 上传文件 uploadFile1(ftpClient, file, remoteFolder); } catch (IOException e) { e.printStackTrace(); } else if (file.isFile()) { // 上传文件(带完整性校验) boolean success = uploadFileWithCheck(ftpClient, file, remoteFolder); if (!success) { logger.error("文件上传失败:" + file.getPath() + " -> " + remoteFolder); } } } } } private static void uploadFile1(FTPClient ftpClient, File localFile, String remoteFolder) throws IOException { try (FileInputStream inputStream = new FileInputStream(localFile)) { ftpClient.storeFile(remoteFolder + "/" + localFile.getName(), inputStream); } } // 将字节数组转换为输入流 public static final InputStream byte2Input(byte[] buf) { return new ByteArrayInputStream(buf); } // 将输入流转为byte[] public static final byte[] input2byte(InputStream inStream) throws IOException { ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); byte[] buff = new byte[100]; int rc = 0; while ((rc = inStream.read(buff, 0, 100)) > 0) { swapStream.write(buff, 0, rc); } byte[] in2b = swapStream.toByteArray(); return in2b; } // 将byte[]转为文件 public static void byte2File(byte[] buf, String filePath, String fileName) { BufferedOutputStream bos = null; FileOutputStream fos = null; File file = null; try { File dir = new File(filePath); if (!dir.exists() && dir.isDirectory()) { dir.mkdirs(); } file = new File(filePath + File.separator + fileName); fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos); bos.write(buf); } catch (Exception e) { e.printStackTrace(); } catch (IOException e) { logger.error("上传文件夹异常:" + e.getMessage(), e); } finally { if (bos != null) { try { bos.close(); } catch (IOException e) { e.printStackTrace(); disconnectQuietly(ftpClient); } } /** * 麒麟递归上传文件夹(核心方法) * * @param localFolder 本地文件夹 * @param remoteFolder 远程父目录 */ public void uploadFolderKylin(File localFolder, String remoteFolder) { if (operatingSystem.equals("windows")){ if (localFolder == null || !localFolder.isDirectory()) { logger.warn("本地文件夹无效:" + (localFolder == null ? "null" : localFolder.getPath())); return; } FTPClient ftpClient = getFTPClient(); if (ftpClient == null) { logger.error("FTP 客户端初始化失败,放弃上传:" + localFolder.getPath()); return; } try { // 麒麟系统FTP服务器必需的设置 ftpClient.enterLocalPassiveMode(); // 启用被动模式(类Unix系统推荐) ftpClient.setFileType(FTP.BINARY_FILE_TYPE); // 二进制传输避免文件损坏 ftpClient.setControlEncoding("UTF-8"); // 控制连接编码 // 确保远程目录存在(递归创建) if (!createRemoteDirectory(ftpClient, remoteFolder)) { logger.error("创建远程目录失败:" + remoteFolder); return; } // 遍历本地文件 File[] files = localFolder.listFiles(); if (files == null) { logger.warn("本地文件夹为空:" + localFolder.getPath()); return; } for (File file : files) { if (file.isDirectory()) { // 递归上传子文件夹 uploadFolder(file, remoteFolder + "/" + file.getName()); } else if (file.isFile()) { // 上传文件(带完整性校验) boolean success = uploadFileWithCheck(ftpClient, file, remoteFolder); if (!success) { logger.error("文件上传失败:" + file.getPath() + " -> " + remoteFolder); } } } } catch (IOException e) { logger.error("上传文件夹异常:" + e.getMessage(), e); } finally { disconnectQuietly(ftpClient); } }else { if (localFolder == null || !localFolder.isDirectory()) { logger.warn("本地文件夹无效:" + (localFolder == null ? "null" : localFolder.getPath())); return; } FTPClient ftpClient = getFTPClient(); if (ftpClient == null) { logger.error("FTP 客户端初始化失败,放弃上传:" + localFolder.getPath()); return; } try { // 麒麟系统FTP服务器必需的设置 ftpClient.enterLocalPassiveMode(); // 启用被动模式(类Unix系统推荐) ftpClient.setFileType(FTP.BINARY_FILE_TYPE); // 二进制传输避免文件损坏 ftpClient.setControlEncoding("UTF-8"); // 控制连接编码 // 确保远程目录存在(递归创建) if (!createKylinFtpDirectory(ftpClient, "/iot", remoteFolder)) { logger.error("创建远程目录失败:" + remoteFolder); return; } // 遍历本地文件 File[] files = localFolder.listFiles(); if (files == null) { logger.warn("本地文件夹为空:" + localFolder.getPath()); return; } for (File file : files) { if (file.isDirectory()) { // 递归上传子文件夹 uploadFolder(file, "/iot"+remoteFolder + "/" + file.getName()); } else if (file.isFile()) { // 上传文件(带完整性校验) boolean success = uploadFileWithCheck(ftpClient, file, "/iot"+remoteFolder); if (!success) { logger.error("文件上传失败:" + file.getPath() + " -> " + remoteFolder); } } } } catch (IOException e) { logger.error("上传文件夹异常:" + e.getMessage(), e); } finally { disconnectQuietly(ftpClient); } } } private boolean createRemoteDirectory(FTPClient ftpClient, String remoteDir) throws IOException { String[] dirs = remoteDir.split("/"); StringBuilder currentDir = new StringBuilder(); for (String dir : dirs) { if (dir.trim().isEmpty()) continue; currentDir.append("/").append(dir); String targetDir = currentDir.toString(); // 打印当前尝试切换/创建的目录 logger.info("尝试操作目录:" + targetDir); if (!ftpClient.changeWorkingDirectory(targetDir)) { boolean mkdirResult = ftpClient.makeDirectory(targetDir); // 打印创建结果和 FTP 响应 logger.info("创建目录结果: " + mkdirResult + ", 响应码: " + ftpClient.getReplyCode() + ", 响应信息: " + ftpClient.getReplyString()); if (!mkdirResult) { logger.error("创建目录失败: " + targetDir); return false; } if (!ftpClient.changeWorkingDirectory(targetDir)) { logger.error("切换目录失败: " + targetDir + ", 响应码: " + ftpClient.getReplyCode() + ", 响应信息: " + ftpClient.getReplyString()); return false; } } if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } return true; } /** * 在麒麟系统的FTP服务器上创建目录(支持多级目录) * * @param baseDir FTP基础目录(如/iot) * @param remoteDir 要创建的目录路径(如data/logs/2023) * @return 是否创建成功 * @throws IOException IO异常 */ public boolean createKylinFtpDirectory(FTPClient ftpClient, String baseDir, String remoteDir) throws IOException { // 确保基础目录以/结尾 String normalizedBase = baseDir.endsWith("/") ? baseDir : baseDir + "/"; // 处理远程目录,移除开头可能的/,避免重复 String normalizedRemote = remoteDir.startsWith("/") ? remoteDir.substring(1) : remoteDir; // 分割目录 String[] dirs = normalizedRemote.split("/"); StringBuilder currentPath = new StringBuilder(normalizedBase); for (String dir : dirs) { if (dir.trim().isEmpty()) continue; // 跳过空目录(处理连续//的情况) currentPath.append(dir).append("/"); String targetDir = currentPath.toString(); // 尝试切换到目录 if (!ftpClient.changeWorkingDirectory(targetDir)) { // 切换失败,尝试创建目录 boolean created = ftpClient.makeDirectory(targetDir); logger.info("创建目录[" + targetDir + "]结果: {" + created + "}, 响应码: {" + ftpClient.getReplyCode() + "}"); if (!created) { logger.error("创建目录失败: {" + targetDir + "}"); return false; } // 验证是否能切换到新创建的目录 if (!ftpClient.changeWorkingDirectory(targetDir)) { logger.error("切换到目录失败: {" + targetDir + "}, 响应: {" + ftpClient.getReplyString() + "}"); return false; } } } return true; } /** * 上传文件(带大小校验) * * @return 是否上传成功且完整 */ private boolean uploadFileWithCheck(FTPClient ftpClient, File localFile, String remoteFolder) throws IOException { String remoteFilePath = remoteFolder + "/" + localFile.getName(); long localFileSize = localFile.length(); // 校验远程文件是否已存在且完整 FTPFile[] remoteFiles = ftpClient.listFiles(remoteFilePath); if (remoteFiles != null && remoteFiles.length > 0) { FTPFile remoteFile = remoteFiles[0]; if (remoteFile.getSize() == localFileSize) { logger.info("文件已存在且完整,跳过上传:" + localFile.getPath()); return true; } } // 执行上传 try (FileInputStream inputStream = new FileInputStream(localFile)) { boolean uploaded = ftpClient.storeFile(remoteFilePath, inputStream); if (!uploaded) { logger.error("上传失败,响应码:" + ftpClient.getReplyCode() + " -> " + localFile.getPath()); return false; } // 二次校验远程文件大小 FTPFile[] checkFiles = ftpClient.listFiles(remoteFilePath); if (checkFiles == null || checkFiles.length == 0) { logger.error("上传后文件丢失:" + localFile.getPath()); return false; } return checkFiles[0].getSize() == localFileSize; } } // ====================== 其他方法优化:资源释放 + 编码健壮性 ====================== /** * 下载文件(优化编码处理、资源释放) */ public void downloadFtpFile(String ftpPath, String localPath, String fileName) { FTPClient ftpClient = getFTPClient(); if (ftpClient == null) { return; } try { // 编码转换(解决中文路径问题) String remoteFilePath = new String((ftpPath + "/" + fileName).getBytes(LOCAL_CHARSET), SERVER_CHARSET); if (!ftpClient.changeWorkingDirectory(ftpPath)) { logger.error("切换远程目录失败:" + ftpPath); return; } // 下载文件 try (InputStream is = ftpClient.retrieveFileStream(remoteFilePath); OutputStream os = new FileOutputStream(new File(localPath, fileName))) { if (is == null) { logger.error("远程文件不存在:" + remoteFilePath); return; } byte[] buffer = new byte[4096]; int len; while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } os.flush(); // 确认下载完成(关键:避免文件截断) if (!ftpClient.completePendingCommand()) { logger.error("FTP 下载未完成:" + remoteFilePath); } } } catch (IOException e) { logger.error("下载文件失败:" + fileName, e); } finally { disconnectQuietly(ftpClient); } } /** * 上传文件(独立方法,供复用) * * @param basePath 远程基础路径 * @param filePath 远程子路径 * @param filename 文件名 * @param input 输入流 * @return 是否成功 */ public boolean uploadFile(String basePath, String filePath, String filename, InputStream input) { FTPClient ftpClient = getFTPClient(); if (ftpClient == null) { return false; } try { String remoteDir = basePath + filePath; if (!createRemoteDirectory(ftpClient, remoteDir)) { return false; } // 编码转换(解决中文文件名) String remoteFileName = new String(filename.getBytes(LOCAL_CHARSET), SERVER_CHARSET); boolean success = ftpClient.storeFile(remoteFileName, input); if (success) { logger.info("文件上传成功:" + remoteDir + "/" + remoteFileName); } else { logger.error("文件上传失败,响应码:" + ftpClient.getReplyCode()); } return success; } catch (IOException e) { logger.error("上传文件异常:" + e.getMessage(), e); return false; } finally { disconnectQuietly(ftpClient); try { input.close(); } catch (IOException e) { logger.warn("关闭输入流失败:" + e.getMessage()); } } } } /** * 上传文件(适配麒麟系统,供复用) * * @param basePath 远程基础路径(如/iot) * @param filePath 远程子路径(如data/logs) * @param filename 文件名 * @param input 输入流 * @return 是否成功 */ public boolean uploadFileKylin(String basePath, String filePath, String filename, InputStream input) { FTPClient ftpClient = getFTPClient(); if (ftpClient == null) { logger.error("获取FTPClient实例失败,无法执行上传"); return false; } try { // 麒麟系统FTP服务器必需的设置 ftpClient.enterLocalPassiveMode(); // 启用被动模式(类Unix系统推荐) ftpClient.setFileType(FTP.BINARY_FILE_TYPE); // 二进制传输避免文件损坏 ftpClient.setControlEncoding("UTF-8"); // 控制连接编码 // 路径规范化处理(适配麒麟系统Unix路径格式) String normalizedBase = normalizePath(basePath); String normalizedFilePath = normalizePath(filePath); String remoteDir = normalizedBase + normalizedFilePath; logger.info("麒麟系统FTP目标目录:" + remoteDir); // 创建目录(修正参数顺序,使用规范化路径) if (!createKylinFtpDirectory(ftpClient, normalizedBase, normalizedFilePath)) { logger.error("目录创建失败,终止上传:" + remoteDir); return false; } // 切换到目标目录(增加验证步骤) if (!ftpClient.changeWorkingDirectory(remoteDir)) { logger.error("切换到目录[" + remoteDir + "]失败,响应码: {" + ftpClient.getReplyCode() + "}"); return false; } // 编码转换(优化中文文件名处理,适配麒麟系统编码) String remoteFileName = new String(filename.getBytes(LOCAL_CHARSET), SERVER_CHARSET); // 执行上传 boolean success = ftpClient.storeFile(remoteFileName, input); if (success) { logger.info("文件上传成功:{" + remoteDir + "/" + filename + "}"); } else { logger.error("文件上传失败,路径:{" + remoteDir + "/" + filename + "},响应码:{" + ftpClient.getReplyCode() + "},响应信息:{" + ftpClient.getReplyString() + "}"); } return success; } catch (IOException e) { logger.error("上传文件异常:{" + e.getMessage(), e); return false; } finally { disconnectQuietly(ftpClient); try { input.close(); } catch (IOException e) { logger.warn("关闭输入流失败:{}" + e.getMessage()); } } } /** * 路径规范化处理(适配麒麟系统) * 处理重复斜杠、首尾斜杠问题,统一Unix风格路径 */ private String normalizePath(String path) { if (path == null || path.trim().isEmpty()) { return ""; } // 替换多个斜杠为单个,移除尾部斜杠(除根目录外) String normalized = path.trim().replaceAll("//+", "/"); if (normalized.length() > 1 && normalized.endsWith("/")) { normalized = normalized.substring(0, normalized.length() - 1); } return normalized; } /** * 创建文件夹(适配麒麟系统,解决编码/权限/路径问题) */ public boolean createFolder(String pathname, String folderName) { FTPClient ftpClient = getFTPClient(); if (ftpClient == null) { return false; } try { // 1. 统一路径格式(Linux兼容) String remoteFolder = (pathname + "/" + folderName) .replace("\\", "/") // 替换Windows反斜杠 .replaceAll("/+", "/") // 合并连续斜杠 .replaceAll("^/", ""); // 去除开头的斜杠(避免绝对路径问题) // 2. 设置UTF-8编码(解决中文路径问题) ftpClient.setControlEncoding("UTF-8"); remoteFolder = new String(remoteFolder.getBytes("UTF-8"), "ISO-8859-1"); // FTP协议默认编码 // 3. 检查目录是否存在(幂等性) if (ftpClient.changeWorkingDirectory(remoteFolder)) { logger.info("文件夹已存在:" + remoteFolder); return true; } // 4. 尝试创建目录(被动模式适配) ftpClient.enterLocalPassiveMode(); // 麒麟系统建议用被动模式 boolean success = ftpClient.makeDirectory(remoteFolder); // 5. 记录详细错误信息 if (!success) { int replyCode = ftpClient.getReplyCode(); String replyMsg = ftpClient.getReplyString(); logger.error("创建文件夹失败,路径:" + remoteFolder + ",响应码:" + replyCode + ",响应信息:" + replyMsg); // 特殊处理550错误(权限/SELinux问题) if (replyCode == 550) { logger.error("可能原因:权限不足/SELinux限制/路径不存在"); } } else { logger.info("创建文件夹成功:" + remoteFolder); } return success; } catch (IOException e) { logger.error("创建文件夹异常:" + e.getMessage(), e); return false; } finally { disconnectQuietly(ftpClient); } } // ====================== 工具方法:字节流转换(保持兼容) ====================== public InputStream byte2Input(byte[] buf) { return new ByteArrayInputStream(Objects.requireNonNull(buf)); } public byte[] input2byte(InputStream inStream) throws IOException { try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { byte[] buffer = new byte[4096]; int len; while ((len = inStream.read(buffer)) != -1) { bos.write(buffer, 0, len); } return bos.toByteArray(); } } public void byte2File(byte[] buf, String filePath, String fileName) { File dir = new File(filePath); if (!dir.exists() && !dir.mkdirs()) { logger.error("创建本地目录失败:" + filePath); return; } try (BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(new File(dir, fileName)))) { bos.write(buf); } catch (IOException e) { logger.error("字节数组写入文件失败:" + fileName, e); } } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/quartz/job/SampleJob.java
@@ -16,7 +16,7 @@ /** * 示例不带参定时任务 * * * @Author Scott */ @Slf4j lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/quartz/job/SampleParamJob.java
@@ -82,8 +82,12 @@ AtomicReference<String> end = new AtomicReference<>(""); AtomicReference<Date> endDate = new AtomicReference<>(null); // 采集时间 serverDeployList.forEach(s -> { if (s.getId().equals(in.getServerDeployId()) && s.getCollectTime() != null) { start.set(sdf.format(s.getCollectTime())); if (s.getId().equals(in.getServerDeployId())) { if (s.getCollectTime() != null) { start.set(sdf.format(s.getCollectTime())); } else { start.set(sdf.format(new Date())); } // 获取当前时间 LocalDateTime now = LocalDateTime.now(); // 减去一分钟 @@ -98,7 +102,7 @@ end.set(formattedTime); } }); if (start.get().equals("")){ if (start.get().equals("")) { return; } LocalDateTime startTime = LocalDateTime.parse(start.get(), inputFormatter); @@ -158,7 +162,13 @@ // 处理设备状态 int lastIndex = table.lastIndexOf('.'); String code = table.substring(lastIndex + 1); EquipmentLog equipmentLog = equipmentLogService.selectEquipmentOporation(code); EquipmentLog equipmentLog = null; if (databaseType.equals("SqlServer")) { equipmentLog = equipmentLogService.selectEquipmentOporationSqlServer(code); } else if (databaseType.equals("MySql")) { equipmentLog = equipmentLogService.selectEquipmentOporationMySql(code); } // 设备状态 Integer equipmentState = null; for (Map.Entry<String, List<Influxdb>> entry : timeListMap.entrySet()) { lxzn-module-system/lxzn-system-start/src/main/resources/application-prod.yml
@@ -53,12 +53,6 @@ instanceId: AUTO jobStore: selectWithLockSQL: SELECT* FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ? # class: org.springframework.scheduling.quartz.LocalDataSourceJobStore # driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # tablePrefix: QRTZ_ # isClustered: true # misfireThreshold: 12000 # clusterCheckinInterval: 15000 threadPool: class: org.quartz.simpl.SimpleThreadPool threadCount: 10 @@ -112,7 +106,7 @@ # 初始化大小,最小,最大 initial-size: 5 min-idle: 5 maxActive: 1000 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 @@ -132,13 +126,13 @@ connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 datasource: master: url: jdbc:sqlserver://192.168.1.123:1433;databasename=lx_iot_mdc username: sa password: 123 driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver url: jdbc:mysql://127.0.0.1:3306/lx_iot_mdc?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver #redis 配置 redis: database: 5 database: 0 host: 127.0.0.1 port: 6379 password: '' @@ -155,7 +149,7 @@ table-underline: true configuration: # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用 #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 返回类型为Map,显示null对应的字段 call-setters-on-nulls: true #jeecg专用配置 @@ -167,7 +161,8 @@ # 签名密钥串(前后端要一致,正式发布请自行修改) signatureSecret: dd05f1c54d63749eda95f9fa6d49v442a # 签名拦截接口 signUrls: /sys/dict/getDictItems/*,/sys/dict/loadDict/*,/sys/dict/loadDictOrderByValue/*,/sys/dict/loadDictItem/*,/sys/dict/loadTreeData,/sys/api/queryTableDictItemsByCode,/sys/api/queryFilterTableDictInfo,/sys/api/queryTableDictByKeys,/sys/api/translateDictFromTable,/sys/api/translateDictFromTableByKeys # signUrls: /sys/dict/getDictItems/*,/sys/dict/loadDict/*,/sys/dict/loadDictOrderByValue/*,/sys/dict/loadDictItem/*,/sys/dict/loadTreeData,/sys/api/queryTableDictItemsByCode,/sys/api/queryFilterTableDictInfo,/sys/api/queryTableDictByKeys,/sys/api/translateDictFromTable,/sys/api/translateDictFromTableByKeys signUrls: /sys/dict/loadDict/*,/sys/dict/loadDictOrderByValue/*,/sys/dict/loadDictItem/*,/sys/dict/loadTreeData,/sys/api/queryTableDictItemsByCode,/sys/api/queryFilterTableDictInfo,/sys/api/queryTableDictByKeys,/sys/api/translateDictFromTable,/sys/api/translateDictFromTableByKeys #local、minio、alioss uploadType: local # 前端访问地址 @@ -180,21 +175,20 @@ #webapp文件路径 webapp: C://opt//upFiles shiro: excludeUrls: /test/jeecgDemo/demo3,/test/jeecgDemo/redisDemo/**,/category/**,/visual/**,/map/**,/jmreport/bigscreen2/**,/api/getUserInfo excludeUrls: /test/jeecgDemo/demo3,/test/jeecgDemo/redisDemo/**,/category/**,/visual/**,/map/**,/jmreport/bigscreen2/** #阿里云oss存储和大鱼短信秘钥配置 oss: accessKey: ?? secretKey: ?? endpoint: oss-cn-beijing.aliyuncs.com bucketName: jeecgdev staticDomain: https://static.jeecg.com # ElasticSearch 设置 # ElasticSearch 6设置 elasticsearch: cluster-name: jeecg-ES cluster-nodes: 127.0.0.1:9200 check-enabled: false # 在线预览文件服务器地址配置 file-view-domain: http://fileview.jeecg.com file-view-domain: 127.0.0.1:8012 # minio文件上传 minio: minio_url: http://minio.jeecg.com @@ -203,7 +197,7 @@ bucketName: otatest #大屏报表参数设置 jmreport: mode: prod mode: dev #数据字典是否进行saas数据隔离,自己看自己的字典 saas: false #是否需要校验token @@ -241,7 +235,7 @@ #开启生产环境屏蔽 production: false basic: enable: true enable: false username: jeecg password: jeecg1314 #第三方登录 @@ -292,29 +286,36 @@ # appSecret client-secret: ?? agent-id: ?? webservice: url: http://localhost:8081/services/EquipmentService?wsdl namespace: http://service.server.webservice.example.com statusMethod: equipmentStatus rateMethod: equipmentRate mqtt: host: tcp://192.168.1.123:1883 clientId: mqtt_manage_platform host: tcp://127.0.0.1:1883 clientId: mqtt_manage_platform_dev username: admin password: public password: lxzn1688 timeout: 30000 keepalive: 300 clientUrl: http://192.168.1.123:18083/api/v5/clients clientUrl: http://127.0.0.1:18083/api/v5/clients #API授权KEY apiKey: fb5d13d541c4ebd9 apiKey: 45204bbc6befd831 #API授权密钥 secretKey: J9A4pFcUyYzyo6uhFh6AKcVBi7xHcUgcNoNMCWylrCHE secretKey: bs7mFjE6qhr39AhC4VWjByUphVxm9Af9CWlvuGJO3coxdM #驱动文件地址 drive: D:\\iot\\configuration\\DriveConfig.xml script: D:\\iot\\configuration\\FunctionConfig.xml drive: /iot/configuration/DriveConfig.xml script: /iot/configuration/FunctionConfig.xml #ftp连接地址 ftp: LOCAL_CHARSET: GBK SERVER_CHARSET: ISO-8859-1 ftpHost: 192.168.1.123 ftpHost: 10.0.221.200 ftpPort: 21 ftpUserName: admin ftpPassword: lx@2024 address: D:/iot/ address: /iot/ #数据库 MySql | SqlServer databaseType: SqlServer databaseType: MySql #操作系统 kylin | windows operatingSystem: kylin