cuikaidong
2025-08-27 1e514802d4ea60245c6d8adf42dba73c154274a6
文件地址修改,单表sql修改
已修改18个文件
1157 ■■■■■ 文件已修改
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/controller/EquipmentController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/controller/ServeDeployController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/entity/Equipment.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/entity/EquipmentAlarm.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/entity/EquipmentLog.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/mapper/EquipmentLogMapper.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/mapper/xml/EquipmentLogMapper.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/service/IEquipmentLogService.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/service/IEquipmentService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/service/impl/EquipmentLogServiceImpl.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/service/impl/EquipmentServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/service/impl/EquipmentServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/service/impl/RealParameterServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/service/impl/ServerDeployServiceImpl.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/util/FtpUtil.java 918 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/quartz/job/SampleJob.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/quartz/job/SampleParamJob.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lxzn-module-system/lxzn-system-start/src/main/resources/application-prod.yml 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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
@@ -14,7 +14,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
@@ -16,7 +16,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/EquipmentLogMapper.java
@@ -18,7 +18,9 @@
    EquipmentLog getRow(@Param("equipmentid") String equipmentid, @Param("startTime") Date startTime);
    EquipmentLog selectEquipmentOporation(@Param("equipmentId") String equipmentId);
    EquipmentLog selectEquipmentOporationMySql(@Param("equipmentId") String equipmentId);
    EquipmentLog selectEquipmentOporationSqlServer(@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
@@ -6,10 +6,14 @@
        SELECT top 1 * FROM EquipmentLog WHERE EquipmentID = #{ equipmentid } AND CollectTime &lt;= #{ startTime } AND Oporation in ('0','1','2','3') ORDER BY CollectTime ASC
    </select>
    <select id="selectEquipmentOporation" resultType="org.jeecg.modules.iot.mdc.entity.EquipmentLog">
    <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="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
lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/iot/mdc/service/IEquipmentLogService.java
@@ -27,7 +27,9 @@
     */
    EquipmentLog getRow(String equipmentid, Date startTime);
    EquipmentLog selectEquipmentOporation(String equipmentId);
    EquipmentLog selectEquipmentOporationSqlServer(String equipmentId);
    EquipmentLog selectEquipmentOporationMySql(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
@@ -50,8 +50,13 @@
    }
    @Override
    public EquipmentLog selectEquipmentOporation(String equipmentId) {
        return this.baseMapper.selectEquipmentOporation(equipmentId);
    public EquipmentLog selectEquipmentOporationMySql(String equipmentId) {
        return this.baseMapper.selectEquipmentOporationMySql(equipmentId);
    }
    @Override
    public EquipmentLog selectEquipmentOporationSqlServer(String equipmentId) {
        return this.baseMapper.selectEquipmentOporationSqlServer(equipmentId);
    }
    @Override
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