xusl hai 4 meses
achega
56784ee90e

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 91 - 0
pom.xml

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.jiayue</groupId>
+    <artifactId>cpp-client</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>cpp-client</name>
+    <description>cpp-client</description>
+    <properties>
+        <java.version>1.8</java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <spring-boot.version>2.3.12.RELEASE</spring-boot.version>
+    </properties>
+    <dependencies>
+        <!--hutool-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.5.3</version>
+        </dependency>
+        <!--Lombok-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <configuration>
+                    <mainClass>com.jiayue.cppclient.CppClientApplication</mainClass>
+                    <skip>true</skip>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>repackage</id>
+                        <goals>
+                            <goal>build-info</goal>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 28 - 0
src/main/java/com/jiayue/cppclient/CppClientApplication.java

@@ -0,0 +1,28 @@
+package com.jiayue.cppclient;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+@SpringBootApplication
+@EnableScheduling
+public class CppClientApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(CppClientApplication.class, args);
+    }
+
+
+    @Bean
+    public TaskScheduler taskScheduler(){
+        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
+        // 线程池大小
+        taskScheduler.setPoolSize(20);
+        // 线程名字的前缀
+        taskScheduler.setThreadNamePrefix("taskScheduler");
+        return taskScheduler;
+    }
+}

+ 147 - 0
src/main/java/com/jiayue/cppclient/job/DownloadFileJob.java

@@ -0,0 +1,147 @@
+package com.jiayue.cppclient.job;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.jiayue.cppclient.util.Md5KeyForCppUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * 下载文件定时
+ *
+ * @author jy
+ * @since 2024/12/10
+ */
+@Component
+@Slf4j
+public class DownloadFileJob {
+
+    @Value("${client.stationCodes}")
+    String stationCodes;
+    @Value("${client.cloudAddress}")
+    String cloudAddress;
+    @Value("${client.tempFilePath}")
+    String tempFilePath;
+    @Value("${client.fxglPath}")
+    String fxglPath;
+
+    @Scheduled(cron="${client.job.downloadCron}")
+    public void downloadFile() {
+        // 遍历场站编号
+        String[] stationCode = stationCodes.split(",");
+        // 当日日期yyyy-MM-dd
+        String forecastDay = DateUtil.format(new Date(),"yyyyMMdd");
+        for (int i=0;i<stationCode.length;i++){
+            Map<String,String> dataMap = null;
+            // 查询云端预测文件
+            try {
+                dataMap = queryToDownfilesFromCloud(stationCode[i],forecastDay);
+            } catch (Exception e) {
+                log.error(stationCode[i]+"获取云端预测文件失败",e);
+            }
+
+            if (dataMap!=null){
+                // 遍历文件下载地址Map
+                for (String fileName:dataMap.keySet()){
+                    try {
+                        String downUrl = dataMap.get(fileName);
+                        // 下载成功后回传更新云端文件状态,同时将临时文件移动道反向隔离路径
+                        Long size = downloadFileByCloud(fileName, downUrl);
+                        if (size>0){
+                            callBackFileDownloadStatusToCloud(stationCode[i], fileName,"down","是");
+                            // 将临时文件移动道反向隔离路径
+                            File fxglPathDir = new File(fxglPath);
+                            if (!fxglPathDir.exists() && !fxglPathDir.isDirectory()){
+                                // 创建目录
+                                fxglPathDir.mkdirs();
+                            }
+
+                            FileUtil.move(new File(tempFilePath + File.separatorChar + fileName),new File(fxglPath),true);
+                        }
+                        else{
+                            callBackFileDownloadStatusToCloud(stationCode[i], fileName,"down","否");
+                        }
+                    }
+                    catch (Exception e){
+                        log.error(stationCode[i]+"下载云端预测文件失败",e);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 请求云端,获取今日未下载文件列表
+     *
+     * @return Map<String,String>: 文件名,文件下载地址
+     */
+    private Map<String,String> queryToDownfilesFromCloud(String stationCode,String forecastDay) throws Exception {
+        String signStr = Md5KeyForCppUtil.downloadFileMd5Key(stationCode,forecastDay);
+        Map postParms = new HashMap<>(3);
+        postParms.put("stationCode", stationCode);
+        postParms.put("forecastDay", forecastDay);
+        postParms.put("signStr",signStr);
+        String body = HttpUtil.post(cloudAddress+"/getCppForecastFileInfo", postParms, 10000);
+        JSONObject json = JSONUtil.parseObj(body);
+        String code = json.get("code").toString();
+        String data = json.get("data").toString();
+        if ("0".equals(code)) {
+            Map dataMap = JSONUtil.toBean(data, Map.class);
+            if (MapUtil.isNotEmpty(dataMap)){
+                return dataMap;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 从minio下载文件
+     *
+     * @param fileName
+     * @param fileDownloadUrl
+     * @return
+     */
+    private Long downloadFileByCloud(String fileName, String fileDownloadUrl) throws Exception{
+        Long size = 0L;
+        size = HttpUtil.downloadFile(fileDownloadUrl, new File(tempFilePath + File.separatorChar
+                + fileName), 10000);
+        if (size > 0) {
+            log.info("云端下载文件成功:{}", fileName);
+            return size;
+        }
+        else{
+            log.info("云端下载文件失败,回传失败状态:{}", fileName);
+        }
+        return size;
+    }
+
+    /**
+     * 回传云端文件状态
+     *
+     * @param stationCode
+     * @param forecastFileName
+     * @param type
+     * @param status
+     * @return
+     */
+    private void callBackFileDownloadStatusToCloud(String stationCode, String forecastFileName,String type,String status) throws Exception{
+        String callbackSignStr = Md5KeyForCppUtil.callbackFileMd5Key(stationCode, forecastFileName,type,status);
+        Map postParms = new HashMap<>(5);
+        postParms.put("stationCode", stationCode);
+        postParms.put("forecastFileName", forecastFileName);
+        postParms.put("type", type);
+        postParms.put("status", status);
+        postParms.put("signStr", callbackSignStr);
+        String body = HttpUtil.post(cloudAddress + "/updateCppFileStatus", postParms, 10000);
+        log.info("文件下载成功,回传云端文件下载状态响应报文");
+    }
+}

+ 107 - 0
src/main/java/com/jiayue/cppclient/job/FxglFileMonitor.java

@@ -0,0 +1,107 @@
+package com.jiayue.cppclient.job;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.watch.WatchMonitor;
+import cn.hutool.core.io.watch.Watcher;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpUtil;
+import com.jiayue.cppclient.util.Md5KeyForCppUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 反向隔离目录下文件监控
+ * @author jy
+ * @since 2024/12/10
+ */
+@Slf4j
+@Component
+public class FxglFileMonitor {
+    @Value("${client.fxglPath}")
+    String fxglPath;
+    @Value("${client.cloudAddress}")
+    String cloudAddress;
+
+    @PostConstruct
+    public void fileCreateMonitor() {
+        // 指定监听目录,实际应用需要由外部配置文件进行配置
+        File file = FileUtil.file(fxglPath);
+        if (ObjectUtil.isNull(file) && !file.exists()) {
+            log.info("监听目录:{},不存在,请检查并修改配置文件后重启程序。", fxglPath);
+            return;
+        }
+        //监听所有事件
+        WatchMonitor watchMonitor = WatchMonitor.create(file, WatchMonitor.EVENTS_ALL);
+        watchMonitor.setWatcher(new Watcher(){
+            @Override
+            public void onCreate(WatchEvent<?> event, Path currentPath) {
+//                Object obj = event.context();
+//                log.info("创建:"+currentPath+"->"+ obj );
+            }
+
+            @Override
+            public void onModify(WatchEvent<?> event, Path currentPath) {
+//                Object obj = event.context();
+//                log.info("修改:"+currentPath+"->"+ obj );
+            }
+
+            @Override
+            public void onDelete(WatchEvent<?> event, Path currentPath) {
+                new Thread(() -> {
+                    String fileName = event.context().toString();
+                    if (fileName.startsWith("NWP_") || fileName.startsWith("DQ_")){
+                        // 回传文件状态
+                        String[] fileNames = fileName.split("_");
+                        String stationCode = fileNames[1];
+                        try {
+                            callBackFileDownloadStatusToCloud(stationCode,fileName,"in", "是");
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                    }
+                }).start();
+
+//                log.info("删除:"+currentPath+"->"+ fileName );
+            }
+
+            @Override
+            public void onOverflow(WatchEvent<?> event, Path currentPath) {
+                Object obj = event.context();
+                log.info("Overflow:"+currentPath+"->"+ obj );
+            }
+        });
+        // 设置监听目录的最大深度,目录层级大于指定层级的变更将不再监听,不设置默认只监听当前目录
+//        watchMonitor.setMaxDepth(3);
+        // 启动监听
+        watchMonitor.start();
+    }
+
+    /**
+     * 回传云端文件状态
+     *
+     * @param stationCode
+     * @param forecastFileName
+     * @param type
+     * @param status
+     * @return
+     */
+    private void callBackFileDownloadStatusToCloud(String stationCode, String forecastFileName,String type,String status) throws Exception{
+        String callbackSignStr = Md5KeyForCppUtil.callbackFileMd5Key(stationCode, forecastFileName,type,status);
+        Map postParms = new HashMap<>(5);
+        postParms.put("stationCode", stationCode);
+        postParms.put("forecastFileName", forecastFileName);
+        postParms.put("type", type);
+        postParms.put("status", status);
+        postParms.put("signStr", callbackSignStr);
+        String body = HttpUtil.post(cloudAddress + "/updateCppFileStatus", postParms, 10000);
+        log.info("反向隔离获取成功,回传云端文件已被隔离获取,返回结果:"+body);
+    }
+}

+ 26 - 0
src/main/java/com/jiayue/cppclient/job/Test.java

@@ -0,0 +1,26 @@
+package com.jiayue.cppclient.job;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.http.HttpUtil;
+import com.jiayue.cppclient.util.Md5KeyForCppUtil;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author jy
+ * @since 2024/12/10
+ */
+public class Test {
+    public static void main(String[] args) {
+        String forecastDay = DateUtil.format(new Date(),"yyyyMMdd");
+        String stationCode = "T00001";
+        Map postParms = new HashMap<>(3);
+        postParms.put("stationCode", stationCode);
+        postParms.put("forecastDay", forecastDay);
+        postParms.put("signStr", Md5KeyForCppUtil.downloadFileMd5Key(stationCode,forecastDay));
+        String body = HttpUtil.post("http://cloud.jiayuepowertech.com/admin-api/cemp/v3/getCppForecastFileInfo", postParms, 10000);
+        System.out.println(body);
+    }
+}

+ 36 - 0
src/main/java/com/jiayue/cppclient/util/Md5KeyForCppUtil.java

@@ -0,0 +1,36 @@
+package com.jiayue.cppclient.util;
+
+import cn.hutool.crypto.SecureUtil;
+
+/**
+ * @author jy
+ * @since 2024/12/09
+ */
+public class Md5KeyForCppUtil {
+    private final static String md5KeyForCpp = "cpp_xsl&t20241212";
+
+    /**
+     * 下载文件md5key
+     * @param stationCode
+     * @param forecastDay
+     * @return
+     */
+    public static String downloadFileMd5Key(String stationCode, String forecastDay) {
+        String signOrg = stationCode + forecastDay + md5KeyForCpp;
+        return SecureUtil.md5(signOrg);
+    }
+
+    /**
+     * 回传文件md5key
+     * @return
+     */
+    public static String callbackFileMd5Key(String stationCode, String fileName,String type,String status) {
+        String signOrg = stationCode + fileName + type+status+md5KeyForCpp;
+        return SecureUtil.md5(signOrg);
+    }
+
+    public static void main(String[] args) {
+        String md5Key = downloadFileMd5Key("T00001", "20241210");
+        System.out.println(md5Key);
+    }
+}

+ 17 - 0
src/main/resources/application.yml

@@ -0,0 +1,17 @@
+
+logging:
+  config: classpath:logback.xml
+
+client:
+  # 下载文件定时任务
+  job:
+    downloadCron: 0/5 * * * * ?
+  # 场站编号
+  stationCodes: T00002,T00001
+  # 云端下载地址
+  cloudAddress: http://cloud.jiayuepowertech.com/admin-api/cemp/v3/
+  # 下载文件存放路径临时文件
+  tempFilePath: /data/download/
+  # 反向隔离路径
+  fxglPath: /data/reverse/
+

+ 78 - 0
src/main/resources/logback.xml

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<configuration scan="false" scanPeriod="60 seconds" debug="false"><!-- 这个是根配置文件,一定要有的
+                            scan:
+                                是当配置文件被修改后会被重新加载
+                            scanPeriod:
+                                设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,
+                                默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
+                            debug:
+                                当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。
+                                默认值为false。
+                             -->
+  <!-- 日志存放路径
+      下面的标签可以自己定义
+      name:相当于Map的key
+      value:就是map的value
+      ${catalina.base}是tomcat的当前路径
+      /logs:就是tomcat下的日志路径,
+      /ehrlog:如果没有目录会默认创建
+  -->
+  <property name="logbase" value="../../../logs"/>
+  <!-- 时间戳:这个时间戳可以作为每日日志的名称 -->
+  <timestamp key="bySecond" datePattern="yyyy-MM-dd"/>
+  <!-- appender:
+      name相当于一个名称
+      class:确定要加载哪个类
+      encoder:一定要加 encoder ,
+      默认配置为PatternLayoutEncoder
+      patter:必填
+      ConsoleAppender:也明白是什么意思,就是输出在控制台上-->
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
+      <charset>UTF-8</charset>
+    </encoder>
+  </appender>
+  <!-- 把日志存储
+      encoding:日志的编码
+      file:指定当前生成的日志文件名称
+      rollingPolicy:滚动策略
+      FileNamePattern:移动文件最后的名称,跟file标签结合使用,
+      比如file里面的内容是  1.txt
+      那么,FileNamePattern里面写的是2.txt,那么最后文件名就为2.txt
+      如果最后结尾是gz或者zip,那么,就会自动打成压缩包
+      -->
+  <appender name="logFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    <!-- 编码 -->
+    <!--<Encoding>UTF-8</Encoding>-->
+    <!-- 按照时间来 -->
+    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+      <!--日志文件输出的文件名-->
+      <FileNamePattern>${logbase}/%d{yyyy-MM-dd}/cpp-client.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
+      <!--日志文件保留天数-->
+      <MaxHistory>180</MaxHistory>
+      <maxFileSize>10MB</maxFileSize>
+      <totalSizeCap>1024MB</totalSizeCap>
+      <cleanHistoryOnStart>true</cleanHistoryOnStart>
+    </rollingPolicy>
+    <!-- 布局 -->
+    <encoder>
+      <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
+      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
+      <charset>UTF-8</charset>
+    </encoder>
+    <append>false</append>
+  </appender>
+
+  <logger name="com.jiayue" level="info" additivity="true">
+    <appender-ref ref="logFile"/>
+  </logger>
+  <logger name="org" level="info" additivity="true">
+    <appender-ref ref="logFile"/>
+  </logger>
+
+  <root level="info">
+    <appender-ref ref="STDOUT"/>
+  </root>
+</configuration>

+ 13 - 0
src/test/java/com/jiayue/cppclient/CppClientApplicationTests.java

@@ -0,0 +1,13 @@
+package com.jiayue.cppclient;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class CppClientApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}