Bläddra i källkod

添加状态转换率计算

xushining 1 år sedan
förälder
incheckning
d42ec44656
40 ändrade filer med 3164 tillägg och 0 borttagningar
  1. 37 0
      metrics/.gitignore
  2. 5 0
      metrics/HELP.md
  3. 41 0
      metrics/build.gradle
  4. 79 0
      metrics/conversion-rate-stats-config.json
  5. 240 0
      metrics/gradlew
  6. 91 0
      metrics/gradlew.bat
  7. 1 0
      metrics/settings.gradle
  8. 13 0
      metrics/src/main/java/com/gyee/gaia/metrics/MetricsApplication.java
  9. 187 0
      metrics/src/main/java/com/gyee/gaia/metrics/cache/InfoCache.java
  10. 25 0
      metrics/src/main/java/com/gyee/gaia/metrics/configuration/CorsConfig.java
  11. 76 0
      metrics/src/main/java/com/gyee/gaia/metrics/configuration/ThreadPoolConfig.java
  12. 42 0
      metrics/src/main/java/com/gyee/gaia/metrics/controller/StatsController.kt
  13. 407 0
      metrics/src/main/java/com/gyee/gaia/metrics/dao/entity/ConversionRateStatsEntity.java
  14. 78 0
      metrics/src/main/java/com/gyee/gaia/metrics/dao/entity/TsPointEntity.java
  15. 184 0
      metrics/src/main/java/com/gyee/gaia/metrics/dao/entity/WindturbineEntity.java
  16. 17 0
      metrics/src/main/java/com/gyee/gaia/metrics/dao/repsoitory/ConversionRateStatsRepository.java
  17. 32 0
      metrics/src/main/java/com/gyee/gaia/metrics/dao/repsoitory/TsPointRepository.java
  18. 29 0
      metrics/src/main/java/com/gyee/gaia/metrics/dao/repsoitory/WindturbineRepository.java
  19. 32 0
      metrics/src/main/java/com/gyee/gaia/metrics/feigns/FeignsBuilder.java
  20. 78 0
      metrics/src/main/java/com/gyee/gaia/metrics/feigns/IDataAdapter.java
  21. 110 0
      metrics/src/main/java/com/gyee/gaia/metrics/manager/FileManager.java
  22. 98 0
      metrics/src/main/java/com/gyee/gaia/metrics/manager/StatsManager.kt
  23. 115 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/BoostStation.java
  24. 220 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/ConversionRateWindTurbineInfo.java
  25. 88 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/DateTimeHelper.kt
  26. 13 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/Message.kt
  27. 63 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/PointData.java
  28. 32 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/Project.kt
  29. 21 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/Station.kt
  30. 83 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/StatsInfo.kt
  31. 15 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/StatusChangedInfo.kt
  32. 10 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/TagInfo.kt
  33. 31 0
      metrics/src/main/java/com/gyee/gaia/metrics/modles/enums/Conditions.java
  34. 181 0
      metrics/src/main/java/com/gyee/gaia/metrics/services/BasicInformationService.java
  35. 312 0
      metrics/src/main/java/com/gyee/gaia/metrics/services/ConversionRateStatsService.kt
  36. 17 0
      metrics/src/main/java/com/gyee/gaia/metrics/targets/UniformCode.java
  37. 44 0
      metrics/src/main/resources/application.yaml
  38. 3 0
      metrics/src/main/resources/banner.txt
  39. 13 0
      metrics/src/test/java/com/gyee/gaia/metrics/MetricsApplicationTests.java
  40. 1 0
      settings.gradle

+ 37 - 0
metrics/.gitignore

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

+ 5 - 0
metrics/HELP.md

@@ -0,0 +1,5 @@
+# 指标计算服务
+
+### 状态转换率计算服务
+用于计算风机状态转换率,以及提供数据查询接口
+

+ 41 - 0
metrics/build.gradle

@@ -0,0 +1,41 @@
+plugins {
+    id 'java'
+    id 'org.springframework.boot' version '2.7.11'
+    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
+    id 'org.jetbrains.kotlin.jvm' version '1.8.21'
+}
+
+group = 'com.gyee.gaia'
+version = '1.0.4'
+sourceCompatibility = '1.8'
+
+repositories {
+    maven {
+        url 'https://maven.aliyun.com/repository/public/'
+    }
+    maven {
+        url 'https://maven.aliyun.com/repository/spring/'
+    }
+    mavenLocal()
+    mavenCentral()
+}
+
+dependencies {
+    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+    implementation("org.springframework.boot:spring-boot-starter-web")
+    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+
+    implementation 'com.oracle.database.jdbc:ojdbc8'
+    implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.31_noneautotype'
+
+    implementation 'com.alibaba:druid:1.2.14'
+    implementation 'com.netflix.feign:feign-core:8.18.0'
+    implementation 'com.netflix.feign:feign-jackson:8.18.0'
+
+    testImplementation 'org.springframework.boot:spring-boot-starter-test'
+}
+
+tasks.named('test') {
+    useJUnitPlatform()
+}

+ 79 - 0
metrics/conversion-rate-stats-config.json

@@ -0,0 +1,79 @@
+[
+  {
+    "stationId": "MHS_FDC",
+    "stationName": "麻黄山风电场",
+    "id": "MHS_BT",
+    "name": "麻黄山第二风电场",
+    "projects": [
+      "MHS01_GC",
+      "MHS02_GC"
+    ],
+    "electricityRestrictionsTag": "MHSFCJSFW.NX_GD_MHSF_XX_XX_XXX_XXX_CI0263"
+  },
+  {
+    "stationId": "NSS_FDC",
+    "stationName": "牛首山风电场",
+    "id": "NSS_BT",
+    "name": "牛首山第二风电场",
+    "projects": [
+      "NSS01_GC",
+      "NSS02_GC",
+      "NSS03_GC"
+    ],
+    "electricityRestrictionsTag": "NSSFCJSFW.NX_GD_NSSF_XX_XX_XXX_XXX_CI0263"
+  },
+  {
+    "stationId": "QS_FDC",
+    "stationName": "青山风电场",
+    "id": "QS_BT",
+    "name": "麻黄山第六风电场",
+    "projects": [
+      "QS01_GC",
+      "QS02_GC"
+    ],
+    "electricityRestrictionsTag": "QSFCJSFW.NX_GD_QSF_XX_XX_XXX_XXX_CI0263"
+  },
+  {
+    "stationId": "QS_FDC",
+    "stationName": "青山风电场",
+    "id": "SL_BT",
+    "name": "宋堡第六风电场",
+    "projects": [
+      "QS03_GC"
+    ],
+    "electricityRestrictionsTag": "SLAGC.NX_GD_QSF_DQ_P1_L1_001_DI0165"
+  },
+  {
+    "stationId": "SBQ_FDC",
+    "stationName": "石板泉风电场",
+    "id": "XN6_BT",
+    "name": "星能第六风电场",
+    "projects": [
+      "SBQ01_GC"
+    ],
+    "electricityRestrictionsTag": "SBQFCJSFW.NX_GD_SBQF_XX_XX_XXX_XXX_CI0263"
+  },
+  {
+    "stationId": "SBQ_FDC",
+    "stationName": "石板泉风电场",
+    "id": "N5_BT",
+    "name": "牛首山第五风电场",
+    "projects": [
+      "SBQ02_GC",
+      "SBQ03_GC",
+      "SBQ04_GC"
+    ],
+    "electricityRestrictionsTag": "SBQFCJSFW.NX_GD_SBQF_XX_XX_XXX_XXX_CI026X"
+  },
+  {
+    "stationId": "XS_FDC",
+    "stationName": "香山风电场",
+    "id": "XS_BT",
+    "name": "香山第五风电场",
+    "projects": [
+      "XS01_GC",
+      "XS02_GC"
+    ],
+    "electricityRestrictionsTag": "XSFCJSFW.NX_GD_XSF_XX_XX_XXX_XXX_CI0263"
+  }
+]

+ 240 - 0
metrics/gradlew

@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"

+ 91 - 0
metrics/gradlew.bat

@@ -0,0 +1,91 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 1 - 0
metrics/settings.gradle

@@ -0,0 +1 @@
+rootProject.name = 'metrics'

+ 13 - 0
metrics/src/main/java/com/gyee/gaia/metrics/MetricsApplication.java

@@ -0,0 +1,13 @@
+package com.gyee.gaia.metrics;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class MetricsApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(MetricsApplication.class, args);
+    }
+
+}

+ 187 - 0
metrics/src/main/java/com/gyee/gaia/metrics/cache/InfoCache.java

@@ -0,0 +1,187 @@
+package com.gyee.gaia.metrics.cache;
+
+import com.gyee.gaia.metrics.modles.TagInfo;
+import com.gyee.gaia.metrics.dao.entity.TsPointEntity;
+import com.gyee.gaia.metrics.dao.entity.WindturbineEntity;
+import com.gyee.gaia.metrics.dao.repsoitory.TsPointRepository;
+import com.gyee.gaia.metrics.dao.repsoitory.WindturbineRepository;
+import com.gyee.gaia.metrics.manager.FileManager;
+import com.gyee.gaia.metrics.modles.BoostStation;
+import com.gyee.gaia.metrics.modles.ConversionRateWindTurbineInfo;
+import com.gyee.gaia.metrics.targets.UniformCode;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Field;
+import java.util.*;
+
+@Component
+public class InfoCache {
+    private final WindturbineRepository windturbineRepository;
+    private final TsPointRepository tsPointRepository;
+    private final FileManager fileManager;
+    private Map<String, ConversionRateWindTurbineInfo> conversionRateWindTurbineInfoMap;
+    /**
+     * 为维护指令的机型
+     */
+    private HashSet<String> noMaintainModels = new HashSet<>();
+    /**
+     * 升压站信息
+     */
+    private BoostStation[] boostStations;
+
+    public Map<String, ConversionRateWindTurbineInfo> getConversionRateWindTurbineInfoMap() {
+        if (conversionRateWindTurbineInfoMap == null) {
+            conversionRateWindTurbineInfoMap = new HashMap<>();
+        }
+        return conversionRateWindTurbineInfoMap;
+    }
+
+    public BoostStation[] getBoostStations() {
+        if (boostStations == null) {
+            boostStations = new BoostStation[0];
+        }
+        return boostStations;
+    }
+
+    public InfoCache(TsPointRepository tsPointRepository, WindturbineRepository windturbineRepository
+    , FileManager fileManager, @Value("${no-maintain-model:}") String noMaintainModel) {
+        this.windturbineRepository = windturbineRepository;
+        this.tsPointRepository = tsPointRepository;
+        this.fileManager = fileManager;
+        noMaintainModels.addAll(Arrays.asList(noMaintainModel.split(",")));
+        System.out.println("开始初始化基本信息...");
+        boostStations = fileManager.getFromFile("conversion-rate-stats-config.json", BoostStation[].class);
+        // 初始化状态转换率风机信息
+        initWindturbineInfo();
+        // 初始状态转换率化状态转换率风机测点
+        initWindturbineTags();
+
+        System.out.println("初始化基本信息结束");
+    }
+
+    private void initWindturbineTags() {
+        // 获取需要刷新的UniformCode
+        String[] ucs = getUniformCodes(ConversionRateWindTurbineInfo.class);
+        // 获取所有的标签点
+        List<TsPointEntity> pls = tsPointRepository.findAllByThingTypeAndUniformCodeIn("windturbine", ucs);
+        // 获取风机中的属性
+        Map<String, Field> fieldsMap = getFields(ConversionRateWindTurbineInfo.class);
+        // 风机信息
+        Map<String, ConversionRateWindTurbineInfo> wbm = this.getConversionRateWindTurbineInfoMap();
+        for (TsPointEntity tp : pls) {
+            if (!wbm.containsKey(tp.getThingId()) || !fieldsMap.containsKey(tp.getUniformCode())) {
+                continue;
+            }
+            ConversionRateWindTurbineInfo wi = wbm.get(tp.getThingId());
+            Field field = fieldsMap.get(tp.getUniformCode());
+            TagInfo ti = getTagInfo(field, wi);
+            ti.setTag(tp.getId());
+            try {
+                field.set(wi, ti);
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 获取风机信息中的属性
+     */
+    private Map<String, Field> getFields(Class<?> c) {
+        Map<String, Field> fields = new HashMap<>(50);
+        Field[] fs = c.getDeclaredFields();
+        for (Field f : fs) {
+            try {
+                UniformCode uc = f.getAnnotation(UniformCode.class);
+                if (uc == null) {
+                    continue;
+                }
+                String ucv = uc.value();
+                if (ucv != null && !"".equals(ucv) && !fields.containsKey(ucv)) {
+                    f.setAccessible(true);
+                    fields.put(ucv, f);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return fields;
+    }
+
+    /**
+     * 初始化风机信息
+     */
+    private void initWindturbineInfo() {
+        Map<String, ConversionRateWindTurbineInfo> wis = getConversionRateWindTurbineInfoMap();
+        List<WindturbineEntity> wes = windturbineRepository.findByWindPowerStationIdLike("%_FDC");
+        for (WindturbineEntity wb : wes) {
+            ConversionRateWindTurbineInfo wi = new ConversionRateWindTurbineInfo();
+            wi.setId(wb.getId());
+            wi.setModelId(wb.getModelId());
+            wi.setStationId(wb.getWindPowerStationId());
+            wi.setProjectId(wb.getProjectId());
+            wi.setMaintainable(!noMaintainModels.contains(wi.getModelId()));
+            wis.put(wi.getId(), wi);
+        }
+
+        for (ConversionRateWindTurbineInfo cwi : wis.values()) {
+            for (BoostStation bs : boostStations) {
+                boolean b = bs.addWindturbine(cwi);
+                if (b) {
+                    break;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 获取一个类中的UniformCode
+     *
+     * @param c 类型
+     * @return
+     */
+    private String[] getUniformCodes(Class<?> c) {
+        List<String> ls = new ArrayList<>();
+        Field[] fs = c.getDeclaredFields();
+        for (Field f : fs) {
+            try {
+                UniformCode uc = f.getAnnotation(UniformCode.class);
+                if (uc == null) {
+                    continue;
+                }
+                String ucv = uc.value();
+                if (ucv != null && !"".equals(ucv)) {
+                    ls.add(ucv);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        String[] ucs = new String[ls.size()];
+        ls.toArray(ucs);
+        return ucs;
+    }
+
+    /**
+     * 获取风机中的TagInfo,如果没有就创建
+     *
+     * @param field 属性
+     * @param wi    风机信息
+     * @return TagInfo
+     */
+    public TagInfo getTagInfo(Field field, ConversionRateWindTurbineInfo wi) {
+        TagInfo ti = null;
+        try {
+            ti = (TagInfo) field.get(wi);
+            if (ti != null) {
+                return ti;
+            }
+            ti = (TagInfo) field.getType().newInstance();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return ti;
+    }
+}

+ 25 - 0
metrics/src/main/java/com/gyee/gaia/metrics/configuration/CorsConfig.java

@@ -0,0 +1,25 @@
+package com.gyee.gaia.metrics.configuration;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CorsConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        //添加映射路径
+        registry.addMapping("/**")
+                //是否发送Cookie
+                .allowCredentials(true)
+                //设置放行哪些原始域   SpringBoot2.4.4下低版本使用.allowedOrigins("*")
+                .allowedOriginPatterns("*")
+                //放行哪些请求方式
+                .allowedMethods("*") //或者放行全部
+                //放行哪些原始请求头部信息
+                .allowedHeaders("*")
+                //暴露哪些原始请求头部信息
+                .exposedHeaders("*");
+    }
+}

+ 76 - 0
metrics/src/main/java/com/gyee/gaia/metrics/configuration/ThreadPoolConfig.java

@@ -0,0 +1,76 @@
+package com.gyee.gaia.metrics.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池配置
+ *
+ * @author xysn
+ */
+@Configuration
+public class ThreadPoolConfig {
+
+    /**
+     *   默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,
+     *	当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
+     *  当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝
+     */
+
+    /**
+     * 核心线程数(默认线程数)
+     */
+    private static final int corePoolSize = 40;
+    /**
+     * 最大线程数
+     */
+    private static final int maxPoolSize = 100;
+    /**
+     * 允许线程空闲时间(单位:默认为秒)
+     */
+    private static final int keepAliveTime = 60;
+    /**
+     * 缓冲队列大小
+     */
+    private static final int queueCapacity = 300;
+    /**
+     * 允许等待最长时间
+     */
+    private static final int awaitTime = 15;
+    /**
+     * 线程池名前缀
+     */
+    private static final String threadNamePrefix = "Alarm-Thread-";
+
+    private ThreadPoolTaskExecutor executor;
+
+    public ThreadPoolTaskExecutor getExecutor() {
+        if (executor == null) {
+            executor = taskExecutor();
+        }
+
+        return executor;
+    }
+
+
+    @Bean
+    public ThreadPoolTaskExecutor taskExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(corePoolSize);
+        executor.setMaxPoolSize(maxPoolSize);
+        executor.setQueueCapacity(queueCapacity);
+        executor.setKeepAliveSeconds(keepAliveTime);
+        executor.setThreadNamePrefix(threadNamePrefix);
+        executor.setAwaitTerminationSeconds(awaitTime);
+
+        // 线程池对拒绝任务的处理策略
+        // CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        // 初始化
+        executor.initialize();
+        return executor;
+    }
+}

+ 42 - 0
metrics/src/main/java/com/gyee/gaia/metrics/controller/StatsController.kt

@@ -0,0 +1,42 @@
+package com.example.conversionratestats.controller
+
+import com.example.conversionratestats.manager.StatsManager
+import com.gyee.gaia.metrics.modles.Message
+import org.springframework.web.bind.annotation.*
+import javax.annotation.Resource
+
+
+@CrossOrigin()
+@RestController
+@RequestMapping("/api/stats")
+open class StatsController {
+
+    @Resource
+    private lateinit var statsManager: StatsManager
+
+    @GetMapping("")
+    fun getConversionRateStats(
+        @RequestParam(value = "startTs") startTs: Long,
+        @RequestParam(value = "endTs") endTs: Long,
+        @RequestParam(value = "station", required = false) station: String?
+    ): Any {
+
+        return statsManager.getConversionRateStats(startTs, endTs, station)
+    }
+
+    /*@GetMapping("/all")
+    fun getConversionRateStatsAll(
+        @RequestParam(value = "startTs") startTs: Long, @RequestParam(value = "endTs") endTs: Long
+    ): Any {
+
+        return statsManager.getConversionRateStatsAll(startTs, endTs)
+    }*/
+
+    @GetMapping("/all")
+    fun getConversionRateStatsAllBody(
+        @RequestParam(value = "startTs") startTs: Long, @RequestParam(value = "endTs") endTs: Long
+    ): Any {
+        val v = statsManager.getConversionRateStatsAll(startTs, endTs)
+        return Message(v)
+    }
+}

+ 407 - 0
metrics/src/main/java/com/gyee/gaia/metrics/dao/entity/ConversionRateStatsEntity.java

@@ -0,0 +1,407 @@
+package com.gyee.gaia.metrics.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+/**
+ * 状态转换实体类
+ */
+
+@Entity
+@Table(name = "CONVERSIONRATESTATS")
+public class ConversionRateStatsEntity {
+    /**
+     * ID MG01_01-20230508
+     */
+    @Id
+    @Column(name = "ID")
+    private String id;
+    @Column(name = "WINDTURBINEID")
+    private String windturbineId;
+    /**
+     * 场站ID
+     */
+    @Column(name = "STATIONID")
+    private String station;
+    /**
+     * 时间戳
+     */
+    @Column(name = "TIME")
+    private long time;
+
+    /**
+     * 未及时转换次数(包括周期内没有转换或超过20分钟转换)
+     */
+    @Column(name = "COUNTUNCONVERTEDALL")
+    private int countUnconvertedAll;
+    /**
+     * 所有次数(包括转换不及时以及未转换的)
+     */
+    @Column(name = "COUNTALL")
+    private int countAll;
+    /**
+     * 所有操作5分钟内次数
+     */
+    @Column(name = "COUNTALL5")
+    private int countAll5;
+    /**
+     * 所有操作10分钟内次数
+     */
+    @Column(name = "COUNTALL10")
+    private int countAll10;
+    /**
+     * 所有操作15分钟内次数
+     */
+    @Column(name = "COUNTALL15")
+    private int countAll15;
+    /**
+     * 所有操作20分钟内次数
+     */
+    @Column(name = "COUNTALL20")
+    private int countAll20;
+
+
+    /**
+     * 启动不及时次数
+     * COUNTUNCONVERTEDSTART
+     * COUNTUNCONVERTEDSTART
+     */
+    @Column(name = "COUNTUNCONVERTEDSTART")
+    private int countUnconvertedStart;
+    /**
+     * 所有启动次数(包括不及时)
+     */
+    @Column(name = "COUNTSTART")
+    private int countStart;
+    @Column(name = "COUNTSTART5")
+    private int countStart5;
+    @Column(name = "COUNTSTART10")
+    private int countStart10;
+    @Column(name = "COUNTSTART15")
+    private int countStart15;
+    @Column(name = "COUNTSTART20")
+    private int countStart20;
+
+    /**
+     * 所有次数(包括不及时)
+     */
+    @Column(name = "COUNTUNCONVERTEDSTOP")
+    private int countUnconvertedStop;
+    @Column(name = "COUNTSTOP")
+    private int countStop;
+    @Column(name = "COUNTSTOP5")
+    private int countStop5;
+    @Column(name = "COUNTSTOP10")
+    private int countStop10;
+    @Column(name = "COUNTSTOP15")
+    private int countStop15;
+    @Column(name = "COUNTSTOP20")
+    private int countStop20;
+
+    /**
+     * 所有次数(包括不及时)
+     */
+    @Column(name = "COUNTUNCONVERTEDMAINTAIN")
+    private int countUnconvertedMaintain;
+    @Column(name = "COUNTMAINTAIN")
+    private int countMaintain;
+    @Column(name = "COUNTMAINTAIN5")
+    private int countMaintain5;
+    @Column(name = "COUNTMAINTAIN10")
+    private int countMaintain10;
+    @Column(name = "COUNTMAINTAIN15")
+    private int countMaintain15;
+    @Column(name = "COUNTMAINTAIN20")
+    private int countMaintain20;
+
+    /**
+     * 所有次数(包括不及时)
+     */
+    @Column(name = "COUNTUNCONVERTEDUNMAINTAIN")
+    private int countUnconvertedUnMaintain;
+    @Column(name = "COUNTUNMAINTAIN")
+    private int countUnMaintain;
+    @Column(name = "COUNTUNMAINTAIN5")
+    private int countUnMaintain5;
+    @Column(name = "COUNTUNMAINTAIN10")
+    private int countUnMaintain10;
+    @Column(name = "COUNTUNMAINTAIN15")
+    private int countUnMaintain15;
+    @Column(name = "COUNTUNMAINTAIN20")
+    private int countUnMaintain20;
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getWindturbineId() {
+        return windturbineId;
+    }
+
+    public void setWindturbineId(String windturbineId) {
+        this.windturbineId = windturbineId;
+    }
+
+    public String getStation() {
+        return station;
+    }
+
+    public void setStation(String station) {
+        this.station = station;
+    }
+
+    public long getTime() {
+        return time;
+    }
+
+    public void setTime(long time) {
+        this.time = time;
+    }
+
+    public int getCountUnconvertedAll() {
+        return countUnconvertedAll;
+    }
+
+    public void setCountUnconvertedAll(int countUnconvertedAll) {
+        this.countUnconvertedAll = countUnconvertedAll;
+    }
+
+    public int getCountAll() {
+        return countAll;
+    }
+
+    public void setCountAll(int countAll) {
+        this.countAll = countAll;
+    }
+
+    public int getCountAll5() {
+        return countAll5;
+    }
+
+    public void setCountAll5(int countAll5) {
+        this.countAll5 = countAll5;
+    }
+
+    public int getCountAll10() {
+        return countAll10;
+    }
+
+    public void setCountAll10(int countAll10) {
+        this.countAll10 = countAll10;
+    }
+
+    public int getCountAll15() {
+        return countAll15;
+    }
+
+    public void setCountAll15(int countAll15) {
+        this.countAll15 = countAll15;
+    }
+
+    public int getCountAll20() {
+        return countAll20;
+    }
+
+    public void setCountAll20(int countAll20) {
+        this.countAll20 = countAll20;
+    }
+
+    public int getCountUnconvertedStart() {
+        return countUnconvertedStart;
+    }
+
+    public void setCountUnconvertedStart(int countUnconvertedStart) {
+        this.countUnconvertedStart = countUnconvertedStart;
+    }
+
+    public int getCountStart() {
+        return countStart;
+    }
+
+    public void setCountStart(int countStart) {
+        this.countStart = countStart;
+    }
+
+    public int getCountStart5() {
+        return countStart5;
+    }
+
+    public void setCountStart5(int countStart5) {
+        this.countStart5 = countStart5;
+    }
+
+    public int getCountStart10() {
+        return countStart10;
+    }
+
+    public void setCountStart10(int countStart10) {
+        this.countStart10 = countStart10;
+    }
+
+    public int getCountStart15() {
+        return countStart15;
+    }
+
+    public void setCountStart15(int countStart15) {
+        this.countStart15 = countStart15;
+    }
+
+    public int getCountStart20() {
+        return countStart20;
+    }
+
+    public void setCountStart20(int countStart20) {
+        this.countStart20 = countStart20;
+    }
+
+    public int getCountUnconvertedStop() {
+        return countUnconvertedStop;
+    }
+
+    public void setCountUnconvertedStop(int countUnconvertedStop) {
+        this.countUnconvertedStop = countUnconvertedStop;
+    }
+
+    public int getCountStop() {
+        return countStop;
+    }
+
+    public void setCountStop(int countStop) {
+        this.countStop = countStop;
+    }
+
+    public int getCountStop5() {
+        return countStop5;
+    }
+
+    public void setCountStop5(int countStop5) {
+        this.countStop5 = countStop5;
+    }
+
+    public int getCountStop10() {
+        return countStop10;
+    }
+
+    public void setCountStop10(int countStop10) {
+        this.countStop10 = countStop10;
+    }
+
+    public int getCountStop15() {
+        return countStop15;
+    }
+
+    public void setCountStop15(int countStop15) {
+        this.countStop15 = countStop15;
+    }
+
+    public int getCountStop20() {
+        return countStop20;
+    }
+
+    public void setCountStop20(int countStop20) {
+        this.countStop20 = countStop20;
+    }
+
+    public int getCountUnconvertedMaintain() {
+        return countUnconvertedMaintain;
+    }
+
+    public void setCountUnconvertedMaintain(int countUnconvertedMaintain) {
+        this.countUnconvertedMaintain = countUnconvertedMaintain;
+    }
+
+    public int getCountMaintain() {
+        return countMaintain;
+    }
+
+    public void setCountMaintain(int countMaintain) {
+        this.countMaintain = countMaintain;
+    }
+
+    public int getCountMaintain5() {
+        return countMaintain5;
+    }
+
+    public void setCountMaintain5(int countMaintain5) {
+        this.countMaintain5 = countMaintain5;
+    }
+
+    public int getCountMaintain10() {
+        return countMaintain10;
+    }
+
+    public void setCountMaintain10(int countMaintain10) {
+        this.countMaintain10 = countMaintain10;
+    }
+
+    public int getCountMaintain15() {
+        return countMaintain15;
+    }
+
+    public void setCountMaintain15(int countMaintain15) {
+        this.countMaintain15 = countMaintain15;
+    }
+
+    public int getCountMaintain20() {
+        return countMaintain20;
+    }
+
+    public void setCountMaintain20(int countMaintain20) {
+        this.countMaintain20 = countMaintain20;
+    }
+
+    public int getCountUnconvertedUnMaintain() {
+        return countUnconvertedUnMaintain;
+    }
+
+    public void setCountUnconvertedUnMaintain(int countUnconvertedUnMaintain) {
+        this.countUnconvertedUnMaintain = countUnconvertedUnMaintain;
+    }
+
+    public int getCountUnMaintain() {
+        return countUnMaintain;
+    }
+
+    public void setCountUnMaintain(int countUnMaintain) {
+        this.countUnMaintain = countUnMaintain;
+    }
+
+    public int getCountUnMaintain5() {
+        return countUnMaintain5;
+    }
+
+    public void setCountUnMaintain5(int countUnMaintain5) {
+        this.countUnMaintain5 = countUnMaintain5;
+    }
+
+    public int getCountUnMaintain10() {
+        return countUnMaintain10;
+    }
+
+    public void setCountUnMaintain10(int countUnMaintain10) {
+        this.countUnMaintain10 = countUnMaintain10;
+    }
+
+    public int getCountUnMaintain15() {
+        return countUnMaintain15;
+    }
+
+    public void setCountUnMaintain15(int countUnMaintain15) {
+        this.countUnMaintain15 = countUnMaintain15;
+    }
+
+    public int getCountUnMaintain20() {
+        return countUnMaintain20;
+    }
+
+    public void setCountUnMaintain20(int countUnMaintain20) {
+        this.countUnMaintain20 = countUnMaintain20;
+    }
+}

+ 78 - 0
metrics/src/main/java/com/gyee/gaia/metrics/dao/entity/TsPointEntity.java

@@ -0,0 +1,78 @@
+package com.gyee.gaia.metrics.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "TESTINGPOINT")
+public class TsPointEntity {
+    /**
+     * 主键ID
+     */
+    @Id
+    //@GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "ID")
+    private String id;
+    /**
+     * 设备类型
+     */
+    @Column(name = "DEVICETYPE")
+    private String thingType;
+    /**
+     * 设备Id
+     */
+    @Column(name = "DEVICEID")
+    private String thingId;
+    /**
+     * 统一编码
+     */
+    @Column(name = "UNIFORMCODE")
+    private String uniformCode;
+    /**
+     * 点类型
+     */
+    @Column(name = "DATATYPE")
+    private String dataType;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getThingType() {
+        return thingType;
+    }
+
+    public void setThingType(String thingType) {
+        this.thingType = thingType;
+    }
+
+    public String getThingId() {
+        return thingId;
+    }
+
+    public void setThingId(String thingId) {
+        this.thingId = thingId;
+    }
+
+    public String getUniformCode() {
+        return uniformCode;
+    }
+
+    public void setUniformCode(String uniformCode) {
+        this.uniformCode = uniformCode;
+    }
+
+    public String getDataType() {
+        return dataType;
+    }
+
+    public void setDataType(String dataType) {
+        this.dataType = dataType;
+    }
+}

+ 184 - 0
metrics/src/main/java/com/gyee/gaia/metrics/dao/entity/WindturbineEntity.java

@@ -0,0 +1,184 @@
+package com.gyee.gaia.metrics.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+
+
+@Entity
+@Table(name = "WINDTURBINE")
+public class WindturbineEntity {
+    /**
+     * 主键ID
+     */
+    @Id
+    /*@GeneratedValue(strategy = GenerationType.IDENTITY)*/
+    @Column(name = "ID")
+    private String id;
+    /**
+     * 编码
+     */
+    @Column(name = "CODE")
+    private String code;
+    /**
+     * 风场id
+     */
+    @Column(name = "WINDPOWERSTATIONID")
+    private String windPowerStationId;
+    /**
+     * 经度
+     */
+    @Column(name = "LONGITUDE")
+    private Double longitude;
+    /**
+     * 纬度
+     */
+    @Column(name = "LATITUDE")
+    private Double latitude;
+    /**
+     * 模型id
+     */
+    @Column(name = "MODELID")
+    private String modelId;
+    /**
+     * 状态
+     */
+    @Column(name = "STATUS")
+    private String status;
+    /**
+     * 项目id
+     */
+    @Column(name = "PROJECTID")
+    private String projectId;
+    /**
+     * 线路id
+     */
+    @Column(name = "LINEID")
+    private String lineId;
+    /**
+     * 首次并网的时间
+     */
+    @Column(name = "FIRSTINTEGRATEDTIME")
+    private Date firstIntegratedTime;
+    /**
+     * app注册ID
+     */
+    @Column(name = "PHOTO")
+    private String photo;
+    /**
+     * app注册ID
+     */
+    @Column(name = "NAME")
+    private String name;
+    /**
+     * app注册ID
+     */
+    @Column(name = "STANDARDID")
+    private String standardId;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getWindPowerStationId() {
+        return windPowerStationId;
+    }
+
+    public void setWindPowerStationId(String windPowerStationId) {
+        this.windPowerStationId = windPowerStationId;
+    }
+
+    public Double getLongitude() {
+        return longitude;
+    }
+
+    public void setLongitude(Double longitude) {
+        this.longitude = longitude;
+    }
+
+    public Double getLatitude() {
+        return latitude;
+    }
+
+    public void setLatitude(Double latitude) {
+        this.latitude = latitude;
+    }
+
+    public String getModelId() {
+        return modelId;
+    }
+
+    public void setModelId(String modelId) {
+        this.modelId = modelId;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getProjectId() {
+        return projectId;
+    }
+
+    public void setProjectId(String projectId) {
+        this.projectId = projectId;
+    }
+
+    public String getLineId() {
+        return lineId;
+    }
+
+    public void setLineId(String lineId) {
+        this.lineId = lineId;
+    }
+
+    public Date getFirstIntegratedTime() {
+        return firstIntegratedTime;
+    }
+
+    public void setFirstIntegratedTime(Date firstIntegratedTime) {
+        this.firstIntegratedTime = firstIntegratedTime;
+    }
+
+    public String getPhoto() {
+        return photo;
+    }
+
+    public void setPhoto(String photo) {
+        this.photo = photo;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getStandardId() {
+        return standardId;
+    }
+
+    public void setStandardId(String standardId) {
+        this.standardId = standardId;
+    }
+}

+ 17 - 0
metrics/src/main/java/com/gyee/gaia/metrics/dao/repsoitory/ConversionRateStatsRepository.java

@@ -0,0 +1,17 @@
+package com.gyee.gaia.metrics.dao.repsoitory;
+
+import com.gyee.gaia.metrics.dao.entity.ConversionRateStatsEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface ConversionRateStatsRepository extends JpaRepository<ConversionRateStatsEntity, String>, JpaSpecificationExecutor {
+    List<ConversionRateStatsEntity> findAllByTimeGreaterThanEqual(long ts);
+
+    List<ConversionRateStatsEntity> findAllByTimeGreaterThanEqualAndTimeLessThanEqualAndStationEquals(long startTs, long endTs, String station);
+
+    List<ConversionRateStatsEntity> findAllByTimeGreaterThanEqualAndTimeLessThanEqual(long startTs, long endTs);
+}

+ 32 - 0
metrics/src/main/java/com/gyee/gaia/metrics/dao/repsoitory/TsPointRepository.java

@@ -0,0 +1,32 @@
+package com.gyee.gaia.metrics.dao.repsoitory;
+
+import com.gyee.gaia.metrics.dao.entity.TsPointEntity;
+import org.springframework.cache.annotation.CacheConfig;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+
+@Repository
+public interface TsPointRepository extends JpaRepository<TsPointEntity, String>, JpaSpecificationExecutor {
+
+    @Cacheable
+    List<TsPointEntity> findAllByIdIn(String... ids);
+
+    @Cacheable
+    List<TsPointEntity> findAllByThingTypeAndThingIdAndUniformCodeIn(String thingType, String thingId, String... uniformCode);
+
+    @Cacheable
+    List<TsPointEntity> findAllByThingType(String thingType);
+
+    @Cacheable
+    List<TsPointEntity> findAllByThingTypeAndUniformCodeAndThingIdIn(String thingType, String uniformCode, String... thingIds);
+
+    List<TsPointEntity> findAllByThingTypeAndThingId(String thingType, String thingId);
+
+    @Cacheable
+    List<TsPointEntity> findAllByThingTypeAndUniformCodeIn(String thingType, String[] uCodes);
+}

+ 29 - 0
metrics/src/main/java/com/gyee/gaia/metrics/dao/repsoitory/WindturbineRepository.java

@@ -0,0 +1,29 @@
+package com.gyee.gaia.metrics.dao.repsoitory;
+
+import com.gyee.gaia.metrics.dao.entity.WindturbineEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+
+@Repository
+public interface WindturbineRepository extends JpaRepository<WindturbineEntity, String>, JpaSpecificationExecutor {
+
+    /*  ModelEntity findFirstByUserName(String userName);*/
+    List<WindturbineEntity> findByWindPowerStationId(String stationId);
+
+    List<WindturbineEntity> findByLineId(String lineId);
+
+    List<WindturbineEntity> findByProjectId(String projectId);
+
+    List<WindturbineEntity> findByModelId(String modelId);
+
+    List<WindturbineEntity> findByWindPowerStationIdAndModelId(String stationId, String modelId);
+
+    List<WindturbineEntity> findByWindPowerStationIdLike(String likeStation);
+
+    List<WindturbineEntity> findByIdLike(String likeId);
+
+}

+ 32 - 0
metrics/src/main/java/com/gyee/gaia/metrics/feigns/FeignsBuilder.java

@@ -0,0 +1,32 @@
+package com.gyee.gaia.metrics.feigns;
+
+import feign.Feign;
+import feign.Request;
+import feign.Retryer;
+import feign.jackson.JacksonDecoder;
+import feign.jackson.JacksonEncoder;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author xysn
+ */
+@Configuration
+public class FeignsBuilder {
+    /**
+     * 数据适配器url
+     */
+    @Value("${urls.adapter: http://127.0.0.1:8011}")
+    private String adapterUrl;
+
+    @Bean
+    public IDataAdapter dataAdapter() {
+        return Feign.builder()
+                .encoder(new JacksonEncoder())
+                .decoder(new JacksonDecoder())
+                .options(new Request.Options(1000, 100000))
+                .retryer(new Retryer.Default(5000, 5000, 3))
+                .target(IDataAdapter.class, adapterUrl);
+    }
+}

+ 78 - 0
metrics/src/main/java/com/gyee/gaia/metrics/feigns/IDataAdapter.java

@@ -0,0 +1,78 @@
+package com.gyee.gaia.metrics.feigns;
+
+import com.gyee.gaia.metrics.modles.PointData;
+import feign.Param;
+import feign.RequestLine;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * DataAdapter数据请求
+ */
+public interface IDataAdapter {
+    /**
+     * 获取实时数据
+     *
+     * @param keys 以逗号(,)隔开的一个或多个标签
+     * @return 实时数据
+     */
+    @RequestLine("GET /ts/latest?keys={keys}")
+    Map<String, PointData> getValueByKeys(@Param(value = "keys") String keys);
+
+
+    /**
+     * 获取等间隔历史数据
+     *
+     * @param tagName  测点名称
+     * @param startTs  开始时间
+     * @param endTs    结束时间
+     * @param interval 数据间隔
+     * @return 历史数据
+     */
+    @RequestLine("GET /ts/history/snap?tagName={tagName}&startTs={startTs}&endTs={endTs}&interval={interval}")
+    List<PointData> getSnapValuesByKey(@Param(value = "tagName") String tagName, @Param(value = "startTs") long startTs,
+                                       @Param(value = "endTs") long endTs, @Param(value = "interval") int interval);
+
+
+    /**
+     * 获取等间隔历史数据
+     *
+     * @param thingId   设备ID
+     * @param thingType 设备类型
+     * @param startTs   开始时间
+     * @param endTs     结束时间
+     * @param interval  时间间隔
+     * @return 历史数据
+     */
+    @RequestLine("GET /ts/history/snap?thingId={thingId}&uniformCode={uniformCode}&thingType={thingType}&startTs={startTs}&endTs={endTs}&interval={interval}")
+    List<PointData> getSnapValuesByUniformCode(@Param(value = "thingId") String thingId, @Param(value = "uniformCode") String uniformCode,
+                                               @Param(value = "thingType") String thingType, @Param(value = "startTs") long startTs,
+                                               @Param(value = "endTs") long endTs, @Param(value = "interval") int interval);
+
+
+    /**
+     * 获取历史数据
+     *
+     * @param thingId   设备ID
+     * @param thingType 设备类型
+     * @param startTs   开始时间
+     * @param endTs     结束时间
+     * @return 历史数据
+     */
+    @RequestLine("GET /ts/history/raw?thingId={thingId}&uniformCode={uniformCode}&thingType={thingType}&startTs={startTs}&endTs={endTs}")
+    List<PointData> getRawValuesByUniformCode(@Param(value = "thingId") String thingId, @Param(value = "uniformCode") String uniformCode,
+                                              @Param(value = "thingType") String thingType, @Param(value = "startTs") long startTs,
+                                              @Param(value = "endTs") long endTs);
+
+    /**
+     * 获取历史数据
+     *
+     * @param tagName 测点名称
+     * @param startTs 开始时间
+     * @param endTs   结束时间
+     * @return 历史数据
+     */
+    @RequestLine("GET /ts/history/raw?tagName={tagName}&startTs={startTs}&endTs={endTs}")
+    List<PointData> getRawValuesByKey(@Param(value = "tagName") String tagName, @Param(value = "startTs") long startTs, @Param(value = "endTs") long endTs);
+}

+ 110 - 0
metrics/src/main/java/com/gyee/gaia/metrics/manager/FileManager.java

@@ -0,0 +1,110 @@
+package com.gyee.gaia.metrics.manager;
+
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import org.springframework.stereotype.Component;
+
+import java.io.*;
+import java.lang.reflect.Type;
+
+/**
+ * 配置文件读取
+ *
+ * @author xysn
+ */
+@Component
+public class FileManager {
+    /**
+     * 从json格式文件中获取对象
+     *
+     * @param path 文件路径
+     * @param type 对象类型
+     * @param <T>  泛型
+     * @return 返回对象
+     */
+    public <T> T getFromFile(String path, Type type) {
+        String str = getStringFormFile(path);
+        return JSON.parseObject(str, type);
+    }
+
+    /**
+     * 从json格式文件中获取对象
+     *
+     * @param path 文件路径
+     * @param type 对象类型
+     * @param <T>  泛型
+     * @return 返回对象
+     */
+    public <T> T getFromFile(String path, TypeReference<T> type) {
+        String str = getStringFormFile(path);
+        return JSON.parseObject(str, type);
+    }
+
+    /**
+     * 从文件中获取字符串
+     *
+     * @param path 路径
+     * @return 字符串
+     */
+    public String getStringFormFile(String path) {
+        BufferedReader bufferedReader = null;
+        File file = new File(path);
+        if (!file.exists()) {
+            return "";
+        }
+        try {
+            bufferedReader = new BufferedReader(new FileReader(file));
+            StringBuilder sb = new StringBuilder();
+            String s = null;
+            while ((s = bufferedReader.readLine()) != null) {
+                sb.append(s);
+            }
+            return sb.toString();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                assert bufferedReader != null;
+                bufferedReader.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 保存对象到文件中
+     *
+     * @param path 文件路径
+     * @param obj  要保存的对象
+     * @return 是否保存成功
+     */
+    public boolean saveObjectToFile(String path, Object obj) {
+        BufferedWriter bufferedWriter = null;
+        try {
+            File file = new File(path);
+            bufferedWriter = new BufferedWriter(new FileWriter(file));
+            String str = "";
+            if (obj instanceof String) {
+                str = obj.toString();
+            } else {
+                str = JSON.toJSONString(obj);
+            }
+            bufferedWriter.write(str);
+            bufferedWriter.flush();
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                assert bufferedWriter != null;
+                bufferedWriter.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return false;
+    }
+}

+ 98 - 0
metrics/src/main/java/com/gyee/gaia/metrics/manager/StatsManager.kt

@@ -0,0 +1,98 @@
+package com.example.conversionratestats.manager
+
+import com.gyee.gaia.metrics.modles.Station
+import com.gyee.gaia.metrics.modles.StatsInfo
+import com.gyee.gaia.metrics.cache.InfoCache
+import com.gyee.gaia.metrics.dao.entity.ConversionRateStatsEntity
+import com.gyee.gaia.metrics.dao.repsoitory.ConversionRateStatsRepository
+import org.springframework.stereotype.Component
+import javax.annotation.Resource
+
+@Component
+open class StatsManager(infoCache: InfoCache) {
+
+    @Resource
+    private lateinit var conversionRateStatsRepository: ConversionRateStatsRepository
+
+
+    private val stationInfo = HashMap<String, Station>()
+
+    init {
+        initStationInfo(infoCache)
+    }
+
+    fun getConversionRateStats(startTs: Long, endTs: Long, station: String?): Any {
+        val ls: MutableList<ConversionRateStatsEntity>?
+        ls = if (station.isNullOrEmpty()) {
+            conversionRateStatsRepository.findAllByTimeGreaterThanEqualAndTimeLessThanEqual(startTs, endTs)
+        } else {
+            conversionRateStatsRepository.findAllByTimeGreaterThanEqualAndTimeLessThanEqualAndStationEquals(
+                startTs,
+                endTs,
+                station
+            )
+        }
+        val si = StatsInfo()
+        for (v in ls) {
+            si.countAll += v.countAll
+            si.countAll5 += v.countAll5
+            si.countAll10 += (v.countAll5 + v.countAll10)
+            si.countAll15 += (v.countAll5 + v.countAll10 + v.countAll15)
+            si.countAll20 += (v.countAll5 + v.countAll10 + v.countAll15 + v.countAll20)
+            si.countUnconvertedAll += v.countUnconvertedAll
+        }
+        return si
+    }
+
+    /**
+     * 获取所有数据
+     */
+    fun getConversionRateStatsAll(startTs: Long, endTs: Long): Any {
+        val vs = conversionRateStatsRepository.findAllByTimeGreaterThanEqualAndTimeLessThanEqual(startTs, endTs)
+        val mp = HashMap<String, StatsInfo>()
+
+        val si = StatsInfo()
+        si.stationId = "all"
+        si.stationName = "合计"
+        for (v in vs) {
+            initStatsInfo(si, v)
+            if (!mp.containsKey(v.station)) {
+                val ns = StatsInfo()
+                ns.stationId = v.station
+                ns.stationName = stationInfo[ns.stationId]?.name ?: ""
+                ns.index = stationInfo[ns.stationId]?.index ?: 0
+                mp[v.station] = ns
+            }
+            initStatsInfo(mp[v.station], v)
+        }
+        si.index = mp.size + 10
+        mp[si.stationId] = si
+        val ls = mp.values
+        return ls.sortedBy { it.index }
+    }
+
+    private fun initStatsInfo(si: StatsInfo?, v: ConversionRateStatsEntity) {
+        if (si == null) {
+            return
+        }
+        si.countAll += v.countAll
+        si.countAll5 += v.countAll5
+        si.countAll10 += (v.countAll5 + v.countAll10)
+        si.countAll15 += (v.countAll5 + v.countAll10 + v.countAll15)
+        si.countAll20 += (v.countAll5 + v.countAll10 + v.countAll15 + v.countAll20)
+        si.countUnconvertedAll += v.countUnconvertedAll
+    }
+
+    private fun initStationInfo(infoCache: InfoCache) {
+        var index = 0
+        for (v in infoCache.boostStations) {
+            if (!stationInfo.containsKey(v.stationId)) {
+                val s = Station()
+                s.index = index++
+                s.name = v.stationName
+                s.id = v.stationId
+                stationInfo[s.id] = s
+            }
+        }
+    }
+}

+ 115 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/BoostStation.java

@@ -0,0 +1,115 @@
+package com.gyee.gaia.metrics.modles;
+
+import java.util.*;
+
+/**
+ * 升压站信息
+ *
+ * @author xysn
+ */
+public class BoostStation {
+    /**
+     * 场站ID
+     */
+    private String stationId;
+    /**
+     * 场站名称
+     */
+    private String stationName;
+    /**
+     * 升压站名称
+     */
+    private String id;
+    private TagInfo electricityRestrictions;
+    /**
+     * 升压站所拥有的期次
+     */
+    private HashSet<String> projects;
+    private Map<String, Project> projectMap = new HashMap<>();
+    /**
+     * 升压站所拥有的风机
+     */
+    private List<ConversionRateWindTurbineInfo> windturbines = new ArrayList<>();
+
+    public String getStationId() {
+        return stationId;
+    }
+
+    public void setStationId(String stationId) {
+        this.stationId = stationId;
+    }
+
+    public String getStationName() {
+        return stationName;
+    }
+
+    public void setStationName(String stationName) {
+        this.stationName = stationName;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public TagInfo getElectricityRestrictions() {
+        return electricityRestrictions;
+    }
+
+    public void setElectricityRestrictions(TagInfo electricityRestrictions) {
+        this.electricityRestrictions = electricityRestrictions;
+    }
+
+    public void setElectricityRestrictionsTag(String tag) {
+        if (this.electricityRestrictions == null) {
+            this.electricityRestrictions = new TagInfo();
+        }
+        this.electricityRestrictions.setTag(tag);
+    }
+
+    public HashSet<String> getProjects() {
+        if (projects == null) {
+            projects = new HashSet<>();
+        }
+        return projects;
+    }
+
+    public void setProjects(HashSet<String> projects) {
+        this.projects = projects;
+    }
+
+    public List<ConversionRateWindTurbineInfo> getWindturbines() {
+        return windturbines;
+    }
+
+    public Map<String, Project> getProjectMap() {
+        return projectMap;
+    }
+
+    public void setWindturbines(List<ConversionRateWindTurbineInfo> windturbines) {
+        this.windturbines = windturbines;
+    }
+
+    /**
+     * 添加风机
+     *
+     * @param wi 风机信息
+     * @return 是否添加成功
+     */
+    public boolean addWindturbine(ConversionRateWindTurbineInfo wi) {
+        if (!getProjects().contains(wi.getProjectId())) {
+            return false;
+        }
+        this.windturbines.add(wi);
+        if (!projectMap.containsKey(wi.getProjectId())) {
+            Project p = new Project();
+            p.setId(wi.getProjectId());
+            projectMap.put(p.getId(), p);
+        }
+        projectMap.get(wi.getProjectId()).getWindturbines().add(wi);
+        return true;
+    }
+}

+ 220 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/ConversionRateWindTurbineInfo.java

@@ -0,0 +1,220 @@
+package com.gyee.gaia.metrics.modles;
+
+import com.gyee.gaia.metrics.modles.enums.Conditions;
+import com.gyee.gaia.metrics.targets.UniformCode;
+
+/**
+ * 风机信息
+ */
+public class ConversionRateWindTurbineInfo {
+    /**
+     * ID
+     */
+    private String id;
+    /**
+     * 型号
+     */
+    private String modelId;
+    /**
+     * 场站信息
+     */
+    private String stationId;
+    /**
+     * 期次
+     */
+    private String projectId;
+    /**
+     * 状态
+     */
+    @UniformCode("FJZT8")
+    private TagInfo status;
+
+    /**
+     * 5分钟平均风速
+     */
+    @UniformCode("YDPJFS5M")
+    private TagInfo averageWindSpeed5;
+
+    /**
+     * 是否有维护指令
+     */
+    private boolean maintainable;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getModelId() {
+        return modelId;
+    }
+
+    public void setModelId(String modelId) {
+        this.modelId = modelId;
+    }
+
+    public String getStationId() {
+        return stationId;
+    }
+
+    public void setStationId(String stationId) {
+        this.stationId = stationId;
+    }
+
+    public String getProjectId() {
+        return projectId;
+    }
+
+    public void setProjectId(String projectId) {
+        this.projectId = projectId;
+    }
+
+    public TagInfo getStatus() {
+        if (status == null) {
+            status = new TagInfo();
+        }
+        return status;
+    }
+
+    public void setStatus(TagInfo status) {
+        this.status = status;
+    }
+
+    public TagInfo getAverageWindSpeed5() {
+        if (averageWindSpeed5 == null) {
+            averageWindSpeed5 = new TagInfo();
+        }
+        return averageWindSpeed5;
+    }
+
+    public void setAverageWindSpeed5(TagInfo averageWindSpeed5) {
+        this.averageWindSpeed5 = averageWindSpeed5;
+    }
+
+    public boolean isMaintainable() {
+        return maintainable;
+    }
+
+    public void setMaintainable(boolean maintainable) {
+        this.maintainable = maintainable;
+    }
+
+    /**
+     * 当前状态
+     */
+    private double currentStatus;
+    /**
+     * 上次满足条件时间点
+     */
+    private long ts;
+    /**
+     * 当前满足的条件
+     */
+    private Conditions currentConditions = Conditions.Other;
+
+    /**
+     * 刷新数据并且检测状态是否改变
+     *
+     * @return 状态改变时间区间()
+     */
+    public StatusChangedInfo refreshAndGetIsStatusChanged(long t, double electricityRestrictions, float maintainableRatio) {
+        // 最新状态
+        double st = getStatus().getValue();
+        if (currentStatus == 0) {
+            currentStatus = st;
+            return null;
+        }
+
+        StatusChangedInfo sci = null;
+
+        // 最新环境类型
+        Conditions c = getConditions(st, averageWindSpeed5.getValue(), electricityRestrictions);
+        // 环境类型改变
+        if (c != currentConditions) {
+            sci = getStatusChangedInfoByCurrentConditions(t);
+            if (st == currentStatus) {
+                this.ts = t;
+            }
+        }
+        // 状态改变
+        if (st != currentStatus) {
+            sci = getStatusChangedInfo(t);
+            this.ts = t;
+        }
+        currentStatus = st;
+        currentConditions = c;
+        if (sci != null) {
+            if (sci.getConditions() == Conditions.Maintain || sci.getConditions() == Conditions.UnMaintain) {
+                if (!this.maintainable || maintainableRatio < 0.3) {
+                    return null;
+                }
+            }
+        }
+        return sci;
+    }
+
+    /**
+     * 根据当前环境类型获取统计信息
+     *
+     * @param t 当前时间
+     * @return 统计信息
+     */
+    private StatusChangedInfo getStatusChangedInfoByCurrentConditions(long t) {
+        long ts = t - this.ts;
+        if (ts >= 20 * 60 * 1000) {
+            return new StatusChangedInfo(currentConditions, 30);
+        }
+        return null;
+    }
+
+    private Conditions getConditions(double st, double speed, double electricityRestrictions) {
+        if (electricityRestrictions >= 1) {
+            return Conditions.Other;
+        }
+        // 待机
+        if (st == 2) {
+            if (speed >= 3) {
+                return Conditions.Start;
+            } else if (speed < 2.75) {
+                return Conditions.Maintain;
+            }
+        }
+        // 并网
+        else if (st == 4) {
+            if (averageWindSpeed5.getValue() < 3) {
+                return Conditions.Stop;
+            }
+        }
+        // 维护
+        else if (st == 6) {
+            if (averageWindSpeed5.getValue() >= 3) {
+                return Conditions.UnMaintain;
+            }
+        }
+
+        // 其他状态
+        return Conditions.Other;
+    }
+
+    private void setCurrentConditions(Conditions conditions, long t) {
+        if (this.currentConditions != conditions) {
+            this.currentConditions = conditions;
+            this.ts = t;
+        }
+    }
+
+    /**
+     * 根据当前状态获取统计信息
+     *
+     * @param t 当前时间
+     * @return 统计信息
+     */
+    private StatusChangedInfo getStatusChangedInfo(long t) {
+        long ts = t - this.ts;
+        long nt = ts / 60000 - 15;
+        return new StatusChangedInfo(currentConditions, (int) nt);
+    }
+}

+ 88 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/DateTimeHelper.kt

@@ -0,0 +1,88 @@
+package com.gyee.gaia.metrics.modles
+
+import java.text.SimpleDateFormat
+import java.time.Instant
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.ZoneOffset
+import java.time.format.DateTimeFormatter
+import java.util.Date
+
+/**
+ * 获取Date
+ */
+fun LocalDateTime.toDate(): Date {
+    val time = toInstant(ZoneOffset.of("+8")).toEpochMilli()
+    return Date(time)
+}
+
+/**
+ * 获取时间戳
+ */
+fun LocalDateTime.getTimeMillis(): Long {
+    return toInstant(ZoneOffset.of("+8")).toEpochMilli()
+}
+
+/**
+ * 字符串转为时间戳
+ */
+fun String.toTimeMillis(pattern: String = "yyyy-MM-dd HH:mm:ss"): Long {
+    val sdf = SimpleDateFormat(pattern)
+    return sdf.parse(this).time
+}
+
+/**
+ * 获取Date
+ */
+fun LocalDate.toDate(): Date {
+    val time = atStartOfDay().toInstant(ZoneOffset.of("+8")).toEpochMilli()
+    return Date(time)
+}
+
+/**
+ * 获取格式化字符串
+ */
+fun LocalDate.toFormatterString(pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
+    val sdf = DateTimeFormatter.ofPattern(pattern)
+    return sdf.format(this)
+}
+
+/**
+ * 获取格式化字符串
+ */
+fun LocalDateTime.toFormatterString(pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
+    val sdf = DateTimeFormatter.ofPattern(pattern)
+    return sdf.format(this)
+}
+
+/**
+ * 获取格式化字符串
+ */
+fun Date.toFormatterString(pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
+    val sdf = SimpleDateFormat(pattern)
+    return sdf.format(this)
+}
+
+/**
+ * 获取格式化字符串
+ */
+fun Long.toFormatterString(pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
+    val date = Date(this)
+    val sdf = SimpleDateFormat(pattern)
+    return sdf.format(date)
+}
+
+/**
+ * 转日期
+ */
+fun Long.toLocalDate(): LocalDate {
+    return Instant.ofEpochMilli(this).atZone(ZoneOffset.ofHours(8)).toLocalDate()
+}
+
+/**
+ * 获取格式化字符串
+ */
+fun String.toDate(pattern: String = "yyyy-MM-dd HH:mm:ss"): Date {
+    val sdf = SimpleDateFormat(pattern)
+    return sdf.parse(this)
+}

+ 13 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/Message.kt

@@ -0,0 +1,13 @@
+package com.gyee.gaia.metrics.modles
+
+class Message(data: Any) {
+    var code = 200
+    var count = 0
+    var success = true
+    var message = "成功"
+    var data: Any
+
+    init {
+        this.data = data
+    }
+}

+ 63 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/PointData.java

@@ -0,0 +1,63 @@
+package com.gyee.gaia.metrics.modles;
+
+/**
+ * 测点数据
+ */
+public class PointData {
+
+    /**
+     * 时间戳
+     */
+    private long ts;
+    /**
+     * 数据
+     */
+    private double doubleValue;
+    /**
+     * boolean 数据
+     */
+    private boolean booleanValue;
+    /**
+     * 状态
+     */
+    private int status;
+
+    public long getTs() {
+        return ts;
+    }
+
+    public void setTs(long ts) {
+        this.ts = ts;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+    public void setStatus(int status) {
+        this.status = status;
+    }
+
+    public double getValue() {
+        if (booleanValue) {
+            return 1;
+        }
+        return doubleValue;
+    }
+
+    public double getDoubleValue() {
+        return doubleValue;
+    }
+
+    public void setDoubleValue(double doubleValue) {
+        this.doubleValue = doubleValue;
+    }
+
+    public boolean getBooleanValue() {
+        return booleanValue;
+    }
+
+    public void setBooleanValue(boolean booleanValue) {
+        this.booleanValue = booleanValue;
+    }
+}

+ 32 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/Project.kt

@@ -0,0 +1,32 @@
+package com.gyee.gaia.metrics.modles
+
+/**
+ * 期次信息
+ */
+class Project {
+    /**
+     * ID
+     */
+    var id = ""
+
+    /**
+     * 风机
+     */
+    var windturbines = ArrayList<ConversionRateWindTurbineInfo>()
+
+    /**
+     * 可打维护风机比例
+     */
+    var maintainableRatio = 0.0F
+
+    /**
+     * 刷新可打维护分级比例
+     */
+    fun refreshMaintainableRatio() {
+        if (windturbines.size == 0) {
+            this.maintainableRatio = 0.0F
+        }
+        val count = windturbines.filter { it.averageWindSpeed5.value < 2.75 }.size
+        maintainableRatio = count.toFloat() / windturbines.size
+    }
+}

+ 21 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/Station.kt

@@ -0,0 +1,21 @@
+package com.gyee.gaia.metrics.modles
+
+/**
+ * 场站信息
+ */
+class Station {
+    /**
+     * 顺序
+     */
+    var index = 0
+
+    /**
+     * 场站名称
+     */
+    var name = ""
+
+    /**
+     * 场站ID
+     */
+    var id = ""
+}

+ 83 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/StatsInfo.kt

@@ -0,0 +1,83 @@
+package com.gyee.gaia.metrics.modles
+
+/**
+ * 统计信息
+ */
+class StatsInfo {
+    /**
+     * 序号
+     */
+    var index = 0
+
+    /**
+     * 场站ID
+     */
+    var stationId = ""
+
+    /**
+     * 场站名称
+     */
+    var stationName = ""
+
+    /**
+     * 未及时转换次数(包括周期内没有转换或超过20分钟转换)
+     */
+    var countUnconvertedAll = 0
+
+    /**
+     * 所有次数(包括转换不及时以及未转换的)
+     */
+    var countAll = 0
+
+    /**
+     * 所有操作5分钟内次数
+     */
+    var countAll5 = 0
+
+    /**
+     * 所有操作5分钟内比例
+     */
+    val rateAll5: Float
+        get() {
+            return countAll5.toFloat() / countAll
+        }
+
+    /**
+     * 所有操作10分钟内次数
+     */
+    var countAll10 = 0
+
+    /**
+     * 所有操作10分钟内比例
+     */
+    val rateAll10: Float
+        get() {
+            return countAll10.toFloat() / countAll
+        }
+
+    /**
+     * 所有操作15分钟内次数
+     */
+    var countAll15 = 0
+
+    /**
+     * 所有操作15分钟内比例
+     */
+    val rateAll15: Float
+        get() {
+            return countAll15.toFloat() / countAll
+        }
+
+    /**
+     * 所有操作20分钟内次数
+     */
+    var countAll20 = 0
+
+    /**
+     * 所有操作20分钟内比例
+     */
+    val rateAl20: Float
+        get() {
+            return countAll20.toFloat() / countAll
+        }
+}

+ 15 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/StatusChangedInfo.kt

@@ -0,0 +1,15 @@
+package com.gyee.gaia.metrics.modles
+
+import com.gyee.gaia.metrics.modles.enums.Conditions
+
+/**
+ * 状态改变信息
+ */
+class StatusChangedInfo(conditions: Conditions, timeInterval: Int) {
+    val conditions: Conditions = conditions
+
+    /**
+     * 时间范围,如果大于20说明这次状态转换不及时
+     */
+    val timeInterval: Int = timeInterval
+}

+ 10 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/TagInfo.kt

@@ -0,0 +1,10 @@
+package com.gyee.gaia.metrics.modles
+
+/**
+ * 测点信息
+ */
+class TagInfo {
+    var tag: String = ""
+    var value: Double = 0.0
+    var timestamp: Long = 0
+}

+ 31 - 0
metrics/src/main/java/com/gyee/gaia/metrics/modles/enums/Conditions.java

@@ -0,0 +1,31 @@
+package com.gyee.gaia.metrics.modles.enums;
+
+/**
+ * 环境条件
+ */
+public enum Conditions {
+    /**
+     * 都不满足
+     */
+    Other,
+    /**
+     * 启动条件
+     */
+    Start,
+    /**
+     * 停机条件
+     */
+    Stop,
+    /**
+     * 维护条件
+     */
+    Maintain,
+    /**
+     * 解除维护条件
+     */
+    UnMaintain,
+    /**
+     * 未知
+     */
+    UnKnow,
+}

+ 181 - 0
metrics/src/main/java/com/gyee/gaia/metrics/services/BasicInformationService.java

@@ -0,0 +1,181 @@
+package com.gyee.gaia.metrics.services;
+
+import com.gyee.gaia.metrics.modles.TagInfo;
+import com.gyee.gaia.metrics.cache.InfoCache;
+import com.gyee.gaia.metrics.feigns.IDataAdapter;
+import com.gyee.gaia.metrics.modles.BoostStation;
+import com.gyee.gaia.metrics.modles.ConversionRateWindTurbineInfo;
+import com.gyee.gaia.metrics.modles.PointData;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 基础信息服务
+ *
+ * @author nnns
+ */
+@Component
+@Order(1)
+public class BasicInformationService implements ApplicationRunner {
+
+    @Value("${refresh-interval: 3}")
+    private long interval;
+    /**
+     * 基础数据缓存
+     */
+    @Resource
+    private InfoCache infoCache;
+    @Resource
+    private IDataAdapter iDataAdapter;
+    /**
+     * 线程池
+     */
+    @Resource
+    private ThreadPoolTaskExecutor taskExecutor;
+
+    /**
+     * 数据标签
+     */
+    private List<TagInfo> tagInfos;
+    /**
+     * 标签点
+     */
+    private List<String> tags;
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        // 初始化数据标签
+        initTagInfos();
+        // 初始化标签点
+        initTags();
+        // 刷新数据
+        refreshData();
+        // 刷新数据
+        taskExecutor.submit(this::toRefreshData);
+    }
+
+    /**
+     * 开始刷新数据
+     */
+    private void toRefreshData() {
+        while (true) {
+            try {
+                Thread.sleep(interval * 1000);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            try {
+                refreshData();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 初始化数据标签
+     */
+    private void initTagInfos() {
+        tagInfos = new ArrayList<>();
+        // 添加升压站
+        addTagInfos(infoCache.getBoostStations(), BoostStation.class);
+        // 添加风机
+        ConversionRateWindTurbineInfo[] windturbines = new ConversionRateWindTurbineInfo[infoCache.getConversionRateWindTurbineInfoMap().size()];
+        infoCache.getConversionRateWindTurbineInfoMap().values().toArray(windturbines);
+        addTagInfos(windturbines, ConversionRateWindTurbineInfo.class);
+    }
+
+    /**
+     * 添加要托管的TagInfos
+     */
+    public <T> void addTagInfos(T[] ls, Class<?> c) {
+        if (ls == null || ls.length == 0) {
+            return;
+        }
+        List<Field> fields = getTagInfoFields(c);
+        for (Object o : ls) {
+            for (Field f : fields) {
+                try {
+                    TagInfo ti = (TagInfo) f.get(o);
+                    if (ti == null || "".equals(ti.getTag())) {
+                        continue;
+                    }
+                    tagInfos.add(ti);
+                } catch (IllegalAccessException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取需要刷新的属性
+     */
+    private List<Field> getTagInfoFields(Class<?> c) {
+        Field[] fs = c.getDeclaredFields();
+        List<Field> ls = new ArrayList<>();
+        for (Field f : fs) {
+            if (!TagInfo.class.isAssignableFrom(f.getType())) {
+                continue;
+            }
+            f.setAccessible(true);
+            ls.add(f);
+        }
+        return ls;
+    }
+
+    /**
+     * 刷新数据
+     */
+    private void refreshData() {
+        try {
+            int count = 0;
+            for (String ks : tags) {
+                Map<String, PointData> mp = iDataAdapter.getValueByKeys(ks);
+                // 刷新数据
+                for (TagInfo ti : tagInfos) {
+                    if (!mp.containsKey(ti.getTag())) {
+                        continue;
+                    }
+                    PointData pd = mp.get(ti.getTag());
+                    ti.setTimestamp(pd.getTs());
+                    ti.setValue(pd.getValue());
+                }
+                count += mp.size();
+            }
+            System.out.print(count + " ");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 初始化标签点
+     */
+    private void initTags() {
+        int count = 0;
+        tags = new ArrayList<>();
+        StringBuilder sb = new StringBuilder();
+        for (TagInfo ti : tagInfos) {
+            sb.append(ti.getTag()).append(",");
+            ++count;
+            if (count != 0 && count % 1000 == 0) {
+                tags.add(sb.toString());
+                sb = new StringBuilder();
+            }
+        }
+        if (count != 0 && count % 1000 != 0) {
+            tags.add(sb.toString());
+        }
+    }
+}

+ 312 - 0
metrics/src/main/java/com/gyee/gaia/metrics/services/ConversionRateStatsService.kt

@@ -0,0 +1,312 @@
+package com.gyee.gaia.metrics.services
+
+import com.gyee.gaia.metrics.cache.InfoCache
+import com.gyee.gaia.metrics.dao.entity.ConversionRateStatsEntity
+import com.gyee.gaia.metrics.dao.repsoitory.ConversionRateStatsRepository
+import com.gyee.gaia.metrics.feigns.IDataAdapter
+import com.gyee.gaia.metrics.modles.*
+import com.gyee.gaia.metrics.modles.enums.Conditions
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.ApplicationArguments
+import org.springframework.boot.ApplicationRunner
+import org.springframework.core.annotation.Order
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
+import org.springframework.stereotype.Component
+import java.time.LocalDate
+import javax.annotation.Resource
+
+
+@Component
+@Order(2)
+class ConversionRateStatsService : ApplicationRunner {
+    @Value("\${refresh-interval: 3}")
+    private val interval: Long = 3
+
+    @Value("\${is-calc-conversion:true}")
+    private val isCalcConversion: Boolean = true
+
+    /**
+     * 线程池
+     */
+    @Resource
+    private lateinit var taskExecutor: ThreadPoolTaskExecutor
+
+    @Resource
+    private lateinit var infoCache: InfoCache
+
+    @Resource
+    private lateinit var conversionRateStatsRepository: ConversionRateStatsRepository
+
+    @Resource
+    private lateinit var iDataAdapter: IDataAdapter
+
+    private val conversionRateStatsEntityMap = HashMap<String, ConversionRateStatsEntity>()
+
+    override fun run(args: ApplicationArguments?) {
+        // 用于跑数据
+        //test();
+        //test2()
+
+        if (!isCalcConversion) {
+            println("状态转换及时率计算已经关闭 [is-calc-conversion]")
+            return
+        }
+        init()
+
+
+        taskExecutor.execute(this::refresh)
+    }
+
+    private fun test2() {
+        val vs = conversionRateStatsRepository.findAll()
+        for (v in vs) {
+            val vv = v.id.split("-")
+            val date = vv[1].toTimeMillis("yyyyMMdd")
+            v.time = date
+        }
+        conversionRateStatsRepository.saveAll(vs)
+    }
+
+    private fun test() {
+        val sts = 1651334400000
+        //val ets = LocalDateTime.now().toDate().time
+        val ets = 1672502400000
+        val bs = infoCache.boostStations
+        for (b in bs) {
+            val ers = iDataAdapter.getSnapValuesByKey(b.electricityRestrictions.tag, sts, ets, 60)
+            for (p in b.projectMap.values) {
+                // 获取数据
+                val dt = getAllData(p, sts, ets)
+
+                val statuse = dt[0]
+                val wspeed = dt[1]
+
+                for (i in ers.indices) {
+                    b.electricityRestrictions.value = ers[i].value
+                    // 更新数据
+                    for ((j, w) in p.windturbines.withIndex()) {
+                        w.status.value = statuse[j][i].value
+                        w.averageWindSpeed5.value = wspeed[j][i].value
+                    }
+                    // 刷新可维护风机比例
+                    p.refreshMaintainableRatio()
+                    // 计算状态转换率
+                    for (w in p.windturbines) {
+                        val ts = ers[i].ts
+                        val state =
+                            w.refreshAndGetIsStatusChanged(ts, b.electricityRestrictions.value, p.maintainableRatio)
+                                ?: continue
+                        val conversionRateStatsEntity: ConversionRateStatsEntity =
+                            initAndGetConversionRateStatsEntity(w, state, ts.toLocalDate())
+                    }
+                }
+                if (conversionRateStatsEntityMap.size != 0) {
+                    conversionRateStatsRepository.saveAll(conversionRateStatsEntityMap.values)
+                }
+                conversionRateStatsEntityMap.clear()
+
+                println(p.id)
+            }
+        }
+    }
+
+    private fun getAllData(p: Project, sts: Long, ets: Long): ArrayList<ArrayList<List<PointData>>> {
+        println("${p.id}-获取数据...")
+        val statuses = ArrayList<List<PointData>>()
+        val wspeeds = ArrayList<List<PointData>>()
+        for (w in p.windturbines) {
+            val status = iDataAdapter.getSnapValuesByKey(w.status.tag, sts, ets, 60)
+            val wspeed = iDataAdapter.getSnapValuesByKey(w.averageWindSpeed5.tag, sts, ets, 60)
+            statuses.add(status)
+            wspeeds.add(wspeed)
+            println("${w.id}-获取数据结束")
+        }
+        println("${p.id}-获取数据结束")
+        return arrayListOf(statuses, wspeeds)
+    }
+
+    /**
+     * 初始化
+     */
+    private fun init() {
+        val ts = LocalDate.now().toDate().time
+
+        val vs = conversionRateStatsRepository.findAllByTimeGreaterThanEqual(ts)
+        for (v in vs) {
+            conversionRateStatsEntityMap.put(v.id, v)
+        }
+    }
+
+    private fun refresh() {
+        while (true) {
+            try {
+                Thread.sleep(interval * 1000)
+            } catch (e: InterruptedException) {
+                e.printStackTrace()
+            }
+            refreshData()
+        }
+    }
+
+    private fun refreshData() {
+        val vs = infoCache.boostStations
+        val ts = System.currentTimeMillis()
+        val now = LocalDate.now()
+        val ls = ArrayList<ConversionRateStatsEntity>()
+        for (v in vs) {
+            for (p in v.projectMap.values) {
+                p.refreshMaintainableRatio()
+                for (w in p.windturbines) {
+                    val state = w.refreshAndGetIsStatusChanged(ts, v.electricityRestrictions.value, p.maintainableRatio)
+                        ?: continue
+                    val conversionRateStatsEntity: ConversionRateStatsEntity =
+                        initAndGetConversionRateStatsEntity(w, state, now)
+                    ls.add(conversionRateStatsEntity)
+                }
+            }
+        }
+        if (ls.size != 0) {
+            conversionRateStatsRepository.saveAll(ls)
+        }
+        print("cr${ls.size} ")
+    }
+
+    private fun initAndGetConversionRateStatsEntity(
+        w: ConversionRateWindTurbineInfo,
+        state: StatusChangedInfo,
+        now: LocalDate
+    ): ConversionRateStatsEntity {
+        val id: String = getEntityId(w, now)
+
+        val crs = conversionRateStatsEntityMap[id] ?: ConversionRateStatsEntity()
+
+        if (!conversionRateStatsEntityMap.containsKey(id)) {
+            crs.id = id
+            crs.station = w.stationId
+            crs.windturbineId = w.id
+            conversionRateStatsEntityMap[id] = crs
+        }
+
+        updateConversionRateStatsEntity(crs, state, now.toDate().time)
+
+        return crs
+    }
+
+    /**
+     * 更新
+     */
+    private fun updateConversionRateStatsEntity(crs: ConversionRateStatsEntity, state: StatusChangedInfo, time: Long) {
+        crs.time = time
+        when (state.conditions) {
+            Conditions.Start -> updateStart(crs, state.timeInterval)
+
+            Conditions.Stop -> updateStop(crs, state.timeInterval)
+
+            Conditions.Maintain -> updateMaintain(crs, state.timeInterval)
+
+            Conditions.UnMaintain -> updateUnMaintain(crs, state.timeInterval)
+
+            else -> {}
+        }
+    }
+
+    private fun updateUnMaintain(crs: ConversionRateStatsEntity, timeInterval: Int) {
+        crs.countAll += 1
+
+        crs.countUnMaintain += 1
+        // 基准为10分钟
+        if (timeInterval < 15) {
+            crs.countAll5 += 1
+            crs.countUnMaintain5 += 1
+        } else if (timeInterval < 20) {
+            crs.countAll10 += 1
+            crs.countUnMaintain10 += 1
+        } else if (timeInterval < 25) {
+            crs.countAll15 += 1
+            crs.countUnMaintain15 += 1
+        } else if (timeInterval < 30) {
+            crs.countAll20 += 1
+            crs.countUnMaintain20 += 1
+        } else {
+            // 超过转换周期
+            crs.countUnconvertedAll += 1
+            crs.countUnconvertedUnMaintain += 1
+        }
+    }
+
+    private fun updateMaintain(crs: ConversionRateStatsEntity, timeInterval: Int) {
+        crs.countAll += 1
+
+        crs.countMaintain += 1
+        // 基准为10分钟
+        if (timeInterval < 15) {
+            crs.countAll5 += 1
+            crs.countMaintain5 += 1
+        } else if (timeInterval < 20) {
+            crs.countAll10 += 1
+            crs.countMaintain10 += 1
+        } else if (timeInterval < 25) {
+            crs.countAll15 += 1
+            crs.countMaintain15 += 1
+        } else if (timeInterval < 30) {
+            crs.countAll20 += 1
+            crs.countMaintain20 += 1
+        } else {
+            // 超过转换周期
+            crs.countUnconvertedAll += 1
+            crs.countUnconvertedMaintain += 1
+        }
+    }
+
+    private fun updateStop(crs: ConversionRateStatsEntity, timeInterval: Int) {
+        crs.countAll += 1
+
+        crs.countStop += 1
+        // 基准为10分钟
+        if (timeInterval < 15) {
+            crs.countAll5 += 1
+            crs.countStop5 += 1
+        } else if (timeInterval < 20) {
+            crs.countAll10 += 1
+            crs.countStop10 += 1
+        } else if (timeInterval < 25) {
+            crs.countAll15 += 1
+            crs.countStop15 += 1
+        } else if (timeInterval < 30) {
+            crs.countAll20 += 1
+            crs.countStop20 += 1
+        } else {
+            // 超过转换周期
+            crs.countUnconvertedStop += 1
+            crs.countUnconvertedAll += 1
+        }
+    }
+
+    private fun updateStart(crs: ConversionRateStatsEntity, timeInterval: Int) {
+        crs.countAll += 1
+        crs.countStart += 1
+        // 基准为10分钟
+        if (timeInterval < 15) {
+            crs.countAll5 += 1
+            crs.countStart5 += 1
+        } else if (timeInterval < 20) {
+            crs.countAll10 += 1
+            crs.countStart10 += 1
+        } else if (timeInterval < 25) {
+            crs.countAll15 += 1
+            crs.countStart15 += 1
+        } else if (timeInterval < 30) {
+            crs.countAll20 += 1
+            crs.countStart20 += 1
+        } else {
+            // 超过转换周期
+            crs.countUnconvertedStart += 1
+            crs.countUnconvertedAll += 1
+        }
+    }
+
+    private fun getEntityId(w: ConversionRateWindTurbineInfo, now: LocalDate): String {
+        val date = now.toFormatterString("yyyyMMdd")
+        return "${w.id}-${date}"
+    }
+}

+ 17 - 0
metrics/src/main/java/com/gyee/gaia/metrics/targets/UniformCode.java

@@ -0,0 +1,17 @@
+package com.gyee.gaia.metrics.targets;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * UniformCode注解
+ *
+ * @author xysn
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UniformCode {
+    String value() default "";
+}

+ 44 - 0
metrics/src/main/resources/application.yaml

@@ -0,0 +1,44 @@
+server:
+  port: 9034
+
+
+spring:
+  http:
+    encoding:
+      charset: utf-8
+      enabled: true
+      force: true
+  application:
+    name: master
+  jpa:
+    show-sql: true
+    properties:
+      hibernate:
+        dialect: org.hibernate.dialect.Oracle10gDialect
+  datasource:
+    driver-class-name: oracle.jdbc.OracleDriver
+    url: jdbc:oracle:thin:@192.168.1.105:1521:gdnxfd
+    username: nxfdprod
+    password: gdnxfd123
+    type: com.alibaba.druid.pool.DruidDataSource
+    druid:
+      max-active: 20
+      initial-size: 1
+      min-idle: 3
+      max-wait: 60000
+      time-between-eviction-runs-millis: 60000
+      min-evictable-idle-time-millis: 300000
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+
+
+urls:
+  adapter: http://192.168.10.18:8011
+
+# 数据刷新频率(秒)
+refresh-interval: 3
+# 是否计算状态转换及时率
+is-calc-conversion: false
+# 没有维护指令的机型
+no-maintain-model: UP105-2000-S,CCWE-1500

+ 3 - 0
metrics/src/main/resources/banner.txt

@@ -0,0 +1,3 @@
+┌┬┐┌─┐┌┬┐┬─┐┬┌─┐┌─┐
+│││├┤  │ ├┬┘││  └─┐
+┴ ┴└─┘ ┴ ┴└─┴└─┘└─┘ v1.0.4

+ 13 - 0
metrics/src/test/java/com/gyee/gaia/metrics/MetricsApplicationTests.java

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

+ 1 - 0
settings.gradle

@@ -16,4 +16,5 @@ include "electricity:meter"
 include "state:wind"
 include "state:cause"
 
+include "metrics"