Explorar el Código

海鑫设备接口

yingp hace 6 años
padre
commit
e39b0c4a5b
Se han modificado 65 ficheros con 3794 adiciones y 1 borrados
  1. 45 0
      applications/device/device-client-biometric/config/inno.iss
  2. 108 0
      applications/device/device-client-biometric/pom.xml
  3. 56 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/DeviceClient.java
  4. 53 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/config/DataSourceConfig.java
  5. 25 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/config/DeviceServerProperties.java
  6. 91 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/config/RestTemplateConfig.java
  7. 28 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/control/NavItem.java
  8. 55 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/control/ParameterizedScene.java
  9. 35 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/control/VideoPanel.java
  10. 170 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/GateController.java
  11. 102 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/GateEditController.java
  12. 68 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/LoginController.java
  13. 71 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/MainController.java
  14. 170 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/PersonController.java
  15. 165 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/PersonEditController.java
  16. 16 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/jdbc/Connectable.java
  17. 143 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/jdbc/DynamicDataSource.java
  18. 25 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/jdbc/DynamicDataSourceContextHolder.java
  19. 44 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/jdbc/DynamicDataSourceRegister.java
  20. 36 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/AccessType.java
  21. 221 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/FaceGate.java
  22. 117 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/Person.java
  23. 46 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/School.java
  24. 36 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/SexType.java
  25. 72 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/repository/FaceGateRepository.java
  26. 71 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/repository/PersonRepository.java
  27. 38 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/repository/SchoolRepository.java
  28. 124 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/service/FaceGateService.java
  29. 63 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/service/PersonService.java
  30. 28 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/service/SchoolService.java
  31. 117 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/support/AbstractJavaFxApplicationSupport.java
  32. 40 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/support/SplashScreen.java
  33. 31 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/AlertUtils.java
  34. 14 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/ClientConstant.java
  35. 15 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/RandomUtils.java
  36. 127 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/Tray.java
  37. 25 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/ViewUtils.java
  38. 32 0
      applications/device/device-client-biometric/src/main/resources/application.yml
  39. 14 0
      applications/device/device-client-biometric/src/main/resources/banner.txt
  40. BIN
      applications/device/device-client-biometric/src/main/resources/favicon.ico
  41. BIN
      applications/device/device-client-biometric/src/main/resources/favicon.png
  42. BIN
      applications/device/device-client-biometric/src/main/resources/image/ydy.png
  43. 54 0
      applications/device/device-client-biometric/src/main/resources/logback-spring.xml
  44. 38 0
      applications/device/device-client-biometric/src/main/resources/schema.sql
  45. 7 0
      applications/device/device-client-biometric/src/main/resources/style/login.css
  46. 175 0
      applications/device/device-client-biometric/src/main/resources/style/main.css
  47. 39 0
      applications/device/device-client-biometric/src/main/resources/view/gate.fxml
  48. 45 0
      applications/device/device-client-biometric/src/main/resources/view/gate_edit.fxml
  49. 32 0
      applications/device/device-client-biometric/src/main/resources/view/login.fxml
  50. 47 0
      applications/device/device-client-biometric/src/main/resources/view/main.fxml
  51. 39 0
      applications/device/device-client-biometric/src/main/resources/view/person.fxml
  52. 49 0
      applications/device/device-client-biometric/src/main/resources/view/person_edit.fxml
  53. 2 1
      applications/device/device-core/src/main/java/com/usoftchina/smartschool/device/exception/ExceptionCode.java
  54. 37 0
      applications/device/device-sdk-biometric/pom.xml
  55. 207 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/AccessPo.java
  56. 14 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/BiometricConfig.java
  57. 46 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/BiometricDeviceService.java
  58. 26 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/DeviceManageController.java
  59. 66 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/HealthInfo.java
  60. 34 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/RequestPo.java
  61. 65 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/ResultPo.java
  62. 25 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/VerificationController.java
  63. 3 0
      applications/device/device-sdk-biometric/src/main/resources/META-INF/spring.factories
  64. 2 0
      applications/device/pom.xml
  65. 5 0
      pom.xml

+ 45 - 0
applications/device/device-client-biometric/config/inno.iss

@@ -0,0 +1,45 @@
+; Script generated by the Inno Setup Script Wizard.
+; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
+
+#define MyAppName "device-client"
+#define MyAppVersion "2.0.0.0"
+#define MyAppPublisher "usoftchina.com"
+#define MyAppURL "https://www.usoftchina.com/"
+#define MyAppExeName "device-client.exe"
+
+[Setup]
+; NOTE: The value of AppId uniquely identifies this application.
+; Do not use the same AppId value in installers for other applications.
+; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
+AppId={{B53F1479-C51D-4EC5-9E67-C6A0D6705E75}
+AppName={#MyAppName}
+AppVersion={#MyAppVersion}
+;AppVerName={#MyAppName} {#MyAppVersion}
+AppPublisher={#MyAppPublisher}
+AppPublisherURL={#MyAppURL}
+AppSupportURL={#MyAppURL}
+AppUpdatesURL={#MyAppURL}
+DefaultDirName={pf}\{#MyAppName}
+DisableProgramGroupPage=yes
+OutputBaseFilename=setup
+SetupIconFile=F:\workspace\smartschool-platform\applications\device\device-client\src\main\resources\icon.ico
+Compression=lzma
+SolidCompression=yes
+
+[Languages]
+Name: "english"; MessagesFile: "compiler:Default.isl"
+
+[Tasks]
+Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
+
+[Files]
+Source: "F:\workspace\smartschool-platform\applications\device\device-client\target\device-client.exe"; DestDir: "{app}"; Flags: ignoreversion
+; NOTE: Don't use "Flags: ignoreversion" on any shared system files
+
+[Icons]
+Name: "{commonprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
+Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
+
+[Run]
+Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
+

+ 108 - 0
applications/device/device-client-biometric/pom.xml

@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>smartschool-platform</artifactId>
+        <groupId>com.usoftchina.smartschool</groupId>
+        <version>1.0.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>device-client-biometric</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.usoftchina.smartschool</groupId>
+            <artifactId>device-sdk-biometric</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.controlsfx</groupId>
+            <artifactId>controlsfx</artifactId>
+            <version>8.40.15</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.sarxos</groupId>
+            <artifactId>webcam-capture</artifactId>
+            <version>0.3.12</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+            <version>4.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+        <!-- JSON -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <!-- httpClient -->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>com.akathist.maven.plugins.launch4j</groupId>
+                <artifactId>launch4j-maven-plugin</artifactId>
+                <version>1.7.25</version>
+                <executions>
+                    <execution>
+                        <id>l4j</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>launch4j</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <jar>${project.build.directory}/${artifactId}-${version}.jar</jar>
+                    <headerType>gui</headerType>
+                    <outfile>${project.build.directory}/device-client.exe</outfile>
+                    <downloadUrl>http://java.com/download</downloadUrl>
+                    <classPath>
+                        <mainClass>org.springframework.boot.loader.JarLauncher</mainClass>
+                    </classPath>
+                    <icon>src/main/resources/favicon.ico</icon>
+                    <stayAlive>true</stayAlive>
+                    <restartOnCrash>true</restartOnCrash>
+                    <jre>
+                        <minVersion>1.8.0</minVersion>
+                        <jdkPreference>preferJre</jdkPreference>
+                    </jre>
+                    <versionInfo>
+                        <fileVersion>2.0.0.0</fileVersion>
+                        <txtFileVersion>${project.version}</txtFileVersion>
+                        <fileDescription>${project.name}</fileDescription>
+                        <copyright>2018 usoftchina.com</copyright>
+                        <productVersion>2.0.0.0</productVersion>
+                        <txtProductVersion>2.0.0.0</txtProductVersion>
+                        <productName>${project.name}</productName>
+                        <companyName>usoftchina.com</companyName>
+                        <internalName>device-client</internalName>
+                        <originalFilename>device-client.exe</originalFilename>
+                    </versionInfo>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 56 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/DeviceClient.java

@@ -0,0 +1,56 @@
+package com.usoftchina.smartschool.device.client.biometric;
+
+import com.usoftchina.smartschool.device.client.biometric.config.DeviceServerProperties;
+import com.usoftchina.smartschool.device.client.biometric.controller.LoginController;
+import com.usoftchina.smartschool.device.client.biometric.support.AbstractJavaFxApplicationSupport;
+import com.usoftchina.smartschool.device.client.biometric.support.SplashScreen;
+import com.usoftchina.smartschool.device.client.biometric.util.ClientConstant;
+import com.usoftchina.smartschool.device.client.biometric.util.Tray;
+import javafx.scene.image.Image;
+import javafx.stage.Stage;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+/**
+ * @author yingp
+ * @date 2019/11/12
+ */
+@EnableConfigurationProperties(DeviceServerProperties.class)
+@SpringBootApplication
+public class DeviceClient extends AbstractJavaFxApplicationSupport {
+
+    public static void main(String[] args) {
+        launch(DeviceClient.class, new SplashScreen(), args);
+    }
+
+    public static Image[] defaultIcons = new Image[]{new Image(ClientConstant.icon.toExternalForm())};
+
+    @Override
+    protected void initStage(Stage stage) {
+        super.initStage(stage);
+        stage.setTitle(ClientConstant.title);
+        stage.setOnCloseRequest(e -> LoginController.logout());
+        Tray.getInstance().listen(stage);
+    }
+
+    @Override
+    protected Image[] getDefaultIcons() {
+        return defaultIcons;
+    }
+
+    public static void showLoginView() {
+        getStage().setMaximized(false);
+        getStage().setResizable(false);
+        showView("/view/login.fxml");
+    }
+
+    public static void showMainView() {
+        if (LoginController.isLogon()) {
+            showView("/view/main.fxml");
+            getStage().setMaximized(true);
+            getStage().setResizable(true);
+        } else {
+            showLoginView();
+        }
+    }
+}

+ 53 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/config/DataSourceConfig.java

@@ -0,0 +1,53 @@
+package com.usoftchina.smartschool.device.client.biometric.config;
+
+import com.usoftchina.smartschool.device.client.biometric.jdbc.DynamicDataSource;
+import com.usoftchina.smartschool.device.client.biometric.jdbc.DynamicDataSourceRegister;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author yingp
+ * @date 2019/3/11
+ */
+@Configuration
+public class DataSourceConfig {
+
+    @Primary
+    @Bean(name = "primaryDataSource")
+    @ConfigurationProperties(prefix = "spring.datasource.primary")
+    public DataSource primaryDataSource() {
+        return DataSourceBuilder.create().build();
+    }
+
+    @Bean(name = "dataSource")
+    public DynamicDataSource dynamicDataSource() {
+        DataSource primaryDataSource = this.primaryDataSource();
+        Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put("primaryDataSource", primaryDataSource);
+        DynamicDataSource dataSource = new DynamicDataSource();
+        dataSource.setDefaultTargetDataSource(primaryDataSource);
+        dataSource.setTargetDataSources(targetDataSources);
+        return dataSource;
+    }
+
+    @Bean
+    public JdbcTemplate jdbcTemplate() {
+        return new JdbcTemplate(this.dynamicDataSource());
+    }
+
+    @Bean
+    public DynamicDataSourceRegister register() {
+        DataSourceProperties dataSourceProperties = new DataSourceProperties();
+        dataSourceProperties.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
+        return new DynamicDataSourceRegister(dataSourceProperties, this.dynamicDataSource());
+    }
+}

+ 25 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/config/DeviceServerProperties.java

@@ -0,0 +1,25 @@
+package com.usoftchina.smartschool.device.client.biometric.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author yingp
+ * @date 2019/3/11
+ */
+@ConfigurationProperties(DeviceServerProperties.PREFIX)
+public class DeviceServerProperties {
+    public static final String PREFIX = "device.server";
+
+    /**
+     * 门禁事件的服务端接口
+     */
+    private String accessControlEvent;
+
+    public String getAccessControlEvent() {
+        return accessControlEvent;
+    }
+
+    public void setAccessControlEvent(String accessControlEvent) {
+        this.accessControlEvent = accessControlEvent;
+    }
+}

+ 91 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/config/RestTemplateConfig.java

@@ -0,0 +1,91 @@
+package com.usoftchina.smartschool.device.client.biometric.config;
+
+import org.apache.http.client.HttpClient;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.http.ssl.TrustStrategy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.web.client.RestTemplate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import java.nio.charset.Charset;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * @Author chenwei
+ * @Date 2019/03/07
+ */
+@Configuration
+public class RestTemplateConfig {
+
+    private static final Logger logger = LoggerFactory.getLogger(RestTemplateConfig.class);
+
+    @Bean
+    public RestTemplate restTemplate(){
+        RestTemplate restTemplate = new RestTemplate();
+        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
+        converterList.add(0, responseBodyConverter());
+        restTemplate.setRequestFactory(httpRequestFactory());
+        return restTemplate;
+    }
+
+    @Bean
+    public HttpMessageConverter<String> responseBodyConverter() {
+        StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
+        return converter;
+    }
+
+    @Bean
+    public HttpComponentsClientHttpRequestFactory httpRequestFactory(){
+        try {
+            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
+            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
+                public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
+                    return true;
+                }
+            }).build();
+            httpClientBuilder.setSSLContext(sslContext);
+            HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
+            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
+            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
+                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                    .register("https", sslConnectionSocketFactory).build();// 注册http和https请求
+            // 开始设置连接池
+            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
+            poolingHttpClientConnectionManager.setMaxTotal(500); // 最大连接数500
+            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100); // 同路由并发数100
+            httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
+            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)); // 重试次数
+            HttpClient httpClient = httpClientBuilder.build();
+            HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // httpClient连接配置
+            clientHttpRequestFactory.setConnectTimeout(20000);              // 连接超时
+            clientHttpRequestFactory.setReadTimeout(30000);                 // 数据读取超时时间
+            clientHttpRequestFactory.setConnectionRequestTimeout(20000);    // 连接不够用的等待时间
+            return clientHttpRequestFactory;
+        }catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e){
+            logger.info("初始化HTTP连接池出错, message={}", e);
+        }
+        return null;
+    }
+
+}

+ 28 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/control/NavItem.java

@@ -0,0 +1,28 @@
+package com.usoftchina.smartschool.device.client.biometric.control;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.scene.control.TreeItem;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+public class NavItem<T> extends TreeItem<T> {
+    private ObjectProperty<String> viewPath;
+
+    public String getViewPath() {
+        return viewPathProperty().get();
+    }
+
+    public ObjectProperty<String> viewPathProperty() {
+        if (null == viewPath) {
+            viewPath = new SimpleObjectProperty<>();
+        }
+        return viewPath;
+    }
+
+    public void setViewPath(String viewPath) {
+        viewPathProperty().set(viewPath);
+    }
+}

+ 55 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/control/ParameterizedScene.java

@@ -0,0 +1,55 @@
+package com.usoftchina.smartschool.device.client.biometric.control;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.SceneAntialiasing;
+import javafx.scene.paint.Paint;
+
+/**
+ * @author yingp
+ * @date 2019/11/15
+ */
+public class ParameterizedScene<T> extends Scene {
+    private ObjectProperty<T> params;
+
+    public ParameterizedScene(Parent root) {
+        super(root);
+    }
+
+    public ParameterizedScene(Parent root, double width, double height) {
+        super(root, width, height);
+    }
+
+    public ParameterizedScene(Parent root, Paint fill, Object params) {
+        super(root, fill);
+    }
+
+    public ParameterizedScene(Parent root, double width, double height, Paint fill) {
+        super(root, width, height, fill);
+    }
+
+    public ParameterizedScene(Parent root, double width, double height, boolean depthBuffer) {
+        super(root, width, height, depthBuffer);
+    }
+
+    public ParameterizedScene(Parent root, double width, double height, boolean depthBuffer, SceneAntialiasing antiAliasing) {
+        super(root, width, height, depthBuffer, antiAliasing);
+    }
+
+    public Object getParams() {
+        return paramsProperty().get();
+    }
+
+    public ObjectProperty<T> paramsProperty() {
+        if (null == params) {
+            params = new SimpleObjectProperty<>();
+        }
+        return params;
+    }
+
+    public void setParams(T params) {
+        this.paramsProperty().set(params);
+    }
+}

+ 35 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/control/VideoPanel.java

@@ -0,0 +1,35 @@
+package com.usoftchina.smartschool.device.client.biometric.control;
+
+import com.github.sarxos.webcam.Webcam;
+import com.github.sarxos.webcam.WebcamPanel;
+import com.github.sarxos.webcam.WebcamResolution;
+import javafx.embed.swing.SwingNode;
+
+import javax.swing.*;
+
+/**
+ * @author yingp
+ * @date 2019/11/15
+ */
+public class VideoPanel {
+
+    public SwingNode getVideoPanel() {
+        final SwingNode swingNode = new SwingNode();
+        createSwingContent(swingNode);
+        return swingNode;
+    }
+
+    private void createSwingContent(final SwingNode swingNode) {
+        SwingUtilities.invokeLater(() -> {
+            Webcam webcam = Webcam.getDefault();
+            webcam.setViewSize(WebcamResolution.VGA.getSize());
+            WebcamPanel panel = new WebcamPanel(webcam);
+            panel.setFPSDisplayed(true);
+            panel.setDisplayDebugInfo(true);
+            panel.setImageSizeDisplayed(true);
+            panel.setMirrored(true);
+
+            swingNode.setContent(panel);
+        });
+    }
+}

+ 170 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/GateController.java

@@ -0,0 +1,170 @@
+package com.usoftchina.smartschool.device.client.biometric.controller;
+
+import com.usoftchina.smartschool.device.client.biometric.DeviceClient;
+import com.usoftchina.smartschool.device.client.biometric.po.AccessType;
+import com.usoftchina.smartschool.device.client.biometric.po.FaceGate;
+import com.usoftchina.smartschool.device.client.biometric.service.FaceGateService;
+import com.usoftchina.smartschool.device.client.biometric.util.AlertUtils;
+import com.usoftchina.smartschool.device.client.biometric.util.ViewUtils;
+import com.usoftchina.smartschool.device.context.SpringContextHolder;
+import javafx.application.Platform;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.cell.PropertyValueFactory;
+import javafx.stage.Stage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+public class GateController implements Initializable {
+
+    private final Logger logger = LoggerFactory.getLogger(GateController.class);
+    @FXML
+    public TableView<FaceGate> tableView;
+    @FXML
+    public TableColumn nameCol;
+    @FXML
+    public TableColumn placeCol;
+    @FXML
+    public TableColumn ipCol;
+    @FXML
+    public TableColumn accessTypeCol;
+    @FXML
+    public TableColumn startTimeCol;
+    @FXML
+    public TableColumn lastActiveCol;
+    @FXML
+    public TableColumn statusCol;
+    @FXML
+    public Button editButton;
+    @FXML
+    public Button delButton;
+    private FaceGateService faceGateService;
+    private ObservableList<FaceGate> faceGateObservableList = FXCollections.observableArrayList();
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        faceGateService = SpringContextHolder.getContext().getBean(FaceGateService.class);
+        initTable();
+    }
+
+    private void initTable() {
+        nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
+        placeCol.setCellValueFactory(new PropertyValueFactory<>("place"));
+        ipCol.setCellValueFactory(new PropertyValueFactory<>("ip"));
+        accessTypeCol.setCellValueFactory(new PropertyValueFactory<>("accessType"));
+        accessTypeCol.setCellFactory((col) -> {
+                    TableCell<FaceGate, AccessType> cell = new TableCell<FaceGate, AccessType>() {
+
+                        @Override
+                        public void updateItem(AccessType item, boolean empty) {
+                            super.updateItem(item, empty);
+                            if (null != item) {
+                                this.setText(item.getText());
+                            } else {
+                                this.setText(null);
+                            }
+                        }
+                    };
+                    return cell;
+                }
+        );
+        startTimeCol.setCellValueFactory(new PropertyValueFactory<>("startTime"));
+        lastActiveCol.setCellValueFactory(new PropertyValueFactory<>("lastActiveTime"));
+        statusCol.setCellValueFactory(new PropertyValueFactory<>("status"));
+
+        tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+        tableView.setItems(faceGateObservableList);
+        loadData();
+
+        tableView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<FaceGate>() {
+            @Override
+            public void changed(ObservableValue<? extends FaceGate> observable, FaceGate oldValue, FaceGate newValue) {
+                if (null == newValue) {
+                    editButton.setDisable(true);
+                    delButton.setDisable(true);
+                } else {
+                    editButton.setDisable(false);
+                    delButton.setDisable(false);
+                }
+            }
+        });
+    }
+
+    private void loadData() {
+        Platform.runLater(() -> {
+            faceGateObservableList.setAll(FXCollections.observableList(faceGateService.findAll()));
+        });
+    }
+
+    @FXML
+    public void handleAdd(ActionEvent event) {
+        createGateEditPane(null);
+    }
+
+    private Scene createGateEditPane(FaceGate faceGate) {
+        try {
+            Scene scene = ViewUtils.load("/view/gate_edit.fxml", faceGate);
+            Stage stage = new Stage();
+            stage.setScene(scene);
+            stage.setResizable(false);
+            stage.setTitle("人脸闸机设备");
+            stage.getIcons().addAll(DeviceClient.defaultIcons);
+            stage.show();
+            stage.showingProperty().addListener(new ChangeListener<Boolean>() {
+                @Override
+                public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
+                    if (null == newValue || !newValue.booleanValue()) {
+                        loadData();
+                    }
+                }
+            });
+            return scene;
+        } catch (Exception e) {
+            logger.error("Edit Device Error", e);
+        }
+        return null;
+    }
+
+    @FXML
+    public void handleRefresh(ActionEvent event) {
+        loadData();
+    }
+
+    @FXML
+    public void handleEdit(ActionEvent event) {
+        FaceGate faceGate = tableView.getSelectionModel().getSelectedItem();
+        if (null == faceGate) {
+            AlertUtils.warn("请先选择一行数据");
+            return;
+        }
+        createGateEditPane(faceGate);
+    }
+
+    @FXML
+    public void handleDelete(ActionEvent event) {
+        FaceGate faceGate = tableView.getSelectionModel().getSelectedItem();
+        if (null == faceGate) {
+            AlertUtils.warn("请先选择一行数据");
+            return;
+        }
+        faceGateService.delete(faceGate.getId());
+        loadData();
+    }
+}

+ 102 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/GateEditController.java

@@ -0,0 +1,102 @@
+package com.usoftchina.smartschool.device.client.biometric.controller;
+
+import com.usoftchina.smartschool.device.client.biometric.control.ParameterizedScene;
+import com.usoftchina.smartschool.device.client.biometric.po.AccessType;
+import com.usoftchina.smartschool.device.client.biometric.po.FaceGate;
+import com.usoftchina.smartschool.device.client.biometric.service.FaceGateService;
+import com.usoftchina.smartschool.device.client.biometric.util.AlertUtils;
+import com.usoftchina.smartschool.device.context.SpringContextHolder;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.Scene;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.TextField;
+import javafx.stage.Stage;
+import org.springframework.util.StringUtils;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+public class GateEditController implements Initializable {
+
+    @FXML
+    public ComboBox<AccessType> accessTypeBox;
+    @FXML
+    public TextField nameField;
+    @FXML
+    public TextField ipField;
+    @FXML
+    public TextField placeField;
+    private FaceGateService faceGateService;
+
+    private ObservableList<AccessType> accessOptions =
+            FXCollections.observableArrayList(
+                    new AccessType(1, "进入"),
+                    new AccessType(2, "出去")
+            );
+
+    private FaceGate faceGate;
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        accessTypeBox.sceneProperty().addListener(new ChangeListener<Scene>() {
+            @Override
+            public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) {
+                if (null != newValue) {
+                    faceGate = new FaceGate();
+                    if (newValue instanceof ParameterizedScene) {
+                        // 监听传过来需要编辑的FaceGate对象
+                        ((ParameterizedScene) newValue).paramsProperty().addListener(new ChangeListener() {
+                            @Override
+                            public void changed(ObservableValue observable, Object oldValue, Object newValue) {
+                                if (null != newValue) {
+                                    faceGate = (FaceGate) newValue;
+                                    initFields();
+                                }
+                            }
+                        });
+                    }
+                    initFields();
+                }
+            }
+        });
+
+        faceGateService = SpringContextHolder.getContext().getBean(FaceGateService.class);
+    }
+
+    private void initFields() {
+        nameField.textProperty().bindBidirectional(faceGate.nameProperty());
+        ipField.textProperty().bindBidirectional(faceGate.ipProperty());
+        placeField.textProperty().bindBidirectional(faceGate.placeProperty());
+        accessTypeBox.valueProperty().bindBidirectional(faceGate.accessTypeProperty());
+        accessTypeBox.setItems(accessOptions);
+        if (null == faceGate.getAccessType()) {
+            accessTypeBox.getSelectionModel().selectFirst();
+        }
+    }
+
+    @FXML
+    public void handleSave(ActionEvent event) {
+        if (StringUtils.isEmpty(faceGate.getName())) {
+            AlertUtils.warn("请填写设备名称").ifPresent(b -> nameField.requestFocus());
+            return;
+        }
+        if (StringUtils.isEmpty(faceGate.getIp())) {
+            AlertUtils.warn("请填写设备IP").ifPresent(b -> ipField.requestFocus());
+            return;
+        }
+        faceGateService.save(faceGate);
+
+        Stage stage = (Stage) accessTypeBox.getScene().getWindow();
+        stage.close();
+    }
+}

+ 68 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/LoginController.java

@@ -0,0 +1,68 @@
+package com.usoftchina.smartschool.device.client.biometric.controller;
+
+import com.usoftchina.smartschool.device.client.biometric.DeviceClient;
+import com.usoftchina.smartschool.device.client.biometric.util.AlertUtils;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.PasswordField;
+import javafx.scene.control.TextField;
+import org.springframework.util.StringUtils;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+/**
+ * @author yingp
+ * @date 2019/11/12
+ */
+public class LoginController implements Initializable {
+    @FXML
+    private TextField usernameField;
+    @FXML
+    private PasswordField passwordField;
+
+    private static boolean logon;
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+
+    }
+
+    @FXML
+    public void handleLogin(ActionEvent event) {
+        String username = usernameField.getText();
+        if (StringUtils.isEmpty(username)) {
+            AlertUtils.warn("请输入用户名").ifPresent(b -> {
+                usernameField.requestFocus();
+            });
+            return;
+        }
+        String password = passwordField.getText();
+        if (StringUtils.isEmpty(password)) {
+            AlertUtils.warn("请输入密码").ifPresent(b -> {
+                passwordField.requestFocus();
+            });
+            return;
+        }
+        if (!valid(username, password)) {
+            AlertUtils.warn("用户名或密码错误");
+            return;
+        }
+        logon = true;
+        passwordField.clear();
+        DeviceClient.showMainView();
+    }
+
+    private boolean valid(String username, String password) {
+        return "admin".equals(username) && "select111***".equals(password);
+    }
+
+    public static boolean isLogon() {
+        return logon;
+    }
+
+    public static void logout() {
+        logon = false;
+    }
+}

+ 71 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/MainController.java

@@ -0,0 +1,71 @@
+package com.usoftchina.smartschool.device.client.biometric.controller;
+
+import com.usoftchina.smartschool.device.client.biometric.control.NavItem;
+import com.usoftchina.smartschool.device.client.biometric.util.ViewUtils;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.Scene;
+import javafx.scene.control.*;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ResourceBundle;
+
+/**
+ * @author yingp
+ * @date 2019/11/12
+ */
+public class MainController implements Initializable {
+    @FXML
+    public TabPane tabPane;
+    @FXML
+    private TreeView<String> treeView;
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS);
+        initStyle();
+        initTreeEvents();
+    }
+
+    private void initStyle() {
+        // 样式
+        treeView.setCellFactory(tv -> new TreeCell<String>() {
+            @Override
+            protected void updateItem(String item, boolean empty) {
+                super.updateItem(item, empty);
+                setText(item);
+                TreeItem<String> treeItem = getTreeItem();
+                if (null != treeItem && treeItem.isLeaf()) {
+                    getStyleClass().add("tree-cell-leaf");
+                }
+            }
+        });
+    }
+
+    private void initTreeEvents() {
+        // 打开新tab页
+        treeView.getSelectionModel().selectedItemProperty().addListener((observable, oldItem, newItem) -> {
+            if (newItem instanceof NavItem) {
+                NavItem navItem = (NavItem) newItem;
+                Tab newTab = tabPane.getTabs().stream()
+                        .filter(t -> navItem.getViewPath().equals(t.getId()))
+                        .findFirst()
+                        .orElseGet(() -> {
+                            try {
+                                Scene scene = ViewUtils.load(navItem.getViewPath());
+                                Tab tab = new Tab(navItem.getValue().toString(), scene.getRoot());
+                                tab.setId(navItem.getViewPath());
+                                tab.setClosable(true);
+                                tabPane.getTabs().add(tab);
+                                return tab;
+                            } catch (IOException e) {
+                                e.printStackTrace();
+                            }
+                            return null;
+                        });
+                tabPane.getSelectionModel().select(newTab);
+            }
+        });
+    }
+}

+ 170 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/PersonController.java

@@ -0,0 +1,170 @@
+package com.usoftchina.smartschool.device.client.biometric.controller;
+
+import com.usoftchina.smartschool.device.client.biometric.DeviceClient;
+import com.usoftchina.smartschool.device.client.biometric.po.FaceGate;
+import com.usoftchina.smartschool.device.client.biometric.po.Person;
+import com.usoftchina.smartschool.device.client.biometric.po.SexType;
+import com.usoftchina.smartschool.device.client.biometric.service.PersonService;
+import com.usoftchina.smartschool.device.client.biometric.util.AlertUtils;
+import com.usoftchina.smartschool.device.client.biometric.util.ViewUtils;
+import com.usoftchina.smartschool.device.context.SpringContextHolder;
+import javafx.application.Platform;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.cell.PropertyValueFactory;
+import javafx.stage.Stage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+/**
+ * @author yingp
+ * @date 2019/11/15
+ */
+public class PersonController implements Initializable {
+    private final Logger logger = LoggerFactory.getLogger(PersonController.class);
+    @FXML
+    public TableView<Person> tableView;
+    @FXML
+    public TableColumn classCol;
+    @FXML
+    public TableColumn nameCol;
+    @FXML
+    public TableColumn sexCol;
+    @FXML
+    public TableColumn cardNoCol;
+    @FXML
+    public TableColumn createTimeCol;
+    @FXML
+    public Button editButton;
+    @FXML
+    public Button delButton;
+
+    private PersonService personService;
+
+    private ObservableList<Person> personObservableList = FXCollections.observableArrayList();
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        personService = SpringContextHolder.getContext().getBean(PersonService.class);
+        initTable();
+    }
+
+    private void initTable() {
+        nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
+        classCol.setCellValueFactory(new PropertyValueFactory<>("clazz"));
+        sexCol.setCellValueFactory(new PropertyValueFactory<>("sex"));
+        sexCol.setCellFactory((col) -> {
+                    TableCell<FaceGate, SexType> cell = new TableCell<FaceGate, SexType>() {
+
+                        @Override
+                        public void updateItem(SexType item, boolean empty) {
+                            super.updateItem(item, empty);
+                            if (null != item) {
+                                this.setText(item.getText());
+                            } else {
+                                this.setText(null);
+                            }
+                        }
+                    };
+                    return cell;
+                }
+        );
+        cardNoCol.setCellValueFactory(new PropertyValueFactory<>("cardNo"));
+        createTimeCol.setCellValueFactory(new PropertyValueFactory<>("createTime"));
+
+        tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+        tableView.setItems(personObservableList);
+        loadData();
+
+        tableView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Person>() {
+            @Override
+            public void changed(ObservableValue<? extends Person> observable, Person oldValue, Person newValue) {
+                if (null == newValue) {
+                    editButton.setDisable(true);
+                    delButton.setDisable(true);
+                } else {
+                    editButton.setDisable(false);
+                    delButton.setDisable(false);
+                }
+            }
+        });
+    }
+
+    private void loadData() {
+        Platform.runLater(() -> {
+            personObservableList.setAll(FXCollections.observableList(personService.findAll()));
+        });
+    }
+
+    @FXML
+    public void handleAdd(ActionEvent event) {
+        createGateEditPane(null);
+    }
+
+    @FXML
+    public void handleEdit(ActionEvent event) {
+        Person person = tableView.getSelectionModel().getSelectedItem();
+        if (null == person) {
+            AlertUtils.warn("请先选择一行数据");
+            return;
+        }
+        createGateEditPane(person);
+    }
+
+    @FXML
+    public void handleDelete(ActionEvent event) {
+        Person person = tableView.getSelectionModel().getSelectedItem();
+        if (null == person) {
+            AlertUtils.warn("请先选择一行数据");
+            return;
+        }
+        personService.delete(person.getId());
+        loadData();
+    }
+
+    @FXML
+    public void handleRefresh(ActionEvent event) {
+        loadData();
+    }
+
+    private Scene createGateEditPane(Person person) {
+        try {
+            Scene scene = ViewUtils.load("/view/person_edit.fxml", person);
+            Stage stage = new Stage();
+            stage.setScene(scene);
+            stage.setResizable(false);
+            stage.setTitle("人员信息");
+            stage.getIcons().addAll(DeviceClient.defaultIcons);
+            stage.show();
+            stage.showingProperty().addListener(new ChangeListener<Boolean>() {
+                @Override
+                public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
+                    if (null == newValue || !newValue.booleanValue()) {
+                        loadData();
+                    }
+                }
+            });
+            return scene;
+        } catch (Exception e) {
+            logger.error("Edit Person Error", e);
+        }
+        return null;
+    }
+
+    @FXML
+    public void handleImport(ActionEvent event) {
+    }
+}

+ 165 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/PersonEditController.java

@@ -0,0 +1,165 @@
+package com.usoftchina.smartschool.device.client.biometric.controller;
+
+import com.github.sarxos.webcam.Webcam;
+import com.usoftchina.smartschool.device.client.biometric.DeviceClient;
+import com.usoftchina.smartschool.device.client.biometric.control.ParameterizedScene;
+import com.usoftchina.smartschool.device.client.biometric.control.VideoPanel;
+import com.usoftchina.smartschool.device.client.biometric.po.Person;
+import com.usoftchina.smartschool.device.client.biometric.po.SexType;
+import com.usoftchina.smartschool.device.client.biometric.service.PersonService;
+import com.usoftchina.smartschool.device.client.biometric.util.AlertUtils;
+import com.usoftchina.smartschool.device.context.SpringContextHolder;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.TextField;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.StackPane;
+import javafx.stage.FileChooser;
+import javafx.stage.Stage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+import javax.imageio.ImageIO;
+import java.io.File;
+import java.net.URL;
+import java.util.ResourceBundle;
+
+/**
+ * @author yingp
+ * @date 2019/11/15
+ */
+public class PersonEditController implements Initializable {
+    private final Logger logger = LoggerFactory.getLogger(PersonEditController.class);
+    @FXML
+    public ComboBox<SexType> sexBox;
+    @FXML
+    public TextField cardNoField;
+    @FXML
+    public TextField nameField;
+    @FXML
+    public TextField classField;
+    @FXML
+    public Button goonButton;
+    @FXML
+    public ImageView imageView;
+
+    private PersonService personService;
+
+    private Person person;
+
+    private FileChooser fileChooser;
+
+    private File imageFile;
+
+    private ObservableList<SexType> sexOptions =
+            FXCollections.observableArrayList(
+                    new SexType(1, "男"),
+                    new SexType(2, "女")
+            );
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        personService = SpringContextHolder.getContext().getBean(PersonService.class);
+
+        nameField.sceneProperty().addListener(new ChangeListener<Scene>() {
+            @Override
+            public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) {
+                if (null != newValue) {
+                    person = new Person();
+                    if (newValue instanceof ParameterizedScene) {
+                        // 监听传过来需要编辑的Person对象
+                        ((ParameterizedScene) newValue).paramsProperty().addListener(new ChangeListener() {
+                            @Override
+                            public void changed(ObservableValue observable, Object oldValue, Object newValue) {
+                                if (null != newValue) {
+                                    person = (Person) newValue;
+                                    initFields();
+                                    goonButton.setVisible(false);
+                                }
+                            }
+                        });
+                    }
+                    initFields();
+                }
+            }
+        });
+    }
+
+    private void initFields() {
+        nameField.textProperty().bindBidirectional(person.nameProperty());
+        classField.textProperty().bindBidirectional(person.clazzProperty());
+        cardNoField.textProperty().bindBidirectional(person.cardNoProperty());
+        sexBox.valueProperty().bindBidirectional(person.sexProperty());
+        sexBox.setItems(sexOptions);
+        if (null == person.getSex()) {
+            sexBox.getSelectionModel().selectFirst();
+        }
+    }
+
+    private void save() {
+        if (StringUtils.isEmpty(person.getName())) {
+            AlertUtils.warn("请填写姓名").ifPresent(b -> nameField.requestFocus());
+            return;
+        }
+        if (StringUtils.isEmpty(person.getCardNo())) {
+            AlertUtils.warn("请填写卡号").ifPresent(b -> cardNoField.requestFocus());
+            return;
+        }
+        personService.save(person);
+    }
+
+    @FXML
+    public void handleSave(ActionEvent event) {
+        save();
+
+        Stage stage = (Stage) nameField.getScene().getWindow();
+        stage.close();
+    }
+
+    @FXML
+    public void handleSaveAndGoon(ActionEvent event) {
+        save();
+        // 继续新增
+        person = new Person();
+        initFields();
+    }
+
+    @FXML
+    public void handleChoosePicture(ActionEvent event) {
+        if (null == fileChooser) {
+            fileChooser = new FileChooser();
+            fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("图片文件", "*.png", "*.jpg", "*.bmp", "*.gif"));
+        }
+        // 打开上次文件夹
+        if (null != imageFile) {
+            fileChooser.setInitialDirectory(imageFile.getParentFile());
+        }
+        imageFile = fileChooser.showOpenDialog(nameField.getScene().getWindow());
+        if (null != imageFile) {
+            imageView.setImage(new Image(imageFile.toURI().toString()));
+        }
+    }
+
+    @FXML
+    public void handleOpenVideo(ActionEvent event) {
+        VideoPanel videoPanel = new VideoPanel();
+        StackPane stackPane = new StackPane();
+        stackPane.getChildren().add(videoPanel.getVideoPanel());
+        Stage stage = new Stage();
+        stage.setScene(new Scene(stackPane, 800, 600));
+        stage.setResizable(false);
+        stage.setTitle("人脸采集");
+        stage.getIcons().addAll(DeviceClient.defaultIcons);
+        stage.show();
+    }
+}

+ 16 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/jdbc/Connectable.java

@@ -0,0 +1,16 @@
+package com.usoftchina.smartschool.device.client.biometric.jdbc;
+
+/**
+ * Created by Pro1 on 2017/7/27.
+ */
+public interface Connectable {
+
+    String qualifier();
+
+    String url();
+
+    String username();
+
+    String password();
+
+}

+ 143 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/jdbc/DynamicDataSource.java

@@ -0,0 +1,143 @@
+package com.usoftchina.smartschool.device.client.biometric.jdbc;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.jdbc.datasource.AbstractDataSource;
+import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
+import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
+import org.springframework.util.Assert;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Created by Pro1 on 2017/7/27.
+ * 基于{@AbstractRoutingDataSource},解决不能动态添加新数据源的问题
+ */
+public class DynamicDataSource extends AbstractDataSource implements InitializingBean {
+
+    private Map<Object, Object> targetDataSources;
+    private Object defaultTargetDataSource;
+    private boolean lenientFallback = true;
+    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
+    private Map<Object, DataSource> resolvedDataSources;
+    private DataSource resolvedDefaultDataSource;
+
+    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
+        this.targetDataSources = targetDataSources;
+    }
+
+    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
+        this.defaultTargetDataSource = defaultTargetDataSource;
+    }
+
+    public void setLenientFallback(boolean lenientFallback) {
+        this.lenientFallback = lenientFallback;
+    }
+
+    public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
+        this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup != null?dataSourceLookup:new JndiDataSourceLookup());
+    }
+
+    @Override
+    public void afterPropertiesSet() {
+        if(this.targetDataSources == null) {
+            throw new IllegalArgumentException("Property \'targetDataSources\' is required");
+        } else {
+            this.resolvedDataSources = new HashMap(this.targetDataSources.size());
+            Iterator var1 = this.targetDataSources.entrySet().iterator();
+
+            while(var1.hasNext()) {
+                Map.Entry entry = (Map.Entry)var1.next();
+                Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
+                DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
+                this.resolvedDataSources.put(lookupKey, dataSource);
+            }
+
+            if(this.defaultTargetDataSource != null) {
+                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
+            }
+
+        }
+    }
+
+    protected Object resolveSpecifiedLookupKey(Object lookupKey) {
+        return lookupKey;
+    }
+
+    protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
+        if(dataSource instanceof DataSource) {
+            return (DataSource)dataSource;
+        } else if(dataSource instanceof String) {
+            return this.dataSourceLookup.getDataSource((String)dataSource);
+        } else {
+            throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
+        }
+    }
+
+    @Override
+    public Connection getConnection() throws SQLException {
+        return this.determineTargetDataSource().getConnection();
+    }
+
+    @Override
+    public Connection getConnection(String username, String password) throws SQLException {
+        return this.determineTargetDataSource().getConnection(username, password);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T unwrap(Class<T> iface) throws SQLException {
+        if (iface.isInstance(this)) {
+            return (T) this;
+        }
+        return determineTargetDataSource().unwrap(iface);
+    }
+
+    @Override
+    public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
+    }
+
+    protected DataSource determineTargetDataSource() {
+        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
+        Object lookupKey = this.determineCurrentLookupKey();
+        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
+        if(dataSource == null && (this.lenientFallback || lookupKey == null)) {
+            dataSource = this.resolvedDefaultDataSource;
+        }
+
+        if(dataSource == null) {
+            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
+        } else {
+            return dataSource;
+        }
+    }
+
+    protected Object determineCurrentLookupKey() {
+        return DynamicDataSourceContextHolder.get();
+    }
+
+    public boolean containsDataSource(Object key) {
+        Object lookupKey = this.resolveSpecifiedLookupKey(key);
+        return this.resolvedDataSources.containsKey(lookupKey);
+    }
+
+    public void addDataSource(Object key, Object value) {
+        Object lookupKey = this.resolveSpecifiedLookupKey(key);
+        if (!containsDataSource(lookupKey)) {
+            DataSource dataSource = this.resolveSpecifiedDataSource(value);
+            this.resolvedDataSources.put(lookupKey, dataSource);
+        }
+    }
+
+    public void removeDataSource(Object key) {
+        Object lookupKey = this.resolveSpecifiedLookupKey(key);
+        if (!containsDataSource(lookupKey)) {
+            this.resolvedDataSources.remove(key);
+        }
+    }
+}

+ 25 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/jdbc/DynamicDataSourceContextHolder.java

@@ -0,0 +1,25 @@
+package com.usoftchina.smartschool.device.client.biometric.jdbc;
+
+/**
+ * Created by Pro1 on 2017/7/27.
+ */
+public class DynamicDataSourceContextHolder {
+
+    private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>();
+
+    public static void set(String dataSource) {
+        contextHolder.set(dataSource);
+    }
+
+    public static void set(Connectable connectable) {
+        contextHolder.set(connectable.qualifier());
+    }
+
+    public static String get() {
+        return contextHolder.get();
+    }
+
+    public static void clear() {
+        contextHolder.remove();
+    }
+}

+ 44 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/jdbc/DynamicDataSourceRegister.java

@@ -0,0 +1,44 @@
+package com.usoftchina.smartschool.device.client.biometric.jdbc;
+
+import com.zaxxer.hikari.HikariDataSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+
+/**
+ * Created by Pro1 on 2017/7/27.
+ */
+public class DynamicDataSourceRegister {
+
+    private DataSourceProperties defaultProperties;
+    private DynamicDataSource dynamicDataSource;
+    private Logger logger = LoggerFactory.getLogger(getClass());
+
+    public DynamicDataSourceRegister(DataSourceProperties defaultProperties, DynamicDataSource dynamicDataSource) {
+        this.defaultProperties = defaultProperties;
+        this.dynamicDataSource = dynamicDataSource;
+    }
+
+    public boolean contains(Connectable connectable) {
+        return dynamicDataSource.containsDataSource(connectable.qualifier());
+    }
+
+    public void createDataSource(Connectable connectable) {
+        if (!dynamicDataSource.containsDataSource(connectable.qualifier())) {
+            logger.info("connectable:" + connectable);
+            HikariDataSource dataSource = DataSourceBuilder.create(defaultProperties.getClassLoader())
+                    .type(HikariDataSource.class)
+                    .driverClassName(defaultProperties.determineDriverClassName())
+                    .url(connectable.url())
+                    .username(connectable.username())
+                    .password(connectable.password())
+                    .build();
+            dynamicDataSource.addDataSource(connectable.qualifier(), dataSource);
+        }
+    }
+
+    public void unregister(Connectable connectable) {
+        dynamicDataSource.removeDataSource(connectable.qualifier());
+    }
+}

+ 36 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/AccessType.java

@@ -0,0 +1,36 @@
+package com.usoftchina.smartschool.device.client.biometric.po;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+public class AccessType {
+    private Integer value;
+    private String text;
+
+    public AccessType(Integer value, String text) {
+        this.value = value;
+        this.text = text;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+
+    public void setValue(Integer value) {
+        this.value = value;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    @Override
+    public String toString() {
+        return text;
+    }
+}

+ 221 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/FaceGate.java

@@ -0,0 +1,221 @@
+package com.usoftchina.smartschool.device.client.biometric.po;
+
+import com.usoftchina.smartschool.device.dto.DeviceInfo;
+import javafx.beans.property.*;
+
+import java.util.Date;
+
+/**
+ * 人脸闸机
+ *
+ * @author yingp
+ * @date 2019/11/14
+ */
+public class FaceGate implements DeviceInfo {
+    private String id;
+    private StringProperty name;
+    /**
+     * 位置
+     */
+    private StringProperty place;
+    private StringProperty ip;
+    private IntegerProperty port;
+    private StringProperty username;
+    private StringProperty password;
+    /**
+     * 出入类型 1 进入 / 2 出去
+     */
+    private ObjectProperty<AccessType> accessType;
+    /**
+     * 注册时间
+     */
+    private ObjectProperty<Date> startTime;
+    /**
+     * 最近心跳时间
+     */
+    private ObjectProperty<Date> lastActiveTime;
+    /**
+     * 健康状况
+     */
+    private StringProperty status;
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return nameProperty().get();
+    }
+
+    public StringProperty nameProperty() {
+        if (null == name) {
+            name = new SimpleStringProperty();
+        }
+        return name;
+    }
+
+    public void setName(String name) {
+        this.nameProperty().set(name);
+    }
+
+    public String getPlace() {
+        return placeProperty().get();
+    }
+
+    public StringProperty placeProperty() {
+        if (null == place) {
+            place = new SimpleStringProperty();
+        }
+        return place;
+    }
+
+    public void setPlace(String place) {
+        this.placeProperty().set(place);
+    }
+
+    @Override
+    public String getIp() {
+        return ipProperty().get();
+    }
+
+    public StringProperty ipProperty() {
+        if (null == ip) {
+            ip = new SimpleStringProperty();
+        }
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ipProperty().set(ip);
+    }
+
+    @Override
+    public int getPort() {
+        return portProperty().get();
+    }
+
+    public IntegerProperty portProperty() {
+        if (null == port) {
+            port = new SimpleIntegerProperty(8080);
+        }
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.portProperty().set(port);
+    }
+
+    @Override
+    public String getUsername() {
+        return usernameProperty().get();
+    }
+
+    public StringProperty usernameProperty() {
+        if (null == username) {
+            username = new SimpleStringProperty();
+        }
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.usernameProperty().set(username);
+    }
+
+    @Override
+    public String getPassword() {
+        return passwordProperty().get();
+    }
+
+    public StringProperty passwordProperty() {
+        if (null == password) {
+            password = new SimpleStringProperty();
+        }
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.passwordProperty().set(password);
+    }
+
+    public Integer getAccessType() {
+        AccessType type = accessTypeProperty().getValue();
+        return null == type ? null : type.getValue();
+    }
+
+    public void setAccessType(Integer accessType) {
+        if (null != accessType) {
+            accessTypeProperty().setValue(new AccessType(accessType, 1 == accessType ? "进入" : "出去"));
+        }
+    }
+
+    public ObjectProperty<AccessType> accessTypeProperty() {
+        if (null == accessType) {
+            accessType = new SimpleObjectProperty<>();
+        }
+        return accessType;
+    }
+
+    public void setAccessType(AccessType accessType) {
+        this.accessTypeProperty().set(accessType);
+    }
+
+    public Date getStartTime() {
+        return startTimeProperty().get();
+    }
+
+    public ObjectProperty<Date> startTimeProperty() {
+        if (null == startTime) {
+            startTime = new SimpleObjectProperty<>();
+        }
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTimeProperty().set(startTime);
+    }
+
+    public Date getLastActiveTime() {
+        return lastActiveTimeProperty().get();
+    }
+
+    public ObjectProperty<Date> lastActiveTimeProperty() {
+        if (null == lastActiveTime) {
+            lastActiveTime = new SimpleObjectProperty<>();
+        }
+        return lastActiveTime;
+    }
+
+    public void setLastActiveTime(Date lastActiveTime) {
+        this.lastActiveTimeProperty().set(lastActiveTime);
+    }
+
+    public String getStatus() {
+        return statusProperty().get();
+    }
+
+    public StringProperty statusProperty() {
+        if (null == status) {
+            status = new SimpleStringProperty();
+        }
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.statusProperty().set(status);
+    }
+
+    /**
+     * 是否在线
+     *
+     * @return
+     */
+    public boolean isActive() {
+        Date time = getLastActiveTime();
+        return null != time && System.currentTimeMillis() - time.getTime() < 60000;
+    }
+}

+ 117 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/Person.java

@@ -0,0 +1,117 @@
+package com.usoftchina.smartschool.device.client.biometric.po;
+
+import javafx.beans.property.*;
+
+import java.util.Date;
+
+/**
+ * @author yingp
+ * @date 2019/11/15
+ */
+public class Person {
+    private String id;
+    private StringProperty clazz;
+    private StringProperty name;
+    private ObjectProperty<SexType> sex;
+    private StringProperty cardNo;
+    private ObjectProperty<Date> createTime;
+    private String faceImage;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getClazz() {
+        return clazzProperty().get();
+    }
+
+    public StringProperty clazzProperty() {
+        if (null == clazz) {
+            clazz = new SimpleStringProperty();
+        }
+        return clazz;
+    }
+
+    public void setClazz(String clazz) {
+        this.clazzProperty().set(clazz);
+    }
+
+    public String getName() {
+        return nameProperty().get();
+    }
+
+    public StringProperty nameProperty() {
+        if (null == name) {
+            name = new SimpleStringProperty();
+        }
+        return name;
+    }
+
+    public void setName(String name) {
+        this.nameProperty().set(name);
+    }
+
+    public Integer getSex() {
+        SexType type = sexProperty().get();
+        return null == type ? null : type.getValue();
+    }
+
+    public void setSex(Integer sex) {
+        if (null != sex) {
+            sexProperty().set(new SexType(sex, 1 == sex ? "男" : "女"));
+        }
+    }
+
+    public ObjectProperty<SexType> sexProperty() {
+        if (null == sex) {
+            sex = new SimpleObjectProperty<>();
+        }
+        return sex;
+    }
+
+    public void setSex(SexType sex) {
+        this.sexProperty().set(sex);
+    }
+
+    public String getCardNo() {
+        return cardNoProperty().get();
+    }
+
+    public StringProperty cardNoProperty() {
+        if (null == cardNo) {
+            cardNo = new SimpleStringProperty();
+        }
+        return cardNo;
+    }
+
+    public void setCardNo(String cardNo) {
+        this.cardNoProperty().set(cardNo);
+    }
+
+    public Date getCreateTime() {
+        return createTimeProperty().get();
+    }
+
+    public ObjectProperty<Date> createTimeProperty() {
+        if (null == createTime) {
+            createTime = new SimpleObjectProperty<>();
+        }
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTimeProperty().set(createTime);
+    }
+
+    public String getFaceImage() {
+        return faceImage;
+    }
+
+    public void setFaceImage(String faceImage) {
+        this.faceImage = faceImage;
+    }
+}

+ 46 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/School.java

@@ -0,0 +1,46 @@
+package com.usoftchina.smartschool.device.client.biometric.po;
+
+import java.io.Serializable;
+
+/**
+ * @author yingp
+ * @date 2019/3/11
+ */
+public class School implements Serializable {
+    /**
+     * 学校名称
+     */
+    private String name;
+    /**
+     * ID
+     */
+    private String id;
+    /**
+     * 私钥
+     */
+    private String secret;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+}

+ 36 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/SexType.java

@@ -0,0 +1,36 @@
+package com.usoftchina.smartschool.device.client.biometric.po;
+
+/**
+ * @author yingp
+ * @date 2019/11/15
+ */
+public class SexType {
+    private Integer value;
+    private String text;
+
+    public SexType(Integer value, String text) {
+        this.value = value;
+        this.text = text;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+
+    public void setValue(Integer value) {
+        this.value = value;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    @Override
+    public String toString() {
+        return text;
+    }
+}

+ 72 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/repository/FaceGateRepository.java

@@ -0,0 +1,72 @@
+package com.usoftchina.smartschool.device.client.biometric.repository;
+
+import com.usoftchina.smartschool.device.client.biometric.po.FaceGate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+@Repository
+public class FaceGateRepository {
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    public List<FaceGate> findAll() {
+        try {
+            return jdbcTemplate.query("select * from face_gate order by startTime desc",
+                    new BeanPropertyRowMapper<>(FaceGate.class));
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    public FaceGate findByIp(String ip) {
+        try {
+            return jdbcTemplate.queryForObject("select * from face_gate where ip=?",
+                    new BeanPropertyRowMapper<>(FaceGate.class), ip);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @Cacheable(value = "faceGate", key = "#id")
+    public FaceGate findById(String id) {
+        try {
+            return jdbcTemplate.queryForObject("select * from face_gate where id=?",
+                    new BeanPropertyRowMapper<>(FaceGate.class), id);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @CacheEvict(value = "faceGate", key = "#faceGate.id")
+    public boolean save(FaceGate faceGate) {
+        int ret = jdbcTemplate.update("insert into face_gate(id,name,ip,port,place,accessType,startTime,lastActiveTime) values " +
+                        "(?,?,?,?,?,?,?,?)", faceGate.getId(), faceGate.getName(), faceGate.getIp(), faceGate.getPort(),
+                faceGate.getPlace(), faceGate.getAccessType(), faceGate.getStartTime(), faceGate.getLastActiveTime());
+        return ret > 0;
+    }
+
+    @CacheEvict(value = "faceGate", key = "#faceGate.id")
+    public boolean update(FaceGate faceGate) {
+        int ret = jdbcTemplate.update("update face_gate set name=?,ip=?,port=?,place=?,accessType=?,lastActiveTime=? where id=?",
+                faceGate.getName(), faceGate.getIp(), faceGate.getPort(),
+                faceGate.getPlace(), faceGate.getAccessType(), faceGate.getLastActiveTime(), faceGate.getId());
+        return ret > 0;
+    }
+
+    @CacheEvict(value = "faceGate", key = "#id")
+    public boolean delete(String id) {
+        return jdbcTemplate.update("delete from face_gate where id=?", id) > 0;
+    }
+}

+ 71 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/repository/PersonRepository.java

@@ -0,0 +1,71 @@
+package com.usoftchina.smartschool.device.client.biometric.repository;
+
+import com.usoftchina.smartschool.device.client.biometric.po.Person;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+@Repository
+public class PersonRepository {
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    public List<Person> findAll() {
+        try {
+            return jdbcTemplate.query("select * from person order by createTime desc",
+                    new BeanPropertyRowMapper<>(Person.class));
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    public Person findByCardNo(String cardNo) {
+        try {
+            return jdbcTemplate.queryForObject("select * from person where cardNo=?",
+                    new BeanPropertyRowMapper<>(Person.class), cardNo);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @Cacheable(value = "person", key = "#id")
+    public Person findById(String id) {
+        try {
+            return jdbcTemplate.queryForObject("select * from person where id=?",
+                    new BeanPropertyRowMapper<>(Person.class), id);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @CacheEvict(value = "person", key = "#person.id")
+    public boolean save(Person person) {
+        int ret = jdbcTemplate.update("insert into person(id,name,cardNo,sex,clazz,createTime,faceImage) values " +
+                        "(?,?,?,?,?,?,?)", person.getId(), person.getName(), person.getCardNo(), person.getSex(),
+                person.getClazz(), person.getCreateTime(), person.getFaceImage());
+        return ret > 0;
+    }
+
+    @CacheEvict(value = "person", key = "#person.id")
+    public boolean update(Person person) {
+        int ret = jdbcTemplate.update("update person set name=?,cardNo=?,sex=?,clazz=?,faceImage=? where id=?",
+                person.getName(), person.getCardNo(), person.getSex(), person.getClazz(), person.getFaceImage(), person.getId());
+        return ret > 0;
+    }
+
+    @CacheEvict(value = "person", key = "#id")
+    public boolean delete(String id) {
+        return jdbcTemplate.update("delete from person where id=?", id) > 0;
+    }
+}

+ 38 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/repository/SchoolRepository.java

@@ -0,0 +1,38 @@
+package com.usoftchina.smartschool.device.client.biometric.repository;
+
+import com.usoftchina.smartschool.device.client.biometric.po.School;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author yingp
+ * @date 2019/3/11
+ */
+@Repository
+public class SchoolRepository {
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    public School find() {
+        try {
+            return jdbcTemplate.queryForObject("select * from school",
+                    new BeanPropertyRowMapper<>(School.class));
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    public void save(School school) {
+        jdbcTemplate.update("insert into school (name,id,secret) values (?,?,?)", school.getName(),
+                school.getId(), school.getSecret());
+    }
+
+    public void update(School school) {
+        jdbcTemplate.update("update school set name=?,id=?,secret=?",
+                school.getName(), school.getId(), school.getSecret());
+    }
+}

+ 124 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/service/FaceGateService.java

@@ -0,0 +1,124 @@
+package com.usoftchina.smartschool.device.client.biometric.service;
+
+import com.usoftchina.smartschool.device.api.DeviceApi;
+import com.usoftchina.smartschool.device.base.Result;
+import com.usoftchina.smartschool.device.client.biometric.config.DeviceServerProperties;
+import com.usoftchina.smartschool.device.client.biometric.po.FaceGate;
+import com.usoftchina.smartschool.device.client.biometric.repository.FaceGateRepository;
+import com.usoftchina.smartschool.device.client.biometric.util.RandomUtils;
+import com.usoftchina.smartschool.device.dto.AccessControlInfo;
+import com.usoftchina.smartschool.device.exception.ExceptionCode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/3/11
+ */
+@Service
+public class FaceGateService {
+    @Autowired
+    private FaceGateRepository faceGateRepository;
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Autowired
+    private DeviceServerProperties deviceServerProperties;
+
+    @Autowired
+    private DeviceApi deviceApi;
+
+    @Autowired
+    private SchoolService schoolService;
+
+    public List<FaceGate> findAll() {
+        return faceGateRepository.findAll();
+    }
+
+    /**
+     * 保存人脸闸机设备配置
+     *
+     * @param faceGate
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void save(FaceGate faceGate) {
+        FaceGate oldOne = faceGateRepository.findByIp(faceGate.getIp());
+        boolean isNew = StringUtils.isEmpty(faceGate.getId());
+        if (null != oldOne) {
+            if (isNew) {
+                faceGate.setId(oldOne.getId());
+                isNew = false;
+            } else if (!faceGate.getId().equals(oldOne.getId())) {
+                ExceptionCode.ERROR_IP_PORT_EXIST.occur();
+            }
+        } else {
+            if (isNew) {
+                faceGate.setId(RandomUtils.randomString());
+            }
+        }
+
+        if (isNew) {
+            faceGate.setStartTime(new Date());
+            // 调用设备接口监听
+            deviceApi.add(faceGate);
+            faceGateRepository.save(faceGate);
+        } else {
+            oldOne = faceGateRepository.findById(faceGate.getId());
+            if (!oldOne.getIp().equals(faceGate.getIp()) || oldOne.getPort() != faceGate.getPort()) {
+                // 调用设备接口监听
+                deviceApi.add(faceGate);
+                // 调用设备接口停止监听
+                deviceApi.remove(oldOne);
+            }
+            faceGateRepository.update(faceGate);
+        }
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(String id) {
+        FaceGate oldOne = faceGateRepository.findById(id);
+        if (null != oldOne) {
+            try {
+                // 先调用设备接口停止监听
+                deviceApi.remove(oldOne);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            faceGateRepository.delete(id);
+        }
+    }
+
+    public FaceGate findById(String id) {
+        return faceGateRepository.findById(id);
+    }
+
+    /**
+     * 远程保存门禁出入记录
+     *
+     * @param info
+     */
+    public void saveAccessRecord(AccessControlInfo info) {
+        //去除cardNo末尾中的\u0000...\u0000
+        info.setCardNo(info.getCardNo().trim());
+        info.setSchoolId(schoolService.find().getId());
+        ResponseEntity<Result> response = restTemplate.postForEntity(
+                deviceServerProperties.getAccessControlEvent(), info, Result.class);
+        if (response.getStatusCode() == HttpStatus.OK) {
+            Result result = response.getBody();
+            if (!result.isSuccess()) {
+                ExceptionCode.ERROR_UNKNOWN.occur(result.getMessage());
+            }
+        } else {
+            ExceptionCode.ERROR_UNKNOWN.occur("保存门禁出入记录失败");
+        }
+    }
+}

+ 63 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/service/PersonService.java

@@ -0,0 +1,63 @@
+package com.usoftchina.smartschool.device.client.biometric.service;
+
+import com.usoftchina.smartschool.device.client.biometric.po.Person;
+import com.usoftchina.smartschool.device.client.biometric.repository.PersonRepository;
+import com.usoftchina.smartschool.device.client.biometric.util.RandomUtils;
+import com.usoftchina.smartschool.device.exception.ExceptionCode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/11/15
+ */
+@Service
+public class PersonService {
+
+    @Autowired
+    private PersonRepository personRepository;
+
+    public List<Person> findAll() {
+        return personRepository.findAll();
+    }
+
+    /**
+     * 保存人员信息
+     *
+     * @param person
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void save(Person person) {
+        Person oldOne = personRepository.findByCardNo(person.getCardNo());
+        boolean isNew = StringUtils.isEmpty(person.getId());
+        if (null != oldOne) {
+            if (isNew) {
+                person.setId(oldOne.getId());
+                isNew = false;
+            } else if (!person.getId().equals(oldOne.getId())) {
+                ExceptionCode.ERROR_PERSON_EXIST.occur();
+            }
+        } else {
+            if (isNew) {
+                person.setId(RandomUtils.randomString());
+            }
+        }
+
+        if (isNew) {
+            person.setCreateTime(new Date());
+            personRepository.save(person);
+        } else {
+            personRepository.update(person);
+        }
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(String id) {
+        personRepository.delete(id);
+    }
+}

+ 28 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/service/SchoolService.java

@@ -0,0 +1,28 @@
+package com.usoftchina.smartschool.device.client.biometric.service;
+
+import com.usoftchina.smartschool.device.client.biometric.po.School;
+import com.usoftchina.smartschool.device.client.biometric.repository.SchoolRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author yingp
+ * @date 2019/3/11
+ */
+@Service
+public class SchoolService {
+    @Autowired
+    private SchoolRepository schoolRepository;
+
+    public School find() {
+        return schoolRepository.find();
+    }
+
+    public void save(School school) {
+        if (null != find()) {
+            schoolRepository.update(school);
+        } else {
+            schoolRepository.save(school);
+        }
+    }
+}

+ 117 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/support/AbstractJavaFxApplicationSupport.java

@@ -0,0 +1,117 @@
+package com.usoftchina.smartschool.device.client.biometric.support;
+
+import com.usoftchina.smartschool.device.client.biometric.util.AlertUtils;
+import com.usoftchina.smartschool.device.client.biometric.util.ViewUtils;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.scene.Scene;
+import javafx.scene.image.Image;
+import javafx.scene.paint.Color;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * @author yingp
+ * @date 2019/11/12
+ */
+public abstract class AbstractJavaFxApplicationSupport extends Application {
+
+    private static String[] savedArgs;
+    private static ConfigurableApplicationContext applicationContext;
+    private static Stage primaryStage;
+    private static SplashScreen splashScreen;
+
+    public static void launch(Class<? extends Application> appClass, SplashScreen screen, String... args) {
+        savedArgs = args;
+        splashScreen = screen;
+        launch(appClass, args);
+    }
+
+    @Override
+    public void init() {
+        CompletableFuture.supplyAsync(() ->
+                SpringApplication.run(getClass(), savedArgs)
+        ).whenComplete((ctx, throwable) -> {
+            if (throwable != null) {
+                Platform.runLater(() -> {
+                    AlertUtils.error(throwable.getMessage()).ifPresent(b -> {
+                        try {
+                            stop();
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                    });
+                });
+            } else {
+                Platform.runLater(() -> {
+                    applicationContext = ctx;
+                });
+            }
+        }).thenAcceptBothAsync(splashScreen.getShowing(), (ctx, closeSplash) -> {
+            Platform.runLater(closeSplash);
+        });
+    }
+
+    @Override
+    public void start(Stage stage) throws Exception {
+        primaryStage = stage;
+        initStage(primaryStage);
+        showSplash();
+    }
+
+    /**
+     * stage initialize
+     *
+     * @param stage
+     */
+    protected void initStage(Stage stage) {
+        stage.getIcons().addAll(getDefaultIcons());
+    }
+
+    protected Image[] getDefaultIcons() {
+        return new Image[0];
+    }
+
+    /**
+     * 进度条
+     */
+    private void showSplash() {
+        Stage splashStage = new Stage(StageStyle.TRANSPARENT);
+        Scene splashScene = new Scene(splashScreen.getParent(), Color.TRANSPARENT);
+        splashStage.setScene(splashScene);
+        splashStage.initStyle(StageStyle.TRANSPARENT);
+        splashStage.getIcons().addAll(getDefaultIcons());
+        splashStage.show();
+
+        splashScreen.getShowing().complete(() -> {
+            splashStage.hide();
+            splashStage.setScene((Scene) null);
+        });
+    }
+
+    @Override
+    public void stop() throws Exception {
+        super.stop();
+        if (applicationContext != null) {
+            applicationContext.close();
+        }
+    }
+
+    public static void showView(String viewPath) {
+        try {
+            Scene scene = ViewUtils.load(viewPath);
+            primaryStage.setScene(scene);
+            primaryStage.show();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static Stage getStage() {
+        return primaryStage;
+    }
+}

+ 40 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/support/SplashScreen.java

@@ -0,0 +1,40 @@
+package com.usoftchina.smartschool.device.client.biometric.support;
+
+import javafx.geometry.Insets;
+import javafx.scene.Node;
+import javafx.scene.Parent;
+import javafx.scene.control.ProgressBar;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.VBox;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * @author yingp
+ * @date 2019/11/12
+ */
+public class SplashScreen {
+    private static String DEFAULT_IMAGE = "/image/ydy.png";
+    private final CompletableFuture<Runnable> showing = new CompletableFuture();
+
+    public SplashScreen() {
+    }
+
+    public Parent getParent() {
+        ImageView imageView = new ImageView(getClass().getResource(getImagePath()).toExternalForm());
+        ProgressBar splashProgressBar = new ProgressBar();
+        splashProgressBar.setPrefWidth(imageView.getImage().getWidth());
+        VBox vbox = new VBox();
+        vbox.setPadding(new Insets(10));
+        vbox.getChildren().addAll(new Node[]{imageView, splashProgressBar});
+        return vbox;
+    }
+
+    public String getImagePath() {
+        return DEFAULT_IMAGE;
+    }
+
+    public CompletableFuture<Runnable> getShowing() {
+        return showing;
+    }
+}

+ 31 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/AlertUtils.java

@@ -0,0 +1,31 @@
+package com.usoftchina.smartschool.device.client.biometric.util;
+
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+
+import java.util.Optional;
+
+/**
+ * @author yingp
+ * @date 2019/11/12
+ */
+public class AlertUtils {
+    public static Optional<ButtonType> info(String message) {
+        return alert(message, Alert.AlertType.INFORMATION);
+    }
+
+    public static Optional<ButtonType> warn(String message) {
+        return alert(message, Alert.AlertType.WARNING);
+    }
+
+    public static Optional<ButtonType> error(String message) {
+        return alert(message, Alert.AlertType.ERROR);
+    }
+
+    public static Optional<ButtonType> alert(String message, Alert.AlertType alertType) {
+        Alert alert = new Alert(alertType);
+        alert.setHeaderText(null);
+        alert.setContentText(message);
+        return alert.showAndWait();
+    }
+}

+ 14 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/ClientConstant.java

@@ -0,0 +1,14 @@
+package com.usoftchina.smartschool.device.client.biometric.util;
+
+import java.net.URL;
+
+/**
+ * @author yingp
+ * @date 2019/11/12
+ */
+public class ClientConstant {
+    public static final String title = "云平安校园";
+
+    public static final URL icon = ClientConstant.class.getResource("/favicon.png");
+
+}

+ 15 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/RandomUtils.java

@@ -0,0 +1,15 @@
+package com.usoftchina.smartschool.device.client.biometric.util;
+
+import java.util.Random;
+
+/**
+ * @author yingp
+ * @date 2019/3/11
+ */
+public class RandomUtils {
+
+    public static String randomString() {
+        return String.valueOf(new Random().nextLong());
+    }
+
+}

+ 127 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/Tray.java

@@ -0,0 +1,127 @@
+package com.usoftchina.smartschool.device.client.biometric.util;
+
+import com.usoftchina.smartschool.device.client.biometric.DeviceClient;
+import javafx.application.Platform;
+import javafx.stage.Stage;
+
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+/**
+ * @author yingp
+ * @date 2019/11/12
+ */
+public class Tray {
+
+    private TrayIcon trayIcon;
+    private MenuItem showItem;
+    private MenuItem exitItem;
+    private ActionListener showListener;
+    private ActionListener exitListener;
+    private MouseListener mouseListener;
+    private static Tray instance;
+
+    private Tray() {
+        if (!SystemTray.isSupported()) {
+            return;
+        }
+        Platform.setImplicitExit(false);
+        trayIcon = new TrayIcon(Toolkit.getDefaultToolkit().getImage(ClientConstant.icon));
+        trayIcon.setImageAutoSize(true);
+        SystemTray tray = SystemTray.getSystemTray();
+        final PopupMenu popup = new PopupMenu();
+        showItem = new MenuItem("管理");
+        exitItem = new MenuItem("退出");
+        popup.setFont(new Font("Microsoft YaHei", Font.PLAIN, 16));
+        popup.add(showItem);
+        popup.add(exitItem);
+
+        trayIcon.setPopupMenu(popup);
+        trayIcon.setToolTip("云平安校园");
+        try {
+            tray.add(trayIcon);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static Tray getInstance() {
+        if (null == Tray.instance) {
+            synchronized (Tray.class) {
+                if (null == Tray.instance) {
+                    Tray.instance = new Tray();
+                }
+            }
+        }
+        return Tray.instance;
+    }
+
+    /**
+     * 更改系统托盘所监听的Stage
+     */
+    public void listen(Stage stage) {
+        if (showItem == null || exitItem == null || trayIcon == null) {
+            return;
+        }
+        if (null != showListener) {
+            showItem.removeActionListener(showListener);
+        }
+        if (null != exitListener) {
+            exitItem.removeActionListener(exitListener);
+        }
+        if (null != mouseListener) {
+            trayIcon.removeMouseListener(mouseListener);
+        }
+        showListener = e -> showStage(stage);
+        exitListener = e -> Platform.runLater(() -> System.exit(0));
+        mouseListener = new MouseAdapter() {
+            @Override
+            public void mouseClicked(MouseEvent e) {
+                if (e.getButton() == MouseEvent.BUTTON1) {
+                    showStage(stage);
+                }
+            }
+        };
+        showItem.addActionListener(showListener);
+        exitItem.addActionListener(exitListener);
+        trayIcon.addMouseListener(mouseListener);
+    }
+
+    /**
+     * 关闭窗口
+     * <pre>
+     *     如果支持系统托盘,就隐藏到托盘,不支持就直接退出
+     * </pre>
+     *
+     * @param stage
+     */
+    public void hide(Stage stage) {
+        Platform.runLater(() -> {
+            if (SystemTray.isSupported()) {
+                stage.hide();
+            } else {
+                System.exit(0);
+            }
+        });
+    }
+
+    /**
+     * 点击系统托盘, 显示界面
+     *
+     * @param stage
+     */
+    private void showStage(Stage stage) {
+        Platform.runLater(() -> {
+            if (stage.isIconified()) {
+                stage.setIconified(false);
+            }
+            if (!stage.isShowing()) {
+                DeviceClient.showMainView();
+            }
+            stage.toFront();
+        });
+    }
+}

+ 25 - 0
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/ViewUtils.java

@@ -0,0 +1,25 @@
+package com.usoftchina.smartschool.device.client.biometric.util;
+
+import com.usoftchina.smartschool.device.client.biometric.DeviceClient;
+import com.usoftchina.smartschool.device.client.biometric.control.ParameterizedScene;
+import javafx.fxml.FXMLLoader;
+
+import java.io.IOException;
+
+/**
+ * @author yingp
+ * @date 2019/11/12
+ */
+public class ViewUtils {
+    public static ParameterizedScene load(String viewPath, Object params) throws IOException {
+        FXMLLoader loader = new FXMLLoader();
+        loader.setLocation(DeviceClient.class.getResource(viewPath));
+        ParameterizedScene scene = new ParameterizedScene(loader.load());
+        scene.setParams(params);
+        return scene;
+    }
+
+    public static ParameterizedScene load(String viewPath) throws IOException {
+        return load(viewPath, null);
+    }
+}

+ 32 - 0
applications/device/device-client-biometric/src/main/resources/application.yml

@@ -0,0 +1,32 @@
+device:
+  server:
+    # 门禁事件的服务端接口
+    accessControlEvent: https://school-api.ydyhz.com/api/device/accesscontrol/event
+  # IC卡传输的服务端接口
+  icCard:
+    icCardUrl: https://school-api.ydyhz.com/api/device/iccard/consume/record
+server:
+  tomcat:
+    uri-encoding: UTF-8
+spring:
+  http:
+    encoding:
+      force: true
+      charset: utf-8
+      enabled: true
+  datasource:
+    primary:
+      driver-class-name: org.h2.Driver
+      jdbc-url: jdbc:h2:file:${user.home}/.device-client/data/device_client;FILE_LOCK=NO;DB_CLOSE_ON_EXIT=FALSE
+      username: admin
+      password: select111***
+    hikari:
+      minimum-idle: 5
+      maximum-pool-size: 50
+      idle-timeout: 30000
+      max-lifetime: 1800000
+      connection-timeout: 30000
+logging:
+  path: ${user.home}/.device-client/logs/
+  level:
+    com.usoftchina.smartschool: debug

+ 14 - 0
applications/device/device-client-biometric/src/main/resources/banner.txt

@@ -0,0 +1,14 @@
+${AnsiColor.BRIGHT_YELLOW}
+
+88        88   ad88888ba     ,ad8888ba,    88888888888  888888888888  ,ad8888ba,   88        88  88  888b      88         db
+88        88  d8"     "8b   d8"'    `"8b   88                88      d8"'    `"8b  88        88  88  8888b     88        d88b
+88        88  Y8,          d8'        `8b  88                88     d8'            88        88  88  88 `8b    88       d8'`8b
+88        88  `Y8aaaaa,    88          88  88aaaaa           88     88             88aaaaaaaa88  88  88  `8b   88      d8'  `8b
+88        88    `"""""8b,  88          88  88"""""           88     88             88""""""""88  88  88   `8b  88     d8YaaaaY8b
+88        88          `8b  Y8,        ,8P  88                88     Y8,            88        88  88  88    `8b 88    d8""""""""8b
+Y8a.    .a8P  Y8a     a8P   Y8a.    .a8P   88                88      Y8a.    .a8P  88        88  88  88     `8888   d8'        `8b
+ `"Y8888Y"'    "Y88888P"     `"Y8888Y"'    88                88       `"Y8888Y"'   88        88  88  88      `888  d8'          `8b
+
+
+@Copyright 2019 Shenzhen Usoft Information Technology Co., Ltd.
+${AnsiColor.DEFAULT}

BIN
applications/device/device-client-biometric/src/main/resources/favicon.ico


BIN
applications/device/device-client-biometric/src/main/resources/favicon.png


BIN
applications/device/device-client-biometric/src/main/resources/image/ydy.png


+ 54 - 0
applications/device/device-client-biometric/src/main/resources/logback-spring.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <include resource="org/springframework/boot/logging/logback/base.xml" />
+    <jmxConfigurator/>
+
+    <!--
+    %m
+    输出代码中指定的消息
+    %p
+    输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
+    %r
+    输出自应用启动到输出该log信息耗费的毫秒数
+    %c
+    输出所属的类目,通常就是所在类的全名
+    %t
+    输出产生该日志事件的线程名
+    %n
+    输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”
+    %d
+    输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},
+    输出类似:2002年10月18日 22:10:28,921
+    %l
+    输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
+    -->
+
+    <springProperty scope="context" name="log.path" source="logging.path" defaultValue="./logs"/>
+    <springProperty scope="context" name="spring.application.name" source="spring.application.name" defaultValue="device-client"/>
+    <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
+    <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
+    <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
+
+    <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
+
+    <appender name="ROOT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/root.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${log.path}/%d{yyyy-MM}/root-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
+            <maxFileSize>128MB</maxFileSize>
+            <maxHistory>7</maxHistory>
+            <totalSizeCap>20GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${common-pattern}</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.springframework" level="INFO"/>
+    <logger name="com.usoftchina.smartschool" level="DEBUG"/>
+
+    <root level="INFO">
+        <appender-ref ref="ROOT_APPENDER"/>
+    </root>
+
+</configuration>

+ 38 - 0
applications/device/device-client-biometric/src/main/resources/schema.sql

@@ -0,0 +1,38 @@
+create table if not exists face_gate
+(
+id varchar(50) primary key not null,
+name varchar(300) not null,
+ip varchar(20) not null,
+port int not null,
+place varchar(30),
+accessType int,
+startTime datetime,
+lastActiveTime datetime
+);
+
+create table if not exists school
+(
+name varchar(500) not null,
+id varchar(32) not null,
+secret varchar(100) not null
+);
+
+create table if not exists iccard
+(
+ip varchar(20) not null,
+port int not null,
+username varchar(30) not null,
+password varchar(50) not null,
+databaseName varchar(30) not null
+);
+
+create table if not exists person
+(
+id varchar(50) primary key not null,
+name varchar(100) not null,
+clazz varchar(100),
+cardNo varchar(20) not null,
+sex int,
+createTime datetime,
+faceImage clob
+);

+ 7 - 0
applications/device/device-client-biometric/src/main/resources/style/login.css

@@ -0,0 +1,7 @@
+.root {
+    -fx-background-image: url("../image/ydy.png");
+    -fx-background-position: center 15px;
+    -fx-background-size: 360px 80px;
+    -fx-background-repeat: no-repeat;
+    -fx-background-color: #fff;
+}

+ 175 - 0
applications/device/device-client-biometric/src/main/resources/style/main.css

@@ -0,0 +1,175 @@
+* {
+    -fx-primary-color: #1890ff;
+    -fx-dark-color: #949495;
+    -fx-gray-color: #e0e0e0;
+    -fx-light-color: #eff3fc;
+}
+
+.root {
+    -fx-font-size: 14px;
+    -fx-font-family: "Microsoft YaHei";
+}
+
+/** tab-pane **/
+.tab {
+    -fx-background-insets: 0;
+    -fx-border-style: solid;
+    -fx-border-width: 0 0 2px 0;
+    -fx-border-color: transparent;
+    -fx-background-color: transparent;
+    -fx-focus-color: transparent;
+    -fx-faint-focus-color: transparent;
+}
+
+.tab .tab-label {
+    -fx-text-fill: -fx-dark-color;
+    -fx-font: bold 14px/20px 'Microsoft YaHei';
+    -fx-focus-color: transparent;
+    -fx-faint-focus-color: transparent;
+    -fx-padding: 0 10px 0 10px;
+}
+
+.tab:selected {
+    -fx-border-color: -fx-primary-color;
+    -fx-focus-color: transparent;
+    -fx-faint-focus-color: transparent;
+}
+
+.tab:selected .tab-label {
+    -fx-text-fill: -fx-primary-color;
+}
+
+.tab:hover {
+    -fx-border-color: -fx-primary-color;
+}
+
+.tab:hover .tab-label {
+    -fx-text-fill: -fx-primary-color;
+}
+
+.tab-pane .tab-header-area .tab-header-background {
+    -fx-background-color: -fx-light-color;
+    -fx-focus-color: transparent;
+    -fx-faint-focus-color: transparent;
+    -fx-border-color: #ceced2;
+    -fx-border-width: 0 0 1px 0;
+}
+
+.tab-pane:top *.tab-header-area {
+    -fx-background-insets: 0;
+    -fx-padding: 0;
+    -fx-focus-color: transparent;
+    -fx-faint-focus-color: transparent;
+}
+
+.tab-pane {
+    -fx-background-color: white;
+    -fx-border-style: solid;
+    -fx-border-color: #bfcfda;
+    -fx-border-width: 1px 0 0 0;
+    -fx-focus-color: transparent;
+    -fx-faint-focus-color: transparent;
+}
+
+/* tree-view */
+.tree-view {
+    -fx-background-color: #f8f8fb;
+    -fx-border-color: #bfcfda;
+}
+
+.tree-view .tree-cell {
+    -fx-background-color: #f8f8fb;
+    -fx-padding: 6px 0;
+    -fx-text-fill: -fx-dark-color;
+    -fx-font-weight: bold;
+}
+
+.tree-cell .tree-disclosure-node .arrow {
+    -fx-background-color: transparent;
+    -fx-text-fill: transparent;
+}
+
+.tree-view .tree-cell.tree-cell-leaf {
+    -fx-font-weight: normal;
+}
+
+.tree-view .tree-cell.tree-cell-leaf:focused {
+    -fx-background-color: -fx-light-color;
+    -fx-text-fill: -fx-primary-color;
+    -fx-border-width: 0;
+}
+
+.tree-view .tree-cell.tree-cell-leaf:hover {
+    -fx-background-color: -fx-light-color;
+}
+
+AnchorPane, BorderPane, VBox {
+    -fx-background-color: white;
+}
+
+/*table-view*/
+.table-view {
+    -fx-background-color: transparent;
+    -fx-padding: 0;
+    -fx-background-insets: 0;
+    -fx-border-width: 0;
+}
+
+.table-view:focused {
+    -fx-background-color: transparent;
+}
+
+.table-view .column-header {
+    -fx-background-color: -fx-light-color;
+    -fx-pref-height: 32px;
+    -fx-border-color: #bfcfda;
+    -fx-border-width: 0 1px 1px 0;
+}
+
+.table-view .column-header .label {
+    -fx-alignment: center;
+    -fx-font: bold 14px/20px 'Microsoft YaHei';
+    -fx-text-fill: -fx-dark-color;
+}
+
+.table-view .column-header-background {
+    -fx-background-color: transparent;
+}
+
+.table-view .table-row-cell:selected {
+    -fx-background-color: -fx-gray-color;
+}
+
+.table-view .table-row-cell:selected .table-cell {
+    -fx-text-fill: #333;
+}
+
+/*toolbar*/
+.tool-bar {
+    -fx-background-color: #f8f8fb;
+    -fx-background-insets: 0;
+    -fx-border-width: 0 0 1px 0;
+    -fx-border-color: #bfcfda;
+}
+
+.tool-bar .button {
+    -fx-background-color: transparent;
+    -fx-border-radius: 0;
+    -fx-background-radius: 0;
+    -fx-border-width: 0;
+}
+
+.tool-bar .button:hover {
+    -fx-background-color: -fx-primary-color;
+    -fx-text-fill: white;
+}
+
+/*form*/
+.field-control {
+    -fx-font-size: 14px;
+}
+
+.image-view {
+    -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 10, 0, 0, 0);
+    -fx-border-color: #bfcfda;
+}

+ 39 - 0
applications/device/device-client-biometric/src/main/resources/view/gate.fxml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.control.TableColumn?>
+<?import javafx.scene.control.TableView?>
+<?import javafx.scene.layout.BorderPane?>
+
+<?import javafx.scene.control.ToolBar?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.Separator?>
+<BorderPane xmlns="http://javafx.com/javafx/8.0.172-ea"
+            fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.GateController"
+            xmlns:fx="http://javafx.com/fxml/1">
+    <top>
+        <ToolBar BorderPane.alignment="CENTER">
+            <items>
+                <Button text="新增" onAction="#handleAdd"/>
+                <Separator/>
+                <Button fx:id="editButton" text="修改" onAction="#handleEdit" disable="true"/>
+                <Separator/>
+                <Button fx:id="delButton" text="删除" onAction="#handleDelete" disable="true"/>
+                <Separator/>
+                <Button text="刷新" onAction="#handleRefresh"/>
+            </items>
+        </ToolBar>
+    </top>
+    <center>
+        <TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
+            <columns>
+                <TableColumn fx:id="nameCol" prefWidth="100.0" text="设备名称"/>
+                <TableColumn fx:id="placeCol" prefWidth="100.0" text="设备位置"/>
+                <TableColumn fx:id="ipCol" prefWidth="100.0" text="设备IP"/>
+                <TableColumn fx:id="accessTypeCol" prefWidth="100.0" text="出入类型"/>
+                <TableColumn fx:id="startTimeCol" prefWidth="200.0" text="注册时间"/>
+                <TableColumn fx:id="lastActiveCol" prefWidth="150.0" text="上次心跳"/>
+                <TableColumn fx:id="statusCol" prefWidth="100.0" text="状态"/>
+            </columns>
+        </TableView>
+    </center>
+</BorderPane>

+ 45 - 0
applications/device/device-client-biometric/src/main/resources/view/gate_edit.fxml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.control.TextField?>
+
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.control.ComboBox?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.layout.AnchorPane?>
+<AnchorPane prefHeight="340.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.172-ea"
+            xmlns:fx="http://javafx.com/fxml/1"
+            stylesheets="/style/main.css"
+            fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.GateEditController">
+    <children>
+        <HBox styleClass="field-control" layoutX="10" layoutY="30">
+            <children>
+                <Label text="设备名称" prefWidth="100" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <TextField fx:id="nameField" prefWidth="200" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="80">
+            <children>
+                <Label text="设备IP" prefWidth="100" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <TextField fx:id="ipField" prefWidth="200" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="130">
+            <children>
+                <Label text="安装位置" prefWidth="100" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <TextField fx:id="placeField" prefWidth="200" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="180">
+            <children>
+                <Label text="出入类型" prefWidth="100" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <ComboBox fx:id="accessTypeBox" prefWidth="200" prefHeight="40"/>
+            </children>
+        </HBox>
+        <AnchorPane styleClass="field-control" layoutX="10" layoutY="230">
+            <children>
+                <Button layoutX="100" layoutY="0" text="保存" prefWidth="200" prefHeight="40" onAction="#handleSave"/>
+            </children>
+        </AnchorPane>
+    </children>
+</AnchorPane>

+ 32 - 0
applications/device/device-client-biometric/src/main/resources/view/login.fxml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.PasswordField?>
+<?import javafx.scene.control.TextField?>
+<?import javafx.scene.layout.AnchorPane?>
+<?import javafx.scene.text.Font?>
+
+<AnchorPane prefHeight="363.0" prefWidth="400.0" stylesheets="/style/login.css"
+            xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1"
+            fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.LoginController">
+    <children>
+        <TextField fx:id="usernameField" layoutX="91.0" layoutY="131.0" prefHeight="43.0" prefWidth="218.0"
+                   promptText="账号">
+            <font>
+                <Font size="14.0"/>
+            </font>
+        </TextField>
+        <PasswordField fx:id="passwordField" layoutX="92.0" layoutY="190.0" prefHeight="43.0" prefWidth="218.0"
+                       promptText="密码">
+            <font>
+                <Font size="14.0"/>
+            </font>
+        </PasswordField>
+        <Button fx:id="loginButton" layoutX="92.0" layoutY="253.0" mnemonicParsing="false" onAction="#handleLogin"
+                prefHeight="43.0" prefWidth="218.0" text="登录">
+            <font>
+                <Font size="14.0"/>
+            </font>
+        </Button>
+    </children>
+</AnchorPane>

+ 47 - 0
applications/device/device-client-biometric/src/main/resources/view/main.fxml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.control.TabPane?>
+<?import javafx.scene.layout.BorderPane?>
+
+<?import javafx.scene.control.TreeView?>
+<?import javafx.scene.control.TreeItem?>
+<?import com.usoftchina.smartschool.device.client.biometric.control.NavItem?>
+<?import javafx.scene.control.Tab?>
+<?import javafx.scene.layout.GridPane?>
+<?import javafx.scene.control.ToolBar?>
+<?import javafx.scene.control.Button?>
+<BorderPane prefHeight="700.0" prefWidth="960.0" stylesheets="/style/main.css"
+            xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1"
+            fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.MainController">
+    <left>
+        <TreeView fx:id="treeView" prefHeight="200.0" prefWidth="140.0" showRoot="false">
+            <root>
+                <TreeItem value="root">
+                    <children>
+                        <TreeItem expanded="true" value="设备管理">
+                            <children>
+                                <NavItem value="人脸闸机" viewPath="/view/gate.fxml"/>
+                            </children>
+                        </TreeItem>
+                        <TreeItem expanded="true" value="人员管理">
+                            <children>
+                                <NavItem value="人员信息" viewPath="/view/person.fxml"/>
+                            </children>
+                        </TreeItem>
+                        <TreeItem expanded="true" value="基础设置">
+                            <children>
+                                <NavItem value="学校信息" viewPath="/view/gate.fxml"/>
+                                <NavItem value="IC卡" viewPath="/view/gate.fxml"/>
+                                <NavItem value="云平台" viewPath="/view/gate.fxml"/>
+                            </children>
+                        </TreeItem>
+                    </children>
+                </TreeItem>
+            </root>
+        </TreeView>
+    </left>
+    <center>
+        <TabPane fx:id="tabPane" prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
+        </TabPane>
+    </center>
+</BorderPane>

+ 39 - 0
applications/device/device-client-biometric/src/main/resources/view/person.fxml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.control.TableColumn?>
+<?import javafx.scene.control.TableView?>
+<?import javafx.scene.layout.BorderPane?>
+
+<?import javafx.scene.control.ToolBar?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.Separator?>
+<BorderPane xmlns="http://javafx.com/javafx/8.0.172-ea"
+            fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.PersonController"
+            xmlns:fx="http://javafx.com/fxml/1">
+    <top>
+        <ToolBar BorderPane.alignment="CENTER">
+            <items>
+                <Button text="新增" onAction="#handleAdd"/>
+                <Separator/>
+                <Button fx:id="editButton" text="修改" onAction="#handleEdit" disable="true"/>
+                <Separator/>
+                <Button fx:id="delButton" text="删除" onAction="#handleDelete" disable="true"/>
+                <Separator/>
+                <Button text="导入" onAction="#handleImport"/>
+                <Separator/>
+                <Button text="刷新" onAction="#handleRefresh"/>
+            </items>
+        </ToolBar>
+    </top>
+    <center>
+        <TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
+            <columns>
+                <TableColumn fx:id="classCol" prefWidth="120.0" text="班级"/>
+                <TableColumn fx:id="nameCol" prefWidth="100.0" text="姓名"/>
+                <TableColumn fx:id="sexCol" prefWidth="100.0" text="性别"/>
+                <TableColumn fx:id="cardNoCol" prefWidth="120.0" text="卡号"/>
+                <TableColumn fx:id="createTimeCol" prefWidth="200.0" text="创建时间"/>
+            </columns>
+        </TableView>
+    </center>
+</BorderPane>

+ 49 - 0
applications/device/device-client-biometric/src/main/resources/view/person_edit.fxml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.ComboBox?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.control.TextField?>
+<?import javafx.scene.image.ImageView?>
+<?import javafx.scene.layout.AnchorPane?>
+<?import javafx.scene.layout.HBox?>
+
+<AnchorPane prefHeight="340.0" prefWidth="600.0" stylesheets="/style/main.css" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.PersonEditController">
+    <children>
+        <HBox layoutX="10" layoutY="30" styleClass="field-control">
+            <children>
+                <Label alignment="CENTER_RIGHT" prefHeight="40" prefWidth="100" style="-fx-padding: 0 5px 0 0" text="班级" />
+                <TextField fx:id="classField" prefHeight="40" prefWidth="200" />
+            </children>
+        </HBox>
+        <HBox layoutX="10" layoutY="80" styleClass="field-control">
+            <children>
+                <Label alignment="CENTER_RIGHT" prefHeight="40" prefWidth="100" style="-fx-padding: 0 5px 0 0" text="姓名" />
+                <TextField fx:id="nameField" prefHeight="40" prefWidth="200" />
+            </children>
+        </HBox>
+        <HBox layoutX="10" layoutY="130" styleClass="field-control">
+            <children>
+                <Label alignment="CENTER_RIGHT" prefHeight="40" prefWidth="100" style="-fx-padding: 0 5px 0 0" text="卡号" />
+                <TextField fx:id="cardNoField" prefHeight="40" prefWidth="200" />
+            </children>
+        </HBox>
+        <HBox layoutX="10" layoutY="180" styleClass="field-control">
+            <children>
+                <Label alignment="CENTER_RIGHT" prefHeight="40" prefWidth="100" style="-fx-padding: 0 5px 0 0" text="性别" />
+                <ComboBox fx:id="sexBox" prefHeight="40" prefWidth="200" />
+            </children>
+        </HBox>
+        <AnchorPane layoutX="10" layoutY="230" styleClass="field-control">
+            <children>
+                <Button layoutX="100" layoutY="0" onAction="#handleSave" prefHeight="40" prefWidth="200" text="保存" />
+                <Button fx:id="goonButton" layoutX="330" layoutY="0" onAction="#handleSaveAndGoon" prefHeight="40" prefWidth="210" text="保存并继续新增" />
+            </children>
+        </AnchorPane>
+        <ImageView fx:id="imageView" fitHeight="155.0" fitWidth="210.0" layoutX="340.0" layoutY="65.0" pickOnBounds="true" preserveRatio="true" />
+        <HBox alignment="CENTER_LEFT" layoutX="340.0" layoutY="30.0" prefHeight="20.0" prefWidth="210.0" spacing="10" style="-fx-background-color: transparent">
+            <Button onAction="#handleChoosePicture" prefHeight="20.0" prefWidth="110.0" text="选择本地图片" />
+            <Button onAction="#handleOpenVideo" prefHeight="20.0" prefWidth="100.0" text="打开摄像头" />
+        </HBox>
+    </children>
+</AnchorPane>

+ 2 - 1
applications/device/device-core/src/main/java/com/usoftchina/smartschool/device/exception/ExceptionCode.java

@@ -10,7 +10,8 @@ import org.springframework.util.StringUtils;
 public enum ExceptionCode implements BaseExceptionCode {
 
     ERROR_UNKNOWN(500),
-    ERROR_IP_PORT_EXIST(501);
+    ERROR_IP_PORT_EXIST(501),
+    ERROR_PERSON_EXIST(502);
 
     private int code;
 

+ 37 - 0
applications/device/device-sdk-biometric/pom.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>smartschool-platform</artifactId>
+        <groupId>com.usoftchina.smartschool</groupId>
+        <version>1.0.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>device-sdk-biometric</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.usoftchina.smartschool</groupId>
+            <artifactId>device-sdk</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.usoftchina.smartschool</groupId>
+            <artifactId>device-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 207 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/AccessPo.java

@@ -0,0 +1,207 @@
+package com.usoftchina.smartschool.device.biometric;
+
+import java.util.Date;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+public class AccessPo {
+    /**
+     * 设备代码
+     */
+    private String deviceCode;
+    /**
+     * 比对结果
+     * 1:比对通过 2:比对不通过 3:未比对
+     */
+    private Integer verifyResult;
+    /**
+     * 通过时间
+     */
+    private Date passTime;
+    /**
+     * 人员 ID
+     */
+    private String personId;
+    private String name;
+    /**
+     * 性别代码
+     * 1 男 2 女
+     */
+    private String genderCode;
+    /**
+     * 民族代码
+     */
+    private String nationCode;
+    private String phone;
+    /**
+     * 人员数据类型
+     * 0:员工
+     * 1:访客
+     * 2:自定义人员类型 1
+     * 3:自定义人员类型 2
+     * 8:白名单
+     * 9:黑名单
+     * 108:设备本地白名单
+     * 109:设备本地黑名单
+     */
+    private String dataType;
+    private Date endTime;
+    private String accessCardNum;
+    private String cardImgPath;
+    private String sceneImgPath;
+    private String faceImgPath;
+    private Float faceQualityScore;
+    private Float faceCompareScore;
+
+    public String getDeviceCode() {
+        return deviceCode;
+    }
+
+    public void setDeviceCode(String deviceCode) {
+        this.deviceCode = deviceCode;
+    }
+
+    public Integer getVerifyResult() {
+        return verifyResult;
+    }
+
+    public void setVerifyResult(Integer verifyResult) {
+        this.verifyResult = verifyResult;
+    }
+
+    public Date getPassTime() {
+        return passTime;
+    }
+
+    public void setPassTime(Date passTime) {
+        this.passTime = passTime;
+    }
+
+    public String getPersonId() {
+        return personId;
+    }
+
+    public void setPersonId(String personId) {
+        this.personId = personId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getGenderCode() {
+        return genderCode;
+    }
+
+    public void setGenderCode(String genderCode) {
+        this.genderCode = genderCode;
+    }
+
+    public String getNationCode() {
+        return nationCode;
+    }
+
+    public void setNationCode(String nationCode) {
+        this.nationCode = nationCode;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getDataType() {
+        return dataType;
+    }
+
+    public void setDataType(String dataType) {
+        this.dataType = dataType;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    public String getAccessCardNum() {
+        return accessCardNum;
+    }
+
+    public void setAccessCardNum(String accessCardNum) {
+        this.accessCardNum = accessCardNum;
+    }
+
+    public String getCardImgPath() {
+        return cardImgPath;
+    }
+
+    public void setCardImgPath(String cardImgPath) {
+        this.cardImgPath = cardImgPath;
+    }
+
+    public String getSceneImgPath() {
+        return sceneImgPath;
+    }
+
+    public void setSceneImgPath(String sceneImgPath) {
+        this.sceneImgPath = sceneImgPath;
+    }
+
+    public String getFaceImgPath() {
+        return faceImgPath;
+    }
+
+    public void setFaceImgPath(String faceImgPath) {
+        this.faceImgPath = faceImgPath;
+    }
+
+    public Float getFaceQualityScore() {
+        return faceQualityScore;
+    }
+
+    public void setFaceQualityScore(Float faceQualityScore) {
+        this.faceQualityScore = faceQualityScore;
+    }
+
+    public Float getFaceCompareScore() {
+        return faceCompareScore;
+    }
+
+    public void setFaceCompareScore(Float faceCompareScore) {
+        this.faceCompareScore = faceCompareScore;
+    }
+
+    @Override
+    public String toString() {
+        return "AccessPo{" +
+                "deviceCode='" + deviceCode + '\'' +
+                ", verifyResult=" + verifyResult +
+                ", passTime=" + passTime +
+                ", personId='" + personId + '\'' +
+                ", name='" + name + '\'' +
+                ", genderCode='" + genderCode + '\'' +
+                ", nationCode='" + nationCode + '\'' +
+                ", phone='" + phone + '\'' +
+                ", dataType='" + dataType + '\'' +
+                ", endTime=" + endTime +
+                ", accessCardNum='" + accessCardNum + '\'' +
+                ", cardImgPath='" + cardImgPath + '\'' +
+                ", sceneImgPath='" + sceneImgPath + '\'' +
+                ", faceImgPath='" + faceImgPath + '\'' +
+                ", faceQualityScore=" + faceQualityScore +
+                ", faceCompareScore=" + faceCompareScore +
+                '}';
+    }
+}

+ 14 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/BiometricConfig.java

@@ -0,0 +1,14 @@
+package com.usoftchina.smartschool.device.biometric;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+@Configuration
+@ComponentScan("com.usoftchina.smartschool.device.biometric")
+public class BiometricConfig {
+
+}

+ 46 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/BiometricDeviceService.java

@@ -0,0 +1,46 @@
+package com.usoftchina.smartschool.device.biometric;
+
+import com.usoftchina.smartschool.device.api.DeviceApi;
+import com.usoftchina.smartschool.device.dto.DeviceInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+@Service
+public class BiometricDeviceService implements DeviceApi {
+
+    private final Logger logger = LoggerFactory.getLogger(BiometricDeviceService.class);
+
+    @Value("${server.port:8080}")
+    private Integer serverPort;
+
+    @Override
+    public void add(DeviceInfo info) {
+        // 广播 服务端IP:端口,让设备拿到服务端的http接口地址
+        try {
+            String serverIp = InetAddress.getLocalHost().getHostAddress();
+            DatagramSocket socket = new DatagramSocket();
+            byte[] message = (serverIp + ":" + serverPort).getBytes("utf-8");
+            DatagramPacket packet = new DatagramPacket(message, message.length,
+                    InetAddress.getByName("255.255.255.255"), 28888);
+            logger.debug("broadcast " + serverIp + ":" + serverPort);
+            socket.send(packet);
+            socket.close();
+        } catch (Exception e) {
+            logger.error("broadcast to " + info.getIp() + " error", e);
+        }
+    }
+
+    @Override
+    public void remove(DeviceInfo info) {
+    }
+}

+ 26 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/DeviceManageController.java

@@ -0,0 +1,26 @@
+package com.usoftchina.smartschool.device.biometric;
+
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+@RestController
+@RequestMapping("/DeviceManageService")
+public class DeviceManageController {
+
+    /**
+     * 接收心跳
+     *
+     * @param request
+     * @return
+     */
+    @PostMapping("/ACSServer")
+    public ResultPo deviceHeart(RequestPo<HealthInfo> request) {
+        System.out.println(request);
+        return ResultPo.success().setHoldRequests("0");
+    }
+}

+ 66 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/HealthInfo.java

@@ -0,0 +1,66 @@
+package com.usoftchina.smartschool.device.biometric;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+public class HealthInfo {
+    /**
+     * 设备代码
+     */
+    private String deviceCode;
+    /**
+     * 设备 IP
+     */
+    private String deviceIp;
+    /**
+     * 健康状态,1:健康 0:不健康
+     */
+    private String healthFlag;
+    /**
+     * 心跳间隔
+     */
+    private Integer informInterval;
+
+    public String getDeviceCode() {
+        return deviceCode;
+    }
+
+    public void setDeviceCode(String deviceCode) {
+        this.deviceCode = deviceCode;
+    }
+
+    public String getDeviceIp() {
+        return deviceIp;
+    }
+
+    public void setDeviceIp(String deviceIp) {
+        this.deviceIp = deviceIp;
+    }
+
+    public String getHealthFlag() {
+        return healthFlag;
+    }
+
+    public void setHealthFlag(String healthFlag) {
+        this.healthFlag = healthFlag;
+    }
+
+    public Integer getInformInterval() {
+        return informInterval;
+    }
+
+    public void setInformInterval(Integer informInterval) {
+        this.informInterval = informInterval;
+    }
+
+    @Override
+    public String toString() {
+        return "HealthInfo{" +
+                "deviceCode='" + deviceCode + '\'' +
+                ", deviceIp='" + deviceIp + '\'' +
+                ", healthFlag='" + healthFlag + '\'' +
+                ", informInterval=" + informInterval +
+                '}';
+    }
+}

+ 34 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/RequestPo.java

@@ -0,0 +1,34 @@
+package com.usoftchina.smartschool.device.biometric;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+public class RequestPo<T> {
+    private String responseName;
+    private T responseBody;
+
+    public String getResponseName() {
+        return responseName;
+    }
+
+    public void setResponseName(String responseName) {
+        this.responseName = responseName;
+    }
+
+    public T getResponseBody() {
+        return responseBody;
+    }
+
+    public void setResponseBody(T responseBody) {
+        this.responseBody = responseBody;
+    }
+
+    @Override
+    public String toString() {
+        return "RequestPo{" +
+                "responseName='" + responseName + '\'' +
+                ", responseBody=" + responseBody +
+                '}';
+    }
+}

+ 65 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/ResultPo.java

@@ -0,0 +1,65 @@
+package com.usoftchina.smartschool.device.biometric;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+public class ResultPo {
+    /**
+     * 返回状态
+     */
+    private String status;
+    /**
+     * 返回信息
+     */
+    private String message;
+    /**
+     * 是否携带请求
+     */
+    private String holdRequests;
+
+    public static ResultPo success() {
+        ResultPo result = new ResultPo();
+        result.setStatus("0");
+        result.setMessage("成功");
+        return result;
+    }
+
+    public static ResultPo error(String message) {
+        ResultPo result = new ResultPo();
+        result.setStatus("-1");
+        result.setMessage(message);
+        return result;
+    }
+
+    public static ResultPo error() {
+        return error("系统异常");
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public ResultPo setStatus(String status) {
+        this.status = status;
+        return this;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public ResultPo setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+    public String getHoldRequests() {
+        return holdRequests;
+    }
+
+    public ResultPo setHoldRequests(String holdRequests) {
+        this.holdRequests = holdRequests;
+        return this;
+    }
+}

+ 25 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/VerificationController.java

@@ -0,0 +1,25 @@
+package com.usoftchina.smartschool.device.biometric;
+
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+@RestController
+@RequestMapping("/verificationInterface")
+public class VerificationController {
+
+    /**
+     * 接收闸机过人数据
+     *
+     * @return
+     */
+    @PostMapping("/passlog/passFullLog")
+    public ResultPo passLog(AccessPo access) {
+        System.out.println(access);
+        return ResultPo.success();
+    }
+}

+ 3 - 0
applications/device/device-sdk-biometric/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,3 @@
+# Auto Configuration
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.usoftchina.smartschool.device.biometric.BiometricConfig

+ 2 - 0
applications/device/pom.xml

@@ -18,6 +18,8 @@
         <module>device-sdk</module>
         <module>device-client</module>
         <module>device-core</module>
+        <module>device-sdk-biometric</module>
+        <module>device-client-biometric</module>
     </modules>
 
 </project>

+ 5 - 0
pom.xml

@@ -283,6 +283,11 @@
                 <artifactId>device-sdk-dahua</artifactId>
                 <version>${project.release.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.usoftchina.smartschool</groupId>
+                <artifactId>device-sdk-biometric</artifactId>
+                <version>${project.release.version}</version>
+            </dependency>
             <dependency>
                 <groupId>com.usoftchina.smartschool</groupId>
                 <artifactId>file-server</artifactId>