commit bfc9c0df6fa3db629e05facbf5c1a6fffce3d4e2 Author: fangyang2021 <3020949587@qq.com> Date: Thu Nov 21 10:50:53 2024 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89bcb65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/ +.jpb/ +*.iws +*.iml +*.ipr +logs/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store!/ \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..bf82ff0 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..ca5ab4b --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/HELP.md b/HELP.md new file mode 100644 index 0000000..811318d --- /dev/null +++ b/HELP.md @@ -0,0 +1,10 @@ +# Getting Started + +### Reference Documentation + +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.7.11/maven-plugin/reference/html/) +* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.7.11/maven-plugin/reference/html/#build-image) + diff --git a/bolt-api/pom.xml b/bolt-api/pom.xml new file mode 100644 index 0000000..d4ba1b5 --- /dev/null +++ b/bolt-api/pom.xml @@ -0,0 +1,31 @@ + + + + bolt-server + com.jiluo.bolt + 0.0.1-SNAPSHOT + + 4.0.0 + + bolt-api + + + org.springframework + spring-context + 5.3.27 + + + + javax.validation + validation-api + + + org.hibernate.validator + hibernate-validator + + + + + \ No newline at end of file diff --git a/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/Hasp.java b/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/Hasp.java new file mode 100644 index 0000000..e111d11 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/Hasp.java @@ -0,0 +1,1012 @@ +package com.jiluo.bolt.Aladdin; + +import java.nio.*; +import com.jiluo.bolt.Aladdin.HaspStatus; +import com.jiluo.bolt.Aladdin.HaspTime; +import com.jiluo.bolt.Aladdin.HaspApiVersion; + +public class Hasp +{ + /** + * handle - pointer to the resulting session handle. + */ + private int[] handle = { 0 }; + + /** + * Unique identifier of the Feature. + */ + private long featureid; + + /** + * Status of the last function call. + */ + private int status; + + /** + * getSessionInfo() format to retrieve update info (C2V). + */ + public static final String HASP_UPDATEINFO = new String(""); + + /** + * getSessionInfo() format to retrieve session info. + */ + public static final String HASP_SESSIONINFO = new String(""); + + /** + * getSessionInfo() format to retrieve key/hardware info. + */ + public static final String HASP_KEYINFO = new String(""); + + /** + * format to retrieve host fingerprint info + */ + public static final String HASP_FINGERPRINT= new String(""); + + /** + * format to retrieve recipient parameter for hasp_detach + */ + public static final String HASP_RECIPIENT = new String(""+ + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " \n"); + + /** + * AND-mask used to identify the Feature type. + */ + public static final long HASP_FEATURETYPE_MASK = 0xffff0000; + + /** + * After AND-ing with HASP_FEATURETYPE_MASK, the Feature type contains + * this value. + */ + public static final long HASP_PROGNUM_FEATURETYPE = 0xffff0000; + + /** + * AND-mask used to extract program number from Feature ID + * if program number Feature. + */ + public static final long HASP_PROGNUM_MASK = 0x000000ff; + + /** + * AND-mask used to identify "prognum" options. + * + * The following "prognum" options can be identified: + * + *
    + *
  • HASP_PROGNUM_OPT_NO_LOCAL + *
  • HASP_PROGNUM_OPT_NO_REMOTE + *
  • HASP_PROGNUM_OPT_PROCESS + *
  • HASP_PROGNUM_OPT_CLASSIC + *
  • HASP_PROGNUM_OPT_TS + *
+ * + * 3 bits of the mask are reserved for future extensions and currently + * unused. Initialize them to zero. + */ + public static final long HASP_PROGNUM_OPT_MASK = 0x0000ff00; + + /** + * "Prognum" option: disables local license search. + */ + public static final long HASP_PROGNUM_OPT_NO_LOCAL = 0x00008000; + + /** + * "Prognum" option: disables network license search. + */ + public static final long HASP_PROGNUM_OPT_NO_REMOTE = 0x00004000; + + /** + * "Prognum" option: sets session count of network licenses + * to "per process". + */ + public static final long HASP_PROGNUM_OPT_PROCESS = 0x00002000; + + /** + * "Prognum" option: enables the API to access "classic" + * (HASP4 or earlier) keys. + */ + public static final long HASP_PROGNUM_OPT_CLASSIC = 0x00001000; + + /** + * "Prognum" option: ignores Terminal Services. + */ + public static final long HASP_PROGNUM_OPT_TS = 0x00000800; + + /** + * HASP default Feature ID. + * Present in every hardware key. + */ + public static final long HASP_DEFAULT_FID = 0; + + /** + * "Prognum" default Feature ID. + * Present in every HASP key. + */ + public static final long HASP_PROGNUM_DEFAULT_FID = (HASP_DEFAULT_FID | HASP_PROGNUM_FEATURETYPE); + + /** + * Minimal block size for hasp_encrypt() and hasp_decrypt() functions. + */ + public static final int HASP_MIN_BLOCK_SIZE = 16; + + /** + * Minimal block size for legacy functions hasp_legacy_encrypt() + * and hasp_legacy_decrypt(). + */ + public static final long HASP_MIN_BLOCK_SIZE_LEGACY = 8; + + /** + * HASP4 memory file: + * File ID for HASP4-compatible memory contents without FAS. + */ + public static final int HASP_FILEID_MAIN = 0xfff0; + + /** + * HASP4 FAS memory file: + * (Dummy) File ID for license data segment of memory contents. + */ + public static final long HASP_FILEID_LICENSE = 0xfff2; + + /** + * File ID for HASP secure writable memory. + */ + public static final long HASP_FILEID_RW = 0xfff4; + + /** + * File ID for HASP secure read only memory. + */ + public static final long HASP_FILEID_RO = 0xfff5; + + /** + * Returns the error that occurred in the last function call. + */ + public int getLastError() + { + return status; + } + + static + { + HaspStatus.Init(); + } + + /* + * private native methods + */ + private static native int Login(long feature_id,String vendor_code,int handle[]); + private static native int LoginScope(long feature_id,String scope,String vendor_code,int handle[]); + private static native int Logout(int handle); + private static native int Encrypt(int handle, byte buffer[], int length); + private static native int Decrypt(int handle, byte buffer[], int length); + private static native int GetRtc(int handle, long time[]); + private static native int LegacyEncrypt(int handle, byte buffer[], int length); + private static native int LegacyDecrypt(int handle, byte buffer[], int length); + private static native int LegacySetRtc(int handle, long time); + private static native int LegacySetIdletime(int handle, short time); + private static native byte[] GetSessioninfo(int handle,String format,int status[]); + private static native byte[] GetInfo(String scope,String format,String vendor_code,int status[]); + private static native void Free(long info); + private static native String Update(String update_data,int status[]); + private static native byte[] Detach(String action,String scope,String vendor_code,String destination,int status[]); + private static native byte[] Transfer(String action,String scope,String vendor_code,String destination,int status[]); + private static native int UpdateSession(int handle,String option); + + /* + * functions to access the memory + */ + private static native int Read(int handle, long fileid, int offset, int length, byte buffer[]); + private static native int Write(int handle, long fileid, int offset, int length, byte buffer[]); + private static native int GetSize(int handle, long fileid, int size[]); + + /** + * Hasp constructor. + * + * For local "prognum" Features, concurrency is not handled and each + * login performs a decrement if it is a counting license. + *

+ * Network "prognum" Features only use the old HASP LM login logic, + * with all its limitations. + *

+ * Only concurrent usage of one server is supported (global server address). + * + * @param feature_id Unique identifier of the Feature. + * + * With "prognum" Features (see HASP_FEATURETYPE_MASK), + * 8 bits are reserved for legacy options (see + * HASP_PROGNUM_OPT_MASK, currently 5 bits are used): + *

    + *
  • only local + *
  • only remote + *
  • login is counted per process ID + *
  • disable terminal server check + *
  • enable access to old (HASP3/HASP4) keys + *
+ */ + public Hasp(long feature_id) + { + status = HaspStatus.HASP_STATUS_OK; + featureid = feature_id; + handle[0] = 0; + } + + /** + * Logs in to a Feature. + * + * Establishes a session context. + *

+ * If a previously established session context exists, the session + * will be logged out. + * + * @param vendor_code The Vendor Code. + * + * @return true/false - indicates success or failure. + * + * @see #loginScope + * @see #logout + * @see #getLastError + */ + public boolean login(String vendor_code) + { + synchronized(this) + { + synchronized(this) + { + logout(); + status = Hasp.Login(featureid, vendor_code, handle); + } + } + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Logs in to a Feature according to customizable search parameters. + * + * This function is an extended login function, where the search for the + * Feature can be restricted. + *

+ * If a previously established session context exists, the session + * will be logged out. + * + * @param scope The hasp_scope of the Feature search. + * @param vendor_code The Vendor Code. + * + * @return true/false - indicates success or failure. + * + * @see #login + * @see #logout + * @see #getLastError + */ + public boolean loginScope(String scope, String vendor_code) + { + if (vendor_code == null) + status = HaspStatus.HASP_INV_VCODE; + else if (scope == null) + status = HaspStatus.HASP_INV_SCOPE; + else + { + synchronized(this) + { + logout(); + status = Hasp.LoginScope(featureid, scope, vendor_code,handle); + } + } + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Logs out from a session and frees all allocated resources for the session. + * + * @return true/false - indicates success or failure. + * + * @see #login + * @see #getLastError + */ + public boolean logout() + { + if (handle[0] == 0) + { + status = HaspStatus.HASP_INV_HND; + return true; + } + synchronized(this) + { + status = Hasp.Logout(handle[0]); + if (status == HaspStatus.HASP_STATUS_OK) + handle[0] = 0; + } + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Encrypts a buffer. + * + * This is the reverse operation of the decrypt() function. + *

+ * If the encryption fails (e.g. key removed during the process) the + * data buffer is unmodified. + *

+ * This function is deprecated. + * + * @param buffer The buffer to be encrypted. + * @param length Size in bytes of the buffer to be encrypted + * (16 bytes minimum). + * + * @return true/false - indicates success or failure. + * + * @see #decrypt + * @see #getLastError + */ + @Deprecated + public boolean encrypt(byte[] buffer, int length) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else if (length > buffer.length) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.Encrypt(handle[0], buffer, length); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Encrypts a buffer. + * + * This is the reverse operation of the decrypt() function. + *

+ * If the encryption fails (e.g. key removed during the process) the + * data buffer is unmodified. + * + * @param buffer The buffer to be encrypted. + * (16 bytes minimum). + * + * @return true/false - indicates success or failure. + * + * @see #decrypt + * @see #getLastError + */ + public boolean encrypt(byte[] buffer) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.Encrypt(handle[0], buffer, buffer.length); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Decrypts a buffer. + * + * This is the reverse operation of the encrypt() function. + *

+ * If the decryption fails (e.g. key removed during the process) the + * data buffer is unmodified. + *

+ * This function is deprecated. + * + * @param buffer The buffer to be decrypted. + * @param length Size in bytes of the buffer to be decrypted + * (16 bytes minimum). + * + * @return true/false - indicates success or failure. + * + * @see #encrypt + * @see #getLastError + */ + @Deprecated + public boolean decrypt(byte[] buffer, int length) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else if (length > buffer.length) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.Decrypt(handle[0], buffer, length); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Decrypts a buffer. + * + * This is the reverse operation of the encrypt() function. + *

+ * If the decryption fails (e.g. key removed during the process) the + * data buffer is unmodified. + * + * @param buffer The buffer to be decrypted. + * (16 bytes minimum). + * + * @return true/false - indicates success or failure. + * + * @see #encrypt + * @see #getLastError + */ + public boolean decrypt(byte[] buffer) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.Decrypt(handle[0], buffer, buffer.length); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Retrieves information about all system components. + * + * Acquires information about all system components. + * The programmer can choose the scope and output structure of the data. + * The function has a "scope" parameter that defines the scope using + * XML syntax. + *

+ * This function is not used in a login context, so it can be used + * in a generic "Monitor" application. + *

+ * @param scope XML definition of the information scope. + * @param format XML definition of the output data structure. + * @param vendor_code The Vendor Code. + * @return info The returned information (XML list). + * + * @see #getSessionInfo + * @see #getLastError + */ + public String getInfo(String scope, String format, String vendor_code) + { + byte[] info = { 0 }; + int[] status1 = { 0 }; + String s = null; + + status = HaspStatus.HASP_STATUS_OK; + if (vendor_code == null) + status = HaspStatus.HASP_INV_VCODE; + else if (scope == null) + status = HaspStatus.HASP_INV_SCOPE; + else if (format == null) + status = HaspStatus.HASP_INV_FORMAT; + if (status != HaspStatus.HASP_STATUS_OK) + return null; + + info = Hasp.GetInfo(scope, format, vendor_code, status1); + + status = status1[0]; + if( status == HaspStatus.HASP_STATUS_OK) + s = new String(info); + + return s; + } + + /** + * Retrieves information regarding a session context. + * + * @param format XML definition of the output data structure. + * @return info The returned information (XML list). + * + * @see #getLastError + */ + public String getSessionInfo(String format) + { + byte[] info = { 0 }; + int[] status1 = { 0 }; + String s = null; + + if (format == null) + { + status = HaspStatus.HASP_INV_FORMAT; + return null; + } + + info = Hasp.GetSessioninfo(handle[0], format, status1); + + status = status1[0]; + if( status == HaspStatus.HASP_STATUS_OK) + s = new String(info); + + return s; + } + + /** + * Reads from the HASP key memory. + * + * This function is deprecated. + * + * @param fileid ID of the file to read (memory descriptor). + * @param offset Position in the file. + * @param length Number of bytes to be read from the file. + * @param buffer The retrieved data. + * + * @return true/false - indicates success or failure. + * + * @see #getLastError + * @see #write + * @see #getSize + */ + @Deprecated + public boolean read(long fileid, int offset, int length, byte[] buffer) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else if (offset < 0) + status = HaspStatus.HASP_INV_PARAM; + else if (length > buffer.length) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.Read(handle[0], fileid, offset, length, buffer); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Reads from the HASP key memory. + * + * @param fileid ID of the file to read (memory descriptor). + * @param offset Position in the file. + * @param buffer Buffer for the retrieved data. + * + * @return true/false - indicates success or failure. + * + * @see #getLastError + * @see #write + * @see #getSize + */ + public boolean read(long fileid, int offset, byte[] buffer) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else if (offset < 0) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.Read(handle[0], fileid, offset, buffer.length, buffer); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Writes to the HASP key memory. + * + * Depending on the provided session handle (either logged into the + * default Feature or any other Feature), write access to the FAS + * memory (HASP_FILEID_LICENSE) is not permitted. + *

+ * This function is deprecated. + * + * @param fileid ID of the file to write (memory descriptor). + * @param offset Position in the file. + * @param length Number of bytes to write to the file. + * @param buffer The data to write. + * + * @return true/false - indicates success or failure. + * + * @see #getLastError + * @see #read + * @see #getSize + */ + @Deprecated + public boolean write(long fileid, int offset, int length, byte[] buffer) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else if (offset < 0) + status = HaspStatus.HASP_INV_PARAM; + else if (length > buffer.length) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.Write(handle[0], fileid, offset, length, buffer); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Writes to the HASP key memory. + * + * Depending on the provided session handle (either logged into the + * default Feature or any other Feature), write access to the FAS + * memory (HASP_FILEID_LICENSE) is not permitted. + * + * @param fileid ID of the file to write (memory descriptor). + * @param offset Position in the file. + * @param buffer The data to write. + * + * @return true/false - indicates success or failure. + * + * @see #getLastError + * @see #read + * @see #getSize + */ + public boolean write(long fileid, int offset, byte[] buffer) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else if (offset < 0) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.Write(handle[0], fileid, offset, buffer.length, buffer); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Retrieves the byte size of a memory file from a HASP key. + * + * @param fileid ID of the file to be queried. + * + * @return Size of the file. + * + * @see #getLastError + * @see #read + * @see #write + */ + public int getSize(long fileid) + { + int[] size = { 0 }; + status = Hasp.GetSize(handle[0], fileid, size); + return size[0]; + } + + /** + * Writes update information to a HASP key. + * + * The update BLOB contains all necessary data to perform the update: + * Where to write (to which HASP key), the necessary + * access data (Vendor Code) and the update itself. + *

+ * If requested by the update BLOB, the function returns an Acknowledge BLOB, + * which is signed/encrypted by the updated instance and contains + * proof that this update was successfully installed. + * + * @param update_data The complete update data. + * + * @return ack_data The acknowledged data (if requested). + * + * @see #getLastError + */ + public String update(String update_data) + { + int[] dll_status = {0}; + String s = null; + + if (update_data == null) { + status = HaspStatus.HASP_INV_PARAM; + return null; + } + + s = Hasp.Update(update_data, dll_status); + status = dll_status[0]; + + return s; + } + + /** + * Reads the current time from a HASP Time key. + * + * Time values are returned as the number of seconds that have elapsed + * since Jan-01-1970 0:00:00 UTC. + *

+ * The general purpose of this function is to + * obtain reliable timestamps that are independent from the system clock. + * + * @return A HaspTime object. + */ + public HaspTime getRealTimeClock() + { + long[] time = { 0 }; + HaspTime rtcTime; + status = Hasp.GetRtc(handle[0], time); + rtcTime = new HaspTime(time[0]); + + if (status == HaspStatus.HASP_STATUS_OK) + status = rtcTime.getLastError(); + + return rtcTime; + } + + /** + * Sets the HASP License Manager idle time. + * + * @param idle_time Idle time in minutes. Set to 0 for default value. + * + * @return true/false - indicates success or failure. + * + * @see #getLastError + */ + /* + public boolean setIdletime(short idle_time) + { + status = Hasp.SetIdletime(handle[0], idle_time); + if (status == HaspStatus.HASP_STATUS_OK) + return true; + + return false; + } + */ + + /** + * Reads the HASP API Version. + * + * @param vendor_code The Vendor Code. + * + * @return A HaspApiVersion object. + * + * @see #getLastError + */ + public HaspApiVersion getVersion(String vendor_code) + { + HaspApiVersion version; + version = new HaspApiVersion(vendor_code); + status = version.getLastError(); + + return version; + } + + /** + * Encrypts a buffer. + * + * This is the reverse operation of the legacydecrypt() function. + *

+ * If the encryption fails (e.g. key removed during the process) the + * data buffer is unmodified. + *

+ * This function is deprecated. + *

+ * @param buffer The buffer to be encrypted. + * @param length Size in bytes of the buffer to be encrypted + * (16 bytes minimum). + * + * @return true/false - indicates success or failure. + * + * @see #decrypt + * @see #getLastError + */ + @Deprecated + public boolean legacyencrypt(byte[] buffer, int length) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else if (length > buffer.length) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.LegacyEncrypt(handle[0], buffer, length); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Encrypts a buffer. + * + * This is the reverse operation of the legacydecrypt() function. + *

+ * If the encryption fails (e.g. key removed during the process) the + * data buffer is unmodified. + *

+ * @param buffer The buffer to be encrypted. Minimum size: 16 bytes + * + * @return true/false - indicates success or failure. + * + * @see #decrypt + * @see #getLastError + */ + public boolean legacyencrypt(byte[] buffer) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.LegacyEncrypt(handle[0], buffer, buffer.length); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Decrypts a buffer. + * + * This is the reverse operation of the legacyencrypt() function. + *

+ * If the decryption fails (e.g. key removed during the process) the + * data buffer is unmodified. + *

+ * This function is deprecated. + *

+ * @param buffer The buffer to be decrypted. + * @param length Size in bytes of the buffer to be decrypted + * (16 bytes minimum). + * + * @return true/false - indicates success or failure. + * + * @see #encrypt + * @see #getLastError + */ + @Deprecated + public boolean legacydecrypt(byte[] buffer, int length) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else if (length > buffer.length) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.LegacyDecrypt(handle[0], buffer, length); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Decrypts a buffer. + * + * This is the reverse operation of the legacyencrypt() function. + *

+ * If the decryption fails (e.g. key removed during the process) the + * data buffer is unmodified. + *

+ * @param buffer The buffer to be decrypted. Minimum size: 16 bytes + * + * @return true/false - indicates success or failure. + * + * @see #encrypt + * @see #getLastError + */ + public boolean legacydecrypt(byte[] buffer) + { + if (buffer == null) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.LegacyDecrypt(handle[0], buffer, buffer.length); + + return (status == HaspStatus.HASP_STATUS_OK); + } + + /** + * Sets the HASP License Manager Rtc. + * + * This function is deprecated. + * + * @param idle_time Idle time in minutes. Set to 0 for default value. + * + * @return true/false - indicates success or failure. + * + * @see #getLastError + */ + @Deprecated + public boolean legacysetRtc(short idle_time) { + status = Hasp.LegacySetRtc(handle[0], (short)idle_time); + if (status == HaspStatus.HASP_STATUS_OK) + return true; + return false; + } + + /** + * Sets the HASP License Manager Rtc. + * + * @param idle_time Idle time in minutes. Set to 0 for default value. + * + * @return true/false - indicates success or failure. + * + * @see #getLastError + */ + public boolean legacysetRtc(long idle_time) + { + status = Hasp.LegacySetRtc(handle[0], idle_time); + if (status == HaspStatus.HASP_STATUS_OK) + return true; + + return false; + } + + /** + * Sets the HASP License Manager idle time. + * + * @param idle_time Idle time in minutes. Set to 0 for default value. + * + * @return true/false - indicates success or failure. + * + * @see #getLastError + */ + public boolean legacysetIdletime(short idle_time) { + status = Hasp.LegacySetIdletime(handle[0], idle_time); + if (status == HaspStatus.HASP_STATUS_OK) + return true; + return false; + } + + /** + * Detach a commuter license. + * + * @deprecated This function is deprecated use transfer API instead + * @param action XML definition of the action. + * @param scope XML definition of the information scope. + * @param vendor_code The Vendor Code. + * @param destination XML definition of the output destination. + * @return info The returned information (XML list). + * + * @see #getLastError + */ + @Deprecated + public String detach(String action, String scope, String vendor_code, String destination) + { + byte[] info = { 0 }; + int[] status1 = { 0 }; + String s = null; + + if (action == null) + status = HaspStatus.HASP_INV_PARAM; + else if (scope == null) + status = HaspStatus.HASP_INV_SCOPE; + else if (vendor_code == null) + status = HaspStatus.HASP_INV_VCODE; + else if (destination == null) + status = HaspStatus.HASP_INV_PARAM; + if (status != HaspStatus.HASP_STATUS_OK) + return null; + + info = Hasp.Detach(action, scope, vendor_code, destination, status1); + status = status1[0]; + + if (status == HaspStatus.HASP_STATUS_OK) + s = new String(info); + + return s; + } + + /** + * Transfer a license (detach or rehost). + * + * @param action XML definition of the action. + * @param scope XML definition of the information scope. + * @param vendor_code The Vendor Code. + * @param destination XML definition of the output destination. + * @return info The returned information (XML list). + * + * @see #getLastError + */ + public String transfer(String action, String scope, String vendor_code, String destination) + { + byte[] info= { 0 }; + int[] status1 = { 0 }; + String s = null; + + if (action == null) + status = HaspStatus.HASP_INV_PARAM; + else if (scope == null) + status = HaspStatus.HASP_INV_SCOPE; + else if (vendor_code == null) + status = HaspStatus.HASP_INV_VCODE; + else if (destination == null) + status = HaspStatus.HASP_INV_PARAM; + if (status != HaspStatus.HASP_STATUS_OK) + return null; + + info = Hasp.Transfer(action, scope, vendor_code, destination, status1); + + status = status1[0]; + + if (status == HaspStatus.HASP_STATUS_OK) + s = new String(info); + + return s; + } + + /** + * Update information regarding a login session + * for a Sentinel protection key. + * + * @param option Definition of the data to be updated by the function. + * + * @return true/false - indicates success or failure. + * + * @see #getLastError + */ + public boolean updateSession(String option) + { + if (option == null) + status = HaspStatus.HASP_INV_PARAM; + else + status = Hasp.UpdateSession(handle[0], option); + + return (status == HaspStatus.HASP_STATUS_OK); + } + +} + diff --git a/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/HaspApiVersion.java b/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/HaspApiVersion.java new file mode 100644 index 0000000..bd60b70 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/HaspApiVersion.java @@ -0,0 +1,94 @@ +package com.jiluo.bolt.Aladdin; +import java.io.UnsupportedEncodingException; +import com.jiluo.bolt.Aladdin.HaspStatus; + +public class HaspApiVersion +{ + private int major_version[] = { 0 }; + private int minor_version[] = { 0 }; + private int build_server[] = { 0 }; + private int build_number[] = { 0 }; + private int status; + + /** + * private native functions + * + */ + private static native int GetVersion(int major_version[], + int minor_version[], + int build_server[], + int build_number[], + byte vendor_code[]); + + /** + * IA 64 not considered yet + */ + static + { + HaspStatus.Init(); + } + + /** + * HaspApiVersion constructor + * + * @param vendor_code The Vendor Code. + * + */ + public HaspApiVersion(String vendor_code) + { + try + { + // Following code is added to ensure that byte array passed to JNI interface + // is NULL terminated. Ideally the JNI GetVersion interface should be accepting + // vendor_code as String like other JNI interface hasp_login, hasp_login_scope etc. + // But changing JNI interface will result in incompatible function signature + // Another solution will be to add new JNI interface + int vc_bytes_count = vendor_code.length(); + byte tmp_vendor_code[] = new byte[vc_bytes_count + 1]; + + System.arraycopy(vendor_code.getBytes("UTF-8"), 0, tmp_vendor_code, 0, vc_bytes_count); + tmp_vendor_code[vc_bytes_count] = 0; // NULL termination + + status = GetVersion(major_version, minor_version, build_server, build_number, tmp_vendor_code); + } + catch (UnsupportedEncodingException ex) + { + // cannot happen, so ignore + } + } + + /** + * Returns the error that occurred in the last function call. + */ + public int getLastError() + { + return status; + } + + /** + * Returns the HASP API major version. + */ + public int majorVersion() + { + return major_version[0]; + } + + /** + * Returns the HASP API minor version. + * + */ + public int minorVersion() + { + return minor_version[0]; + } + + /** + * Returns the HASP API build number. + * + */ + public int buildNumber() + { + return build_number[0]; + } +} + diff --git a/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/HaspStatus.java b/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/HaspStatus.java new file mode 100644 index 0000000..2dfbe3c --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/HaspStatus.java @@ -0,0 +1,727 @@ +package com.jiluo.bolt.Aladdin; + +public class HaspStatus +{ + /** + * return codes + */ + + /** + * Request successfully completed + */ + public static final int HASP_STATUS_OK = 0; + + /** + * Request exceeds memory range of a Sentinel file + */ + public static final int HASP_MEM_RANGE = 1; + + /** + * Legacy HASP HL Run-time API: Unknown/Invalid Feature ID option + */ + public static final int HASP_INV_PROGNUM_OPT = 2; + + /** + * System is out of memory + */ + public static final int HASP_INSUF_MEM = 3; + + /** + * Too many open Features/login sessions + */ + public static final int HASP_TMOF = 4; + + /** + * Access to Feature, Sentinel protection key or functionality denied + */ + public static final int HASP_ACCESS_DENIED = 5; + + /** + * Legacy decryption function cannot work on Feature + */ + public static final int HASP_INCOMPAT_FEATURE = 6; + + /** + * Sentinel protection key not available + */ + public static final int HASP_HASP_NOT_FOUND = 7; + + /** + * Deprecated - use HASP_HASP_NOT_FOUND + */ + public static final int HASP_CONTAINER_NOT_FOUND = 7; + + /** + * Encrypted/decrypted data length too short to execute function call + */ + public static final int HASP_TOO_SHORT = 8; + + /** + * Invalid login handle passed to function + */ + public static final int HASP_INV_HND = 9; + + /** + * Specified File ID not recognized by API + */ + public static final int HASP_INV_FILEID = 10; + + /** + * Installed driver or daemon too old to execute function + */ + public static final int HASP_OLD_DRIVER = 11; + + /** + * Real-time clock (rtc) not available + */ + public static final int HASP_NO_TIME = 12; + + /** + * Generic error from host system call + */ + public static final int HASP_SYS_ERR = 13; + + /** + * Required driver not installed + */ + public static final int HASP_NO_DRIVER = 14; + + /** + * Invalid XML format + */ + public static final int HASP_INV_FORMAT = 15; + + /** + * Unable to execute function in this context; the requested + * functionality is not implemented + */ + public static final int HASP_REQ_NOT_SUPP = 16; + + /** + * Binary data passed to function does not contain valid update + */ + public static final int HASP_INV_UPDATE_OBJ = 17; + + /** + * Sentinel protection key not found + */ + public static final int HASP_KEYID_NOT_FOUND = 18; + + /** + * Required XML tags not found; Contents in binary data are missing + * or invalid + */ + public static final int HASP_INV_UPDATE_DATA = 19; + + /** + * Update request not supported by Sentinel protection key + */ + public static final int HASP_INV_UPDATE_NOTSUPP = 20; + + /** + * Update counter set incorrectly + */ + public static final int HASP_INV_UPDATE_CNTR = 21; + + /** + * Invalid Vendor Code passed + */ + public static final int HASP_INV_VCODE = 22; + + /** + * Sentinel protection key does not support encryption type + */ + public static final int HASP_ENC_NOT_SUPP = 23; + + /** + * Passed time value outside supported value range + */ + public static final int HASP_INV_TIME = 24; + + /** + * Real-time clock battery out of power + */ + public static final int HASP_NO_BATTERY_POWER = 25; + + /** + * Acknowledge data requested by update, but ack_data parameter + * is NULL + */ + public static final int HASP_NO_ACK_SPACE = 26; + + /** + * Program running on a terminal server + */ + public static final int HASP_TS_DETECTED = 27; + + /** + * Requested Feature type not implemented + */ + public static final int HASP_FEATURE_TYPE_NOT_IMPL = 28; + + /** + * Unknown algorithm used in H2R/V2C file + */ + public static final int HASP_UNKNOWN_ALG = 29; + + /** + * Signature verification operation failed + */ + public static final int HASP_INV_SIG = 30; + + /** + * Requested Feature not available + */ + public static final int HASP_FEATURE_NOT_FOUND = 31; + + /** + * Access log not enabled + */ + public static final int HASP_NO_LOG = 32; + + /** + * Communication error between API and local Sentinel License Manager + */ + public static final int HASP_LOCAL_COMM_ERR = 33; + + /** + * Vendor Code not recognized by API + */ + public static final int HASP_UNKNOWN_VCODE = 34; + + /** + * Invalid XML specification + */ + public static final int HASP_INV_SPEC = 35; + + /** + * Invalid XML scope + */ + public static final int HASP_INV_SCOPE = 36; + + /** + * Too many Sentinel HASP protection keys currently connected + */ + public static final int HASP_TOO_MANY_KEYS = 37; + + /** + * Too many concurrent user sessions currently connected + */ + public static final int HASP_TOO_MANY_USERS = 38; + + /** + * Session has been interrupted + */ + public static final int HASP_BROKEN_SESSION = 39; + + /** + * Communication error between local and remote HASP License Manager + */ + public static final int HASP_REMOTE_COMM_ERR = 40; + + /** + * Feature expired + */ + public static final int HASP_FEATURE_EXPIRED = 41; + + /** + * HASP License Manager version too old + */ + public static final int HASP_OLD_LM = 42; + + /** + * Input/Output error occurred in secure storage area of HASP SL key OR + * a USB error occurred when communicating with a HASP HL key + */ + public static final int HASP_DEVICE_ERR = 43; + + /** + * Update installation not permitted; This update was already applied + */ + public static final int HASP_UPDATE_BLOCKED = 44; + + /** + * System time has been tampered with + */ + public static final int HASP_TIME_ERR = 45; + + /** + * Communication error occurred in secure channel + */ + public static final int HASP_SCHAN_ERR = 46; + + /** + * Corrupt data exists in secure storage area of HASP SL protection key + */ + public static final int HASP_STORAGE_CORRUPT = 47; + + /** + * Unable to find Vendor library + */ + public static final int HASP_NO_VLIB = 48; + + /** + * Unable to load Vendor library + */ + public static final int HASP_INV_VLIB = 49; + + /** + * Unable to locate any Features matching scope + */ + public static final int HASP_SCOPE_RESULTS_EMPTY = 50; + + /** + * Program running on a virtual machine + */ + public static final int HASP_VM_DETECTED = 51; + + /** + * HASP SL key incompatible with machine hardware; HASP SL key is locked + * to different hardware. OR: + * In the case of a V2C file, conflict between HASP SL key data and machine + * hardware data; HASP SL key locked to different hardware + */ + public static final int HASP_HARDWARE_MODIFIED = 52; + + /** + * Login denied because of user restrictions + */ + public static final int HASP_USER_DENIED = 53; + + /** + * Trying to install a V2C file with an update counter that is out of + * sequence with the update counter on Sentinel HASP protection key. The + * update counter value in the V2C file is lower than the value in Sentinel + * HASP protection key. + */ + public static final int HASP_UPDATE_TOO_OLD = 54; + + /** + * Trying to install a V2C file with an update counter that is out of + * sequence with update counter in Sentinel HASP protection key. The first + * value in the V2C file is greater than the value in Sentinel HASP + * protection key. + */ + public static final int HASP_UPDATE_TOO_NEW = 55; + + /** + * Vendor library version too old + */ + public static final int HASP_OLD_VLIB = 56; + + /** + * Upload via ACC failed, e.g. because of illegal format + */ + public static final int HASP_UPLOAD_ERROR = 57; + + /** + * Invalid XML "recipient" parameter + */ + public static final int HASP_INV_RECIPIENT = 58; + + /** + * Invalid XML "action" parameter + */ + public static final int HASP_INV_DETACH_ACTION = 59; + + /** + * Scope does not specify a unique Product + */ + public static final int HASP_TOO_MANY_PRODUCTS = 60; + + /** + * Invalid Product information + */ + public static final int HASP_INV_PRODUCT = 61; + + /** + * Unknown Recipient; update can only be applied to the + * Recipient specified in detach(), and not to this computer + */ + public static final int HASP_UNKNOWN_RECIPIENT = 62; + + /** + * Invalid duration + */ + public static final int HASP_INV_DURATION = 63; + + /** + * Cloned secure storage area detected + */ + public static final int HASP_CLONE_DETECTED = 64; + + /** + * Specified V2C update already installed in the LLM + */ + public static final int HASP_UPDATE_ALREADY_ADDED = 65; + + /** + * Specified Hasp Id is in Inactive state + */ + public static final int HASP_HASP_INACTIVE = 66; + + /** + * No detachable feature exists + */ + public static final int HASP_NO_DETACHABLE_FEATURE = 67; + + /** + * No detachable feature exists (typo; kept for compatibility) + */ + public static final int HASP_NO_DEATCHABLE_FEATURE = 67; + + /** + * scope does not specify a unique Host + */ + public static final int HASP_TOO_MANY_HOSTS = 68; + + /** + * Rehost is not allowed for any license + */ + public static final int HASP_REHOST_NOT_ALLOWED = 69; + + /** + * License is rehosted to other machine + */ + public static final int HASP_LICENSE_REHOSTED = 70; + + /** + * Old rehost license try to apply + */ + public static final int HASP_REHOST_ALREADY_APPLIED = 71; + + /** + * File not found or access denied + */ + public static final int HASP_CANNOT_READ_FILE = 72; + + /** + * Extension of license not allowed as number of detached + * licenses is greater than current concurrency count + */ + public static final int HASP_EXTENSION_NOT_ALLOWED = 73; + + /** + * Detach of license not allowed as product + * contains VM disabled feature and host machine is a virtual machine + */ + public static final int HASP_DETACH_DISABLED = 74; + + /** + * Rehost of license not allowed as container + * contains VM disabled feature and host machine is a virtual machine + */ + public static final int HASP_REHOST_DISABLED = 75; + + /** + * Format SL-AdminMode or migrate SL-Legacy to SL-AdminMode not allowed + * as container has detached license + */ + public static final int HASP_DETACHED_LICENSE_FOUND = 76; + + /** + * Recipient of the requested operation is older than expected + */ + public static final int HASP_RECIPIENT_OLD_LM = 77; + + /** + * Secure storage ID mismatch + */ + public static final int HASP_SECURE_STORE_ID_MISMATCH = 78; + + /** + * Duplicate Hostname found while key contains Hostname Fingerprinting + */ + public static final int HASP_DUPLICATE_HOSTNAME = 79; + + /** + * The Sentinel License Manager is required for this operation + */ + public static final int HASP_MISSING_LM = 80; + + /** + * Attempting to consume multiple executions during log in to a Feature. + * However, the license for the Feature does not contain enough remaining executions + */ + public static final int HASP_FEATURE_INSUFFICIENT_EXECUTION_COUNT = 81; + + /** + * Attempting to perform an operation not compatible with target platform + */ + public static final int HASP_INCOMPATIBLE_PLATFORM = 82; + + /** + * The key is disabled due to suspected tampering + */ + public static final int HASP_HASP_DISABLED = 83; + + /** + * The key is inaccessible due to sharing + */ + public static final int HASP_SHARING_VIOLATION = 84; + + /** + * The session was killed due a network malfunction or manually from ACC + */ + public static final int HASP_KILLED_SESSION = 85; + + /** + * Program running on a virtual storage + */ + public static final int HASP_VS_DETECTED = 86; + + /** + * An identity is required + */ + public static final int HASP_IDENTITY_REQUIRED = 87; + + /** + * The identity is not authenticated + */ + public static final int HASP_IDENTITY_UNAUTHENTICATED = 88; + + /** + * The identity is disabled + */ + public static final int HASP_IDENTITY_DISABLED = 89; + + /** + * The identity doesn't have enough permission for the operation + */ + public static final int HASP_IDENTITY_DENIED = 90; + + /** + * A session for this identity from a different machine already exists + */ + public static final int HASP_IDENTITY_SHARING_VIOLATION = 91; + + /** + * The maximum number of machines usable by the identity was reached + */ + public static final int HASP_IDENTITY_TOO_MANY_MACHINES = 92; + + /** + * The server is not ready to authenticate + */ + public static final int HASP_IDENTITY_SERVER_NOT_READY = 93; + + /** + * Trying to install a V2C file with an update counter that is out of + * sync with update counter in the Sentinel protection key + */ + public static final int HASP_UPDATE_OUT_OF_SYNC = 94; + + /** + * Multiple attemps to access the key from remote with a proxy + */ + public static final int HASP_REMOTE_SHARING_VIOLATION = 95; + + /** + * The session was released because the seat was requested from a different location + */ + public static final int HASP_CLOUD_SESSION_OCCUPIED_REMOTELY = 96; + + /** + * Cloud licensing authorization is required to use this license + */ + public static final int HASP_CLOUD_MISSING_AUTHORIZATION = 97; + + /** + * Invalid seat value in network detach. Seat count cannot be decreased when modifying a detach + */ + public static final int HASP_INV_NETWORK_SEATS = 98; + + /** + * Network detach is disabled on products with only unlimited concurrency features + */ + public static final int HASP_NETWORK_DETACH_DISABLED = 99; + + /** + * The requested cloud functionality is not supported + */ + public static final int HASP_CLOUD_NOT_SUPP = 100; + + /** + * Only trusted licenses can be installed in the trusted license storage + */ + public static final int HASP_CLOUD_NOT_TRUSTED = 101; + + /** + * Communication error with the license storage + */ + public static final int HASP_CLOUD_STORAGE_COMM_ERR = 102; + + /** + * The identity is expired + */ + public static final int HASP_IDENTITY_EXPIRED = 103; + + /** + * Invalid option value + */ + public static final int HASP_INV_OPTION = 104; + + /** + * Busy error with the license storage + */ + public static final int HASP_CLOUD_STORAGE_BUSY = 105; + + /** + * This machine cannot be used + */ + public static final int HASP_MACHINE_DENIED = 106; + + /** + * This machine is disabled + */ + public static final int HASP_MACHINE_DISABLED = 107; + + /** + * The rate at which identity requests are received exceeds the contracted limit + */ + public static final int HASP_IDENTITY_RATE_LIMIT_EXCEEDED = 108; + + /** + * Feature start date not yet reached + */ + public static final int HASP_FEATURE_START_DATE_NOT_REACHED = 109; + + /** + * API dispatcher: API for this Vendor Code was not found + */ + public static final int HASP_NO_API_DYLIB = 400; + + /** + * API dispatcher: Unable to load API; DLL possibly corrupt? + */ + public static final int HASP_INV_API_DYLIB = 401; + + /** + * API dispatcher: Unable to find API function; DLL possibly old? + */ + public static final int HASP_INCOMPLETE_API_DYLIB = 402; + + /** + * C++ API: Object incorrectly initialized + */ + public static final int HASP_INVALID_OBJECT = 500; + + /** + * Invalid function parameter + */ + public static final int HASP_INV_PARAM = 501; + + /** + * C++ API: Logging in twice to the same object + */ + public static final int HASP_ALREADY_LOGGED_IN = 502; + + /** + * C++ API: Logging out twice of the same object + */ + public static final int HASP_ALREADY_LOGGED_OUT = 503; + + /** + * .NET API: Incorrect use of system or platform + */ + public static final int HASP_OPERATION_FAILED = 525; + + /* + * Internal use: no classic memory extension block available + */ + public static final int HASP_NO_EXTBLOCK = 600; + + /* + * Internal use: invalid port type + */ + public static final int HASP_INV_PORT_TYPE = 650; + + /* + * Internal use: invalid port value + */ + public static final int HASP_INV_PORT = 651; + + /* + * Dot-Net DLL found broken + */ + public static final int HASP_NET_DLL_BROKEN = 652; + + /** + * Requested function not implemented + */ + public static final int HASP_NOT_IMPL = 698; + + /** + * Internal error occurred in API + */ + public static final int HASP_INT_ERR = 699; + + /* + * Reserved for Sentinel helper libraries + */ + public static final int HASP_FIRST_HELPER = 2001; + + /* + * Reserved for Sentinel Activation API + */ + public static final int HASP_FIRST_HASP_ACT = 3001; + + public static final int HASP_NEXT_FREE_VALUES = 7001; + + public static String runtime_library_default = "HASPJava"; + public static String runtime_library_x64_windows = "HASPJava_x64"; + public static String runtime_library_x64_linux = "HASPJava_x86_64"; + public static String runtime_library_armhf_linux = "HASPJava_armhf"; + public static String runtime_library_arm64_linux = "HASPJava_arm64"; + + public static void Init() + { + String operatingSystem = System.getProperty("os.name"); + String architecture = System.getProperty("os.arch"); + + try + { + if (operatingSystem.indexOf("Windows") != -1) + { + if (architecture.equals("x86_64") || architecture.equals("amd64")) + { + System.loadLibrary(runtime_library_x64_windows); + } + else + { + System.loadLibrary(runtime_library_default); + } + } + else if (operatingSystem.indexOf("Linux") != -1) + { + if (architecture.equals("x86_64") || architecture.equals("amd64")) + { + System.loadLibrary(runtime_library_x64_linux); + } + else if (architecture.equals("arm")) + { + System.loadLibrary(runtime_library_armhf_linux); + } + else if (architecture.equals("aarch64")) + { + System.loadLibrary(runtime_library_arm64_linux); + } + else + { + System.loadLibrary(runtime_library_default); + } + } + else + { + System.loadLibrary(runtime_library_default); + } + } + catch (UnsatisfiedLinkError e) + { + if (e.getMessage().indexOf("already loaded in another classloader") == -1) + { + throw e; + } + } + } +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/HaspTime.java b/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/HaspTime.java new file mode 100644 index 0000000..7125825 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/Aladdin/HaspTime.java @@ -0,0 +1,118 @@ +package com.jiluo.bolt.Aladdin; + +import com.jiluo.bolt.Aladdin.HaspStatus; + +public class HaspTime +{ + private long time[] = {0}; + private int day[] = {0}; + private int month[] = {0}; + private int year[] = {0}; + private int hour[] = {0}; + private int minute[] = {0}; + private int second[] = {0}; + private int status; + + /* + * private native functions + * + */ + private static native int DatetimeToHasptime(int day, int month, int year, int hour, int minute, int second, long time[]); + private static native int HasptimeToDatetime(long time, int day[], int month[], int year[],int hour[], int minute[], int second[]); + + /** + * IA 64 not considered yet + */ + static + { + HaspStatus.Init(); + } + + /** + * HaspTime constructor. + * + * @param year input year + * @param month input month + * @param day input day + * @param hour input hour + * @param minute input minute + * @param second input second + * + */ + public HaspTime(int year, int month, int day, int hour, + int minute, int second) + { + status = DatetimeToHasptime(day, month, year, hour, minute, second, time); + } + + public HaspTime(long hasptime) + { + time[0] = hasptime; + status = HasptimeToDatetime(hasptime,day,month,year,hour,minute,second); + } + + /** + * Returns the error that occurred in the last function call. + */ + public int getLastError() + { + return status; + } + + /** + * Returns the HASP Time value in UTC format. + */ + public long getHaspTime() + { + return time[0]; + } + + /** + * Returns the month value of the time. + */ + public int getMonth() + { + return month[0]; + } + + /** + * Returns the year value of the time. + */ + public int getYear() + { + return year[0]; + } + + /** + * Returns the day value of the time. + */ + public int getDay() + { + return day[0]; + } + + /** + * Returns the hour value of the time. + */ + public int getHour() + { + return hour[0]; + } + + /** + * Returns the minute value of the time. + */ + public int getMinute() + { + return minute[0]; + } + + /** + * Returns the second value of the time. + */ + public int getSecond() + { + return second[0]; + } +} + diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/BigDecimalValueFilter.java b/bolt-api/src/main/java/com/jiluo/bolt/common/BigDecimalValueFilter.java new file mode 100644 index 0000000..fd4e0b1 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/BigDecimalValueFilter.java @@ -0,0 +1,23 @@ +package com.jiluo.bolt.common; + +import com.alibaba.fastjson.serializer.ValueFilter; + +import java.math.BigDecimal; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/28/9:40 + * @Description: + */ +public class BigDecimalValueFilter implements ValueFilter { + @Override + public Object process(Object object, String name, Object value) { + if (value instanceof BigDecimal) { + value = ((BigDecimal) value).setScale(2, BigDecimal.ROUND_HALF_EVEN); + return String.format("%.2f", value); + } + return value; + } +} \ No newline at end of file diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/CameraDriverEnum.java b/bolt-api/src/main/java/com/jiluo/bolt/common/CameraDriverEnum.java new file mode 100644 index 0000000..a2c8aaa --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/CameraDriverEnum.java @@ -0,0 +1,35 @@ +package com.jiluo.bolt.common; + +import lombok.Getter; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/17/10:33 + * @Description: + */ +@Getter +public enum CameraDriverEnum { + LucidCamera("lucid_camera","Lucid"), + BaslerCamera("basler_camera","Basler"), + BaumerCamera("baumer_camera","Baumer"); + + String vender; + + String driverName; + + CameraDriverEnum(String vender, String driverName){ + this.vender = vender; + this.driverName = driverName; + } + + public static String getDriverName(String vender){ + for (CameraDriverEnum driver: CameraDriverEnum.values()) { + if (driver.getVender().equals(vender)){ + return driver.getDriverName(); + } + } + return null; + } +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/DefectType.java b/bolt-api/src/main/java/com/jiluo/bolt/common/DefectType.java new file mode 100644 index 0000000..3ef7977 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/DefectType.java @@ -0,0 +1,50 @@ +package com.jiluo.bolt.common; + +import lombok.Getter; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/27/14:12 + * @Description:检测结果缺陷类型 + */ +@Getter +public enum DefectType { + bolt("螺栓松动", DetectType.BOLT_AND_LINE, 0), + + line("引出线变形", DetectType.BOLT_AND_LINE, 0), + + pole("磁极开闸", DetectType.BOLT_AND_LINE, 0), + + temperature("无线测温", DetectType.TEMPERATURE, 0); + + @Getter + String desc; + + @Getter + DetectType detectType; + + @Getter + BigDecimal defaultValue; + + DefectType(String desc, DetectType detectType, int defaultValue) { + this.desc = desc; + this.detectType = detectType; + this.defaultValue = new BigDecimal(defaultValue).setScale(2, RoundingMode.HALF_UP); + } + + public static DefectType toDefectType(String type) { + return Arrays.stream(DefectType.values()).filter(_t -> _t.getDesc().equalsIgnoreCase(type) || _t.name().equalsIgnoreCase(type)).findFirst().orElse(null); + } + + public static List defectTypes(String product) { + return Arrays.stream(DefectType.values()).filter(_t -> _t.getDetectType().getProduct().equalsIgnoreCase(product)).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/DetectAttribute.java b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectAttribute.java new file mode 100644 index 0000000..b0ae369 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectAttribute.java @@ -0,0 +1,38 @@ +package com.jiluo.bolt.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/11/16:12 + * @Description:检测结果描述 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DetectAttribute { + private String productId; + + private String productName; + + private Integer detectZone; + + private Integer detectTotal; + + private Integer detectDuration; + + private Integer defectTotal; + + private Map> defectZone; + + private String detectTrack; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/DetectConfig.java b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectConfig.java new file mode 100644 index 0000000..5ad51e0 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectConfig.java @@ -0,0 +1,68 @@ +package com.jiluo.bolt.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/11/14:19 + * @Description:检测参数配置 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DetectConfig { + + private Boolean bolt; + + private Boolean line; + + private Boolean pole; + + private Boolean temp; + + private Double detect_threshold_bolt; + + private Double detect_threshold_line; + + private Double detect_threshold_pole; + + private Double detect_threshold_temperature; + + private Integer detect_duration; + + private Integer detect_work_zone; + + private String defect_work_dir; + + private Integer delayDetect; + + private String plc_ip; + + private Integer plc_delay; + + private String serial_number; + + private Integer width; + + private Integer height; + + private Double fps; + + private Double exposureTime; + + private String jobId; + + private String algorithm_id; + + private String algorithm_type; + + private String algorithm_name; + + private String algorithm_model; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/DetectJob.java b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectJob.java new file mode 100644 index 0000000..76a8f42 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectJob.java @@ -0,0 +1,34 @@ +package com.jiluo.bolt.common; + +import com.alibaba.fastjson.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:56 + * @Description:检测任务 + */ + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DetectJob { + private String jobId; + private String powerStation; + private String motorGroup; + private String point; + private String deviceId; + private JSONObject attribute; + private JSONObject config; + + /** + * 当前的检测状态 0-检测结束,结果正常,1-检测结束,结果异常,2-检测过程中程序错误,3-检测中 + */ + private Integer status; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/DetectMessage.java b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectMessage.java new file mode 100644 index 0000000..3e1a88c --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectMessage.java @@ -0,0 +1,25 @@ +package com.jiluo.bolt.common; + +import com.alibaba.fastjson.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/09/18/16:24 + * @Description:检测结果Websocket消息队列 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DetectMessage { + + private String pointId; + + private JSONObject jsonObject; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/DetectResult.java b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectResult.java new file mode 100644 index 0000000..60fbf85 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectResult.java @@ -0,0 +1,26 @@ +package com.jiluo.bolt.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/10/11:08 + * @Description:统一检测结果 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DetectResult { + private int zone; // 区域, 如: 1号扇区 + private int position; // 位置, 如: 3号螺栓,5号引出线 + private String type; // 类型, 如:螺栓,引出线 + private Float value; // 检测值 + private String img; + private int alarm; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/DetectResultKey.java b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectResultKey.java new file mode 100644 index 0000000..fdb0659 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectResultKey.java @@ -0,0 +1,50 @@ +package com.jiluo.bolt.common; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/26/14:07 + * @Description: + */ +public class DetectResultKey { + private int zone; + private int position; + private String type; + + public DetectResultKey(DetectResult detect) { + this.zone = detect.getZone(); + this.position = detect.getPosition(); + this.type = detect.getType(); + } + + // 实现hashCode()和equals()方法以便正确操作HashMap + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + zone; + result = prime * result + position; + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + DetectResultKey other = (DetectResultKey) obj; + if (zone != other.zone) + return false; + if (position != other.position) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return true; + } +} \ No newline at end of file diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/DetectType.java b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectType.java new file mode 100644 index 0000000..39eda68 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/DetectType.java @@ -0,0 +1,34 @@ +package com.jiluo.bolt.common; + +import lombok.Getter; + +import java.util.Arrays; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/27/14:14 + * @Description:检测类型 + */ +@Getter +public enum DetectType { + BOLT_AND_LINE("BOLT_AND_LINE", "螺栓引出线磁极开闸检测"), + + TEMPERATURE("TEMPERATURE", "无线测温"); + + @Getter + String desc; + + @Getter + String product; + + DetectType(String product, String desc) { + this.desc = desc; + this.product = product; + } + + public static DetectType toDetectType(String type) { + return Arrays.stream(DetectType.values()).filter(_t -> _t.getProduct().equalsIgnoreCase(type) || _t.name().equalsIgnoreCase(type)).findFirst().orElse(null); + } +} \ No newline at end of file diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/LocalStatus.java b/bolt-api/src/main/java/com/jiluo/bolt/common/LocalStatus.java new file mode 100644 index 0000000..610b475 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/LocalStatus.java @@ -0,0 +1,28 @@ +package com.jiluo.bolt.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/19/10:06 + * @Description:设备状态-内存存储 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LocalStatus { + + private static final long serialVersionUID = 1L; + + private Integer cameraStatus; //相机设备状态:0-停用,1-正常,2-离线 + + private Integer pointStatus; //检测点状态:0-检测正常,1-检测异常,2-设备掉线 + + private Integer tempSensorStatus; //温度传感器状态:0-停用,1-正常,2-离线 +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/common/PermissionValue.java b/bolt-api/src/main/java/com/jiluo/bolt/common/PermissionValue.java new file mode 100644 index 0000000..1a3aedf --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/common/PermissionValue.java @@ -0,0 +1,28 @@ +package com.jiluo.bolt.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/06/11:12 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PermissionValue implements Serializable { + + private static final long serialVersionUID = 1L; + + private String permissionId; + + private Boolean value; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/AlgorithmConfigDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/AlgorithmConfigDto.java new file mode 100644 index 0000000..8e0cdef --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/AlgorithmConfigDto.java @@ -0,0 +1,39 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.*; +import lombok.experimental.Accessors; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.List; + +/** + * (AlgorithmConfig)表实体类 + * + * @author Fangy + * @since 2023-05-08 17:46:48 + */ +@Data +@Builder +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class AlgorithmConfigDto implements Serializable { + private static final long serialVersionUID = 1L; + + private String algorithmConfigId; + + private String name; + + private Double boltThreshold; + + private Double lineThreshold; + + @DateTimeFormat(pattern = "HH:mm") + private List dailyAutoDetectionTime; + + private Integer delayDetect; + + private List pointIdList; + +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/AlgorithmDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/AlgorithmDto.java new file mode 100644 index 0000000..641e2fd --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/AlgorithmDto.java @@ -0,0 +1,41 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/15:43 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AlgorithmDto implements Serializable { + + private static final long serialVersionUID = 1L; + + private String algorithmId; + + private String source; + + private String algorithmName; + + private String algorithmFileName; + + private String powerStationId; + + private String motorGroupId; + + private String pointId; + + List points; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/AlgorithmModelDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/AlgorithmModelDto.java new file mode 100644 index 0000000..8578a3b --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/AlgorithmModelDto.java @@ -0,0 +1,26 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/15/16:59 + * @Description: + */ +@Data +@Builder +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +public class AlgorithmModelDto { + + private String detect_threshold_model; + + private String detect_threshold_bk; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/DetectDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/DetectDto.java new file mode 100644 index 0000000..3484c51 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/DetectDto.java @@ -0,0 +1,53 @@ +package com.jiluo.bolt.entity.dto; + +import java.io.Serializable; +import java.util.List; + +import lombok.Data; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Builder; +import org.springframework.format.annotation.DateTimeFormat; + +/** + * 检测过程表(Detect)实体类 + * + * @author Fangy + * @since 2023-05-05 09:57:05 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DetectDto implements Serializable { + private static final long serialVersionUID = -11167425972916095L; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + private String startTime; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + private String endTime; + + Integer current; + + Integer size; + + private Integer id; + + private String powerStationId; + + private String motorGroupId; + + private String pointId; + + private String jobId; + + private Integer zone; + + private Integer status; + + private List features; + + private List jobs; +} + diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/DetectResultDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/DetectResultDto.java new file mode 100644 index 0000000..6ba73bf --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/DetectResultDto.java @@ -0,0 +1,30 @@ +package com.jiluo.bolt.entity.dto; + +import com.jiluo.bolt.common.DetectResult; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/14/16:56 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DetectResultDto implements Serializable { + + private static final long serialVersionUID = 1L; + + private String jobId; + + private List detectList; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/DeviceDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/DeviceDto.java new file mode 100644 index 0000000..514cbb1 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/DeviceDto.java @@ -0,0 +1,92 @@ +package com.jiluo.bolt.entity.dto; + +import java.math.BigDecimal; +import java.util.Date; +import java.io.Serializable; + +import lombok.Data; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Builder; +import org.hibernate.validator.constraints.Length; + +/** + * 设备信息表(Device)实体类 + * + * @author Fangy + * @since 2023-05-05 09:57:05 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class DeviceDto implements Serializable { + private static final long serialVersionUID = -34441038340206111L; + + private Date gmtCreate; + + private Date gmtModify; + + private Integer id; + /** + * 设备编号 + */ + @Length(max=50,message = "输入长度应小于{max}字") + private String deviceId; + /** + * 设备类别 + */ + private String type; + /** + * 设备名称 + */ + @Length(max=50,message = "输入长度应小于{max}字") + private String name; + /** + * 关联电站 + */ + private String powerStationId; + /** + * 关联检测类型 + */ + private String product; + /** + * 关联机组 + */ + private String motorGroupId; + /** + * 关联检测点位 + */ + private String pointId; + /** + * 设备状态:0-停用,1-正常,2-离线 + */ + private Integer status; + /** + * 工作保护温度 + */ + private BigDecimal tempThreshold; + /** + * 设备配置属性 + */ + private String config; + /** + * 标识厂商 + */ + private String nameOrSN; + + private String sn; + + private String vender; + + private Double fps; + + private Double exposureTime; + + @Length(max=50,message = "输入长度应小于{max}字") + private String plcIp; + + private Integer plcDelay; + +} + diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/IdToNameDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/IdToNameDto.java new file mode 100644 index 0000000..7b684ca --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/IdToNameDto.java @@ -0,0 +1,23 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/06/15/14:59 + * @Description: + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class IdToNameDto { + private static final long serialVersionUID = -18677499673088561L; + + private String id; + + private String name; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/ImgDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/ImgDto.java new file mode 100644 index 0000000..2461bf0 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/ImgDto.java @@ -0,0 +1,23 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/06/06/15:27 + * @Description: + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ImgDto { + private static final long serialVersionUID = -18677499673088561L; + + private String jobId; + + private String img; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/MotorGroupDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/MotorGroupDto.java new file mode 100644 index 0000000..6924f1b --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/MotorGroupDto.java @@ -0,0 +1,38 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import java.io.Serializable; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/06/26/15:08 + * @Description: + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MotorGroupDto implements Serializable { + + private static final long serialVersionUID = 1L; + + private String powerStationId; + + private String motorGroupId; + + @Length(max=50,message = "输入长度应小于{max}字") + private String number; + + @Length(max=50,message = "输入长度应小于{max}字") + private String contact; + + @Length(max=50,message = "输入长度应小于{max}字") + private String phone; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/PermissionDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/PermissionDto.java new file mode 100644 index 0000000..df8c192 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/PermissionDto.java @@ -0,0 +1,30 @@ +package com.jiluo.bolt.entity.dto; + +import com.jiluo.bolt.common.PermissionValue; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/06/11:06 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PermissionDto implements Serializable { + + private static final long serialVersionUID = 1L; + + private String roleId; + + private List permissionList; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/PointDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/PointDto.java new file mode 100644 index 0000000..845dc99 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/PointDto.java @@ -0,0 +1,56 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.*; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/06/27/10:18 + * @Description: + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PointDto implements Serializable { + + private static final long serialVersionUID = 1L; + + private String powerStationId; + + private String motorGroupId; + + private String pointId; + + @Length(max=10,message = "输入长度应小于{max}字") + private String name; + + @Min(value = 1, message = "磁极数量应大于1个") + @Max(value = 999, message = "磁极数量应小于999个") + private Integer poleNum; + + @DecimalMin(value = "0.0", inclusive = false, message = "检测时长应大于0s") + @DecimalMax(value = "600.0", inclusive = false, message = "检测时长应小于600s") + private BigDecimal manualTime; + + @DecimalMin(value = "0.0", inclusive = false, message = "检测时长应大于0s") + @DecimalMax(value = "600.0", inclusive = false, message = "检测时长应小于600s") + private BigDecimal automaticTime; + + private Boolean boltDetect; + + private Boolean lineDetect; + + private Boolean poleOpenDetect; + + private Boolean pointTempDetect; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/PowerStationDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/PowerStationDto.java new file mode 100644 index 0000000..f09a216 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/PowerStationDto.java @@ -0,0 +1,45 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/06/26/10:10 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PowerStationDto implements Serializable { + + private static final long serialVersionUID = 1L; + + private String powerStationId; + + @NotBlank(message = "名称不能为空") + @Length(max=50,message = "输入长度应小于{max}字") + private String name; + + @Length(max=50,message = "输入长度应小于{max}字") + private String address; + + @Length(max=50,message = "输入长度应小于{max}字") + private String contact; + + @Length(max=50,message = "输入长度应小于{max}字") + private String phone; + + @Length(max=200,message = "输入长度应小于{max}字") + private String introduction; + +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/RoleDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/RoleDto.java new file mode 100644 index 0000000..522bfee --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/RoleDto.java @@ -0,0 +1,30 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/06/9:59 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RoleDto implements Serializable { + + private static final long serialVersionUID = 1L; + + private String roleId; + + private String roleName; + + +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/SystemInfoConfigDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/SystemInfoConfigDto.java new file mode 100644 index 0000000..752f21a --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/SystemInfoConfigDto.java @@ -0,0 +1,40 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import java.io.Serializable; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/05/13:08 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SystemInfoConfigDto implements Serializable { + + private static final long serialVersionUID = 1L; + + private String systemInfoId; + + private String systemInfoName; + + private String logoFileName; + + @Length(max=10,message = "输入长度应小于{max}字") + private String systemName; + + List powerStations; + + private String reservedField; + +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/TempSenserDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/TempSenserDto.java new file mode 100644 index 0000000..cfd9710 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/TempSenserDto.java @@ -0,0 +1,60 @@ +package com.jiluo.bolt.entity.dto; + +import com.alibaba.fastjson.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import java.io.Serializable; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/11/16:41 + * @Description: + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TempSenserDto implements Serializable { + + private static final long serialVersionUID = 1L; + + private String deviceId; + @Length(max=50,message = "输入长度应小于{max}字") + private String name; + + private String serialPort; + + private String nameOrSerialPort; + + private String type; + + private String powerStationId; + + private String motorGroupId; + + private String pointId; + + private String powerStation; + + private String Group_Point; + + private String relatedDeviceId; + + private String relatedDevice; + + private Integer status; + + private String typeId; + + private String jobId; + + private Double value; + + private JSONObject config; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/TempThresholdDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/TempThresholdDto.java new file mode 100644 index 0000000..0609a63 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/TempThresholdDto.java @@ -0,0 +1,34 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/10/15:33 + * @Description: + */ +@Data +@Builder +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +public class TempThresholdDto { + private static final long serialVersionUID = 1L; + + private String tempThresholdId; + + private String name; + + private BigDecimal tempThreshold; + + private List deviceIdList; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/ThemeConfigDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/ThemeConfigDto.java new file mode 100644 index 0000000..4fc55b7 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/ThemeConfigDto.java @@ -0,0 +1,46 @@ +package com.jiluo.bolt.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/05/15:58 + * @Description: + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ThemeConfigDto implements Serializable { + + private static final long serialVersionUID = 1L; + + private String themeId; + + private String themeName; + + private String primary; + private String primary_btn_color; + private String bg_color; + private String border_color; + private String nav_bg; + private String text_color; + private String desc_color; + private String thead_bg; + private String tag_bg; + private String tag_color; + private String card_text_color; + private String card_hd_bg_color; + private String corner_color; + private String title_color; + + List powerStationList; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/UserDto.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/UserDto.java new file mode 100644 index 0000000..9adc2cb --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/dto/UserDto.java @@ -0,0 +1,54 @@ +package com.jiluo.bolt.entity.dto; + +import java.util.Date; +import java.io.Serializable; +import lombok.Data; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Builder; +import org.hibernate.validator.constraints.Length; + +/** + * 用户信息表(User)实体类 + * + * @author Fangy + * @since 2023-05-05 09:57:05 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserDto implements Serializable { + private static final long serialVersionUID = -18677499673088561L; + + private Date gmtCreate; + + private Date gmtModify; + + private Integer id; + /** + * 用户编号 + */ + @Length(max=50,message = "输入长度应小于{max}字") + private String uid; + /** + * 用户姓名 + */ + @Length(max=50,message = "输入长度应小于{max}字") + private String userName; + /** + * 用户密码 + */ + @Length(max=50,message = "输入长度应小于{max}字") + private String password; + /** + * 用户类型 + */ + private String role; + + private String clientVersion; + + private String clientType; + +} + diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/AlgorithmVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/AlgorithmVo.java new file mode 100644 index 0000000..9ce2853 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/AlgorithmVo.java @@ -0,0 +1,35 @@ +package com.jiluo.bolt.entity.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/15:37 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AlgorithmVo { + + private String AlgorithmId; + + private String name; + + private String source; + + private Integer group_point_num; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date gmtCreate; + +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/DefectVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/DefectVo.java new file mode 100644 index 0000000..766d4c3 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/DefectVo.java @@ -0,0 +1,41 @@ +package com.jiluo.bolt.entity.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.jiluo.bolt.common.DefectType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.math.BigDecimal; +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/21/18:35 + * @Description:检测结果 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DefectVo { + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date time; + + private DefectType type; + + private Integer zone; + + private Integer position; + + private String img; + + private BigDecimal value; + + private Boolean alarm; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/DeviceVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/DeviceVo.java new file mode 100644 index 0000000..c6735eb --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/DeviceVo.java @@ -0,0 +1,56 @@ +package com.jiluo.bolt.entity.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/06/28/15:22 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DeviceVo { + + private String deviceId; + + private String name; + + private String sn; + + private String plcIp; + + private String powerStationId; + + private String motorGroupId; + + private String pointId; + + private String powerStation; + + private String Group_Point; + + private Double fps; + + private Double exposureTime; + + private Integer status; + + private String venderId; + + private String vender; + + private Integer plcDelay; + + @JsonFormat(pattern = "yyyy-MM-dd") + private Date gmtCreate; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/DropDownVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/DropDownVo.java new file mode 100644 index 0000000..fb31766 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/DropDownVo.java @@ -0,0 +1,28 @@ +package com.jiluo.bolt.entity.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/11:51 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DropDownVo { + + private String powerStationId; + + private String motorGroupId; + + private String pointId; + + private String name; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/HistoricalDataVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/HistoricalDataVo.java new file mode 100644 index 0000000..6f80810 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/HistoricalDataVo.java @@ -0,0 +1,39 @@ +package com.jiluo.bolt.entity.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/21/17:01 + * @Description:历史数据 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class HistoricalDataVo { + + private String jobId; + + private String motorGroup; + + private String point; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date gmtCreate; + + private String type; + + private Integer status; + + private String info; + +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/MotorGroupVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/MotorGroupVo.java new file mode 100644 index 0000000..dc7390d --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/MotorGroupVo.java @@ -0,0 +1,39 @@ +package com.jiluo.bolt.entity.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/06/26/15:16 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MotorGroupVo { + private String motorGroupId; + + private String name; + + private String powerStationId; + + private String powerStation; + + private Integer pointNum; + + private String contact; + + private String phone; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date gmtCreate; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/PermissionVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/PermissionVo.java new file mode 100644 index 0000000..689a62c --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/PermissionVo.java @@ -0,0 +1,29 @@ +package com.jiluo.bolt.entity.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/06/10:55 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PermissionVo { + + private String permissionId; + + private String permissionName; + + private boolean value; + + //应用范围 0-前端检测系统权限;1-后台管理系统权限 + private Integer scope; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/PointVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/PointVo.java new file mode 100644 index 0000000..b9e00a9 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/PointVo.java @@ -0,0 +1,52 @@ +package com.jiluo.bolt.entity.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/06/27/10:38 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PointVo { + + private String pointId; + + private String name; + + private String motorGroupId; + + private String motorGroup; + + private String powerStationId; + + private String powerStation; + + private Integer poleNum; + + private Boolean boltDetect; + + private Boolean lineDetect; + + private Boolean poleOpenDetect; + + private Boolean pointTempDetect; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date gmtCreate; + + private Integer manualTime; + + private Integer automaticTime; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/PowerStationVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/PowerStationVo.java new file mode 100644 index 0000000..4280c5a --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/PowerStationVo.java @@ -0,0 +1,39 @@ +package com.jiluo.bolt.entity.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/06/26/10:06 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PowerStationVo { + private String powerStationId; + + private String name; + + private String address; + + private Integer groupNum; + + private String contact; + + private String phone; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date gmtCreate; + + private String introduction; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/RoleVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/RoleVo.java new file mode 100644 index 0000000..d89ba04 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/RoleVo.java @@ -0,0 +1,46 @@ +package com.jiluo.bolt.entity.vo; + +import java.io.Serializable; +import lombok.Data; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Builder; + +/** + * 角色信息表(Role)实体类 + * + * @author Fangy + * @since 2023-05-05 09:57:05 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class RoleVo implements Serializable { + private static final long serialVersionUID = -63387953100329487L; + + /** + * 权限编号 + */ + private String roleId; + + /** + * 权限名称 + */ + private String name; + + /** + * 权限值 + */ + private Object value; + + + private Integer scope; + + /** + * 0-菜单权限,1-检测功能权限,2-机组权限 + */ + private Integer type; + +} + diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/SystemInfoConfigVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/SystemInfoConfigVo.java new file mode 100644 index 0000000..c183567 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/SystemInfoConfigVo.java @@ -0,0 +1,38 @@ +package com.jiluo.bolt.entity.vo; + +import com.alibaba.fastjson.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/05/14:41 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SystemInfoConfigVo { + + private String systemInfoId; + + private String systemInfoName; + + private String logoImg; + + private String logoFileName; + + private String systemName; + + private List powerStations; + + private String reservedField; + +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/TempSenserVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/TempSenserVo.java new file mode 100644 index 0000000..7466c76 --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/TempSenserVo.java @@ -0,0 +1,52 @@ +package com.jiluo.bolt.entity.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/11/16:38 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TempSenserVo { + + private String deviceId; + + private String name; + + private String serialPort; + + private String typeId; + + private String type; + + private String powerStationId; + + private String motorGroupId; + + private String pointId; + + private String powerStation; + + private String Group_Point; + + private String relatedDeviceId; + + private String relatedDevice; + + private Integer status; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date gmtCreate; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/ThemeConfigVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/ThemeConfigVo.java new file mode 100644 index 0000000..63fddcc --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/ThemeConfigVo.java @@ -0,0 +1,39 @@ +package com.jiluo.bolt.entity.vo; + +import com.alibaba.fastjson.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/05/15:48 + * @Description: + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ThemeConfigVo { + + private String themeId; + + private String themeName; + + private String themeColor; + + private String textColor; + + private String topMenuSelectColor; + + private String secondMenuSelectColor; + + private String guideColor; + + List powerStations; +} diff --git a/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/UserVo.java b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/UserVo.java new file mode 100644 index 0000000..6ab169f --- /dev/null +++ b/bolt-api/src/main/java/com/jiluo/bolt/entity/vo/UserVo.java @@ -0,0 +1,49 @@ +package com.jiluo.bolt.entity.vo; + +import java.util.Date; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Builder; + +/** + * 用户信息表(User)实体类 + * + * @author Fangy + * @since 2023-05-05 09:57:05 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserVo { + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Integer id; + /** + * 用户编号 + */ + private String uid; + /** + * 用户姓名 + */ + private String userName; + /** + * 用户类型 + */ + private String role; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private List permissionList; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date gmtCreate; + + private String password; + +} + diff --git a/bolt-core/pom.xml b/bolt-core/pom.xml new file mode 100644 index 0000000..fbdf4e4 --- /dev/null +++ b/bolt-core/pom.xml @@ -0,0 +1,76 @@ + + + + bolt-server + com.jiluo.bolt + 0.0.1-SNAPSHOT + + 4.0.0 + + bolt-core + + + + com.jiluo.bolt + bolt-dao + 0.0.1-SNAPSHOT + + + + com.jiluo.bolt + bolt-api + 0.0.1-SNAPSHOT + + + + + com.auth0 + java-jwt + + + + + cn.hutool + hutool-all + + + + com.opencsv + opencsv + + + + com.google.guava + guava + + + + com.deepoove + poi-tl + + + + + com.alibaba + easyexcel + + + + io.minio + minio + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework + spring-test + + + + \ No newline at end of file diff --git a/bolt-core/src/main/java/com/jiluo/bolt/config/MinioConfig.java b/bolt-core/src/main/java/com/jiluo/bolt/config/MinioConfig.java new file mode 100644 index 0000000..da5d7a4 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/config/MinioConfig.java @@ -0,0 +1,44 @@ +package com.jiluo.bolt.config; + +import io.minio.MinioClient; +import io.minio.errors.InvalidEndpointException; +import io.minio.errors.InvalidPortException; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/13/14:35 + * @Description: + */ +@Data +@Component +@ConfigurationProperties(prefix = "minio") +public class MinioConfig { + + // endPoint是一个URL,域名,IPv4或者IPv6地址 + private String endpoint; + + // TCP/IP端口号 + private int port; + + private String accessKey; + + private String secretKey; + + // 如果是true,则用的是https而不是http,默认值是true + private Boolean secure; + + // 默认存储桶 + private String bucketName; + + @Bean + public MinioClient getMinioClient() throws InvalidEndpointException, InvalidPortException { + MinioClient minioClient = new MinioClient(endpoint, port, accessKey, secretKey, secure); + return minioClient; + } +} \ No newline at end of file diff --git a/bolt-core/src/main/java/com/jiluo/bolt/config/UploadFile.java b/bolt-core/src/main/java/com/jiluo/bolt/config/UploadFile.java new file mode 100644 index 0000000..b559dc9 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/config/UploadFile.java @@ -0,0 +1,110 @@ +package com.jiluo.bolt.config; + +import cn.hutool.core.io.file.FileNameUtil; +import com.jiluo.bolt.service.MinioService; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/13/14:55 + * @Description: + */ +@Component +public class UploadFile { + + @Autowired + private MinioService minioService; + + @Autowired + private MinioConfig minioConfig; + + @SneakyThrows + public void uploadFile(MultipartFile file, String bucketName) { + + // 获取存储桶名称 + bucketName = StringUtils.isNotBlank(bucketName) ? bucketName : minioConfig.getBucketName(); + + // 判断是否存在该存储桶 + if (!minioService.bucketExists(bucketName)) { + // 不存在则创建 + minioService.makeBucket(bucketName); + // 设置存储桶读写策略 + + String bucketPolicy = "{\"Version\":\"2023-10-10\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]}," + + "\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + + bucketName + "\"]}," + + "{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + + bucketName + "/*\"]}]}"; + + // 设置存储桶策略 + minioService.setBucketPolicy(bucketName, bucketPolicy); + } + // 原始文件名 + String fileName = file.getOriginalFilename(); + // 获取文件后缀名 + String extName = FileNameUtil.extName(fileName); + // 定义文件路径 + String format = new SimpleDateFormat("yyyy/MM/dd/").format(new Date()); + // 定义文件修改之后的名字,去除uuid中的' - ' + String fileuuid = UUID.randomUUID().toString().replaceAll("-", ""); + // 定义新的文件名 + //String objectName = format + fileuuid + "." + extName; + String objectName = format + fileName; + //上传文件 + minioService.putObject(bucketName, file, objectName); + } + + @SneakyThrows + public void uploadFiles(List files) { + + // 获取存储桶名称 + String bucketName = minioConfig.getBucketName(); + + // 判断是否存在该存储桶 + if (!minioService.bucketExists(bucketName)) { + // 不存在则创建 + minioService.makeBucket(bucketName); + + // 设置存储桶读写策略 + String bucketPolicy = "{\"Version\":\"2023-10-10\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]}," + + "\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + + bucketName + "\"]}," + + "{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + + bucketName + "/*\"]}]}"; + + // 设置存储桶策略 + minioService.setBucketPolicy(bucketName, bucketPolicy); + } + for (MultipartFile file : files) { + //上传文件 + minioService.putObject(bucketName, file, file.getOriginalFilename()); + } + } + + @SneakyThrows + public void uploadFile(String objectName, InputStream stream, String contentType) { + String bucketName = minioConfig.getBucketName(); + if (!minioService.bucketExists(bucketName)) { + minioService.makeBucket(bucketName); + String bucketPolicy = "{\"Version\":\"2023-10-10\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]}," + + "\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + + bucketName + "\"]}," + + "{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + + bucketName + "/*\"]}]}"; + minioService.setBucketPolicy(bucketName, bucketPolicy); + } + minioService.putObject(bucketName, objectName, stream, contentType); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/domain/DefectInfo.java b/bolt-core/src/main/java/com/jiluo/bolt/domain/DefectInfo.java new file mode 100644 index 0000000..478cdb4 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/domain/DefectInfo.java @@ -0,0 +1,41 @@ +package com.jiluo.bolt.domain; + +import com.alibaba.fastjson.annotation.JSONField; +import com.jiluo.bolt.common.DefectType; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/28/13:39 + * @Description: + */ + +@Data +@Builder +@EqualsAndHashCode(of = {"type", "zone", "position", "time"}) +public class DefectInfo { + private DefectType type; + private Integer zone; + private Integer position; + private BigDecimal value; + private Boolean alarm; + private Date time; + + @JSONField(serialize = false) + private String file; + + public void setValue(BigDecimal value) { + this.value = value; + } + + public BigDecimal getValue() { + return this.value; + } +} \ No newline at end of file diff --git a/bolt-core/src/main/java/com/jiluo/bolt/export/Analysis.java b/bolt-core/src/main/java/com/jiluo/bolt/export/Analysis.java new file mode 100644 index 0000000..58887e4 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/export/Analysis.java @@ -0,0 +1,127 @@ +package com.jiluo.bolt.export; + +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.common.DetectType; +import com.jiluo.bolt.domain.DefectInfo; +import com.jiluo.bolt.entity.po.*; +import com.jiluo.bolt.service.*; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.Range; +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/28/15:04 + * @Description: + */ +@Slf4j +@Component +public class Analysis { + + @Resource + PointService pointService; + + @Resource + MotorGroupService motorGroupService; + + @Resource + PowerStationService powerStationService; + + @Resource + JobService jobService; + + @Resource + DefectService defectService; + + @Value("${defect_work_dir}") + private String defect_work_dir; + + public Report report(String pointId, List jobs) { + Pair, List> data = load(pointId, jobs, null,null); + if (data.getKey().isEmpty()) { + data.getKey().add(new Date()); + } + return analysis(pointId, data.getKey(), data.getValue()); + } + + public Report report(String pointId, Range dateRange, Integer status) { + Pair, List> data = load(pointId, null, dateRange, status); + if (data.getKey().isEmpty()) { + data.getKey().add(dateRange.getMinimum()); + data.getKey().add(dateRange.getMaximum()); + } + return analysis(pointId, data.getKey(), data.getValue()); + } + + private Report analysis(String pointId, List timeDim, List defects) { + Point _point = pointService.getByBizId(pointId); + MotorGroup _motorGroup = motorGroupService.getByBizId(_point.getMotorGroup()); + PowerStation _powerStation = powerStationService.getByBizId(_point.getPowerStation()); + + return Report.builder().powerStationName(_powerStation.getName()).groupName(_motorGroup.getName()) + .pointName(_point.getName()) + .timeDimensions(timeDim).zoneDimensions(zoneDim(_point.getPoleNum())).typeDimensions(typeDim()) + .allDefectData(defects.parallelStream().collect(Collectors.groupingBy(DefectInfo::getType))) + .build() + .generate(); + } + + private List typeDim() { + return DefectType.defectTypes(DetectType.BOLT_AND_LINE.getProduct()); + } + + private List zoneDim(Integer zone) { + return IntStream.rangeClosed(1, zone).boxed().collect(Collectors.toList()); + } + + private Pair, List> load(String pointId, List jobs, Range dateRange, Integer status) { + List jobList = new ArrayList<>(); + List defectList = new ArrayList<>(); + if (!CollectionUtils.isEmpty(jobs)) { + jobList = jobService.getByBizId(jobs); + defectList = defectService.getByJob(jobs); + } + if (dateRange != null) { + jobList = jobService.exportData(dateRange.getMinimum(), dateRange.getMaximum()); + defectList = defectService.exportData(pointId,dateRange.getMinimum(), dateRange.getMaximum(),status); + } + return apply(jobList, defectList); + } + + private Pair, List> apply(List jobs, List defects) { + Map _job = jobs.stream().collect(Collectors.toMap(Job::getJobId, Job::getGmtCreate)); + + List _defects = defects.stream().map(defect -> { + DefectType type = DefectType.toDefectType(defect.getType()); + BigDecimal value = (type == DefectType.bolt || type == DefectType.temperature) ? BigDecimal.valueOf(defect.getValue() / 100.0).setScale(2, RoundingMode.HALF_UP) : (type == DefectType.line ? BigDecimal.valueOf(defect.getValue() / 1000.0).setScale(3, RoundingMode.HALF_UP) : BigDecimal.valueOf(defect.getValue())); + + return DefectInfo.builder() + .time(_job.get(defect.getJob())) + .type(type) + .alarm(BooleanUtils.toBoolean(defect.getAlarm())) + .zone(defect.getZone()) + .position(defect.getPosition()) + .value(value) + .file(DetectType.BOLT_AND_LINE == type.getDetectType() ? String.format("ipc:%s/%s/%s/%s", defect_work_dir, defect.getJob(), "/detect/", defect.getData()) : null) + .build(); + }) + .filter(x -> x.getZone() != null && x.getType() != null && x.getPosition() != null && x.getValue() != null && x.getTime() != null) + .sorted(Comparator.comparing(DefectInfo::getTime).thenComparing(DefectInfo::getZone).thenComparing(DefectInfo::getType).thenComparing(DefectInfo::getPosition)).collect(Collectors.toList()); + + List timeDim = jobs.parallelStream().map(Job::getGmtCreate).distinct().sorted(Date::compareTo).collect(Collectors.toList()); + return Pair.of(timeDim, _defects); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/export/Export.java b/bolt-core/src/main/java/com/jiluo/bolt/export/Export.java new file mode 100644 index 0000000..f31ce81 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/export/Export.java @@ -0,0 +1,136 @@ +package com.jiluo.bolt.export; + +import com.jiluo.bolt.entity.po.*; +import com.jiluo.bolt.service.*; +import com.jiluo.bolt.util.ExcelUtils; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.Range; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.time.*; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/28/14:38 + * @Description:报告导出 + */ +@Slf4j +@Component +public class Export { + private static String AUTO_EXPORT_PATH = "/data/work/report"; + private static Integer AUTO_EXPORT_PERIOD = 30; + + @Resource + Analysis analysis; + + @Resource + PointService pointService; + + @Resource + ConfigService configService; + + @PostConstruct + private void init() { + Config report_auto_export_dir = configService.selectByBizId("report_auto_export_dir"); + Config report_auto_export_time = configService.selectByBizId("report_auto_export_time"); + if (report_auto_export_time != null) { + AUTO_EXPORT_PERIOD = NumberUtils.toInt(report_auto_export_time.getValue()); + } + if (report_auto_export_dir != null) { + AUTO_EXPORT_PATH = report_auto_export_dir.getValue().endsWith(File.pathSeparator) ? report_auto_export_dir.getValue() : report_auto_export_dir.getValue() + File.separator; + } + log.info(String.format("[Export] init AUTO_EXPORT_PATH=%s; AUTO_EXPORT_PERIOD=%s", AUTO_EXPORT_PATH, AUTO_EXPORT_PERIOD)); + } + + @Scheduled(cron = "0 0 1 * * ?") + private void autoExport() { +// Date now = new Date(); +// Date start = getStartOfDay(now, -AUTO_EXPORT_PERIOD); +// Date end = getEndOfDay(now, -1); +// Range dateRange = Range.between(start, end); + LocalDate now = LocalDate.now(); + LocalDateTime start = now.minusDays(1).atStartOfDay(); + LocalDateTime end = now.minusDays(1).atTime(LocalTime.MAX); + if (AUTO_EXPORT_PERIOD == 1) { + // 每天导出一次报告 + LocalDate startDaily = now.minusDays(1); + LocalDate endDaily = now.minusDays(1); + start = startDaily.atStartOfDay(); + end = endDaily.atTime(LocalTime.MAX); + } else if (AUTO_EXPORT_PERIOD == 7) { + // 每周导出一次报告(周一开始,周日结束) + LocalDate startWeekly = now.minusWeeks(1).with(java.time.DayOfWeek.MONDAY); + LocalDate endWeekly = now.minusWeeks(1).with(java.time.DayOfWeek.SUNDAY); + start = startWeekly.atStartOfDay(); + end = endWeekly.atTime(LocalTime.MAX); + } else if (AUTO_EXPORT_PERIOD == 30) { + // 每月导出一次报告(上个月的起始和结束日期) + LocalDate startMonthly = now.minusMonths(1).withDayOfMonth(1); + LocalDate endMonthly = now.minusMonths(1).withDayOfMonth(now.minusMonths(1).lengthOfMonth()); + start = startMonthly.atStartOfDay(); + end = endMonthly.atTime(LocalTime.MAX); + } + + Range dateRange = Range.between(Date.from(start.toInstant(ZoneOffset.UTC)), Date.from(end.toInstant(ZoneOffset.UTC))); + + List points = pointService.getAll(); + for (Point point : points) { + try { + String filePath = analysis.report(point.getPointId(), dateRange, null).export(AUTO_EXPORT_PATH).getExportFileName(); + log.info(String.format("[Export] autoExport point=%s; dateRange=%s~%s; filePath=%s", point.getName(), start, end, filePath)); + } catch (Exception e) { + log.error(String.format("[Export] autoExport point=%s; dateRange=%s~%s", point.getName(), start, end), e); + } + } + } + + @SneakyThrows + public String export(String pointId, List jobs, ByteArrayOutputStream target) { + return analysis.report(pointId, jobs).export(target).getExportFileName(); + } + + @SneakyThrows + public String export(String pointId, Range dateRange, Integer status, ByteArrayOutputStream target) { + return analysis.report(pointId, dateRange, status).export(target).getExportFileName(); + } + + @SneakyThrows + public String export(String pointId, List jobs, String path) { + return analysis.report(pointId, jobs).export(path).getExportFileName(); + } + + @SneakyThrows + public String export(String pointId, Range dateRange, Integer status, String path) { + return analysis.report(pointId, dateRange, status).export(path).getExportFileName(); + } + + + public static Date getEndOfDay(Date date, int offset) { + Date _date = DateUtils.addDays(date, offset); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(_date.getTime()), ZoneId.systemDefault()); + LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX); + return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant()); + } + + public static Date getStartOfDay(Date date, int offset) { + Date _date = DateUtils.addDays(date, offset); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(_date.getTime()), ZoneId.systemDefault()); + LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN); + return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant()); + } + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/export/Report.java b/bolt-core/src/main/java/com/jiluo/bolt/export/Report.java new file mode 100644 index 0000000..b0face3 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/export/Report.java @@ -0,0 +1,278 @@ +package com.jiluo.bolt.export; + +import com.deepoove.poi.XWPFTemplate; +import com.deepoove.poi.config.Configure; +import com.deepoove.poi.data.*; +import com.deepoove.poi.data.style.TableStyle; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.domain.DefectInfo; +import lombok.Builder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.Range; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateFormatUtils; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.ResourceUtils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.stream.Collectors; + +@Data +@Slf4j +@Builder +public class Report { + private static final String AUTO_EXPORT_FILE_NAME = "转子视觉检测报告_%s.docx"; + private static final String AUTO_EXPORT_TEMPLATE = "report.docx"; + + private String exportFileName; + private String powerStationName; + private String groupName; + private String pointName; + private Range reportTime; + + private List timeDimensions; + private List typeDimensions; + private List zoneDimensions; + + private Map> allDefectData; + + private Map analysisResult; + + private AnalysisResult analysis(DefectType type, List defects) { + defects.removeAll(defects.parallelStream().filter(x -> !timeDimensions.contains(x.getTime()) || !zoneDimensions.contains(x.getZone())).collect(Collectors.toList())); + Map> _defects = defects.parallelStream().collect(Collectors.groupingBy(DefectInfo::getZone)); + + AnalysisResult result = new AnalysisResult(type, !defects.isEmpty()); + zoneDimensions.forEach(z -> { + if (!_defects.containsKey(z)) { + result.getCensus().add(new DetectReport(type, z)); + } else { + DetectReport detectReport = new DetectReport(type, z); + Map defectReports = Maps.newConcurrentMap(); + _defects.get(z).forEach(d -> { + detectReport.addValue(d.getAlarm(), d.getValue()); + defectReports.putIfAbsent(d.getPosition(), new DefectReport(type, z, d.getPosition())); + defectReports.get(d.getPosition()).addValue(d.getAlarm(), d.getTime(), d.getValue(), d.getFile()); + }); + timeDimensions.forEach(d -> defectReports.values().stream().filter(r -> !r.getValues().containsKey(d)).forEach(r -> r.addValue(d))); + defectReports.keySet().stream().filter(k -> !defectReports.get(k).isAlarm()).forEach(defectReports::remove); + + result.getCensus().add(detectReport); + result.getDetail().addAll(defectReports.values()); + } + }); + + result.getCensus().sort(Comparator.comparing(DetectReport::getZone)); + result.getDetail().sort(Comparator.comparing(DefectReport::getZone).thenComparing(DefectReport::getPosition)); + result.fill(); + return result; + } + + public Report generate() { + if (timeDimensions.isEmpty()) { + timeDimensions.add(new Date()); + } + reportTime = Range.between(timeDimensions.get(0), timeDimensions.get(timeDimensions.size() -1)); + analysisResult = typeDimensions.parallelStream().map(t -> analysis(t, allDefectData.getOrDefault(t, new ArrayList<>()))).collect(Collectors.toMap(AnalysisResult::getType, x -> x)); + exportFileName = String.format(AUTO_EXPORT_FILE_NAME, String.format("%s_%s", DateFormatUtils.format(getReportTime().getMinimum(), "yyyy年MM月dd日"), DateFormatUtils.format(getReportTime().getMaximum(), "yyyy年MM月dd日"))); + return this; + } + + private Map convert() { + Map data = new HashMap<>(); + data.put("productLine", getPowerStationName()); + data.put("groupName", getGroupName()); + data.put("pointName", getPointName()); + data.put("reportTime", String.format("%s-%s", DateFormatUtils.format(getReportTime().getMinimum(), "yyyy年MM月dd日"), DateFormatUtils.format(getReportTime().getMaximum(), "yyyy年MM月dd日"))); + data.put("boltDetail", getAnalysisResult().get(DefectType.bolt).getDetail()); + data.put("lineDetail", getAnalysisResult().get(DefectType.line).getDetail()); + data.put("poleDetail", getAnalysisResult().get(DefectType.pole).getDetail()); + data.put("boltTable", getAnalysisResult().get(DefectType.bolt).getTable()); + data.put("lineTable", getAnalysisResult().get(DefectType.line).getTable()); + data.put("poleTable", getAnalysisResult().get(DefectType.pole).getTable()); + return data; + } + + public Report export(String path) throws IOException { + File dir = FileUtils.getFile(path, powerStationName, groupName, pointName); + FileUtils.forceMkdir(dir); + this.exportFileName = FileUtils.getFile(dir, exportFileName).getAbsolutePath(); + XWPFTemplate.compile(new ClassPathResource(AUTO_EXPORT_TEMPLATE).getInputStream(), Configure.builder().useSpringEL().build()).render(convert()).writeToFile(exportFileName); + return this; + } + + public Report export(ByteArrayOutputStream target) throws IOException { + XWPFTemplate.compile(new ClassPathResource(AUTO_EXPORT_TEMPLATE).getInputStream(), Configure.builder().useSpringEL().build()).render(convert()).writeAndClose(target); + return this; + } +} + +@Data +class AnalysisResult { + private DefectType type; + private boolean hasDefect; + + private List census; + private List detail; + + private TableRenderData table; + + public AnalysisResult(DefectType type, boolean hasDefect) { + this.type = type; + this.hasDefect = hasDefect; + census = new ArrayList<>(); + detail = new ArrayList<>(); + } + + public void fill() { + List rows = new ArrayList<>(); + Lists.partition(census, 10).forEach(_values -> { + Rows.RowBuilder rowA = Rows.of("编号"); + Rows.RowBuilder rowB = Rows.of("范围"); + _values.forEach(x -> { + rowA.addCell(Cells.of(String.valueOf(x.getZone())).create()); + StringBuilder _v = new StringBuilder(); + _v.append(x.getMin().setScale(2, RoundingMode.HALF_UP)).append('~').append(x.getMax().setScale(2, RoundingMode.HALF_UP)); + + if (x.isAlarm()) { + rowB.addCell(Cells.of(Texts.of(_v.toString()).bold().create()).create()); + } else { + rowB.addCell(Cells.of(_v.toString()).create()); + } + }); + if (_values.size() < 10) { + int _lack = 10 - _values.size(); + while(_lack-- > 0) { + rowA.addCell(new CellRenderData()); + rowB.addCell(new CellRenderData()); + } + } + rows.add(rowA.textFontSize(10).center().create()); + rows.add(rowB.textFontSize(10).center().create()); + }); + table = Tables.of(rows.toArray(new RowRenderData[]{})).width(16, null).center().border(TableStyle.BorderStyle.DEFAULT).create(); + detail.forEach(DefectReport::fill); + } +} + +@Data +class DetectReport { + private DefectType type; + private int zone; + private BigDecimal max = BigDecimal.ZERO; + private BigDecimal min = BigDecimal.ZERO; + private boolean alarm = false; + + public DetectReport(DefectType type, int zone) { + this.type = type; + this.zone = zone; + } + + public synchronized void addValue(boolean alarm, BigDecimal v) { + this.alarm = this.alarm || alarm; + if (v.compareTo(max) > 0) { + this.max = v; + } + if (this.min.equals(BigDecimal.ZERO)) { + this.min = v; + } else { + if (min.compareTo(v) > 0) { + this.min = v; + } + } + } +} + +@Data +@Slf4j +class DefectReport { + private DefectType type; + private int zone; + private int position; + private BigDecimal value; + private String url; + private boolean alarm; + private TreeMap values; + + private PictureRenderData file; + private ChartMultiSeriesRenderData chart; + + public DefectReport(DefectType type, int zone, int position) { + this.type = type; + this.zone = zone; + this.position = position; + this.value = type.getDefaultValue(); + this.values = new TreeMap<>(); + } + + public synchronized void addValue(Date t) { + this.values.put(t, type.getDefaultValue()); + this.value = this.values.lastEntry().getValue(); + if (this.value.equals(type.getDefaultValue())) { + this.alarm = false; + } + } + + public synchronized void addValue(boolean alarm, Date t, BigDecimal v, String url) { + this.values.put(t, v); + this.value = this.values.lastEntry().getValue(); + if (this.value.equals(v)) { + this.alarm = alarm; + } + if (this.alarm) { + this.url = url; + } + } + + public void fill() { + fillChart(); + fillFile(); + } + + private void fillFile() { + try { + if (StringUtils.isBlank(url)) { + return; + } + + if (url.startsWith("ipc")) { + String path = url.split(":")[1]; + File img = ResourceUtils.getFile(path); + FileInputStream fileInputStream = new FileInputStream(img); + byte[] bytes = IOUtils.toByteArray(fileInputStream); + fileInputStream.close(); + // 读取图像文件的字节数组 + file = Pictures.ofBytes(bytes, PictureType.JPEG).altMeta("图片丢失!").size(577, 397).create(); + } else { + file = Pictures.ofStream(new ClassPathResource(url.split(":")[1]).getInputStream(), PictureType.JPEG).altMeta("图片丢失!").size(577, 397).create(); + } + } catch (Exception e) { + log.error("[DefectReport] fillFile exception!", e); + } + } + + private void fillChart() { + String series = String.format("%s#磁极", zone); + if (type == DefectType.bolt) { + series = series + String.format("%s号螺栓", position); + } + if (type == DefectType.line) { + series = series + String.format("%s号引出线", position); + } + chart = Charts.ofLine( + this.type.getDesc(), this.getValues().keySet().stream().map(integer -> DateFormatUtils.format(integer, "yyyy-MM-dd HH:mm:ss")).toArray(String[]::new)) + .addSeries(series, this.getValues().values().toArray(new Number[]{})) + .create(); + } +} \ No newline at end of file diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/AlarmService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/AlarmService.java new file mode 100644 index 0000000..3914c01 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/AlarmService.java @@ -0,0 +1,22 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.po.Alarm; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 告警信息表(Alarm)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:06 + */ + +public interface AlarmService extends IService { + + public static Map ALARM_MAP = new ConcurrentHashMap<>(); + + public List getAll(); + + public void add(Alarm alarm); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/AlgorithmService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/AlgorithmService.java new file mode 100644 index 0000000..0142108 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/AlgorithmService.java @@ -0,0 +1,23 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.dto.AlgorithmDto; +import com.jiluo.bolt.entity.po.Algorithm; + +import java.util.List; + +/** + * 算法信息表(Algorithm)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:08 + */ + +public interface AlgorithmService extends IService { + + public Algorithm getByPoint(String pointId); + + public Integer getPointCount(String algorithmId); + + public List select(AlgorithmDto algorithmDto); + + public void add(AlgorithmDto algorithmDto); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/AlgorithmTempleteService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/AlgorithmTempleteService.java new file mode 100644 index 0000000..6aff0b6 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/AlgorithmTempleteService.java @@ -0,0 +1,24 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.dto.AlgorithmDto; +import com.jiluo.bolt.entity.po.AlgorithmTemplete; + +import java.util.List; + +/** + * (AlgorithmTemplete)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:09 + */ + +public interface AlgorithmTempleteService extends IService { + + + public List select(AlgorithmDto algorithmDto); + + public Integer selectTotal(AlgorithmDto algorithmDto); + + public String add(AlgorithmDto algorithmDto); + + public Boolean updateByBizId(AlgorithmDto algorithmDto); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/ConfigService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/ConfigService.java new file mode 100644 index 0000000..b1e9471 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/ConfigService.java @@ -0,0 +1,32 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.po.Config; + +import java.util.List; + +/** + * 系统配置表(Config)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:11 + */ + +public interface ConfigService extends IService { + + public boolean updateByBizId(String bizId,String value); + + public Config selectByBizId(String bizId); + + public List selectByCategory(String category); + + public Config selectByDescription(String description); + + public List select(Config configDto); + + public Boolean add(Config configDto,String type); + + public Boolean updateByBizId(Config configDto); + + + + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/DefectService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/DefectService.java new file mode 100644 index 0000000..e24d909 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/DefectService.java @@ -0,0 +1,35 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.po.Defect; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 检测结果表(Defect)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:11 + */ + +public interface DefectService extends IService { + + public static Map TEMPERATUREMAP = new ConcurrentHashMap<>(); + + public List getRealTimeData(String jobId); + + public List getByJob(String jobId); + + public List getByJob(List jobIds); + + public List chartData(String pointId,String product,Integer zone,String startTime,String endTime); + + public List exportData(String pointId, Date startTime, Date endTime, Integer status); + + public Defect selectToCompare(String jobId,String type,Integer zone,Integer position); + + public void add(Defect defect); + + public void update(Defect defect); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/DetectService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/DetectService.java new file mode 100644 index 0000000..50bc365 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/DetectService.java @@ -0,0 +1,23 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.po.Detect; + +import java.util.List; +import java.util.Map; + +/** + * 检测过程表(Detect)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:11 + */ + +public interface DetectService extends IService { + + public Map> getImg(String jobId); + + public void add(Detect detect); + + public Integer selectByJob(String jobId); + + public List getByJob(String jobId); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/DeviceService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/DeviceService.java new file mode 100644 index 0000000..0825c8e --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/DeviceService.java @@ -0,0 +1,50 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.common.LocalStatus; +import com.jiluo.bolt.entity.dto.DeviceDto; +import com.jiluo.bolt.entity.dto.TempSenserDto; +import com.jiluo.bolt.entity.po.Device; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 设备信息表(Device)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:11 + */ + +public interface DeviceService extends IService { + + Map DeviceLocalStatus = new ConcurrentHashMap<>(); + + public Device selectByBizId(String deviceId); + + public List selectByPoint(String pointId); + + public List selectAll(); + + public void updateTemp(String deviceId,String tempThreshold); + + public void updateStatus(String deviceId,Integer status); + + public List select(DeviceDto deviceDto); + + public Integer selectTotal(DeviceDto deviceDto); + + public Boolean add(DeviceDto deviceDto,String type); + + public Boolean updateByBizId(DeviceDto deviceDto); + + public Boolean deactivate(DeviceDto deviceDto); + + public List select(TempSenserDto tempSenserDto); + + public Integer selectTotal(TempSenserDto tempSenserDto); + + public Boolean add(TempSenserDto tempSenserDto,String type); + + public Boolean updateByBizId(TempSenserDto tempSenserDto); + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/DeviceTempleteService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/DeviceTempleteService.java new file mode 100644 index 0000000..37315c8 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/DeviceTempleteService.java @@ -0,0 +1,20 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.po.DeviceTemplete; + +import java.util.List; + +/** + * (DeviceTemplete)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:11 + */ + +public interface DeviceTempleteService extends IService { + + public DeviceTemplete getByBizId(String venderId); + + public List getByBizId(List venderIds); + + List getAll(); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/JobService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/JobService.java new file mode 100644 index 0000000..afa679f --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/JobService.java @@ -0,0 +1,41 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.common.DetectJob; +import com.jiluo.bolt.entity.po.Job; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 检测任务表(Job)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ + +public interface JobService extends IService { + + public static Map JOB_MAP = new ConcurrentHashMap<>(); + + public Job getByBizId(String jobId); + + public List getByBizId(List jobIds); + + public Job getRealtimeJob(String pointId,String product); + + public List getHistoryJob(String pointId,Integer status, String product, Integer current, Integer size, + @DateTimeFormat(pattern = "yyyy-MM-dd") String startTime, + @DateTimeFormat(pattern = "yyyy-MM-dd") String endTime); + + public Integer getHistoryJobTotal(String pointId,Integer status, String product, + @DateTimeFormat(pattern = "yyyy-MM-dd") String startTime, + @DateTimeFormat(pattern = "yyyy-MM-dd") String endTime); + public List exportData(Date startTime,Date endTime); + + public void addJob(Job job); + + public void updateAttribute(String jobId, String attribute, Integer status); + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/MinioService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/MinioService.java new file mode 100644 index 0000000..ca4b60b --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/MinioService.java @@ -0,0 +1,148 @@ +package com.jiluo.bolt.service; + +import io.minio.ObjectStat; +import io.minio.Result; +import io.minio.errors.*; +import io.minio.messages.Item; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/13/14:56 + * @Description: + */ +public interface MinioService { + + + /** + * 判断 bucket是否存在 + * + * @param bucketName + * @return + */ + boolean bucketExists(String bucketName); + + /** + * 创建 bucket + * + * @param bucketName + */ + void makeBucket(String bucketName); + + /** + * 文件上传 + * + * @param bucketName + * @param objectName + * @param filename + */ + void putObject(String bucketName, String objectName, String filename); + + /** + * 文件上传 + * + * @param bucketName + * @param objectName + * @param stream + */ + void putObject(String bucketName, String objectName, InputStream stream, String contentType); + + /** + * 文件上传 + * + * @param bucketName + * @param multipartFile + */ + void putObject(String bucketName, MultipartFile multipartFile, String filename); + + /** + * 删除文件 + * + * @param bucketName + * @param objectName + */ + boolean removeObject(String bucketName, String objectName); + + /** + * 下载文件 + * + * @param fileName + * @param originalName + * @param response + */ + void downloadFile(String bucketName, String fileName, String originalName, HttpServletResponse response); + + /** + * 获取文件路径 + * + * @param bucketName + * @param objectName + * @return + */ + String getObjectUrl(String bucketName, String objectName); + + + /** + * @description: 文件下载 + * @param: bucketName + * objectName + * @return: io.minio.ObjectStat + * @author yangc + * @date: 2020-10-20 20:24 + */ + ObjectStat statObject(String bucketName, String objectName); + + /** + * 以流的形式获取一个文件对象 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @return + */ + InputStream getObject(String bucketName, String objectName); + + + /** + * 列出存储桶中所有对象 + * + * @param bucketName 存储桶名称 + * @return + */ + Iterable> listObjects(String bucketName); + + + /** + * 生成一个给HTTP GET请求用的presigned URL + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @param expires 失效时间(以秒为单位),默认是7天,不得大于七天 + * @return + */ + String presignedGetObject(String bucketName, String objectName, Integer expires); + + + /** + * 设置存储桶策略 + * + * @param bucketName 存储桶名称 + * @return + */ + void setBucketPolicy(String bucketName, String policy) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException; + + + /** + * 获取存储桶策略 + * + * @param bucketName 存储桶名称 + * @return + */ + String getBucketPolicy(String bucketName) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, BucketPolicyTooLargeException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException; +} \ No newline at end of file diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/MotorGroupService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/MotorGroupService.java new file mode 100644 index 0000000..7d2546d --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/MotorGroupService.java @@ -0,0 +1,29 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.dto.MotorGroupDto; +import com.jiluo.bolt.entity.po.MotorGroup; + +import java.util.List; + +/** + * 机组信息表(MotorGroup)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ + +public interface MotorGroupService extends IService { + + public List getAll(); + + public MotorGroup getByBizId(String bizId); + + public Integer getTotalByPowerStation(String powerStationId); + + public List select(MotorGroupDto motorGroupDto); + + public Integer selectTotal(MotorGroupDto motorGroupDto); + + public Boolean add(MotorGroupDto motorGroupDto); + + public Boolean updateByBizId(MotorGroupDto motorGroupDto); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/PointService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/PointService.java new file mode 100644 index 0000000..e88324c --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/PointService.java @@ -0,0 +1,42 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.dto.PointDto; +import com.jiluo.bolt.entity.po.Point; + +import java.util.Date; +import java.util.List; + +/** + * 检测点表(Point)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ + +public interface PointService extends IService { + + public List getAll(); + + public List getByRole(String groupPermission); + + public void updateEnableDetect(String pointId,Integer enableDetect); + + public void updateResetTime(String pointId, Date date); + + public void updateConfig(String pointId, String config); + + public Point getByBizId(String bizId); + + public Integer getTotalByMotorGroup(String motorGroupId); + + public List select(PointDto pointDto); + + public Integer selectTotal(PointDto pointDto); + + public Boolean add(PointDto pointDto); + + public Boolean updateByBizId(PointDto pointDto); + + public Boolean updateDetect(PointDto pointDto); + + public List getByPowerStation(String powerStationId); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/PowerStationService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/PowerStationService.java new file mode 100644 index 0000000..cff0c5a --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/PowerStationService.java @@ -0,0 +1,27 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.dto.PowerStationDto; +import com.jiluo.bolt.entity.po.PowerStation; + +import java.util.List; + +/** + * 电站表(PowerStation)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ + +public interface PowerStationService extends IService { + public List getAll(); + + public PowerStation getByBizId(String bizId); + + public List select(PowerStationDto powerStationDto); + + public Integer selectTotal(PowerStationDto powerStationDto); + + public Boolean add(PowerStationDto powerStationDto); + + public Boolean updateByBizId(PowerStationDto powerStationDto); + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/ProductService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/ProductService.java new file mode 100644 index 0000000..5b4af64 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/ProductService.java @@ -0,0 +1,13 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.po.Product; + +/** + * 检测类型表(Product)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ + +public interface ProductService extends IService { + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/RoleItemService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/RoleItemService.java new file mode 100644 index 0000000..752e04d --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/RoleItemService.java @@ -0,0 +1,20 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.po.RoleItem; + +import java.util.List; + +/** + * 权限信息表(RoleItem)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ + +public interface RoleItemService extends IService { + + public RoleItem getByBizId(String bizId); + + public List getByBizId(List itemIds); + + public List getAll(); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/RoleService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/RoleService.java new file mode 100644 index 0000000..377136f --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/RoleService.java @@ -0,0 +1,23 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.dto.RoleDto; +import com.jiluo.bolt.entity.po.Role; + +import java.util.List; + +/** + * 角色信息表(Role)表服务接口 + * @author Fangy + * @date 2023-05-05 18:59:16 + */ + +public interface RoleService extends IService { + + public Role getByRoleId(String roleId); + + public List select(RoleDto roleDto); + + public String add(RoleDto roleDto); + + public Boolean updateByBizId(RoleDto roleDto); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/RoleValueService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/RoleValueService.java new file mode 100644 index 0000000..81fb210 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/RoleValueService.java @@ -0,0 +1,25 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.dto.PermissionDto; +import com.jiluo.bolt.entity.po.RoleValue; + +import java.util.List; + +/** + * 角色信息表(Role)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ + +public interface RoleValueService extends IService { + + public List getByRoleId(String roleId); + + public RoleValue getProduct(String roleId); + + public List select(PermissionDto permissionDto); + + public Boolean add(RoleValue roleValue); + + public Boolean updateByItemId(RoleValue roleValue); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/UserService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/UserService.java new file mode 100644 index 0000000..0c6fad8 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/UserService.java @@ -0,0 +1,23 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.dto.UserDto; +import com.jiluo.bolt.entity.po.User; + +import java.util.List; + +/** + * 用户信息表(User)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ + +public interface UserService extends IService { + + public User getByUid(String uid); + + public List select(UserDto userDto); + + public Boolean add(UserDto userDto); + + public Boolean updateByBizId(UserDto userDto); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/VersionService.java b/bolt-core/src/main/java/com/jiluo/bolt/service/VersionService.java new file mode 100644 index 0000000..0e9f7d1 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/VersionService.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.jiluo.bolt.entity.po.Version; + +/** + * 版本信息表(Version)表服务接口 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ + +public interface VersionService extends IService { + + public Version select(); +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/AlarmServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/AlarmServiceImpl.java new file mode 100644 index 0000000..e4f1b2e --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/AlarmServiceImpl.java @@ -0,0 +1,36 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.mapper.AlarmMapper; +import com.jiluo.bolt.entity.po.Alarm; +import com.jiluo.bolt.service.AlarmService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * 告警信息表(Alarm)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:07 + */ +@Service +public class AlarmServiceImpl extends ServiceImpl implements AlarmService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(AlarmServiceImpl.class); + + @Autowired + AlarmMapper alarmMapper; + + public List getAll(){ + return new ArrayList<>(ALARM_MAP.values()); + } + + public void add(Alarm alarm){ + alarmMapper.insert(alarm); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/AlgorithmServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/AlgorithmServiceImpl.java new file mode 100644 index 0000000..1cfe893 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/AlgorithmServiceImpl.java @@ -0,0 +1,86 @@ +package com.jiluo.bolt.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.entity.dto.AlgorithmDto; +import com.jiluo.bolt.entity.po.AlgorithmTemplete; +import com.jiluo.bolt.entity.po.Config; +import com.jiluo.bolt.mapper.AlgorithmMapper; +import com.jiluo.bolt.entity.po.Algorithm; +import com.jiluo.bolt.mapper.AlgorithmTempleteMapper; +import com.jiluo.bolt.mapper.ConfigMapper; +import com.jiluo.bolt.service.AlgorithmService; +import com.jiluo.bolt.util.SnowFlakeUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 算法信息表(Algorithm)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:09 + */ +@Service +public class AlgorithmServiceImpl extends ServiceImpl implements AlgorithmService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(AlgorithmServiceImpl.class); + + @Autowired + AlgorithmMapper algorithmMapper; + + @Autowired + AlgorithmTempleteMapper algorithmTempleteMapper; + + @Autowired + ConfigMapper configMapper; + + public Algorithm getByPoint(String pointId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("point",pointId); + return algorithmMapper.selectOne(queryWrapper); + } + + @Override + public Integer getPointCount(String algorithmId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("algorithm",algorithmId); + return algorithmMapper.selectCount(queryWrapper); + } + + @Override + public List select(AlgorithmDto algorithmDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(StringUtils.isNotBlank(algorithmDto.getAlgorithmId()),"algorithm",algorithmDto.getAlgorithmId()); + return algorithmMapper.selectList(queryWrapper); + } + + @Override + public void add(AlgorithmDto algorithmDto) { + algorithmDto.getPoints().forEach(item ->{ + Algorithm algorithm = new Algorithm(); + JSONObject config = new JSONObject(); + JSONObject attribute = new JSONObject(); + Config algorithm_model_config = configMapper.selectOne(new QueryWrapper().eq("biz_id","algorithm_model_dir")); + if (algorithm_model_config!=null){ + String algorithm_model_dir = algorithm_model_config.getValue()+algorithmDto.getAlgorithmFileName(); + config.put("algorithm_name",algorithmDto.getAlgorithmName()); + config.put("algorithm_model",algorithm_model_dir); + attribute.put("detect_threshold_model",algorithmDto.getAlgorithmFileName()); + algorithm.setBizId("algorithm_"+ SnowFlakeUtil.getDefaultSnowFlakeId()) + .setAlgorithm(algorithmDto.getAlgorithmId()) + .setPowerStation(item.getPowerStationId()) + .setMotorGroup(item.getMotorGroupId()) + .setPoint(item.getPointId()) + .setConfig(config.toJSONString()) + .setAttribute(attribute.toJSONString()) + .setVersion("1.0.0"); + algorithmMapper.insert(algorithm); + } + }); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/AlgorithmTempleteServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/AlgorithmTempleteServiceImpl.java new file mode 100644 index 0000000..dbdead9 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/AlgorithmTempleteServiceImpl.java @@ -0,0 +1,79 @@ +package com.jiluo.bolt.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.entity.dto.AlgorithmDto; +import com.jiluo.bolt.mapper.AlgorithmTempleteMapper; +import com.jiluo.bolt.entity.po.AlgorithmTemplete; +import com.jiluo.bolt.service.AlgorithmTempleteService; +import com.jiluo.bolt.util.SnowFlakeUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * (AlgorithmTemplete)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:10 + */ +@Service +public class AlgorithmTempleteServiceImpl extends ServiceImpl implements AlgorithmTempleteService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(AlgorithmTempleteServiceImpl.class); + + @Autowired + AlgorithmTempleteMapper algorithmTempleteMapper; + + @Override + public List select(AlgorithmDto algorithmDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StringUtils.isNotBlank(algorithmDto.getAlgorithmName()),"name",algorithmDto.getAlgorithmName()) + .eq(StringUtils.isNotBlank(algorithmDto.getSource()),"source",algorithmDto.getSource()) + .eq(StringUtils.isNotBlank(algorithmDto.getAlgorithmId()),"biz_id",algorithmDto.getAlgorithmId());; + return algorithmTempleteMapper.selectList(queryWrapper); + + } + + @Override + public Integer selectTotal(AlgorithmDto algorithmDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StringUtils.isNotBlank(algorithmDto.getAlgorithmName()),"name",algorithmDto.getAlgorithmName()) + .eq(StringUtils.isNotBlank(algorithmDto.getSource()),"source",algorithmDto.getSource()); + return algorithmTempleteMapper.selectCount(queryWrapper); + } + + @Override + public String add(AlgorithmDto algorithmDto) { + AlgorithmTemplete algorithmTemplete = new AlgorithmTemplete(); + JSONObject config = new JSONObject(); + config.put("detect_threshold_model",algorithmDto.getAlgorithmFileName()); + algorithmTemplete.setBizId(algorithmDto.getSource()+ SnowFlakeUtil.getDefaultSnowFlakeId()) + .setSource(algorithmDto.getSource()) + .setName(algorithmDto.getAlgorithmName()) + .setDrive(algorithmDto.getAlgorithmFileName()) + .setVersion("1.0.0") + .setConfig(config.toJSONString()); + algorithmTempleteMapper.insert(algorithmTemplete); + return algorithmTemplete.getBizId(); + } + + @Override + public Boolean updateByBizId(AlgorithmDto algorithmDto) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + JSONObject config = new JSONObject(); + config.put("detect_threshold_model",algorithmDto.getAlgorithmFileName()); + + updateWrapper.eq("biz_id",algorithmDto.getAlgorithmId()) + .set("source",algorithmDto.getSource()) + .set("name",algorithmDto.getAlgorithmName()) + .set("drive",algorithmDto.getAlgorithmFileName()) + .set("config",config.toJSONString()); + return algorithmTempleteMapper.update(null,updateWrapper)>0?true:false; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/ConfigServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/ConfigServiceImpl.java new file mode 100644 index 0000000..f815632 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/ConfigServiceImpl.java @@ -0,0 +1,85 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.mapper.ConfigMapper; +import com.jiluo.bolt.entity.po.Config; +import com.jiluo.bolt.service.ConfigService; +import com.jiluo.bolt.util.SnowFlakeUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 系统配置表(Config)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:11 + */ +@Service +public class ConfigServiceImpl extends ServiceImpl implements ConfigService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(ConfigServiceImpl.class); + + @Autowired + ConfigMapper configMapper; + + public boolean updateByBizId(String bizId,String value){ + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",bizId).set("value",value); + configMapper.update(null,updateWrapper); + return true; + } + + public Config selectByBizId(String bizId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("biz_id",bizId); + return configMapper.selectOne(queryWrapper); + } + + @Override + public List selectByCategory(String category) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("category",category); + return configMapper.selectList(queryWrapper); + } + + @Override + public Config selectByDescription(String description) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("description",description); + return configMapper.selectOne(queryWrapper); + } + + @Override + public List select(Config configDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(StringUtils.isNotBlank(configDto.getConfigId()),"biz_id",configDto.getConfigId()) + .eq(StringUtils.isNotBlank(configDto.getDescription()),"description",configDto.getDescription()) + .eq(StringUtils.isNotBlank(configDto.getCategory()),"category",configDto.getCategory()); + + return configMapper.selectList(queryWrapper); + } + + @Override + public Boolean add(Config configDto,String type) { + configDto.setConfigId(type+ SnowFlakeUtil.getDefaultSnowFlakeId()); + return configMapper.insert(configDto)>0?true:false; + } + + @Override + public Boolean updateByBizId(Config configDto) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",configDto.getConfigId()) + .set(StringUtils.isNotBlank(configDto.getValue()),"value",configDto.getValue()) + .set(StringUtils.isNotBlank(configDto.getDescription()),"description",configDto.getDescription()) + .set(StringUtils.isNotBlank(configDto.getCategory()),"category",configDto.getCategory()) + .set(StringUtils.isNotBlank(configDto.getType()),"type",configDto.getType()); + + return configMapper.update(null,updateWrapper)>0?true:false; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DefectServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DefectServiceImpl.java new file mode 100644 index 0000000..0dbf323 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DefectServiceImpl.java @@ -0,0 +1,106 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.common.DetectType; +import com.jiluo.bolt.common.LocalStatus; +import com.jiluo.bolt.entity.po.Job; +import com.jiluo.bolt.mapper.DefectMapper; +import com.jiluo.bolt.entity.po.Defect; +import com.jiluo.bolt.mapper.JobMapper; +import com.jiluo.bolt.service.DefectService; +import com.jiluo.bolt.util.BigDecimalUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 检测结果表(Defect)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:11 + */ +@Service +public class DefectServiceImpl extends ServiceImpl implements DefectService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(DefectServiceImpl.class); + + @Autowired + DefectMapper defectMapper; + + @Autowired + JobMapper jobMapper; + + @PostConstruct + public void init() { + defectMapper.selectTemperature().forEach(defect -> { + DefectService.TEMPERATUREMAP.put(defect.getPoint(), BigDecimalUtils.DBToFront(DefectType.temperature,defect.getValue()).floatValue()); + }); + } + + @Scheduled(cron = "0 0 * * * *") + public void updateLocal() { + defectMapper.selectTemperature().forEach(defect -> { + DefectService.TEMPERATUREMAP.put(defect.getPoint(), BigDecimalUtils.DBToFront(DefectType.temperature,defect.getValue()).floatValue()); + }); + } + + public List getRealTimeData(String jobId){ + QueryWrapper wrapper = new QueryWrapper(); + wrapper.eq("job",jobId).eq("status",0); + return defectMapper.selectList(wrapper); + } + + public List getByJob(String jobId){ + QueryWrapper wrapper = new QueryWrapper(); + wrapper.eq("job",jobId).eq("status",0); + return defectMapper.selectList(wrapper); + } + + public List getByJob(List jobIds){ + QueryWrapper wrapper = new QueryWrapper(); + wrapper.in("job",jobIds); + return defectMapper.selectList(wrapper); + } + + public List chartData(String pointId,String product,Integer zone,String startTime,String endTime){ + QueryWrapper wrapper = new QueryWrapper(); + wrapper.eq(StringUtils.isNotBlank(pointId),"point",pointId) + .eq(StringUtils.isNotBlank(product),"type",product) + .eq("zone",zone) + .between(StringUtils.isNotBlank(startTime)&&StringUtils.isNotBlank(endTime),"gmt_create",startTime,endTime); + return defectMapper.selectList(wrapper); + } + public List exportData(String pointId, Date startTime, Date endTime, Integer status){ + QueryWrapper wrapper = new QueryWrapper(); + wrapper.eq(pointId!=null&&StringUtils.isNotBlank(pointId),"point",pointId).between("gmt_create",startTime,endTime).eq(status!=null,"status",status); + return defectMapper.selectList(wrapper); + } + + public Defect selectToCompare(String jobId,String type,Integer zone,Integer position){ + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("job",jobId) + .eq("type",type) + .eq("zone",zone) + .eq("position",position); + return defectMapper.selectOne(wrapper); + } + + public void add(Defect defect){ + defectMapper.insert(defect); + } + + public void update(Defect defect){ + defectMapper.updateById(defect); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DetectServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DetectServiceImpl.java new file mode 100644 index 0000000..2a6a503 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DetectServiceImpl.java @@ -0,0 +1,63 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.mapper.DetectMapper; +import com.jiluo.bolt.entity.po.Detect; +import com.jiluo.bolt.service.DetectService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 检测过程表(Detect)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:11 + */ +@Service +public class DetectServiceImpl extends ServiceImpl implements DetectService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(DetectServiceImpl.class); + + @Autowired + DetectMapper detectMapper; + + public Map> getImg(String jobId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("job",jobId); + Map> result = new HashMap<>(); + detectMapper.selectList(queryWrapper).stream().collect(Collectors.groupingBy(Detect::getZone)) + .forEach((k,v)->{ + List imgs = new ArrayList<>(); + v.stream().forEach(defect->imgs.add(defect.getData())); + result.put(k,imgs); + }); + return result; + } + + public void add(Detect detect){ + detectMapper.insert(detect); + } + + @Override + public Integer selectByJob(String jobId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("job",jobId); + return detectMapper.selectCount(queryWrapper); + } + + @Override + public List getByJob(String jobId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("job",jobId); + return detectMapper.selectList(queryWrapper); + } + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DeviceServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DeviceServiceImpl.java new file mode 100644 index 0000000..8fe0aff --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DeviceServiceImpl.java @@ -0,0 +1,202 @@ +package com.jiluo.bolt.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.common.LocalStatus; +import com.jiluo.bolt.entity.dto.DeviceDto; +import com.jiluo.bolt.entity.dto.TempSenserDto; +import com.jiluo.bolt.mapper.DeviceMapper; +import com.jiluo.bolt.entity.po.Device; +import com.jiluo.bolt.service.DeviceService; +import com.jiluo.bolt.util.SnowFlakeUtil; +import org.apache.commons.lang3.StringUtils; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import java.util.*; + +/** + * 设备信息表(Device)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:11 + */ +@Service +public class DeviceServiceImpl extends ServiceImpl implements DeviceService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(DeviceServiceImpl.class); + + @Autowired + DeviceMapper deviceMapper; + + @PostConstruct + public void init() { + deviceMapper.selectList(new QueryWrapper<>()).forEach(device -> DeviceLocalStatus.putIfAbsent(device.getPointId(), LocalStatus.builder().pointStatus(2).cameraStatus(2).tempSensorStatus(2).build())); + } + + public Device selectByBizId(String deviceId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("biz_id",deviceId); + Device device = deviceMapper.selectOne(queryWrapper); + if (DeviceLocalStatus.get(device.getPointId()).getCameraStatus()==2){ + device.setStatus(2); + } + return device; + } + + public List selectByPoint(String pointId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("point",pointId); + List deviceList = deviceMapper.selectList(queryWrapper); + deviceList.stream().filter(device -> DeviceLocalStatus.get(device.getPointId()).getCameraStatus()==2).forEach(device -> device.setStatus(2)); + return deviceList; + } + + public List selectAll(){ + List deviceList = deviceMapper.selectList(new QueryWrapper<>()); + deviceList.stream().filter(device -> DeviceLocalStatus.get(device.getPointId()).getCameraStatus()==2).forEach(device -> device.setStatus(2)); + return deviceList; + } + + public void updateTemp(String deviceId,String tempThreshold){ + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",deviceId).set("temp_threshold",tempThreshold); + deviceMapper.update(null,updateWrapper); + } + + public void updateStatus(String deviceId,Integer status){ + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",deviceId).set("status",status); + deviceMapper.update(null,updateWrapper); + } + + @Override + public List select(DeviceDto deviceDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("type","camera") + .eq(StringUtils.isNotBlank(deviceDto.getPowerStationId()),"power_station",deviceDto.getPowerStationId()) + .eq(StringUtils.isNotBlank(deviceDto.getPointId()),"point",deviceDto.getPointId()) + .eq(deviceDto.getStatus()!=null,"status",deviceDto.getStatus()); + List deviceList = deviceMapper.selectList(queryWrapper); + deviceList.stream().filter(device -> DeviceLocalStatus.get(device.getPointId()).getCameraStatus()==2).forEach(device -> device.setStatus(2)); + return deviceList; + } + + @Override + public Integer selectTotal(DeviceDto deviceDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("type","camera") + .eq(StringUtils.isNotBlank(deviceDto.getPowerStationId()),"power_station",deviceDto.getPowerStationId()) + .eq(StringUtils.isNotBlank(deviceDto.getPointId()),"point",deviceDto.getPointId()) + .eq(deviceDto.getStatus()!=null,"status",deviceDto.getStatus()); + return deviceMapper.selectCount(queryWrapper); + } + + @Override + public Boolean add(DeviceDto deviceDto,String type) { + Device device = new Device(); + String deviceId = "camera_"+SnowFlakeUtil.getDefaultSnowFlakeId(); + device.setDeviceId(deviceId) + .setType(type) + .setName(deviceDto.getName()) + .setPowerStationId(deviceDto.getPowerStationId()) + .setProduct("BOLT_AND_LINE") + .setMotorGroupId(deviceDto.getMotorGroupId()) + .setPointId(deviceDto.getPointId()) + .setStatus(1) + .setTempThreshold("") + .setConfig(deviceDto.getConfig()) + .setVender(deviceDto.getVender()); + boolean result = deviceMapper.insert(device)>0?true:false; + if(result){ + DeviceLocalStatus.putIfAbsent(device.getPointId(), LocalStatus.builder().pointStatus(2).cameraStatus(2).tempSensorStatus(2).build()); + } + return result; + } + + @Override + public Boolean updateByBizId(DeviceDto deviceDto) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",deviceDto.getDeviceId()) + .set("power_station",deviceDto.getPowerStationId()) + .set("motor_group",deviceDto.getMotorGroupId()) + .set("point",deviceDto.getPointId()) + .set("name",deviceDto.getName()) + .set("vender",deviceDto.getVender()) + .set("config",deviceDto.getConfig()); + return deviceMapper.update(null,updateWrapper)>0?true:false; + } + + @Override + public Boolean deactivate(DeviceDto deviceDto) { + if (!DeviceLocalStatus.containsKey(deviceDto.getPointId())){ + return false; + } + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",deviceDto.getDeviceId()).set("status",deviceDto.getStatus()); + deviceMapper.update(null,updateWrapper); + return true; + } + @Override + public List select(TempSenserDto tempSenserDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("type","temperature_sensor") + .eq(StringUtils.isNotBlank(tempSenserDto.getPowerStationId()),"power_station",tempSenserDto.getPowerStationId()) + .eq(StringUtils.isNotBlank(tempSenserDto.getPointId()),"point",tempSenserDto.getPointId()) + .eq(tempSenserDto.getStatus()!=null,"status",tempSenserDto.getStatus()); + List deviceList = deviceMapper.selectList(queryWrapper); + deviceList.stream().filter(device -> DeviceLocalStatus.get(device.getPointId()).getCameraStatus()==2).forEach(device -> device.setStatus(2)); + return deviceList; + } + + @Override + public Integer selectTotal(TempSenserDto tempSenserDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("type","temperature_sensor") + .eq(StringUtils.isNotBlank(tempSenserDto.getPowerStationId()),"power_station",tempSenserDto.getPowerStationId()) + .eq(StringUtils.isNotBlank(tempSenserDto.getPointId()),"point",tempSenserDto.getPointId()) + .eq(tempSenserDto.getStatus()!=null,"status",tempSenserDto.getStatus()); + return deviceMapper.selectCount(queryWrapper); + } + + @Override + public Boolean add(TempSenserDto tempSenserDto,String type) { + Device device = new Device(); + String deviceId = "temperature_sensor_"+SnowFlakeUtil.getDefaultSnowFlakeId(); + device.setDeviceId(deviceId) + .setType(type) + .setName(tempSenserDto.getName()) + .setPowerStationId(tempSenserDto.getPowerStationId()) + .setProduct("TEMPERATURE") + .setMotorGroupId(tempSenserDto.getMotorGroupId()) + .setPointId(tempSenserDto.getPointId()) + .setStatus(1) + .setTempThreshold("") + .setConfig(tempSenserDto.getConfig().toJSONString()) + .setVender(tempSenserDto.getTypeId()); + boolean result = deviceMapper.insert(device)>0?true:false; + if(result){ + DeviceLocalStatus.putIfAbsent(device.getPointId(), LocalStatus.builder().pointStatus(2).cameraStatus(2).tempSensorStatus(2).build()); + } + return result; + } + + @Override + public Boolean updateByBizId(TempSenserDto tempSenserDto) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",tempSenserDto.getDeviceId()) + .set("power_station",tempSenserDto.getPowerStationId()) + .set("motor_group",tempSenserDto.getMotorGroupId()) + .set("point",tempSenserDto.getPointId()) + .set("name",tempSenserDto.getName()) + .set("vender",tempSenserDto.getTypeId()) + .set("config",tempSenserDto.getConfig().toJSONString()); + return deviceMapper.update(null,updateWrapper)>0?true:false; + } + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DeviceTempleteServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DeviceTempleteServiceImpl.java new file mode 100644 index 0000000..6f32a84 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/DeviceTempleteServiceImpl.java @@ -0,0 +1,54 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.mapper.DeviceTempleteMapper; +import com.jiluo.bolt.entity.po.DeviceTemplete; +import com.jiluo.bolt.service.DeviceTempleteService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * (DeviceTemplete)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:11 + */ +@Service +public class DeviceTempleteServiceImpl extends ServiceImpl implements DeviceTempleteService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(DeviceTempleteServiceImpl.class); + + List deviceTempleteList = new ArrayList<>(); + + @Autowired + DeviceTempleteMapper deviceTempleteMapper; + + @PostConstruct + private void init(){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + deviceTempleteList = deviceTempleteMapper.selectList(queryWrapper); + } + + @Override + public DeviceTemplete getByBizId(String venderId) { + return deviceTempleteList.stream().filter(x-> StringUtils.equalsIgnoreCase(x.getBizId(),venderId)).findFirst().orElse(null); + } + + @Override + public List getByBizId(List venderIds) { + return deviceTempleteList.stream().filter(x->venderIds.stream().anyMatch(_venderId -> StringUtils.equalsIgnoreCase(x.getBizId(),_venderId))).collect(Collectors.toList()); + } + + @Override + public List getAll() { + return deviceTempleteList; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/JobServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/JobServiceImpl.java new file mode 100644 index 0000000..a854c1c --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/JobServiceImpl.java @@ -0,0 +1,99 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.mapper.JobMapper; +import com.jiluo.bolt.entity.po.Job; +import com.jiluo.bolt.service.JobService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; +import java.util.List; + +/** + * 检测任务表(Job)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ +@Service +public class JobServiceImpl extends ServiceImpl implements JobService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(JobServiceImpl.class); + + @Autowired + JobMapper jobMapper; + + @Override + public Job getByBizId(String jobId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("biz_id",jobId); + return jobMapper.selectOne(queryWrapper); + } + + public List getByBizId(List jobIds) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("biz_id", jobIds); + return jobMapper.selectList(queryWrapper); + } + + public Job getRealtimeJob(String pointId, String product){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("point",pointId) + .eq("product",product) + .orderByDesc("gmt_create") + .last("limit 1"); + return jobMapper.selectOne(queryWrapper); + } + + public List getHistoryJob(String pointId, Integer status, String product, Integer current, Integer size, + @DateTimeFormat(pattern = "yyyy-MM-dd") String startTime, + @DateTimeFormat(pattern = "yyyy-MM-dd") String endTime){ + StringBuilder lastSql = new StringBuilder(); + lastSql.append("limit ").append(current-1).append(",").append(size); + QueryWrapper wrapper = new QueryWrapper(); + //组装模糊查询条件 + wrapper.eq(StringUtils.isNotBlank(pointId),"point",pointId) + .eq(status!=null,"status",status) + .eq(StringUtils.isNotBlank(product),"product",product) + .between(StringUtils.isNotBlank(startTime)&&StringUtils.isNotBlank(endTime),"gmt_create",startTime,endTime) + .ne("attribute","{}") + .orderByDesc("gmt_create"); + wrapper.last(lastSql.toString()); + return jobMapper.selectList(wrapper); + } + + public Integer getHistoryJobTotal(String pointId,Integer status, String product, + @DateTimeFormat(pattern = "yyyy-MM-dd") String startTime, + @DateTimeFormat(pattern = "yyyy-MM-dd") String endTime){ + QueryWrapper wrapper = new QueryWrapper(); + //组装模糊查询条件 + wrapper.eq(StringUtils.isNotBlank(pointId),"point",pointId) + .eq(status!=null,"status",status) + .eq(StringUtils.isNotBlank(product),"product",product) + .between(StringUtils.isNotBlank(startTime)&&StringUtils.isNotBlank(endTime),"gmt_create",startTime,endTime); + return jobMapper.selectCount(wrapper); + } + + public List exportData(Date startTime, Date endTime){ + QueryWrapper wrapper = new QueryWrapper(); + //组装模糊查询条件 + wrapper.between("gmt_create",startTime,endTime); + return jobMapper.selectList(wrapper); + } + + public void addJob(Job job){ + jobMapper.insert(job); + } + + public void updateAttribute(String jobId, String attribute, Integer status){ + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",jobId).set("attribute",attribute).set("status",status); + jobMapper.update(null,updateWrapper); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/MinioServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/MinioServiceImpl.java new file mode 100644 index 0000000..a981624 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/MinioServiceImpl.java @@ -0,0 +1,191 @@ +package com.jiluo.bolt.service.impl; + +import com.jiluo.bolt.service.MinioService; +import com.jiluo.bolt.util.MinioUtil; +import io.minio.ObjectStat; +import io.minio.Result; +import io.minio.errors.*; +import io.minio.messages.Item; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/13/14:58 + * @Description: + */ +@Service +public class MinioServiceImpl implements MinioService { + + @Autowired + private MinioUtil minioUtil; + + /** + * 判断 bucket是否存在 + * + * @param bucketName + * @return + */ + @Override + public boolean bucketExists(String bucketName) { + return minioUtil.bucketExists(bucketName); + } + + /** + * 创建 bucket + * + * @param bucketName + */ + @Override + public void makeBucket(String bucketName) { + minioUtil.makeBucket(bucketName); + } + + /** + * 文件上传 + * + * @param bucketName + * @param objectName + * @param filename + */ + @Override + public void putObject(String bucketName, String objectName, String filename) { + minioUtil.putObject(bucketName, objectName, filename); + } + + + @Override + public void putObject(String bucketName, String objectName, InputStream stream, String contentType) { + minioUtil.putObject(bucketName, objectName, stream, contentType); + } + + /** + * 文件上传 + * + * @param bucketName + * @param multipartFile + */ + @Override + public void putObject(String bucketName, MultipartFile multipartFile, String filename) { + minioUtil.putObject(bucketName, multipartFile, filename); + } + + /** + * 删除文件 + * + * @param bucketName + * @param objectName + */ + @Override + public boolean removeObject(String bucketName, String objectName) { + return minioUtil.removeObject(bucketName, objectName); + } + + /** + * 下载文件 + * + * @param fileName + * @param originalName + * @param response + */ + @Override + public void downloadFile(String bucketName, String fileName, String originalName, HttpServletResponse response) { + minioUtil.downloadFile(bucketName, fileName, originalName, response); + } + + /** + * 获取文件路径 + * + * @param bucketName + * @param objectName + * @return + */ + @Override + public String getObjectUrl(String bucketName, String objectName) { + return minioUtil.getObjectUrl(bucketName, objectName); + } + + /** + * @param bucketName + * @param objectName + * @description: 文件下载 + * @param: bucketName + * objectName + * @return: io.minio.ObjectStat + * @author yangc + * @date: 2020-10-20 20:24 + */ + @Override + public ObjectStat statObject(String bucketName, String objectName) { + return minioUtil.statObject(bucketName, objectName); + } + + /** + * 以流的形式获取一个文件对象 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @return + */ + @Override + public InputStream getObject(String bucketName, String objectName) { + return minioUtil.getObject(bucketName, objectName); + } + + /** + * 列出存储桶中所有对象 + * + * @param bucketName 存储桶名称 + * @return + */ + @Override + public Iterable> listObjects(String bucketName) { + return minioUtil.listObjects(bucketName); + } + + /** + * 生成一个给HTTP GET请求用的presigned URL + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @param expires 失效时间(以秒为单位),默认是7天,不得大于七天 + * @return + */ + @Override + public String presignedGetObject(String bucketName, String objectName, Integer expires) { + return minioUtil.presignedGetObject(bucketName, objectName, expires); + } + + /** + * 设置存储桶策略 + * + * @param bucketName 存储桶名称 + * @param policy + * @return + */ + @Override + public void setBucketPolicy(String bucketName, String policy) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException { + minioUtil.setBucketPolicy(bucketName, policy); + } + + /** + * 获取存储桶策略 + * + * @param bucketName 存储桶名称 + * @return + */ + @Override + public String getBucketPolicy(String bucketName) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, BucketPolicyTooLargeException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException { + return minioUtil.getBucketPolicy(bucketName); + } + + +} \ No newline at end of file diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/MotorGroupServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/MotorGroupServiceImpl.java new file mode 100644 index 0000000..a5b0c5b --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/MotorGroupServiceImpl.java @@ -0,0 +1,89 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.entity.dto.MotorGroupDto; +import com.jiluo.bolt.mapper.MotorGroupMapper; +import com.jiluo.bolt.entity.po.MotorGroup; +import com.jiluo.bolt.service.MotorGroupService; +import com.jiluo.bolt.util.SnowFlakeUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 机组信息表(MotorGroup)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ +@Service +public class MotorGroupServiceImpl extends ServiceImpl implements MotorGroupService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(MotorGroupServiceImpl.class); + + @Autowired + MotorGroupMapper motorGroupMapper; + + public List getAll(){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + return motorGroupMapper.selectList(queryWrapper); + } + + + public MotorGroup getByBizId(String bizId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("biz_id",bizId); + return motorGroupMapper.selectOne(queryWrapper); + } + + public Integer getTotalByPowerStation(String powerStationId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("power_station",powerStationId); + return motorGroupMapper.selectCount(queryWrapper); + } + + @Override + public List select(MotorGroupDto motorGroupDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StringUtils.isNotBlank(motorGroupDto.getNumber()),"name",motorGroupDto.getNumber()) + .eq(StringUtils.isNotBlank(motorGroupDto.getPowerStationId()),"power_station",motorGroupDto.getPowerStationId()); + return motorGroupMapper.selectList(queryWrapper); + } + + @Override + public Integer selectTotal(MotorGroupDto motorGroupDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StringUtils.isNotBlank(motorGroupDto.getNumber()),"name",motorGroupDto.getNumber()) + .eq(StringUtils.isNotBlank(motorGroupDto.getPowerStationId()),"power_station",motorGroupDto.getPowerStationId()); + return motorGroupMapper.selectCount(queryWrapper); + } + + @Override + public Boolean add(MotorGroupDto motorGroupDto) { + MotorGroup motorGroup = new MotorGroup(); + motorGroup.setMotorGroupId("GROUP_"+ SnowFlakeUtil.getDefaultSnowFlakeId()) + .setName(motorGroupDto.getNumber()+"#机组") + .setContact(motorGroupDto.getContact()) + .setPhone(motorGroupDto.getPhone()) + .setPowerStation(motorGroupDto.getPowerStationId()); + return motorGroupMapper.insert(motorGroup)>0?true:false; + } + + @Override + public Boolean updateByBizId(MotorGroupDto motorGroupDto) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",motorGroupDto.getMotorGroupId()) + .set("name",motorGroupDto.getNumber()+"#机组") + .set("contact",motorGroupDto.getContact()) + .set("phone",motorGroupDto.getPhone()) + .set("power_station",motorGroupDto.getPowerStationId()); + return motorGroupMapper.update(null,updateWrapper)>0?true:false; + } + + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/PointServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/PointServiceImpl.java new file mode 100644 index 0000000..ebb230c --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/PointServiceImpl.java @@ -0,0 +1,152 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.entity.dto.PointDto; +import com.jiluo.bolt.mapper.PointMapper; +import com.jiluo.bolt.entity.po.Point; +import com.jiluo.bolt.service.DeviceService; +import com.jiluo.bolt.service.PointService; +import com.jiluo.bolt.util.SnowFlakeUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.*; + +/** + * 检测点表(Point)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ +@Service +public class PointServiceImpl extends ServiceImpl implements PointService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(PointServiceImpl.class); + + @Autowired + PointMapper pointMapper; + + + public List getAll(){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + List points = pointMapper.selectList(queryWrapper); + points.stream().forEach(point -> { + if (DeviceService.DeviceLocalStatus.containsKey(point.getPointId())){ + point.setStatus(DeviceService.DeviceLocalStatus.get(point.getPointId()).getPointStatus()); + } + }); + return points; + } + + public List getByRole(String motorGroup){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("motor_group",motorGroup); + return pointMapper.selectList(queryWrapper); + } + + public void updateEnableDetect(String pointId,Integer enableDetect){ + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",pointId).set("enable_detect",enableDetect); + pointMapper.update(null,updateWrapper); + } + + public void updateResetTime(String pointId,Date date){ + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",pointId).set("gmt_reset",date); + pointMapper.update(null,updateWrapper); + } + + public void updateConfig(String pointId, String config){ + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",pointId).set("config",config); + pointMapper.update(null,updateWrapper); + } + + public Point getByBizId(String bizId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("biz_id",bizId); + return pointMapper.selectOne(queryWrapper); + } + + @Override + public Integer getTotalByMotorGroup(String motorGroupId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("motor_group",motorGroupId); + + return pointMapper.selectCount(queryWrapper); + } + + @Override + public List select(PointDto pointDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StringUtils.isNotBlank(pointDto.getName()),"name",pointDto.getName()) + .eq(StringUtils.isNotBlank(pointDto.getPowerStationId()),"power_station",pointDto.getPowerStationId()) + .eq(StringUtils.isNotBlank(pointDto.getMotorGroupId()),"motor_group",pointDto.getMotorGroupId()); + return pointMapper.selectList(queryWrapper); + } + + @Override + public Integer selectTotal(PointDto pointDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StringUtils.isNotBlank(pointDto.getName()),"name",pointDto.getName()) + .eq(StringUtils.isNotBlank(pointDto.getPowerStationId()),"power_station",pointDto.getPowerStationId()) + .eq(StringUtils.isNotBlank(pointDto.getMotorGroupId()),"motor_group",pointDto.getMotorGroupId()); + return pointMapper.selectCount(queryWrapper); + } + + @Override + public Boolean add(PointDto pointDto) { + Point point = new Point(); + point.setPointId("POINT_"+ SnowFlakeUtil.getDefaultSnowFlakeId()) + .setPowerStation(pointDto.getPowerStationId()) + .setMotorGroup(pointDto.getMotorGroupId()) + .setName(pointDto.getName()) + .setPoleNum(pointDto.getPoleNum()) + .setManualTime(pointDto.getManualTime().intValue()) + .setAutomaticTime(pointDto.getAutomaticTime().intValue()) + .setStatus(0) + .setEnableDetect(0) + .setBoltDetect(0) + .setLineDetect(0) + .setPoleOpenDetect(0) + .setPointTempDetect(0) + .setConfig(" "); + return pointMapper.insert(point)>0?true:false; + } + + @Override + public Boolean updateByBizId(PointDto pointDto) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",pointDto.getPointId()) + .set("power_station",pointDto.getPowerStationId()) + .set("motor_group",pointDto.getMotorGroupId()) + .set("name",pointDto.getName()) + .set("pole_num",pointDto.getPoleNum()) + .set("manual_time",pointDto.getManualTime()) + .set("automatic_time",pointDto.getAutomaticTime()); + return pointMapper.update(null,updateWrapper)>0?true:false; + } + + @Override + public Boolean updateDetect(PointDto pointDto) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",pointDto.getPointId()) + .set("bolt_detect",pointDto.getBoltDetect().equals(true)?0:1) + .set("line_detect",pointDto.getLineDetect().equals(true)?0:1) + .set("pole_open_detect",pointDto.getPoleOpenDetect().equals(true)?0:1) + .set("point_temp_detect",pointDto.getPointTempDetect().equals(true)?0:1); + return pointMapper.update(null,updateWrapper)>0?true:false; + } + + @Override + public List getByPowerStation(String powerStationId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("power_station",powerStationId); + return pointMapper.selectList(queryWrapper); + } + + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/PowerStationServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/PowerStationServiceImpl.java new file mode 100644 index 0000000..ac3f741 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/PowerStationServiceImpl.java @@ -0,0 +1,77 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.entity.dto.PowerStationDto; +import com.jiluo.bolt.mapper.PowerStationMapper; +import com.jiluo.bolt.entity.po.PowerStation; +import com.jiluo.bolt.service.PowerStationService; +import com.jiluo.bolt.util.SnowFlakeUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 电站表(PowerStation)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ +@Service +public class PowerStationServiceImpl extends ServiceImpl implements PowerStationService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(PowerStationServiceImpl.class); + + @Autowired + PowerStationMapper powerStationMapper; + + public List getAll(){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + return powerStationMapper.selectList(queryWrapper); + } + + public PowerStation getByBizId(String bizId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("biz_id",bizId); + return powerStationMapper.selectOne(queryWrapper); + } + + public List select(PowerStationDto powerStationDto){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StringUtils.isNotBlank(powerStationDto.getName()),"name",powerStationDto.getName()); + return powerStationMapper.selectList(queryWrapper); + } + + public Integer selectTotal(PowerStationDto powerStationDto){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StringUtils.isNotBlank(powerStationDto.getName()),"name",powerStationDto.getName()); + return powerStationMapper.selectCount(queryWrapper); + } + + public Boolean add(PowerStationDto powerStationDto){ + PowerStation powerStation = new PowerStation(); + powerStation.setPowerStationId("STATION_"+SnowFlakeUtil.getDefaultSnowFlakeId()) + .setName(powerStationDto.getName()) + .setAddress(powerStationDto.getAddress()) + .setContact(powerStationDto.getContact()) + .setPhone(powerStationDto.getPhone()) + .setIntroduction(powerStationDto.getIntroduction()); + return powerStationMapper.insert(powerStation)>0?true:false; + } + + public Boolean updateByBizId(PowerStationDto powerStationDto){ + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("biz_id",powerStationDto.getPowerStationId()) + .set(StringUtils.isNotBlank(powerStationDto.getName()),"name",powerStationDto.getName()) + .set("address",powerStationDto.getAddress()) + .set("contact",powerStationDto.getContact()) + .set("phone",powerStationDto.getPhone()) + .set("introduction",powerStationDto.getIntroduction()); + return powerStationMapper.update(null,updateWrapper)>0?true:false; + } + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/ProductServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/ProductServiceImpl.java new file mode 100644 index 0000000..8690bc8 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/ProductServiceImpl.java @@ -0,0 +1,21 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.mapper.ProductMapper; +import com.jiluo.bolt.entity.po.Product; +import com.jiluo.bolt.service.ProductService; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 检测类型表(Product)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ +@Service +public class ProductServiceImpl extends ServiceImpl implements ProductService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(ProductServiceImpl.class); + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/RoleItemServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/RoleItemServiceImpl.java new file mode 100644 index 0000000..4bdf8eb --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/RoleItemServiceImpl.java @@ -0,0 +1,53 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.entity.po.RoleItem; +import com.jiluo.bolt.mapper.RoleItemMapper; +import com.jiluo.bolt.service.RoleItemService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 权限信息表(RoleItem)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ +@Service +public class RoleItemServiceImpl extends ServiceImpl implements RoleItemService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(RoleItemServiceImpl.class); + + private List roleItemList = new ArrayList<>(); + + @Autowired + RoleItemMapper roleItemMapper; + + + @PostConstruct + private void init(){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + roleItemList = roleItemMapper.selectList(queryWrapper); + } + + + public RoleItem getByBizId(String itemId){ + return roleItemList.stream().filter(x -> StringUtils.equalsIgnoreCase(x.getBizId(), itemId)).findFirst().orElse(null); + } + + public List getByBizId(List itemIds){ + return roleItemList.stream().filter(x -> itemIds.stream().anyMatch(_itemId -> StringUtils.equalsIgnoreCase(x.getBizId(), _itemId))).collect(Collectors.toList()); + } + + public List getAll(){ + return roleItemList; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/RoleServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..400ebd4 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/RoleServiceImpl.java @@ -0,0 +1,62 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.entity.dto.RoleDto; +import com.jiluo.bolt.mapper.RoleMapper; +import com.jiluo.bolt.entity.po.Role; +import com.jiluo.bolt.service.RoleService; +import com.jiluo.bolt.util.SnowFlakeUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 角色信息表(Role)表服务实现类 + * @author Fangy + * @date 2023-05-05 18:59:16 + */ +@Service +public class RoleServiceImpl extends ServiceImpl implements RoleService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(RoleServiceImpl.class); + + @Autowired + RoleMapper roleMapper; + + public Role getByRoleId(String roleId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("role_id",roleId); + return roleMapper.selectOne(queryWrapper); + } + + @Override + public List select(RoleDto roleDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(StringUtils.isNotBlank(roleDto.getRoleId()),"role_id",roleDto.getRoleId()) + .like(StringUtils.isNotBlank(roleDto.getRoleName()),"role_name",roleDto.getRoleName()); + return roleMapper.selectList(queryWrapper); + } + + @Override + public String add(RoleDto roleDto) { + Role role = new Role(); + role.setRoleId("role_"+ SnowFlakeUtil.getDefaultSnowFlakeId()) + .setRoleName(roleDto.getRoleName()); + roleMapper.insert(role); + return role.getRoleId(); + } + + @Override + public Boolean updateByBizId(RoleDto roleDto) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("role_id",roleDto.getRoleId()) + .set("role_name",roleDto.getRoleName()); + return roleMapper.update(null,updateWrapper)>0?true:false; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/RoleValueServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/RoleValueServiceImpl.java new file mode 100644 index 0000000..0a53f98 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/RoleValueServiceImpl.java @@ -0,0 +1,64 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.api.R; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.entity.dto.PermissionDto; +import com.jiluo.bolt.entity.po.RoleValue; +import com.jiluo.bolt.mapper.RoleValueMapper; +import com.jiluo.bolt.service.RoleValueService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 角色信息表(Role)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ +@Service +public class RoleValueServiceImpl extends ServiceImpl implements RoleValueService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(RoleValueServiceImpl.class); + + @Autowired + RoleValueMapper roleValueMapper; + + public List getByRoleId(String roleId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("role_id",roleId); + return roleValueMapper.selectList(queryWrapper); + } + + public RoleValue getProduct(String roleId){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("role_id",roleId) + .eq("item_id","detect_pole"); + return roleValueMapper.selectOne(queryWrapper); + } + + @Override + public List select(PermissionDto permissionDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("role_id",permissionDto.getRoleId()); + return roleValueMapper.selectList(queryWrapper); + } + + @Override + public Boolean add(RoleValue roleValue) { + return roleValueMapper.insert(roleValue)>0?true:false; + } + + @Override + public Boolean updateByItemId(RoleValue roleValue) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("role_id",roleValue.getRoleId()) + .eq("item_id",roleValue.getItemId()) + .set("item_value",roleValue.getItemValue()); + return roleValueMapper.update(null,updateWrapper)>0?true:false; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/UserServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..7e88251 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/UserServiceImpl.java @@ -0,0 +1,65 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.entity.dto.UserDto; +import com.jiluo.bolt.mapper.UserMapper; +import com.jiluo.bolt.entity.po.User; +import com.jiluo.bolt.service.UserService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 用户信息表(User)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ +@Service +public class UserServiceImpl extends ServiceImpl implements UserService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); + + @Autowired + private UserMapper userMapper; + + public User getByUid(String uid){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("biz_id",uid); + User user = userMapper.selectOne(queryWrapper); + return user; + } + + @Override + public List select(UserDto userDto) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(StringUtils.isNotBlank(userDto.getRole()),"role",userDto.getRole()) + .like(StringUtils.isNotBlank(userDto.getUserName()),"user_name",userDto.getUserName()); + return userMapper.selectList(queryWrapper); + } + + @Override + public Boolean add(UserDto userDto) { + User user = new User(); + user.setBizId(userDto.getUid()) + .setUserName(userDto.getUserName()) + .setPassword(userDto.getPassword()) + .setRole(userDto.getRole()); + return userMapper.insert(user)>0?true:false; + } + + @Override + public Boolean updateByBizId(UserDto userDto) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("id",userDto.getId()) + .set("biz_id",userDto.getUid()) + .set("user_name",userDto.getUserName()) + .set("password",userDto.getPassword()); + return userMapper.update(null,updateWrapper)>0?true:false; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/service/impl/VersionServiceImpl.java b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/VersionServiceImpl.java new file mode 100644 index 0000000..f243fc4 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/service/impl/VersionServiceImpl.java @@ -0,0 +1,31 @@ +package com.jiluo.bolt.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jiluo.bolt.mapper.VersionMapper; +import com.jiluo.bolt.entity.po.Version; +import com.jiluo.bolt.service.VersionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 版本信息表(Version)表服务实现类 + * @author Fangy + * @date 2023-05-05 10:10:12 + */ +@Service +public class VersionServiceImpl extends ServiceImpl implements VersionService { + /** logger:日志文件 */ + private static final Logger logger = LoggerFactory.getLogger(VersionServiceImpl.class); + + @Autowired + VersionMapper versionMapper; + + public Version select(){ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("available",1); + return versionMapper.selectOne(queryWrapper); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/AesEncryptUtil.java b/bolt-core/src/main/java/com/jiluo/bolt/util/AesEncryptUtil.java new file mode 100644 index 0000000..3dfeafc --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/AesEncryptUtil.java @@ -0,0 +1,259 @@ +package com.jiluo.bolt.util; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/03/10:05 + * @Description: + */ + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.NoSuchAlgorithmException; + +/** + * AES 加/解密工具类 + * 使用密钥时请使用 initKey() 方法来生成随机密钥 + * initKey 方法内部使用 java.crypto.KeyGenerator 密钥生成器来生成特定于 AES 算法参数集的随机密钥 + */ +@Slf4j +public class AesEncryptUtil { + + private AesEncryptUtil() { + } + + /** + * 密钥算法类型 + */ + public static final String KEY_ALGORITHM = "AES"; + + /** + * 密钥的默认位长度 + */ + public static final int DEFAULT_KEY_SIZE = 128; + + /** + * 加解密算法/工作模式/填充方式 + */ + private static final String ECB_PKCS_5_PADDING = "AES/ECB/PKCS5Padding"; + public static final String ECB_NO_PADDING = "AES/ECB/NoPadding"; + + public static String base64Encode(byte[] bytes) { + return Base64.encodeBase64String(bytes); + } + + public static byte[] base64Decode(String base64Code) { + return Base64.decodeBase64(base64Code); + } + + public static byte[] aesEncryptToBytes(String content, String hexAesKey) throws Exception { + return encrypt(content.getBytes(StandardCharsets.UTF_8), Hex.decodeHex(hexAesKey.toCharArray())); + } + + public static String aesEncrypt(String content, String hexAesKey) throws Exception { + return base64Encode(aesEncryptToBytes(content, hexAesKey)); + } + + public static String aesDecryptByBytes(byte[] encryptBytes, String hexAesKey) throws Exception { + byte[] decrypt = decrypt(encryptBytes, Hex.decodeHex(hexAesKey.toCharArray())); + return new String(decrypt, StandardCharsets.UTF_8); + } + + public static String aesDecrypt(String encryptStr, String hexAesKey) throws Exception { + return aesDecryptByBytes(base64Decode(encryptStr), hexAesKey); + } + + + /** + * 生成 Hex 格式默认长度的随机密钥 + * 字符串长度为 32,解二进制后为 16 个字节 + * + * @return String Hex 格式的随机密钥 + */ + public static String initHexKey() { + return Hex.encodeHexString(initKey()); + } + + /** + * 生成默认长度的随机密钥 + * 默认长度为 128 + * + * @return byte[] 二进制密钥 + */ + public static byte[] initKey() { + return initKey(DEFAULT_KEY_SIZE); + } + + /** + * 生成密钥 + * 128、192、256 可选 + * + * @param keySize 密钥长度 + * @return byte[] 二进制密钥 + */ + public static byte[] initKey(int keySize) { + // AES 要求密钥长度为 128 位、192 位或 256 位 + if (keySize != 128 && keySize != 192 && keySize != 256) { + throw new RuntimeException("error keySize: " + keySize); + } + // 实例化 + KeyGenerator keyGenerator; + try { + keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("no such algorithm exception: " + KEY_ALGORITHM, e); + } + keyGenerator.init(keySize); + // 生成秘密密钥 + SecretKey secretKey = keyGenerator.generateKey(); + // 获得密钥的二进制编码形式 + return secretKey.getEncoded(); + } + + /** + * 转换密钥 + * + * @param key 二进制密钥 + * @return Key 密钥 + */ + private static Key toKey(byte[] key) { + // 实例化 DES 密钥材料 + return new SecretKeySpec(key, KEY_ALGORITHM); + } + + /** + * 加密 + * + * @param data 待加密数据 + * @param key 密钥 + * @return byte[] 加密的数据 + */ + public static byte[] encrypt(byte[] data, byte[] key) { + return encrypt(data, key, ECB_PKCS_5_PADDING); + } + + /** + * 加密 + * + * @param data 待加密数据 + * @param key 密钥 + * @param cipherAlgorithm 算法/工作模式/填充模式 + * @return byte[] 加密的数据 + */ + public static byte[] encrypt(byte[] data, byte[] key, final String cipherAlgorithm) { + // 还原密钥 + Key k = toKey(key); + try { + Cipher cipher = Cipher.getInstance(cipherAlgorithm); + // 初始化,设置为加密模式 + cipher.init(Cipher.ENCRYPT_MODE, k); + + // 发现使用 NoPadding 时,使用 ZeroPadding 填充 + if (ECB_NO_PADDING.equals(cipherAlgorithm)) { + return cipher.doFinal(formatWithZeroPadding(data, cipher.getBlockSize())); + } + + // 执行操作 + return cipher.doFinal(data); + } catch (Exception e) { + throw new RuntimeException("AES encrypt error", e); + } + } + + /** + * 解密 + * + * @param data 待解密数据 + * @param key 密钥 + * @return byte[] 解密的数据 + */ + public static byte[] decrypt(byte[] data, byte[] key) { + return decrypt(data, key, ECB_PKCS_5_PADDING); + } + + /** + * 解密 + * + * @param data 待解密数据 + * @param key 密钥 + * @param cipherAlgorithm 算法/工作模式/填充模式 + * @return byte[] 解密的数据 + */ + public static byte[] decrypt(byte[] data, byte[] key, final String cipherAlgorithm) { + // 还原密钥 + Key k = toKey(key); + try { + Cipher cipher = Cipher.getInstance(cipherAlgorithm); + // 初始化,设置为解密模式 + cipher.init(Cipher.DECRYPT_MODE, k); + + // 发现使用 NoPadding 时,使用 ZeroPadding 填充 + if (ECB_NO_PADDING.equals(cipherAlgorithm)) { + return removeZeroPadding(cipher.doFinal(data), cipher.getBlockSize()); + } + + // 执行操作 + return cipher.doFinal(data); + } catch (Exception e) { + throw new RuntimeException("AES decrypt error", e); + } + } + + private static byte[] formatWithZeroPadding(byte[] data, final int blockSize) { + final int length = data.length; + final int remainLength = length % blockSize; + + if (remainLength > 0) { + byte[] inputData = new byte[length + blockSize - remainLength]; + System.arraycopy(data, 0, inputData, 0, length); + return inputData; + } + return data; + } + + private static byte[] removeZeroPadding(byte[] data, final int blockSize) { + final int length = data.length; + final int remainLength = length % blockSize; + if (remainLength == 0) { + // 解码后的数据正好是块大小的整数倍,说明可能存在补 0 的情况,去掉末尾所有的 0 + int i = length - 1; + while (i >= 0 && 0 == data[i]) { + i--; + } + byte[] outputData = new byte[i + 1]; + System.arraycopy(data, 0, outputData, 0, outputData.length); + return outputData; + } + return data; + } + +// public static void main(String[] args) throws Exception { +// +// byte[] bytes = AesEncryptUtil.initKey(); +// +// // 使用密钥生成器 KeyGenerator 生成的 16 字节随机密钥的 hex 字符串,使用时解 hex 得到二进制密钥 +// String aesKey = Hex.encodeHexString(bytes); +// System.out.println(aesKey); +// +//// String aesKey = "dbf13279f5bc85b038cbc9ee21dfbc03"; +// +// String content = "123456"; +// System.out.println("加密前:" + content); +// +// String encrypt = aesEncrypt(content, aesKey); +// System.out.println(encrypt.length() + ":加密后:" + encrypt); +// +// String decrypt = aesDecrypt(encrypt, aesKey); +// System.out.println("解密后:" + decrypt); +// } + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/BigDecimalUtils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/BigDecimalUtils.java new file mode 100644 index 0000000..082d155 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/BigDecimalUtils.java @@ -0,0 +1,24 @@ +package com.jiluo.bolt.util; + +import com.jiluo.bolt.common.DefectType; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/25/13:58 + * @Description: + */ +public class BigDecimalUtils { + public static BigDecimal DBToFront(DefectType _type, Integer value){ + return (_type == DefectType.bolt || _type == DefectType.temperature) ? BigDecimal.valueOf(value / 100.0).setScale(2, RoundingMode.HALF_UP) : (_type == DefectType.line ? BigDecimal.valueOf(value / 1000.0).setScale(3, RoundingMode.HALF_UP) : BigDecimal.valueOf(value)); + } + + public static BigDecimal FrontToDB(DefectType _type, Float value){ + return (_type == DefectType.bolt || _type == DefectType.temperature) ? BigDecimal.valueOf(value * 100).setScale(0, RoundingMode.HALF_UP) : + (_type == DefectType.line) ? BigDecimal.valueOf(value * 1000).setScale(0, RoundingMode.HALF_UP) : BigDecimal.valueOf(value); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/CSVUtils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/CSVUtils.java new file mode 100644 index 0000000..0e03d47 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/CSVUtils.java @@ -0,0 +1,66 @@ +package com.jiluo.bolt.util; + +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.entity.po.Defect; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import com.opencsv.CSVWriter; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.List; +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/28/10:32 + * @Description:检测缺陷结果——生成CSV文件存储 + */ +@Slf4j +@Component +public class CSVUtils { + public void generateCSV(List defects, String folderPath, String fileName) { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String filePath = folderPath + "/" + fileName + ".csv"; + try { + Files.createDirectories(Paths.get(folderPath)); + } catch (Exception e) { + log.error("[CSVUtils] generateCSV: 创建文件夹错误",e.getMessage(),e); + } + try (CSVWriter writer = new CSVWriter(new OutputStreamWriter(new FileOutputStream(filePath), StandardCharsets.UTF_8))) { + // 写入CSV文件的表头 + writer.writeNext(new String[] {"创建时间", "修改时间", "缺陷编号", "电站", "机组", "检测点", "任务编号", "设备", "检测类型", "磁极编号", "螺栓编号", "检测数值", "图片", "是否告警:0-否,1-是", "状态:0-有效 1-无效,2-已处理"}); + // 写入每个Defect对象的数据 + for (Defect defect : defects) { + BigDecimal value = (defect.getType().equals(DefectType.bolt.name()) || defect.getType().equals(DefectType.temperature.name())) ? BigDecimal.valueOf(defect.getValue() / 100.0).setScale(2, RoundingMode.HALF_UP) : (defect.getType().equals(DefectType.line.name()) ? BigDecimal.valueOf(defect.getValue() / 1000.0).setScale(3, RoundingMode.HALF_UP) : BigDecimal.valueOf(defect.getValue())); + writer.writeNext(new String[] { + formatter.format(defect.getGmtCreate()), + formatter.format(defect.getGmtModify()), + defect.getBizId(), + defect.getPowerStation(), + defect.getMotorGroup(), + defect.getPoint(), + defect.getJob(), + defect.getDevice(), + defect.getType(), + defect.getZone().toString(), + defect.getPosition().toString(), + value.toString(), + defect.getData(), + defect.getAlarm().toString(), + defect.getStatus().toString() + }); + } + writer.flush(); + } catch (Exception e) { + log.error("[CSVUtils] generateCSV:写入CSV文件错误",e.getMessage(),e); + } + } + + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/DogUtil.java b/bolt-core/src/main/java/com/jiluo/bolt/util/DogUtil.java new file mode 100644 index 0000000..8d11a65 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/DogUtil.java @@ -0,0 +1,984 @@ +package com.jiluo.bolt.util; + +import com.jiluo.bolt.Aladdin.Hasp; +import com.jiluo.bolt.Aladdin.HaspTime; +import com.jiluo.bolt.Aladdin.HaspStatus; +import com.jiluo.bolt.Aladdin.HaspApiVersion; +import lombok.extern.slf4j.Slf4j; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/18/10:40 + * @Description: + */ +@Slf4j +public class DogUtil { + +// //开发商代码,通过软件用主锁导入API之后在Sentinel LDK\VendorCodes目录下可以找到 +// public static final String vendorCode = new String( +// "AzIceaqfA1hX5wS+M8cGnYh5ceevUnOZIzJBbXFD6dgf3tBkb9cvUF/Tkd/iKu2fsg9wAysYKw7RMA" + +// "sVvIp4KcXle/v1RaXrLVnNBJ2H2DmrbUMOZbQUFXe698qmJsqNpLXRA367xpZ54i8kC5DTXwDhfxWT" + +// "OZrBrh5sRKHcoVLumztIQjgWh37AzmSd1bLOfUGI0xjAL9zJWO3fRaeB0NS2KlmoKaVT5Y04zZEc06" + +// "waU2r6AU2Dc4uipJqJmObqKM+tfNKAS0rZr5IudRiC7pUwnmtaHRe5fgSI8M7yvypvm+13Wm4Gwd4V" + +// "nYiZvSxf8ImN3ZOG9wEzfyMIlH2+rKPUVHI+igsqla0Wd9m7ZUR9vFotj1uYV0OzG7hX0+huN2E/Id" + +// "gLDjbiapj1e2fKHrMmGFaIvI6xzzJIQJF9GiRZ7+0jNFLKSyzX/K3JAyFrIPObfwM+y+zAgE1sWcZ1" + +// "YnuBhICyRHBhaJDKIZL8MywrEfB2yF+R3k9wFG1oN48gSLyfrfEKuB/qgNp+BeTruWUk0AwRE9XVMU" + +// "uRbjpxa4YA67SKunFEgFGgUfHBeHJTivvUl0u4Dki1UKAT973P+nXy2O0u239If/kRpNUVhMg8kpk7" + +// "s8i6Arp7l/705/bLCx4kN5hHHSXIqkiG9tHdeNV8VYo5+72hgaCx3/uVoVLmtvxbOIvo120uTJbuLV" + +// "TvT8KtsOlb3DxwUrwLzaEMoAQAFk6Q9bNipHxfkRQER4kR7IYTMzSoW5mxh3H9O8Ge5BqVeYMEW36q" + +// "9wnOYfxOLNw6yQMf8f9sJN4KhZty02xm707S7VEfJJ1KNq7b5pP/3RjE0IKtB2gE6vAPRvRLzEohu0" + +// "m7q1aUp8wAvSiqjZy7FLaTtLEApXYvLvz6PEJdj4TegCZugj7c8bIOEqLXmloZ6EgVnjQ7/ttys7VF" + +// "ITB3mazzFiyQuKf4J6+b/a/Y"); +// +// public static final int DEMO_MEMBUFFER_SIZE = 128; +// +// //登录加密狗(一般用此函数查找加密狗) +// public int login() +// { +// +// int status; +// +// Hasp hasp = new Hasp(Hasp.HASP_DEFAULT_FID); +// hasp.login(vendorCode); +// +// //获取返回值,HASP_STATUS_OK代表成功找到加密狗,值为0 +// status = hasp.getLastError(); +// +// switch (status) { +// case HaspStatus.HASP_STATUS_OK: +// log.info("OK"); +// break; +// case HaspStatus.HASP_FEATURE_NOT_FOUND: +// log.info("no Sentinel DEMOMA key found"); +// break; +// case HaspStatus.HASP_HASP_NOT_FOUND: +// log.info("Sentinel key not found"); +// break; +// case HaspStatus.HASP_OLD_DRIVER: +// log.info("outdated driver version or no driver installed"); +// break; +// case HaspStatus.HASP_NO_DRIVER: +// log.info("Sentinel key not found"); +// break; +// case HaspStatus.HASP_INV_VCODE: +// log.info("invalid vendor code"); +// break; +// default: +// log.info("login to default feature failed"); +// } +// +// return status; +// } +// +// //写入 +// public int write(String str) +// { +// +// int status; +// int fsize = 0; +// int i; +// +// Hasp hasp = new Hasp(Hasp.HASP_DEFAULT_FID); +// +// //先获取空间大小 +// fsize = getSize(); +// +// //String转换成byte[] +// byte[] membuffer = str.getBytes(); +// +//// log.info("\nincrementing every byte in memory buffer"); +//// for (i = 0; i < fsize; i++) membuffer[i]++; +// +// log.info("\nwriting " + fsize + " bytes to memory : "); +// +// hasp.write(Hasp.HASP_FILEID_RW, 0, membuffer); +// status = hasp.getLastError(); +// +// switch (status) { +// case HaspStatus.HASP_STATUS_OK: +// log.info("OK"); +// break; +// case HaspStatus.HASP_INV_HND: +// log.info("handle not active"); +// break; +// case HaspStatus.HASP_INV_FILEID: +// log.info("invalid file id"); +// break; +// case HaspStatus.HASP_MEM_RANGE: +// log.info("beyond memory range of attached Sentinel key"); +// break; +// case HaspStatus.HASP_HASP_NOT_FOUND: +// log.info("Sentinel key not found"); +// break; +// default: +// log.info("write memory failed"); +// } +// return status; +// } +// +// //读取 +// public String read() +// { +// int status = -1; +// int fsize = 0; +// +// Hasp hasp = new Hasp(Hasp.HASP_DEFAULT_FID); +// +// //准备一个byte数组用来存读取到的数据 +// byte[] membuffer = new byte[DEMO_MEMBUFFER_SIZE]; +// +// fsize = getSize(); +// +// if (fsize != 0) { +// if (fsize > DEMO_MEMBUFFER_SIZE) fsize = DEMO_MEMBUFFER_SIZE; +// +// log.info("\nreading " + fsize + " bytes from memory : "); +// +// +// hasp.read(Hasp.HASP_FILEID_RW, 0, membuffer); +// status = hasp.getLastError(); +// +// switch (status) { +// case HaspStatus.HASP_STATUS_OK: +// log.info("OK"); +// dump(membuffer, " "); +// break; +// case HaspStatus.HASP_INV_HND: +// log.info("handle not active"); +// break; +// case HaspStatus.HASP_INV_FILEID: +// log.info("invalid file id"); +// break; +// case HaspStatus.HASP_MEM_RANGE: +// log.info("beyond memory range of attached Sentinel key"); +// break; +// case HaspStatus.HASP_HASP_NOT_FOUND: +// log.info("hasp not found"); +// break; +// default: +// log.info("read memory failed\n"); +// } +// } +// String content = byteToString(membuffer); +// //String content = new String(membuffer); +// return content; +// } +// +// //byte[]转String +// private String byteToString(byte[] bytes) { +// if (null == bytes || bytes.length == 0) { +// return ""; +// } +// String strContent = ""; +// try { +// strContent = new String(bytes, "utf-8"); +// } catch (UnsupportedEncodingException e) { +// e.printStackTrace(); +// } +// return strContent; +// } +// +// //获取空间大小 +// public int getSize() +// { +// +// int status; +// int fsize = 0; +// +// if(login()==0) +// return 0; +// Hasp hasp = new Hasp(Hasp.HASP_DEFAULT_FID); +// +// log.info("\nretrieving the key's memory size : "); +// +// fsize = hasp.getSize(Hasp.HASP_FILEID_RW); +// status = hasp.getLastError(); +// +// switch (status) { +// case HaspStatus.HASP_STATUS_OK: +// log.info("memory size is " + fsize + " bytes"); +// break; +// case HaspStatus.HASP_INV_HND: +// log.info("handle not active"); +// break; +// case HaspStatus.HASP_INV_FILEID: +// log.info("invalid file id"); +// break; +// case HaspStatus.HASP_HASP_NOT_FOUND: +// log.info("Sentinel key not found"); +// break; +// default: +// log.info("could not retrieve memory size"); +// } +// +// +// +// return fsize; +// } +// +// +// /************************************************************************ +// * +// * helper function: dumps a given block of data, in hex and ascii +// */ +// +// /* +// * Converts a byte to hex digit and writes to the supplied buffer +// */ +// private static void byte2hex(byte b, StringBuffer buf) +// { +// char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', +// '9', 'A', 'B', 'C', 'D', 'E', 'F' }; +// int high = ((b & 0xf0) >> 4); +// int low = (b & 0x0f); +// buf.append(hexChars[high]); +// buf.append(hexChars[low]); +// } +// +// +// /* +// * Converts a byte array to hex string +// */ +// private static String toHexString(byte[] block) +// { +// StringBuffer buf = new StringBuffer(); +// +// int len = block.length; +// +// for (int i = 0; i < len; i++) { +// byte2hex(block[i], buf); +// if (i < len - 1) { +// buf.append(":"); +// } +// } +// return buf.toString(); +// } +// +// public static void dump(byte[] data, String margin) +// { +// int i, j; +// byte b; +// byte[] s = new byte[16]; +// byte hex[] = {0}; +// String shex; +// String PrtString; +// +// if (data.length == 0) return; +// +// s[0] = 0; +// j = 0; +// for (i = 0; i < data.length; i++) { +// if (j == 0) log.info(margin); +// b = data[i]; +// if ((b < 32) || (b > 127)) s[j] = '.'; else s[j] = b; +// if (j < 15) +// s[j+1] = 0; +// hex[0] = b; shex = toHexString(hex); +// log.info(shex + " "); +// j++; +// if (((j & 3) == 0) && (j < 15)) log.info("| "); +// PrtString = new String(s); +// if (j > 15) { +// log.info("[" + PrtString + "]"); +// j = 0; +// s[0] = 0; +// } +// } +// if (j != 0) { +// while (j < 16) { +// log.info(" "); +// j++; +// if (((j & 3) == 0) && (j < 15)) log.info("| "); +// } +// PrtString = new String(s); +// log.info(" [" + PrtString + "]"); +// } +// } + + public static final int DEMO_MEMBUFFER_SIZE = 128; + + public static final String vendorCode = new String( + "AzIceaqfA1hX5wS+M8cGnYh5ceevUnOZIzJBbXFD6dgf3tBkb9cvUF/Tkd/iKu2fsg9wAysYKw7RMA" + + "sVvIp4KcXle/v1RaXrLVnNBJ2H2DmrbUMOZbQUFXe698qmJsqNpLXRA367xpZ54i8kC5DTXwDhfxWT" + + "OZrBrh5sRKHcoVLumztIQjgWh37AzmSd1bLOfUGI0xjAL9zJWO3fRaeB0NS2KlmoKaVT5Y04zZEc06" + + "waU2r6AU2Dc4uipJqJmObqKM+tfNKAS0rZr5IudRiC7pUwnmtaHRe5fgSI8M7yvypvm+13Wm4Gwd4V" + + "nYiZvSxf8ImN3ZOG9wEzfyMIlH2+rKPUVHI+igsqla0Wd9m7ZUR9vFotj1uYV0OzG7hX0+huN2E/Id" + + "gLDjbiapj1e2fKHrMmGFaIvI6xzzJIQJF9GiRZ7+0jNFLKSyzX/K3JAyFrIPObfwM+y+zAgE1sWcZ1" + + "YnuBhICyRHBhaJDKIZL8MywrEfB2yF+R3k9wFG1oN48gSLyfrfEKuB/qgNp+BeTruWUk0AwRE9XVMU" + + "uRbjpxa4YA67SKunFEgFGgUfHBeHJTivvUl0u4Dki1UKAT973P+nXy2O0u239If/kRpNUVhMg8kpk7" + + "s8i6Arp7l/705/bLCx4kN5hHHSXIqkiG9tHdeNV8VYo5+72hgaCx3/uVoVLmtvxbOIvo120uTJbuLV" + + "TvT8KtsOlb3DxwUrwLzaEMoAQAFk6Q9bNipHxfkRQER4kR7IYTMzSoW5mxh3H9O8Ge5BqVeYMEW36q" + + "9wnOYfxOLNw6yQMf8f9sJN4KhZty02xm707S7VEfJJ1KNq7b5pP/3RjE0IKtB2gE6vAPRvRLzEohu0" + + "m7q1aUp8wAvSiqjZy7FLaTtLEApXYvLvz6PEJdj4TegCZugj7c8bIOEqLXmloZ6EgVnjQ7/ttys7VF" + + "ITB3mazzFiyQuKf4J6+b/a/Y"); + + public static final String scope = new String( + "\n" + + " \n" + + "\n"); + + public static final String scope1 = new String( + "\n"); + + public static final String view = new String( + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"); + + public static final byte[] data = { 0x74, 0x65, 0x73, 0x74, 0x20, 0x73, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x20, 0x31, 0x32, 0x33, 0x00 }; + private static HaspTime datetime; + + /************************************************************************ + * + * helper function: dumps a given block of data, in hex and ascii + */ + + /* + * Converts a byte to hex digit and writes to the supplied buffer + */ + private static void byte2hex(byte b, StringBuffer buf) + { + char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + int high = ((b & 0xf0) >> 4); + int low = (b & 0x0f); + buf.append(hexChars[high]); + buf.append(hexChars[low]); + } + + /* + * Converts a byte array to hex string + */ + private static String toHexString(byte[] block) + { + StringBuffer buf = new StringBuffer(); + + int len = block.length; + + for (int i = 0; i < len; i++) { + byte2hex(block[i], buf); + if (i < len - 1) { + buf.append(":"); + } + } + return buf.toString(); + } + + public static void dump(byte[] data, String margin) + { + int i, j; + byte b; + byte[] s = new byte[16]; + byte hex[] = {0}; + String shex; + String PrtString; + + if (data.length == 0) return; + + s[0] = 0; + j = 0; + for (i = 0; i < data.length; i++) { + if (j == 0) log.info(margin); + b = data[i]; + if ((b < 32) || (b > 127)) s[j] = '.'; else s[j] = b; + if (j < 15) + s[j+1] = 0; + hex[0] = b; shex = toHexString(hex); + log.info(shex + " "); + j++; + if (((j & 3) == 0) && (j < 15)) log.info("| "); + PrtString = new String(s); + if (j > 15) { + log.info("[" + PrtString + "]"); + j = 0; + s[0] = 0; + } + } + if (j != 0) { + while (j < 16) { + log.info(" "); + j++; + if (((j & 3) == 0) && (j < 15)) log.info("| "); + } + PrtString = new String(s); + log.info(" [" + PrtString + "]"); + } + } + + public static void check() + { + int status; + String infos; + int i; + int fsize; + byte c; + int input = 0; + InputStreamReader reader = new InputStreamReader(System.in); + BufferedReader in = new BufferedReader(reader); + + Hasp hasp = new Hasp(Hasp.HASP_DEFAULT_FID); + + log.info("\nThis is a simple demo program for the Sentinel LDK licensing functions\n"); + log.info("Copyright (C) THALES. All rights reserved.\n\n"); + + HaspApiVersion version = hasp.getVersion(vendorCode); + status = version.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + break; + case HaspStatus.HASP_NO_API_DYLIB: + log.info("Sentinel API dynamic library not found"); + return; + case HaspStatus.HASP_INV_API_DYLIB: + log.info("Sentinel API dynamic library is corrupt"); + return; + default: + log.info("unexpected error"); + } + + log.info("API Version: " + version.majorVersion() + "." + + version.minorVersion() + + "." + version.buildNumber() + "\n"); + + /********************************************************************** + * hasp_login + * establish a context for Sentinel services + */ + + log.info("login to default feature : "); + + /* login feature 0 */ + /* this default feature is available on any key */ + /* search for local and remote Sentinel key */ + hasp.login(vendorCode); + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("OK"); + break; + case HaspStatus.HASP_FEATURE_NOT_FOUND: + log.info("no Sentinel DEMOMA key found"); + break; + case HaspStatus.HASP_HASP_NOT_FOUND: + log.info("Sentinel key not found"); + break; + case HaspStatus.HASP_OLD_DRIVER: + log.info("outdated driver version or no driver installed"); + break; + case HaspStatus.HASP_NO_DRIVER: + log.info("Sentinel key not found"); + break; + case HaspStatus.HASP_INV_VCODE: + log.info("invalid vendor code"); + break; + default: + log.info("login to default feature failed"); + } + + /******************************************************************** + * hasp_get_sessioninfo + * retrieve Sentinel key attributes + */ + + log.info("\nget session info : "); + + infos = hasp.getSessionInfo(Hasp.HASP_KEYINFO); + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("OK, Sentinel key attributes retrieved\n\n" + + "Key info:\n===============\n" + infos + + "\n===============\n"); + break; + case HaspStatus.HASP_INV_HND: + log.info("handle not active"); + break; + case HaspStatus.HASP_INV_FORMAT: + log.info("unrecognized format"); + break; + case HaspStatus.HASP_HASP_NOT_FOUND: + log.info("Sentinel key not found"); + break; + default: + log.info("hasp_get_sessioninfo failed"); + } + + /*******************************************************************/ + + log.info("\npress ENTER to continue"); + try { + input = in.read(); + } catch (IOException e) { + log.error(e.getMessage(),e); + } + + /******************************************************************** + * hasp_get_size + * retrieve the memory size of the Sentinel key + */ + + log.info("\nretrieving the key's memory size : "); + + fsize = hasp.getSize(Hasp.HASP_FILEID_RW); + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("memory size is " + fsize + " bytes"); + break; + case HaspStatus.HASP_INV_HND: + log.info("handle not active"); + break; + case HaspStatus.HASP_INV_FILEID: + log.info("invalid file id"); + break; + case HaspStatus.HASP_HASP_NOT_FOUND: + log.info("Sentinel key not found"); + break; + default: + log.info("could not retrieve memory size"); + } + + if (fsize != 0) { /* skip memory access if no memory available */ + + /****************************************************************** + * hasp_read + * read from memory + */ + + /* limit memory size to be used in this demo program */ + + if (fsize > DEMO_MEMBUFFER_SIZE) fsize = DEMO_MEMBUFFER_SIZE; + + log.info("\nreading " + fsize + " bytes from memory : "); + + byte[] membuffer = new byte[DEMO_MEMBUFFER_SIZE]; + + hasp.read(Hasp.HASP_FILEID_RW, 0, membuffer); + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("OK"); + dump(membuffer, " "); + break; + case HaspStatus.HASP_INV_HND: + log.info("handle not active"); + break; + case HaspStatus.HASP_INV_FILEID: + log.info("invalid file id"); + break; + case HaspStatus.HASP_MEM_RANGE: + log.info("beyond memory range of attached Sentinel key"); + break; + case HaspStatus.HASP_HASP_NOT_FOUND: + log.info("hasp not found"); + break; + default: + log.info("read memory failed\n"); + } + + /****************************************************************** + * hasp_write + * write to memory + */ + + log.info("\nincrementing every byte in memory buffer"); + for (i = 0; i < fsize; i++) membuffer[i]++; + + log.info("\nwriting " + fsize + " bytes to memory : "); + + hasp.write(Hasp.HASP_FILEID_RW, 0, membuffer); + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("OK"); + break; + case HaspStatus.HASP_INV_HND: + log.info("handle not active"); + break; + case HaspStatus.HASP_INV_FILEID: + log.info("invalid file id"); + break; + case HaspStatus.HASP_MEM_RANGE: + log.info("beyond memory range of attached Sentinel key"); + break; + case HaspStatus.HASP_HASP_NOT_FOUND: + log.info("Sentinel key not found"); + break; + default: + log.info("write memory failed"); + } + + /****************************************************************** + * hasp_read + * read from memory + */ + + log.info("\nreading "+fsize+" bytes from memory : "); + + hasp.read(Hasp.HASP_FILEID_RW, 0, membuffer); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("OK"); + dump(membuffer, " "); + break; + case HaspStatus.HASP_INV_HND: + log.info("handle not active\n"); + break; + case HaspStatus.HASP_INV_FILEID: + log.info("invalid file id"); + break; + case HaspStatus.HASP_MEM_RANGE: + log.info("beyond memory range of attached Sentinel key"); + break; + case HaspStatus.HASP_HASP_NOT_FOUND: + log.info("Sentinel key not found"); + break; + default: + log.info("read memory failed"); + } + } /* end of memory demo */ + + /********************************************************************** + * hasp_encrypt + * encrypts a block of data using the Sentinel key + * (minimum buffer size is 16 bytes) + */ + + log.info("\nencrypting a data buffer:"); + dump(data, " "); + + hasp.encrypt(data); + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("encryption ok:"); + dump(data, " "); + break; + case HaspStatus.HASP_INV_HND: + log.info("handle not active"); + break; + case HaspStatus.HASP_TOO_SHORT: + log.info("data length too short"); + break; + case HaspStatus.HASP_ENC_NOT_SUPP: + log.info("attached key does not support AES encryption"); + break; + case HaspStatus.HASP_FEATURE_NOT_FOUND: + log.info("Sentinel key not found"); + break; + default: + log.info("encryption failed"); + } + + /********************************************************************** + * hasp_decrypt + * decrypts a block of data using the Sentinel key + * (minimum buffer size is 16 bytes) + */ + + hasp.decrypt(data); + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("decryption ok:"); + dump(data, " "); + break; + case HaspStatus.HASP_INV_HND: + log.info("handle not active"); + break; + case HaspStatus.HASP_TOO_SHORT: + log.info("data length too short"); + break; + case HaspStatus.HASP_ENC_NOT_SUPP: + log.info("attached key does not support AES encryption"); + break; + case HaspStatus.HASP_FEATURE_NOT_FOUND: + log.info("key not found"); + break; + default: + log.info("decryption failed"); + } + + /**********************************************************************/ + + log.info("login to feature 42 : "); + + Hasp hasp1 = new Hasp(42); + + /* search for local and remote HASP key */ + hasp1.login(vendorCode); + status = hasp1.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("OK"); + break; + case HaspStatus.HASP_FEATURE_NOT_FOUND: + log.info("no Sentinel DEMOMA key found with feature 42 enabled"); + break; + case HaspStatus.HASP_HASP_NOT_FOUND: + log.info("Sentinel key not found"); + break; + case HaspStatus.HASP_OLD_DRIVER: + log.info("outdated driver version or no driver installed"); + break; + case HaspStatus.HASP_NO_DRIVER: + log.info("Sentinel key not found"); + break; + case HaspStatus.HASP_INV_VCODE: + log.info("invalid vendor code"); + break; + default: + log.info("login feature 42 failed"); + } + + log.info("\nencrypt/decrypt again to see different encryption for different features:"); + + /********************************************************************** + * hasp_encrypt + * encrypts a block of data using the Sentinel key + * (minimum buffer size is 16 bytes) + */ + + log.info("\nencrypting a data buffer:"); + dump(data, " "); + + hasp1.encrypt(data); + status = hasp1.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("encryption ok:"); + dump(data, " "); + break; + case HaspStatus.HASP_INV_HND: + log.info("handle not active"); + break; + case HaspStatus.HASP_TOO_SHORT: + log.info("data length too short"); + break; + case HaspStatus.HASP_ENC_NOT_SUPP: + log.info("attached key does not support AES encryption"); + break; + case HaspStatus.HASP_FEATURE_NOT_FOUND: + log.info("key not found"); + break; + default: + log.info("encryption failed"); + } + + /********************************************************************** + * hasp_decrypt + * decrypts a block of data using the Sentinel key + * (minimum buffer size is 16 bytes) + */ + + hasp1.decrypt(data); + status = hasp1.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("decryption ok:"); + dump(data, " "); + break; + case HaspStatus.HASP_INV_HND: + log.info("handle not active"); + break; + case HaspStatus.HASP_TOO_SHORT: + log.info("data length too short"); + break; + case HaspStatus.HASP_ENC_NOT_SUPP: + log.info("attached key does not support AES encryption"); + break; + case HaspStatus.HASP_FEATURE_NOT_FOUND: + log.info("key not found"); + break; + default: + log.info("decryption failed"); + } + + hasp1.logout(); + status = hasp1.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("OK"); + break; + case HaspStatus.HASP_INV_HND: + log.info("failed: handle not active"); + break; + default: + log.info("failed with status " + status); + } + + /********************************************************************** + * hasp_get_rtc + * read current time from Sentinel Time key + */ + + log.info("\nreading current time and date : "); + + datetime = hasp.getRealTimeClock(); + + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("time: " + datetime.getHour() + ":" + + datetime.getMinute() + ":" + datetime.getSecond() +" H:M:S"); + log.info(" date: " + + datetime.getDay() + "/" + datetime.getMonth() + "/" + + datetime.getYear() + " D/M/Y"); + break; + case HaspStatus.HASP_INV_TIME: + log.info("time value outside supported range\n"); + break; + case HaspStatus.HASP_INV_HND: + log.info("handle not active"); + break; + case HaspStatus.HASP_NO_TIME: + log.info("no Sentinel Time connected"); + break; + default: + log.info("could not read time from Sentinel key"); + } + + /********************************************************************** + * hasp_logout + * closes established session and releases allocated memory + */ + + log.info("\nlogout from feature 1 : "); + + hasp.logout(); + + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("OK"); + break; + case HaspStatus.HASP_INV_HND: + log.info("failed: handle not active"); + break; + default: + log.info("failed"); + } + + /******************************************************************** + * hasp_login_scope + * establishes a context for Sentinel services + * allows specification of several restrictions + */ + + hasp = new Hasp(Hasp.HASP_DEFAULT_FID); + + log.info("restricting the license to be used to 'local':"); + + log.info(scope); + + log.info("hasp_login_scope : "); + + hasp.loginScope(scope,vendorCode); + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("OK"); + break; + case HaspStatus.HASP_FEATURE_NOT_FOUND: + log.info("login to default feature failed"); + break; + case HaspStatus.HASP_HASP_NOT_FOUND: + log.info("Sentinel key not found"); + break; + case HaspStatus.HASP_OLD_DRIVER: + log.info("outdated driver version or no driver installed"); + break; + case HaspStatus.HASP_NO_DRIVER: + log.info("Sentinel key not found"); + break; + case HaspStatus.HASP_INV_VCODE: + log.info("invalid vendor code"); + break; + case HaspStatus.HASP_INV_SCOPE: + log.info("invalid XML scope"); + break; + default: + log.info("login to default feature failed with status " + status); + } + + /*******************************************************************/ + + log.info("\ngetting information about connected keys and usage: "); + + infos = hasp.getInfo(scope1, view, vendorCode); + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + /* use the info you received ... */ + log.info("OK\n" + infos); + break; + case HaspStatus.HASP_INV_FORMAT: + log.info("invalid XML info format\n"); + break; + case HaspStatus.HASP_INV_SCOPE: + log.info("invalid XML scope\n"); + break; + default: + log.info("hasp_get_info failed with status " + status); + } + + /******************************************************************** + * hasp_logout + * closes established session and releases allocated memory + */ + + log.info("\nlogout : "); + hasp.logout(); + status = hasp.getLastError(); + + switch (status) { + case HaspStatus.HASP_STATUS_OK: + log.info("OK"); + break; + case HaspStatus.HASP_INV_HND: + log.info("failed: handle not active"); + break; + default: + log.info("failed\n"); + } + } +} \ No newline at end of file diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/ExcelUtils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/ExcelUtils.java new file mode 100644 index 0000000..74446b9 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/ExcelUtils.java @@ -0,0 +1,72 @@ +package com.jiluo.bolt.util; + +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.entity.po.Defect; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/08/16:18 + * @Description: + */ +public class ExcelUtils { + private static final String EXPORT_FILE_NAME = "转子视觉检测历史数据_%s.xlsx"; + public static String generateExcel(OutputStream outputStream, List defects) throws IOException { + + Workbook workbook = new XSSFWorkbook(); + Sheet sheet = workbook.createSheet("Sheet1"); + Row headerRow = sheet.createRow(0); + headerRow.createCell(0).setCellValue("创建时间"); + headerRow.createCell(1).setCellValue("修改时间"); + headerRow.createCell(2).setCellValue("缺陷编号"); + headerRow.createCell(3).setCellValue("电站"); + headerRow.createCell(4).setCellValue("机组"); + headerRow.createCell(5).setCellValue("检测点"); + headerRow.createCell(6).setCellValue("任务编号"); + headerRow.createCell(7).setCellValue("设备"); + headerRow.createCell(8).setCellValue("检测类型"); + headerRow.createCell(9).setCellValue("磁极编号"); + headerRow.createCell(10).setCellValue("螺栓编号"); + headerRow.createCell(11).setCellValue("检测数值"); + headerRow.createCell(12).setCellValue("图片"); + headerRow.createCell(13).setCellValue("是否告警:0-否,1-是"); + headerRow.createCell(14).setCellValue("状态:0-有效 1-无效,2-已处理"); + + int rowNum = 1; + for (Defect defect : defects) { + BigDecimal value = (defect.getType().equals(DefectType.bolt.name()) || defect.getType().equals(DefectType.temperature.name())) ? BigDecimal.valueOf(defect.getValue() / 100.0).setScale(2, RoundingMode.HALF_UP) : (defect.getType().equals(DefectType.line.name()) ? BigDecimal.valueOf(defect.getValue() / 1000.0).setScale(3, RoundingMode.HALF_UP) : BigDecimal.valueOf(defect.getValue())); + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(SystemDateUtils.formatDate3(defect.getGmtCreate())); + row.createCell(1).setCellValue(SystemDateUtils.formatDate3(defect.getGmtModify())); + row.createCell(2).setCellValue(defect.getBizId()); + row.createCell(3).setCellValue(defect.getPowerStation()); + row.createCell(4).setCellValue(defect.getMotorGroup()); + row.createCell(5).setCellValue(defect.getPoint()); + row.createCell(6).setCellValue(defect.getJob()); + row.createCell(7).setCellValue(defect.getDevice()); + row.createCell(8).setCellValue(defect.getType()); + row.createCell(9).setCellValue(defect.getZone()); + row.createCell(10).setCellValue(defect.getPosition()); + row.createCell(11).setCellValue(value.toString()); + row.createCell(12).setCellValue(defect.getData()); + row.createCell(13).setCellValue(defect.getAlarm()); + row.createCell(14).setCellValue(defect.getStatus()); + } + + workbook.write(outputStream); + workbook.close(); + return String.format(EXPORT_FILE_NAME,SystemDateUtils.findMinMaxDate(defects.stream().map(Defect::getGmtCreate).collect(Collectors.toList()))); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/FileUtils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/FileUtils.java new file mode 100644 index 0000000..ca3b23a --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/FileUtils.java @@ -0,0 +1,60 @@ +package com.jiluo.bolt.util; + +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/17/11:05 + * @Description: + */ +public class FileUtils { + + public static List convertFilesToList(String folderPath) throws IOException { + List fileList = new ArrayList<>(); + + File folder = new File(folderPath); + File[] files = folder.listFiles(); + if (files != null) { + for (File file : files) { + MultipartFile multipartFile = convertFileToMultipartFile(file); + if (multipartFile != null) { + fileList.add(multipartFile); + } + } + } + + return fileList; + } + + private static MultipartFile convertFileToMultipartFile(File file) throws IOException { + FileInputStream inputStream = new FileInputStream(file); + MultipartFile multipartFile = new MockMultipartFile( + file.getName(), // 文件名 + file.getName(), // 原始文件名 + getContentType(file.getName()), // 文件类型 + inputStream // 文件内容 + ); + inputStream.close(); + return multipartFile; + } + + private static String getContentType(String filename) { + // 根据文件名获取文件类型,可以根据需要进行扩展 + if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) { + return "image/jpeg"; + } else if (filename.endsWith(".png")) { + return "image/png"; + } + // 默认返回application/octet-stream + return "application/octet-stream"; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/ImgUtils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/ImgUtils.java new file mode 100644 index 0000000..39d48e6 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/ImgUtils.java @@ -0,0 +1,84 @@ +package com.jiluo.bolt.util; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.ResourceUtils; + +import java.io.File; +import java.io.FileInputStream; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/01/10:19 + * @Description:图片转换 + */ +@Component +@Slf4j +public class ImgUtils { + + public static String ToBase64(String defect_work_dir, String jobId, String img) throws Exception { + String path = defect_work_dir + jobId + "/detect/" + img; + File file = ResourceUtils.getFile(path); + FileInputStream fileInputStream = new FileInputStream(file); + byte[] bytes = IOUtils.toByteArray(fileInputStream); + String encodeBase64 = Base64.encodeBase64String(bytes); + fileInputStream.close(); + return encodeBase64; + } + + public static byte[] ToBase64(String path) throws Exception { + long startTime = System.currentTimeMillis(); + + File file = ResourceUtils.getFile(path); + + long getFileTime = System.currentTimeMillis(); + log.info("getFile耗时:" + (getFileTime - startTime) + "毫秒"); + + FileInputStream fileInputStream = new FileInputStream(file); + + long createStreamTime = System.currentTimeMillis(); + log.info("createStream耗时:" + (createStreamTime - getFileTime) + "毫秒"); + + byte[] bytes = IOUtils.toByteArray(fileInputStream); + + long readBytesTime = System.currentTimeMillis(); + log.info("readBytes耗时:" + (readBytesTime - createStreamTime) + "毫秒"); + + fileInputStream.close(); + + long closeStreamTime = System.currentTimeMillis(); + log.info("closeStream耗时:" + (closeStreamTime - readBytesTime) + "毫秒"); + + long totalTime = System.currentTimeMillis() - startTime; + log.info("总耗时:" + totalTime + "毫秒"); + + return bytes; + } + + public String ToBase64Logo(String path, String fileName) throws Exception { + if (StringUtils.isBlank(path) || StringUtils.isBlank(fileName)) { + return null; + } + File file = ResourceUtils.getFile(path + fileName); + FileInputStream fileInputStream = new FileInputStream(file); + byte[] bytes = IOUtils.toByteArray(fileInputStream); + String encodeBase64 = Base64.encodeBase64String(bytes); + fileInputStream.close(); + return encodeBase64; + } + + public byte[] ToBytes(String path) throws Exception { + String dir = path; + File file = ResourceUtils.getFile(dir); + FileInputStream fileInputStream = new FileInputStream(file); + byte[] bytes = IOUtils.toByteArray(fileInputStream); + fileInputStream.close(); + return bytes; + } +} + diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/JwtUtils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/JwtUtils.java new file mode 100644 index 0000000..c76155d --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/JwtUtils.java @@ -0,0 +1,61 @@ +package com.jiluo.bolt.util; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/24/16:27 + * @Description:生成token + */ +@Component +public class JwtUtils { + + @Value("${jwt.config.secretKey}") + private String secretKey; + /** + * 加密token. + */ + public String getToken(String uid,String sid, String clientVersion, String clientType) { + //这个是放到负载payLoad 里面,魔法值可以使用常量类进行封装. + String token = JWT + .create() + .withClaim("uid" ,uid) + .withClaim("sid",sid) + .withClaim("clientVersion", clientVersion) + .withClaim("clientType", clientType) + .withClaim("timeStamp", System.currentTimeMillis()) + .sign(Algorithm.HMAC256(secretKey)); + return token; + } + /** + * 解析token. + */ + public Map parseToken(String token) { + HashMap map = new HashMap(); + DecodedJWT decodedjwt = JWT.require(Algorithm.HMAC256(secretKey)) + .build().verify(token); + Claim uid = decodedjwt.getClaim("uid"); + Claim sid = decodedjwt.getClaim("sid"); + Claim clientVersion = decodedjwt.getClaim("clientVersion"); + Claim clientType = decodedjwt.getClaim("clientType"); + Claim timeStamp = decodedjwt.getClaim("timeStamp"); + + map.put("uid", uid.asString()); + map.put("sid", sid.asString()); + map.put("clientVersion", clientVersion.asString()); + map.put("clientType", clientType.asString()); + map.put("timeStamp", timeStamp.asLong().toString()); + return map; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/MD5Utils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/MD5Utils.java new file mode 100644 index 0000000..efa5b13 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/MD5Utils.java @@ -0,0 +1,39 @@ +package com.jiluo.bolt.util; + +import org.springframework.stereotype.Component; +import org.springframework.util.DigestUtils; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/19/10:09 + * @Description:MD5密码加密 + */ +@Component +public class MD5Utils { + public static String md5(String src){ + return DigestUtils.md5DigestAsHex(src.getBytes()); + } + private static final String salt = "1a2b3c4d"; + + //第一次加密 + public static String inputPassToFormPass(String inputPass){ + //md5加密密码前,先对密码进行处理,按以下salt的规则处理密码 + String str = "" + salt.charAt(0) + salt.charAt(2) + inputPass + salt.charAt(5) + salt.charAt(4); + return md5(str); + } + + //第二次加密 + public static String formPassToDBPass(String formPass, String salt){ + String str = "" + salt.charAt(0) + salt.charAt(2) + formPass + salt.charAt(5) + salt.charAt(4); + return md5(str); + } + + //实际调用的方法,将第一次加密和第二次加密合并,结果应该一致 + public static String inputPassToDBPass(String inputPass, String salt){ + String formPass = inputPassToFormPass(inputPass); + String dbPass = formPassToDBPass(formPass, salt); + return dbPass; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/MinioUtil.java b/bolt-core/src/main/java/com/jiluo/bolt/util/MinioUtil.java new file mode 100644 index 0000000..399f501 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/MinioUtil.java @@ -0,0 +1,462 @@ +package com.jiluo.bolt.util; + +import io.minio.MinioClient; +import io.minio.ObjectStat; +import io.minio.PutObjectOptions; +import io.minio.Result; +import io.minio.errors.*; +import io.minio.messages.Bucket; +import io.minio.messages.DeleteError; +import io.minio.messages.Item; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/13/14:37 + * @Description: + */ +@Component +public class MinioUtil { + + @Autowired + private MinioClient minioClient; + + private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600; + + /** + * 检查存储桶是否存在 + * + * @param bucketName 存储桶名称 + * @return + */ + @SneakyThrows + public boolean bucketExists(String bucketName) { + boolean flag = false; + flag = minioClient.bucketExists(bucketName); + if (flag) { + return true; + } + return false; + } + + /** + * 创建存储桶 + * + * @param bucketName 存储桶名称 + */ + @SneakyThrows + public boolean makeBucket(String bucketName) { + boolean flag = bucketExists(bucketName); + if (!flag) { + minioClient.makeBucket(bucketName); + return true; + } else { + return false; + } + } + + /** + * 列出所有存储桶名称 + * + * @return + */ + @SneakyThrows + public List listBucketNames() { + List bucketList = listBuckets(); + List bucketListName = new ArrayList<>(); + for (Bucket bucket : bucketList) { + bucketListName.add(bucket.name()); + } + return bucketListName; + } + + /** + * 列出所有存储桶 + * + * @return + */ + @SneakyThrows + public List listBuckets() { + return minioClient.listBuckets(); + } + + /** + * 删除存储桶 + * + * @param bucketName 存储桶名称 + * @return + */ + @SneakyThrows + public boolean removeBucket(String bucketName) { + boolean flag = bucketExists(bucketName); + if (flag) { + Iterable> myObjects = listObjects(bucketName); + for (Result result : myObjects) { + Item item = result.get(); + // 有对象文件,则删除失败 + if (item.size() > 0) { + return false; + } + } + // 删除存储桶,注意,只有存储桶为空时才能删除成功。 + minioClient.removeBucket(bucketName); + flag = bucketExists(bucketName); + if (!flag) { + return true; + } + + } + return false; + } + + /** + * 列出存储桶中的所有对象名称 + * + * @param bucketName 存储桶名称 + * @return + */ + @SneakyThrows + public List listObjectNames(String bucketName) { + List listObjectNames = new ArrayList<>(); + boolean flag = bucketExists(bucketName); + if (flag) { + Iterable> myObjects = listObjects(bucketName); + for (Result result : myObjects) { + Item item = result.get(); + listObjectNames.add(item.objectName()); + } + } + return listObjectNames; + } + + /** + * 列出存储桶中的所有对象 + * + * @param bucketName 存储桶名称 + * @return + */ + @SneakyThrows + public Iterable> listObjects(String bucketName) { + boolean flag = bucketExists(bucketName); + if (flag) { + return minioClient.listObjects(bucketName); + } + return null; + } + + /** + * 通过文件上传到对象 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @param fileName File name + * @return + */ + @SneakyThrows + public boolean putObject(String bucketName, String objectName, String fileName) { + boolean flag = bucketExists(bucketName); + if (flag) { + minioClient.putObject(bucketName, objectName, fileName, null); + ObjectStat statObject = statObject(bucketName, objectName); + if (statObject != null && statObject.length() > 0) { + return true; + } + } + return false; + + } + + /** + * 文件上传 + * + * @param bucketName + * @param multipartFile + */ + @SneakyThrows + public void putObject(String bucketName, MultipartFile multipartFile, String filename) { + PutObjectOptions putObjectOptions = new PutObjectOptions(multipartFile.getSize(), PutObjectOptions.MIN_MULTIPART_SIZE); + putObjectOptions.setContentType(multipartFile.getContentType()); + InputStream inputStream = multipartFile.getInputStream(); + minioClient.putObject(bucketName, filename, inputStream, putObjectOptions); + inputStream.close(); + + } + + + /** + * 通过InputStream上传对象 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @param stream 要上传的流 + * @param stream 要上传的文件类型 MimeTypeUtils.IMAGE_JPEG_VALUE + * @return + */ + @SneakyThrows + public boolean putObject(String bucketName, String objectName, InputStream stream, String contentType) { + boolean flag = bucketExists(bucketName); + if (flag) { + PutObjectOptions putObjectOptions = new PutObjectOptions(stream.available(), -1); + /** + * 开启公共类功能设置setContentType + */ + if (StringUtils.isNotBlank(contentType)) { + putObjectOptions.setContentType(contentType); + } + minioClient.putObject(bucketName, objectName, stream, putObjectOptions); + ObjectStat statObject = statObject(bucketName, objectName); + if (statObject != null && statObject.length() > 0) { + return true; + } + } + return false; + } + + /** + * 以流的形式获取一个文件对象 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @return + */ + @SneakyThrows + public InputStream getObject(String bucketName, String objectName) { + boolean flag = bucketExists(bucketName); + if (flag) { + ObjectStat statObject = statObject(bucketName, objectName); + if (statObject != null && statObject.length() > 0) { + InputStream stream = minioClient.getObject(bucketName, objectName); + return stream; + } + } + return null; + } + + /** + * 以流的形式获取一个文件对象(断点下载) + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @param offset 起始字节的位置 + * @param length 要读取的长度 (可选,如果无值则代表读到文件结尾) + * @return + */ + @SneakyThrows + public InputStream getObject(String bucketName, String objectName, long offset, Long length) { + boolean flag = bucketExists(bucketName); + if (flag) { + ObjectStat statObject = statObject(bucketName, objectName); + if (statObject != null && statObject.length() > 0) { + InputStream stream = minioClient.getObject(bucketName, objectName, offset, length); + return stream; + } + } + return null; + } + + /** + * 下载并将文件保存到本地 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @param fileName File name + * @return + */ + @SneakyThrows + public boolean getObject(String bucketName, String objectName, String fileName) { + boolean flag = bucketExists(bucketName); + if (flag) { + ObjectStat statObject = statObject(bucketName, objectName); + if (statObject != null && statObject.length() > 0) { + minioClient.getObject(bucketName, objectName, fileName); + return true; + } + } + return false; + } + + /** + * 删除一个对象 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + */ + @SneakyThrows + public boolean removeObject(String bucketName, String objectName) { + boolean flag = bucketExists(bucketName); + if (flag) { + minioClient.removeObject(bucketName, objectName); + return true; + } + return false; + } + + /** + * 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表 + * + * @param bucketName 存储桶名称 + * @param objectNames 含有要删除的多个object名称的迭代器对象 + * @return + */ + @SneakyThrows + public List removeObject(String bucketName, List objectNames) { + List deleteErrorNames = new ArrayList<>(); + boolean flag = bucketExists(bucketName); + if (flag) { + Iterable> results = minioClient.removeObjects(bucketName, objectNames); + for (Result result : results) { + DeleteError error = result.get(); + deleteErrorNames.add(error.objectName()); + } + } + return deleteErrorNames; + } + + /** + * 生成一个给HTTP GET请求用的presigned URL。 + * 浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @param expires 失效时间(以秒为单位),默认是7天,不得大于七天 + * @return + */ + @SneakyThrows + public String presignedGetObject(String bucketName, String objectName, Integer expires) { + boolean flag = bucketExists(bucketName); + String url = ""; + if (flag) { + if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) { + throw new InvalidExpiresRangeException(expires, + "expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME); + } + url = minioClient.presignedGetObject(bucketName, objectName, expires); + } + return url; + } + + /** + * 生成一个给HTTP PUT请求用的presigned URL。 + * 浏览器/移动端的客户端可以用这个URL进行上传,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @param expires 失效时间(以秒为单位),默认是7天,不得大于七天 + * @return + */ + @SneakyThrows + public String presignedPutObject(String bucketName, String objectName, Integer expires) { + boolean flag = bucketExists(bucketName); + String url = ""; + if (flag) { + if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) { + throw new InvalidExpiresRangeException(expires, + "expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME); + } + url = minioClient.presignedPutObject(bucketName, objectName, expires); + } + return url; + } + + /** + * 获取对象的元数据 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @return + */ + @SneakyThrows + public ObjectStat statObject(String bucketName, String objectName) { + boolean flag = bucketExists(bucketName); + if (flag) { + ObjectStat statObject = minioClient.statObject(bucketName, objectName); + return statObject; + } + return null; + } + + /** + * 文件访问路径 + * + * @param bucketName 存储桶名称 + * @param objectName 存储桶里的对象名称 + * @return + */ + @SneakyThrows + public String getObjectUrl(String bucketName, String objectName) { + boolean flag = bucketExists(bucketName); + String url = ""; + if (flag) { + url = minioClient.getObjectUrl(bucketName, objectName); + } + return url; + } + + + /** + * 设置存储桶策略 + * + * @param bucketName 存储桶名称 + * @param policy 存储桶里的对象名称 + */ + public void setBucketPolicy(String bucketName, String policy) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException { + + minioClient.setBucketPolicy(bucketName, policy); + } + + + /** + * 获取存储桶策略 + * + * @param bucketName 存储桶名称 + * @return + */ + public String getBucketPolicy(String bucketName) throws IOException, InvalidKeyException, InvalidResponseException, BucketPolicyTooLargeException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, InsufficientDataException, ErrorResponseException { + + return minioClient.getBucketPolicy(bucketName); + } + + + public void downloadFile(String bucketName, String fileName, String originalName, HttpServletResponse response) { + try { + + InputStream file = minioClient.getObject(bucketName, fileName); + String filename = new String(fileName.getBytes("ISO8859-1"), StandardCharsets.UTF_8); + if (StringUtils.isNotEmpty(originalName)) { + fileName = originalName; + } + response.setHeader("Content-Disposition", "attachment;filename=" + filename); + ServletOutputStream servletOutputStream = response.getOutputStream(); + int len; + byte[] buffer = new byte[1024]; + while ((len = file.read(buffer)) > 0) { + servletOutputStream.write(buffer, 0, len); + } + servletOutputStream.flush(); + file.close(); + servletOutputStream.close(); + } catch (ErrorResponseException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/MyFileUtils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/MyFileUtils.java new file mode 100644 index 0000000..75dd289 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/MyFileUtils.java @@ -0,0 +1,94 @@ +package com.jiluo.bolt.util; + +import java.io.File; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/09/10:03 + * @Description: + */ +public class MyFileUtils { + // 验证字符串是否为正确路径名的正则表达式 + private static String matches = "[A-Za-z]:\\\\[^:?\"><*]*"; + // 通过 sPath.matches(matches) 方法的返回值判断是否正确 + // sPath 为路径字符串 + + /** + * 根据路径删除指定的目录或文件,无论存在与否 + *@param sPath 要删除的目录或文件 + *@return 删除成功返回 true,否则返回 false。 + */ + public boolean DeleteFolder(String sPath) { + boolean flag = false; + File file = new File(sPath); + // 判断目录或文件是否存在 + if (!file.exists()) { // 不存在返回 false + return flag; + } else { + // 判断是否为文件 + if (file.isFile()) { // 为文件时调用删除文件方法 + return deleteFile(sPath); + } else { // 为目录时调用删除目录方法 + return deleteDirectory(sPath); + } + } + } + + /** + * 删除单个文件 + * @param sPath 被删除文件的文件名 + * @return 单个文件删除成功返回true,否则返回false + */ + public boolean deleteFile(String sPath) { + boolean flag = false; + File file = new File(sPath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) { + file.delete(); + flag = true; + } + return flag; + } + + /** + * 删除目录(文件夹)以及目录下的文件 + * @param sPath 被删除目录的文件路径 + * @return 目录删除成功返回true,否则返回false + */ + public boolean deleteDirectory(String sPath) { + //如果sPath不以文件分隔符结尾,自动添加文件分隔符 + if (!sPath.endsWith(File.separator)) { + sPath = sPath + File.separator; + } + File dirFile = new File(sPath); + //如果dir对应的文件不存在,或者不是一个目录,则退出 + if (!dirFile.exists() || !dirFile.isDirectory()) { + return false; + } + boolean flag = true; + //删除文件夹下的所有文件(包括子目录) + File[] files = dirFile.listFiles(); + for (int i = 0; i < files.length; i++) { + //删除子文件 + if (files[i].isFile()) { + flag = deleteFile(files[i].getAbsolutePath()); + if (!flag) break; + } //删除子目录 + else { + flag = deleteDirectory(files[i].getAbsolutePath()); + if (!flag) break; + } + } + if (!flag) return false; + //删除当前目录 + if (dirFile.delete()) { + return true; + } else { + return false; + } + } + + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/SessionHolder.java b/bolt-core/src/main/java/com/jiluo/bolt/util/SessionHolder.java new file mode 100644 index 0000000..0be9b56 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/SessionHolder.java @@ -0,0 +1,24 @@ +package com.jiluo.bolt.util; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/05/17:50 + * @Description: + */ +public class SessionHolder { + private static final ThreadLocal USER_SESSION = new ThreadLocal<>(); + + public static void setUserSession(String userSession) { + USER_SESSION.set(userSession); + } + + public static String getUserSession() { + return USER_SESSION.get(); + } + + public static void clearSession() { + USER_SESSION.remove(); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/ShellUtils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/ShellUtils.java new file mode 100644 index 0000000..8c427d0 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/ShellUtils.java @@ -0,0 +1,129 @@ +package com.jiluo.bolt.util; + +import com.jiluo.bolt.common.CameraDriverEnum; +import lombok.extern.slf4j.Slf4j; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.Socket; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/22/18:43 + * @Description: + */ +@Slf4j +public class ShellUtils { + + public static Map cameraPortMap = new ConcurrentHashMap<>(); + + private static Map processMap = new HashMap<>(); + + private static int startPort = 8090; + private static int endPort = 8099; + + public static void exec(String cmd) { + try { + Process ps = Runtime.getRuntime().exec(cmd); + if (!ps.waitFor(120, TimeUnit.SECONDS)) { + ps.destroy(); + log.info("[ShellUtils] exec linux shell timeout,120s"); + } + } catch (Exception e) { + log.error("[ShellUtils] exec: 执行脚本异常:" + e.getMessage(), e); + } + } + + public static void exec(String type, String cmd) { + killProcess(CameraDriverEnum.getDriverName(type)); + + while (startPort <= endPort) { + if (!isPortInUse(startPort)) { + cameraPortMap.put(type, startPort); + String newCmd = cmd + " " + startPort; + startPort++; + try { + processMap.put(type, Runtime.getRuntime().exec(newCmd)); + } catch (Exception e) { + log.error("[ShellUtils] exec: 执行脚本异常:" + e.getMessage(), e); + } + break; + } + if (startPort == 8099) { + log.info("[ShellUtils] exec: 端口全部被占用,执行脚本失败"); + } + } + } + + private static boolean isPortInUse(int port) { + try { + Socket socket = new Socket("localhost", port); + socket.close(); + return true; + } catch (Exception e) { + log.info("[ShellUtils] isPortInUse: " + port + "端口可用"); + return false; + } + } + +// private static String getProcessPID(String processName) { +// try { +// Process process = Runtime.getRuntime().exec("pgrep " + processName); +// BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); +// String pid = reader.readLine(); +// reader.close(); +// return pid; +// } catch (Exception e) { +// log.error("[ShellUtils] getProcessPID: " + processName + e.getMessage(), e); +// } +// return null; +// } + + private static List getProcessPID(String processName) { + List pids = new ArrayList<>(); + + try { + Process process = Runtime.getRuntime().exec("pgrep " + processName); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + pids.add(line.trim()); + } + reader.close(); + } catch (Exception e) { + log.error("[ShellUtils] getProcessPID: " + processName + e.getMessage(), e); + } + + return pids; + } + + private static boolean killProcess(String processName) { + List processes = getProcessPID(processName); + if (processes == null || processes.isEmpty()) { + log.info("未找到进程: " + processName); + } else { + try { + for (String pid:processes) { + Process process = Runtime.getRuntime().exec("kill -9 " + pid); + if (process.waitFor() == 0) { + log.info("已终止进程: " + processName + " (PID: " + pid + ")"); + } else { + log.info("无法终止进程: " + processName + " (PID: " + pid + ")"); + } + } + } catch (Exception e) { + log.error("[ShellUtils] killProcess: " + e.getMessage(), e); + } + } + + return false; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/SnowFlakeUtil.java b/bolt-core/src/main/java/com/jiluo/bolt/util/SnowFlakeUtil.java new file mode 100644 index 0000000..b4740c8 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/SnowFlakeUtil.java @@ -0,0 +1,75 @@ +package com.jiluo.bolt.util; +import cn.hutool.core.lang.Singleton; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/09/15:41 + * @Description:雪花算法工具类 + */ +public class SnowFlakeUtil { + private static final long START_STMP = 1420041600000L; + private static final long SEQUENCE_BIT = 9L; + private static final long MACHINE_BIT = 2L; + private static final long DATACENTER_BIT = 2L; + private static final long MAX_SEQUENCE = 511L; + private static final long MAX_MACHINE_NUM = 3L; + private static final long MAX_DATACENTER_NUM = 3L; + private static final long MACHINE_LEFT = 9L; + private static final long DATACENTER_LEFT = 11L; + private static final long TIMESTMP_LEFT = 13L; + private long datacenterId; + private long machineId; + private long sequence = 0L; + private long lastStmp = -1L; + + public SnowFlakeUtil(long datacenterId, long machineId) { + if (datacenterId <= 3L && datacenterId >= 0L) { + if (machineId <= 3L && machineId >= 0L) { + this.datacenterId = datacenterId; + this.machineId = machineId; + } else { + throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); + } + } else { + throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); + } + } + + public synchronized long nextId() { + long currStmp = this.getNewstmp(); + if (currStmp < this.lastStmp) { + throw new RuntimeException("Clock moved backwards. Refusing to generate id"); + } else { + if (currStmp == this.lastStmp) { + this.sequence = this.sequence + 1L & 511L; + if (this.sequence == 0L) { + currStmp = this.getNextMill(); + } + } else { + this.sequence = 0L; + } + + this.lastStmp = currStmp; + return currStmp - 1420041600000L << 13 | this.datacenterId << 11 | this.machineId << 9 | this.sequence; + } + } + + private long getNextMill() { + long mill; + for(mill = this.getNewstmp(); mill <= this.lastStmp; mill = this.getNewstmp()) { + } + + return mill; + } + + private long getNewstmp() { + return System.currentTimeMillis(); + } + + public static Long getDefaultSnowFlakeId() { + return ((SnowFlakeUtil)Singleton.get(SnowFlakeUtil.class, new Object[]{1L, 1L})).nextId(); + } + +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/SpringContextHolder.java b/bolt-core/src/main/java/com/jiluo/bolt/util/SpringContextHolder.java new file mode 100644 index 0000000..9d06f28 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/SpringContextHolder.java @@ -0,0 +1,27 @@ +package com.jiluo.bolt.util; + +import lombok.NonNull; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +@Service +@Lazy(false) +public class SpringContextHolder implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + public static T getBean(Class clazz) { + return applicationContext.getBean(clazz); + } + + public static T getBean(Class clazz, String name) { + return applicationContext.getBean(name, clazz); + } + + @Override + public void setApplicationContext(@NonNull ApplicationContext context) { + applicationContext = context; + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/SystemDateUtils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/SystemDateUtils.java new file mode 100644 index 0000000..c75d4e0 --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/SystemDateUtils.java @@ -0,0 +1,126 @@ +package com.jiluo.bolt.util; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/17/16:55 + * @Description: + */ + +public class SystemDateUtils { + //获取系统当前时间,字符串类型 + public static String getStrDate() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + //设置为东八区 + sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); + Date newDate = new Date(); + String dateStr = sdf.format(newDate); + return dateStr; + } + + //获取系统当前时间Date类型,需要将字符串类型转成时间 + public static Date getNewDate() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + //设置为东八区 + sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); + Date date = new Date(); + String dateStr = sdf.format(date); + + //将字符串转成时间 + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date newDate = null; + try { + newDate = df.parse(dateStr); + } catch (ParseException e) { + e.printStackTrace(); + } + return newDate; + } + + //获取系统当前时间,字符串类型 + public static String getStrYMD() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + //设置为东八区 + sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); + Date newDate = new Date(); + String dateStr = sdf.format(newDate); + return dateStr; + } + + //获取系统当前时间Date类型,需要将字符串类型转成时间 + public static Date getNewYMD() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + //设置为东八区 + sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); + Date date = new Date(); + String dateStr = sdf.format(date); + + //将字符串转成时间 + DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + Date newDate = null; + try { + newDate = df.parse(dateStr); + } catch (ParseException e) { + e.printStackTrace(); + } + return newDate; + } + + public static String formatDate(Date date) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + return dateFormat.format(date); + } + public static String formatDate2(Date date) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); + return dateFormat.format(date); + } + public static String formatDate3(Date date) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return dateFormat.format(date); + } + + public static String formatDate(Date date, String format) { + SimpleDateFormat dateFormat = new SimpleDateFormat(format); + return dateFormat.format(date); + } + + public static Date StringToDate(String dateString) throws ParseException { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + return dateFormat.parse(dateString); + } + + public static String findMinMaxDate(List dates) { + if (dates == null || dates.isEmpty()) { + return null; + } + Date minDate = dates.stream().min(Date::compareTo).get(); + Date maxDate = dates.stream().max(Date::compareTo).get(); + String minDateString = formatDate2(minDate); + String maxDateString = formatDate2(maxDate); + if (minDateString.equals(maxDateString)) { + return minDateString; + } else { + return minDateString + "_" + maxDateString; + } + } + + public static Date toDate(LocalDateTime temp){ + ZonedDateTime zdt = temp.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + public static Date toDate(LocalDate temp){ + LocalDateTime localDateTime = LocalDateTime.of(temp, LocalTime.of(0,0,0)); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } +} diff --git a/bolt-core/src/main/java/com/jiluo/bolt/util/ToStringUtils.java b/bolt-core/src/main/java/com/jiluo/bolt/util/ToStringUtils.java new file mode 100644 index 0000000..3fdb20c --- /dev/null +++ b/bolt-core/src/main/java/com/jiluo/bolt/util/ToStringUtils.java @@ -0,0 +1,199 @@ +package com.jiluo.bolt.util; + +import com.alibaba.fastjson.JSON; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Pattern; +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/09/16:09 + * @Description: + */ + + +public class ToStringUtils { + + /** + * toString格式反序列化 + */ + /** + * 数字类型匹配(包括整形和浮点型) & 日期类型匹配 & 对象类型匹配 & ... + */ + public static Pattern datePattern = Pattern.compile("^[a-zA-Z]{3} [a-zA-Z]{3} [0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} CST ((19|20)\\d{2})$"); + public static Pattern numPattern = Pattern.compile("^-?[0-9]+\\.?[0-9]*$"); + public static Pattern objectPattern = Pattern.compile("^[a-zA-Z0-9\\.]+\\(.+\\)$"); + public static Pattern listPattern = Pattern.compile("^\\[.*\\]$"); + public static Pattern mapPattern = Pattern.compile("^\\{.*\\}$"); + public static Pattern supperPattern = Pattern.compile("^super=[a-zA-Z0-9\\.]+\\(.+\\)$"); + public static final String NULL = "null"; + + /** + * toString -> json + */ + public static String toJSONString(String toString) throws ParseException { + return JSON.toJSONString(toMap(toString)); + } + + /** + * toString -> object + */ + public static T toObject(String toString, Class clazz) throws ParseException { + return JSON.parseObject(toJSONString(toString), clazz); + } + + /** + * toString -> map + */ + private static Map toMap(String toString) throws ParseException { + if (StringUtils.isEmpty(toString = StringUtils.trim(toString))) { + return toString == null ? null : new HashMap<>(); + } + + // 移除最外层"()" + toString = StringUtils.substringAfter(toString, "(").trim(); + toString = StringUtils.substringBeforeLast(toString, ")").trim(); + + String token; + Map map = new HashMap<>(); + while (StringUtils.isNotEmpty(toString) && StringUtils.isNotEmpty(token =splitToken(toString))) { + toString = StringUtils.removeStart(StringUtils.removeStart(toString, token).trim(), ",").trim(); + + // 如果带"super="(lombok的@ToString(callSuper=true)引入),按照当前层继续处理 + if (supperPattern.matcher(token).matches()) { + token = token.substring(token.indexOf("(") + 1, token.length() - 1); + toString = String.format("%s,%s", token, toString); + continue; + } + + Pair keyValue = parseToken(token); + map.put(keyValue.getKey(), buildTypeValue(keyValue.getKey(), keyValue.getValue())); + } + return map; + } + static Pair parseToken(String token) { + assert Objects.nonNull(token) && token.contains("="); + int pos = token.indexOf("="); + return Pair.of(token.substring(0, pos), token.substring(pos + 1)); + } + + /** + * 单个token解析 + * + * @param key 可根据key设置自定义序列化操作 + */ + private static Object buildTypeValue(String key, String value) throws ParseException { + if (StringUtils.isEmpty(value)) { + return null; + } else if (value.equals(NULL)) { + return null; + } + + // 日期类型 + if (datePattern.matcher(value).matches()) { + return new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", new Locale("us")).parse(value).getTime(); + } + // 数字类型 + if (numPattern.matcher(value).matches()) { + return value; + } + // 集合类型 + if (listPattern.matcher(value).matches()) { + return buildListValue(value); + } + // map类型 + if (mapPattern.matcher(value).matches()) { + return buildMapValue(value); + } + // 对象类型 + if (objectPattern.matcher(value).matches()) { + return toMap(value); + } + + // 其他都认为是string类型 + return value; + } + + /** + * 集合类型 + */ + private static Object buildListValue(String value) throws ParseException { + List result = new ArrayList<>(); + + value = value.substring(1, value.length() - 1).trim(); + if (StringUtils.isEmpty(value)) { + return result; + } + + String token = null; + while (StringUtils.isNotBlank(value) && StringUtils.isNotBlank(token = splitToken(value))) { + result.add(buildTypeValue(null, token)); + value = StringUtils.removeStart(StringUtils.removeStart(value, token).trim(), ",").trim(); + } + + return result; + } + + static String splitToken(String toString) { + if (StringUtils.isBlank(toString)) { + return toString; + } + + int bracketNum = 0; + Stack stack = new Stack<>(); + for (int i = 0; i < toString.length(); i++) { + Character c = toString.charAt(i); + if (tokenMap.containsValue(c)) { + stack.push(c); + } else if (tokenMap.containsKey(c) && Objects.equals(stack.peek(), tokenMap.get(c))) { + stack.pop(); + } else if ((c == ',') && stack.isEmpty()) { + return toString.substring(0, i); + } + } + if (stack.isEmpty()) { + return toString; + } + throw new RuntimeException("splitFirstToken error, bracketNum=" + bracketNum + ", toString=" + toString); + } + + /** + * 获取第一个token,注意: toString不再包括最外层的() + */ + private final static Map tokenMap = new HashMap<>(); + static { + tokenMap.put(')', '('); + tokenMap.put('}', '{'); + tokenMap.put(']', '['); + } + + + /** + * map类型 + */ + private static Map buildMapValue(String value) throws ParseException { + Map result = new HashMap<>(); + value = value.substring(1, value.length() - 1).trim(); + if (StringUtils.isEmpty(value)) { + return result; + } + + String token = null; + while (StringUtils.isNotEmpty(token =splitToken(value))) { + Pair keyValue =parseToken(token); + result.put(buildTypeValue(keyValue.getKey(), keyValue.getKey()), buildTypeValue(keyValue.getKey(), keyValue.getValue())); + + value = StringUtils.removeStart(StringUtils.removeStart(value, token).trim(), ",").trim(); + } + + return result; + } + + + +} \ No newline at end of file diff --git a/bolt-core/src/main/resources/mock.jpg b/bolt-core/src/main/resources/mock.jpg new file mode 100644 index 0000000..4210360 Binary files /dev/null and b/bolt-core/src/main/resources/mock.jpg differ diff --git a/bolt-core/src/main/resources/report.docx b/bolt-core/src/main/resources/report.docx new file mode 100644 index 0000000..906c3f0 Binary files /dev/null and b/bolt-core/src/main/resources/report.docx differ diff --git a/bolt-core/src/main/resources/report.xml b/bolt-core/src/main/resources/report.xml new file mode 100644 index 0000000..923206a --- /dev/null +++ b/bolt-core/src/main/resources/report.xml @@ -0,0 +1,6641 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 转子视觉检测报告 + + + + + + + + + + 电厂名称: + + + + + + + {{productLine}} + + + + + + + + + + + + + + + 机组编号: + + + + + + + {{groupName}} + + + + + + + + + + + + + + + 检测点编号: + + + + + + + {{pointName}} + + + + + + + + + + 报告时间: + + + + + + + {{reportTime}} + + + + + + + + + + + + + + + + + + + + + + + + + 1 诊断概述 + + + + + + + + + + + 通过对报告时间范围内视觉检测数据的分析诊断,得出如下结论: + + + + + + + + + + + + + + + + 磁极引出线螺栓松动情况 + + + + + + + + + + + + + + + {{?boltDetail== null or boltDetail.isEmpty()}} + + + + + + + + + + + + + + + + + 无异常。 + + + + + + + + + + + + + + + {{/}} + + + + + + + + + + + + + + + + {{?boltDetail}} + + + + + + + + + + + + + + + + + + {{_index+1}} + + + + + + + + + + + + + + + + {{zone}}# + + + + + + + + 磁极 + + + + + + + + {{position}} + + + + + + + + 号螺栓出现松动,松动角度为 + + + + + + + + {{value}} + + + + + + + + 度。 + + + + + + + + + + + + + {{/boltDetail}} + + + + + + + + + + + + + + + + 磁极引出线变形情况 + + + + + + + + + + + + + + + {{?lineDetail == null or lineDetail.isEmpty()}} + + + + + + + + + + + + + + + + + 无异常。 + + + + + + + + + + + + + + + {{/}} + + + + + + + + + + + + + + + + {{?lineDetail}} + + + + + + + + + + + + + + + + + + {{_index+1}} + + + + + + + + + + + + + + + + {{zone}}# + + + + + + + + 磁极 + + + + + + + + {{position}} + + + + + + + + 号引出线变形过大,变形量为 + + + + + + + + {{value}}mm + + + + + + + + + + + + + + + + + + + + + + + + {{/lineDetail}} + + + + + + + + + + + + + + + + 磁极开闸变化情况 + + + + + + + + + + + + + + + {{?poleDetail == null or poleDetail.isEmpty()}} + + + + + + + + + + + + + + + + + 无异常。 + + + + + + + + + + + + + + + {{/}} + + + + + + + + + + + + + + + + {{?poleDetail}} + + + + + + + + + + + + + + + + + + {{_index+1}} + + + + + + + + + + + + + + + + {{zone}}# + + + + + + + + 磁极开闸变化过大,变化量为 + + + + + + + + {{value}} + + + + + + + + + + + + + + + + + + + + + + + + {{/poleDetail}} + + + + + + + + + + + + + + + + 检测点温度变化情况 + + + + + + + + + + + + + + + {{?temperatureDetail == null or temperatureDetail.isEmpty()}} + + + + + + + + + + + + + + + + + 无异常。 + + + + + + + + + + + + + + + {{/}} + + + + + + + + + + + + + + + + {{?temperatureDetail}} + + + + + + + + + + + + + + + + + + {{_index+1}} + + + + + + + + + + + + + + + + 检测点温度变化过大,最大温度为 + + + + + + + + {{value}}℃ + + + + + + + + + + + + + + + + + + + + + + + + {{/temperatureDetail}} + + + + + + + + + + + + + + + + + + + + + + + + + 2 数据总览 + + + + + + + + + + + + + + + + + 2.1 + + + + + + + + 磁极引出线螺栓松动 + + + + + + + + + + + + + + + + + 磁极引出线螺栓松动角度(度)统计( + + + + + + + + {{reportTime}} + + + + + + + + + + + + + + + + + + + + + + + + + {{#boltTable}} + + + + + + + + + + + + + + + + {{?boltDetail}} + + + + + + + + + + + + + + + + + + {{_index+1}} + + + + + + + + + + + + + + + + {{zone}}# + + + + + + + + 磁极 + + + + + + + + {{position}} + + + + + + + + 号螺栓出现松动,松动角度为 + + + + + + + + {{value}} + + + + + + + + 度。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{@file}} + + + + + + + + + + + + + + + + {{/boltDetail}} + + + + + + + + + + + + + + + + 2.2 + + + + + + + + 磁极引出线变形 + + + + + + + + + + + + + + + + + 磁极引出线变形( + + + + + + + + mm + + + + + + + + )统计( + + + + + + + + {{reportTime}} + + + + + + + + + + + + + + + + + + + + + + + + + {{#lineTable}} + + + + + + + + + + + + + + + + {{?lineDetail}} + + + + + + + + + + + + + + + + + + {{_index+1}} + + + + + + + + + + + + + + + + {{zone}}# + + + + + + + + 磁极 + + + + + + + + {{position}} + + + + + + + + 号引出线变形过大,变形量为 + + + + + + + + {{value}}mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{@file}} + + + + + + + + + + + + + + + + {{/lineDetail}} + + + + + + + + + + + + + + + + 2.3 + + + + + + + + 磁极开匝变化 + + + + + + + + + + + + + + + + + 磁极开匝变化 + + + + + + + + 统计( + + + + + + + + {{reportTime}} + + + + + + + + + + + + + + + + + + + + + + + + + {{#poleTable}} + + + + + + + + + + + + + + + + {{?poleDetail}} + + + + + + + + + + + + + + + + + + {{_index+1}} + + + + + + + + + + + + + + + + {{zone}}# + + + + + + + + 磁极 + + + + + + + + 开匝变化过大,变化量为 + + + + + + + + {{value}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{@file}} + + + + + + + + + + + + + + + + {{/poleDetail}} + + + + + + + + + + + + + + + + 2.4 + + + + + + + + 检测点温度变化 + + + + + + + + + + + + + + + + + 检测点温度变化( + + + + + + + + + + + + + + + + )统计( + + + + + + + + {{reportTime}} + + + + + + + + + + + + + + + + + + + + + + + + + {{#temperatureTable}} + + + + + + + + + + + + + + + + {{?temperatureDetail}} + + + + + + + + + + + + + + + + + + {{_index+1}} + + + + + + + + + + + + + + + + 检测点温度变化过大,最大温度为 + + + + + + + + {{value}}℃ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{@file}} + + + + + + + + + + + + + + + + {{/temperatureDetail}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PAGE + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PAGE + + + + + + + + + + + + + + + UEsDBBQABgAIAAAAIQDdK4tYbwEAABAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAAC +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACs +VMtuwjAQvFfqP0S+Vomhh6qqCBz6OLZIpR9g4g2JcGzLu1D4+27MQy2iRAguWcXenRmvdzwYrRqT +LCFg7Wwu+llPJGALp2s7y8XX5C19FAmSsloZZyEXa0AxGt7eDCZrD5hwtcVcVET+SUosKmgUZs6D +5Z3ShUYR/4aZ9KqYqxnI+17vQRbOElhKqcUQw8ELlGphKHld8fJGSQCDInneJLZcuVDem7pQxErl +0uoDlnTLkHFlzMGq9njHMoQ8ytDu/E+wrfvg1oRaQzJWgd5VwzLkyshvF+ZT5+bZaZAjKl1Z1gVo +Vywa7kCGPoDSWAFQY7IYs0bVdqf7BH9MRhlD/8pC2vNF4A4dxPcNMn4vlxBhOgiR1gbwyqfdgHYx +VyqA/qTAzri6gN/YHTpITbkDMobLe/53/iLoKX6e23FwHtnBAc6/hZ1F2+rUMxAEqmFv0mPDvmdk +959PeOA2aN8XDfoIt4zv2fAHAAD//wMAUEsDBBQABgAIAAAAIQC1VTAj9QAAAEwCAAALAAgCX3Jl +bHMvLnJlbHMgogQCKKAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAjJLPTsMwDMbvSLxD5PvqbkgIoaW7TEi7IVQewCTuH7WNoyRA9/aEA4JK +Y9vR9ufPP1ve7uZpVB8cYi9Ow7ooQbEzYnvXanitn1YPoGIiZ2kUxxqOHGFX3d5sX3iklJti1/uo +souLGrqU/CNiNB1PFAvx7HKlkTBRymFo0ZMZqGXclOU9hr8eUC081cFqCAd7B6o++jz5src0TW94 +L+Z9YpdOjECeEzvLduVDZgupz9uomkLLSYMV85zTEcn7ImMDnibaXE/0/7Y4cSJLidBI4PM834pz +QOvrgS6faKn4vc484qeE4U1k+GHBxQ9UXwAAAP//AwBQSwMEFAAGAAgAAAAhAIE+lJf0AAAAugIA +ABoACAF4bC9fcmVscy93b3JrYm9vay54bWwucmVscyCiBAEooAABAAAAAAAAAAAAAAAAAAAAAAAA +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 +cHAueG1sUEsFBgAAAAAMAAwAEwMAAJMgAAAAAA== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sheet1!$B$1 + + + + 系列 1 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$B$2:$B$5 + + General + + + 4.3 + + + 2.5 + + + 3.5 + + + 4.5 + + + + + + + + + + + + Sheet1!$C$1 + + + + 系列 2 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$C$2:$C$5 + + General + + + 2.4 + + + 4.4000000000000004 + + + 1.8 + + + 2.8 + + + + + + + + + + + + Sheet1!$D$1 + + + + 系列 3 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$D$2:$D$5 + + General + + + 2 + + + 2 + + + 3 + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + iVBORw0KGgoAAAANSUhEUgAACj4AAAD5CAYAAACdg97MAAAACXBIWXMAAEzlAABM5QF1zvCVAAAK +TWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQ +WaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec +5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28A +AgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0 +ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaO +WJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHi +wmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryM +AgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0l +YqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHi +NLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYA +QH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6c +wR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBie +whi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1c +QPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqO +Y4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hM +WEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgoh +JZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSU +Eko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/p +dLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Y +b1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7O +UndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsb +di97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W +7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83 +MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxr +PGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW +2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1 +U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd +8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H0 +8PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+H +vqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsG +Lww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjg +R2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4 +qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWY +EpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1Ir +eZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/Pb +FWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYj +i1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVk +Ve9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0Ibw +Da0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vz +DoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+y +CW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawt +o22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtd +UV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3r +O9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0 +/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv95 +63Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+ +UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAA +ADqYAAAXb5JfxUYAAMxNSURBVHja7N13nCRF+cfxz0WOnHMqJCMgFhkRJIMkyQICAgIiIjmXiFAI +IkiQpCICgkiWDAKSlCglOYciZziOfInfH9X3Yzn3dmdmu3u6Z77v12teHLuz1T1PV/d0dT/91KAv +vvgCEREREREREREREREREREREREREZE6GKwQiIiIiIiIiIiIiIiIiIiIiIiIiEhdKPFRRERERERE +RERERERERERERERERGpDiY8iIiIiIiIiIiIiIiIiIiIiIiIiUhtKfBQRERERERERERERERERERER +ERGR2lDio4iIiIiIiIiIiIiIiIiIiIiIiIjUhhIfRURERERERERERERERERERERERKQ2lPgoIiIi +IiIiIiIiIiIiIiIiIiIiIrWhxEcRERERERERERERERERERERERERqQ0lPoqIiIiIiIiIiIiIiIiI +iIiIiIhIbSjxUURERERERERERERERERERERERERqQ4mPIiIiIiIiIiIiIiIiIiIiIiIiIlIbSnwU +ERERERERERERERERERERERERkdpQ4qOIiIiIiIiIiIiIiIiIiIiIiIiI1IYSH0VERERERERERERE +RERERERERESkNpT4KCIiIiIiIiIiIiIiIiIiIiIiIiK1ocRHEREREREREREREREREREREREREakN +JT6KiIiIiIiIiIiIiIiIiIiIiIiISG0o8VFEREREREREREREREREREREREREakOJjyIiIiIiIiIi +IiIiIiIiIiIiIiJSG0p8FBEREREREREREREREREREREREZHaUOKjiIiIiIiIiIiIiIiIiIiIiIiI +iNSGEh9FREREREREREREREREREREREREpDaU+CgiIiIiIiIiIiIiIiIiIiIiIiIitaHERxERERER +ERERERERERERERERERGpDSU+ioiIiIiIiIiIiIiIiIiIiIiIiEhtDFUIuoexbggwAzAFMAIYnr1G +ZK/BwGjgs+y/E//7/18x+DGKqIiIiIiIiIiIiIiIiIiIiIiIiJRt0BdffKEo1JixbhAwBzBPj9fc +wEykJMcJ/50BmJr8qnyOB8aQEiE/AkYC72WvkT1eH2Q/ext4FXglBv+2tpyIiIiIiIiIiIiIiIiI +iIiIiIi0QomPNWCsGwzMByzS4zU/KclxLmCymn2k0cDrwCvAa9nrVeBl4BngmRj8KG15ERERERER +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 +0ONU4FDv7CdVTv8Y4KNKb/DODgT+WfLUIsBxwOXAH5hbNnkWcFdb31dOYtItmLfM9Cnxe+c8vguU +llw+uK3v9M6OBw6N81XJ8cCrJX+vBZwJXEQowdyjZL1c28H+tXsrjzUqfG5hQvnylo/WAnWXqDCd +5Ura5W3mzRC6ALAP8LvS9wG3eWcfqWPf/yfwacnfGyQm3aPaD3tnbwL6lDy1HHAYIRB3gZI+eax3 +dox2cyIiIiIiIiIiIiIiIiIiIiIi0ky652AepgFvEbLxreGdfaXlG7yzbwHrAncQstiVmgEMBDb3 +zvardqIxOPAQ2ggO9M4eRyhF/WqZlycCDwIbeWf39s5+2YHlP7jF3/28s1+VPEYxb4nmjROTrl3F +8g0CrmzjPRMIAagXAaPLvGU0cDGwqnf2gmbo8N7ZqwhZQ18r8/JY4B9UEVzayXmYTAgwLXVGLPte +rd8CfwNGlHntXWBf7+z12sWJiIiIiIiIiIiIiIiIiIiIiEizaWSp63OBS8o8P9E7O7OtD3tnRwK/ +TUy6AJAAyxCCID/yzo6r8NEVgTkBZVNbfOegxKRLMDcAdHor074FuCUx6XeA78V2897Z0RWmexVw +U8nfk1p539GUZCH0zn5V5j17MjeTH4RS1ABPMTfrJIRAzFLHAKeW+Vzpsk0GTkhMejLwfUKGyQnA ++97ZSR1YzztW2a9Ks1jeBlQTtFq6fN8vWa+VTCqzzAOAAYlJlwOWJ2TyHAe8VUVfbNnmUyq8dwvm +Zs2c3eK1a4A7W/ncrcxborrceptJKJF9XmLSVQhZH7sDn3pn39euTURERERERERERERERERERERE +mlW32bNnqxVEREREREREREREREREREREREREpBC6qwlEREREREREREREREREREREREREpCgU+Cgi +IiIiIiIiIiIiIiIiIiIiIiIihaHARxEREREREREREREREREREREREREpDAU+ioiIiIiIiIiIiIiI +iIiIiIiIiEhhKPBRRERERERERERERERERERERERERApDgY8iIiIiIiIiIiIiIiIiIiIiIiIiUhgK +fBQRERERERERERERERERERERERGRwlDgo4iIiIiIiIiIiIiIiIiIiIiIiIgUhgIfRURERERERERE +RERERERERERERKQwFPgoIiIiIiIiIiIiIiIiIiIiIiIiIoWhwEcRERERERERERERERERERERERER +KQwFPoqIiIiIiIiIiIiIiIiIiIiIiIhIYSjwUUREREREREREREREREREREREREQKQ4GPIiIiIiIi +IiIiIiIiIiIiIiIiIlIYCnwUERERERERERERERERERERERERkcJQ4KOIiIiIiIiIiIiIiIiIiIiI +iIiIFIYCH0VERERERERERERERERERERERESkMBT4KCIiIiIiIiIiIiIiIiIiIiIiIiKFocBHERER +ERERERERERERERERERERESkMBT6KiIiIiIiIiIiIiIiIiIiIiIiISGEo8FFERERERERERERERERE +RERERERECkOBjyIiIiIiIiIiIiIiIiIiIiIiIiJSGAp8FBEREREREREREREREREREREREZHCUOCj +iIiIiIiIiIiIiIiIiIiIiIiIiBSGAh9FREREREREREREREREREREREREpDD+fwDvgih3sdYjsAAA +AABJRU5ErkJggg== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sheet1!$B$1 + + + + 系列 1 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$B$2:$B$5 + + General + + + 4.3 + + + 2.5 + + + 3.5 + + + 4.5 + + + + + + + + + + + + Sheet1!$C$1 + + + + 系列 2 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$C$2:$C$5 + + General + + + 2.4 + + + 4.4000000000000004 + + + 1.8 + + + 2.8 + + + + + + + + + + + + Sheet1!$D$1 + + + + 系列 3 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$D$2:$D$5 + + General + + + 2 + + + 2 + + + 3 + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UEsDBBQABgAIAAAAIQDdK4tYbwEAABAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAAC +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACs +VMtuwjAQvFfqP0S+Vomhh6qqCBz6OLZIpR9g4g2JcGzLu1D4+27MQy2iRAguWcXenRmvdzwYrRqT +LCFg7Wwu+llPJGALp2s7y8XX5C19FAmSsloZZyEXa0AxGt7eDCZrD5hwtcVcVET+SUosKmgUZs6D +5Z3ShUYR/4aZ9KqYqxnI+17vQRbOElhKqcUQw8ELlGphKHld8fJGSQCDInneJLZcuVDem7pQxErl +0uoDlnTLkHFlzMGq9njHMoQ8ytDu/E+wrfvg1oRaQzJWgd5VwzLkyshvF+ZT5+bZaZAjKl1Z1gVo +Vywa7kCGPoDSWAFQY7IYs0bVdqf7BH9MRhlD/8pC2vNF4A4dxPcNMn4vlxBhOgiR1gbwyqfdgHYx +VyqA/qTAzri6gN/YHTpITbkDMobLe/53/iLoKX6e23FwHtnBAc6/hZ1F2+rUMxAEqmFv0mPDvmdk +959PeOA2aN8XDfoIt4zv2fAHAAD//wMAUEsDBBQABgAIAAAAIQC1VTAj9QAAAEwCAAALAAgCX3Jl +bHMvLnJlbHMgogQCKKAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAjJLPTsMwDMbvSLxD5PvqbkgIoaW7TEi7IVQewCTuH7WNoyRA9/aEA4JK +Y9vR9ufPP1ve7uZpVB8cYi9Ow7ooQbEzYnvXanitn1YPoGIiZ2kUxxqOHGFX3d5sX3iklJti1/uo +souLGrqU/CNiNB1PFAvx7HKlkTBRymFo0ZMZqGXclOU9hr8eUC081cFqCAd7B6o++jz5src0TW94 +L+Z9YpdOjECeEzvLduVDZgupz9uomkLLSYMV85zTEcn7ImMDnibaXE/0/7Y4cSJLidBI4PM834pz +QOvrgS6faKn4vc484qeE4U1k+GHBxQ9UXwAAAP//AwBQSwMEFAAGAAgAAAAhAIE+lJf0AAAAugIA +ABoACAF4bC9fcmVscy93b3JrYm9vay54bWwucmVscyCiBAEooAABAAAAAAAAAAAAAAAAAAAAAAAA +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 +cHAueG1sUEsFBgAAAAAMAAwAEwMAAJMgAAAAAA== + + + + + + + + + + + + + + + + + + + + + + + + + + + + 图表标题 + + + + + + + + + + + + + + + + + + Sheet1!$B$1 + + + + 系列 1 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$B$2:$B$5 + + General + + + 4.3 + + + 2.5 + + + 3.5 + + + 4.5 + + + + + + + + + + + + Sheet1!$C$1 + + + + 系列 2 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$C$2:$C$5 + + General + + + 2.4 + + + 4.4000000000000004 + + + 1.8 + + + 2.8 + + + + + + + + + + + + Sheet1!$D$1 + + + + 系列 3 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$D$2:$D$5 + + General + + + 2 + + + 2 + + + 3 + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sheet1!$B$1 + + + + 系列 1 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$B$2:$B$5 + + General + + + 4.3 + + + 2.5 + + + 3.5 + + + 4.5 + + + + + + + + + + + + Sheet1!$C$1 + + + + 系列 2 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$C$2:$C$5 + + General + + + 2.4 + + + 4.4000000000000004 + + + 1.8 + + + 2.8 + + + + + + + + + + + + Sheet1!$D$1 + + + + 系列 3 + + + + + + + + + + Sheet1!$A$2:$A$5 + + + + 类别 1 + + + 类别 2 + + + 类别 3 + + + 类别 4 + + + + + + + Sheet1!$D$2:$D$5 + + General + + + 2 + + + 2 + + + 3 + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UEsDBBQABgAIAAAAIQDdK4tYbwEAABAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAAC +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACs +VMtuwjAQvFfqP0S+Vomhh6qqCBz6OLZIpR9g4g2JcGzLu1D4+27MQy2iRAguWcXenRmvdzwYrRqT +LCFg7Wwu+llPJGALp2s7y8XX5C19FAmSsloZZyEXa0AxGt7eDCZrD5hwtcVcVET+SUosKmgUZs6D +5Z3ShUYR/4aZ9KqYqxnI+17vQRbOElhKqcUQw8ELlGphKHld8fJGSQCDInneJLZcuVDem7pQxErl +0uoDlnTLkHFlzMGq9njHMoQ8ytDu/E+wrfvg1oRaQzJWgd5VwzLkyshvF+ZT5+bZaZAjKl1Z1gVo +Vywa7kCGPoDSWAFQY7IYs0bVdqf7BH9MRhlD/8pC2vNF4A4dxPcNMn4vlxBhOgiR1gbwyqfdgHYx +VyqA/qTAzri6gN/YHTpITbkDMobLe/53/iLoKX6e23FwHtnBAc6/hZ1F2+rUMxAEqmFv0mPDvmdk +959PeOA2aN8XDfoIt4zv2fAHAAD//wMAUEsDBBQABgAIAAAAIQC1VTAj9QAAAEwCAAALAAgCX3Jl +bHMvLnJlbHMgogQCKKAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAjJLPTsMwDMbvSLxD5PvqbkgIoaW7TEi7IVQewCTuH7WNoyRA9/aEA4JK +Y9vR9ufPP1ve7uZpVB8cYi9Ow7ooQbEzYnvXanitn1YPoGIiZ2kUxxqOHGFX3d5sX3iklJti1/uo +souLGrqU/CNiNB1PFAvx7HKlkTBRymFo0ZMZqGXclOU9hr8eUC081cFqCAd7B6o++jz5src0TW94 +L+Z9YpdOjECeEzvLduVDZgupz9uomkLLSYMV85zTEcn7ImMDnibaXE/0/7Y4cSJLidBI4PM834pz +QOvrgS6faKn4vc484qeE4U1k+GHBxQ9UXwAAAP//AwBQSwMEFAAGAAgAAAAhAIE+lJf0AAAAugIA +ABoACAF4bC9fcmVscy93b3JrYm9vay54bWwucmVscyCiBAEooAABAAAAAAAAAAAAAAAAAAAAAAAA +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 +cHAueG1sUEsFBgAAAAAMAAwAEwMAAJMgAAAAAA== + + + UEsDBBQABgAIAAAAIQDdK4tYbwEAABAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAAC +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACs +VMtuwjAQvFfqP0S+Vomhh6qqCBz6OLZIpR9g4g2JcGzLu1D4+27MQy2iRAguWcXenRmvdzwYrRqT +LCFg7Wwu+llPJGALp2s7y8XX5C19FAmSsloZZyEXa0AxGt7eDCZrD5hwtcVcVET+SUosKmgUZs6D +5Z3ShUYR/4aZ9KqYqxnI+17vQRbOElhKqcUQw8ELlGphKHld8fJGSQCDInneJLZcuVDem7pQxErl +0uoDlnTLkHFlzMGq9njHMoQ8ytDu/E+wrfvg1oRaQzJWgd5VwzLkyshvF+ZT5+bZaZAjKl1Z1gVo +Vywa7kCGPoDSWAFQY7IYs0bVdqf7BH9MRhlD/8pC2vNF4A4dxPcNMn4vlxBhOgiR1gbwyqfdgHYx +VyqA/qTAzri6gN/YHTpITbkDMobLe/53/iLoKX6e23FwHtnBAc6/hZ1F2+rUMxAEqmFv0mPDvmdk +959PeOA2aN8XDfoIt4zv2fAHAAD//wMAUEsDBBQABgAIAAAAIQC1VTAj9QAAAEwCAAALAAgCX3Jl +bHMvLnJlbHMgogQCKKAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAjJLPTsMwDMbvSLxD5PvqbkgIoaW7TEi7IVQewCTuH7WNoyRA9/aEA4JK +Y9vR9ufPP1ve7uZpVB8cYi9Ow7ooQbEzYnvXanitn1YPoGIiZ2kUxxqOHGFX3d5sX3iklJti1/uo +souLGrqU/CNiNB1PFAvx7HKlkTBRymFo0ZMZqGXclOU9hr8eUC081cFqCAd7B6o++jz5src0TW94 +L+Z9YpdOjECeEzvLduVDZgupz9uomkLLSYMV85zTEcn7ImMDnibaXE/0/7Y4cSJLidBI4PM834pz +QOvrgS6faKn4vc484qeE4U1k+GHBxQ9UXwAAAP//AwBQSwMEFAAGAAgAAAAhAIE+lJf0AAAAugIA +ABoACAF4bC9fcmVscy93b3JrYm9vay54bWwucmVscyCiBAEooAABAAAAAAAAAAAAAAAAAAAAAAAA +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 +cHAueG1sUEsFBgAAAAAMAAwAEwMAAJMgAAAAAA== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2052-11.1.0.10314 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hp + CCC + 42 + 2012-08-14T00:53:00Z + 2021-03-29T16:08:00Z + 2021-04-05T13:31:00Z + + + + + + + + 56 + 4 + 154 + 880 + Microsoft Office Word + 0 + 7 + 2 + false + Hewlett-Packard Company + false + 1032 + false + false + 14.0000 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bolt-dao/pom.xml b/bolt-dao/pom.xml new file mode 100644 index 0000000..e68d3ba --- /dev/null +++ b/bolt-dao/pom.xml @@ -0,0 +1,55 @@ + + + + bolt-server + com.jiluo.bolt + 0.0.1-SNAPSHOT + + 4.0.0 + + bolt-dao + + + + + com.baomidou + mybatis-plus-boot-starter + + + com.baomidou + mybatis-plus-generator + + + + com.github.yulichang + mybatis-plus-join + + + + + mysql + mysql-connector-java + + + org.springframework.boot + spring-boot-starter-jdbc + + + + org.springframework.boot + spring-boot-starter-validation + + + + javax.validation + validation-api + + + org.hibernate.validator + hibernate-validator + + + + \ No newline at end of file diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/config/DateConfig.java b/bolt-dao/src/main/java/com/jiluo/bolt/config/DateConfig.java new file mode 100644 index 0000000..c05e731 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/config/DateConfig.java @@ -0,0 +1,38 @@ +package com.jiluo.bolt.config; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/17/15:30 + * @Description:自动生成创建和修改日期 + */ +@Component +public class DateConfig implements MetaObjectHandler { + + /** + * 使用mp做添加操作时候,这个方法执行 + * @param metaObject + */ + @Override + public void insertFill(MetaObject metaObject) { + //设置属性值 + this.setFieldValByName("gmtCreate",new Date(),metaObject); + this.setFieldValByName("gmtModify",new Date(),metaObject); + } + + /** + * 使用mp做修改操作时候,这个方法执行 + * @param metaObject + */ + @Override + public void updateFill(MetaObject metaObject) { + this.setFieldValByName("gmtModify",new Date(),metaObject); + } +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/config/MPConfig.java b/bolt-dao/src/main/java/com/jiluo/bolt/config/MPConfig.java new file mode 100644 index 0000000..40a01f3 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/config/MPConfig.java @@ -0,0 +1,32 @@ +package com.jiluo.bolt.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/20/9:34 + * @Description: + */ +@Configuration +@MapperScan("com.jiluo.blot.mapper") +public class MPConfig { + /** + * 分页插件 + */ + // 最新版 + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2)); + return interceptor; + } + + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/BaseEntity.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/BaseEntity.java new file mode 100644 index 0000000..926ff1b --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/BaseEntity.java @@ -0,0 +1,38 @@ +package com.jiluo.bolt.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + + +import java.io.Serializable; +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/11/19:03 + * @Description: + */ +@Data +public class BaseEntity implements Serializable { + + private static final long serialVersionUID = -4631068394422882064L; + + @JsonIgnore + @TableId(value="id",type= IdType.AUTO) + private Integer id; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @TableField(fill = FieldFill.INSERT) + private Date gmtCreate; + + @JsonIgnore + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date gmtModify; +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Alarm.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Alarm.java new file mode 100644 index 0000000..0135ebd --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Alarm.java @@ -0,0 +1,31 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 告警信息表(Alarm)表实体类 + * + * @author Fangy + * @since 2023-05-04 17:15:44 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "alarm") + public class Alarm extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String alarmId; + + @TableField("type") + private Integer type; + + @TableField("content") + private String content; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Algorithm.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Algorithm.java new file mode 100644 index 0000000..b843121 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Algorithm.java @@ -0,0 +1,45 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 算法信息表(Algorithm)表实体类 + * + * @author Fangy + * @since 2023-05-05 10:15:05 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "algorithm") + public class Algorithm extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String bizId; + + @TableField("algorithm") + private String algorithm; + + @TableField("attribute") + private String attribute; + + @TableField("power_station") + private String powerStation; + + @TableField("motor_group") + private String motorGroup; + + @TableField("point") + private String point; + + @TableField("version") + private String version; + + @TableField("config") + private String config; + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/AlgorithmConfig.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/AlgorithmConfig.java new file mode 100644 index 0000000..0c5a4aa --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/AlgorithmConfig.java @@ -0,0 +1,46 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.util.List; + +/** + * (AlgorithmConfig)表实体类 + * + * @author Fangy + * @since 2023-05-08 17:46:48 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "algorithm_config") + public class AlgorithmConfig extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String bizId; + + @TableField("name") + private String name; + + @TableField("bolt_threshold") + private Double boltThreshold; + + @TableField("line_threshold") + private Double lineThreshold; + + @TableField("daily_auto_detection_time") + private String dailyAutoDetectionTime; + + @TableField("delay_detect") + private Integer delayDetect; + + @TableField(exist = false) + private List pointIdList; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/AlgorithmTemplete.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/AlgorithmTemplete.java new file mode 100644 index 0000000..6a5f8ad --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/AlgorithmTemplete.java @@ -0,0 +1,41 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * (AlgorithmTemplete)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:26 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "algorithm_templete") + public class AlgorithmTemplete extends BaseEntity { + private static final long serialVersionUID = 1L; + + + @TableField("biz_id") + private String bizId; + + @TableField("source") + private String source; + + @TableField("name") + private String name; + + @TableField("drive") + private String drive; + + @TableField("version") + private String version; + + @TableField("config") + private String config; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Config.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Config.java new file mode 100644 index 0000000..dca8d5b --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Config.java @@ -0,0 +1,37 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 系统配置表(Config)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "config") + public class Config extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String configId; + + @TableField("value") + private String value; + + @TableField("description") + private String description; + + @TableField("category") + private String category; + + @TableField("type") + private String type; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Defect.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Defect.java new file mode 100644 index 0000000..da2972b --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Defect.java @@ -0,0 +1,64 @@ +package com.jiluo.bolt.entity.po; + + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 检测结果表(Defect)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "defect") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public class Defect extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String bizId; + + @TableField("power_station") + private String powerStation; + + @TableField("motor_group") + private String motorGroup; + + @TableField("point") + private String point; + + @TableField("job") + private String job; + + @TableField("device") + private String device; + + @TableField("type") + private String type; + + @TableField("zone") + private Integer zone; + + @TableField("position") + private Integer position; + + @TableField("value") + private Integer value; + + @TableField("data") + private String data; + + @TableField("alarm") + private Integer alarm; + + @TableField("status") + private Integer status; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Detect.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Detect.java new file mode 100644 index 0000000..09b4cfe --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Detect.java @@ -0,0 +1,46 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 检测过程表(Detect)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "detect") + public class Detect extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String bizId; + + @TableField("power_station") + private String powerStation; + + @TableField("motor_group") + private String motorGroup; + + @TableField("point") + private String point; + + @TableField("job") + private String job; + + @TableField("zone") + private Integer zone; + + @TableField("data") + private String data; + + @TableField("device") + private String device; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Device.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Device.java new file mode 100644 index 0000000..d43143c --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Device.java @@ -0,0 +1,60 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.*; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 设备信息表(Device)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "device") + public class Device extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String deviceId; + + @TableField("type") + private String type; + + @TableField("name") + private String name; + + @TableField("power_station") + private String powerStationId; + + @TableField("product") + private String product; + + @TableField("motor_group") + private String motorGroupId; + + @TableField("point") + private String pointId; + + @TableField("status") + private Integer status; + + @JsonIgnore + @TableField("temp_threshold") + private String tempThreshold; + + @JsonIgnore + @TableField("config") + private String config; + + @TableField("vender") + private String vender; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/DeviceTemplete.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/DeviceTemplete.java new file mode 100644 index 0000000..5d74f34 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/DeviceTemplete.java @@ -0,0 +1,34 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * (DeviceTemplete)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "device_templete") + public class DeviceTemplete extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String bizId; + + @TableField("vender") + private String vender; + + @TableField("drive") + private String drive; + + @TableField("config") + private String config; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Job.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Job.java new file mode 100644 index 0000000..2f8fd8b --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Job.java @@ -0,0 +1,60 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 检测任务表(Job)表实体类 + * + * @author Fangy + * @since 2023-05-11 11:04:59 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +@TableName(value = "job") +public class Job extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String jobId; + + @TableField("power_station") + private String powerStation; + + @TableField("motor_group") + private String motorGroup; + + @TableField("point") + private String point; + + @TableField("product") + private String product; + + @TableField("type") + private String type; + + @TableField("name") + private String name; + + @TableField("description") + private String description; + + @TableField("attribute") + private String attribute; + + @JsonIgnore + @TableField("config") + private String config; + + @TableField("operator") + private String operator; + + @TableField("status") + private Integer status; + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/MotorGroup.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/MotorGroup.java new file mode 100644 index 0000000..95b0759 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/MotorGroup.java @@ -0,0 +1,41 @@ +package com.jiluo.bolt.entity.po; + +import java.util.List; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 机组信息表(MotorGroup)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "motor_group") + public class MotorGroup extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String motorGroupId; + + @TableField("name") + private String name; + + @TableField("contact") + private String contact; + + @TableField("phone") + private String phone; + + @TableField("power_station") + private String powerStation; + + @TableField(exist = false) + private List pointList; + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Point.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Point.java new file mode 100644 index 0000000..12c1118 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Point.java @@ -0,0 +1,73 @@ +package com.jiluo.bolt.entity.po; + +import java.util.Date; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 检测点表(Point)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "point") + public class Point extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String pointId; + + @TableField("name") + private String name; + + @TableField("pole_num") + private Integer poleNum; + + @TableField("manual_time") + private Integer manualTime; + + @TableField("automatic_time") + private Integer automaticTime; + + @TableField("enable_detect") + private Integer enableDetect; + + @TableField("power_station") + private String powerStation; + + @TableField("motor_group") + private String motorGroup; + + @TableField("bolt_detect") + private Integer boltDetect; + + @TableField("line_detect") + private Integer lineDetect; + + @TableField("pole_open_detect") + private Integer poleOpenDetect; + + @TableField("point_temp_detect") + private Integer pointTempDetect; + + @TableField("status") + private Integer status; + + + @TableField("gmt_reset") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8") + private Date gmtReset; + + @JsonIgnore + @TableField("config") + private String config; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/PowerStation.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/PowerStation.java new file mode 100644 index 0000000..5b70b19 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/PowerStation.java @@ -0,0 +1,45 @@ +package com.jiluo.bolt.entity.po; + +import java.util.List; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 电站表(PowerStation)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "power_station") + public class PowerStation extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String powerStationId; + + @TableField("name") + private String name; + + @TableField("address") + private String address; + + @TableField("contact") + private String contact; + + @TableField("phone") + private String phone; + + @TableField("introduction") + private String introduction; + + @TableField(exist = false) + private List groupList; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Product.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Product.java new file mode 100644 index 0000000..1f42fab --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Product.java @@ -0,0 +1,37 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 检测类型表(Product)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "product") + public class Product extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String bizId; + + @TableField("name") + private String name; + + @TableField("drive") + private String drive; + + @TableField("tags") + private String tags; + + @TableField("type") + private String type; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Role.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Role.java new file mode 100644 index 0000000..a1eef10 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Role.java @@ -0,0 +1,28 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 角色信息表(Role)表实体类 + * + * @author Fangy + * @since 2023-05-05 18:42:15 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "role") + public class Role extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("role_id") + private String roleId; + + @TableField("role_name") + private String roleName; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/RoleItem.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/RoleItem.java new file mode 100644 index 0000000..8796bb0 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/RoleItem.java @@ -0,0 +1,34 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 权限信息表(RoleItem)表实体类 + * + * @author Fangy + * @since 2023-05-05 18:42:15 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "role_item") + public class RoleItem extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String bizId; + + @TableField("name") + private String name; + + @TableField("scope") + private Integer scope; + + @TableField("type") + private Integer type; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/RoleValue.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/RoleValue.java new file mode 100644 index 0000000..0e1ce01 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/RoleValue.java @@ -0,0 +1,31 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 角色信息表(RoleValue)表实体类 + * + * @author Fangy + * @since 2023-05-05 18:42:15 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "role_value") + public class RoleValue extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("role_id") + private String roleId; + + @TableField("item_id") + private String itemId; + + @TableField("item_value") + private String itemValue; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/User.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/User.java new file mode 100644 index 0000000..b5a0eb9 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/User.java @@ -0,0 +1,37 @@ +package com.jiluo.bolt.entity.po; + + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 用户信息表(User)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "user") + public class User extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String bizId; + + @TableField("user_name") + private String userName; + + @JsonIgnore + @TableField("password") + private String password; + + @TableField("role") + private String role; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Version.java b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Version.java new file mode 100644 index 0000000..ca520a6 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/entity/po/Version.java @@ -0,0 +1,37 @@ +package com.jiluo.bolt.entity.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.jiluo.bolt.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 版本信息表(Version)表实体类 + * + * @author Fangy + * @since 2023-05-05 09:28:27 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @Accessors(chain = true) + @TableName(value = "version") + public class Version extends BaseEntity { + private static final long serialVersionUID = 1L; + + @TableField("biz_id") + private String bizId; + + @TableField("version") + private String version; + + @TableField("note") + private String note; + + @TableField("path") + private String path; + + @TableField("available") + private Integer available; + + } diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlarmMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlarmMapper.java new file mode 100644 index 0000000..0a673cd --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlarmMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.Alarm; + +/** + * 告警信息表(Alarm)表数据库访问层 + * @author Fangy + * @date 2023-05-04 17:15:41 + */ +@Repository +public interface AlarmMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlgorithmConfigMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlgorithmConfigMapper.java new file mode 100644 index 0000000..1b9e6d1 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlgorithmConfigMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.AlgorithmConfig; + +/** + * (AlgorithmConfig)表数据库访问层 + * @author Fangy + * @date 2023-05-08 17:48:41 + */ +@Repository +public interface AlgorithmConfigMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlgorithmMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlgorithmMapper.java new file mode 100644 index 0000000..ced0b2a --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlgorithmMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.Algorithm; + +/** + * 算法信息表(Algorithm)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:44 + */ +@Repository +public interface AlgorithmMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlgorithmTempleteMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlgorithmTempleteMapper.java new file mode 100644 index 0000000..0e78d8f --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/AlgorithmTempleteMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.AlgorithmTemplete; + +/** + * (AlgorithmTemplete)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:44 + */ +@Repository +public interface AlgorithmTempleteMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/ConfigMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/ConfigMapper.java new file mode 100644 index 0000000..0f773d2 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/ConfigMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.Config; + +/** + * 系统配置表(Config)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface ConfigMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DefectMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DefectMapper.java new file mode 100644 index 0000000..4881de6 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DefectMapper.java @@ -0,0 +1,27 @@ +package com.jiluo.bolt.mapper; +import org.apache.ibatis.annotations.Select; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.Defect; + +import java.util.List; + +/** + * 检测结果表(Defect)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface DefectMapper extends BaseMapper { + + @Select({"SELECT * " + + " FROM `defect` " + + " WHERE type = 'temperature' " + + " AND (point, gmt_create) IN ( " + + " SELECT point, MAX(gmt_create) " + + " FROM defect " + + " WHERE type = 'temperature' " + + " GROUP BY point ) ;"}) + public List selectTemperature(); + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DetectMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DetectMapper.java new file mode 100644 index 0000000..3a5166c --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DetectMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.Detect; + +/** + * 检测过程表(Detect)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface DetectMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DeviceMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DeviceMapper.java new file mode 100644 index 0000000..69f2478 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DeviceMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.Device; + +/** + * 设备信息表(Device)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface DeviceMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DeviceTempleteMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DeviceTempleteMapper.java new file mode 100644 index 0000000..387b583 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/DeviceTempleteMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.DeviceTemplete; + +/** + * (DeviceTemplete)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface DeviceTempleteMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/JobMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/JobMapper.java new file mode 100644 index 0000000..39e14ca --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/JobMapper.java @@ -0,0 +1,16 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.Job; + +import java.util.List; + +/** + * 检测任务表(Job)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface JobMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/MotorGroupMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/MotorGroupMapper.java new file mode 100644 index 0000000..4226df8 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/MotorGroupMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.MotorGroup; + +/** + * 机组信息表(MotorGroup)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface MotorGroupMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/PointMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/PointMapper.java new file mode 100644 index 0000000..e4e43f8 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/PointMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.Point; + +/** + * 检测点表(Point)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface PointMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/PowerStationMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/PowerStationMapper.java new file mode 100644 index 0000000..93987fd --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/PowerStationMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.PowerStation; + +/** + * 电站表(PowerStation)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface PowerStationMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/ProductMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/ProductMapper.java new file mode 100644 index 0000000..6e96c8f --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/ProductMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.Product; + +/** + * 检测类型表(Product)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface ProductMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/RoleItemMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/RoleItemMapper.java new file mode 100644 index 0000000..46fdd6a --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/RoleItemMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import com.jiluo.bolt.entity.po.RoleItem; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 权限信息表(RoleItem)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface RoleItemMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/RoleMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/RoleMapper.java new file mode 100644 index 0000000..310a0d9 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/RoleMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import com.jiluo.bolt.entity.po.Role; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 角色信息表(Role)表数据库访问层 + * @author Fangy + * @date 2023-05-05 18:30:46 + */ +@Repository +public interface RoleMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/RoleValueMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/RoleValueMapper.java new file mode 100644 index 0000000..2d2d082 --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/RoleValueMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import com.jiluo.bolt.entity.po.RoleValue; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 角色信息表(RoleValue)表数据库访问层 + * @author Fangy + * @date 2023-05-05 18:30:49 + */ +@Repository +public interface RoleValueMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/UserMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/UserMapper.java new file mode 100644 index 0000000..47d974e --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/UserMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.User; + +/** + * 用户信息表(User)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface UserMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/java/com/jiluo/bolt/mapper/VersionMapper.java b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/VersionMapper.java new file mode 100644 index 0000000..4f74e3f --- /dev/null +++ b/bolt-dao/src/main/java/com/jiluo/bolt/mapper/VersionMapper.java @@ -0,0 +1,14 @@ +package com.jiluo.bolt.mapper; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jiluo.bolt.entity.po.Version; + +/** + * 版本信息表(Version)表数据库访问层 + * @author Fangy + * @date 2023-05-05 09:37:46 + */ +@Repository +public interface VersionMapper extends BaseMapper { + +} diff --git a/bolt-dao/src/main/resources/mapper/AlarmMapper.xml b/bolt-dao/src/main/resources/mapper/AlarmMapper.xml new file mode 100644 index 0000000..5065ad1 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/AlarmMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, type, content + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/AlgorithmConfigMapper.xml b/bolt-dao/src/main/resources/mapper/AlgorithmConfigMapper.xml new file mode 100644 index 0000000..129d914 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/AlgorithmConfigMapper.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, name, bolt_threshold, line_threshold, daily_auto_detection_time, delay_detect + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/AlgorithmMapper.xml b/bolt-dao/src/main/resources/mapper/AlgorithmMapper.xml new file mode 100644 index 0000000..902bcc9 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/AlgorithmMapper.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, algorithm, attribute, power_station, motor_group, point, version, config + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/AlgorithmTempleteMapper.xml b/bolt-dao/src/main/resources/mapper/AlgorithmTempleteMapper.xml new file mode 100644 index 0000000..118cd7c --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/AlgorithmTempleteMapper.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, source, name, drive, version, config + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/ConfigMapper.xml b/bolt-dao/src/main/resources/mapper/ConfigMapper.xml new file mode 100644 index 0000000..9bfacc6 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/ConfigMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, value, description, category, type + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/DefectMapper.xml b/bolt-dao/src/main/resources/mapper/DefectMapper.xml new file mode 100644 index 0000000..8b53ec3 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/DefectMapper.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, power_station, motor_group, point, job, device, type, zone, position, value, data, alarm, status + + + + + diff --git a/bolt-dao/src/main/resources/mapper/DetectMapper.xml b/bolt-dao/src/main/resources/mapper/DetectMapper.xml new file mode 100644 index 0000000..03b9a9e --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/DetectMapper.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, power_station, motor_group, point, job, zone, data, device + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/DeviceMapper.xml b/bolt-dao/src/main/resources/mapper/DeviceMapper.xml new file mode 100644 index 0000000..9434a20 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/DeviceMapper.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, type, name, power_station, product, motor_group, point, status, temp_threshold, config, vender + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/DeviceTempleteMapper.xml b/bolt-dao/src/main/resources/mapper/DeviceTempleteMapper.xml new file mode 100644 index 0000000..e65d1a1 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/DeviceTempleteMapper.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, vender, drive, config + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/JobMapper.xml b/bolt-dao/src/main/resources/mapper/JobMapper.xml new file mode 100644 index 0000000..02f9614 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/JobMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, power_station, motor_group, point, product, type, name, description, attribute, config, operator, status + + + + diff --git a/bolt-dao/src/main/resources/mapper/MotorGroupMapper.xml b/bolt-dao/src/main/resources/mapper/MotorGroupMapper.xml new file mode 100644 index 0000000..e2a6897 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/MotorGroupMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, name, contact, phone, power_station + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/PointMapper.xml b/bolt-dao/src/main/resources/mapper/PointMapper.xml new file mode 100644 index 0000000..25657c8 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/PointMapper.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, name, pole_num, manual_time, automatic_time, enable_detect, power_station, motor_group, bolt_detect, line_detect, pole_open_detect, point_temp_detect, status, gmt_reset + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/PowerStationMapper.xml b/bolt-dao/src/main/resources/mapper/PowerStationMapper.xml new file mode 100644 index 0000000..60e7d7a --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/PowerStationMapper.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, name, address, contact, phone, introduction + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/ProductMapper.xml b/bolt-dao/src/main/resources/mapper/ProductMapper.xml new file mode 100644 index 0000000..af4c3b5 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/ProductMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, name, drive, tags, type + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/RoleItemMapper.xml b/bolt-dao/src/main/resources/mapper/RoleItemMapper.xml new file mode 100644 index 0000000..040e2cb --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/RoleItemMapper.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, name, scope, type + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/RoleMapper.xml b/bolt-dao/src/main/resources/mapper/RoleMapper.xml new file mode 100644 index 0000000..d7db2da --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/RoleMapper.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, role_id, role_name + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/RoleValueMapper.xml b/bolt-dao/src/main/resources/mapper/RoleValueMapper.xml new file mode 100644 index 0000000..2b6ca7b --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/RoleValueMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, role_id, item_id, item_value + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/UserMapper.xml b/bolt-dao/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..d729a9b --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, user_name, password, role + + + + + + diff --git a/bolt-dao/src/main/resources/mapper/VersionMapper.xml b/bolt-dao/src/main/resources/mapper/VersionMapper.xml new file mode 100644 index 0000000..02a2b82 --- /dev/null +++ b/bolt-dao/src/main/resources/mapper/VersionMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + gmt_create, gmt_modify, id, biz_id, version, note, path, available + + + + + + diff --git a/bolt-kernel/pom.xml b/bolt-kernel/pom.xml new file mode 100644 index 0000000..f55672e --- /dev/null +++ b/bolt-kernel/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + + bolt-server + com.jiluo.bolt + 0.0.1-SNAPSHOT + + + com.jiluo.bolt + bolt-kernel + 0.0.1-SNAPSHOT + + + + com.jiluo.bolt + bolt-core + 0.0.1-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-amqp + + + + net.java.dev.jna + jna + 5.12.1 + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + com.infiniteautomation + modbus4j + + + + + io.netty + netty-all + + + commons-io + commons-io + + + + org.apache.httpcomponents + httpclient + + + + com.squareup.okhttp3 + okhttp + + + + io.minio + minio + + + + + \ No newline at end of file diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/OKHttp/OkHttpUtil.java b/bolt-kernel/src/main/java/com/jiluo/bolt/OKHttp/OkHttpUtil.java new file mode 100644 index 0000000..32bd995 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/OKHttp/OkHttpUtil.java @@ -0,0 +1,148 @@ +package com.jiluo.bolt.OKHttp; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import com.jiluo.bolt.util.ShellUtils; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.stereotype.Component; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/13/10:55 + * @Description: + */ +@Component +@Slf4j +public class OkHttpUtil { + + private OkHttpUtil() { + } + + /** + * 发送get请求 + * + * @param url 地址 + * @param params 参数 + * @return 请求结果 + */ + public static String get(String url, Map params) { + return request("get", url, params); + } + + /** + * 发送post请求 + * + * @param url 地址 + * @param params 参数 + * @return 请求结果 + */ + public static String post(String url, Map params) { + return request("post", url, params); + } + + /** + * 发送http请求 + * + * @param method 请求方法 + * @param url 地址 + * @param params 参数 + * @return 请求结果 + */ + public static String request(String method, String url, Map params) { + + if (method == null) { + throw new RuntimeException("请求方法不能为空"); + } + + if (url == null) { + throw new RuntimeException("url不能为空"); + } + + HttpUrl.Builder httpBuilder = HttpUrl.parse(url).newBuilder(); + + if (params != null) { + for (Map.Entry param : params.entrySet()) { + httpBuilder.addQueryParameter(param.getKey(), param.getValue()); + } + } + + Request request = new Request.Builder() + .url(httpBuilder.build()) + .method(method, new FormBody.Builder().build()) + .build(); + + try { + OkHttpClient client = new OkHttpClient.Builder() + .readTimeout(20, TimeUnit.SECONDS) + .build(); + Response response = client.newCall(request).execute(); + return response.body().string(); + } catch (IOException e) { + return null; + } + } + + /** + * 发送post请求(json格式) + * + * @param url url + * @param json json字符串 + * @return 请求结果 + */ + public static String postJson(String url, String json) { + Request request = new Request.Builder() + .url(url) + .post(RequestBody.create(json.getBytes(), MediaType.parse("application/json"))) + .build(); + try { + OkHttpClient client = new OkHttpClient(); + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + return response.body().string(); + } else { + log.info("请求失败,错误码:" + response.code()); + return null; + } + } catch (IOException e) { + log.error("[OkHttpUtil] postJson:", e.getMessage(), e); + return null; + } + } + + public static String postJson(String type, String vendor, String func, String json) { + if (!ShellUtils.cameraPortMap.containsKey(vendor)){ + log.error("[OkHttpUtil] postJson:"+vendor+"相机驱动端口获取失败"); + return "false"; + } + String url = "http://localhost:" + ShellUtils.cameraPortMap.get(vendor) + "/api/" + type + "/" + func; + Request request = new Request.Builder() + .url(url) + .post(RequestBody.create(json.getBytes(), MediaType.parse("application/json"))) + .build(); + try { + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(3, TimeUnit.MINUTES) // 设置超时时间为3分钟 + .readTimeout(3, TimeUnit.MINUTES) + .writeTimeout(3, TimeUnit.MINUTES) + .build(); + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + String responseBody = response.body().string(); + response.close(); + return responseBody; + } else { + log.info("请求失败,错误码:" + response.code()); + return "false"; + } + } catch (IOException e) { + log.error("[OkHttpUtil] postJson:", e.getMessage(), e); + return "false"; + } + } + +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/AbstractAlgorithm.java b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/AbstractAlgorithm.java new file mode 100644 index 0000000..509f82c --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/AbstractAlgorithm.java @@ -0,0 +1,34 @@ +package com.jiluo.bolt.algorithm; + + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/10/15:37 + * @Description: + */ +public abstract class AbstractAlgorithm implements IAlgorithm { + private static ConcurrentHashMap ALGORITHMS = new ConcurrentHashMap<>(); + + protected String identity; + protected AlgorithmType algorithmType; + + public AbstractAlgorithm(AlgorithmType algorithmType, String identity){ + this.algorithmType = algorithmType; + this.identity = identity; + ALGORITHMS.putIfAbsent(identity,this); + } + + public String identity() { + return identity; + } + + public AlgorithmType type() { + return this.algorithmType; + } + + public IAlgorithm getByIdentity(String identity){ return ALGORITHMS.get(identity); } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/AlgorithmName.java b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/AlgorithmName.java new file mode 100644 index 0000000..b8da2e4 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/AlgorithmName.java @@ -0,0 +1,45 @@ +package com.jiluo.bolt.algorithm; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/10/15:54 + * @Description: + */ +@Getter +public enum AlgorithmName { + MockAlgorithm(AlgorithmType.mock_algorithm, "mock_algorithm", com.jiluo.bolt.algorithm.algorithm.MockAlgorithm.class), + SelfAlgorithm(AlgorithmType.self_algorithm, "自建算法1", com.jiluo.bolt.algorithm.algorithm.SelfAlgorithm.class); + + + String name; + AlgorithmType algorithmType; + Class clazz; + + AlgorithmName(AlgorithmType algorithmType, String name, Class clazz){ + this.algorithmType = algorithmType; + this.name = name; + this.clazz = clazz; + } + + public static AlgorithmName getByName(String name){ + for (AlgorithmName algorithmName:AlgorithmName.values()){ + if (algorithmName.getName().equals(name)){ + return algorithmName; + } + } + return null; + } + + public static List getName(AlgorithmType algorithmType){ + return Arrays.stream(AlgorithmName.values()).filter(algorithmName -> algorithmName.getAlgorithmType().equals(algorithmType)).map(AlgorithmName::getName).collect(Collectors.toList()); + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/AlgorithmType.java b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/AlgorithmType.java new file mode 100644 index 0000000..9eba606 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/AlgorithmType.java @@ -0,0 +1,12 @@ +package com.jiluo.bolt.algorithm; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/10/15:32 + * @Description: + */ +public enum AlgorithmType { + mock_algorithm, self_algorithm +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/IAlgorithm.java b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/IAlgorithm.java new file mode 100644 index 0000000..31c60ba --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/IAlgorithm.java @@ -0,0 +1,29 @@ +package com.jiluo.bolt.algorithm; + +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/10/15:29 + * @Description: + */ +public interface IAlgorithm { + + /** + * 下发算法参数 + */ + boolean config(Map input); + + /** + * 查询类型 + */ + AlgorithmType type(); + + /** + * 算法标识 + */ + String identity(); + +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/algorithm/MockAlgorithm.java b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/algorithm/MockAlgorithm.java new file mode 100644 index 0000000..fad70d3 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/algorithm/MockAlgorithm.java @@ -0,0 +1,25 @@ +package com.jiluo.bolt.algorithm.algorithm; + +import com.jiluo.bolt.algorithm.AbstractAlgorithm; +import com.jiluo.bolt.algorithm.AlgorithmType; + +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/10/15:57 + * @Description: + */ +public class MockAlgorithm extends AbstractAlgorithm { + + public MockAlgorithm(String identity){ + super(AlgorithmType.self_algorithm,identity); + } + + @Override + public boolean config(Map input) { + return true; + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/algorithm/SelfAlgorithm.java b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/algorithm/SelfAlgorithm.java new file mode 100644 index 0000000..7a0344d --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/algorithm/algorithm/SelfAlgorithm.java @@ -0,0 +1,25 @@ +package com.jiluo.bolt.algorithm.algorithm; + +import com.jiluo.bolt.algorithm.AbstractAlgorithm; +import com.jiluo.bolt.algorithm.AlgorithmType; + +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/10/15:57 + * @Description: + */ +public class SelfAlgorithm extends AbstractAlgorithm { + + public SelfAlgorithm(String identity){ + super(AlgorithmType.self_algorithm,identity); + } + + @Override + public boolean config(Map input) { + return true; + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/AbstractDevice.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/AbstractDevice.java new file mode 100644 index 0000000..062ef27 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/AbstractDevice.java @@ -0,0 +1,40 @@ +package com.jiluo.bolt.device; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:15 + * @Description: + */ +public abstract class AbstractDevice implements IDevice{ + private static ConcurrentHashMap DEVICES = new ConcurrentHashMap<>(); + + protected String identity; + protected DeviceType deviceType; + protected DeviceStatus deviceStatus; + + public AbstractDevice(DeviceType deviceType, String identity) { + this.deviceType = deviceType; + this.identity = identity; + DEVICES.putIfAbsent(identity, this); + } + + public String identity() { + return identity; + } + + public DeviceType type() { + return this.deviceType; + } + + public DeviceStatus status() { + return deviceStatus; + } + + public IDevice getByIdentity(String identity) { + return DEVICES.get(identity); + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/DeviceStatus.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/DeviceStatus.java new file mode 100644 index 0000000..280a6a0 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/DeviceStatus.java @@ -0,0 +1,12 @@ +package com.jiluo.bolt.device; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:09 + * @Description: + */ +public enum DeviceStatus { + ready, online, open, close, offline, working +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/DeviceType.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/DeviceType.java new file mode 100644 index 0000000..5b8325c --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/DeviceType.java @@ -0,0 +1,12 @@ +package com.jiluo.bolt.device; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:12 + * @Description: + */ +public enum DeviceType { + plc, camera, temperature_sensor +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/DeviceVender.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/DeviceVender.java new file mode 100644 index 0000000..a593dae --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/DeviceVender.java @@ -0,0 +1,52 @@ +package com.jiluo.bolt.device; + +import com.jiluo.bolt.device.camera.BaslerCamera; +import com.jiluo.bolt.device.camera.BaumerCamera; +import com.jiluo.bolt.device.camera.LucidCamera; +import com.jiluo.bolt.device.camera.MockCamera; +import com.jiluo.bolt.device.plc.MathvisionPlc; +import com.jiluo.bolt.device.plc.MockPlc; +import com.jiluo.bolt.device.sensor.CameraTemperatureSensor; +import com.jiluo.bolt.device.sensor.MockTemperatureSensor; +import com.jiluo.bolt.device.sensor.RFIDTemperatureSensor; +import lombok.Getter; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:46 + * @Description: + */ +@Getter +public enum DeviceVender { + MockCamera(DeviceType.camera, "mock_camera", MockCamera.class), + MockTemperatureSensor(DeviceType.temperature_sensor, "mock_temperature_sensor", MockTemperatureSensor.class), + MockPlc(DeviceType.plc, "mock_plc", MockPlc.class), + + LucidCamera(DeviceType.camera, "lucid_camera", LucidCamera.class), + BaslerCamera(DeviceType.camera, "basler_camera", BaslerCamera.class), + BaumerCamera(DeviceType.camera, "baumer_camera", BaumerCamera.class), + CameraTemperatureSensor(DeviceType.temperature_sensor, "camera_temperature_sensor", CameraTemperatureSensor.class), + RFIDTemperatureSensor(DeviceType.temperature_sensor, "rfid_temperature_sensor", RFIDTemperatureSensor.class), + MathvisionPlc(DeviceType.plc, "mathvision_plc", MathvisionPlc.class); + + String vender; + DeviceType deviceType; + Class clazz; + + DeviceVender(DeviceType deviceType, String vender, Class clazz) { + this.deviceType = deviceType; + this.vender = vender; + this.clazz = clazz; + } + + public static DeviceVender getByVender(String vender){ + for (DeviceVender deviceVender: DeviceVender.values()) { + if (deviceVender.getVender().equals(vender)){ + return deviceVender; + } + } + return null; + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/IDevice.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/IDevice.java new file mode 100644 index 0000000..c02e7a6 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/IDevice.java @@ -0,0 +1,43 @@ +package com.jiluo.bolt.device; + +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:07 + * @Description: + */ +public interface IDevice { + + /** + * 打开设备 + */ + boolean config(Map input); + + /** + * 打开设备 + */ + boolean open(Map input); + + /** + * 关闭设备 + */ + boolean close(Map input); + + /** + * 查询状态 + */ + DeviceStatus status(); + + /** + * 查询类型 + */ + DeviceType type(); + + /** + * 设备标识 + */ + String identity(); +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/BaslerCamera.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/BaslerCamera.java new file mode 100644 index 0000000..c4a7cb9 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/BaslerCamera.java @@ -0,0 +1,53 @@ +package com.jiluo.bolt.device.camera; + +import com.alibaba.fastjson.JSON; +import com.jiluo.bolt.OKHttp.OkHttpUtil; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.device.AbstractDevice; +import com.jiluo.bolt.device.DeviceVender; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:23 + * @Description: + */ +@Slf4j +public class BaslerCamera extends AbstractDevice { + private static final Logger logger = LoggerFactory.getLogger(BaslerCamera.class); + + public BaslerCamera(String identity) { + super(DeviceType.camera, identity); + deviceStatus = DeviceStatus.online; + } + + @Override + public boolean config(Map input) { + deviceStatus = DeviceStatus.ready; + return Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.BaslerCamera.getVender(),"config", JSON.toJSONString(input))); + } + + @Override + public boolean open(Map input) { + deviceStatus = DeviceStatus.open; + return Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.BaslerCamera.getVender(),"start",JSON.toJSONString(input))); + } + @Override + public boolean close(Map input) { + deviceStatus = DeviceStatus.close; + return Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.BaslerCamera.getVender(),"stop",JSON.toJSONString(input))); + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/BaumerCamera.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/BaumerCamera.java new file mode 100644 index 0000000..faa7f2f --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/BaumerCamera.java @@ -0,0 +1,46 @@ +package com.jiluo.bolt.device.camera; + +import com.alibaba.fastjson.JSON; +import com.jiluo.bolt.OKHttp.OkHttpUtil; +import com.jiluo.bolt.device.AbstractDevice; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.device.DeviceVender; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/09/18/12:02 + * @Description: + */ +public class BaumerCamera extends AbstractDevice { + + private static final Logger logger = LoggerFactory.getLogger(BaumerCamera.class); + + public BaumerCamera(String identity) { + super(DeviceType.camera, identity); + deviceStatus = DeviceStatus.online; + } + + @Override + public boolean config(Map input) { + deviceStatus = DeviceStatus.ready; + return Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.BaumerCamera.getVender(),"config", JSON.toJSONString(input))); + } + + @Override + public boolean open(Map input) { + deviceStatus = DeviceStatus.open; + return Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.BaumerCamera.getVender(),"start",JSON.toJSONString(input))); + } + @Override + public boolean close(Map input) { + deviceStatus = DeviceStatus.close; + return Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.BaumerCamera.getVender(),"stop",JSON.toJSONString(input))); + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/LucidCamera.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/LucidCamera.java new file mode 100644 index 0000000..6f12db5 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/LucidCamera.java @@ -0,0 +1,48 @@ +package com.jiluo.bolt.device.camera; + +import com.alibaba.fastjson.JSON; +import com.jiluo.bolt.OKHttp.OkHttpUtil; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.device.AbstractDevice; +import com.jiluo.bolt.device.DeviceVender; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:23 + * @Description: + */ +@Slf4j +public class LucidCamera extends AbstractDevice { + + private static final Logger logger = LoggerFactory.getLogger(LucidCamera.class); + + public LucidCamera(String identity) { + super(DeviceType.camera, identity); + deviceStatus = DeviceStatus.online; + } + + @Override + public boolean config(Map input) { + deviceStatus = DeviceStatus.ready; + return Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.LucidCamera.getVender(), "config", JSON.toJSONString(input))); + } + + @Override + public boolean open(Map input) { + deviceStatus = DeviceStatus.open; + return Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.LucidCamera.getVender(), "start", JSON.toJSONString(input))); + } + + @Override + public boolean close(Map input) { + deviceStatus = DeviceStatus.close; + return Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.LucidCamera.getVender(), "stop", JSON.toJSONString(input))); + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/MockCamera.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/MockCamera.java new file mode 100644 index 0000000..97a8034 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/camera/MockCamera.java @@ -0,0 +1,160 @@ +package com.jiluo.bolt.device.camera; + +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.common.DetectResult; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.device.AbstractDevice; +import com.jiluo.bolt.engine.Engine; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:23 + * @Description: + */ +@Slf4j +public class MockCamera extends AbstractDevice { + + private static final Logger logger = LoggerFactory.getLogger(MockCamera.class); + + public MockCamera(String identity) { + super(DeviceType.camera, identity); + deviceStatus = DeviceStatus.online; + } + + @Override + public boolean config(Map input) { + deviceStatus = DeviceStatus.ready; + return true; + } + + @Override + public boolean open(Map input) { + deviceStatus = DeviceStatus.open; + String destinationFolderPath = input.get("defect_work_dir") + "/detect"; + String sourceFolderPath = "/data/record/job_mock/detect"; + File folder = new File(sourceFolderPath); + File[] files = folder.listFiles(); // 获取文件夹下的所有文件 + List fileNames = new ArrayList<>(); + if (files != null) { + for (File file : files) { + if (file.isFile()) { + fileNames.add(file.getName()); // 将文件名添加到列表 + } + } + } + File destinationFolder = new File(destinationFolderPath); + if (!destinationFolder.exists()) { + destinationFolder.mkdirs(); // 创建目标文件夹(包括父文件夹) + } + try { + Path sourcePath = new File(sourceFolderPath).toPath(); + Path destinationPath = new File(destinationFolderPath).toPath(); + Files.walk(sourcePath) + .forEach(source -> { + try { + Path destination = destinationPath.resolve(sourcePath.relativize(source)); + Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + logger.error("无法复制文件:" + e.getMessage()); + } + }); + logger.info("文件夹复制成功!"); + } catch (IOException e) { + logger.error("无法复制文件夹:" + e.getMessage()); + } + + float minValue = 0.0f; + float maxValue = 1.0f; + Random random = new Random(); + float detect_threshold_bolt = Float.parseFloat(input.get("detect_threshold_bolt").toString()); + float detect_threshold_line = Float.parseFloat(input.get("detect_threshold_line").toString()); + int detect_work_zone = Integer.parseInt(input.get("detect_work_zone").toString()); + + for (int index = 1; index <= fileNames.size(); index++) { + String fileName = fileNames.get(index % fileNames.size()); + + int zone; + float[] value = new float[8]; + int firstUnderscoreIndex = fileName.indexOf("_"); + int secondUnderscoreIndex = fileName.indexOf("_", firstUnderscoreIndex + 1); + + + if (firstUnderscoreIndex != -1) { + String zonePart = fileName.substring(0, firstUnderscoreIndex); + zone = Integer.parseInt(zonePart); + } else { + zone = (index % detect_work_zone) + 1; + } + if (secondUnderscoreIndex != -1) { + String valuePart = fileName.substring(firstUnderscoreIndex + 1, secondUnderscoreIndex); + regex(valuePart,value); + } else { + for (int i = 0; i < value.length; i++) { + value[i] = minValue + random.nextFloat() * (maxValue - minValue); + } + } + + + List detectList = new ArrayList<>(); + for (int i = 1; i <= 8; i++) { + detectList.add(DetectResult.builder().zone(zone) + .position(i) + .type(DefectType.bolt.name()) + .value(value[i - 1]) + .img(fileName) + .alarm(value[i - 1] > detect_threshold_bolt ? 1 : 0).build()); + } + for (int i = 1; i <= 2; i++) { + float value1 = minValue + random.nextFloat() * (maxValue - minValue); + detectList.add(DetectResult.builder().zone(zone) + .position(i) + .type(DefectType.line.name()) + .value(value1) + .img(fileName) + .alarm(value1 > detect_threshold_line ? 1 : 0).build()); + } + Engine.callback(input.get("jobId").toString(), detectList); + } + return true; + } + + @Override + public boolean close(Map input) { + deviceStatus = DeviceStatus.close; + return true; + } + + private static void regex(String input, float[] value) { + // 定义正则表达式 + String regex = "(\\d+)\\((\\d+)\\)"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(input); + + // 查找匹配项 + while (matcher.find()) { + // 提取匹配的值 + String positionStr = matcher.group(1); + String valueStr = matcher.group(2); + value[Integer.parseInt(positionStr)] = Float.parseFloat(valueStr) / 100; + } + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/plc/MathvisionPlc.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/plc/MathvisionPlc.java new file mode 100644 index 0000000..1a500dd --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/plc/MathvisionPlc.java @@ -0,0 +1,122 @@ +package com.jiluo.bolt.device.plc; + +import com.alibaba.fastjson.JSON; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.device.AbstractDevice; +import com.jiluo.bolt.modbusTcp.utils.Modbus4jReadUtils; +import com.jiluo.bolt.modbusTcp.utils.Modbus4jWriteUtils; +import com.serotonin.modbus4j.ModbusFactory; +import com.serotonin.modbus4j.ModbusMaster; +import com.serotonin.modbus4j.ip.IpParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:24 + * @Description: + */ + +public class MathvisionPlc extends AbstractDevice { + + private static final Logger logger = LoggerFactory.getLogger(MathvisionPlc.class); + private static final ModbusFactory modbusFactory = new ModbusFactory(); + private final ModbusMaster modbusMaster; + private final Modbus4jWriteUtils modbus4jWriteUtils; + private final Modbus4jReadUtils modbus4jReadUtils; + private static Set POINTS = new HashSet<>(); + public MathvisionPlc(String identity) throws Exception { + super(DeviceType.plc, identity); + modbusMaster = getMaster(identity, 502); + modbus4jWriteUtils = new Modbus4jWriteUtils(); + modbus4jReadUtils = new Modbus4jReadUtils(); + logger.info("[MathvisionPlc] init; plc值:"+ JSON.toJSONString(modbus4jReadUtils.readHoldingRegister(modbusMaster,1,000,1))); + deviceStatus = DeviceStatus.online; + } + + @Override + public boolean config(Map input) { + if(!modbusMaster.isConnected()){ + deviceStatus = DeviceStatus.offline; + return false; + } + deviceStatus = DeviceStatus.ready; + return true; + } + + @Override + public boolean open(Map input) { + boolean flag = true; + try { + if (input.get("serial_number") == null || input.get("plc_delay")==null)return false; + POINTS.add(input.get("serial_number").toString()); + + if (deviceStatus != DeviceStatus.open){ + flag = modbus4jWriteUtils.writeRegister(modbusMaster,1,000,(short) 1); + logger.info("[MathvisionPlc] open; plc值:"+ JSON.toJSONString(modbus4jReadUtils.readHoldingRegister(modbusMaster,1,000,1))); + deviceStatus = DeviceStatus.open; + } + + Long _plc_delay = Long.parseLong(input.get("plc_delay").toString()); + logger.info("相机延时开启时间:"+_plc_delay+"s"); + Thread.sleep(_plc_delay * 1000); + } catch (Exception e) { + flag = false; + deviceStatus = DeviceStatus.offline; + logger.error("[MathvisionPlc] open exception:" + e.getMessage(), e); + } + return flag; + } + + @Override + public boolean close(Map input) { + boolean flag = true; + try { + POINTS.remove(input.get("serial_number")); + if (POINTS.isEmpty()){ + deviceStatus = DeviceStatus.close; + flag = modbus4jWriteUtils.writeRegister(modbusMaster,1,000,(short) 0); + logger.info("[MathvisionPlc] PLC已关闭! IP:" + identity); + logger.info("[MathvisionPlc] close; plc值:"+ JSON.toJSONString(modbus4jReadUtils.readHoldingRegister(modbusMaster,1,000,1))); + }else { + logger.info("[MathvisionPlc] 存在其他PLC控制设备还在开启中! 已完成的设备序列号为:" + input.get("serial_number")); + } + } catch (Exception e) { + flag = false; + deviceStatus = DeviceStatus.offline; + logger.error("[MathvisionPlc] close exception:" + e.getMessage(), e); + } + return flag; + } + + @Override + public DeviceStatus status() { + return deviceStatus; + } + + public static ModbusMaster getMaster(String ip, int port) throws Exception { + IpParameters params = new IpParameters(); + params.setHost(ip); + params.setPort(port); + //这个属性确定了协议帧是否是通过tcp封装的RTU结构,采用modbus tcp/ip时,要设为false, 采用modbus rtu over tcp/ip时,要设为true + params.setEncapsulated(false); + // 参数1:IP和端口信息 参数2:保持连接激活 + ModbusMaster master = modbusFactory.createTcpMaster(params, true); + //设置超时时间 + master.setTimeout(500); + //设置重连次数 + master.setRetries(2); + master.setConnected(true); + //初始化 + master.init(); + return master; + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/plc/MockPlc.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/plc/MockPlc.java new file mode 100644 index 0000000..41adf6d --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/plc/MockPlc.java @@ -0,0 +1,73 @@ +package com.jiluo.bolt.device.plc; + +import com.alibaba.fastjson.JSON; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.device.AbstractDevice; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:24 + * @Description: + */ +public class MockPlc extends AbstractDevice { + private static final Logger logger = LoggerFactory.getLogger(MockPlc.class); + private static Set POINTS = new HashSet<>(); + + public MockPlc(String identity) { + super(DeviceType.plc, identity); + deviceStatus = DeviceStatus.online; + } + + @Override + public boolean config(Map input) { + deviceStatus = DeviceStatus.ready; + return true; + } + + @Override + public boolean open(Map input) { + try { + if (input.get("plc_delay")==null) return false; + POINTS.add(input.get("serial_number").toString()); + if (deviceStatus != DeviceStatus.open){ + logger.info("plc开启!"); + deviceStatus = DeviceStatus.open; + } + Long _plc_delay = Long.parseLong(input.get("plc_delay").toString()); + logger.info("相机延时开启时间:"+_plc_delay+"s"); + Thread.sleep(_plc_delay * 1000); + + } catch (Exception e) { + logger.error("[MockPlc] open:",e.getMessage(),e); + } + return true; + } + + @Override + public boolean close(Map input) { + POINTS.remove(input.get("serial_number")); + if (POINTS.isEmpty()){ + deviceStatus = DeviceStatus.close; + logger.info("[MockPlc] PLC已关闭! IP:" + identity); + }else { + logger.info("[MockPlc] 存在其他PLC控制设备还在开启中! 已完成的设备序列号为:" + input.get("serial_number")); + } + + deviceStatus = DeviceStatus.close; + return true; + } + + @Override + public DeviceStatus status() { + return deviceStatus; + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/sensor/CameraTemperatureSensor.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/sensor/CameraTemperatureSensor.java new file mode 100644 index 0000000..9594a49 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/sensor/CameraTemperatureSensor.java @@ -0,0 +1,47 @@ +package com.jiluo.bolt.device.sensor; + +import com.alibaba.fastjson.JSON; +import com.jiluo.bolt.OKHttp.OkHttpUtil; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.device.AbstractDevice; +import com.jiluo.bolt.device.DeviceVender; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/07/13:56 + * @Description: + */ +public class CameraTemperatureSensor extends AbstractDevice { + + private static final Logger logger = LoggerFactory.getLogger(CameraTemperatureSensor.class); + public CameraTemperatureSensor(String identity) { + super(DeviceType.temperature_sensor, identity); + deviceStatus = DeviceStatus.online; + } + + @Override + public boolean config(Map input) { + deviceStatus = DeviceStatus.ready; + if (! input.containsKey("relatedDeviceVendor")) return false; + return Boolean.parseBoolean(OkHttpUtil.postJson("temperature", input.get("relatedDeviceVendor").toString(),"config", JSON.toJSONString(input))); + } + + @Override + public boolean open(Map input) { + deviceStatus = DeviceStatus.open; + return true; + } + + @Override + public boolean close(Map input) { + deviceStatus = DeviceStatus.close; + return true; + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/sensor/MockTemperatureSensor.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/sensor/MockTemperatureSensor.java new file mode 100644 index 0000000..80052d0 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/sensor/MockTemperatureSensor.java @@ -0,0 +1,80 @@ +package com.jiluo.bolt.device.sensor; + +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.common.DetectResult; +import com.jiluo.bolt.device.AbstractDevice; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.engine.Engine; +import com.jiluo.bolt.engine.EngineDriver; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CompletableFuture; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/07/13:56 + * @Description: + */ +@Slf4j +public class MockTemperatureSensor extends AbstractDevice { + + private static CompletableFuture CurrentJob; + + public MockTemperatureSensor(String identity) { + super(DeviceType.temperature_sensor, identity); + deviceStatus = DeviceStatus.online; + } + + @Override + public boolean config(Map input) { + deviceStatus = DeviceStatus.ready; + return true; + } + + @Override + public boolean open(Map input) { + String jobId = input.get("jobId").toString(); + if (StringUtils.isBlank(jobId)) { + return false; + } + deviceStatus = DeviceStatus.open; + Random random = new Random(); + CurrentJob = CompletableFuture.runAsync(() -> { + try { + Thread.sleep(5000); + while (EngineDriver.tempSensorMap.containsKey(identity) && EngineDriver.tempSensorMap.get(identity).status().equals(DeviceStatus.open)) { + Thread.sleep(1000); + List detectResultList = new ArrayList<>(); + detectResultList.add(DetectResult.builder().zone(1).position(1).type(DefectType.temperature.name()).value(40.0f + random.nextFloat() * 10.0f).img("-").alarm(0).build()); + Engine.callback(jobId, detectResultList); + } + } catch (Exception e) { + log.error("[MockTemperatureSensor] open:", e.getMessage(), e); + } + }); + CurrentJob.handle((result, ex) -> { + if (ex != null) { + log.error("[MockTemperatureSensor] CurrentJob: " + ex); + } + return result; + }); + return true; + } + + @Override + public boolean close(Map input) { + if (CurrentJob != null && !CurrentJob.isDone()) { + CurrentJob.cancel(true); + } + deviceStatus = DeviceStatus.close; + return true; + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/device/sensor/RFIDTemperatureSensor.java b/bolt-kernel/src/main/java/com/jiluo/bolt/device/sensor/RFIDTemperatureSensor.java new file mode 100644 index 0000000..94b1e00 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/device/sensor/RFIDTemperatureSensor.java @@ -0,0 +1,47 @@ +package com.jiluo.bolt.device.sensor; + +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.common.DetectResult; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.device.AbstractDevice; +import com.jiluo.bolt.engine.Engine; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/07/13:56 + * @Description: + */ +public class RFIDTemperatureSensor extends AbstractDevice { + + public RFIDTemperatureSensor(String identity) { + super(DeviceType.temperature_sensor, identity); + deviceStatus = DeviceStatus.online; + } + + @Override + public boolean config(Map input) { + deviceStatus = DeviceStatus.ready; + return true; + } + + @Override + public boolean open(Map input) { + deviceStatus = DeviceStatus.open; + return true; + } + + @Override + public boolean close(Map input) { + deviceStatus = DeviceStatus.close; + return true; + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/engine/AbstractEngine.java b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/AbstractEngine.java new file mode 100644 index 0000000..6937571 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/AbstractEngine.java @@ -0,0 +1,23 @@ +package com.jiluo.bolt.engine; + +import com.jiluo.bolt.common.DetectJob; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/20:02 + * @Description: + */ +public abstract class AbstractEngine implements IEngine { + + protected DetectJob job; + + public AbstractEngine(DetectJob job) { + this.job = job; + } + + public DetectJob get() { + return job; + } +} \ No newline at end of file diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/engine/BoltEngine.java b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/BoltEngine.java new file mode 100644 index 0000000..eaa0dbb --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/BoltEngine.java @@ -0,0 +1,143 @@ +package com.jiluo.bolt.engine; + +import com.jiluo.bolt.algorithm.AlgorithmName; +import com.jiluo.bolt.algorithm.IAlgorithm; +import com.jiluo.bolt.common.DetectJob; +import com.jiluo.bolt.common.DetectResult; +import com.jiluo.bolt.device.IDevice; +import com.jiluo.bolt.entity.po.Detect; +import com.jiluo.bolt.util.SnowFlakeUtil; +import com.jiluo.bolt.util.SpringContextHolder; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/20:14 + * @Description: + */ + +public class BoltEngine extends AbstractEngine { + + private static final Logger logger = LoggerFactory.getLogger(BoltEngine.class); + + private List LOCALDETECT = new ArrayList<>(); + + DataCallBack dataCallBack = SpringContextHolder.getBean(DataCallBack.class); + + public BoltEngine(DetectJob job) { + super(job); + } + + @Override + public boolean run() { + + IDevice plc = null; + + IDevice camera = null; + + IAlgorithm algorithm = null; + + String plcIp = job.getConfig().getString("plc_ip"); + + String cameraId = job.getDeviceId(); + + String algorithmName = job.getConfig().getString("algorithm_name"); + + String algorithmType = job.getConfig().getString("algorithm_type"); + + String algorithmId = job.getConfig().getString("algorithm_id"); + + if (StringUtils.isNotBlank(plcIp) && EngineDriver.plcMap.containsKey(plcIp) && + StringUtils.isNotBlank(cameraId) && EngineDriver.cameraMap.containsKey(cameraId) && + StringUtils.isNotBlank(algorithmName) && StringUtils.isNotBlank(algorithmType)) { + + plc = EngineDriver.plcMap.get(plcIp); + + camera = EngineDriver.cameraMap.get(cameraId); + + try { + algorithm = AlgorithmName.getByName(algorithmName).getClazz().getDeclaredConstructor(String.class).newInstance(algorithmId); + } catch (Exception e) { + logger.error("[BoltEngine] run:", e.getMessage(), e); + } + } + + if (plc == null || camera == null || algorithm == null) { + job.setStatus(2); + dataCallBack.addAlarm("获取plc、相机或者算法失败!", job.getPoint()); + return false; + } + + if (!plc.open(job.getConfig())) { + job.setStatus(2); + dataCallBack.addAlarm("plc开启失败!", job.getPoint()); + return false; + } + + if (!camera.config(job.getConfig())) { + job.setStatus(2); + dataCallBack.addAlarm("相机配置下发失败!", job.getPoint()); + return false; + } + + if (!algorithm.config(job.getConfig())) { + job.setStatus(2); + dataCallBack.addAlarm("算法配置下发失败!", job.getPoint()); + return false; + } + + long startTime = System.currentTimeMillis(); + + if (!camera.open(job.getConfig())) { + job.setStatus(2); + dataCallBack.addAlarm("相机开启失败!", job.getPoint()); + return false; + } + + long endTime = System.currentTimeMillis(); + logger.info("【相机-开启-采图】代码运行总耗时:" + (endTime - startTime) + " ms"); + + if (!camera.close(job.getConfig())) { + job.setStatus(2); + dataCallBack.addAlarm("相机关闭失败!", job.getPoint()); + return false; + } + + if (!plc.close(job.getConfig())) { + job.setStatus(2); + dataCallBack.addAlarm("plc关闭失败!", job.getPoint()); + return false; + } + + dataCallBack.detectSaveDB(LOCALDETECT); + + job.setStatus(dataCallBack.defectSaveDB(job.getPowerStation(), job.getMotorGroup(), job.getPoint(), job.getJobId(), job.getDeviceId())); + + return true; + } + + @Override + public void callback(List detectList) { + + dataCallBack.DetectData(job.getPowerStation(), job.getMotorGroup(), job.getPoint(), job.getJobId(), job.getDeviceId(), detectList); + + Detect detect = new Detect(); + + detect.setBizId("detect_" + SnowFlakeUtil.getDefaultSnowFlakeId()) + .setPowerStation(job.getPowerStation()) + .setMotorGroup(job.getMotorGroup()) + .setPoint(job.getPoint()) + .setJob(job.getJobId()) + .setZone(detectList.get(0).getZone()) + .setData(detectList.get(0).getImg()) + .setDevice(job.getDeviceId()); + + LOCALDETECT.add(detect); + } +} \ No newline at end of file diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/engine/DataCallBack.java b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/DataCallBack.java new file mode 100644 index 0000000..d9e3f88 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/DataCallBack.java @@ -0,0 +1,335 @@ +package com.jiluo.bolt.engine; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.jiluo.bolt.common.*; +import com.jiluo.bolt.config.UploadFile; +import com.jiluo.bolt.entity.po.*; +import com.jiluo.bolt.service.*; +import com.jiluo.bolt.util.CSVUtils; +import com.jiluo.bolt.util.SnowFlakeUtil; +import com.jiluo.bolt.util.SystemDateUtils; +import com.jiluo.bolt.websocket.WebSocket; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/10/11:12 + * @Description: + */ +@Slf4j +@Component +public class DataCallBack { + private Map> allDefect = new ConcurrentHashMap<>(); + public static Map allTemperatureData = new ConcurrentHashMap<>(); + + @Value("${defect_work_dir}") + private String defect_work_dir; + + @Value("${defect_csv_dir}") + private String defect_csv_dir; + + @Value("${max_file_keep_time}") + private Integer max_file_keep_time; + + @Resource + private PowerStationService powerStationService; + @Resource + private MotorGroupService motorGroupService; + @Resource + private PointService pointService; + @Resource + private DeviceService deviceService; + @Resource + private DefectService defectService; + @Resource + private DetectService detectService; + @Resource + private JobService jobService; + @Resource + private AlarmService alarmService; + @Resource + private WebSocket webSocket; + @Resource + private CSVUtils csvUtils; + @Resource + private UploadFile uploadFile; + + public void DetectData(String powerStationId, String motorGroupId, String pointId, String jobId, String deviceId, List detectList) { + if (StringUtils.isAnyBlank(powerStationId, motorGroupId, pointId, jobId, deviceId) || detectList == null || detectList.isEmpty()) { + return; + } + + List imgs = new ArrayList<>(); + List detects_websocket = new ArrayList<>(); + + detectList.stream().filter(distinctByKey(DetectResult::getZone)).forEach(detectResult -> { + JSONObject img = new JSONObject(); + img.put("zone", detectResult.getZone()); + img.put("data", detectResult.getImg()); + imgs.add(img); + }); + webSocket.sendImgMessage(pointId, imgs); + updateDefect(jobId, detectList).forEach(detectResult -> { + JSONObject detect = new JSONObject(); + detect.put("zone", detectResult.getZone()); + detect.put("position", detectResult.getPosition()); + detect.put("type", detectResult.getType()); + detect.put("value", detectResult.getValue()); + detect.put("alarm", detectResult.getAlarm() != 0 ? 1 : 0); + detects_websocket.add(detect); + }); + if (!detects_websocket.isEmpty()) { + webSocket.sendBoltMessage(pointId, detects_websocket); + } + } + + private List updateDefect(String jobId, List detectListInput) { + if (allDefect == null || allDefect.isEmpty() || allDefect.get(jobId) == null || allDefect.get(jobId).isEmpty()) { + allDefect.put(jobId, detectListInput); + return detectListInput; + } else { + List detectListStored = allDefect.get(jobId); + Map detectMapStored = createDetectResultMap(detectListStored); + + List detectResultListReturn = new ArrayList<>(); + + List newDetectListStored = new ArrayList<>(detectListStored); + for (DetectResult detect : detectListInput) { + DetectResultKey detectKey = new DetectResultKey(detect); + DetectResult detect1 = detectMapStored.get(detectKey); + if (detect1 == null) { + newDetectListStored.add(detect); +// detectListStored.add(detect); + detectMapStored.put(detectKey, detect); + detectResultListReturn.add(detect); + } else { + if (detect.getValue() > detect1.getValue()) { + detect1.setValue(detect.getValue()); + detect1.setImg(detect.getImg()); + detect1.setAlarm(detect.getAlarm()); + detectResultListReturn.add(detect); + } + } + } + allDefect.put(jobId, newDetectListStored); + return detectResultListReturn; + } + } + + public void detectSaveDB(List detectList) { + detectService.saveBatch(detectList); + } + + public int defectSaveDB(String powerStationId, String motorGroupId, String pointId, String jobId, String deviceId) { + if (allDefect == null || allDefect.isEmpty() || allDefect.get(jobId) == null || allDefect.get(jobId).isEmpty()) { + log.error("[DataCallBack] saveDB: 参数缺失"); + return 2; + } else { + List defects = new ArrayList<>(); + allDefect.get(jobId).stream().forEach(detectResult -> { + BigDecimal _value = (detectResult.getType().equals(DefectType.bolt.name()) || detectResult.getType().equals(DefectType.temperature.name())) ? + BigDecimal.valueOf(detectResult.getValue() * 100).setScale(0, RoundingMode.HALF_UP) : + (detectResult.getType().equals(DefectType.line.name()) ? + BigDecimal.valueOf(detectResult.getValue() * 1000).setScale(0, RoundingMode.HALF_UP) : + BigDecimal.valueOf(detectResult.getValue())); + Defect defect = new Defect(); + defects.add(defect.setBizId("defect_" + SnowFlakeUtil.getDefaultSnowFlakeId()) + .setPowerStation(powerStationId) + .setMotorGroup(motorGroupId) + .setPoint(pointId) + .setJob(jobId) + .setDevice(deviceId) + .setType(detectResult.getType()) + .setZone(detectResult.getZone()) + .setPosition(detectResult.getPosition()) + .setValue(_value.intValue()) + .setData(detectResult.getImg()) + .setAlarm(detectResult.getAlarm() == 0 ? 0 : 1) + .setStatus(0)); + }); + allDefect.remove(jobId); + if (defectService.saveBatch(defects) && StringUtils.isNotBlank(defect_csv_dir)) { + csvUtils.generateCSV(defectCSV(defects), defect_csv_dir, jobId); + } + return createAttribute(defects, pointId, jobId); + } + } + + @Scheduled(cron = "0 0 1 * * ?") + private void autoDelete() { + LocalDate currentDate = LocalDate.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + String formattedDate = currentDate.format(formatter); + String endTime = LocalDate.parse(formattedDate).minusDays(max_file_keep_time).toString(); + if (StringUtils.isNotBlank(endTime)) { + jobService.remove(Wrappers.query().lambda().lt(Job::getGmtCreate, endTime)); + detectService.remove(Wrappers.query().lambda().lt(Detect::getGmtCreate, endTime)); + defectService.remove(Wrappers.query().lambda().lt(Defect::getGmtCreate, endTime)); + } + } + + public void temperatureData(String powerStationId, String motorGroupId, String pointId, String jobId, String deviceId, List detectList) { + if (StringUtils.isNotBlank(powerStationId) && StringUtils.isNotBlank(motorGroupId) && StringUtils.isNotBlank(pointId) && StringUtils.isNotBlank(jobId) && + StringUtils.isNotBlank(deviceId) && detectList != null && detectList.size() > 0) { + updateTemperature(powerStationId, motorGroupId, pointId, jobId, deviceId, detectList); + } + } + + private void updateTemperature(String powerStationId, String motorGroupId, String pointId, String jobId, String deviceId, List detectList) { + detectList.forEach(detectResult -> { + BigDecimal _value = BigDecimal.valueOf(detectResult.getValue() * 100).setScale(0, RoundingMode.HALF_UP); + if (allTemperatureData.get(pointId) == null) { + Defect defect = new Defect(); + defect.setBizId("defect_" + SnowFlakeUtil.getDefaultSnowFlakeId()) + .setPowerStation(powerStationId) + .setMotorGroup(motorGroupId) + .setPoint(pointId) + .setJob(jobId) + .setDevice(deviceId) + .setType(DefectType.temperature.name()) + .setZone(detectResult.getZone()) + .setPosition(detectResult.getPosition()) + .setValue(_value.intValue()) + .setData(detectResult.getImg()) + .setAlarm(detectResult.getAlarm()) + .setStatus(0); + allTemperatureData.put(pointId, defect); + } else if (allTemperatureData.get(pointId).getValue() * 100 < _value.intValue()) { + allTemperatureData.get(pointId).setValue(_value.intValue()).setAlarm(detectResult.getAlarm()); + } + webSocket.sendTemperatureMessage(pointId, detectResult.getValue()); + }); + } + + @Scheduled(cron = "0 0 * * * *") + private void temperatureSaveDB() { + allTemperatureData.forEach((k, v) -> defectService.save(v)); + allTemperatureData.clear(); + } + + private Map createDetectResultMap(List detectList) { + Map detectMap = new HashMap<>(); + for (DetectResult detect : detectList) { + DetectResultKey detectKey = new DetectResultKey(detect); + detectMap.put(detectKey, detect); + } + return detectMap; + } + + private static Predicate distinctByKey(Function keyExtractor) { + Map seen = new ConcurrentHashMap<>(); + return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; + } + + private List DuplicateRemove(List detectListInput) { + Map resultMap = new HashMap<>(); + + for (DetectResult detectResult : detectListInput) { + String key = detectResult.getZone() + "-" + detectResult.getPosition() + "-" + detectResult.getType(); + DetectResult existingResult = resultMap.get(key); + + if (existingResult == null || detectResult.getValue() > existingResult.getValue()) { + resultMap.put(key, detectResult); + } + } + + return new ArrayList<>(resultMap.values()); + } + + private List defectCSV(List defectList) { + Map powerStationMap = powerStationService.getAll().stream().collect(Collectors.toMap(PowerStation::getPowerStationId, PowerStation::getName)); + Map motorGroupMap = motorGroupService.getAll().stream().collect(Collectors.toMap(MotorGroup::getMotorGroupId, MotorGroup::getName)); + Map pointMap = pointService.getAll().stream().collect(Collectors.toMap(Point::getPointId, Point::getName)); + Map deviceMap = deviceService.selectAll().stream().collect(Collectors.toMap(Device::getDeviceId, Device::getName)); + defectList.forEach(defect -> { + if (powerStationMap.containsKey(defect.getPowerStation())) defect.setPowerStation(defect.getPowerStation()); + if (motorGroupMap.containsKey(defect.getMotorGroup())) defect.setMotorGroup(defect.getMotorGroup()); + if (pointMap.containsKey(defect.getPoint())) defect.setPoint(defect.getPoint()); + if (deviceMap.containsKey(defect.getDevice())) defect.setDevice(defect.getDevice()); + }); + return defectList; + } + + private int createAttribute(List defects, String pointId, String job_id) { + Point point = pointService.getByBizId(pointId); + Map> defectZone = new HashMap<>(); + List bolts = new ArrayList<>(); + List lines = new ArrayList<>(); + List poles = new ArrayList<>(); + List temps = new ArrayList<>(); + defects.stream().filter(defect -> defect.getAlarm() != 0).forEach(defect -> { + if (defect.getType().equals("bolt")) { + bolts.add(defect.getZone()); + } else if (defect.getType().equals("line")) { + lines.add(defect.getZone()); + } else if (defect.getType().equals("pole")) { + poles.add(defect.getZone()); + } else if (defect.getType().equals("temperature")) { + temps.add(defect.getZone()); + } + }); + defectZone.put("螺栓松动", bolts.stream().distinct().sorted().collect(Collectors.toList())); + defectZone.put("引出线变形", lines.stream().distinct().sorted().collect(Collectors.toList())); + defectZone.put("磁极开闸", poles.stream().distinct().sorted().collect(Collectors.toList())); + defectZone.put("温度异常", temps.stream().distinct().sorted().collect(Collectors.toList())); + DetectAttribute detectAttribute = DetectAttribute.builder().productId(DetectType.BOLT_AND_LINE.getProduct()) + .productName(DetectType.BOLT_AND_LINE.getDesc()) + .detectZone(point.getPoleNum()) + .detectTotal(detectService.selectByJob(job_id)) + .detectDuration(point.getManualTime()) + .defectZone(defectZone) + .defectTotal(defects.size()) + .detectTrack(DetectType.BOLT_AND_LINE.getDesc() + "数据分析结束!").build(); + if (defects.stream().filter(x -> x.getAlarm() == 1).collect(Collectors.toList()).size() > 0) { + jobService.updateAttribute(job_id, JSONObject.toJSONString(detectAttribute), 1); + Alarm alarm = new Alarm(); + alarm.setAlarmId("alarm_" + SnowFlakeUtil.getDefaultSnowFlakeId()) + .setType(0) + .setContent(motorGroupService.getByBizId(point.getMotorGroup()).getName() + "/" + point.getName() + "检测异常"); + alarmService.add(alarm); + AlarmService.ALARM_MAP.put(pointId, alarm); + if (DeviceService.DeviceLocalStatus.containsKey(pointId)) { + DeviceService.DeviceLocalStatus.get(pointId).setPointStatus(1); + } + return 1; + } else { + jobService.updateAttribute(job_id, JSONObject.toJSONString(detectAttribute), 0); + if (AlarmService.ALARM_MAP.containsKey(pointId)) AlarmService.ALARM_MAP.remove(pointId); + if (DeviceService.DeviceLocalStatus.containsKey(pointId) && DeviceService.DeviceLocalStatus.get(pointId).equals(1)) { + DeviceService.DeviceLocalStatus.get(pointId).setPointStatus(0); + } + return 0; + } + } + + public void addAlarm(String info, String pointId) { + Point point = pointService.getByBizId(pointId); + Alarm alarm = new Alarm(); + alarm.setAlarmId("alarm_" + SnowFlakeUtil.getDefaultSnowFlakeId()) + .setType(0) + .setContent(point.getName() + ":" + info); + alarm.setGmtCreate(SystemDateUtils.getNewDate()); + AlarmService.ALARM_MAP.put(pointId, alarm); + webSocket.sendStatusMessage(pointId, 0); + } + +} + diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/engine/Engine.java b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/Engine.java new file mode 100644 index 0000000..72a61d1 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/Engine.java @@ -0,0 +1,43 @@ +package com.jiluo.bolt.engine; + +import com.jiluo.bolt.common.DetectJob; +import com.jiluo.bolt.common.DetectResult; +import lombok.SneakyThrows; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/20:09 + * @Description: + */ +public class Engine { + private static ConcurrentHashMap DETECTS = new ConcurrentHashMap<>(); + + @SneakyThrows + public static boolean run(JobType jobType, DetectJob job) { + job.setStatus(3); + IEngine engine = jobType.clazz.getDeclaredConstructor(DetectJob.class).newInstance(job); + DETECTS.putIfAbsent(job.getJobId(), engine); + engine.run(); + return true; + } + + public static Integer get(String jobId) { + DETECTS.forEach((k,v)->{ + System.out.println(k+":"+v.get().getStatus()); + }); + IEngine engine = DETECTS.get(jobId); + return engine == null ? null : engine.get().getStatus(); + } + + public static void callback(String jobId, List data) { + IEngine engine = DETECTS.get(jobId); + if (engine != null) { + engine.callback(data); + } + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/engine/EngineDriver.java b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/EngineDriver.java new file mode 100644 index 0000000..5e83747 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/EngineDriver.java @@ -0,0 +1,301 @@ +package com.jiluo.bolt.engine; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.jiluo.bolt.OKHttp.OkHttpUtil; +import com.jiluo.bolt.common.DetectJob; +import com.jiluo.bolt.common.DetectType; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.device.DeviceVender; +import com.jiluo.bolt.device.IDevice; +import com.jiluo.bolt.entity.po.Device; +import com.jiluo.bolt.common.LocalStatus; +import com.jiluo.bolt.entity.po.DeviceTemplete; +import com.jiluo.bolt.entity.po.Job; +import com.jiluo.bolt.service.DeviceService; +import com.jiluo.bolt.service.DeviceTempleteService; +import com.jiluo.bolt.service.JobService; +import com.jiluo.bolt.util.ShellUtils; +import com.jiluo.bolt.util.SnowFlakeUtil; +import com.jiluo.bolt.util.SystemDateUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/11/9:59 + * @Description: + */ +@Component +public class EngineDriver { + private static final Logger logger = LoggerFactory.getLogger(EngineDriver.class); + + @Autowired + DeviceTempleteService deviceTempleteService; + + @Autowired + DeviceService deviceService; + + @Value("${device.camera.driver}") + private String cameraDriverStr; + + private List cameraDriverStrList; + + @Resource + private JobService jobService; + + private Integer LucidMonitorErrorTimes = 0; + private Integer BaumerMonitorErrorTimes = 0; + private Integer BaslerMonitorErrorTimes = 0; + + + private Map drivers = new HashMap<>(); + + public static Map plcMap = new ConcurrentHashMap<>(); + public static Map cameraMap = new ConcurrentHashMap<>(); + public static Map tempSensorMap = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + drivers = deviceTempleteService.getAll().stream().collect(Collectors.toMap(DeviceTemplete::getBizId, DeviceTemplete::getDrive)); + cameraDriverStrList = Arrays.asList(cameraDriverStr.split(",")); + cameraDriverStrList.forEach(driver -> { + if (driver.equals("Lucid")) RunLucidCameraDriver(); + else if (driver.equals("Baumer")) RunBaumerCameraDriver(); + else if (driver.equals("Basler")) RunBaslerCameraDriver(); + }); + } + + @Scheduled(fixedRate = 1000, initialDelay = 5000) // 每1秒钟执行一次,延迟5秒后开始执行第一次定时任务。 + public void deviceMonitor() { + List deviceList = deviceService.selectAll(); + deviceList.stream().filter(x -> x.getType().equals("camera")).forEach(camera -> { + JSONObject config = JSON.parseObject(camera.getConfig()); + String plcIp = config.getString("PLC_Ip"); + String plcVender = config.getString("PLC_vender"); + String cameraId = camera.getDeviceId(); + if (plcIp == null || plcVender == null) { + logger.info("[EngineDriver] deviceMonitor: plc的ip、vender参数缺失"); + } else { + if (!plcMap.containsKey(plcIp)) { + createPlc(camera); + } else { + if (!plcMap.get(plcIp).getClass().equals(DeviceVender.getByVender(plcVender).getClazz())) { + createPlc(camera); + } else if (plcMap.get(plcIp).status().equals(DeviceStatus.offline)) { + plcOfflineSetStatus(camera.getPointId()); + plcMap.remove(plcIp); + } else if (DeviceService.DeviceLocalStatus.containsKey(camera.getPointId()) && DeviceService.DeviceLocalStatus.get(camera.getPointId()).getPointStatus().equals(2)) { + DeviceService.DeviceLocalStatus.put(camera.getPointId(), LocalStatus.builder().pointStatus(0).cameraStatus(1).tempSensorStatus(1).build()); + } + } + } + if (!cameraMap.containsKey(cameraId)) { + createCamera(camera); + } else { + if (!DeviceVender.getByVender(camera.getVender()).getClazz().equals(cameraMap.get(cameraId).getClass())) { + createCamera(camera); + } + } + }); + deviceList.stream().filter(x -> x.getType().equals("temperature_sensor")).forEach(tempSensor -> { + String tempSensorId = tempSensor.getDeviceId(); + if (!tempSensorMap.containsKey(tempSensorId)) { + createTempSensor(tempSensor); + } else { + if (!DeviceVender.getByVender(tempSensor.getVender()).getClazz().equals(tempSensorMap.get(tempSensorId).getClass())) { + createTempSensor(tempSensor); + } + } + }); + + Set plcIdSet = new HashSet<>(deviceList.size()); + Set cameraIdSet = new HashSet<>(deviceList.size()); + Set tempSensorIdSet = new HashSet<>(deviceList.size()); + deviceList.forEach(x -> { + if (x.getType().equals("camera")) { + plcIdSet.add(JSON.parseObject(x.getConfig()).getString("PLC_Ip")); + cameraIdSet.add(x.getDeviceId()); + } else if (x.getType().equals("temperature_sensor")) { + tempSensorIdSet.add(x.getDeviceId()); + } + }); + plcMap.keySet().retainAll(plcIdSet); + cameraMap.keySet().retainAll(cameraIdSet); + tempSensorMap.keySet().retainAll(tempSensorIdSet); + } + + @Scheduled(fixedRate = 1000) + public void monitor() { + cameraDriverStrList.forEach(driver -> { + if (driver.equals("Lucid")) LucidCameraMonitor(); + else if (driver.equals("Baumer")) BaumerCameraMonitor(); + else if (driver.equals("Basler")) BaslerCameraMonitor(); + }); + } + + private void RunLucidCameraDriver() { + if (drivers.containsKey(DeviceVender.LucidCamera.getVender())) { + ShellUtils.exec(DeviceVender.LucidCamera.getVender(), drivers.get(DeviceVender.LucidCamera.getVender())); + } + } + + private void RunBaumerCameraDriver() { + if (drivers.containsKey(DeviceVender.BaumerCamera.getVender())) { + ShellUtils.exec(DeviceVender.BaumerCamera.getVender(), drivers.get(DeviceVender.BaumerCamera.getVender())); + } + } + + private void RunBaslerCameraDriver() { + if (drivers.containsKey(DeviceVender.BaslerCamera.getVender())) { + ShellUtils.exec(DeviceVender.BaslerCamera.getVender(), drivers.get(DeviceVender.BaslerCamera.getVender())); + } + } + + private void LucidCameraMonitor() { + if (LucidMonitorErrorTimes > 5) { + RunLucidCameraDriver(); + logger.info("lucid相机驱动重启动"); + LucidMonitorErrorTimes = 0; + } + boolean flag = Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.LucidCamera.getVender(), "monitor", new JSONObject().toJSONString())); + if (flag) { + LucidMonitorErrorTimes = 0; + } else { + logger.info("lucid相机驱动掉线"); + LucidMonitorErrorTimes++; + } + } + + private void BaumerCameraMonitor() { + if (BaumerMonitorErrorTimes > 5) { + RunBaumerCameraDriver(); + logger.info("Baumer相机驱动重启动"); + BaumerMonitorErrorTimes = 0; + } + boolean flag = Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.BaumerCamera.getVender(), "monitor", new JSONObject().toJSONString())); + if (flag) { + BaumerMonitorErrorTimes = 0; + } else { + logger.info("Baumer相机驱动掉线"); + BaumerMonitorErrorTimes++; + } + } + + private void BaslerCameraMonitor() { + if (BaslerMonitorErrorTimes > 5) { + RunBaslerCameraDriver(); + logger.info("Basler相机驱动重启动"); + BaslerMonitorErrorTimes = 0; + } + boolean flag = Boolean.parseBoolean(OkHttpUtil.postJson(DeviceType.camera.name(), DeviceVender.BaslerCamera.getVender(), "monitor", new JSONObject().toJSONString())); + if (flag) { + BaslerMonitorErrorTimes = 0; + } else { + logger.info("Basler相机驱动掉线"); + BaslerMonitorErrorTimes++; + } + } + + private void createPlc(Device camera) { + try { + JSONObject config = JSON.parseObject(camera.getConfig()); + String plcIp = config.getString("PLC_Ip"); + String plcVender = config.getString("PLC_vender"); + IDevice plc = DeviceVender.getByVender(plcVender).getClazz().getDeclaredConstructor(String.class).newInstance(plcIp); + if (plc != null) { + if (!plc.config(config)) { + logger.info("plc连接失败,地址为:" + "plcVender" + plcVender + "plcIp" + plcIp); + plcOfflineSetStatus(camera.getPointId()); + } else { + logger.info("plc连接成功,地址为:" + plcVender + plcIp); + plcMap.put(plcIp, plc); + DeviceService.DeviceLocalStatus.put(camera.getPointId(), LocalStatus.builder().pointStatus(0).cameraStatus(1).tempSensorStatus(1).build()); + } + } else { + logger.info("plc创建失败!" + plcVender + plcIp); + plcOfflineSetStatus(camera.getPointId()); + } + } catch (Exception e) { + logger.error("[EngineDriver] deviceMonitor plc_exception:" + e.getMessage(), e); + plcOfflineSetStatus(camera.getPointId()); + } + } + + private void createCamera(Device camera) { + String cameraId = camera.getDeviceId(); + IDevice _camera = null; + try { + _camera = DeviceVender.getByVender(camera.getVender()).getClazz().getDeclaredConstructor(String.class).newInstance(cameraId); + } catch (Exception e) { + logger.error("[EngineDriver] deviceMonitor camera_exception:" + e.getMessage(), e); + } + if (_camera != null) { + cameraMap.put(cameraId, _camera); + } else { + logger.info("camera创建失败!" + camera.getVender() + cameraId); + } + } + + private void createTempSensor(Device tempSensor) { + String tempSensorId = tempSensor.getDeviceId(); + IDevice _tempSensor = null; + try { + _tempSensor = DeviceVender.getByVender(tempSensor.getVender()).getClazz().getDeclaredConstructor(String.class).newInstance(tempSensorId); + logger.info("温度传感器创建成功! 设备id:" + tempSensorId); + } catch (Exception e) { + logger.error("[EngineDriver] deviceMonitor tempSensor_exception:" + e.getMessage(), e); + } + if (_tempSensor != null) { + tempSensorMap.put(tempSensorId, _tempSensor); + jobService.addJob(createTemperatureJob(tempSensor)); + } else { + logger.info("温度传感器创建失败!" + tempSensor.getVender() + tempSensorId); + } + } + + private void plcOfflineSetStatus(String pointId) { + DeviceService.DeviceLocalStatus.put(pointId, LocalStatus.builder().pointStatus(2).cameraStatus(2).tempSensorStatus(2).build()); + } + + private static Job createTemperatureJob(Device tempSensor) { + String job_id = "job_" + SystemDateUtils.getStrYMD() + "_" + SnowFlakeUtil.getDefaultSnowFlakeId(); + Job job = new Job(); + job.setJobId(job_id) + .setPowerStation(tempSensor.getPowerStationId()) + .setMotorGroup(tempSensor.getMotorGroupId()) + .setPoint(tempSensor.getPointId()) + .setProduct(DetectType.TEMPERATURE.getProduct()) + .setType("自动检测") + .setName(DetectType.TEMPERATURE.getDesc()) + .setDescription(DetectType.TEMPERATURE.getDesc() + "[自动检测]") + .setAttribute(new JSONObject().toJSONString()) + .setConfig(tempSensor.getConfig()) + .setOperator("SYSTEM") + .setStatus(0); + JSONObject config = JSON.parseObject(tempSensor.getConfig()); + config.put("jobId", job_id); + Engine.run(JobType.TEMPERATURE, DetectJob.builder().jobId(job_id) + .powerStation(tempSensor.getPowerStationId()) + .motorGroup(tempSensor.getMotorGroupId()) + .point(tempSensor.getPointId()) + .attribute(new JSONObject()) + .config(config) + .deviceId(tempSensor.getDeviceId()) + .build()); + return job; + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/engine/IEngine.java b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/IEngine.java new file mode 100644 index 0000000..9b9e1d2 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/IEngine.java @@ -0,0 +1,32 @@ +package com.jiluo.bolt.engine; + +import com.jiluo.bolt.common.DetectJob; +import com.jiluo.bolt.common.DetectResult; + +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/19:53 + * @Description: + */ +public interface IEngine { + + /** + * 启动检测 + */ + boolean run(); + + /** + * 获取检测任务 + */ + DetectJob get(); + + /** + * 检测结果回调 + * @param detectList + */ + void callback(List detectList); +} \ No newline at end of file diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/engine/JobType.java b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/JobType.java new file mode 100644 index 0000000..744591e --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/JobType.java @@ -0,0 +1,21 @@ +package com.jiluo.bolt.engine; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/20:11 + * @Description: + */ +public enum JobType { + BOLT_And_LINE("BOLT_AND_LINE", BoltEngine.class), + TEMPERATURE("TEMPERATURE", TemperatureEngine.class); + + String name; + Class clazz; + + JobType(String name, Class clazz) { + this.name = name; + this.clazz = clazz; + } +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/engine/TemperatureEngine.java b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/TemperatureEngine.java new file mode 100644 index 0000000..37f154b --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/engine/TemperatureEngine.java @@ -0,0 +1,70 @@ +package com.jiluo.bolt.engine; + +import com.jiluo.bolt.common.DetectJob; +import com.jiluo.bolt.common.DetectResult; +import com.jiluo.bolt.device.IDevice; +import com.jiluo.bolt.util.SpringContextHolder; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/03/20:14 + * @Description: + */ + +public class TemperatureEngine extends AbstractEngine { + + private static final Logger logger = LoggerFactory.getLogger(TemperatureEngine.class); + + DataCallBack dataCallBack = SpringContextHolder.getBean(DataCallBack.class); + + public TemperatureEngine(DetectJob job) { + super(job); + } + + @Override + public boolean run() { + IDevice tempSensor = null; + + String deviceId = job.getDeviceId(); + if (StringUtils.isNotBlank(deviceId) && EngineDriver.tempSensorMap.containsKey(deviceId)) { + tempSensor = EngineDriver.tempSensorMap.get(deviceId); + } + if (tempSensor == null) { + job.setStatus(2); + logger.info("温度传感器获取失败!设备编号:" + deviceId); + return false; + } + if (!tempSensor.config(job.getConfig())) { + job.setStatus(2); + logger.info("温度传感器参数下发失败!设备编号:" + deviceId); + return false; + } + Map openMap = job.getConfig(); + openMap.put("jobId", job.getJobId()); + if (!tempSensor.open(openMap)) { + job.setStatus(2); + logger.info("温度传感器开启失败!设备编号:" + deviceId); + return false; + } +// if (!tempSensor.close(job.getConfig())){ +// job.setStatus(2); +// logger.info("温度传感器关闭失败!设备编号:" + deviceId); +// return false; +// } + job.setStatus(0); + return true; + } + + @Override + public void callback(List data) { + dataCallBack.temperatureData(job.getPowerStation(), job.getMotorGroup(), job.getPoint(), job.getJobId(), job.getDeviceId(), data); + } +} \ No newline at end of file diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/modbusTcp/config/ModbusTcpMaster.java b/bolt-kernel/src/main/java/com/jiluo/bolt/modbusTcp/config/ModbusTcpMaster.java new file mode 100644 index 0000000..935e742 --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/modbusTcp/config/ModbusTcpMaster.java @@ -0,0 +1,49 @@ +package com.jiluo.bolt.modbusTcp.config; + +import com.serotonin.modbus4j.ModbusFactory; +import com.serotonin.modbus4j.ModbusMaster; +import com.serotonin.modbus4j.exception.ModbusInitException; +import com.serotonin.modbus4j.ip.IpParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class ModbusTcpMaster { + private static ModbusFactory modbusFactory; + private static final Logger logger = LoggerFactory.getLogger(ModbusTcpMaster.class); + + static { + if (modbusFactory == null) { + modbusFactory = new ModbusFactory(); + } + } + + /** + * 获取Tcp master + * @param ip + * @param port + * @return + */ + public static ModbusMaster getMaster(String ip, int port) { + IpParameters params = new IpParameters(); + params.setHost(ip); + params.setPort(port); + //这个属性确定了协议帧是否是通过tcp封装的RTU结构,采用modbus tcp/ip时,要设为false, 采用modbus rtu over tcp/ip时,要设为true + params.setEncapsulated(false); + // 参数1:IP和端口信息 参数2:保持连接激活 + ModbusMaster master = null; + master = modbusFactory.createTcpMaster(params, true); + try { + //设置超时时间 + master.setTimeout(500); + //设置重连次数 + master.setRetries(2); + master.setConnected(true); + //初始化 + master.init(); + } catch (ModbusInitException e) { + e.printStackTrace(); + } + return master; + } +} \ No newline at end of file diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/modbusTcp/utils/Modbus4jReadUtils.java b/bolt-kernel/src/main/java/com/jiluo/bolt/modbusTcp/utils/Modbus4jReadUtils.java new file mode 100644 index 0000000..a6360ba --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/modbusTcp/utils/Modbus4jReadUtils.java @@ -0,0 +1,161 @@ +package com.jiluo.bolt.modbusTcp.utils; + +import com.serotonin.modbus4j.BatchRead; +import com.serotonin.modbus4j.BatchResults; +import com.serotonin.modbus4j.ModbusMaster; +import com.serotonin.modbus4j.code.DataType; +import com.serotonin.modbus4j.exception.ErrorResponseException; +import com.serotonin.modbus4j.exception.ModbusInitException; +import com.serotonin.modbus4j.exception.ModbusTransportException; +import com.serotonin.modbus4j.locator.BaseLocator; +import com.serotonin.modbus4j.msg.*; + + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/29/17:13 + * @Description: + */ +public class Modbus4jReadUtils { + + /** + * 读(线圈)开关量数据 + * 功能码为:01; 读取开关量输出点的ON/OFF状态,可以读写的布尔类型(0x)---00001 至 0xxxx – 开关量输出 + * @param slaveId slaveId-从站编号-自行约定 + * @param offset 位置 + * @return 读取值-读取多少个 + */ + public boolean[] readCoilStatus(ModbusMaster master,int slaveId, int offset, int numberOfBits) + throws ModbusTransportException, ErrorResponseException, ModbusInitException { + + ReadCoilsRequest request = new ReadCoilsRequest(slaveId, offset, numberOfBits); + ReadCoilsResponse response = (ReadCoilsResponse) master.send(request); + boolean[] booleans = response.getBooleanData(); + return valueRegroup(numberOfBits, booleans); + } + + + /**开关数据 读取外围设备输入的开关量 + * 功能码为:02;读取开关量输入点的ON/OFF状态,只能读的布尔类型(1x)---10001 至 1xxxx – 开关量输入 + * @param slaveId-从站编号-自行约定 + * @param offset-预访问的地址-地址范围:0-255 + * @param numberOfBits-读取多少个 + * @return + * @throws ModbusTransportException + * @throws ErrorResponseException + * @throws ModbusInitException + */ + public boolean[] readInputStatus(ModbusMaster master,int slaveId, int offset, int numberOfBits) + throws ModbusTransportException, ErrorResponseException, ModbusInitException { + ReadDiscreteInputsRequest request = new ReadDiscreteInputsRequest(slaveId, offset, numberOfBits); + ReadDiscreteInputsResponse response = (ReadDiscreteInputsResponse) master.send(request); + boolean[] booleans = response.getBooleanData(); + return valueRegroup(numberOfBits, booleans); + } + + /** + * 读取保持寄存器数据 + * 功能码为:03 读取保持寄存器的数据,可以读写的数字类型(4x)---40001 至 4xxxx – 保持寄存器 + * + **举例子说明:S7-200 + Smart PLC中,设置 [HoldStr~]=&VB1000;则对应的保持寄存器地址为VW1000\VW1002\VW10004 + **在java中对应的address为:0、1、2 + * @param slaveId slave Id-从站编号-自行约定 + * @param offset 位置 + * @param numberOfBits numberOfRegisters 寄存器个数 每个寄存器表示一个16位无符号整数 相当于一个short + */ + public static short[] readHoldingRegister(ModbusMaster master,int slaveId, int offset, int numberOfBits) + throws ModbusTransportException, ErrorResponseException, ModbusInitException { + ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, offset, numberOfBits); + ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request); + return response.getShortData(); + } + + + /** + * 读取[03 Holding Register类型 2x]模拟量数据 + * @param slaveId slave Id + * @param offset 位置 + * @param dataType 数据类型,来自com.serotonin.modbus4j.code.DataType + * @return + * @throws ModbusTransportException 异常 + * @throws ErrorResponseException 异常 + * @throws ModbusInitException 异常 + */ + public static Number readHoldingRegisterByDataType(ModbusMaster master,int slaveId, int offset, int dataType) + throws ModbusTransportException, ErrorResponseException, ModbusInitException { + // 03 Holding Register类型数据读取 + BaseLocator loc = BaseLocator.holdingRegister(slaveId, offset, dataType); + Number value = master.getValue(loc); + return value; + } + + /** + * 读取外围设备输入的数据 + * 功能码为:04 读取模拟量输入值,只能读的数字类型(3x)---30001 至 3xxxx – 模拟量输入 + * + * 举例子说明:S7-200 Smart PLC中,模拟量输入寄存器AIW16\AIW18,则对应 + * java中对应的address为:8\9 + * @param slaveId slaveId-从站编号-自行约定 + * @param offset 位置-预访问的地址-地址范围:0-55 + */ + public short[] readInputRegisters(ModbusMaster master,int slaveId, int offset, int numberOfBits) + throws ModbusTransportException, ErrorResponseException, ModbusInitException { + ReadInputRegistersRequest request = new ReadInputRegistersRequest(slaveId, offset, numberOfBits); + ReadInputRegistersResponse response = (ReadInputRegistersResponse) master.send(request); + return response.getShortData(); + } + + /** + * 批量读取 可以批量读取不同寄存器中数据 + */ + public static void batchRead(ModbusMaster master) throws ModbusTransportException, ErrorResponseException, ModbusInitException { + BatchRead batch = new BatchRead(); + batch.addLocator(0, BaseLocator.holdingRegister(1, 1, DataType.TWO_BYTE_INT_SIGNED)); + batch.addLocator(1, BaseLocator.inputStatus(1, 0)); + batch.setContiguousRequests(true); + BatchResults results = master.send(batch); + System.out.println("batchRead:" + results.getValue(0)); + System.out.println("batchRead:" + results.getValue(1)); + } + + + /** + * 批量读取 可以批量读取不同寄存器中数据 + */ + public static void batchReadTest(ModbusMaster master,int slaveId, int offset, int dataType) throws ModbusTransportException, ErrorResponseException, ModbusInitException { + BatchRead batch = new BatchRead(); +// BaseLocator loc = BaseLocator.holdingRegister(slaveId, offset, dataType); +// Number value = master.getValue(loc); + batch.addLocator(0, BaseLocator.holdingRegister(1, 2, DataType.TWO_BYTE_INT_SIGNED)); + batch.addLocator(1, BaseLocator.inputStatus(1, 0)); + batch.addLocator(2, BaseLocator.holdingRegister(slaveId, offset, dataType)); + batch.setContiguousRequests(true); + BatchResults results = master.send(batch); + System.out.println("batchRead:" + results.getValue(0)); + System.out.println("batchRead:" + results.getValue(1)); + System.out.println("batchRead:" + results.getValue(2)); + } + + + /** + * 数据重组 + * @param numberOfBits + * @param values + * @return + */ + private boolean[] valueRegroup(int numberOfBits, boolean[] values) { + boolean[] bs = new boolean[numberOfBits]; + int temp = 1; + for (boolean b : values) { + bs[temp - 1] = b; + temp++; + if (temp > numberOfBits) + break; + } + return bs; + } + +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/modbusTcp/utils/Modbus4jWriteUtils.java b/bolt-kernel/src/main/java/com/jiluo/bolt/modbusTcp/utils/Modbus4jWriteUtils.java new file mode 100644 index 0000000..db84d7d --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/modbusTcp/utils/Modbus4jWriteUtils.java @@ -0,0 +1,100 @@ +package com.jiluo.bolt.modbusTcp.utils; + +import com.serotonin.modbus4j.ModbusMaster; +import com.serotonin.modbus4j.exception.ErrorResponseException; +import com.serotonin.modbus4j.exception.ModbusInitException; +import com.serotonin.modbus4j.exception.ModbusTransportException; +import com.serotonin.modbus4j.locator.BaseLocator; +import com.serotonin.modbus4j.msg.*; +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/29/17:14 + * @Description: + */ +public class Modbus4jWriteUtils { + + /** + * 写单个(线圈)开关量数据 + * 功能码为:05,开关量输出点Q置位或复位,写入数据到真机的DO类型的寄存器上面,可以读写的布尔类型(0x) + * @param slaveId slave的ID + * @param writeOffset 位置-预访问的地址-地址范围:0-255 + * @param writeValue 值-置位则为1,复位则为0 + * @return 是否写入成功 + */ + public boolean writeCoil(ModbusMaster master,int slaveId, int writeOffset, boolean writeValue) + throws ModbusTransportException, ModbusInitException { + // 创建请求 + WriteCoilRequest request = new WriteCoilRequest(slaveId, writeOffset, writeValue); + // 发送请求并获取响应对象 + WriteCoilResponse response = (WriteCoilResponse) master.send(request); + return !response.isException(); + } + + /** + * 写多个开关量数据(线圈) + * 功能码为:0F,写多个开关量数据(线圈) + * @param slaveId slaveId + * @param startOffset 开始位置 + * @param bdata 写入的数据 + * @return 是否写入成功 + */ + public boolean writeCoils(ModbusMaster master,int slaveId, int startOffset, boolean[] bdata) + throws ModbusTransportException, ModbusInitException { + // 创建请求 + WriteCoilsRequest request = new WriteCoilsRequest(slaveId, startOffset, bdata); + // 发送请求并获取响应对象 + WriteCoilsResponse response = (WriteCoilsResponse) master.send(request); + return !response.isException(); + + } + + /*** + * 保持寄存器写单个 + * 功能码为:06,将数据写入至V存储器, 数据到真机,数据类型是Int,可以读写的数字类型(4x) + * @param slaveId slaveId + * @param writeOffset 开始位置 + * @param writeValue 写入的数据 + */ + public static boolean writeRegister(ModbusMaster master,int slaveId, int writeOffset, short writeValue) + throws ModbusTransportException, ModbusInitException { + // 创建请求对象 + WriteRegisterRequest request = new WriteRegisterRequest(slaveId, writeOffset, writeValue); + // 发送请求并获取响应对象 + WriteRegisterResponse response = (WriteRegisterResponse) master.send(request); + return !response.isException(); + + } + + /** + * 保持寄存器写入多个模拟量数据 + * 功能码为:16,将数据写入至多个V存储器,写入数据到真机,数据类型是short[],可以读写的数字类型(4x) + * @param slaveId modbus的slaveID + * @param startOffset 起始位置偏移量值 + * @param sdata 写入的数据 + * @return 返回是否写入成功 + */ + public boolean writeRegisters(ModbusMaster master,int slaveId, int startOffset, short[] sdata) + throws ModbusTransportException, ModbusInitException { + // 创建请求对象 + WriteRegistersRequest request = new WriteRegistersRequest(slaveId, startOffset, sdata); + // 发送请求并获取响应对象 + WriteRegistersResponse response = (WriteRegistersResponse) master.send(request); + return !response.isException(); + } + + /** + * 根据类型写数据(如:写入Float类型的模拟量、Double类型模拟量、整数类型Short、Integer、Long) + * + * @param value 写入值 + * @param dataType com.serotonin.modbus4j.code.DataType + */ + public static void writeHoldingRegister(ModbusMaster master,int slaveId, int offset, Number value, int dataType) + throws ModbusTransportException, ErrorResponseException, ModbusInitException { + // 类型 + BaseLocator locator = BaseLocator.holdingRegister(slaveId, offset, dataType); + master.setValue(locator, value); + } + +} diff --git a/bolt-kernel/src/main/java/com/jiluo/bolt/websocket/WebSocket.java b/bolt-kernel/src/main/java/com/jiluo/bolt/websocket/WebSocket.java new file mode 100644 index 0000000..c70bb9e --- /dev/null +++ b/bolt-kernel/src/main/java/com/jiluo/bolt/websocket/WebSocket.java @@ -0,0 +1,229 @@ +package com.jiluo.bolt.websocket; + +import com.alibaba.fastjson.JSONObject; +import com.jiluo.bolt.common.DetectMessage; +import com.jiluo.bolt.service.DefectService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.*; + + +@Slf4j +@Component +@ServerEndpoint("/websocket/{userId}") // 接口路径 ws://localhost:8081/websocket/userId; +public class WebSocket { + + private Session session; + + private String userId; + + private static Set webSockets = new CopyOnWriteArraySet<>(); + + private static Map sessionPool = new ConcurrentHashMap<>(); + + private static Map pointPool = new ConcurrentHashMap<>(); + + private static Map pointStatusPool = new ConcurrentHashMap<>(); + + private static WebSocketMQ webSocketMQ; + + @PostConstruct + public void init() { + webSocketMQ = new WebSocketMQ(); + webSocketMQ.startThread(); + } + + @PreDestroy + public void cleanup() { + webSocketMQ.stopThread(); + } + + @OnOpen + public void onOpen(Session session, @PathParam(value = "userId") String userId) { + try { + this.session = session; + this.userId = userId; + webSockets.add(this); + sessionPool.put(userId, session); + log.info(userId); + log.info("【websocket消息】有新的连接,总数为:" + webSockets.size()); + } catch (Exception e) { + } + } + + @OnClose + public void onClose() { + try { + webSockets.remove(this); + sessionPool.remove(this.userId); + pointPool.remove(this.userId); + log.info("【websocket消息】连接断开,总数为:" + webSockets.size()); + } catch (Exception e) { + log.error("[WebSocket] onClose:", e.getMessage(), e); + } + } + + @OnMessage + public void onMessage(String message) { + log.info("【websocket消息】有新的消息:" + message); + JSONObject jsonObject = JSONObject.parseObject(message); + if (jsonObject.getObject("status", Boolean.class)) { + String pointId = jsonObject.getString("pointId"); + pointPool.put(this.userId, pointId); + if (pointStatusPool.containsKey(pointId)){ + sendStatusMessage1(pointId, pointStatusPool.get(pointId)); + }else { + sendStatusMessage1(pointId, 0); + } + if (DefectService.TEMPERATUREMAP.containsKey(pointId)){ + sendTemperatureMessage(pointId,DefectService.TEMPERATUREMAP.get(pointId)); + } + + } else { + pointPool.remove(this.userId); + } + log.info(pointPool.toString()); + } + + @OnError + public void onError(Session session, Throwable error) { + log.error("[WebSocket] onError:", error.getMessage(), error); + } + + public void sendAllMessage(String message) { + for (WebSocket webSocket : webSockets) { + try { + if (webSocket.session.isOpen()) { + webSocket.session.getAsyncRemote().sendText(message); + } + } catch (Exception e) { + log.error("[WebSocket] sendAllMessage:", e.getMessage(), e); + } + } + } + + public void sendOneMessage(String userId, String message) { + Session session = sessionPool.get(userId); + if (session != null && session.isOpen()) { + try { + session.getAsyncRemote().sendText(message); + } catch (Exception e) { + log.error("[WebSocket] sendOneMessage:", e.getMessage(), e); + } + } + } + + public void sendMoreMessage(String[] userIds, String message) { + for (String userId : userIds) { + Session session = sessionPool.get(userId); + if (session != null && session.isOpen()) { + try { + session.getAsyncRemote().sendText(message); + } catch (Exception e) { + log.error("[WebSocket] sendMoreMessage:", e.getMessage(), e); + } + } + } + } + + // 发送实时检测消息 + public void sendBoltMessage(String pointId, List detects) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("title", "detect"); + jsonObject.put("data", detects); + webSocketMQ.addMessageToMq(DetectMessage.builder().pointId(pointId).jsonObject(jsonObject).build()); + } + + public void sendImgMessage(String pointId, List imgs) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("title", "img"); + jsonObject.put("imgs", imgs); + webSocketMQ.addMessageToMq(DetectMessage.builder().pointId(pointId).jsonObject(jsonObject).build()); + } + + public void sendTemperatureMessage(String pointId, Float value) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("title", "temperature"); + jsonObject.put("data", value); + webSocketMQ.addMessageToMq(DetectMessage.builder().pointId(pointId).jsonObject(jsonObject).build()); + } + + public void sendStatusMessage(String pointId, Integer value) { + pointStatusPool.put(pointId,value); + sendStatusMessage1(pointId,value); + } + + private void sendStatusMessage1(String pointId, Integer value) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("title", "detectStatus"); + jsonObject.put("status", value); + webSocketMQ.addMessageToMq(DetectMessage.builder().pointId(pointId).jsonObject(jsonObject).build()); + } + + private void sendMessage(Session session, String message) { + try { + session.getBasicRemote().sendText(message); + } catch (Exception e) { + log.error("[WebSocket] sendMessage:", e.getMessage(), e); + } + } + + private void sendMessage(Set webSockets, String message) { + for (WebSocket webSocket : webSockets) { + Session session = webSocket.session; + if (session != null && session.isOpen()) { + sendMessage(session, message); + } + } + } + + + + public class WebSocketMQ implements Runnable { + private BlockingQueue mq = new LinkedBlockingQueue<>(); + private volatile boolean running = false; + + public void startThread() { + running = true; + new Thread(this).start(); + } + + public void stopThread() { + running = false; + //Thread.currentThread().interrupt(); + } + + public void addMessageToMq(DetectMessage message) { + mq.add(message); + } + + @Override + public void run() { + try { + while (running) { + DetectMessage message = mq.take(); // 阻塞直到队列不为空 + for (WebSocket webSocket : webSockets) { + if (pointPool.containsKey(webSocket.userId) && pointPool.get(webSocket.userId).equals(message.getPointId())) { + Session session = webSocket.session; + if (session != null && session.isOpen()) { + sendMessage(session, message.getJsonObject().toJSONString()); + } + } + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("[WebSocketMQ]:" + e.getMessage(), e); + } + } + } +} diff --git a/bolt-web/classfinal-fatjar.jar b/bolt-web/classfinal-fatjar.jar new file mode 100644 index 0000000..56b97a7 Binary files /dev/null and b/bolt-web/classfinal-fatjar.jar differ diff --git a/bolt-web/pom.xml b/bolt-web/pom.xml new file mode 100644 index 0000000..2670e67 --- /dev/null +++ b/bolt-web/pom.xml @@ -0,0 +1,71 @@ + + + + bolt-server + com.jiluo.bolt + 0.0.1-SNAPSHOT + + 4.0.0 + + bolt-web + + + + + com.jiluo.bolt + bolt-kernel + 0.0.1-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-devtools + true + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + com.google.guava + guava + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + src/main/resources + + *.properties + *.xml + *.yml + **/*.so + **/*.dll + static/** + + + + + + + \ No newline at end of file diff --git a/bolt-web/proguard.cfg b/bolt-web/proguard.cfg new file mode 100644 index 0000000..5b852cd --- /dev/null +++ b/bolt-web/proguard.cfg @@ -0,0 +1,78 @@ +#jdk版本1.8 +#-target 1.8 +#不做收缩(不删除注释以及未被引用的代码) +-dontshrink +#不做优化(不变更代码实现逻辑) +-dontoptimize + +#-dontobfuscate +#-microedition + +#不使用大小写混合,混淆后的类名为小写 +-dontusemixedcaseclassnames + +#使用唯一的类名来混淆 +-useuniqueclassmembernames + +#允许访问并修改有修饰符的类和类的成员 +-allowaccessmodification + +#保持 包名不变 +-keeppackagenames + +#需要保持的属性:异常,内部类,注解等 +-keepattributes Exceptions,InnerClass,Signature,Deprecated,SourceFile,LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod + +#spring 相关的注解,不要混淆 +-keepclassmembers class * { + @org.springframework.** *; + @org.springframework.beans.factory.annotation.Autowired ; + @org.springframework.beans.factory.annotation.Autowired ; + @javax.annotation.PostConstruct *; + @javax.annotation.PreDestroy *; + @javax.annotation.Resource *; + @org.springframework.scheduling.annotation.Async ; +} + +#不混淆所有的get/set方法 +-keepclassmembers public class *{ + void set*(***); *** get*(); +} + +-keepnames interface ** {*;} +-keep interface * extends * {*;} +-keepparameternames +-keepclassmembers enum * {*;} + +# 保持启动类不变 +-keep public class com.jiluo.bolt.BoltServerApplication {*;} + + +#不混淆被Component等注解标记的类 +-keep @org.springframework.stereotype.Component class * {*;} +-keep @org.springframework.stereotype.Service class * {*;} +-keep @org.springframework.web.bind.annotation.RestController class * {*;} +-keep @org.springframework.context.annotation.Configuration class * {*;} +#-keep @org.aspectj.lang.annotation.Aspect class * {*;} + + + +-adaptclassstrings +#跳过非公共库的类 +-skipnonpubliclibraryclasses + + +#忽略警告 +-ignorewarnings +-dontnote +# 打印配置内容 +-printconfiguration + + +# 配置不混淆某些类 +-keep class org.slf4j.** {*;} + + + + + diff --git a/bolt-web/src/main/java/com/jiluo/bolt/BoltServerApplication.java b/bolt-web/src/main/java/com/jiluo/bolt/BoltServerApplication.java new file mode 100644 index 0000000..8e809f2 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/BoltServerApplication.java @@ -0,0 +1,19 @@ +package com.jiluo.bolt; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +@MapperScan("com.jiluo.bolt.mapper") +@EnableWebMvc +@SpringBootApplication +@EnableScheduling +public class BoltServerApplication { + + public static void main(String[] args) { + SpringApplication.run(BoltServerApplication.class, args); + } + +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/aspect/GlobalExceptionHandler.java b/bolt-web/src/main/java/com/jiluo/bolt/aspect/GlobalExceptionHandler.java new file mode 100644 index 0000000..b751578 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/aspect/GlobalExceptionHandler.java @@ -0,0 +1,65 @@ +package com.jiluo.bolt.aspect; + +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.exception.BoltException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.MissingRequestCookieException; +import org.springframework.web.bind.MissingRequestHeaderException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartException; + +import javax.servlet.http.HttpServletRequest; + +@Slf4j +@Controller +@ControllerAdvice +public class GlobalExceptionHandler { + + @ResponseBody + @ExceptionHandler(BoltException.class) + public Result errorHandler(BoltException e) { + return _innerHandler(e.getResult(), e, false); + } + + @ResponseBody + @ExceptionHandler({MissingServletRequestParameterException.class, MissingPathVariableException.class, MissingRequestCookieException.class, MissingRequestHeaderException.class, MissingServletRequestParameterException.class}) + public Result errorHandler(MissingServletRequestParameterException e) { + return _innerHandler(Result.ILLEGAL_ARGUMENT, e, false); + } + + + @ResponseBody + @ExceptionHandler({MultipartException.class}) + public Result errorHandler(MultipartException e) { + return _innerHandler(Result.NOT_SUPPORT, e, false); + } + + @ResponseBody + @ExceptionHandler + public Result errorHandler(Exception e) { + return _innerHandler(Result.ERROR, e, true); + } + + private Result _innerHandler(Result result, Exception e, boolean needExceptionTrace) { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + if (requestAttributes.getResponse() != null) { + requestAttributes.getResponse().setStatus(result.getCode()); + } + HttpServletRequest request = requestAttributes.getRequest(); + if (!needExceptionTrace) { + log.error("[GlobalExceptionHandler:" + request.getServletPath() + "] exception:" + e.getMessage()); + } else { + log.error("[GlobalExceptionHandler:" + request.getServletPath() + "] exception:" + e.getMessage(), e); + } + } + return result; + } +} \ No newline at end of file diff --git a/bolt-web/src/main/java/com/jiluo/bolt/aspect/GlobalRequestAspect.java b/bolt-web/src/main/java/com/jiluo/bolt/aspect/GlobalRequestAspect.java new file mode 100644 index 0000000..07b6cdc --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/aspect/GlobalRequestAspect.java @@ -0,0 +1,88 @@ +package com.jiluo.bolt.aspect; + +import com.alibaba.fastjson.JSON; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.exception.BoltException; +import com.jiluo.bolt.util.JwtUtils; +import com.jiluo.bolt.util.SessionHolder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +@Slf4j +@Configuration +@ControllerAdvice +public class GlobalRequestAspect implements HandlerInterceptor, WebMvcConfigurer { + + @Autowired + JwtUtils tokenUtil; + @Value("${jwt.config.refreshTime}") + private Long refreshTime; + @Value("${jwt.config.expiresTime}") + private Long expiresTime; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(this) + .addPathPatterns("/api/**") + .excludePathPatterns("/api/user/login") + .excludePathPatterns("/api/user/logout") + .excludePathPatterns("/api/config/selectSystemInfoById") + .excludePathPatterns("/api/camera/dataCallback") + .excludePathPatterns("/api/temperature/dataCallback") + ; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { + if (handler instanceof ResourceHttpRequestHandler) { + response.setStatus(Result.NOT_SUPPORT.getCode()); + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/json; charset=utf-8"); + response.getOutputStream().write(JSON.toJSONString(Result.NOT_SUPPORT).getBytes(StandardCharsets.UTF_8)); + return false; + } + + // 如果不是映射到方法直接通过,可以访问资源. + if (!(handler instanceof HandlerMethod)) { + return true; + } + + //为空就返回错误 + String token = request.getHeader("Authorization"); + if (null == token || "".equals(token.trim())) { + throw new BoltException(Result.NOT_LOGIN); + } + + Map map = tokenUtil.parseToken(token); + String uid = map.get("uid"); + String sid = map.get("sid"); + String clientVersion = map.get("clientVersion"); + String clientType = map.get("clientType"); + + long timeOfUse = System.currentTimeMillis() - Long.parseLong(map.get("timeStamp")); + + if (timeOfUse < refreshTime) { + SessionHolder.setUserSession(uid); + return true; + } else if (timeOfUse >= refreshTime && timeOfUse < expiresTime) { + response.setHeader("Authorization",tokenUtil.getToken(uid,sid,clientVersion,clientType)); + return true; + } else { + throw new BoltException(Result.NOT_LOGIN); + } + } +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/aspect/GlobalResponseAspect.java b/bolt-web/src/main/java/com/jiluo/bolt/aspect/GlobalResponseAspect.java new file mode 100644 index 0000000..af19572 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/aspect/GlobalResponseAspect.java @@ -0,0 +1,56 @@ +package com.jiluo.bolt.aspect; + +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson.support.config.FastJsonConfig; +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; + +import com.jiluo.bolt.constant.Constant; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.util.SessionHolder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import java.nio.charset.StandardCharsets; + +@Slf4j +@Configuration +@ControllerAdvice +public class GlobalResponseAspect implements ResponseBodyAdvice { + + @Override + public boolean supports(MethodParameter methodParameter, Class clazz) { + return true; + } + + @Bean + FastJsonHttpMessageConverter fastJsonHttpMessageConverters() { + FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); + FastJsonConfig jsonConfig = new FastJsonConfig(); + jsonConfig.setCharset(StandardCharsets.UTF_8); + jsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); + jsonConfig.setSerializerFeatures(SerializerFeature.BrowserCompatible); + jsonConfig.setSerializerFeatures(SerializerFeature.WriteEnumUsingToString); + converter.setFastJsonConfig(jsonConfig); + converter.setDefaultCharset(StandardCharsets.UTF_8); + converter.setSupportedMediaTypes(Constant.SUPPORTED_MEDIA_TYPES); + return converter; + } + + @Override + public Object beforeBodyWrite(Object result, MethodParameter methodParameter, MediaType mediaType, Class> clazz, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { + SessionHolder.clearSession(); + if (result instanceof Result) { + return result; + } + return new Result(ResultCode.success, result); + } +} \ No newline at end of file diff --git a/bolt-web/src/main/java/com/jiluo/bolt/config/WebSocketConfig.java b/bolt-web/src/main/java/com/jiluo/bolt/config/WebSocketConfig.java new file mode 100644 index 0000000..8e07c67 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/config/WebSocketConfig.java @@ -0,0 +1,27 @@ +package com.jiluo.bolt.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/27/9:26 + * @Description: + */ +@Configuration +@EnableWebSocket +public class WebSocketConfig { + /** + * 注入ServerEndpointExporter, + * 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint + */ + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } + +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/constant/Constant.java b/bolt-web/src/main/java/com/jiluo/bolt/constant/Constant.java new file mode 100644 index 0000000..0eadae1 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/constant/Constant.java @@ -0,0 +1,29 @@ +package com.jiluo.bolt.constant; + +import com.google.common.collect.Lists; +import org.springframework.http.MediaType; + +import java.util.List; + +public class Constant { + public static final String SESSION_USER_KEY = "user"; + + public static final List SUPPORTED_MEDIA_TYPES = Lists.newArrayList( + MediaType.APPLICATION_JSON, + MediaType.APPLICATION_ATOM_XML, + MediaType.APPLICATION_FORM_URLENCODED, + MediaType.APPLICATION_OCTET_STREAM, + MediaType.APPLICATION_PDF, + MediaType.APPLICATION_RSS_XML, + MediaType.APPLICATION_XHTML_XML, + MediaType.APPLICATION_XML, + MediaType.IMAGE_GIF, + MediaType.IMAGE_JPEG, + MediaType.IMAGE_PNG, + MediaType.TEXT_EVENT_STREAM, + MediaType.TEXT_HTML, + MediaType.TEXT_MARKDOWN, + MediaType.TEXT_PLAIN, + MediaType.TEXT_XML + ); +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/AlgorithmController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/AlgorithmController.java new file mode 100644 index 0000000..55a681a --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/AlgorithmController.java @@ -0,0 +1,309 @@ +package com.jiluo.bolt.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.algorithm.AlgorithmName; +import com.jiluo.bolt.algorithm.AlgorithmType; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.dto.AlgorithmConfigDto; +import com.jiluo.bolt.entity.dto.AlgorithmDto; +import com.jiluo.bolt.entity.dto.MotorGroupDto; +import com.jiluo.bolt.entity.dto.PointDto; +import com.jiluo.bolt.entity.po.*; +import com.jiluo.bolt.entity.vo.AlgorithmVo; +import com.jiluo.bolt.service.*; +import com.jiluo.bolt.util.SnowFlakeUtil; +import com.jiluo.bolt.util.ToStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 算法信息表(algorithm)表控制层 + * @author Fangy + * @since 2023-04-17 13:26:11 + */ +@RestController +@RequestMapping("api/algorithm") +public class AlgorithmController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(AlgorithmController.class); + /** + * 服务对象 + */ + @Resource + private PointService pointService; + + @Resource + private MotorGroupService motorGroupService; + + @Resource + private PowerStationService powerStationService; + + @Resource + private AlgorithmTempleteService algorithmTempleteService; + + @Resource + private AlgorithmService algorithmService; + + @Resource + private ConfigService configService; + + /** + * 检测算法参数查询 + * @param + * @return + */ + @PostMapping("/selectParams") + public Result selectParams(){ + List algorithmConfigDtos = new ArrayList<>(); + List pointList = pointService.getAll(); + List pointIds = pointList.stream().map(Point::getPointId).collect(Collectors.toList()); + pointList.stream().map(Point::getConfig).filter(x -> StringUtils.isNotBlank(x)).distinct().forEach(x-> {algorithmConfigDtos.add(JSONObject.parseObject(x,AlgorithmConfigDto.class));}); + algorithmConfigDtos.forEach(x->x.getPointIdList().retainAll(pointIds)); + return new Result(ResultCode.success,algorithmConfigDtos); + } + + /** + * 修改检测算法参数设置 + * @param + * @return + */ + @PostMapping("/updateParams") + public Result updateParams(@RequestBody List algorithmConfigDtos){ + if (algorithmConfigDtos.stream().anyMatch(dto -> dto.getBoltThreshold() == null || + dto.getLineThreshold() == null || + dto.getPointIdList() == null || + dto.getPointIdList().isEmpty())){ + return new Result(ResultCode.illegal_argument); + } + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.set("config",""); + pointService.update(updateWrapper); + algorithmConfigDtos.forEach(algorithmConfigDto -> { + if (algorithmConfigDto.getAlgorithmConfigId() == null){ + algorithmConfigDto.setAlgorithmConfigId(String.valueOf(SnowFlakeUtil.getDefaultSnowFlakeId())); + algorithmConfigDto.getPointIdList().forEach(e-> { + pointService.updateConfig(e, JSON.toJSONString(algorithmConfigDto));}); + }else if(algorithmConfigDto.getAlgorithmConfigId() != null){ + algorithmConfigDto.getPointIdList().forEach(e-> { + pointService.updateConfig(e, JSON.toJSONString(algorithmConfigDto));}); + }}); + return new Result(ResultCode.success,"设置成功"); + } + + /** + * 条件查询数据 + */ + @PostMapping("/select") + public Result select(@RequestBody AlgorithmDto algorithmDto) { + Integer total = algorithmTempleteService.selectTotal(algorithmDto); + if (total.equals(0)){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("total",0); + jsonObject.put("data",null); + return new Result(ResultCode.success,jsonObject); + } + List algorithmTempletes = algorithmTempleteService.select(algorithmDto); + List algorithmVos = new ArrayList<>(); + algorithmTempletes.forEach(item->{ + algorithmVos.add(AlgorithmVo.builder().AlgorithmId(item.getBizId()) + .name(item.getName()) + .source(item.getSource()) + .group_point_num(algorithmService.getPointCount(item.getBizId())) + .gmtCreate(item.getGmtCreate()) + .build());}); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("total",total); + jsonObject.put("data",algorithmVos); + return new Result(ResultCode.success,jsonObject); + } + + /** + * 上传算法文件 + */ + @PostMapping("/upload") + public Result upload(@RequestParam("file") MultipartFile file) throws IOException { + if (file.isEmpty()) { + return new Result(ResultCode.illegal_argument,"上传的文件不能为空"); + } + String originalFilename = file.getOriginalFilename(); + String path = configService.selectByBizId("algorithm_model_dir").getValue(); + logger.info("上传的算法路径:"+ path); + File dataFile = new File(path); + if (!dataFile.exists()) { + dataFile.mkdirs(); + } + file.transferTo(new File(dataFile, originalFilename)); + return new Result(ResultCode.success,"上传成功"); + } + + /** + * 算法应用范围 + */ + @PostMapping("/selectDetail") + public Result selectRange(@RequestBody AlgorithmDto algorithmDto) { + JSONObject detail = new JSONObject(); + AlgorithmTemplete algorithmTemplete = algorithmTempleteService.select(algorithmDto).get(0); + detail.put("algorithmId",algorithmTemplete.getBizId()); + detail.put("algorithmName",algorithmTemplete.getName()); + detail.put("algorithmFileName",JSONObject.parseObject(algorithmTemplete.getConfig()).getString("detect_threshold_model")); + detail.put("source",algorithmTemplete.getSource()); + List powerStations = powerStationService.getAll(); + List motorGroups = motorGroupService.getAll(); + List points = pointService.getAll(); + List algorithms = algorithmService.select(algorithmDto); + List result = new ArrayList<>(); + points.stream().forEach(item->{ + Algorithm algorithm = algorithms.stream().filter(x->x.getPoint().equals(item.getPointId())).findAny().orElse(null); + if (algorithm != null && item.getPointId().equals(algorithm.getPoint()) && algorithmDto.getAlgorithmId().equals(algorithm.getAlgorithm())){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("powerStationId",item.getPowerStation()); + jsonObject.put("motorGroupId",item.getMotorGroup()); + jsonObject.put("pointId",item.getPointId()); + jsonObject.put("powerStation", powerStations.stream().filter(x -> x.getPowerStationId().equals(item.getPowerStation())).findAny().orElse(null).getName()); + jsonObject.put("motorGroup",motorGroups.stream().filter(x -> x.getMotorGroupId().equals(item.getMotorGroup())).findAny().orElse(null).getName()); + jsonObject.put("point",item.getName()); + jsonObject.put("flag",1); + result.add(jsonObject); + }}); + detail.put("ranges",result); + return new Result(ResultCode.success,detail); + } + + /** + * 检测点 + */ + @PostMapping("/selectPoints") + public Result selectPoints(@RequestBody AlgorithmDto algorithmDto) { + List powerStations = powerStationService.getAll(); + List motorGroups = motorGroupService.select(MotorGroupDto.builder().powerStationId(algorithmDto.getPowerStationId()).build()); + List points = pointService.select(PointDto.builder().powerStationId(algorithmDto.getPowerStationId()).motorGroupId(algorithmDto.getMotorGroupId()).build()); + List algorithms = algorithmService.select(new AlgorithmDto()); + List result = new ArrayList<>(); + points.stream().forEach(item->{ + Algorithm algorithm = algorithms.stream().filter(x->x.getPoint().equals(item.getPointId())).findAny().orElse(null); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("powerStationId",item.getPowerStation()); + jsonObject.put("motorGroupId",item.getMotorGroup()); + jsonObject.put("pointId",item.getPointId()); + jsonObject.put("powerStation", powerStations.stream().filter(x -> x.getPowerStationId().equals(item.getPowerStation())).findAny().orElse(null).getName()); + jsonObject.put("motorGroup",motorGroups.stream().filter(x -> x.getMotorGroupId().equals(item.getMotorGroup())).findAny().orElse(null).getName()); + jsonObject.put("point",item.getName()); + //0-可选,1-当前选中,2-不可选(其他算法选中) + if (algorithm == null){ + jsonObject.put("flag",0); + }else if (item.getPointId().equals(algorithm.getPoint()) && algorithmDto.getAlgorithmId().equals(algorithm.getAlgorithm())){ + jsonObject.put("flag",1); + }else if (item.getPointId().equals(algorithm.getPoint()) && !algorithmDto.getAlgorithmId().equals(algorithm.getAlgorithm())){ + jsonObject.put("flag",2); + } + result.add(jsonObject); + }); + return new Result(ResultCode.success,result); + } + /** + * 删除检测点绑定 + */ + @PostMapping("/deletePoint") + public Result deletePoint(@RequestBody AlgorithmDto algorithmDto) { + Map columnMap = new HashMap<>(); + columnMap.put("algorithm",algorithmDto.getAlgorithmId()); + columnMap.put("point",algorithmDto.getPointId()); + if (!algorithmService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + return new Result(ResultCode.success,"删除成功"); + } + + /** + * 新增数据 + * + */ + @PostMapping("/add") + public Result insert(@RequestBody @Valid AlgorithmDto algorithmDto) { + if (StringUtils.isBlank(algorithmDto.getSource()) || StringUtils.isBlank(algorithmDto.getAlgorithmName()) || StringUtils.isBlank(algorithmDto.getAlgorithmFileName())){ + return new Result(ResultCode.illegal_argument); + } + algorithmDto.setAlgorithmId(algorithmTempleteService.add(algorithmDto)); + algorithmService.add(algorithmDto); + return new Result(ResultCode.success,"添加成功"); + } + + /** + * 修改数据 + */ + @PostMapping("/update") + public Result update(@RequestBody @Valid AlgorithmDto algorithmDto) { + if (!StringUtils.isNotBlank(algorithmDto.getAlgorithmId())){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + if (!algorithmTempleteService.updateByBizId(algorithmDto)){ + return new Result(ResultCode.operate_failure,"修改失败"); + } + Map columnMap = new HashMap<>(); + columnMap.put("algorithm",algorithmDto.getAlgorithmId()); + algorithmService.removeByMap(columnMap); + algorithmService.add(algorithmDto); + return new Result(ResultCode.success,"修改成功"); + } + + /** + * 删除数据 + */ + @PostMapping("/del") + public Result delete(@RequestBody AlgorithmDto algorithmDto) { + Map columnMap = new HashMap<>(); + columnMap.put("biz_id",algorithmDto.getAlgorithmId()); + Map columnMap2 = new HashMap<>(); + columnMap2.put("algorithm",algorithmDto.getAlgorithmId()); + algorithmService.removeByMap(columnMap2); + if (!algorithmTempleteService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + return new Result(ResultCode.success,"删除成功"); + } + + /** + * 算法来源 + */ + @PostMapping("/source") + public Result source() { + List result = new ArrayList<>(); + Arrays.stream(AlgorithmType.values()).forEach(x->{ + if (x.equals(AlgorithmType.self_algorithm)) result.add("自建算法"); + else if (x.equals(AlgorithmType.mock_algorithm)) result.add("MOCK算法"); + }); + return new Result(ResultCode.success,result); + } + + /** + * 算法 + */ + @PostMapping("/name") + public Result name(@RequestBody AlgorithmDto algorithmDto) { + if (algorithmDto.getSource()==null || StringUtils.isBlank(algorithmDto.getSource())){ + return new Result(ResultCode.illegal_argument); + } + if (algorithmDto.getSource().equals("自建算法")){ + return new Result(ResultCode.success, AlgorithmName.getName(AlgorithmType.self_algorithm)); + }else if (algorithmDto.getSource().equals("MOCK算法")){ + return new Result(ResultCode.success, AlgorithmName.getName(AlgorithmType.mock_algorithm)); + }else { + return new Result(ResultCode.illegal_argument); + } + } +} + diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/AnalysisDataController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/AnalysisDataController.java new file mode 100644 index 0000000..656dca0 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/AnalysisDataController.java @@ -0,0 +1,108 @@ +package com.jiluo.bolt.controller; + +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.dto.DetectDto; +import com.jiluo.bolt.entity.dto.PointDto; +import com.jiluo.bolt.entity.po.Defect; +import com.jiluo.bolt.entity.po.Point; +import com.jiluo.bolt.entity.vo.DefectVo; +import com.jiluo.bolt.service.DefectService; +import com.jiluo.bolt.service.PointService; +import org.apache.commons.lang3.BooleanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/26/14:40 + * @Description: + */ +@RestController +@RequestMapping("api/analysis") +public class AnalysisDataController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(AnalysisDataController.class); + + @Resource + DefectService defectService; + @Resource + PointService pointService; + + @PostMapping("/features") + public Result features(@RequestBody DetectDto detectDto){ + Point point = pointService.getByBizId(detectDto.getPointId()); + Map permissionMap = new HashMap<>(); + if (point.getBoltDetect()==0){ + permissionMap.put(DefectType.bolt.name(),DefectType.bolt.getDesc()); + } + if (point.getLineDetect()==0){ + permissionMap.put(DefectType.line.name(),DefectType.line.getDesc()); + } + if (point.getPoleOpenDetect()==0){ + permissionMap.put(DefectType.pole.name(),DefectType.pole.getDesc()); + } + if (point.getPointTempDetect()==0){ + permissionMap.put(DefectType.temperature.name(),DefectType.temperature.getDesc()); + } + return new Result(ResultCode.success,permissionMap); + } + + /** + * 趋势图数据 + * @RequestParam(required = false) String pointId, + * @RequestParam(required = false) List features, + * @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") String startTime, + * @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") String endTime + */ + @PostMapping("/chartData") + public Result chartData(@RequestBody DetectDto detectDto){ + if (detectDto.getPointId()==null || detectDto.getFeatures()==null || detectDto.getStartTime()==null || detectDto.getEndTime()==null ||detectDto.getZone()==null){ + return new Result(ResultCode.illegal_argument); + } + LocalDate endTime = LocalDate.parse(detectDto.getEndTime()).plusDays(1); + List defects = new ArrayList<>(); + detectDto.getFeatures().forEach(e->{ + if (e.equals(DefectType.temperature.name())){ + defects.addAll(defectService.chartData(detectDto.getPointId(), DefectType.temperature.name(), 1 , detectDto.getStartTime(), endTime.toString())); + }else { + defects.addAll(defectService.chartData(detectDto.getPointId(), e, detectDto.getZone() , detectDto.getStartTime(), endTime.toString())); + + } + }); + Point point = pointService.getByBizId(detectDto.getPointId()); + Map permissionMap = new HashMap<>(); + permissionMap.put("bolt",point.getBoltDetect()==0); + permissionMap.put("line",point.getLineDetect()==0); + permissionMap.put("pole",point.getPoleOpenDetect()==0); + permissionMap.put("temperature",point.getPointTempDetect()==0); + List _defects = defects.stream().filter(x->permissionMap.get(x.getType())).map(defect -> { + DefectType _type = DefectType.toDefectType(defect.getType()); + BigDecimal value = (_type == DefectType.bolt || _type == DefectType.temperature) ? BigDecimal.valueOf(defect.getValue() / 100.0).setScale(2, RoundingMode.HALF_UP) : (_type == DefectType.line ? BigDecimal.valueOf(defect.getValue() / 1000.0).setScale(3, RoundingMode.HALF_UP) : BigDecimal.valueOf(defect.getValue())); + return DefectVo.builder() + .time(defect.getGmtModify()) + .type(_type) + .alarm(BooleanUtils.toBoolean(defect.getAlarm())) + .zone(defect.getZone()) + .position(defect.getPosition()) + .value(value) + .build(); + }) + .filter(x -> x.getZone() != null && x.getType() != null && x.getPosition() != null && x.getValue() != null && x.getTime() != null) + .sorted(Comparator.comparing(DefectVo::getTime).thenComparing(DefectVo::getZone).thenComparing(DefectVo::getType).thenComparing(DefectVo::getPosition)).collect(Collectors.toList()); + return new Result(ResultCode.success,_defects.stream().collect(Collectors.groupingBy(DefectVo::getType))); + } + +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/CameraController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/CameraController.java new file mode 100644 index 0000000..10eff28 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/CameraController.java @@ -0,0 +1,36 @@ +package com.jiluo.bolt.controller; + +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.common.DetectJob; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.engine.DataCallBack; +import com.jiluo.bolt.engine.Engine; +import com.jiluo.bolt.entity.dto.DetectResultDto; +import com.jiluo.bolt.service.JobService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/07/13/13:13 + * @Description: + */ +@RestController +@RequestMapping("api") +public class CameraController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(CameraController.class); + + @PostMapping("/camera/dataCallback") + public Result dataCallback(@RequestBody DetectResultDto detectResultDto){ + Engine.callback(detectResultDto.getJobId(),detectResultDto.getDetectList()); + return new Result(ResultCode.success); + } +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/ConfigController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/ConfigController.java new file mode 100644 index 0000000..b2bf731 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/ConfigController.java @@ -0,0 +1,306 @@ +package com.jiluo.bolt.controller; + + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.dto.PowerStationDto; +import com.jiluo.bolt.entity.dto.SystemInfoConfigDto; +import com.jiluo.bolt.entity.dto.ThemeConfigDto; +import com.jiluo.bolt.entity.po.Config; +import com.jiluo.bolt.entity.po.PowerStation; +import com.jiluo.bolt.entity.vo.SystemInfoConfigVo; +import com.jiluo.bolt.service.ConfigService; +import com.jiluo.bolt.service.PowerStationService; +import com.jiluo.bolt.service.VersionService; +import com.jiluo.bolt.util.ImgUtils; +import com.jiluo.bolt.util.SnowFlakeUtil; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 系统配置表(config)表控制层 + * @author Fangy + * @since 2023-04-18 16:06:24 + */ +@RestController +@RequestMapping("api/config") +public class ConfigController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(ConfigController.class); + /** + * 服务对象 + */ + @Resource + private ConfigService configService; + + @Resource + private VersionService versionService; + + @Resource + private PowerStationService powerStationService; + + @Resource + ImgUtils imgUtils; + + /** + * 版本信息查询 + */ + @PostMapping("/selectVersion") + public Result selectVersion() { + return new Result(ResultCode.success,versionService.select()); + } + + /** + * 查询参数 + */ + @PostMapping("/select") + public Result select(@RequestBody Config config) { + return new Result(ResultCode.success,configService.selectByBizId(config.getConfigId())); + } + + /** + * 更新参数 + */ + @PostMapping("/update") + public Result update(@RequestBody Config config) { + if (config.getValue()==null){ + return new Result(ResultCode.illegal_argument); + } + return new Result(ResultCode.success,configService.updateByBizId(config.getConfigId(),config.getValue())); + } + + /** + * 上传文件 + */ + @PostMapping("/upload") + public Result upload(@RequestParam("file") MultipartFile file) throws IOException { + if (file.isEmpty()) { + return new Result(ResultCode.illegal_argument,"上传的文件不能为空"); + } + String originalFilename = file.getOriginalFilename(); + String path = configService.selectByBizId("img_logo_dir").getValue(); + logger.info("上传的文件路径:"+ path); + File dataFile = new File(path); + if (!dataFile.exists()) { + dataFile.mkdirs(); + } + file.transferTo(new File(dataFile, originalFilename)); + return new Result(ResultCode.success,"上传成功"); + } + + /** + * 系统信息查询 + */ + @PostMapping("/selectSystemInfo") + public Result selectSystemInfo() { + Config configDto = new Config(); + List systemInfoConfigVos = new ArrayList<>(); + configService.select(configDto.setCategory("系统信息管理")).forEach(config -> { + JSONObject jsonObject = JSON.parseObject(config.getValue()); + try { + if (StringUtils.isNotBlank(jsonObject.getString("logo")) && StringUtils.isNotBlank(jsonObject.getString("power_station"))){ + String encodeBase64 = imgUtils.ToBase64Logo(configService.selectByBizId("img_logo_dir").getValue(),jsonObject.getString("logo")); + List powerStations = new ArrayList<>(); + JSONObject.parseArray(jsonObject.getString("power_station"),String.class).stream().forEach(item->{ + PowerStation p = powerStationService.getByBizId(item); + if (p!=null){ + JSONObject powerStation = new JSONObject(); + powerStation.put("powerStationId",item); + powerStation.put("powerStationName",p.getName()); + powerStations.add(powerStation); + }}); + systemInfoConfigVos.add(SystemInfoConfigVo.builder().systemInfoId(config.getConfigId()) + .systemInfoName(jsonObject.getString("name")) + .logoFileName(jsonObject.getString("logo")) + .logoImg(encodeBase64) + .reservedField(jsonObject.getString("reserved_field")) + .powerStations(powerStations).build());} + } catch (Exception e) { + logger.error("[ConfigController] selectSystemInfo:", e.getMessage(), e); + }}); + return new Result(ResultCode.success,systemInfoConfigVos); + } + + /** + * 系统信息修改 + */ + @PostMapping("/updateSystemInfo") + public Result updateSystemInfo(@RequestBody @Valid List systemInfoConfigDtos) { + List powerStations = new ArrayList<>(); + AtomicInteger i = new AtomicInteger(1); + systemInfoConfigDtos.stream().map(SystemInfoConfigDto::getPowerStations).forEach( x-> powerStations.addAll(x) ); + if (powerStations.size() != powerStations.stream().distinct().count()){ + return new Result(ResultCode.operate_failure,"修改失败:应用电站id重复"); + } + systemInfoConfigDtos.stream().forEach(systemInfoConfigDto -> { + if (StringUtils.isBlank(systemInfoConfigDto.getSystemInfoId())){ + Config configDto = new Config(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("logo",systemInfoConfigDto.getLogoFileName()); + jsonObject.put("name",systemInfoConfigDto.getSystemInfoName()); + jsonObject.put("power_station",systemInfoConfigDto.getPowerStations()); + jsonObject.put("reserved_field",systemInfoConfigDto.getReservedField()); + configDto.setValue(jsonObject.toJSONString()) + .setDescription("自定义信息策略"+i.getAndIncrement()) + .setCategory("系统信息管理") + .setType(jsonObject.getClass().getSimpleName()); + configService.add(configDto,"self_system_information_policy_"); + }else { + Config configDto = new Config(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("logo",systemInfoConfigDto.getLogoFileName()); + jsonObject.put("name",systemInfoConfigDto.getSystemInfoName()); + jsonObject.put("power_station",systemInfoConfigDto.getPowerStations()); + jsonObject.put("reserved_field",systemInfoConfigDto.getReservedField()); + configDto.setConfigId(systemInfoConfigDto.getSystemInfoId()) + .setValue(jsonObject.toJSONString()) + .setCategory("系统信息管理") + .setType(jsonObject.getClass().getTypeName()); + if (systemInfoConfigDto.getSystemInfoId().equals("default_system_information_policy")){ + configDto.setDescription("默认系统信息策略"); + }else { + configDto.setDescription("自定义信息策略"+i.getAndIncrement()); + } + configService.updateByBizId(configDto); + }}); + return new Result(ResultCode.success,"修改成功"); + } + + /** + * 系统信息删除 + */ + @PostMapping("/deleteSystemInfo") + public Result deleteSystemInfo(@RequestBody SystemInfoConfigDto systemInfoConfigDto) { + Map columnMap = new HashMap<>(); + columnMap.put("biz_id",systemInfoConfigDto.getSystemInfoId()); + if (!configService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + return new Result(ResultCode.success,"删除成功"); + } + + /** + * 主题策略查询 + */ + @PostMapping("/selectTheme") + public Result selectTheme() { + Config configDto = new Config(); + List results = new ArrayList<>(); + configService.select(configDto.setCategory("系统主题管理")).forEach(config -> { + ThemeConfigDto themeConfig = JSON.parseObject(config.getValue(),ThemeConfigDto.class); + List powerStations = new ArrayList<>(); + themeConfig.getPowerStationList().stream().forEach(x->{ + PowerStation p = powerStationService.getByBizId(x); + if (p!=null){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("powerStationId",x); + jsonObject.put("powerStationName",p.getName()); + powerStations.add(jsonObject); + }}); + JSONObject result = JSON.parseObject(config.getValue()); + result.put("themeId",config.getConfigId()); + result.put("themeName",config.getDescription()); + result.put("powerStationList",powerStations); + results.add(result);}); + return new Result(ResultCode.success,results); + } + + /** + * 主题策略修改 + */ + @PostMapping("/updateTheme") + public Result updateTheme(@RequestBody @Valid List themeConfigDtos) { + List powerStations = new ArrayList<>(); + themeConfigDtos.stream().map(ThemeConfigDto::getPowerStationList).forEach( x-> powerStations.addAll(x) ); + if (powerStations.size() != powerStations.stream().distinct().count()){ + return new Result(ResultCode.operate_failure,"修改失败:应用电站id重复"); + } + themeConfigDtos.stream().forEach(themeConfigDto->{ + if (!StringUtils.isNotBlank(themeConfigDto.getThemeId())){ + Config configDto = new Config(); + configDto.setConfigId("self_theme_policy_"+ SnowFlakeUtil.getDefaultSnowFlakeId()) + .setValue(JSON.toJSONString(themeConfigDto)) + .setDescription(themeConfigDto.getThemeName()) + .setCategory("系统主题管理") + .setType(JSONObject.class.getSimpleName()); + configService.add(configDto,"self_theme_policy_"); + } else{ + Config configDto = new Config(); + configDto.setConfigId(themeConfigDto.getThemeId()) + .setValue(JSON.toJSONString(themeConfigDto)) + .setDescription(themeConfigDto.getThemeName()) + .setCategory("系统主题管理") + .setType(JSONObject.class.getSimpleName()); + configService.updateByBizId(configDto); + }}); + return new Result(ResultCode.success,"修改成功"); + } + + /** + * 主题策略删除 + */ + @PostMapping("/deleteTheme") + public Result deleteTheme(@RequestBody ThemeConfigDto themeConfigDto) { + Map columnMap = new HashMap<>(); + columnMap.put("biz_id",themeConfigDto.getThemeId()); + if (!configService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + return new Result(ResultCode.success,"删除成功"); + } + + @PostMapping("/selectSystemInfoById") + public Result selectSystemInfoById(@RequestBody PowerStationDto powerStationDto) throws Exception { + Config systemInfo = new Config(); + Config systemTheme = new Config(); + if (powerStationDto == null || StringUtils.isBlank(powerStationDto.getPowerStationId())){ + List powerStations = powerStationService.getAll(); + if (powerStations!=null && powerStations.size()!=0){ + powerStationDto.setPowerStationId(powerStations.get(0).getPowerStationId()); + Config configDto = new Config(); + systemInfo = configService.select(configDto.setCategory("系统信息管理")).stream().filter(config -> config.getValue().contains(powerStationDto.getPowerStationId())).findFirst().orElse(null); + if (systemInfo == null){ + systemInfo = configService.selectByBizId("default_system_information_policy"); + } + systemTheme = configService.select(configDto.setCategory("系统主题管理")).stream().filter(config -> config.getValue().contains(powerStationDto.getPowerStationId())).findFirst().orElse(null); + if (systemTheme == null){ + systemTheme = configService.selectByBizId("default_theme_policy1"); + } + } + else { + systemInfo = configService.selectByBizId("default_system_information_policy"); + systemTheme = configService.selectByBizId("default_theme_policy1"); + } + } + + JSONObject systemInfoConfigVo = new JSONObject(); + JSONObject systemThemeVo = JSON.parseObject(systemTheme.getValue()); + JSONObject jsonObject = JSON.parseObject(systemInfo.getValue()); + systemThemeVo.remove("powerStationList"); + String encodeBase64 = imgUtils.ToBase64Logo(configService.selectByBizId("img_logo_dir").getValue(), jsonObject.getString("logo")); + systemInfoConfigVo.put("logoImg",encodeBase64); + systemInfoConfigVo.put("systemInfoName",jsonObject.getString("name")); + systemInfoConfigVo.put("reservedField",jsonObject.getString("reserved_field")); + JSONObject result = new JSONObject(); + result.put("systemInfo",systemInfoConfigVo); + result.put("theme",systemThemeVo); + return new Result(ResultCode.success,result); + } + +} + diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/DetectController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/DetectController.java new file mode 100644 index 0000000..496f738 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/DetectController.java @@ -0,0 +1,352 @@ +package com.jiluo.bolt.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.algorithm.AlgorithmName; +import com.jiluo.bolt.common.*; +import com.jiluo.bolt.device.DeviceStatus; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.engine.*; +import com.jiluo.bolt.entity.dto.DetectDto; +import com.jiluo.bolt.entity.po.*; +import com.jiluo.bolt.entity.vo.DefectVo; +import com.jiluo.bolt.service.*; +import com.jiluo.bolt.util.SessionHolder; +import com.jiluo.bolt.util.SnowFlakeUtil; +import com.jiluo.bolt.util.SystemDateUtils; +import com.jiluo.bolt.websocket.WebSocket; +import io.netty.util.HashedWheelTimer; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 检测页面(Detect) + * + * @author Fangy + * @since 2023-04-17 13:26:12 + */ +@RestController +@RequestMapping("api/detect") +@EnableScheduling +public class DetectController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(DetectController.class); + + private static CompletableFuture CurrentJob; + + private final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm"); + + @Value("${defect_work_dir}") + private String defect_work_dir; + + /** + * 服务对象 + */ + @Resource + private JobService jobService; + + @Resource + private DetectService detectService; + + @Resource + private DefectService defectService; + + @Resource + private AlarmService alarmService; + + @Resource + private MotorGroupService motorGroupService; + + @Resource + private PointService pointService; + + @Resource + private DeviceService deviceService; + + @Resource + private AlgorithmService algorithmService; + + @Resource + private WebSocket webSocket; + + private static HashedWheelTimer hashedWheelTimer = new HashedWheelTimer(100, TimeUnit.MILLISECONDS, 256); + + /** + * 根据选中检测点,获取实时检测的job + */ + @PostMapping("/getRealtimeJob") + public Result getJob(@RequestBody DetectDto detectDto) { + Job job = jobService.getRealtimeJob(detectDto.getPointId(), DetectType.BOLT_AND_LINE.getProduct()); + return new Result(ResultCode.success, job); + } + + /** + * 根据选中检测点,获取实时检测的job + */ + @PostMapping("/getImg") + public Result getImg(@RequestBody DetectDto detectDto) { + return new Result(ResultCode.success, detectService.getImg(detectDto.getJobId())); + } + + /** + * 根据选中磁极,检测结果数据 + */ + @PostMapping("/query") + public Result selectRealTimeData(@RequestBody DetectDto detectDto) { + + List defects = defectService.getRealTimeData(detectDto.getJobId()); + + JSONObject jsonObject = JSON.parseObject(jobService.getByBizId(detectDto.getJobId()).getConfig()); + + Map permissionMap = new HashMap<>(); + + permissionMap.put("bolt", jsonObject.getBooleanValue("bolt")); + permissionMap.put("line", jsonObject.getBooleanValue("line")); + permissionMap.put("pole", jsonObject.getBooleanValue("pole")); + permissionMap.put("temperature", jsonObject.getBooleanValue("temp")); + + List _defects = defects.stream().filter(x -> permissionMap.get(x.getType())).map(defect -> { + DefectType _type = DefectType.toDefectType(defect.getType()); + BigDecimal value = (_type == DefectType.bolt || _type == DefectType.temperature) ? BigDecimal.valueOf(defect.getValue() / 100.0).setScale(2, RoundingMode.HALF_UP) : (_type == DefectType.line ? BigDecimal.valueOf(defect.getValue() / 1000.0).setScale(3, RoundingMode.HALF_UP) : BigDecimal.valueOf(defect.getValue())); + return DefectVo.builder().type(_type).alarm(BooleanUtils.toBoolean(defect.getAlarm())).zone(defect.getZone()).position(defect.getPosition()).value(value).img(defect.getData()).build(); + }).filter(x -> x.getZone() != null && x.getType() != null && x.getPosition() != null && x.getValue() != null).sorted(Comparator.comparing(DefectVo::getZone).thenComparing(DefectVo::getType).thenComparing(DefectVo::getPosition)).collect(Collectors.toList()); + + return new Result(ResultCode.success, _defects.stream().collect(Collectors.groupingBy(DefectVo::getZone))); + } + + /** + * 告警信息 + */ + @PostMapping("/alarm") + public Result selectAlarm() { + return new Result(ResultCode.success, alarmService.getAll()); + } + + /** + * 手动检测 + */ + @PostMapping("/manualDetection") + public Result manualDetection(@RequestBody DetectDto detectDto) { + + Point point = pointService.getByBizId(detectDto.getPointId()); + + Device camera = deviceService.selectByPoint(point.getPointId()).stream().filter(x -> x.getType().equals(DeviceType.camera.name())).findAny().orElse(null); + + Algorithm algorithm = algorithmService.getByPoint(point.getPointId()); + + if (point.getEnableDetect().equals(1)) + { + addAlarm(point,"检测点未开启检测权限!"); + return new Result(ResultCode.operate_failure, "检测点未开启检测权限"); + } + + if (camera == null || camera.getStatus().equals(0)) + { + addAlarm(point,"当前检测点相机设备未配置或已停用!"); + return new Result(ResultCode.operate_failure, "当前检测点相机设备未配置或已停用"); + } + + if (algorithm == null) + { + addAlarm(point,"当前检测点算法文件未配置!"); + return new Result(ResultCode.operate_failure, "当前检测点算法文件未配置"); + } + + String job_id = "job_" + SystemDateUtils.getStrYMD() + "_" + SnowFlakeUtil.getDefaultSnowFlakeId(); + + AlgorithmConfig algorithmConfig = JSONObject.parseObject(point.getConfig(), AlgorithmConfig.class); + + JSONObject cameraConfig = JSON.parseObject(camera.getConfig()); + + JSONObject algorithmTempConfig = JSON.parseObject(algorithm.getConfig()); + + if (camera.getTempThreshold() == null) { + addAlarm(point,"检测点温度检测参数缺失!"); + return new Result(ResultCode.operate_failure, "检测点温度检测参数缺失!"); + } + + Double detect_threshold_temperature = JSON.parseObject(camera.getTempThreshold()).getDoubleValue("tempThreshold"); + if (detect_threshold_temperature == null || StringUtils.isBlank(cameraConfig.getString("PLC_vender")) || StringUtils.isBlank(cameraConfig.getString("PLC_Ip")) || StringUtils.isBlank(cameraConfig.getString("PLC_Delay")) || StringUtils.isBlank(cameraConfig.getString("serial_number")) || StringUtils.isBlank(cameraConfig.getString("width")) || StringUtils.isBlank(cameraConfig.getString("height")) || StringUtils.isBlank(cameraConfig.getString("fps")) || StringUtils.isBlank(cameraConfig.getString("exposure_time"))) { + addAlarm(point,"相机设备参数缺失!"); + return new Result(ResultCode.operate_failure, "相机设备参数缺失!"); + } + + if (algorithmConfig == null || algorithmTempConfig.getString("algorithm_name") == null || algorithmTempConfig.getString("algorithm_model") == null) { + addAlarm(point,"检测算法参数缺失!"); + return new Result(ResultCode.operate_failure, "检测算法参数缺失!"); + } + + if (DataCallBack.allTemperatureData.containsKey(point.getPointId()) && BigDecimal.valueOf(DataCallBack.allTemperatureData.get(point.getPointId()).getValue() / 100.0).setScale(2, RoundingMode.HALF_UP).doubleValue() > detect_threshold_temperature) { + addAlarm(point,"检测点温度告警!"); + return new Result(ResultCode.operate_failure, "检测点温度告警!"); + } + + if (EngineDriver.cameraMap.get(camera.getDeviceId()).status().equals(DeviceStatus.open)) { + addAlarm(point,"当前检测点相机设备已存在检测中的任务!"); + return new Result(ResultCode.operate_failure, "当前检测点相机设备已存在检测中的任务"); + } + + DetectJob detectJob = createJob(job_id, point, camera, algorithm, "手动检测"); + + CurrentJob = CompletableFuture.runAsync(() -> { + webSocket.sendStatusMessage(point.getPointId(), 1); + Engine.run(JobType.BOLT_And_LINE, detectJob); + webSocket.sendStatusMessage(point.getPointId(), 0); + List defects = defectService.getRealTimeData(job_id); + delayDetect(defects, point, algorithmConfig, detectJob); + }); + + return new Result(ResultCode.success, "开始检测"); + } + + @Scheduled(cron = "0 * * * * *") // 每分钟执行一次任务 + public void automaticDetection() { + pointService.getAll().stream().filter(x -> StringUtils.isNotBlank(x.getConfig())).forEach(point -> { + + List dailyAutoDetectionTime = new ArrayList<>(); + + if (StringUtils.isNotBlank(JSON.parseObject(point.getConfig()).getString("dailyAutoDetectionTime"))) { + dailyAutoDetectionTime = JSON.parseObject(point.getConfig()).getJSONArray("dailyAutoDetectionTime").toJavaList(String.class); + } + + Date now = new Date(); + + String currentTime = dateFormat.format(now); + + if (dailyAutoDetectionTime.contains(currentTime)) { + + logger.info("[自动检测] 检测点:" + point.getName()); + boolean flag = true; + + if (point.getEnableDetect().equals(1)) { + flag = false; + logger.info("检测点未开启检测权限"); + } + + Device camera = deviceService.selectByPoint(point.getPointId()).stream().filter(x -> x.getType().equals(DeviceType.camera.name())).findAny().orElse(null); + + if (camera == null || camera.getStatus().equals(0)) { + flag = false; + logger.info("当前检测点相机设备已停用"); + } + + Algorithm algorithm = algorithmService.getByPoint(point.getPointId()); + if (algorithm == null) { + flag = false; + logger.info("当前检测点算法文件未配置"); + } + + String job_id = "job_" + SystemDateUtils.getStrYMD() + "_" + SnowFlakeUtil.getDefaultSnowFlakeId(); + + AlgorithmConfig algorithmConfig = JSONObject.parseObject(point.getConfig(), AlgorithmConfig.class); + + JSONObject cameraConfig = JSON.parseObject(camera.getConfig()); + + JSONObject algorithmTempConfig = JSON.parseObject(algorithm.getConfig()); + + Double detect_threshold_temperature = JSON.parseObject(camera.getTempThreshold()).getDoubleValue("tempThreshold"); + + if (detect_threshold_temperature == null || StringUtils.isBlank(cameraConfig.getString("PLC_vender")) || StringUtils.isBlank(cameraConfig.getString("PLC_Ip")) || StringUtils.isBlank(cameraConfig.getString("PLC_Delay")) || StringUtils.isBlank(cameraConfig.getString("serial_number")) || StringUtils.isBlank(cameraConfig.getString("width")) || StringUtils.isBlank(cameraConfig.getString("height")) || StringUtils.isBlank(cameraConfig.getString("fps")) || StringUtils.isBlank(cameraConfig.getString("exposure_time"))) { + flag = false; + logger.info("相机设备参数缺失"); + } + + if (algorithmConfig == null || algorithmTempConfig.getString("algorithm_name") == null || algorithmTempConfig.getString("algorithm_model") == null) { + flag = false; + logger.info("检测算法参数缺失"); + } + + if (DataCallBack.allTemperatureData.containsKey(point.getPointId()) && BigDecimal.valueOf(DataCallBack.allTemperatureData.get(point.getPointId()).getValue() / 100.0).setScale(2, RoundingMode.HALF_UP).doubleValue() > detect_threshold_temperature) { + flag = false; + logger.info("检测点温度告警!"); + } + + if (EngineDriver.cameraMap.get(camera.getDeviceId()).status().equals(DeviceStatus.open)) { + flag = false; + logger.info("当前检测点相机设备已存在检测中的任务"); + } + + if (flag) { + DetectJob detectJob = createJob(job_id, point, camera, algorithm, "自动检测"); + CurrentJob = CompletableFuture.runAsync(() -> { + webSocket.sendStatusMessage(point.getPointId(), 1); + Engine.run(JobType.BOLT_And_LINE, detectJob); + webSocket.sendStatusMessage(point.getPointId(), 0); + List defects = defectService.getRealTimeData(job_id); + delayDetect(defects, point, algorithmConfig, detectJob); + }); + } + } + }); + } + + private DetectJob createJob(String job_id, Point point, Device camera, Algorithm algorithm, String type) { + + AlgorithmConfig algorithmConfig = JSONObject.parseObject(point.getConfig(), AlgorithmConfig.class); + + JSONObject cameraConfig = JSON.parseObject(camera.getConfig()); + + JSONObject algorithmTempConfig = JSON.parseObject(algorithm.getConfig()); + + Double detect_threshold_temperature = JSON.parseObject(camera.getTempThreshold()).getDoubleValue("tempThreshold"); + + DetectConfig detectConfig = DetectConfig.builder().bolt(point.getBoltDetect().equals(0)).line(point.getLineDetect().equals(0)).pole(point.getPoleOpenDetect().equals(0)).temp(point.getPointTempDetect().equals(0)).detect_threshold_bolt(algorithmConfig.getBoltThreshold()).detect_threshold_line(algorithmConfig.getLineThreshold()).detect_threshold_pole(1.0).detect_threshold_temperature(detect_threshold_temperature).detect_duration(point.getManualTime()).detect_work_zone(point.getPoleNum()).defect_work_dir(defect_work_dir + job_id).delayDetect(algorithmConfig.getDelayDetect()).plc_ip(cameraConfig.getString("PLC_Ip")).plc_delay(cameraConfig.getInteger("PLC_Delay")).serial_number(cameraConfig.getString("serial_number")).width(cameraConfig.getInteger("width")).height(cameraConfig.getInteger("height")).fps(cameraConfig.getDouble("fps")).exposureTime(cameraConfig.getDouble("exposure_time")).jobId(job_id).algorithm_id(algorithm.getBizId()).algorithm_type(AlgorithmName.getByName(algorithmTempConfig.getString("algorithm_name")).getAlgorithmType().name()).algorithm_name(algorithmTempConfig.getString("algorithm_name")).algorithm_model(algorithmTempConfig.getString("algorithm_model")).build(); + + Job job = new Job(); + + job.setJobId(job_id).setPowerStation(point.getPowerStation()).setMotorGroup(point.getMotorGroup()).setPoint(point.getPointId()).setProduct(DetectType.BOLT_AND_LINE.getProduct()).setType(type).setName(DetectType.BOLT_AND_LINE.getDesc()).setDescription(DetectType.BOLT_AND_LINE.getDesc() + "[" + type + "]").setAttribute(new JSONObject().toJSONString()).setConfig(JSON.toJSONString(detectConfig)).setOperator(SessionHolder.getUserSession()).setStatus(0); + + jobService.addJob(job); + + DetectJob detectJob = DetectJob.builder().jobId(job_id).powerStation(point.getPowerStation()).motorGroup(point.getMotorGroup()).point(point.getPointId()).attribute(new JSONObject()).config((JSONObject) JSON.toJSON(detectConfig)).deviceId(camera.getDeviceId()).build(); + + JobService.JOB_MAP.putIfAbsent(job_id, detectJob); + + return detectJob; + } + + private void delayDetect(List defects, Point point, AlgorithmConfig algorithmConfig, DetectJob detectJob) { + if (defects.size() > 1 && defects.stream().map(Defect::getZone).distinct().collect(Collectors.toList()).size() == 1) { + + logger.info("检测到:" + point.getName() + "检测点下的磁极静止"); + + Integer delayDetect = algorithmConfig.getDelayDetect(); + + if (delayDetect == null || delayDetect == 0) { + logger.info("延时检测设置关闭"); + } else { + logger.info("延时" + delayDetect + "分钟检测"); + hashedWheelTimer.newTimeout((x) -> Engine.run(JobType.BOLT_And_LINE, detectJob), delayDetect, TimeUnit.MINUTES); + } + } + } + + public void addAlarm(Point point, String info) { + Alarm alarm = new Alarm(); + alarm.setAlarmId("alarm_" + SnowFlakeUtil.getDefaultSnowFlakeId()) + .setType(0) + .setContent(point.getName() + ":" + info); + alarm.setGmtCreate(SystemDateUtils.getNewDate()); + AlarmService.ALARM_MAP.put(point.getPointId(), alarm); + webSocket.sendStatusMessage(point.getPointId(), 0); + } +} + diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/DeviceController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/DeviceController.java new file mode 100644 index 0000000..5237436 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/DeviceController.java @@ -0,0 +1,339 @@ +package com.jiluo.bolt.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.device.DeviceType; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.dto.DeviceDto; +import com.jiluo.bolt.entity.dto.TempSenserDto; +import com.jiluo.bolt.entity.dto.TempThresholdDto; +import com.jiluo.bolt.entity.po.Device; +import com.jiluo.bolt.entity.vo.DeviceVo; +import com.jiluo.bolt.entity.vo.TempSenserVo; +import com.jiluo.bolt.service.*; +import com.jiluo.bolt.util.SnowFlakeUtil; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; +import javax.validation.Valid; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 设备信息表(device)表控制层 + * @author Fangy + * @since 2023-04-19 11:46:17 + */ +@RestController +@RequestMapping("api/device") +public class DeviceController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(DeviceController.class); + /** + * 服务对象 + */ + @Resource + private DeviceService deviceService; + + @Resource + private PowerStationService powerStationService; + + @Resource + private MotorGroupService motorGroupService; + + @Resource + private PointService pointService; + + @Resource + private DeviceTempleteService deviceTempleteService; + + @PostMapping("/select") + public Result select(@RequestBody Device device){ + return new Result(ResultCode.success,deviceService.selectByPoint(device.getPointId()).stream().filter(x->x.getType().equals(DeviceType.camera.name()))); + } + + /** + * 查询设备温度检测阈值 + */ + @PostMapping("/selectTemp") + public Result selectTemp() { + List tempThresholdDtos = new ArrayList<>(); + List deviceList = deviceService.selectAll(); + List deviceIds = deviceList.stream().map(Device::getDeviceId).collect(Collectors.toList()); + deviceList.stream().map(Device::getTempThreshold).filter(x -> StringUtils.isNotBlank(x)).distinct().forEach(e-> tempThresholdDtos.add(JSONObject.parseObject(e,TempThresholdDto.class))); + tempThresholdDtos.forEach(x->x.getDeviceIdList().removeIf(y->!deviceIds.contains(y.getId()))); + return new Result(ResultCode.success,tempThresholdDtos); + } + /** + * 修改设备温度检测阈值 + */ + @PostMapping("/updateTemp") + public Result updateTemp(@RequestBody List tempThresholdDtos) { + if (tempThresholdDtos.stream().anyMatch(dto -> dto.getTempThreshold() == null || + dto.getDeviceIdList() == null || + dto.getDeviceIdList().isEmpty())){ + return new Result(ResultCode.illegal_argument); + } + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.set("temp_threshold",""); + deviceService.update(updateWrapper); + tempThresholdDtos.forEach(tempThresholdDto -> { + if (tempThresholdDto.getTempThresholdId() == null){ + BigDecimal temp = tempThresholdDto.getTempThreshold().setScale(2, RoundingMode.HALF_UP); + tempThresholdDto.setTempThresholdId(String.valueOf(SnowFlakeUtil.getDefaultSnowFlakeId())).setTempThreshold(temp); + tempThresholdDto.getDeviceIdList().stream().forEach(e-> deviceService.updateTemp(e.getId(),JSONObject.toJSONString(tempThresholdDto))); + }else if (tempThresholdDto.getTempThresholdId() != null){ + BigDecimal temp = tempThresholdDto.getTempThreshold().setScale(2, RoundingMode.HALF_UP); + tempThresholdDto.setTempThreshold(temp); + tempThresholdDto.getDeviceIdList().stream().forEach(e-> deviceService.updateTemp(e.getId(),JSONObject.toJSONString(tempThresholdDto))); + }}); + return new Result(ResultCode.success,"设置成功"); + } + /** + * 条件查询数据 + */ + @PostMapping("/query") + public Result query(@RequestBody DeviceDto deviceDto) { + List devices = deviceService.select(deviceDto); + List deviceVos = new ArrayList<>(); + devices.forEach(item->{ + JSONObject config = JSON.parseObject(item.getConfig()); + if (!StringUtils.isNotBlank(deviceDto.getName()) || StringUtils.containsOnly(deviceDto.getName(),item.getName()) || StringUtils.containsOnly(deviceDto.getName(),config.getString("serial_number"))){ + deviceVos.add(DeviceVo.builder().deviceId(item.getDeviceId()) + .name(item.getName()) + .sn(config.getString("serial_number")) + .plcIp(config.getString("PLC_Ip")) + .powerStationId(item.getPowerStationId()) + .motorGroupId(item.getMotorGroupId()) + .pointId(item.getPointId()) + .powerStation(powerStationService.getByBizId(item.getPowerStationId()).getName()) + .Group_Point(motorGroupService.getByBizId(item.getMotorGroupId()).getName() + "/" + pointService.getByBizId(item.getPointId()).getName()) + .fps(config.getDouble("fps")) + .exposureTime(config.getDouble("exposure_time")/1000) + .status(item.getStatus()) + .gmtCreate(item.getGmtCreate()) + .vender(item.getVender()) + .plcDelay(config.getInteger("PLC_Delay")) + .build());}}); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("total",deviceVos.size()); + jsonObject.put("data",deviceVos); + return new Result(ResultCode.success,jsonObject); + } + + + /** + * 新增数据 + * + */ + @PostMapping("/add") + public Result insert(@RequestBody @Valid DeviceDto deviceDto) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("serial_number",deviceDto.getSn()); + jsonObject.put("width",2448); + jsonObject.put("height",2048); + jsonObject.put("fps",deviceDto.getFps()); + jsonObject.put("exposure_time",deviceDto.getExposureTime()*1000); + jsonObject.put("PLC_Ip",deviceDto.getPlcIp()); + jsonObject.put("PLC_Port",502); + jsonObject.put("PLC_Delay",deviceDto.getPlcDelay()); + if (deviceDto.getVender().equals("mock_camera") || deviceDto.getPlcIp().equals("mock")){ + jsonObject.put("PLC_vender","mock_plc"); + }else { + jsonObject.put("PLC_vender","mathvision_plc"); + } + deviceDto.setConfig(jsonObject.toJSONString()); + if (!deviceService.add(deviceDto, DeviceType.camera.name())){ + return new Result(ResultCode.operate_failure,"添加失败"); + } + return new Result(ResultCode.success,"添加成功"); + } + + /** + * 修改数据 + * + */ + @PostMapping("/update") + public Result update(@RequestBody @Valid DeviceDto deviceDto) { + if (!StringUtils.isNotBlank(deviceDto.getDeviceId())){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + JSONObject jsonObject = new JSONObject(); + jsonObject.put("serial_number",deviceDto.getSn()); + jsonObject.put("width",2448); + jsonObject.put("height",2048); + jsonObject.put("fps",deviceDto.getFps()); + jsonObject.put("exposure_time",deviceDto.getExposureTime()*1000); + jsonObject.put("PLC_Ip",deviceDto.getPlcIp()); + jsonObject.put("PLC_Port",502); + jsonObject.put("PLC_Delay",deviceDto.getPlcDelay()); + if (deviceDto.getVender().equals("mock_camera") || deviceDto.getPlcIp().equals("mock")){ + jsonObject.put("PLC_vender","mock_plc"); + }else { + jsonObject.put("PLC_vender","mathvision_plc"); + } + deviceDto.setConfig(jsonObject.toJSONString()); + if (!deviceService.updateByBizId(deviceDto)){ + return new Result(ResultCode.operate_failure,"修改失败"); + } + return new Result(ResultCode.success,"修改成功"); + } + + /** + * 删除数据 + * + */ + @PostMapping("/del") + public Result delete(@RequestBody DeviceDto deviceDto) { + Map columnMap = new HashMap<>(); + columnMap.put("biz_id",deviceDto.getDeviceId()); + if (!deviceService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + return new Result(ResultCode.success,"删除成功"); + } + + /** + * 设备停用/启用 + */ + @PostMapping("/deactivate") + public Result deactivate(@RequestBody DeviceDto deviceDto) { + if (!StringUtils.isNotBlank(deviceDto.getDeviceId())){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + if (deviceService.deactivate(deviceDto)){ + return new Result(ResultCode.success,"操作成功"); + }else { + return new Result(ResultCode.operate_failure); + } + } + /** + * 获取相机型号 + */ + @PostMapping("/getTypes") + public Result getType() { + List result = new ArrayList<>(); + deviceTempleteService.getAll().stream().filter(x->x.getConfig().equals("camera")).forEach(item->{ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("venderId",item.getBizId()); + jsonObject.put("vender",item.getVender()); + result.add(jsonObject); + }); + return new Result(ResultCode.success,result); + } + + /** + * 条件查询数据 + */ + @PostMapping("/selectTempSensor") + public Result selectTempSensor(@RequestBody TempSenserDto tempSenserDto) { + List devices = deviceService.select(tempSenserDto); + List tempSenserVos = new ArrayList<>(); + devices.forEach(item->{ + JSONObject config = JSON.parseObject(item.getConfig()); + if (!StringUtils.isNotBlank(tempSenserDto.getNameOrSerialPort()) || StringUtils.containsOnly(tempSenserDto.getNameOrSerialPort(),item.getName()) || StringUtils.containsOnly(tempSenserDto.getNameOrSerialPort(),config.getString("serial_port"))){ + tempSenserVos.add(TempSenserVo.builder().deviceId(item.getDeviceId()) + .name(item.getName()) + .serialPort(config.getString("serial_port")) + .powerStationId(item.getPowerStationId()) + .motorGroupId(item.getMotorGroupId()) + .pointId(item.getPointId()) + .powerStation(powerStationService.getByBizId(item.getPowerStationId()).getName()) + .Group_Point(motorGroupService.getByBizId(item.getMotorGroupId()).getName() + "/" + pointService.getByBizId(item.getPointId()).getName()) + .status(item.getStatus()) + .gmtCreate(item.getGmtCreate()) + .typeId(item.getVender()) + .type(deviceTempleteService.getByBizId(item.getVender()).getVender()) + .relatedDeviceId(config.getString("relatedDeviceId")) + .relatedDevice(config.getString("relatedDeviceName")) + .build()); + }}); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("total",tempSenserVos.size()); + jsonObject.put("data",tempSenserVos); + return new Result(ResultCode.success,jsonObject); + } + + + /** + * 新增数据 + * + */ + @PostMapping("/addTempSensor") + public Result insertTempSensor(@RequestBody @Valid TempSenserDto tempSenserDto) { + Device RelatedDevice = deviceService.selectByBizId(tempSenserDto.getRelatedDeviceId()); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("relatedDeviceId",tempSenserDto.getRelatedDeviceId()); + jsonObject.put("relatedDeviceSN",JSON.parseObject(RelatedDevice.getConfig()).getString("serial_number")); + jsonObject.put("serial_port",tempSenserDto.getSerialPort()); + jsonObject.put("relatedDeviceVendor",RelatedDevice.getVender()); + jsonObject.put("relatedDeviceName",RelatedDevice.getName()); + tempSenserDto.setConfig(jsonObject); + if (!deviceService.add(tempSenserDto, DeviceType.temperature_sensor.name())){ + return new Result(ResultCode.operate_failure,"添加失败"); + } + return new Result(ResultCode.success,"添加成功"); + } + + /** + * 修改数据 + * + */ + @PostMapping("/updateTempSensor") + public Result updateTempSensor(@RequestBody @Valid TempSenserDto tempSenserDto) { + if (!StringUtils.isNotBlank(tempSenserDto.getDeviceId())){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + Device RelatedDevice = deviceService.selectByBizId(tempSenserDto.getRelatedDeviceId()); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("relatedDeviceId",tempSenserDto.getRelatedDeviceId()); + jsonObject.put("relatedDeviceSN",JSON.parseObject(RelatedDevice.getConfig()).getString("serial_number")); + jsonObject.put("serial_port",tempSenserDto.getSerialPort()); + jsonObject.put("relatedDeviceVendor",RelatedDevice.getVender()); + jsonObject.put("relatedDeviceName",RelatedDevice.getName()); + tempSenserDto.setConfig(jsonObject); + if (!deviceService.updateByBizId(tempSenserDto)){ + return new Result(ResultCode.operate_failure,"修改失败"); + } + return new Result(ResultCode.success,"修改成功"); + } + /** + * 删除数据 + * + */ + @PostMapping("/deleteTempSensor") + public Result deleteTempSensor(@RequestBody TempSenserDto tempSenserDto) { + Map columnMap = new HashMap<>(); + columnMap.put("biz_id",tempSenserDto.getDeviceId()); + if (!deviceService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + return new Result(ResultCode.success,"删除成功"); + } + /** + * 获取传感器类型 + */ + @PostMapping("/getSensorTypes") + public Result getSensorTypes() { + + List result = new ArrayList<>(); + deviceTempleteService.getAll().stream().filter(x->x.getConfig().equals("sensor")).forEach(item->{ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("typeId",item.getBizId()); + jsonObject.put("type",item.getVender()); + result.add(jsonObject); + }); + return new Result(ResultCode.success,result); + } +} + diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/HistoryDataController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/HistoryDataController.java new file mode 100644 index 0000000..43ff3ce --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/HistoryDataController.java @@ -0,0 +1,286 @@ +package com.jiluo.bolt.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.common.DetectType; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.dto.DetectDto; +import com.jiluo.bolt.entity.po.*; +import com.jiluo.bolt.entity.vo.DefectVo; +import com.jiluo.bolt.entity.vo.HistoricalDataVo; +import com.jiluo.bolt.export.Export; +import com.jiluo.bolt.service.*; +import com.jiluo.bolt.util.ExcelUtils; +import com.jiluo.bolt.util.SystemDateUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.Range; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.*; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/04/26/14:33 + * @Description:历史数据接口(History) + */ +@RestController +@RequestMapping("api/history") +public class HistoryDataController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(HistoryDataController.class); + /** + * 服务对象 + */ + @Resource + private DefectService defectService; + + @Resource + private JobService jobService; + + @Resource + private PowerStationService powerStationService; + + @Resource + private MotorGroupService motorGroupService; + + @Resource + private PointService pointService; + + @Resource + private DeviceService deviceService; + + @Resource + private Export export; + + /** + * 历史数据 + */ + @PostMapping("/query") + public Result selectByHistoricalData(@RequestBody DetectDto detectDto) { + if (detectDto.getCurrent()==null || detectDto.getCurrent().equals(0)){detectDto.setCurrent(1);} + if (detectDto.getSize()==null ||detectDto.getSize().equals(0)){detectDto.setSize(10);} + LocalDate endTime = LocalDate.parse(detectDto.getEndTime()).plusDays(1); + Integer current = (detectDto.getCurrent()-1)*detectDto.getSize()+1; + Integer total = jobService.getHistoryJobTotal(detectDto.getPointId(),detectDto.getStatus(), DetectType.BOLT_AND_LINE.getProduct(),detectDto.getStartTime(),endTime.toString()); + if (total.equals(0)){ + return new Result(ResultCode.success,new Page<>().setTotal(total)); + } + Map motorGroupName = motorGroupService.getAll().stream().collect(Collectors.toMap(MotorGroup::getMotorGroupId,MotorGroup::getName)); + Map pointName = pointService.getAll().stream().collect(Collectors.toMap(Point::getPointId,Point::getName)); + List jobList = jobService.getHistoryJob(detectDto.getPointId(),detectDto.getStatus(), DetectType.BOLT_AND_LINE.getProduct(),current,detectDto.getSize(), detectDto.getStartTime(),endTime.toString()).stream().filter(x->x.getAttribute()!=null&&!x.getAttribute().equals("{}")).collect(Collectors.toList()); + List historicalDataVoList = new ArrayList<>(); + jobList.forEach(item->{ + Map> defectZone = JSONObject.parseObject(item.getAttribute()).getObject("defectZone", new TypeReference>>() {}); + StringBuilder errorInfoBuilder = new StringBuilder(); + for (Map.Entry> entry : defectZone.entrySet()) { + String key = entry.getKey(); + List values = entry.getValue(); + if (values.size()>0){ + StringBuilder valuesBuilder = new StringBuilder(); + for (int value : values) { + if (valuesBuilder.length() > 0) { + valuesBuilder.append(","); + } + valuesBuilder.append(value); + } + String error = valuesBuilder + "#" + key; + errorInfoBuilder.append(error + ";"); + } + } + String errorInfo = errorInfoBuilder.toString(); + if (errorInfo.endsWith(";")) { + errorInfo = errorInfo.substring(0, errorInfo.length() - 1); + } + historicalDataVoList.add(HistoricalDataVo.builder().jobId(item.getJobId()) + .motorGroup(motorGroupName.get(item.getMotorGroup())) + .point(pointName.get(item.getPoint())) + .gmtCreate(item.getGmtCreate()) + .type(item.getType()) + .status(item.getStatus()) + .info(errorInfo) + .build()); + }); + return new Result(ResultCode.success,new Page().setCurrent(detectDto.getCurrent()).setSize(detectDto.getSize()).setTotal(total).setRecords(historicalDataVoList)); + } + + /** + * 详情数据 + */ + @PostMapping("/detail") + public Result detail(@RequestBody DetectDto detectDto){ + List defects = defectService.getByJob(detectDto.getJobId()); + JSONObject jsonObject = JSON.parseObject(jobService.getByBizId(detectDto.getJobId()).getConfig()); + Map permissionMap = new HashMap<>(); + permissionMap.put("bolt",jsonObject.getBooleanValue("bolt")); + permissionMap.put("line",jsonObject.getBooleanValue("line")); + permissionMap.put("pole",jsonObject.getBooleanValue("pole")); + permissionMap.put("temperature",jsonObject.getBooleanValue("temp")); + List _defects = defects.stream().filter(x->permissionMap.get(x.getType())).map(defect -> { + DefectType _type = DefectType.toDefectType(defect.getType()); + BigDecimal value = (_type == DefectType.bolt || _type == DefectType.temperature) ? BigDecimal.valueOf(defect.getValue() / 100.0).setScale(2, RoundingMode.HALF_UP) : (_type == DefectType.line ? BigDecimal.valueOf(defect.getValue() / 1000.0).setScale(3, RoundingMode.HALF_UP) : BigDecimal.valueOf(defect.getValue())); + return DefectVo.builder() + .type(_type) + .alarm(BooleanUtils.toBoolean(defect.getAlarm())) + .zone(defect.getZone()) + .position(defect.getPosition()) + .value(value) + .img(defect.getData()) + .build(); + }) + .filter(x -> x.getZone() != null && x.getType() != null && x.getPosition() != null && x.getValue() != null) + .sorted(Comparator.comparing(DefectVo::getZone).thenComparing(DefectVo::getType).thenComparing(DefectVo::getPosition)).collect(Collectors.toList()); + Map> map = _defects.stream().collect(Collectors.groupingBy(DefectVo::getZone)); + JSONObject result = new JSONObject(); + result.put("total",map.size()); + result.put("dataList",map); + return new Result(ResultCode.success,result); + } + + /** + * 导出word + */ + @PostMapping("/exportWord") + public void exportWord(@RequestBody DetectDto detectDto, HttpServletResponse response) throws Exception { + ZipOutputStream zipOutputStream = null; + String encodedFileName = URLEncoder.encode("转子视觉检测报告.zip", "UTF-8"); + response.reset(); + response.setContentType("application/octet-stream"); + response.setCharacterEncoding("utf-8"); + response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\""); + zipOutputStream = new ZipOutputStream(response.getOutputStream()); + if (detectDto.getJobs()!=null && detectDto.getJobs().size()>0){ + List pointIds = jobService.getByBizId(detectDto.getJobs()).stream().map(Job::getPoint).distinct().collect(Collectors.toList()); + for ( String pointId: pointIds) { + ByteArrayOutputStream target = new ByteArrayOutputStream(); + Point point = pointService.getByBizId(pointId); + String fileName = motorGroupService.getByBizId(point.getMotorGroup()).getName()+"/" + point.getName() +"/"+export.export(pointId,detectDto.getJobs(),target); + addFileToZip(fileName, target, zipOutputStream); + } + zipOutputStream.close(); + }else if (StringUtils.isNotBlank(detectDto.getStartTime()) && StringUtils.isNotBlank(detectDto.getEndTime())){ + if (detectDto.getPointId()!=null&&StringUtils.isNotBlank(detectDto.getPointId())){ + ByteArrayOutputStream target = new ByteArrayOutputStream(); + SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd"); + Range dateRange = Range.between(ft.parse(detectDto.getStartTime()),ft.parse(detectDto.getEndTime())); + Point point = pointService.getByBizId(detectDto.getPointId()); + String fileName = motorGroupService.getByBizId(point.getMotorGroup()).getName()+"/" + point.getName() +"/"+export.export(detectDto.getPointId(),dateRange,detectDto.getStatus(),target); + addFileToZip(fileName, target, zipOutputStream); + zipOutputStream.close(); + }else { + SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd"); + List pointIds = jobService.exportData(ft.parse(detectDto.getStartTime()),Date.from(LocalDate.parse(detectDto.getEndTime()).plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant())).stream().filter(x->detectDto.getStatus()==null||x.getStatus().equals(detectDto.getStatus())).map(Job::getPoint).distinct().collect(Collectors.toList()); + for ( String pointId: pointIds) { + ByteArrayOutputStream target = new ByteArrayOutputStream(); + Point point = pointService.getByBizId(pointId); + String fileName = motorGroupService.getByBizId(point.getMotorGroup()).getName()+"/" + point.getName() +"/"+export.export(pointId,detectDto.getJobs(),target); + addFileToZip(fileName, target, zipOutputStream); + } + zipOutputStream.close(); + } +// ByteArrayOutputStream target = new ByteArrayOutputStream(); +// SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd"); +// Range dateRange = Range.between(ft.parse(detectDto.getStartTime()),ft.parse(detectDto.getEndTime())); +// String fileName = export.export(detectDto.getPointId(),dateRange,detectDto.getStatus(),target); +// response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); +// response.setHeader("Content-Disposition", "attachment; filename="+URLEncoder.encode(fileName, "UTF-8")); +// OutputStream outputStream = response.getOutputStream(); +// target.writeTo(outputStream); +// outputStream.close(); + }else { + if (null != zipOutputStream) zipOutputStream.close(); + logger.info("exportExcel: 参数缺失"); + } + } + + /** + * 导出Excel + */ + @PostMapping("/exportExcel") + public void exportExcel(@RequestBody DetectDto detectDto, HttpServletResponse response) throws Exception { + ByteArrayOutputStream target = new ByteArrayOutputStream(); + if (detectDto.getJobs()!=null && detectDto.getJobs().size()>0) { + List defectList = defectService.getByJob(detectDto.getJobs()); + String fileName = ExcelUtils.generateExcel(target, defectToExcel(defectList)); + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()); + response.reset(); + response.setContentType("application/vnd.ms-excel;charset=utf-8"); + OutputStream outputStream = response.getOutputStream(); + response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", encodedFileName)); + response.setHeader("Pragma", "no-cache"); + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setHeader("Expires", "0"); + target.writeTo(outputStream); + outputStream.flush(); + outputStream.close(); + } + else if (StringUtils.isNotBlank(detectDto.getStartTime()) && StringUtils.isNotBlank(detectDto.getEndTime())) { + LocalDate endTime = LocalDate.parse(detectDto.getEndTime()).plusDays(1); + String fileName = ExcelUtils.generateExcel(target, defectToExcel(defectService.exportData(detectDto.getPointId(),SystemDateUtils.StringToDate(detectDto.getStartTime()),SystemDateUtils.StringToDate(endTime.toString()), detectDto.getStatus()).stream().filter(x->!x.getType().equals(DefectType.temperature.name())).collect(Collectors.toList()))); + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()); + response.reset(); + response.setContentType("application/vnd.ms-excel;charset=utf-8"); + OutputStream outputStream = response.getOutputStream(); + response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", encodedFileName)); + response.setHeader("Pragma", "no-cache"); + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setHeader("Expires", "0"); + target.writeTo(outputStream); + outputStream.flush(); + outputStream.close(); + }else logger.info("exportExcel: 参数缺失"); + } + + private JSONObject generateBody(String name, byte[] data) { + JSONObject body = new JSONObject(); + body.put("data", data); + body.put("name", name); + return body; + } + + private List defectToExcel(List defectList){ + Map powerStationMap = powerStationService.getAll().stream().collect(Collectors.toMap(PowerStation::getPowerStationId,PowerStation::getName)); + Map motorGroupMap = motorGroupService.getAll().stream().collect(Collectors.toMap(MotorGroup::getMotorGroupId,MotorGroup::getName)); + Map pointMap = pointService.getAll().stream().collect(Collectors.toMap(Point::getPointId,Point::getName)); + Map deviceMap = deviceService.selectAll().stream().collect(Collectors.toMap(Device::getDeviceId,Device::getName)); + defectList.forEach(defect -> { + if (powerStationMap.containsKey(defect.getPowerStation())) defect.setPowerStation(powerStationMap.get(defect.getPowerStation())); + if (motorGroupMap.containsKey(defect.getMotorGroup())) defect.setMotorGroup(motorGroupMap.get(defect.getMotorGroup())); + if (pointMap.containsKey(defect.getPoint())) defect.setPoint(pointMap.get(defect.getPoint())); + if (deviceMap.containsKey(defect.getDevice())) defect.setDevice(deviceMap.get(defect.getDevice())); + }); + return defectList; + } + + private void addFileToZip(String filename, ByteArrayOutputStream byteArrayOutputStream, + ZipOutputStream zipOutputStream) throws IOException { + ZipEntry zipEntry = new ZipEntry(filename); + zipOutputStream.putNextEntry(zipEntry); + zipOutputStream.write(byteArrayOutputStream.toByteArray()); + zipOutputStream.closeEntry(); + } +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/ImgController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/ImgController.java new file mode 100644 index 0000000..80f365c --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/ImgController.java @@ -0,0 +1,90 @@ +package com.jiluo.bolt.controller; + + +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.dto.ImgDto; +import com.jiluo.bolt.util.ImgUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/06/06/15:24 + * @Description: + */ +@RestController +@RequestMapping("api/img") +public class ImgController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(ImgController.class); + + @Value("${defect_work_dir}") + private String defect_work_dir; + + @Value("${defect_img_dir}") + private String defect_img_dir; + + @PostMapping("/base") + public Result getImg(@RequestBody ImgDto imgDto) throws Exception { + StringBuilder stringBuilder = new StringBuilder(); + return new Result(ResultCode.success, ImgUtils.ToBase64(stringBuilder.append(defect_work_dir).append(imgDto.getJobId()).append("/detect/").append(imgDto.getImg()).toString())); + } + + @PostMapping("/path") + public Result getPath() { + return new Result(ResultCode.success, defect_img_dir); + } + + @GetMapping("/getImages") + public List getImages(@RequestParam(required = false) Integer day) { + List result = new ArrayList<>(); + + File directory = new File(defect_work_dir); + + File[] folders = directory.listFiles(File::isDirectory); + + for (File folder : folders) { + // 获取文件夹创建日期 + BasicFileAttributes attr; + try { + attr = Files.readAttributes(folder.toPath(), BasicFileAttributes.class); + } catch (Exception e) { + logger.error(e.getMessage(), e); + continue; + } + LocalDate createDate = attr.creationTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + + // 如果day为空或小于文件夹创建日期,添加到结果列表 + if (day == null || ChronoUnit.DAYS.between(createDate, LocalDate.now()) < day) { + + File[] images = folder.listFiles(file -> file.isFile() && isImage(file)); + for (File image : images) { + result.add(image.getAbsolutePath()); + } + } + } + + return result; + } + + // 判断文件是否为图片 + private boolean isImage(File file) { + String extension = file.getName().substring(file.getName().lastIndexOf(".") + 1); + return extension.equalsIgnoreCase("jpg") || extension.equalsIgnoreCase("jpeg") || extension.equalsIgnoreCase("png")|| extension.equalsIgnoreCase("bmp"); + } +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/MinioController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/MinioController.java new file mode 100644 index 0000000..b45f31d --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/MinioController.java @@ -0,0 +1,331 @@ +package com.jiluo.bolt.controller; + +import cn.hutool.core.io.file.FileNameUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.jiluo.bolt.config.MinioConfig; +import com.jiluo.bolt.config.UploadFile; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.po.Defect; +import com.jiluo.bolt.entity.po.Detect; +import com.jiluo.bolt.entity.po.Job; +import com.jiluo.bolt.service.DefectService; +import com.jiluo.bolt.service.DetectService; +import com.jiluo.bolt.service.JobService; +import com.jiluo.bolt.service.MinioService; +import com.jiluo.bolt.util.SystemDateUtils; +import io.minio.ObjectStat; +import io.minio.errors.*; +import io.minio.messages.Item; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.tomcat.util.http.fileupload.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.util.ResourceUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/13/15:00 + * @Description: + */ +@RequestMapping("api/minio") +@RestController +@Slf4j +public class MinioController { + + @Autowired + private MinioService minioService; + + @Autowired + private MinioConfig minioConfig; + + @Autowired + private UploadFile uploadFile; + + @Value("${defect_work_dir}") + private String defect_work_dir; + + @Autowired + private DetectService detectService; + + @Autowired + private DefectService defectService; + + @Autowired + private JobService jobService; + + + @SneakyThrows + @Scheduled(cron = "${minio.time}") + public void uploadFile() { + List jobs = jobService.exportData(SystemDateUtils.toDate(LocalDate.now().minusDays(1)), new Date()); + jobs.stream().filter(job -> { + JSONObject attribute = JSON.parseObject(job.getAttribute()); + if (attribute.containsKey("defectTotal") && (Integer) attribute.get("defectTotal") > 0) { + return true; + } + return false; + }); + + for (int i = 0; i < (jobs.size() > 3 ? 3 : jobs.size()); i++) { + List detectList = detectService.getByJob(jobs.get(i).getJobId()); + List defectList = defectService.getByJob(jobs.get(i).getJobId()); + ToMinio(detectList, defectList, i + 1); + } +// File directory = new File(defect_work_dir); +// File[] folders = directory.listFiles(File::isDirectory); +// LocalDate currentDate = LocalDate.now(); +// +// for (File folder : folders) { +// // 获取文件夹创建日期 +// BasicFileAttributes attr = Files.readAttributes(folder.toPath(), BasicFileAttributes.class); +// LocalDate createDate = attr.creationTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); +// +// if (createDate.equals(currentDate)) { +// String path = folder.getAbsolutePath(); +// +// List detectFiles = FileUtils.convertFilesToList(path + "/detect"); +// List defectFiles = FileUtils.convertFilesToList(path + "/defect"); +// +// String jobId = regexJobId(path); +// if (jobId != null && StringUtils.isNotBlank(jobId)) { +// if (detectFiles != null && !detectFiles.isEmpty()) { +// uploadFile.uploadFiles(detectFiles, jobId, "detect"); +// } +// +// if (defectFiles != null && !defectFiles.isEmpty()) { +// uploadFile.uploadFiles(defectFiles, jobId, "defect"); +// } +// } +// } +// } + } + + @GetMapping("/test") + @SneakyThrows + public void test() { + List jobs = jobService.exportData(SystemDateUtils.toDate(LocalDate.now().minusDays(1)), new Date()); + jobs.stream().filter(job -> { + JSONObject attribute = JSON.parseObject(job.getAttribute()); + if (attribute.containsKey("defectTotal") && (Integer) attribute.get("defectTotal") > 0) { + return true; + } + return false; + }); + + for (int i = 0; i < (jobs.size() > 3 ? 3 : jobs.size()); i++) { + List detectList = detectService.getByJob(jobs.get(i).getJobId()); + List defectList = defectService.getByJob(jobs.get(i).getJobId()); + ToMinio(detectList, defectList, i + 1); + } + } + + private void ToMinio(List detectList, List defectList, Integer jobTimes) { + Set alarmSet = defectList.stream() + .filter(defect -> defect.getAlarm().equals(1)) + .map(Defect::getData) + .collect(Collectors.toSet()); + Map zoneTimesMap = new HashMap<>(); + + for (Detect detect : detectList) { + Integer zone = detect.getZone(); + String img = detect.getData(); + String fileDir = defect_work_dir + detect.getJob() + "/detect/" + img; + + File file; + try { + file = ResourceUtils.getFile(fileDir); + if (!file.exists()) { + log.warn("File does not exist: {}", fileDir); + continue; + } + } catch (Exception e) { + log.error("Error getting file: {}", fileDir, e); + continue; + } + + zoneTimesMap.compute(zone, (key, value) -> (value == null) ? 1 : value + 1); + + String newName = minioNameRule(detect, alarmSet.contains(img), jobTimes, zoneTimesMap.get(zone)); + + try (FileInputStream fileInputStream = new FileInputStream(file)) { + uploadFile.uploadFile(newName, fileInputStream, "image/jpeg"); + } catch (Exception e) { + log.error("Error uploading file: {}", newName, e); + } + } + } + + private String minioNameRule(Detect detect, boolean alarm, Integer jobTimes, Integer detectTimes) { + return SystemDateUtils.formatDate(detect.getGmtCreate(), "yyyy/MM/dd") + "/record" + jobTimes + "/" + detect.getZone() + "-" + detectTimes + "-" + detect.getId() + "-" + (alarm ? "A" : "N" + ".jpg"); + } + + + @SneakyThrows + @PostMapping("/file") + public Result uploadFile(MultipartFile file, @RequestParam("bucketName") String bucketName) { + uploadFile.uploadFile(file, bucketName); + return Result.SUCCESS; + } + + @PostMapping("/files") + public Result uploadFiles(@RequestParam("multipartFiles") List files, @RequestParam("bucketName") String bucketName) { + for (MultipartFile file : files) { + uploadFile.uploadFile(file, bucketName); + } + return Result.SUCCESS; + } + + @GetMapping("/file") + public Result download(HttpServletResponse response, @RequestParam("fileName") String fileName, @RequestParam("bucketName") String bucketName) { + try { + //获取存储捅名 + bucketName = StringUtils.isNotBlank(bucketName) ? bucketName : minioConfig.getBucketName(); + //获取对象信息和对象的元数据。 + ObjectStat objectStat = minioService.statObject(bucketName, fileName); + //setContentType 设置发送到客户机的响应的内容类型 + response.setContentType(objectStat.contentType()); + //设置响应头 + response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(objectStat.name(), "UTF-8")); + //文件流 + InputStream object = minioService.getObject(bucketName, fileName); + //设置文件大小 + response.setHeader("Content-Length", String.valueOf(objectStat.length())); + IOUtils.copy(object, response.getOutputStream()); + //关闭流 + object.close(); + return Result.SUCCESS; + } catch (Exception e) { + log.error("下载文件失败,错误信息: " + e.getMessage()); + + return new Result(ResultCode.system_error, e.getMessage()); + } + } + + @DeleteMapping(value = "/file") + public Result deleteFile(@RequestParam("bucketName") String bucketName, @RequestParam("objectName") String objectName) { + minioService.removeObject(bucketName, objectName); + return Result.SUCCESS; + } + + @GetMapping(value = "/file/list") + public Result getFileList(@RequestParam("bucketName") String bucketName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException { + try { + //获取存储捅名 + bucketName = StringUtils.isNotBlank(bucketName) ? bucketName : minioConfig.getBucketName(); + //列出存储桶中所有对象 + Iterable> results = minioService.listObjects(bucketName); + //迭代器 + Iterator> iterator = results.iterator(); + + List items = new ArrayList<>(); + + String format = "{'fileName':'%s','fileSize':'%s'}"; + + while (iterator.hasNext()) { + //返回迭代中的下一个元素。 + Item item = iterator.next().get(); + //封装信息 + items.add(JSON.parse(String.format(format, "http://localhost:9000/" + bucketName + "/" + item.objectName(), formatFileSize(item.size())))); + } + return new Result(ResultCode.success, items); + } catch (Exception e) { + log.error("获取文件列表失败,错误信息: " + e.getMessage()); + return new Result(ResultCode.system_error, "获取文件列表失败"); + } + } + + @GetMapping("/preview/file") + public Result getPreviewFile(@RequestParam("bucketName") String bucketName, + @RequestParam("expires") Integer expires, + @RequestParam("objectName") String objectName) { + //获取存储捅名 + bucketName = StringUtils.isNotBlank(bucketName) ? bucketName : minioConfig.getBucketName(); + //生成一个给HTTP GET请求用的presigned URL + String filePath = minioService.presignedGetObject(bucketName, objectName, expires); + //封装信息 + + return new Result(ResultCode.success, filePath); + } + + @GetMapping("/previewList") + public Result getPreviewList(@RequestParam("bucketName") String bucketName, + @RequestParam("expires") Integer expires + ) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException { + + try { + //获取存储捅名 + bucketName = StringUtils.isNotBlank(bucketName) ? bucketName : minioConfig.getBucketName(); + //列出存储桶中所有对象 + Iterable> myObjects = minioService.listObjects(bucketName); + //迭代器 + Iterator> iterator = myObjects.iterator(); + List items = new ArrayList<>(); + String format = "{'fileName':'%s','fileSize':'%s'}"; + while (iterator.hasNext()) { + //返回迭代中的下一个元素。 + Item item = iterator.next().get(); + //生成一个给HTTP GET请求用的presigned URL + String filePath = minioService.presignedGetObject(bucketName, item.objectName(), expires); + //封装信息 + items.add(JSON.parse(String.format(format, filePath, formatFileSize(item.size())))); + } + return new Result(ResultCode.success, items); + } catch (Exception e) { + return new Result(ResultCode.system_error, "生成可以预览的文件链接失败,错误信息:" + e.getMessage()); + } + + } + + /** + * 显示文件大小信息单位 + * + * @param fileS + * @return + */ + private static String formatFileSize(long fileS) { + DecimalFormat df = new DecimalFormat("#.00"); + String fileSizeString = ""; + String wrongSize = "0B"; + if (fileS == 0) { + return wrongSize; + } + if (fileS < 1024) { + fileSizeString = df.format((double) fileS) + " B"; + } else if (fileS < 1048576) { + fileSizeString = df.format((double) fileS / 1024) + " KB"; + } else if (fileS < 1073741824) { + fileSizeString = df.format((double) fileS / 1048576) + " MB"; + } else { + fileSizeString = df.format((double) fileS / 1073741824) + " GB"; + } + return fileSizeString; + } + +} \ No newline at end of file diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/MotorGroupController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/MotorGroupController.java new file mode 100644 index 0000000..d46c749 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/MotorGroupController.java @@ -0,0 +1,155 @@ +package com.jiluo.bolt.controller; + + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.dto.MotorGroupDto; +import com.jiluo.bolt.entity.po.MotorGroup; +import com.jiluo.bolt.entity.vo.MotorGroupVo; +import com.jiluo.bolt.service.*; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 机组信息表(motor_group)表控制层 + * @author Fangy + * @since 2023-04-17 13:26:12 + */ +@RestController +@RequestMapping("api/motorGroup") +public class MotorGroupController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(MotorGroupController.class); + /** + * 服务对象 + */ + @Resource + private PowerStationService powerStationService; + @Resource + private MotorGroupService motorGroupService; + @Resource + private PointService pointService; + @Resource + private DeviceService deviceService; + @Resource + private AlgorithmService algorithmService; + + /** + * 分页查询所有数据 + * + * @param page 分页对象 + * @param motorGroup 查询实体 + * @return 所有数据 + */ + @PostMapping("/selectAll") + public Result selectAll(Page page, @RequestBody MotorGroup motorGroup) { + return new Result(ResultCode.success,this.motorGroupService.page(page, new QueryWrapper<>(motorGroup))); + } + + /** + * 获取下拉框数据 + */ + @PostMapping("/dropDownBox") + public Result dropDownBox(){ + List result = new ArrayList<>(); + motorGroupService.getAll().stream().forEach(item->{ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("powerStationId",item.getPowerStation()); + jsonObject.put("motorGroupId",item.getMotorGroupId()); + jsonObject.put("motorGroupName",item.getName()); + result.add(jsonObject); + }); + return new Result(ResultCode.success,result); + } + + /** + * 条件查询数据 + */ + @PostMapping("/select") + public Result select(@RequestBody MotorGroupDto motorGroupDto) { + Integer total = motorGroupService.selectTotal(motorGroupDto); + if (total.equals(0)){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("total",0); + jsonObject.put("data",null); + return new Result(ResultCode.success,jsonObject); + } + List motorGroups = motorGroupService.select(motorGroupDto); + List motorGroupVos = new ArrayList<>(); + motorGroups.stream().forEach(item->{ + motorGroupVos.add(MotorGroupVo.builder().motorGroupId(item.getMotorGroupId()) + .name(item.getName()) + .powerStationId(item.getPowerStation()) + .powerStation(powerStationService.getByBizId(item.getPowerStation()).getName()) + .pointNum(pointService.getTotalByMotorGroup(item.getMotorGroupId())) + .contact(item.getContact()) + .phone(item.getPhone()) + .gmtCreate(item.getGmtCreate()) + .build()); + }); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("total",total); + jsonObject.put("data",motorGroupVos); + return new Result(ResultCode.success,jsonObject); + } + + + /** + * 新增数据 + * + */ + @PostMapping("/add") + public Result insert(@RequestBody @Valid MotorGroupDto motorGroupDto) { + if (!motorGroupService.add(motorGroupDto)){ + return new Result(ResultCode.operate_failure,"添加失败"); + } + return new Result(ResultCode.success,"添加成功"); + } + + /** + * 修改数据 + * + */ + @PostMapping("/update") + public Result update(@RequestBody @Valid MotorGroupDto motorGroupDto) { + if (!StringUtils.isNotBlank(motorGroupDto.getMotorGroupId())){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + if (!motorGroupService.updateByBizId(motorGroupDto)){ + return new Result(ResultCode.operate_failure,"修改失败"); + } + return new Result(ResultCode.success,"修改成功"); + } + + /** + * 删除数据 + * + */ + @PostMapping("/del") + public Result delete(@RequestBody MotorGroupDto motorGroupDto) { + Map columnMap = new HashMap<>(); + columnMap.put("biz_id",motorGroupDto.getMotorGroupId()); + Map columnMap2 = new HashMap<>(); + columnMap2.put("motor_group",motorGroupDto.getMotorGroupId()); + pointService.removeByMap(columnMap2); + deviceService.removeByMap(columnMap2); + algorithmService.removeByMap(columnMap2); + if (!motorGroupService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + return new Result(ResultCode.success,"删除成功"); + } +} + diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/PointController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/PointController.java new file mode 100644 index 0000000..fb82205 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/PointController.java @@ -0,0 +1,216 @@ +package com.jiluo.bolt.controller; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.dto.PointDto; +import com.jiluo.bolt.entity.po.*; +import com.jiluo.bolt.entity.vo.DropDownVo; +import com.jiluo.bolt.entity.vo.PointVo; +import com.jiluo.bolt.service.*; +import com.jiluo.bolt.util.SystemDateUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 检测点表(point)表控制层 + * @author Fangy + * @since 2023-04-17 13:26:12 + */ +@RestController +@RequestMapping("api/point") +public class PointController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(PointController.class); + /** + * 服务对象 + */ + @Resource + private PointService pointService; + @Resource + private MotorGroupService motorGroupService; + @Resource + private PowerStationService powerStationService; + @Resource + private DeviceService deviceService; + @Resource + private AlgorithmService algorithmService; + + /** + * 查询所有检测点 + */ + @PostMapping("/getAll") + public Result selectAll() { + List powerStations = powerStationService.getAll(); + List motorGroups = motorGroupService.getAll(); + List points = pointService.getAll(); + if (powerStations!=null && powerStations.size()!=0){ + motorGroups.stream().forEach(y->y.setPointList(points.stream().filter(z->z.getMotorGroup().equals(y.getMotorGroupId())).collect(Collectors.toList()))); + powerStations.stream().forEach(x->x.setGroupList(motorGroups.stream().filter(y->y.getPowerStation().equals(x.getPowerStationId())).collect(Collectors.toList()))); + } + return new Result(ResultCode.success,powerStations); + } + + /** + * 修改 检测设备开关 + * @RequestParam String pointId, @RequestParam boolean enableDetect + */ + @PostMapping("/switch") + public Result update(@RequestBody List points) { + points.forEach(point->pointService.updateEnableDetect(point.getPointId(),point.getEnableDetect())); + return new Result(ResultCode.success,"设置成功"); + } + + @PostMapping("/reset") + public Result reset(@RequestBody Point point) { + pointService.updateResetTime(point.getPointId(), SystemDateUtils.getNewDate()); + /** + *把算法生成的那个a2.txt删了 + * String path = "/xxx/xxx/a2.txt"; + * FileUtils fileUtils = new FileUtils(); + * fileUtils.DeleteFolder(path); + */ + return new Result(ResultCode.success,"数据复位成功"); + } + + /** + * 条件查询数据 + */ + @PostMapping("/select") + public Result select(@RequestBody PointDto pointDto) { + Integer total = pointService.selectTotal(pointDto); + if (total.equals(0)){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("total",0); + jsonObject.put("data",null); + return new Result(ResultCode.success,jsonObject); + } + List points = pointService.select(pointDto); + List pointVos = new ArrayList<>(); + points.stream().forEach(item->{ + pointVos.add(PointVo.builder().pointId(item.getPointId()) + .name(item.getName()) + .powerStationId(item.getPowerStation()) + .powerStation(powerStationService.getByBizId(item.getPowerStation()).getName()) + .motorGroupId(item.getMotorGroup()) + .motorGroup(motorGroupService.getByBizId(item.getMotorGroup()).getName()) + .poleNum(item.getPoleNum()) + .boltDetect(item.getBoltDetect().equals(0)) + .lineDetect(item.getLineDetect().equals(0)) + .poleOpenDetect(item.getPoleOpenDetect().equals(0)) + .pointTempDetect(item.getPointTempDetect().equals(0)) + .gmtCreate(item.getGmtCreate()) + .manualTime(item.getManualTime()) + .automaticTime(item.getAutomaticTime()) + .build()); + }); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("total",total); + jsonObject.put("data",pointVos); + return new Result(ResultCode.success,jsonObject); + } + + + /** + * 新增数据 + * + */ + @PostMapping("/add") + public Result insert(@RequestBody @Valid PointDto pointDto) { + if (!pointService.add(pointDto)){ + return new Result(ResultCode.operate_failure,"添加失败"); + } + return new Result(ResultCode.success,"添加成功"); + } + + /** + * 修改数据 + * + */ + @PostMapping("/update") + public Result update(@RequestBody @Valid PointDto pointDto) { + if (!StringUtils.isNotBlank(pointDto.getPointId())){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + if (!pointService.updateByBizId(pointDto)){ + return new Result(ResultCode.operate_failure,"修改失败"); + } + return new Result(ResultCode.success,"修改成功"); + } + + /** + * 删除数据 + * + */ + @PostMapping("/del") + public Result delete(@RequestBody PointDto pointDto) { + Map columnMap = new HashMap<>(); + columnMap.put("biz_id",pointDto.getPointId()); + Map columnMap2 = new HashMap<>(); + columnMap2.put("point",pointDto.getPointId()); + deviceService.removeByMap(columnMap2); + algorithmService.removeByMap(columnMap2); + if (!pointService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + return new Result(ResultCode.success,"删除成功"); + } + + /** + * 修改检测权限 + * + */ + @PostMapping("/updateDetect") + public Result updateDetect(@RequestBody PointDto pointDto) { + if (!StringUtils.isNotBlank(pointDto.getPointId())){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + if (!pointService.updateDetect(pointDto)){ + return new Result(ResultCode.operate_failure,"修改失败"); + } + return new Result(ResultCode.success,"修改成功"); + } + + /** + * 获取下拉框数据 + */ + @PostMapping("/dropDownBox") + public Result dropDownBox(@RequestBody PointDto pointDto){ + if (StringUtils.isBlank(pointDto.getPowerStationId())){ + PowerStation powerStation = powerStationService.getAll().stream().findFirst().orElse(null); + if (powerStation == null) return new Result(ResultCode.success,new ArrayList()); + pointDto.setPowerStationId(powerStation.getPowerStationId()); + } + List dropDownVos = new ArrayList<>(); + pointService.getByPowerStation(pointDto.getPowerStationId()).stream().forEach(item->{ + dropDownVos.add(DropDownVo.builder().powerStationId(item.getPowerStation()) + .motorGroupId(item.getMotorGroup()) + .pointId(item.getPointId()) + .name(motorGroupService.getByBizId(item.getMotorGroup()).getName() + "/" + item.getName()) + .build()); + }); + return new Result(ResultCode.success,dropDownVos); + } + + @PostMapping("/permission") + public Result permission(@RequestBody PointDto pointDto) { + Map permission = new HashMap<>(); + Point point = pointService.getByBizId(pointDto.getPointId()); + permission.put("bolt", point.getBoltDetect() == 0 ? true : false); + permission.put("line", point.getLineDetect() == 0 ? true : false); + permission.put("pole", point.getPoleOpenDetect() == 0 ? true : false); + permission.put("temperature", point.getPointTempDetect() == 0 ? true : false); + return new Result(ResultCode.success, permission); + } +} + diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/PowerStationController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/PowerStationController.java new file mode 100644 index 0000000..e85686f --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/PowerStationController.java @@ -0,0 +1,147 @@ +package com.jiluo.bolt.controller; + + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.dto.PowerStationDto; +import com.jiluo.bolt.entity.po.PowerStation; +import com.jiluo.bolt.entity.vo.PowerStationVo; +import com.jiluo.bolt.service.*; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 电站表(power_station)表控制层 + * @author Fangy + * @since 2023-04-17 13:26:12 + */ +@RestController +@RequestMapping("api/powerStation") +public class PowerStationController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(PowerStationController.class); + /** + * 服务对象 + */ + @Resource + private PowerStationService powerStationService; + @Resource + private MotorGroupService motorGroupService; + @Resource + private PointService pointService; + @Resource + private DeviceService deviceService; + @Resource + private AlgorithmService algorithmService; + + /** + * 获取下拉框数据 + */ + @PostMapping("/dropDownBox") + public Result dropDownBox(){ + List result = new ArrayList<>(); + powerStationService.getAll().stream().forEach(item->{ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("powerStationId",item.getPowerStationId()); + jsonObject.put("powerStationName",item.getName()); + result.add(jsonObject); + }); + return new Result(ResultCode.success,result); + } + + /** + * powerStation条件查询数据 + */ + @PostMapping("/select") + public Result select(@RequestBody PowerStationDto powerStationDto) { + Integer total = powerStationService.selectTotal(powerStationDto); + if (total.equals(0)){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("total",0); + jsonObject.put("data",null); + return new Result(ResultCode.success,jsonObject); + } + List powerStations = powerStationService.select(powerStationDto); + List powerStationVos = new ArrayList<>(); + powerStations.stream().forEach(item->{ + powerStationVos.add(PowerStationVo.builder().powerStationId(item.getPowerStationId()) + .name(item.getName()) + .address(item.getAddress()) + .groupNum(motorGroupService.getTotalByPowerStation(item.getPowerStationId())) + .contact(item.getContact()) + .phone(item.getPhone()) + .gmtCreate(item.getGmtCreate()) + .introduction(item.getIntroduction()) + .build()); + }); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("total",total); + jsonObject.put("data",powerStationVos); + return new Result(ResultCode.success,jsonObject); + } + + + /** + * 新增数据 + * + * @param powerStationDto 实体对象 + * @return 新增结果 + */ + @PostMapping("/add") + public Result insert(@RequestBody @Valid PowerStationDto powerStationDto) { + if (!powerStationService.add(powerStationDto)){ + return new Result(ResultCode.operate_failure,"添加失败"); + } + return new Result(ResultCode.success,"添加成功"); + } + + /** + * 修改数据 + * + * @param powerStationDto 实体对象 + * @return 修改结果 + */ + @PostMapping("/update") + public Result update(@RequestBody @Valid PowerStationDto powerStationDto) { + if (!StringUtils.isNotBlank(powerStationDto.getPowerStationId())){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + if (!powerStationService.updateByBizId(powerStationDto)){ + return new Result(ResultCode.operate_failure,"修改失败"); + } + return new Result(ResultCode.success,"修改成功"); + } + + /** + * 删除数据 + * + * @param + * @return 删除结果 + */ + @PostMapping("/del") + public Result delete(@RequestBody PowerStationDto powerStationDto) { + Map columnMap = new HashMap<>(); + columnMap.put("biz_id",powerStationDto.getPowerStationId()); + Map columnMap2 = new HashMap<>(); + columnMap2.put("power_station",powerStationDto.getPowerStationId()); + motorGroupService.removeByMap(columnMap2); + pointService.removeByMap(columnMap2); + deviceService.removeByMap(columnMap2); + algorithmService.removeByMap(columnMap2); + if (!powerStationService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + return new Result(ResultCode.success,"删除成功"); + } +} + diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/TemperatureController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/TemperatureController.java new file mode 100644 index 0000000..7848bbd --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/TemperatureController.java @@ -0,0 +1,39 @@ +package com.jiluo.bolt.controller; + +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.common.DetectResult; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.engine.Engine; +import com.jiluo.bolt.entity.dto.TempSenserDto; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/08/04/16:39 + * @Description: + */ +@RestController +@RequestMapping("api") +public class TemperatureController extends ApiController { + private static final Logger logger = LoggerFactory.getLogger(TemperatureController.class); + + @PostMapping("/temperature/dataCallback") + public Result dataCallback(@RequestBody TempSenserDto tempSenserDto){ + List detectResultList = new ArrayList<>(); + detectResultList.add(DetectResult.builder().zone(1).position(1).type(DefectType.temperature.name()).value(tempSenserDto.getValue().floatValue()).img("-").alarm(0).build()); + Engine.callback(tempSenserDto.getJobId(),detectResultList); + return new Result(ResultCode.success); + } +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/UserController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/UserController.java new file mode 100644 index 0000000..f1859a3 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/UserController.java @@ -0,0 +1,271 @@ +package com.jiluo.bolt.controller; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.common.DefectType; +import com.jiluo.bolt.domain.Result; +import com.jiluo.bolt.domain.ResultCode; +import com.jiluo.bolt.entity.dto.PermissionDto; +import com.jiluo.bolt.entity.dto.RoleDto; +import com.jiluo.bolt.entity.dto.UserDto; +import com.jiluo.bolt.entity.po.*; +import com.jiluo.bolt.entity.vo.PermissionVo; +import com.jiluo.bolt.entity.vo.RoleVo; +import com.jiluo.bolt.entity.vo.UserVo; +import com.jiluo.bolt.service.RoleItemService; +import com.jiluo.bolt.service.RoleService; +import com.jiluo.bolt.service.RoleValueService; +import com.jiluo.bolt.service.UserService; +import com.jiluo.bolt.util.AesEncryptUtil; +import com.jiluo.bolt.util.JwtUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 用户信息表(user)表控制层 + * @author Fangy + * @since 2023-04-17 13:26:12 + */ +@RestController +@RequestMapping("api/user") +public class UserController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(UserController.class); + + /** + * 服务对象 + */ + @Resource + private UserService userService; + @Resource + private RoleService roleService; + @Resource + private RoleValueService roleValueService; + @Resource + private RoleItemService roleItemService; + @Autowired + JwtUtils jwtUtils; + @Value("${aesKey}") + String aesKey; + + /** + * 登录功能 + */ + @PostMapping("/login") + public Object login(@RequestBody UserDto userDto,HttpServletRequest request, HttpServletResponse response) throws Exception { + User user = null; + if ((userDto.getUid()==null || StringUtils.isBlank(userDto.getUid())) && (userDto.getPassword()==null || StringUtils.isBlank(userDto.getPassword()))){ + user = userService.getByUid("user_defaul"); + if (user == null){ + user = userService.select(new UserDto()).get(0); + } + }else { + user = userService.getByUid(userDto.getUid()); + if(user == null || !StringUtils.equals(userDto.getPassword(), AesEncryptUtil.aesDecrypt(user.getPassword(),aesKey))) { + return Result.INVALID_USER_PASS; + } + } + List roleValueList = roleValueService.getByRoleId(user.getRole()); + List roles = roleItemService.getByBizId(roleValueList.stream().map(RoleValue::getItemId).collect(Collectors.toList())) + .stream().map(x -> RoleVo.builder().roleId(x.getBizId()).name(x.getName()).scope(x.getScope()).type(x.getType()).value(roleValueList.stream() + .filter(r -> StringUtils.equalsIgnoreCase(r.getItemId(), x.getBizId())).findFirst().map(RoleValue::getItemValue).orElse(null)).build()) + .collect(Collectors.toList()); + response.setHeader("Authorization", jwtUtils.getToken(userDto.getUid(),request.getSession().getId(),userDto.getClientVersion(),userDto.getClientType())); + roles.stream().forEach(x->{ + if (x.getRoleId().equals("detect_pole")){ + x.setValue(JSONObject.parseArray(x.getValue().toString())); + }}); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("userId",user.getBizId()); + jsonObject.put("userName",user.getUserName()); + jsonObject.put("role",roleService.getByRoleId(user.getRole()).getRoleName()); + jsonObject.put("permissionList",roles); + return jsonObject; + } + + /** + * 条件查询-角色数据 + */ + @PostMapping("/selectRole") + public Result selectRole() { + List roles = roleService.select(new RoleDto()); + List roleVos = new ArrayList<>(); + roles.stream().forEach(item->{ + JSONObject roleVo = new JSONObject(); + roleVo.put("roleId",item.getRoleId()); + roleVo.put("roleName",item.getRoleName()); + roleVos.add(roleVo); + }); + return new Result(ResultCode.success,roleVos); + } + + + /** + * 新增-角色数据 + */ + @PostMapping("/addRole") + public Result insertRole(@RequestBody @Valid RoleDto roleDto) { + roleDto.setRoleId(roleService.add(roleDto)); + List roleValues = new ArrayList<>(); + roleItemService.getAll().stream().forEach(roleItem -> { + RoleValue roleValue = new RoleValue(); + String item_value = new String(); + if (roleItem.getType().equals(0)){ + item_value = "true"; + }else if (roleItem.getType().equals(1)){ + item_value = JSONObject.toJSONString(DefectType.values()); + } + roleValue.setRoleId(roleDto.getRoleId()) + .setItemId(roleItem.getBizId()) + .setItemValue(item_value); + roleValues.add(roleValue); + }); + if (!roleValueService.saveBatch(roleValues)){ + return new Result(ResultCode.operate_failure,"添加失败"); + } + return new Result(ResultCode.success,"添加成功"); + } + + /** + * 修改-角色数据 + */ + @PostMapping("/updateRole") + public Result updateRole(@RequestBody @Valid RoleDto roleDto) { + if (!StringUtils.isNotBlank(roleDto.getRoleId())){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + if (!roleService.updateByBizId(roleDto)){ + return new Result(ResultCode.operate_failure,"修改失败"); + } + return new Result(ResultCode.success,"修改成功"); + } + + /** + * 删除-角色数据 + */ + @PostMapping("/deleteRole") + public Result deleteRole(@RequestBody RoleDto roleDto) { + Map columnMap = new HashMap<>(); + columnMap.put("role_id",roleDto.getRoleId()); + Map columnMap2 = new HashMap<>(); + columnMap2.put("role",roleDto.getRoleId()); + if (!roleService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + roleValueService.removeByMap(columnMap); + userService.removeByMap(columnMap2); + return new Result(ResultCode.success,"删除成功"); + } + + /** + * 条件查询-用户数据 + */ + @PostMapping("/selectUser") + public Result selectUser(@RequestBody UserDto userDto) { + List users = userService.select(userDto); + List userVos = new ArrayList<>(); + users.stream().forEach(item->{ + try { + userVos.add(UserVo.builder().id(item.getId()) + .uid(item.getBizId()) + .userName(item.getUserName()) + .role(roleService.getByRoleId(item.getRole()).getRoleName()) + .gmtCreate(item.getGmtCreate()) + .password(AesEncryptUtil.aesDecrypt(item.getPassword(), aesKey)).build()); + } catch (Exception e) { + logger.error("[UserController] selectUser:", e.getMessage(), e); + } + }); + return new Result(ResultCode.success,userVos); + } + + /** + * 新增-用户数据 + */ + @PostMapping("/addUser") + public Result insertUser(@RequestBody @Valid UserDto userDto) throws Exception { + userDto.setPassword(AesEncryptUtil.aesEncrypt(userDto.getPassword(),aesKey)); + if (!userService.add(userDto)){ + return new Result(ResultCode.operate_failure,"添加失败"); + } + return new Result(ResultCode.success,"添加成功"); + } + + /** + * 修改-用户数据 + */ + @PostMapping("/updateUser") + public Result updateUser(@RequestBody @Valid UserDto userDto) throws Exception { + if (!StringUtils.isNotBlank(userDto.getUid()) || userDto.getId().equals(0)){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + userDto.setPassword(AesEncryptUtil.aesEncrypt(userDto.getPassword(),aesKey)); + if (!userService.updateByBizId(userDto)){ + return new Result(ResultCode.operate_failure,"修改失败"); + } + return new Result(ResultCode.success,"修改成功"); + } + + /** + * 删除-用户数据 + */ + @PostMapping("/deleteUser") + public Result deleteUser(@RequestBody UserDto userDto) { + Map columnMap = new HashMap<>(); + columnMap.put("biz_id",userDto.getUid()); + if (!userService.removeByMap(columnMap)){ + return new Result(ResultCode.operate_failure,"删除失败"); + } + return new Result(ResultCode.success,"删除成功"); + } + + /** + * 条件查询-角色权限数据 + */ + @PostMapping("/selectPermission") + public Result selectPermission(@RequestBody PermissionDto permissionDto) { + List roleValues = roleValueService.select(permissionDto); + List permissionVos = new ArrayList<>(); + roleValues.stream().filter(x->!x.getItemId().equals("detect_pole")).forEach(item->{ + permissionVos.add(PermissionVo.builder().permissionId(item.getItemId()) + .permissionName(roleItemService.getByBizId(item.getItemId()).getName()) + .value(Boolean.parseBoolean(item.getItemValue())) + .scope(roleItemService.getByBizId(item.getItemId()).getScope()).build()); + }); + return new Result(ResultCode.success,permissionVos); + } + + /** + * 修改-角色权限数据 + */ + @PostMapping("/updatePermission") + public Result updatePermission(@RequestBody @Valid PermissionDto permissionDto) { + if (!StringUtils.isNotBlank(permissionDto.getRoleId())){ + return new Result(ResultCode.illegal_argument,"缺失id参数"); + } + permissionDto.getPermissionList().stream().forEach(item->{ + RoleValue roleValue = new RoleValue(); + roleValue.setRoleId(permissionDto.getRoleId()) + .setItemId(item.getPermissionId()) + .setItemValue(item.getValue().toString()); + roleValueService.updateByItemId(roleValue); + }); + return new Result(ResultCode.success,"修改成功"); + } + + +} + diff --git a/bolt-web/src/main/java/com/jiluo/bolt/controller/WebSocketController.java b/bolt-web/src/main/java/com/jiluo/bolt/controller/WebSocketController.java new file mode 100644 index 0000000..c0c7a3c --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/controller/WebSocketController.java @@ -0,0 +1,38 @@ +package com.jiluo.bolt.controller; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.api.ApiController; +import com.jiluo.bolt.websocket.WebSocket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/10/16/10:55 + * @Description: + */ +@RestController +@RequestMapping("api/websocket") +public class WebSocketController extends ApiController { + + private static final Logger logger = LoggerFactory.getLogger(WebSocketController.class); + + @Resource + WebSocket webSocket; + + // 每30秒执行一次,维持websocket连接 + @Scheduled(fixedRate = 30000) + public void pointStatus(){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("title","test"); + jsonObject.put("value","keep connect /30s "); + webSocket.sendAllMessage(jsonObject.toJSONString()); + } +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/domain/Result.java b/bolt-web/src/main/java/com/jiluo/bolt/domain/Result.java new file mode 100644 index 0000000..4fb561c --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/domain/Result.java @@ -0,0 +1,63 @@ +package com.jiluo.bolt.domain; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.io.Serializable; + +/** + * 统一输出定义 + * + * @author caoyiwen + * @creation 2015年9月2日 + */ +@ToString +public class Result implements Serializable { + + private static final long serialVersionUID = 1L; + public static final Result NOT_SUPPORT = new Result(ResultCode.not_support_operate); + public static final Result NOT_PRIVILEGED = new Result(ResultCode.not_privileged); + public static final Result INVALID_USER_PASS = new Result(ResultCode.invalidUserOrPassword); + public static final Result ILLEGAL_ARGUMENT = new Result(ResultCode.illegal_argument); + public static final Result FAILURE = new Result(ResultCode.operate_failure); + public static final Result ERROR = new Result(ResultCode.system_error); + public static final Result NOT_LOGIN = new Result(ResultCode.need_login); + public static final Result SUCCESS = new Result(ResultCode.success); + + @Getter + @Setter + private int code; + @Getter + @Setter + private String desc; + @Getter + @Setter + private Object body; + + public Result(ResultCode resultCode) { + setCode(resultCode.getCode()); + setDesc(resultCode.getDesc()); + } + + public Result(int code, String msg) { + setCode(code); + setDesc(msg); + } + + /** + * 使用通用结果码生成对象 + */ + public Result(ResultCode resultCode, Object body) { + setCode(resultCode.getCode()); + setDesc(resultCode.getDesc()); + setBody(body); + } + + /** + * 判断执行结果是否成功 + */ + public boolean isSuccess() { + return ResultCode.success.getCode() == code; + } +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/domain/ResultCode.java b/bolt-web/src/main/java/com/jiluo/bolt/domain/ResultCode.java new file mode 100644 index 0000000..3106e1c --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/domain/ResultCode.java @@ -0,0 +1,32 @@ +package com.jiluo.bolt.domain; + +import lombok.Getter; + +/** + * 统一结果码定义 + * + * @author caoyiwen + * @creation 2015年9月2日 + */ +public enum ResultCode { + + success(200, "成功"), + + invalidUserOrPassword(300, "用户名或者密码错误!"), + illegal_argument(400, "参数错误!"), + need_login(401, "未登录!"), + not_support_operate(404, "不支持的请求!"), + not_privileged(405, "无权限执行该操作!"), + system_error(500, "系统异常!"), + operate_failure(503, "操作失败!"); + + @Getter + private final int code; + @Getter + private final String desc; + + ResultCode(int code, String desc) { + this.code = code; + this.desc = desc; + } +} diff --git a/bolt-web/src/main/java/com/jiluo/bolt/exception/BoltException.java b/bolt-web/src/main/java/com/jiluo/bolt/exception/BoltException.java new file mode 100644 index 0000000..8979896 --- /dev/null +++ b/bolt-web/src/main/java/com/jiluo/bolt/exception/BoltException.java @@ -0,0 +1,21 @@ +package com.jiluo.bolt.exception; + +import com.jiluo.bolt.domain.Result; +import lombok.Getter; + +/** + * Created with IntelliJ IDEA. + * + * @Author: fy + * @Date: 2023/05/04/18:08 + * @Description: + */ +@Getter +public class BoltException extends RuntimeException{ + + private final Result result; + + public BoltException(Result result) { + this.result = result; + } +} diff --git a/bolt-web/src/main/resources/application-dev.yml b/bolt-web/src/main/resources/application-dev.yml new file mode 100644 index 0000000..188fc83 --- /dev/null +++ b/bolt-web/src/main/resources/application-dev.yml @@ -0,0 +1,28 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/bolt2?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai + username: root #用户名 + password: 123456 #密码 + +minio: + endpoint: http://47.109.27.8/ #Minio服务ip + port: 9000 + accessKey: minioadmin #访问的key + secretKey: minioadmin #访问的秘钥 + secure: false + bucketName: "defaultbucket" #默认存储桶名称 + time: "0 50 23 * * *" # 每天23:50 上传当天的图片 + +defect_work_dir: "D:/data/record/" #图片存储路径 + +defect_img_dir: "/record/" #前端图片url头 + +defect_csv_dir: "D:/data/work/permanent/" #检测数据本地csv长期存储路径 + +max_file_keep_time: 60 #数据库检测数据存储最大天数 + +device: + camera: +# driver: "Lucid,Baumer,Basler" + driver: "" diff --git a/bolt-web/src/main/resources/application-prod.yml b/bolt-web/src/main/resources/application-prod.yml new file mode 100644 index 0000000..8aa2410 --- /dev/null +++ b/bolt-web/src/main/resources/application-prod.yml @@ -0,0 +1,28 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/bolt2?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai + username: root #用户名 + password: 123456 #密码 + +minio: + endpoint: http://192.168.210.109/ #Minio服务ip + port: 9000 + accessKey: AKIAIOSFODNN7EXAMPLE #访问的key + secretKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY #访问的秘钥 + secure: false + bucketName: "au0000000005" #默认存储桶名称 李家峡5号机 + time: "0 50 23 * * *" # 每天23:50 上传当天的图片 + +defect_work_dir: "/data/record/" #图片存储路径 + +defect_img_dir: "/record/" #前端图片url头 + +defect_csv_dir: "/data/work/permanent/" #检测数据本地csv长期存储路径 + +max_file_keep_time: 60 #数据库检测数据存储最大天数 + +device: + camera: + # driver: "Lucid,Baumer,Basler" + driver: "" diff --git a/bolt-web/src/main/resources/application.properties b/bolt-web/src/main/resources/application.properties new file mode 100644 index 0000000..2c06833 --- /dev/null +++ b/bolt-web/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.jackson.date-format=yyyy-MM-dd HH:mm:ss +spring.jackson.time-zone=Asia/Shanghai diff --git a/bolt-web/src/main/resources/application.yml b/bolt-web/src/main/resources/application.yml new file mode 100644 index 0000000..05c0307 --- /dev/null +++ b/bolt-web/src/main/resources/application.yml @@ -0,0 +1,28 @@ +spring: + application: + name: bolt-web + profiles: + active: prod + web: + resources: + static-locations: file:doc,file:static,file:/data/img + + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + +mybatis: + mapper-locations: classpath:mapper/*.xml + +aesKey: "dbf13279f5bc85b038cbc9ee21dfbc03" + +jwt: + config: + header: Authorization + refreshTime: 3600000 # 刷新时间 1小时 + expiresTime: 7200000 # 过期 2小时 + secretKey: jiluo2019 + +server: + port: 8081 diff --git a/bolt-web/src/main/resources/logback-spring.xml b/bolt-web/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..b58dfd1 --- /dev/null +++ b/bolt-web/src/main/resources/logback-spring.xml @@ -0,0 +1,152 @@ + + + + + + + bolt + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${log.level} + + + ${CONSOLE_LOG_PATTERN} + + UTF-8 + + + + + + + + ${log.path}/${log.name}/${log.name}-info.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %.-${log.length}msg%n + UTF-8 + + + + ${log.path}/${log.name}/${log.name}-info-%d{yyyy-MM-dd}.%i.log + + ${log.max.file} + + ${log.max.history} + + ${log.max.size} + + + + INFO + ACCEPT + DENY + + + + + + ${log.path}/${log.name}/${log.name}-warn.log + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %.-${log.length}msg%n + UTF-8 + + + ${log.path}/${log.name}/${log.name}-warn-%d{yyyy-MM-dd}.%i.log + ${log.max.file} + ${log.max.history} + ${log.max.size} + + + WARN + ACCEPT + DENY + + + + + + ${log.path}/${log.name}/${log.name}-error.log + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %.-${log.length}msg%n + UTF-8 + + + ${log.path}/${log.name}/${log.name}-error-%d{yyyy-MM-dd}.%i.log + ${log.max.file} + ${log.max.history} + ${log.max.size} + + + ERROR + ACCEPT + DENY + + + + + + ${log.path}/${log.name}/${log.name}-protocol.log + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %.-${log.length}msg%n + UTF-8 + + + ${log.path}/${log.name}/${log.name}-protocol-%d{yyyy-MM-dd}.%i.log + ${log.max.file} + ${log.max.history} + ${log.max.size} + + + + + + ${log.path}/${log.name}/${log.name}-message.log + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %.-${log.length}msg%n + UTF-8 + + + ${log.path}/${log.name}/${log.name}-message-%d{yyyy-MM-dd}.%i.log + ${log.max.file} + ${log.max.history} + ${log.max.size} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/console.log b/console.log new file mode 100644 index 0000000..d1e1924 --- /dev/null +++ b/console.log @@ -0,0 +1,213 @@ + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v2.7.10) + +2023-10-17 14:58:30.038 INFO 27232 --- [kground-preinit] o.h.validator.internal.util.Version : HV000001: Hibernate Validator 6.0.20.Final +2023-10-17 14:58:30.051 INFO 27232 --- [ main] com.jiluo.bolt.BoltServerApplication : Starting BoltServerApplication v0.0.1-SNAPSHOT using Java 1.8.0_382 on iZ2vca7ty2zom016x70b97Z with PID 27232 (/export/bolt-web/bolt-web-0/bolt-web-0.jar started by root in /export/bolt-web/bolt-web-0) +2023-10-17 14:58:30.052 INFO 27232 --- [ main] com.jiluo.bolt.BoltServerApplication : No active profile set, falling back to 1 default profile: "default" +2023-10-17 14:58:31.688 WARN 27232 --- [ main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.jiluo.bolt]' package. Please check your configuration. +2023-10-17 14:58:31.902 WARN 27232 --- [ main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.jiluo.blot.mapper]' package. Please check your configuration. +2023-10-17 14:58:32.757 INFO 27232 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http) +2023-10-17 14:58:32.774 INFO 27232 --- [ main] o.a.coyote.http11.Http11NioProtocol : Initializing ProtocolHandler ["http-nio-8081"] +2023-10-17 14:58:32.774 INFO 27232 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] +2023-10-17 14:58:32.775 INFO 27232 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.73] +2023-10-17 14:58:32.877 INFO 27232 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext +2023-10-17 14:58:32.877 INFO 27232 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2691 ms + _ _ |_ _ _|_. ___ _ | _ +| | |\/|_)(_| | |_\ |_)||_|_\ + / | + 3.4.0 +2023-10-17 14:58:34.620 INFO 27232 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... +2023-10-17 14:58:35.119 INFO 27232 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. +2023-10-17 14:58:35.436 INFO 27232 --- [ main] com.jiluo.bolt.export.Export : [Export] init AUTO_EXPORT_PATH=/data/report/; AUTO_EXPORT_PERIOD=7 +2023-10-17 14:58:37.698 INFO 27232 --- [ main] o.a.coyote.http11.Http11NioProtocol : Starting ProtocolHandler ["http-nio-8081"] +2023-10-17 14:58:37.720 INFO 27232 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path '' +2023-10-17 14:58:37.739 INFO 27232 --- [ main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing +2023-10-17 14:58:37.756 INFO 27232 --- [ main] com.jiluo.bolt.BoltServerApplication : Started BoltServerApplication in 8.747 seconds (JVM running for 9.51) +2023-10-17 14:58:41.630 INFO 27232 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' +2023-10-17 14:58:41.630 INFO 27232 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' +2023-10-17 14:58:41.632 INFO 27232 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms +2023-10-17 14:58:41.683 INFO 27232 --- [nio-8081-exec-1] com.jiluo.bolt.websocket.WebSocket : admin +2023-10-17 14:58:41.684 INFO 27232 --- [nio-8081-exec-1] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的连接,总数为:1 +2023-10-17 14:58:42.799 INFO 27232 --- [pool-4-thread-1] com.jiluo.bolt.engine.EngineDriver : plc连接成功,地址为:mock_plcmock_plc +2023-10-17 14:58:42.800 INFO 27232 --- [pool-4-thread-1] com.jiluo.bolt.engine.EngineDriver : plc连接成功,地址为:mock_plc192.168.1.60 +2023-10-17 14:58:42.801 INFO 27232 --- [pool-4-thread-1] com.jiluo.bolt.engine.EngineDriver : 温度传感器创建成功! 设备id:temperature_sensor_2228774264146432 +2023-10-17 14:58:43.304 INFO 27232 --- [nio-8081-exec-2] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的消息:{"status":true,"pointId":"POINT_2206768181389824"} +2023-10-17 14:58:43.308 ERROR 27232 --- [nio-8081-exec-2] com.jiluo.bolt.websocket.WebSocket : [WebSocket] onError: + +java.lang.NullPointerException: null + at com.jiluo.bolt.websocket.WebSocket.sendStatusMessage1(WebSocket.java:165) + at com.jiluo.bolt.websocket.WebSocket.onMessage(WebSocket.java:84) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:104) + at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:415) + at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:130) + at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:515) + at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:301) + at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) + at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:85) + at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:185) + at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:164) + at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:157) + at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:57) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) + at java.lang.Thread.run(Thread.java:750) + +2023-10-17 14:58:43.312 INFO 27232 --- [nio-8081-exec-2] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】连接断开,总数为:0 +2023-10-17 14:58:56.667 INFO 27232 --- [nio-8081-exec-6] com.jiluo.bolt.websocket.WebSocket : amind +2023-10-17 14:58:56.667 INFO 27232 --- [nio-8081-exec-6] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的连接,总数为:1 +2023-10-17 14:58:56.736 INFO 27232 --- [nio-8081-exec-8] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的消息:{"status":true,"pointId":"POINT_2206768181389824"} +2023-10-17 14:58:56.736 ERROR 27232 --- [nio-8081-exec-8] com.jiluo.bolt.websocket.WebSocket : [WebSocket] onError: + +java.lang.NullPointerException: null + at com.jiluo.bolt.websocket.WebSocket.sendStatusMessage1(WebSocket.java:165) + at com.jiluo.bolt.websocket.WebSocket.onMessage(WebSocket.java:84) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:104) + at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:415) + at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:130) + at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:515) + at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:301) + at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) + at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:85) + at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:185) + at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:164) + at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:157) + at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:57) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) + at java.lang.Thread.run(Thread.java:750) + +2023-10-17 14:58:56.737 INFO 27232 --- [nio-8081-exec-8] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】连接断开,总数为:0 +2023-10-17 14:59:03.810 INFO 27232 --- [nio-8081-exec-2] com.jiluo.bolt.websocket.WebSocket : admin +2023-10-17 14:59:03.810 INFO 27232 --- [nio-8081-exec-2] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的连接,总数为:1 +2023-10-17 14:59:26.751 INFO 27232 --- [nio-8081-exec-5] com.jiluo.bolt.websocket.WebSocket : amind +2023-10-17 14:59:26.751 INFO 27232 --- [nio-8081-exec-5] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的连接,总数为:2 +2023-10-17 14:59:26.799 INFO 27232 --- [nio-8081-exec-9] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的消息:{"status":true,"pointId":"POINT_2206768181389824"} +2023-10-17 14:59:26.799 ERROR 27232 --- [nio-8081-exec-9] com.jiluo.bolt.websocket.WebSocket : [WebSocket] onError: + +java.lang.NullPointerException: null + at com.jiluo.bolt.websocket.WebSocket.sendStatusMessage1(WebSocket.java:165) + at com.jiluo.bolt.websocket.WebSocket.onMessage(WebSocket.java:84) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:104) + at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:415) + at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:130) + at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:515) + at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:301) + at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) + at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:85) + at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:185) + at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:164) + at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:157) + at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:57) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) + at java.lang.Thread.run(Thread.java:750) + +2023-10-17 14:59:26.800 INFO 27232 --- [nio-8081-exec-9] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】连接断开,总数为:1 +2023-10-17 14:59:32.533 INFO 27232 --- [io-8081-exec-10] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的消息:{"status":true,"pointId":"POINT_2206768181389824"} +2023-10-17 14:59:32.533 ERROR 27232 --- [io-8081-exec-10] com.jiluo.bolt.websocket.WebSocket : [WebSocket] onError: + +java.lang.NullPointerException: null + at com.jiluo.bolt.websocket.WebSocket.sendStatusMessage1(WebSocket.java:165) + at com.jiluo.bolt.websocket.WebSocket.onMessage(WebSocket.java:84) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:104) + at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:415) + at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:130) + at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:515) + at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:301) + at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) + at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:85) + at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:185) + at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:164) + at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:157) + at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:57) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) + at java.lang.Thread.run(Thread.java:750) + +2023-10-17 14:59:32.534 INFO 27232 --- [io-8081-exec-10] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】连接断开,总数为:0 +2023-10-17 15:00:10.007 WARN 27232 --- [nio-8081-exec-2] o.s.web.servlet.PageNotFound : No mapping for GET /index.php +2023-10-17 15:00:10.007 WARN 27232 --- [nio-8081-exec-3] o.s.web.servlet.PageNotFound : No mapping for GET / +2023-10-17 15:00:10.034 WARN 27232 --- [nio-8081-exec-4] o.s.web.servlet.PageNotFound : No mapping for GET /login/stylesheets/theme.css +2023-10-17 15:00:10.052 WARN 27232 --- [nio-8081-exec-6] o.s.web.servlet.PageNotFound : No mapping for GET /public/stylesheets/theme.css +2023-10-17 15:00:10.062 WARN 27232 --- [nio-8081-exec-5] o.s.web.servlet.PageNotFound : No mapping for GET /chs/js/lang_zh_tw.js +2023-10-17 15:00:10.065 WARN 27232 --- [nio-8081-exec-7] o.s.web.servlet.PageNotFound : No mapping for GET /stylesheets/theme.css +2023-10-17 15:00:10.073 WARN 27232 --- [nio-8081-exec-9] o.s.web.servlet.PageNotFound : No mapping for GET /customer/js/lang_zh_tw.js +2023-10-17 15:00:10.076 WARN 27232 --- [nio-8081-exec-8] o.s.web.servlet.PageNotFound : No mapping for GET /index.php +2023-10-17 15:00:10.087 WARN 27232 --- [nio-8081-exec-1] o.s.web.servlet.PageNotFound : No mapping for GET /ips/index.php +2023-10-17 15:00:16.454 INFO 27232 --- [io-8081-exec-10] com.jiluo.bolt.websocket.WebSocket : admin +2023-10-17 15:00:16.455 INFO 27232 --- [io-8081-exec-10] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的连接,总数为:1 +2023-10-17 15:00:21.494 INFO 27232 --- [nio-8081-exec-4] com.jiluo.bolt.websocket.WebSocket : amind +2023-10-17 15:00:21.495 INFO 27232 --- [nio-8081-exec-4] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的连接,总数为:2 +2023-10-17 15:00:21.564 INFO 27232 --- [nio-8081-exec-8] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】有新的消息:{"status":true,"pointId":"POINT_2206768181389824"} +2023-10-17 15:00:21.564 ERROR 27232 --- [nio-8081-exec-8] com.jiluo.bolt.websocket.WebSocket : [WebSocket] onError: + +java.lang.NullPointerException: null + at com.jiluo.bolt.websocket.WebSocket.sendStatusMessage1(WebSocket.java:165) + at com.jiluo.bolt.websocket.WebSocket.onMessage(WebSocket.java:84) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:104) + at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:415) + at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:130) + at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:515) + at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:301) + at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) + at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:85) + at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:185) + at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:164) + at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:157) + at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:57) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) + at java.lang.Thread.run(Thread.java:750) + +2023-10-17 15:00:21.565 INFO 27232 --- [nio-8081-exec-8] com.jiluo.bolt.websocket.WebSocket : 【websocket消息】连接断开,总数为:1 diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..8a8fb22 --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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 + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..1d8ab01 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. 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, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b2de94d --- /dev/null +++ b/pom.xml @@ -0,0 +1,318 @@ + + + 4.0.0 + pom + + bolt-dao + bolt-web + bolt-core + bolt-api + bolt-kernel + + + org.springframework.boot + spring-boot-starter-parent + 2.7.10 + + + com.jiluo.bolt + bolt-server + 0.0.1-SNAPSHOT + bolt-server + bolt-server + + 1.8 + + 2.7.10 + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + + org.apache.httpcomponents + httpclient + 4.5.6 + + + + javax.validation + validation-api + 2.0.1.Final + + + + org.hibernate.validator + hibernate-validator + 6.0.20.Final + + + + + net.java.dev.jna + jna + 5.12.1 + + + + + com.baomidou + mybatis-plus-boot-starter + 3.1.0 + + + com.baomidou + mybatis-plus-generator + 3.4.0 + + + + com.github.yulichang + mybatis-plus-join + 1.2.4 + + + + + mysql + mysql-connector-java + 8.0.27 + + + org.springframework.boot + spring-boot-starter-jdbc + ${spring-boot.version} + + + + + org.projectlombok + lombok + 1.18.12 + provided + + + + + io.springfox + springfox-boot-starter + 3.0.0 + + + + + org.springframework.boot + spring-boot-devtools + true + ${spring-boot.version} + + + + + org.slf4j + slf4j-api + 1.7.25 + + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + + commons-codec + commons-codec + 1.15 + + + + commons-io + commons-io + 2.6 + + + + + com.auth0 + java-jwt + 3.10.3 + + + + + cn.hutool + hutool-all + 5.4.4 + + + + org.springframework.boot + spring-boot-starter-validation + ${spring-boot.version} + + + + + org.springframework.boot + spring-boot-starter-websocket + ${spring-boot.version} + + + + + com.alibaba + fastjson + 1.2.41 + + + + com.fasterxml.jackson.core + jackson-core + 2.10.1 + + + + com.fasterxml.jackson.core + jackson-databind + 2.10.1 + + + + + com.google.guava + guava + 31.1-jre + + + + + com.infiniteautomation + modbus4j + 3.0.3 + + + + + io.netty + netty-all + 4.1.42.Final + + + + com.squareup.okhttp3 + okhttp + 4.3.1 + + + + com.opencsv + opencsv + 5.6 + + + + com.deepoove + poi-tl + 1.9.1 + + + + + com.alibaba + easyexcel + 2.0.5 + + + + + io.minio + minio + 7.0.2 + + + + org.springframework + spring-test + 3.0.7.RELEASE + + + + + + + + org.projectlombok + lombok + provided + + + + org.slf4j + slf4j-api + + + + + org.apache.commons + commons-lang3 + + + + commons-codec + commons-codec + + + + com.alibaba + fastjson + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + commons-io + commons-io + + + + + + + + false + + + true + + ias-snapshots + Infinite Automation Snapshot Repository + https://maven.mangoautomation.net/repository/ias-snapshot/ + + + + true + + + false + + ias-releases + Infinite Automation Release Repository + https://maven.mangoautomation.net/repository/ias-release/ + + + +