瀏覽代碼

客户端开发

yingp 6 年之前
父節點
當前提交
2af54ec423
共有 52 個文件被更改,包括 3009 次插入107 次删除
  1. 110 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/control/IntegerField.java
  2. 1 4
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/CameraController.java
  3. 69 3
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/GateController.java
  4. 13 8
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/GateEditController.java
  5. 92 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/GateIssueController.java
  6. 126 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/GateIssueStatusController.java
  7. 159 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/GateSettingController.java
  8. 50 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/PersonController.java
  9. 20 4
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/PersonEditController.java
  10. 15 12
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/AccessType.java
  11. 30 14
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/FaceGate.java
  12. 75 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/GatePerson.java
  13. 154 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/GatePersonView.java
  14. 39 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/HealthStatus.java
  15. 134 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/PersonGateView.java
  16. 27 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/po/Selectable.java
  17. 6 6
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/repository/FaceGateRepository.java
  18. 91 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/repository/GatePersonRepository.java
  19. 69 12
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/service/FaceGateService.java
  20. 169 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/service/GatePersonService.java
  21. 4 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/service/PersonService.java
  22. 46 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/task/GateScheduler.java
  23. 31 0
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/NotifyUtils.java
  24. 2 2
      applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/util/RandomUtils.java
  25. 13 1
      applications/device/device-client-biometric/src/main/resources/schema.sql
  26. 4 0
      applications/device/device-client-biometric/src/main/resources/style/main.css
  27. 14 6
      applications/device/device-client-biometric/src/main/resources/view/gate.fxml
  28. 4 4
      applications/device/device-client-biometric/src/main/resources/view/gate_edit.fxml
  29. 22 0
      applications/device/device-client-biometric/src/main/resources/view/gate_issue.fxml
  30. 33 0
      applications/device/device-client-biometric/src/main/resources/view/gate_issue_status.fxml
  31. 109 0
      applications/device/device-client-biometric/src/main/resources/view/gate_setting.fxml
  32. 5 0
      applications/device/device-client-biometric/src/main/resources/view/person.fxml
  33. 4 4
      applications/device/device-client-biometric/src/main/resources/view/person_edit.fxml
  34. 14 0
      applications/device/device-client-biometric/src/main/resources/view/person_issue.fxml
  35. 161 0
      applications/device/device-core/src/main/java/com/usoftchina/smartschool/device/util/DateUtils.java
  36. 87 0
      applications/device/device-core/src/main/java/com/usoftchina/smartschool/device/util/DirtyBean.java
  37. 9 0
      applications/device/device-sdk-biometric/pom.xml
  38. 3 1
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/AccessLog.java
  39. 219 19
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/BiometricDeviceService.java
  40. 17 2
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/DeviceManageController.java
  41. 384 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/DeviceSetting.java
  42. 44 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/FaceLevel.java
  43. 2 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/HealthInfo.java
  44. 20 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/HeartbeatEvent.java
  45. 43 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/LivingLevel.java
  46. 139 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/PersonPo.java
  47. 23 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/ReqListPo.java
  48. 51 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/ResultListPo.java
  49. 7 1
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/ResultPo.java
  50. 34 0
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/Setting.java
  51. 6 2
      applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/VerificationController.java
  52. 6 2
      applications/device/device-sdk/src/main/java/com/usoftchina/smartschool/device/api/DeviceApi.java

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

@@ -0,0 +1,110 @@
+package com.usoftchina.smartschool.device.client.biometric.control;
+
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.control.TextField;
+
+import java.math.BigDecimal;
+import java.text.NumberFormat;
+import java.text.ParseException;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public class IntegerField extends TextField {
+    private final NumberFormat nf;
+    private IntegerProperty value;
+    private IntegerProperty maxValue;
+
+    public final int getValue() {
+        return valueProperty().get();
+    }
+
+    public final void setValue(int value) {
+        valueProperty().set(value);
+    }
+
+    public IntegerProperty valueProperty() {
+        if (null == value) {
+            value = new SimpleIntegerProperty(this, "value");
+        }
+        return value;
+    }
+
+    public final int getMaxValue() {
+        return maxValueProperty().get();
+    }
+
+    public final void setMaxValue(int maxVal) {
+        this.maxValueProperty().set(maxVal);
+    }
+
+    public final IntegerProperty maxValueProperty() {
+        if (null == maxValue) {
+            maxValue = new SimpleIntegerProperty(this, "maxValue", Integer.MAX_VALUE);
+        }
+        return maxValue;
+    }
+
+    public IntegerField() {
+        this(0);
+    }
+
+    public IntegerField(int value) {
+        this(value, NumberFormat.getInstance());
+    }
+
+    public IntegerField(int value, NumberFormat nf) {
+        super();
+        getStyleClass().add("integer-field");
+        this.nf = nf;
+        initHandlers();
+        setValue(value);
+    }
+
+    private void initHandlers() {
+
+        // try to parse when focus is lost or RETURN is hit
+        setOnAction(e -> parseAndFormatInput());
+
+        focusedProperty().addListener(new ChangeListener<Boolean>() {
+
+            @Override
+            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
+                if (!newValue.booleanValue()) {
+                    parseAndFormatInput();
+                }
+            }
+        });
+
+        // Set text in field if BigDecimal property is changed from outside.
+        valueProperty().addListener(new ChangeListener<Number>() {
+            @Override
+            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
+                setText(nf.format(newValue));
+            }
+        });
+    }
+
+    /**
+     * Tries to parse the user input to a number according to the provided
+     * NumberFormat
+     */
+    private void parseAndFormatInput() {
+        try {
+            String input = getText();
+            if (input == null || input.length() == 0) {
+                return;
+            }
+            Number parsedNumber = nf.parse(input);
+            BigDecimal newValue = new BigDecimal(parsedNumber.toString());
+            setValue(newValue.intValue());
+            selectAll();
+        } catch (ParseException ex) {
+            setText(nf.format(getValue()));
+        }
+    }
+}

+ 1 - 4
applications/device/device-client-biometric/src/main/java/com/usoftchina/smartschool/device/client/biometric/controller/CameraController.java

@@ -6,13 +6,11 @@ import com.github.sarxos.webcam.WebcamResolution;
 import com.usoftchina.smartschool.device.client.biometric.control.ParameterizedScene;
 import javafx.application.Platform;
 import javafx.beans.property.ObjectProperty;
-import javafx.embed.swing.SwingFXUtils;
 import javafx.embed.swing.SwingNode;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.scene.Node;
 import javafx.scene.Scene;
-import javafx.scene.image.Image;
 import org.springframework.util.CollectionUtils;
 
 import javax.imageio.ImageIO;
@@ -90,12 +88,11 @@ public class CameraController extends ViewController {
                     Scene scene = getScene();
                     if (scene instanceof ParameterizedScene) {
                         ObjectProperty<File> fileProperty = (ObjectProperty<File>) ((ParameterizedScene) scene).getParams();
-//                        imageObjectProperty.setValue(SwingFXUtils.toFXImage(webcam.getImage(), null));
                         File tempImageFile = Files.createTempFile("snapshot", "png").toFile();
                         ImageIO.write(webcam.getImage(), "PNG", tempImageFile);
                         fileProperty.setValue(tempImageFile);
                     }
-                    cameraNode.getScene().getWindow().hide();
+                    scene.getWindow().hide();
                 } catch (Exception ex) {
                     ex.printStackTrace();
                 }

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

@@ -3,8 +3,10 @@ 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.po.HealthStatus;
 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.NotifyUtils;
 import com.usoftchina.smartschool.device.client.biometric.util.ViewUtils;
 import com.usoftchina.smartschool.device.context.SpringContextHolder;
 import javafx.application.Platform;
@@ -15,6 +17,7 @@ import javafx.collections.ObservableList;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.fxml.Initializable;
+import javafx.geometry.Pos;
 import javafx.scene.Scene;
 import javafx.scene.control.Button;
 import javafx.scene.control.TableCell;
@@ -55,6 +58,10 @@ public class GateController implements Initializable {
     public Button editButton;
     @FXML
     public Button delButton;
+    @FXML
+    public Button settingButton;
+    @FXML
+    public TableColumn codeCol;
     private FaceGateService faceGateService;
     private ObservableList<FaceGate> faceGateObservableList = FXCollections.observableArrayList();
 
@@ -68,8 +75,9 @@ public class GateController implements Initializable {
         nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
         placeCol.setCellValueFactory(new PropertyValueFactory<>("place"));
         ipCol.setCellValueFactory(new PropertyValueFactory<>("ip"));
+        codeCol.setCellValueFactory(new PropertyValueFactory<>("code"));
         accessTypeCol.setCellValueFactory(new PropertyValueFactory<>("accessType"));
-        accessTypeCol.setCellFactory((col) -> {
+        accessTypeCol.setCellFactory(col -> {
                     TableCell<FaceGate, AccessType> cell = new TableCell<FaceGate, AccessType>() {
 
                         @Override
@@ -88,6 +96,22 @@ public class GateController implements Initializable {
         startTimeCol.setCellValueFactory(new PropertyValueFactory<>("startTime"));
         lastActiveCol.setCellValueFactory(new PropertyValueFactory<>("lastActiveTime"));
         statusCol.setCellValueFactory(new PropertyValueFactory<>("status"));
+        statusCol.setCellFactory((col) -> {
+                    TableCell<FaceGate, HealthStatus> cell = new TableCell<FaceGate, HealthStatus>() {
+
+                        @Override
+                        public void updateItem(HealthStatus item, boolean empty) {
+                            super.updateItem(item, empty);
+                            if (null != item) {
+                                this.setText(item.getText());
+                            } else {
+                                this.setText(null);
+                            }
+                        }
+                    };
+                    return cell;
+                }
+        );
 
         tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
         tableView.setItems(faceGateObservableList);
@@ -99,9 +123,11 @@ public class GateController implements Initializable {
                 if (null == newValue) {
                     editButton.setDisable(true);
                     delButton.setDisable(true);
+                    settingButton.setDisable(true);
                 } else {
                     editButton.setDisable(false);
                     delButton.setDisable(false);
+                    settingButton.setDisable(false);
                 }
             }
         });
@@ -165,7 +191,47 @@ public class GateController implements Initializable {
             AlertUtils.warn("请先选择一行数据");
             return;
         }
-        faceGateService.delete(faceGate.getId());
-        loadData();
+        Platform.runLater(() -> {
+            faceGateService.delete(faceGate.getId());
+            NotifyUtils.info("删除成功");
+            loadData();
+        });
+    }
+
+    @FXML
+    public void handleIssue(ActionEvent event) {
+    }
+
+    @FXML
+    public void handleIssueStatus(ActionEvent event) {
+    }
+
+    @FXML
+    public void handleSetting(ActionEvent event) {
+        FaceGate faceGate = tableView.getSelectionModel().getSelectedItem();
+        if (null == faceGate) {
+            AlertUtils.warn("请先选择一行数据");
+            return;
+        }
+        try {
+            Scene scene = ViewUtils.load("/view/gate_setting.fxml", faceGate);
+            Stage stage = new Stage();
+            stage.setScene(scene);
+            stage.setResizable(false);
+            stage.setTitle("人脸闸机设置");
+            stage.getIcons().addAll(DeviceClient.defaultIcons);
+            stage.centerOnScreen();
+            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();
+                    }
+                }
+            });
+        } catch (Exception e) {
+            logger.error("Set Device Error", e);
+        }
     }
 }

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

@@ -5,7 +5,9 @@ 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.NotifyUtils;
 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;
@@ -17,6 +19,7 @@ import javafx.scene.Scene;
 import javafx.scene.control.ComboBox;
 import javafx.scene.control.TextField;
 import javafx.stage.Stage;
+import org.controlsfx.control.Notifications;
 import org.springframework.util.StringUtils;
 
 import java.net.URL;
@@ -39,10 +42,7 @@ public class GateEditController implements Initializable {
     private FaceGateService faceGateService;
 
     private ObservableList<AccessType> accessOptions =
-            FXCollections.observableArrayList(
-                    new AccessType(1, "进入"),
-                    new AccessType(2, "出去")
-            );
+            FXCollections.observableArrayList(AccessType.values());
 
     private FaceGate faceGate;
 
@@ -94,9 +94,14 @@ public class GateEditController implements Initializable {
             AlertUtils.warn("请填写设备IP").ifPresent(b -> ipField.requestFocus());
             return;
         }
-        faceGateService.save(faceGate);
-
-        Stage stage = (Stage) accessTypeBox.getScene().getWindow();
-        stage.close();
+        Platform.runLater(() -> {
+            try {
+                faceGateService.save(faceGate);
+                accessTypeBox.getScene().getWindow().hide();
+                NotifyUtils.info("保存成功");
+            } catch (Exception e) {
+                AlertUtils.error(e.getMessage());
+            }
+        });
     }
 }

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

@@ -0,0 +1,92 @@
+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.FaceGate;
+import com.usoftchina.smartschool.device.client.biometric.service.FaceGateService;
+import com.usoftchina.smartschool.device.client.biometric.service.GatePersonService;
+import com.usoftchina.smartschool.device.client.biometric.util.AlertUtils;
+import com.usoftchina.smartschool.device.client.biometric.util.NotifyUtils;
+import com.usoftchina.smartschool.device.context.SpringContextHolder;
+import javafx.application.Platform;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.DatePicker;
+import javafx.scene.layout.HBox;
+import org.springframework.util.CollectionUtils;
+
+import java.net.URL;
+import java.sql.Date;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.stream.Collectors;
+
+/**
+ * @author yingp
+ * @date 2019/11/18
+ */
+public class GateIssueController implements Initializable {
+    @FXML
+    public HBox gateList;
+    @FXML
+    public DatePicker endTimePicker;
+    private FaceGateService faceGateService;
+    private GatePersonService gatePersonService;
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        faceGateService = SpringContextHolder.getContext().getBean(FaceGateService.class);
+        gatePersonService = SpringContextHolder.getContext().getBean(GatePersonService.class);
+        // 默认100年
+        endTimePicker.setValue(LocalDate.now().plusYears(100));
+        Platform.runLater(() -> {
+            List<FaceGate> faceGates = faceGateService.findAll();
+            if (!CollectionUtils.isEmpty(faceGates)) {
+                faceGates.forEach(gate -> {
+                    CheckBox checkBox = new CheckBox(gate.getName());
+                    checkBox.setSelected(true);
+                    checkBox.setPrefWidth(120);
+                    checkBox.setUserData(gate.getId());
+                    gateList.getChildren().add(checkBox);
+                });
+            }
+        });
+    }
+
+    @FXML
+    public void handleIssue(ActionEvent event) {
+        List<String> gateIds = gateList.getChildren().stream()
+                .filter(node -> ((CheckBox) node).isSelected())
+                .map(node -> (String) ((CheckBox) node).getUserData())
+                .collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(gateIds)) {
+            AlertUtils.warn("请先选择设备");
+            return;
+        }
+        LocalDate endTime = endTimePicker.getValue();
+        if (null == endTime) {
+            AlertUtils.warn("请指定有效截止时间");
+            return;
+        }
+        Platform.runLater(() -> {
+            ParameterizedScene scene = (ParameterizedScene) endTimePicker.getScene();
+            Object params = scene.getParams();
+            try {
+                if (params instanceof List) {
+                    gatePersonService.issue(((List<String>) params).toArray(new String[]{}), gateIds.toArray(new String[]{}),
+                            Date.from(endTime.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));
+                } else {
+                    gatePersonService.issue((String) params, gateIds.toArray(new String[]{}),
+                            Date.from(endTime.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));
+                }
+                scene.getWindow().hide();
+                NotifyUtils.info("下发成功");
+            } catch (Exception e) {
+                AlertUtils.warn(e.getMessage());
+            }
+        });
+    }
+}

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

@@ -0,0 +1,126 @@
+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.PersonGateView;
+import com.usoftchina.smartschool.device.client.biometric.repository.GatePersonRepository;
+import com.usoftchina.smartschool.device.client.biometric.service.GatePersonService;
+import com.usoftchina.smartschool.device.client.biometric.util.AlertUtils;
+import com.usoftchina.smartschool.device.client.biometric.util.NotifyUtils;
+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.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.cell.CheckBoxTableCell;
+import javafx.scene.control.cell.PropertyValueFactory;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public class GateIssueStatusController implements Initializable {
+    @FXML
+    public TableColumn gateNameCol;
+    @FXML
+    public TableColumn gatePlaceCol;
+    @FXML
+    public TableColumn gateIpCol;
+    @FXML
+    public TableColumn issueTimeCol;
+    @FXML
+    public TableColumn endTimeCol;
+    @FXML
+    public TableColumn selectedCol;
+    @FXML
+    public TableView<PersonGateView> tableView;
+    @FXML
+    public Button cancelIssueButton;
+
+    private GatePersonService gatePersonService;
+    private GatePersonRepository gatePersonRepository;
+
+    private ObservableList<PersonGateView> observableList = FXCollections.observableArrayList();
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        gatePersonService = SpringContextHolder.getContext().getBean(GatePersonService.class);
+        gatePersonRepository = SpringContextHolder.getContext().getBean(GatePersonRepository.class);
+        initTable();
+
+        cancelIssueButton.sceneProperty().addListener(new ChangeListener<Scene>() {
+            @Override
+            public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) {
+                if (null != newValue) {
+                    loadData();
+                    ((ParameterizedScene) newValue).paramsProperty().addListener(new ChangeListener() {
+                        @Override
+                        public void changed(ObservableValue observable, Object oldValue, Object newValue) {
+                            loadData();
+                        }
+                    });
+                }
+            }
+        });
+    }
+
+    private void loadData() {
+        Platform.runLater(() -> {
+            Object params = ((ParameterizedScene) tableView.getScene()).getParams();
+            if (null != params) {
+                observableList.setAll(gatePersonRepository.findByPerson((String) params));
+            }
+        });
+    }
+
+    private void initTable() {
+        selectedCol.setCellFactory(CheckBoxTableCell.forTableColumn(selectedCol));
+        selectedCol.setCellValueFactory(new PropertyValueFactory<>("selected"));
+        gateNameCol.setCellValueFactory(new PropertyValueFactory<>("gateName"));
+        gatePlaceCol.setCellValueFactory(new PropertyValueFactory<>("gatePlace"));
+        gateIpCol.setCellValueFactory(new PropertyValueFactory<>("gateIp"));
+        issueTimeCol.setCellValueFactory(new PropertyValueFactory<>("issueTime"));
+        endTimeCol.setCellValueFactory(new PropertyValueFactory<>("endTime"));
+
+        tableView.setItems(observableList);
+
+        tableView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<PersonGateView>() {
+            @Override
+            public void changed(ObservableValue<? extends PersonGateView> observable, PersonGateView oldValue, PersonGateView newValue) {
+                if (null == newValue) {
+                    cancelIssueButton.setDisable(true);
+                } else {
+                    cancelIssueButton.setDisable(false);
+                }
+            }
+        });
+    }
+
+    @FXML
+    public void handleCancelIssue(ActionEvent event) {
+        PersonGateView personGateView = tableView.getSelectionModel().getSelectedItem();
+        if (null == personGateView) {
+            AlertUtils.warn("请先选择一行数据");
+            return;
+        }
+        Platform.runLater(() -> {
+            try {
+                gatePersonService.cancelIssue(personGateView.getId());
+                NotifyUtils.info("取消成功");
+                loadData();
+            } catch (Exception e) {
+                AlertUtils.warn(e.getMessage());
+            }
+        });
+    }
+}

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

@@ -0,0 +1,159 @@
+package com.usoftchina.smartschool.device.client.biometric.controller;
+
+import com.usoftchina.smartschool.device.biometric.BiometricDeviceService;
+import com.usoftchina.smartschool.device.biometric.DeviceSetting;
+import com.usoftchina.smartschool.device.biometric.FaceLevel;
+import com.usoftchina.smartschool.device.biometric.LivingLevel;
+import com.usoftchina.smartschool.device.client.biometric.control.IntegerField;
+import com.usoftchina.smartschool.device.client.biometric.control.ParameterizedScene;
+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.NotifyUtils;
+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.*;
+import javafx.util.converter.NumberStringConverter;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public class GateSettingController implements Initializable {
+    @FXML
+    public Label softwareVersionLabel;
+    @FXML
+    public Label systemVersionLabel;
+    @FXML
+    public Label deviceCodeLabel;
+    @FXML
+    public TextField ipAddressField;
+    @FXML
+    public TextField maskField;
+    @FXML
+    public TextField gatewayField;
+    @FXML
+    public TextField dnsField;
+    @FXML
+    public DatePicker authorizeDateField;
+    @FXML
+    public ComboBox<FaceLevel> faceLevelBox;
+    @FXML
+    public ComboBox<LivingLevel> livingLevelBox;
+    @FXML
+    public IntegerField faceSizeField;
+    @FXML
+    public TextArea dataServiceUrlField;
+    @FXML
+    public TextArea deviceServiceUrlField;
+    @FXML
+    public IntegerField informIntervalField;
+    @FXML
+    public Label capacityLabel;
+    @FXML
+    public Button saveButton;
+
+    private BiometricDeviceService deviceService;
+    private FaceGateService faceGateService;
+
+    private ObservableList<FaceLevel> faceLevelOptions =
+            FXCollections.observableArrayList(FaceLevel.values());
+
+    private ObservableList<LivingLevel> livingLevelOptions =
+            FXCollections.observableArrayList(LivingLevel.values());
+
+    private FaceGate faceGate;
+    private DeviceSetting deviceSetting;
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        deviceService = SpringContextHolder.getContext().getBean(BiometricDeviceService.class);
+        faceGateService = SpringContextHolder.getContext().getBean(FaceGateService.class);
+        softwareVersionLabel.sceneProperty().addListener(new ChangeListener<Scene>() {
+            @Override
+            public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) {
+                if (null != newValue) {
+                    deviceSetting = new DeviceSetting();
+                    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;
+                                    Platform.runLater(() -> {
+                                        deviceSetting = deviceService.getSetting(faceGate);
+                                        if (null != deviceSetting) {
+                                            initFields();
+                                        }
+                                    });
+                                }
+                            }
+                        });
+                    }
+                    initFields();
+                }
+            }
+        });
+    }
+
+    private void initFields() {
+        softwareVersionLabel.textProperty().bind(deviceSetting.softwareVersionProperty());
+        systemVersionLabel.textProperty().bind(deviceSetting.systemVersionProperty());
+        deviceCodeLabel.textProperty().bind(deviceSetting.deviceCodeProperty());
+        ipAddressField.textProperty().bindBidirectional(deviceSetting.ipAddressProperty());
+        maskField.textProperty().bindBidirectional(deviceSetting.maskProperty());
+        gatewayField.textProperty().bindBidirectional(deviceSetting.gatewayProperty());
+        dnsField.textProperty().bindBidirectional(deviceSetting.dnsProperty());
+        authorizeDateField.valueProperty().bindBidirectional(deviceSetting.authorizeDateProperty());
+        faceLevelBox.valueProperty().bindBidirectional(deviceSetting.faceLevelProperty());
+        faceLevelBox.setItems(faceLevelOptions);
+        if (null == deviceSetting.getFaceLevel()) {
+            faceLevelBox.getSelectionModel().selectFirst();
+        }
+        livingLevelBox.valueProperty().bindBidirectional(deviceSetting.livingLevelProperty());
+        livingLevelBox.setItems(livingLevelOptions);
+        if (null == deviceSetting.getFaceLevel()) {
+            livingLevelBox.getSelectionModel().selectFirst();
+        }
+        faceSizeField.valueProperty().bindBidirectional(deviceSetting.faceSizeProperty());
+        dataServiceUrlField.textProperty().bindBidirectional(deviceSetting.dataServiceUrlProperty());
+        deviceServiceUrlField.textProperty().bindBidirectional(deviceSetting.deviceServiceUrlProperty());
+        informIntervalField.valueProperty().bindBidirectional(deviceSetting.informIntervalProperty());
+        capacityLabel.textProperty().bindBidirectional(deviceSetting.capacityProperty(), new NumberStringConverter());
+    }
+
+    @FXML
+    public void handleSave(ActionEvent event) {
+        if (null != deviceSetting && deviceSetting.isDirty()) {
+            Platform.runLater(() -> {
+                try {
+                    deviceService.saveSetting(faceGate, deviceSetting);
+                    boolean ipChanged = !faceGate.getIp().equals(deviceSetting.getIpAddress())
+                            && !"-".equals(deviceSetting.getIpAddress());
+                    if (ipChanged) {
+                        faceGate.setIp(deviceSetting.getIpAddress());
+                        faceGateService.save(faceGate);
+                    }
+                    NotifyUtils.info("设置成功");
+                    softwareVersionLabel.getScene().getWindow().hide();
+                } catch (Exception e) {
+                    AlertUtils.error(e.getMessage());
+                }
+            });
+        } else {
+            AlertUtils.warn("没有修改");
+        }
+    }
+}

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

@@ -51,6 +51,10 @@ public class PersonController implements Initializable {
     public Button editButton;
     @FXML
     public Button delButton;
+    @FXML
+    public Button issueButton;
+    @FXML
+    public Button issueStatusButton;
 
     private PersonService personService;
 
@@ -95,9 +99,13 @@ public class PersonController implements Initializable {
                 if (null == newValue) {
                     editButton.setDisable(true);
                     delButton.setDisable(true);
+                    issueButton.setDisable(true);
+                    issueStatusButton.setDisable(true);
                 } else {
                     editButton.setDisable(false);
                     delButton.setDisable(false);
+                    issueButton.setDisable(false);
+                    issueStatusButton.setDisable(false);
                 }
             }
         });
@@ -168,4 +176,46 @@ public class PersonController implements Initializable {
     @FXML
     public void handleImport(ActionEvent event) {
     }
+
+    @FXML
+    public void handleIssue(ActionEvent event) {
+        Person person = tableView.getSelectionModel().getSelectedItem();
+        if (null == person) {
+            AlertUtils.warn("请先选择一行数据");
+            return;
+        }
+        try {
+            Scene scene = ViewUtils.load("/view/gate_issue.fxml", person.getId());
+            Stage stage = new Stage();
+            stage.setScene(scene);
+            stage.setResizable(false);
+            stage.setTitle("人员下发");
+            stage.getIcons().addAll(DeviceClient.defaultIcons);
+            stage.centerOnScreen();
+            stage.show();
+        } catch (Exception e) {
+            logger.error("Issue Person Error", e);
+        }
+    }
+
+    @FXML
+    public void handleIssueStatus(ActionEvent event) {
+        Person person = tableView.getSelectionModel().getSelectedItem();
+        if (null == person) {
+            AlertUtils.warn("请先选择一行数据");
+            return;
+        }
+        try {
+            Scene scene = ViewUtils.load("/view/gate_issue_status.fxml", person.getId());
+            Stage stage = new Stage();
+            stage.setScene(scene);
+            stage.setResizable(false);
+            stage.setTitle("人员下发状态");
+            stage.getIcons().addAll(DeviceClient.defaultIcons);
+            stage.centerOnScreen();
+            stage.show();
+        } catch (Exception e) {
+            logger.error("Issue Status Error", e);
+        }
+    }
 }

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

@@ -7,8 +7,10 @@ import com.usoftchina.smartschool.device.client.biometric.po.SexType;
 import com.usoftchina.smartschool.device.client.biometric.service.FileService;
 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.NotifyUtils;
 import com.usoftchina.smartschool.device.client.biometric.util.ViewUtils;
 import com.usoftchina.smartschool.device.context.SpringContextHolder;
+import javafx.application.Platform;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
@@ -113,7 +115,6 @@ public class PersonEditController implements Initializable {
             sexBox.getSelectionModel().selectFirst();
         }
         lastSnapshot = null;
-        lastSelectedImage = null;
         unsavedImage = null;
         if (null != person.getFaceImage()) {
             File imageFile = fileService.getById(person.getFaceImage());
@@ -136,6 +137,13 @@ public class PersonEditController implements Initializable {
         }
         if (null != unsavedImage) {
             try {
+                // 删除旧的文件
+                if (null != person.getFaceImage()) {
+                    File oldImageFile = fileService.getById(person.getFaceImage());
+                    if (oldImageFile.exists()) {
+                        oldImageFile.delete();
+                    }
+                }
                 person.setFaceImage(fileService.save(unsavedImage));
                 // 清除临时文件
                 if (null != lastSnapshot) {
@@ -144,10 +152,18 @@ public class PersonEditController implements Initializable {
             } catch (IOException e) {
                 logger.error("Save image error", e);
             }
-        } else if (null == imageView.getImage()){
+        } else if (null == imageView.getImage()) {
             person.setFaceImage(null);
         }
-        personService.save(person);
+
+        Platform.runLater(() -> {
+            try {
+                personService.save(person);
+                NotifyUtils.info("保存成功");
+            } catch (Exception e) {
+                AlertUtils.error(e.getMessage());
+            }
+        });
     }
 
     @FXML
@@ -170,7 +186,7 @@ public class PersonEditController implements Initializable {
     public void handleChoosePicture(ActionEvent event) {
         if (null == fileChooser) {
             fileChooser = new FileChooser();
-            fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("图片文件", "*.png", "*.jpg", "*.bmp", "*.gif"));
+            fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("图片文件", "*.png", "*.jpg", "*.bmp"));
         }
         // 打开上次文件夹
         if (null != lastSelectedImage) {

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

@@ -4,11 +4,13 @@ package com.usoftchina.smartschool.device.client.biometric.po;
  * @author yingp
  * @date 2019/11/14
  */
-public class AccessType {
-    private Integer value;
-    private String text;
+public enum AccessType {
+    IN(1, "进入"),
+    OUT(2, "出去");
+    private final Integer value;
+    private final String text;
 
-    public AccessType(Integer value, String text) {
+    AccessType(Integer value, String text) {
         this.value = value;
         this.text = text;
     }
@@ -17,20 +19,21 @@ public class AccessType {
         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;
     }
+
+    public static AccessType of(Integer value) {
+        for (AccessType type : values()) {
+            if (type.getValue().equals(value)) {
+                return type;
+            }
+        }
+        return IN;
+    }
 }

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

@@ -19,6 +19,10 @@ public class FaceGate implements DeviceInfo {
      */
     private StringProperty place;
     private StringProperty ip;
+    /**
+     * 设备编号
+     */
+    private StringProperty code;
     private IntegerProperty port;
     private StringProperty username;
     private StringProperty password;
@@ -37,7 +41,7 @@ public class FaceGate implements DeviceInfo {
     /**
      * 健康状况
      */
-    private StringProperty status;
+    private ObjectProperty<HealthStatus> status;
 
     @Override
     public String getId() {
@@ -48,6 +52,21 @@ public class FaceGate implements DeviceInfo {
         this.id = id;
     }
 
+    public String getCode() {
+        return codeProperty().get();
+    }
+
+    public StringProperty codeProperty() {
+        if (null == code) {
+            code = new SimpleStringProperty();
+        }
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.codeProperty().set(code);
+    }
+
     public String getName() {
         return nameProperty().get();
     }
@@ -149,7 +168,7 @@ public class FaceGate implements DeviceInfo {
 
     public void setAccessType(Integer accessType) {
         if (null != accessType) {
-            accessTypeProperty().setValue(new AccessType(accessType, 1 == accessType ? "进入" : "出去"));
+            accessTypeProperty().setValue(AccessType.of(accessType));
         }
     }
 
@@ -195,27 +214,24 @@ public class FaceGate implements DeviceInfo {
     }
 
     public String getStatus() {
-        return statusProperty().get();
+        HealthStatus status = statusProperty().get();
+        return null == status ? null : status.getValue();
     }
 
-    public StringProperty statusProperty() {
+    public ObjectProperty<HealthStatus> statusProperty() {
         if (null == status) {
-            status = new SimpleStringProperty();
+            status = new SimpleObjectProperty<>();
         }
         return status;
     }
 
-    public void setStatus(String status) {
+    public void setStatus(HealthStatus status) {
         this.statusProperty().set(status);
     }
 
-    /**
-     * 是否在线
-     *
-     * @return
-     */
-    public boolean isActive() {
-        Date time = getLastActiveTime();
-        return null != time && System.currentTimeMillis() - time.getTime() < 60000;
+    public void setStatus(String status) {
+        if (null != status) {
+            this.statusProperty().set(HealthStatus.of(status));
+        }
     }
 }

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

@@ -0,0 +1,75 @@
+package com.usoftchina.smartschool.device.client.biometric.po;
+
+import java.util.Date;
+
+/**
+ * 人员下发到闸机设备,绑定关系
+ *
+ * @author yingp
+ * @date 2019/11/18
+ */
+public class GatePerson {
+    private String id;
+    private String personId;
+    private String gateId;
+    /**
+     * 下发后,返回的ID
+     */
+    private Integer devicePersonId;
+    /**
+     * 有效截止时间
+     */
+    private Date endTime;
+    /**
+     * 下发时间
+     */
+    private Date issueTime;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getPersonId() {
+        return personId;
+    }
+
+    public void setPersonId(String personId) {
+        this.personId = personId;
+    }
+
+    public String getGateId() {
+        return gateId;
+    }
+
+    public void setGateId(String gateId) {
+        this.gateId = gateId;
+    }
+
+    public Integer getDevicePersonId() {
+        return devicePersonId;
+    }
+
+    public void setDevicePersonId(Integer devicePersonId) {
+        this.devicePersonId = devicePersonId;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    public Date getIssueTime() {
+        return issueTime;
+    }
+
+    public void setIssueTime(Date issueTime) {
+        this.issueTime = issueTime;
+    }
+}

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

@@ -0,0 +1,154 @@
+package com.usoftchina.smartschool.device.client.biometric.po;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+
+import java.util.Date;
+
+/**
+ * @author yingp
+ * @date 2019/11/18
+ */
+public class GatePersonView extends Selectable{
+    private String id;
+    private String personId;
+    private String gateId;
+    private Integer devicePersonId;
+    private StringProperty personClass;
+    private StringProperty personName;
+    private ObjectProperty<SexType> personSex;
+    private StringProperty personCardNo;
+    private ObjectProperty<Date> issueTime;
+    private ObjectProperty<Date> endTime;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getPersonId() {
+        return personId;
+    }
+
+    public void setPersonId(String personId) {
+        this.personId = personId;
+    }
+
+    public String getGateId() {
+        return gateId;
+    }
+
+    public void setGateId(String gateId) {
+        this.gateId = gateId;
+    }
+
+    public Integer getDevicePersonId() {
+        return devicePersonId;
+    }
+
+    public void setDevicePersonId(Integer devicePersonId) {
+        this.devicePersonId = devicePersonId;
+    }
+
+    public String getPersonClass() {
+        return personClassProperty().get();
+    }
+
+    public StringProperty personClassProperty() {
+        if (null == personClass) {
+            personClass = new SimpleStringProperty();
+        }
+        return personClass;
+    }
+
+    public void setPersonClass(String clazz) {
+        this.personClassProperty().set(clazz);
+    }
+
+    public String getPersonName() {
+        return personNameProperty().get();
+    }
+
+    public StringProperty personNameProperty() {
+        if (null == personName) {
+            personName = new SimpleStringProperty();
+        }
+        return personName;
+    }
+
+    public void setPersonName(String name) {
+        this.personNameProperty().set(name);
+    }
+
+    public Integer getPersonSex() {
+        SexType type = personSexProperty().get();
+        return null == type ? null : type.getValue();
+    }
+
+    public void setPersonSex(Integer sex) {
+        if (null != sex) {
+            personSexProperty().set(new SexType(sex, 1 == sex ? "男" : "女"));
+        }
+    }
+
+    public ObjectProperty<SexType> personSexProperty() {
+        if (null == personSex) {
+            personSex = new SimpleObjectProperty<>();
+        }
+        return personSex;
+    }
+
+    public void setPersonSex(SexType sex) {
+        this.personSexProperty().set(sex);
+    }
+
+    public String getPersonCardNo() {
+        return personCardNoProperty().get();
+    }
+
+    public StringProperty personCardNoProperty() {
+        if (null == personCardNo) {
+            personCardNo = new SimpleStringProperty();
+        }
+        return personCardNo;
+    }
+
+    public void setPersonCardNo(String cardNo) {
+        this.personCardNoProperty().set(cardNo);
+    }
+
+    public Date getIssueTime() {
+        return issueTimeProperty().get();
+    }
+
+    public ObjectProperty<Date> issueTimeProperty() {
+        if (null == issueTime) {
+            issueTime = new SimpleObjectProperty<>();
+        }
+        return issueTime;
+    }
+
+    public void setIssueTime(Date issueTime) {
+        this.issueTimeProperty().set(issueTime);
+    }
+
+    public Date getEndTime() {
+        return endTimeProperty().get();
+    }
+
+    public ObjectProperty<Date> endTimeProperty() {
+        if (null == endTime) {
+            endTime = new SimpleObjectProperty<>();
+        }
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTimeProperty().set(endTime);
+    }
+}

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

@@ -0,0 +1,39 @@
+package com.usoftchina.smartschool.device.client.biometric.po;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public enum HealthStatus {
+    HEALTHY("0", "健康"),
+    UN_HEALTHY("1", "异常");
+    private final String value;
+    private final String text;
+
+    HealthStatus(String value, String text) {
+        this.value = value;
+        this.text = text;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public static HealthStatus of(String value) {
+        for (HealthStatus status : values()) {
+            if (status.getValue().equals(value)) {
+                return status;
+            }
+        }
+        return UN_HEALTHY;
+    }
+
+    @Override
+    public String toString() {
+        return text;
+    }
+}

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

@@ -0,0 +1,134 @@
+package com.usoftchina.smartschool.device.client.biometric.po;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+
+import java.util.Date;
+
+/**
+ * @author yingp
+ * @date 2019/11/18
+ */
+public class PersonGateView extends Selectable{
+    private String id;
+    private String personId;
+    private String gateId;
+    private Integer devicePersonId;
+    private StringProperty gateName;
+    /**
+     * 位置
+     */
+    private StringProperty gatePlace;
+    private StringProperty gateIp;
+    private ObjectProperty<Date> issueTime;
+    private ObjectProperty<Date> endTime;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getPersonId() {
+        return personId;
+    }
+
+    public void setPersonId(String personId) {
+        this.personId = personId;
+    }
+
+    public String getGateId() {
+        return gateId;
+    }
+
+    public void setGateId(String gateId) {
+        this.gateId = gateId;
+    }
+
+    public Integer getDevicePersonId() {
+        return devicePersonId;
+    }
+
+    public void setDevicePersonId(Integer devicePersonId) {
+        this.devicePersonId = devicePersonId;
+    }
+
+    public String getGateName() {
+        return gateNameProperty().get();
+    }
+
+    public StringProperty gateNameProperty() {
+        if (null == gateName) {
+            gateName = new SimpleStringProperty();
+        }
+        return gateName;
+    }
+
+    public void setGateName(String gateName) {
+        this.gateNameProperty().set(gateName);
+    }
+
+    public String getGatePlace() {
+        return gatePlaceProperty().get();
+    }
+
+    public StringProperty gatePlaceProperty() {
+        if (null == gatePlace) {
+            gatePlace = new SimpleStringProperty();
+        }
+        return gatePlace;
+    }
+
+    public void setGatePlace(String gatePlace) {
+        this.gatePlaceProperty().set(gatePlace);
+    }
+
+    public String getGateIp() {
+        return gateIpProperty().get();
+    }
+
+    public StringProperty gateIpProperty() {
+        if (null == gateIp) {
+            gateIp = new SimpleStringProperty();
+        }
+        return gateIp;
+    }
+
+    public void setGateIp(String gateIp) {
+        this.gateIpProperty().set(gateIp);
+    }
+
+    public Date getIssueTime() {
+        return issueTimeProperty().get();
+    }
+
+    public ObjectProperty<Date> issueTimeProperty() {
+        if (null == issueTime) {
+            issueTime = new SimpleObjectProperty<>();
+        }
+        return issueTime;
+    }
+
+    public void setIssueTime(Date issueTime) {
+        this.issueTimeProperty().set(issueTime);
+    }
+
+    public Date getEndTime() {
+        return endTimeProperty().get();
+    }
+
+    public ObjectProperty<Date> endTimeProperty() {
+        if (null == endTime) {
+            endTime = new SimpleObjectProperty<>();
+        }
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTimeProperty().set(endTime);
+    }
+}

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

@@ -0,0 +1,27 @@
+package com.usoftchina.smartschool.device.client.biometric.po;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public abstract class Selectable {
+    private BooleanProperty selected;
+
+    public boolean isSelected() {
+        return selected.get();
+    }
+
+    public BooleanProperty selectedProperty() {
+        if (null == selected) {
+            selected = new SimpleBooleanProperty();
+        }
+        return selected;
+    }
+
+    public void setSelected(boolean selected) {
+        this.selected.set(selected);
+    }
+}

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

@@ -51,17 +51,17 @@ public class FaceGateRepository {
 
     @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());
+        int ret = jdbcTemplate.update("insert into face_gate(id,name,ip,port,code,place,accessType,startTime,status,lastActiveTime) values " +
+                        "(?,?,?,?,?,?,?,?,?,?)", faceGate.getId(), faceGate.getName(), faceGate.getIp(), faceGate.getPort(),
+                faceGate.getCode(), faceGate.getPlace(), faceGate.getAccessType(), faceGate.getStartTime(), faceGate.getStatus(), 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());
+        int ret = jdbcTemplate.update("update face_gate set name=?,ip=?,port=?,code=?,place=?,accessType=?,lastActiveTime=?,status=? where id=?",
+                faceGate.getName(), faceGate.getIp(), faceGate.getPort(), faceGate.getCode(),
+                faceGate.getPlace(), faceGate.getAccessType(), faceGate.getLastActiveTime(), faceGate.getStatus(), faceGate.getId());
         return ret > 0;
     }
 

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

@@ -0,0 +1,91 @@
+package com.usoftchina.smartschool.device.client.biometric.repository;
+
+import com.usoftchina.smartschool.device.client.biometric.po.*;
+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;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/11/14
+ */
+@Repository
+public class GatePersonRepository {
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    public GatePerson findById(String id) {
+        try {
+            return jdbcTemplate.queryForObject("select * from gate_person where id=?",
+                    new BeanPropertyRowMapper<>(GatePerson.class), id);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+
+    public GatePerson findByGateAndPerson(String gateId, String personId) {
+        try {
+            return jdbcTemplate.queryForObject("select * from gate_person where gateId=? and personId=?",
+                    new BeanPropertyRowMapper<>(GatePerson.class), gateId, personId);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    public List<GatePersonView> findByGate(String gateId) {
+        try {
+            return jdbcTemplate.query("select gate_person.id,gate_person.personId,gate_person.gateId,gate_person.devicePersonId," +
+                            "gate_person.issueTime,person.name personName,person.cardNo personCardNo,person.sex personSex," +
+                            "person.clazz personClass,gate_person.endTime from face_gate,person,gate_person " +
+                            "where face_gate.id=gate_person.gateId and person.id=gate_person.personId and face_gate.id=? " +
+                            "order by person.createTime",
+                    new BeanPropertyRowMapper<>(GatePersonView.class), gateId);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    public List<PersonGateView> findByPerson(String personId) {
+        try {
+            return jdbcTemplate.query("select gate_person.id,gate_person.personId,gate_person.gateId,gate_person.devicePersonId," +
+                            "gate_person.issueTime,face_gate.name gateName,face_gate.place gatePlace,face_gate.ip gateIp,gate_person.endTime " +
+                            "from face_gate,person,gate_person " +
+                            "where face_gate.id=gate_person.gateId and person.id=gate_person.personId and person.id=? " +
+                            "order by face_gate.startTime",
+                    new BeanPropertyRowMapper<>(PersonGateView.class), personId);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    public boolean save(GatePerson gatePerson) {
+        int ret = jdbcTemplate.update("insert into gate_person(id,personId,gateId,devicePersonId,issueTime,endTime) values " +
+                        "(?,?,?,?,?,?)", gatePerson.getId(), gatePerson.getPersonId(), gatePerson.getGateId(), gatePerson.getDevicePersonId(),
+                gatePerson.getIssueTime(), gatePerson.getEndTime());
+        return ret > 0;
+    }
+
+    public boolean update(GatePerson gatePerson) {
+        int ret = jdbcTemplate.update("update gate_person set issueTime=?,endTime=? where id=?",
+                gatePerson.getIssueTime(), gatePerson.getEndTime(), gatePerson.getId());
+        return ret > 0;
+    }
+
+    public boolean delete(String id) {
+        return jdbcTemplate.update("delete from gate_person where id=?", id) > 0;
+    }
+
+    public boolean deleteByPerson(String personId) {
+        return jdbcTemplate.update("delete from gate_person where personId=?", personId) > 0;
+    }
+
+    public boolean deleteByGate(String gateId) {
+        return jdbcTemplate.update("delete from gate_person where gateId=?", gateId) > 0;
+    }
+}

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

@@ -1,14 +1,20 @@
 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.biometric.BiometricDeviceService;
+import com.usoftchina.smartschool.device.biometric.DeviceSetting;
 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.po.HealthStatus;
 import com.usoftchina.smartschool.device.client.biometric.repository.FaceGateRepository;
+import com.usoftchina.smartschool.device.client.biometric.repository.GatePersonRepository;
 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
@@ -16,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
 import org.springframework.web.client.RestTemplate;
 
+import java.net.InetAddress;
 import java.util.Date;
 import java.util.List;
 
@@ -25,9 +32,13 @@ import java.util.List;
  */
 @Service
 public class FaceGateService {
+    private final Logger logger = LoggerFactory.getLogger(FaceGateService.class);
     @Autowired
     private FaceGateRepository faceGateRepository;
 
+    @Autowired
+    private GatePersonRepository gatePersonRepository;
+
     @Autowired
     private RestTemplate restTemplate;
 
@@ -35,10 +46,13 @@ public class FaceGateService {
     private DeviceServerProperties deviceServerProperties;
 
     @Autowired
-    private DeviceApi deviceApi;
+    private SchoolService schoolService;
 
     @Autowired
-    private SchoolService schoolService;
+    private BiometricDeviceService deviceService;
+
+    @Value("${server.port:8080}")
+    private Integer serverPort;
 
     public List<FaceGate> findAll() {
         return faceGateRepository.findAll();
@@ -68,32 +82,75 @@ public class FaceGateService {
 
         if (isNew) {
             faceGate.setStartTime(new Date());
-            // 调用设备接口监听
-            deviceApi.add(faceGate);
+            bindDevice(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);
+                try {
+                    unbindDevice(oldOne);
+                } catch (Exception e) {
+                    logger.warn("unbind device error", e);
+                }
+                bindDevice(faceGate);
             }
             faceGateRepository.update(faceGate);
         }
     }
 
+    /**
+     * 连接到设备
+     *
+     * @param faceGate
+     */
+    private void bindDevice(FaceGate faceGate) {
+        DeviceSetting deviceSetting = deviceService.getSetting(faceGate);
+        try {
+            // 设置数据回调接口
+            String serverIp = InetAddress.getLocalHost().getHostAddress();
+            deviceSetting.setDataServiceUrl(String.format("http://%s:%s/verificationInterface/passlog/passFullLog", serverIp, serverPort));
+            deviceSetting.setDeviceServiceUrl(String.format("http://%s:%s/DeviceManageService/ACSServer", serverIp, serverPort));
+        } catch (Exception e) {
+            logger.error("Inet error", e);
+        }
+        if (deviceSetting.isDirty()) {
+            deviceService.saveSetting(faceGate, deviceSetting);
+        }
+        faceGate.setCode(deviceSetting.getDeviceCode());
+        faceGate.setStatus(HealthStatus.HEALTHY.getValue());
+        faceGate.setLastActiveTime(new Date());
+    }
+
+    /**
+     * 取消连接到设备
+     *
+     * @param faceGate
+     */
+    private void unbindDevice(FaceGate faceGate) {
+        DeviceSetting deviceSetting = deviceService.getSetting(faceGate);
+        try {
+            // 取消数据回调
+            deviceSetting.setDataServiceUrl(null);
+            deviceSetting.setDeviceServiceUrl(null);
+        } catch (Exception e) {
+            logger.error("Inet error", e);
+        }
+        if (deviceSetting.isDirty()) {
+            deviceService.saveSetting(faceGate, deviceSetting);
+        }
+    }
+
     @Transactional(rollbackFor = Exception.class)
     public void delete(String id) {
         FaceGate oldOne = faceGateRepository.findById(id);
         if (null != oldOne) {
             try {
-                // 先调用设备接口停止监听
-                deviceApi.remove(oldOne);
+                unbindDevice(oldOne);
             } catch (Exception e) {
-                e.printStackTrace();
+                logger.warn("unbind device error", e);
             }
             faceGateRepository.delete(id);
+            gatePersonRepository.deleteByGate(id);
         }
     }
 

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

@@ -0,0 +1,169 @@
+package com.usoftchina.smartschool.device.client.biometric.service;
+
+import com.usoftchina.smartschool.device.biometric.BiometricDeviceService;
+import com.usoftchina.smartschool.device.biometric.PersonPo;
+import com.usoftchina.smartschool.device.client.biometric.po.FaceGate;
+import com.usoftchina.smartschool.device.client.biometric.po.GatePerson;
+import com.usoftchina.smartschool.device.client.biometric.po.Person;
+import com.usoftchina.smartschool.device.client.biometric.po.PersonGateView;
+import com.usoftchina.smartschool.device.client.biometric.repository.FaceGateRepository;
+import com.usoftchina.smartschool.device.client.biometric.repository.GatePersonRepository;
+import com.usoftchina.smartschool.device.client.biometric.repository.PersonRepository;
+import com.usoftchina.smartschool.device.client.biometric.util.RandomUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Base64;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/11/18
+ */
+@Service
+public class GatePersonService {
+
+    @Autowired
+    private GatePersonRepository gatePersonRepository;
+
+    @Autowired
+    private FaceGateRepository faceGateRepository;
+
+    @Autowired
+    private PersonRepository personRepository;
+
+    @Autowired
+    private BiometricDeviceService deviceService;
+
+    @Autowired
+    private FileService fileService;
+
+    /**
+     * 人员信息下发到指定设备
+     *
+     * @param personId
+     * @param gateId
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void issue(String personId, String gateId, Date endTime) {
+        boolean isNew = false;
+        GatePerson gatePerson = gatePersonRepository.findByGateAndPerson(gateId, personId);
+        if (null == gatePerson) {
+            gatePerson = new GatePerson();
+            gatePerson.setId(RandomUtils.randomString());
+            gatePerson.setDevicePersonId(Math.abs((gateId + personId).hashCode()));
+            isNew = true;
+        }
+        gatePerson.setGateId(gateId);
+        gatePerson.setPersonId(personId);
+        gatePerson.setIssueTime(new Date());
+        gatePerson.setEndTime(endTime);
+
+        // 先下发信息到设备上
+        FaceGate faceGate = faceGateRepository.findById(gateId);
+        Person person = personRepository.findById(personId);
+        PersonPo personPo = new PersonPo();
+        personPo.setAccessCardNum(person.getCardNo());
+        personPo.setDataType(0);
+        personPo.setEndTime(endTime);
+        personPo.setName(person.getName());
+        personPo.setGenderCode(String.valueOf(person.getSex()));
+        personPo.setId(gatePerson.getDevicePersonId());
+        try {
+            File imageFile = fileService.getById(person.getFaceImage());
+            String imageBase64 = Base64.getEncoder().encodeToString(Files.readAllBytes(imageFile.toPath()));
+            personPo.setPicture(imageBase64);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        deviceService.issue(faceGate, personPo);
+        if (isNew) {
+            gatePersonRepository.save(gatePerson);
+        } else {
+            gatePersonRepository.update(gatePerson);
+        }
+    }
+
+    /**
+     * 人员信息批量下发
+     *
+     * @param personId
+     * @param gateIds
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void issue(String personId, String[] gateIds, Date endTime) {
+        for (String gateId : gateIds) {
+            issue(personId, gateId, endTime);
+        }
+    }
+
+    /**
+     * 人员信息批量下发
+     *
+     * @param personIds
+     * @param gateIds
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void issue(String[] personIds, String[] gateIds, Date endTime) {
+        for (String personId : personIds) {
+            issue(personId, gateIds, endTime);
+        }
+    }
+
+    /**
+     * 人员信息下发到全部设备
+     *
+     * @param personId
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void issue(String personId) {
+        List<FaceGate> gates = faceGateRepository.findAll();
+        if (!CollectionUtils.isEmpty(gates)) {
+            // 默认100年
+            Date endTime = Date.from(LocalDate.now().plusYears(100).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
+            gates.forEach(gate -> issue(personId, gate.getId(), endTime));
+        }
+    }
+
+    /**
+     * 人员信息下发到指定设备
+     *
+     * @param id
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void cancelIssue(String id) {
+        // 先删除设备上的记录
+        GatePerson gatePerson = gatePersonRepository.findById(id);
+        FaceGate faceGate = faceGateRepository.findById(gatePerson.getGateId());
+        deviceService.cancelIssue(faceGate, gatePerson.getDevicePersonId());
+
+        gatePersonRepository.delete(id);
+    }
+
+    /**
+     * 人员信息下发到指定设备
+     *
+     * @param personId
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void cancelIssueByPerson(String personId) {
+        List<PersonGateView> gatePersonList = gatePersonRepository.findByPerson(personId);
+        if (!CollectionUtils.isEmpty(gatePersonList)) {
+            // 先删除设备上的记录
+            for (PersonGateView personGate : gatePersonList) {
+                FaceGate faceGate = faceGateRepository.findById(personGate.getGateId());
+                deviceService.cancelIssue(faceGate, personGate.getDevicePersonId());
+            }
+
+            gatePersonRepository.deleteByPerson(personId);
+        }
+    }
+}

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

@@ -22,6 +22,9 @@ public class PersonService {
     @Autowired
     private PersonRepository personRepository;
 
+    @Autowired
+    private GatePersonService gatePersonService;
+
     public List<Person> findAll() {
         return personRepository.findAll();
     }
@@ -58,6 +61,7 @@ public class PersonService {
 
     @Transactional(rollbackFor = Exception.class)
     public void delete(String id) {
+        gatePersonService.cancelIssueByPerson(id);
         personRepository.delete(id);
     }
 }

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

@@ -0,0 +1,46 @@
+package com.usoftchina.smartschool.device.client.biometric.task;
+
+import com.usoftchina.smartschool.device.biometric.BiometricDeviceService;
+import com.usoftchina.smartschool.device.client.biometric.po.FaceGate;
+import com.usoftchina.smartschool.device.client.biometric.po.HealthStatus;
+import com.usoftchina.smartschool.device.client.biometric.service.FaceGateService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+@Component
+public class GateScheduler {
+    @Autowired
+    private FaceGateService faceGateService;
+    @Autowired
+    private BiometricDeviceService deviceService;
+
+    @Scheduled(fixedDelay = 120000)
+    public void pingDevice() {
+        List<FaceGate> faceGates = faceGateService.findAll();
+        if (!CollectionUtils.isEmpty(faceGates)) {
+            try {
+                faceGates.parallelStream().forEach(faceGate -> {
+                    boolean success = deviceService.ping(faceGate);
+                    if (success) {
+                        faceGate.setLastActiveTime(new Date());
+                        faceGate.setStatus(HealthStatus.HEALTHY.getValue());
+                    } else {
+                        faceGate.setStatus(HealthStatus.UN_HEALTHY.getValue());
+                    }
+                    faceGateService.save(faceGate);
+                });
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}

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

@@ -0,0 +1,31 @@
+package com.usoftchina.smartschool.device.client.biometric.util;
+
+import javafx.geometry.Pos;
+import javafx.util.Duration;
+import org.controlsfx.control.Notifications;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public class NotifyUtils {
+
+    public static void info(String message) {
+        notify("提示", message).hideAfter(Duration.seconds(3)).showInformation();
+    }
+
+    public static void warn(String message) {
+        notify("警告", message).hideAfter(Duration.seconds(5)).showWarning();
+    }
+
+    public static void error(String message) {
+        notify("错误", message).hideAfter(Duration.seconds(5)).showError();
+    }
+
+    public static Notifications notify(String title, String message) {
+        return Notifications.create()
+                .title(title)
+                .text(message)
+                .position(Pos.TOP_CENTER);
+    }
+}

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

@@ -1,6 +1,6 @@
 package com.usoftchina.smartschool.device.client.biometric.util;
 
-import java.util.Random;
+import java.util.UUID;
 
 /**
  * @author yingp
@@ -9,7 +9,7 @@ import java.util.Random;
 public class RandomUtils {
 
     public static String randomString() {
-        return String.valueOf(new Random().nextLong());
+        return UUID.randomUUID().toString();
     }
 
 }

+ 13 - 1
applications/device/device-client-biometric/src/main/resources/schema.sql

@@ -1,13 +1,15 @@
 create table if not exists face_gate
 (
 id varchar(50) primary key not null,
+code varchar(50),
 name varchar(300) not null,
 ip varchar(20) not null,
 port int not null,
 place varchar(30),
 accessType int,
 startTime datetime,
-lastActiveTime datetime
+lastActiveTime datetime,
+status varchar(10)
 );
 
 create table if not exists school
@@ -35,4 +37,14 @@ cardNo varchar(20) not null,
 sex int,
 createTime datetime,
 faceImage varchar(100)
+);
+
+create table if not exists gate_person
+(
+id varchar(50) primary key not null,
+gateId varchar(50) not null,
+personId varchar(50) not null,
+devicePersonId int,
+issueTime datetime,
+endTime datetime
 );

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

@@ -169,6 +169,10 @@ AnchorPane, BorderPane, VBox {
     -fx-font-size: 14px;
 }
 
+.field-control .field-label {
+    -fx-font-weight: bold;
+}
+
 .image-view {
     -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 10, 0, 0, 0);
     -fx-border-color: #bfcfda;

+ 14 - 6
applications/device/device-client-biometric/src/main/resources/view/gate.fxml

@@ -8,6 +8,7 @@
 <?import javafx.scene.control.Button?>
 <?import javafx.scene.control.Separator?>
 <BorderPane xmlns="http://javafx.com/javafx/8.0.172-ea"
+            stylesheets="/style/main.css"
             fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.GateController"
             xmlns:fx="http://javafx.com/fxml/1">
     <top>
@@ -19,6 +20,12 @@
                 <Separator/>
                 <Button fx:id="delButton" text="删除" onAction="#handleDelete" disable="true"/>
                 <Separator/>
+                <Button fx:id="settingButton" text="参数设置" onAction="#handleSetting" disable="true"/>
+                <Separator/>
+                <Button fx:id="issueButton" text="批量绑定" onAction="#handleIssue" disable="true"/>
+                <Separator/>
+                <Button fx:id="issueStatusButton" text="绑定状态" onAction="#handleIssueStatus" disable="true"/>
+                <Separator/>
                 <Button text="刷新" onAction="#handleRefresh"/>
             </items>
         </ToolBar>
@@ -26,13 +33,14 @@
     <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="nameCol" prefWidth="100.0" minWidth="100" 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="状态"/>
+                <TableColumn fx:id="ipCol" prefWidth="100.0" minWidth="100" text="设备IP" style="-fx-alignment: CENTER"/>
+                <TableColumn fx:id="codeCol" prefWidth="100.0" minWidth="100" text="设备编号" style="-fx-alignment: CENTER"/>
+                <TableColumn fx:id="accessTypeCol" prefWidth="100.0" text="出入类型" style="-fx-alignment: CENTER"/>
+                <TableColumn fx:id="startTimeCol" prefWidth="160.0" minWidth="160" text="注册时间" style="-fx-alignment: CENTER"/>
+                <TableColumn fx:id="lastActiveCol" prefWidth="160.0" minWidth="160" text="上次心跳" style="-fx-alignment: CENTER"/>
+                <TableColumn fx:id="statusCol" prefWidth="100.0" text="状态" style="-fx-alignment: CENTER"/>
             </columns>
         </TableView>
     </center>

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

@@ -14,25 +14,25 @@
     <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"/>
+                <Label text="设备名称" styleClass="field-label" 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"/>
+                <Label text="设备IP" styleClass="field-label" 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"/>
+                <Label text="安装位置" styleClass="field-label" 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"/>
+                <Label text="出入类型" styleClass="field-label" prefWidth="100" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
                 <ComboBox fx:id="accessTypeBox" prefWidth="200" prefHeight="40"/>
             </children>
         </HBox>

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

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.layout.HBox?>
+
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.control.DatePicker?>
+<?import javafx.scene.control.Button?>
+<VBox prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.172-ea"
+      xmlns:fx="http://javafx.com/fxml/1"
+      stylesheets="/style/main.css"
+      spacing="20"
+      style="-fx-padding: 20px"
+      fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.GateIssueController">
+    <children>
+        <Label text="选择人脸闸机:" style="-fx-font-weight: bold;-fx-font-size: 14px"/>
+        <HBox spacing="20" fx:id="gateList"/>
+        <Label text="有效截止时间:" style="-fx-font-weight: bold;-fx-font-size: 14px"/>
+        <DatePicker fx:id="endTimePicker" prefWidth="200" prefHeight="40"/>
+        <Button text="下发" onAction="#handleIssue" prefWidth="200" prefHeight="40"/>
+    </children>
+</VBox>

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

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import java.lang.*?>
+<?import java.util.*?>
+<?import javafx.scene.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<BorderPane xmlns="http://javafx.com/javafx"
+            xmlns:fx="http://javafx.com/fxml"
+            stylesheets="/style/main.css"
+            fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.GateIssueStatusController"
+            prefHeight="600.0" prefWidth="800.0">
+    <top>
+        <ToolBar BorderPane.alignment="CENTER">
+            <items>
+                <Button fx:id="cancelIssueButton" text="取消下发" onAction="#handleCancelIssue" disable="true"/>
+            </items>
+        </ToolBar>
+    </top>
+    <center>
+        <TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
+            <columns>
+                <TableColumn fx:id="selectedCol" prefWidth="50.0" text="勾选"/>
+                <TableColumn fx:id="gateNameCol" prefWidth="120.0" text="设备名称"/>
+                <TableColumn fx:id="gatePlaceCol" prefWidth="120.0" text="设备位置"/>
+                <TableColumn fx:id="gateIpCol" prefWidth="120.0" text="设备IP"/>
+                <TableColumn fx:id="issueTimeCol" prefWidth="200.0" text="下发时间"/>
+                <TableColumn fx:id="endTimeCol" prefWidth="200.0" text="截止时间"/>
+            </columns>
+        </TableView>
+    </center>
+</BorderPane>

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

@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<?import com.usoftchina.smartschool.device.client.biometric.control.IntegerField?>
+<AnchorPane xmlns="http://javafx.com/javafx"
+            xmlns:fx="http://javafx.com/fxml"
+            stylesheets="/style/main.css"
+            fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.GateSettingController"
+            prefHeight="600.0" prefWidth="900.0">
+    <children>
+        <HBox styleClass="field-control" layoutX="10" layoutY="30">
+            <children>
+                <Label text="软件版本:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <Label fx:id="softwareVersionLabel" prefWidth="200" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="80">
+            <children>
+                <Label text="固件版本:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <Label fx:id="systemVersionLabel" prefWidth="300" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="130">
+            <children>
+                <Label text="设备编号:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <Label fx:id="deviceCodeLabel" prefWidth="300" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="180">
+            <children>
+                <Label text="IP 地址:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <TextField fx:id="ipAddressField" prefWidth="300" prefHeight="40" disable="true"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="230">
+            <children>
+                <Label text="子网掩码:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <TextField fx:id="maskField" prefWidth="300" prefHeight="40" disable="true"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="280">
+            <children>
+                <Label text="网关:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <TextField fx:id="gatewayField" prefWidth="300" prefHeight="40" disable="true"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="330">
+            <children>
+                <Label text="DNS:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <TextField fx:id="dnsField" prefWidth="300" prefHeight="40" disable="true"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="380">
+            <children>
+                <Label text="授权截止日期:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <DatePicker fx:id="authorizeDateField" prefWidth="300" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="10" layoutY="430">
+            <children>
+                <Label text="已入库人像容量:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <Label fx:id="capacityLabel" prefWidth="300" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="440" layoutY="30">
+            <children>
+                <Label text="人脸阈值级别:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <ComboBox fx:id="faceLevelBox" prefWidth="300" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="440" layoutY="80">
+            <children>
+                <Label text="活体阈值级别:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <ComboBox fx:id="livingLevelBox" prefWidth="300" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="440" layoutY="130">
+            <children>
+                <Label text="人脸框像素大小:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <IntegerField fx:id="faceSizeField" prefWidth="300" prefHeight="40"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="440" layoutY="180">
+            <children>
+                <Label text="数据上传地址:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <TextArea fx:id="dataServiceUrlField" wrapText="true" prefWidth="300" prefHeight="90" disable="true"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="440" layoutY="280">
+            <children>
+                <Label text="心跳地址:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <TextArea fx:id="deviceServiceUrlField" wrapText="true" prefWidth="300" prefHeight="90" disable="true"/>
+            </children>
+        </HBox>
+        <HBox styleClass="field-control" layoutX="440" layoutY="380">
+            <children>
+                <Label text="心跳间隔:" styleClass="field-label" prefWidth="120" prefHeight="40" alignment="CENTER_RIGHT" style="-fx-padding: 0 5px 0 0"/>
+                <IntegerField fx:id="informIntervalField" prefWidth="300" prefHeight="40"/>
+            </children>
+        </HBox>
+        <AnchorPane styleClass="field-control" layoutX="10" layoutY="480">
+            <children>
+                <Button fx:id="saveButton" layoutX="120" layoutY="0" text="保存" prefWidth="300" prefHeight="40" onAction="#handleSave"/>
+            </children>
+        </AnchorPane>
+    </children>
+</AnchorPane>

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

@@ -8,6 +8,7 @@
 <?import javafx.scene.control.Button?>
 <?import javafx.scene.control.Separator?>
 <BorderPane xmlns="http://javafx.com/javafx/8.0.172-ea"
+            stylesheets="/style/main.css"
             fx:controller="com.usoftchina.smartschool.device.client.biometric.controller.PersonController"
             xmlns:fx="http://javafx.com/fxml/1">
     <top>
@@ -21,6 +22,10 @@
                 <Separator/>
                 <Button text="导入" onAction="#handleImport"/>
                 <Separator/>
+                <Button fx:id="issueButton" text="批量下发" onAction="#handleIssue" disable="true"/>
+                <Separator/>
+                <Button fx:id="issueStatusButton" text="下发状态" onAction="#handleIssueStatus" disable="true"/>
+                <Separator/>
                 <Button text="刷新" onAction="#handleRefresh"/>
             </items>
         </ToolBar>

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

@@ -12,25 +12,25 @@
     <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="班级" />
+                <Label alignment="CENTER_RIGHT" styleClass="field-label" 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="姓名" />
+                <Label alignment="CENTER_RIGHT" styleClass="field-label" 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="卡号" />
+                <Label alignment="CENTER_RIGHT" styleClass="field-label" 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="性别" />
+                <Label alignment="CENTER_RIGHT" styleClass="field-label" prefHeight="40" prefWidth="100" style="-fx-padding: 0 5px 0 0" text="性别" />
                 <ComboBox fx:id="sexBox" prefHeight="40" prefWidth="200" />
             </children>
         </HBox>

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

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import java.lang.*?>
+<?import java.util.*?>
+<?import javafx.scene.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<AnchorPane xmlns="http://javafx.com/javafx"
+            xmlns:fx="http://javafx.com/fxml"
+            fx:controller="view.PersonIssue"
+            prefHeight="400.0" prefWidth="600.0">
+
+</AnchorPane>

+ 161 - 0
applications/device/device-core/src/main/java/com/usoftchina/smartschool/device/util/DateUtils.java

@@ -0,0 +1,161 @@
+package com.usoftchina.smartschool.device.util;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.temporal.TemporalAdjusters;
+import java.time.temporal.TemporalField;
+import java.time.temporal.WeekFields;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+/**
+ * @author yingp
+ * @date 2019/9/11
+ */
+public class DateUtils {
+
+    private final static Map<String, DateFormat> formats = new ConcurrentHashMap<>();
+    private final static Pattern P_YMD = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
+    private final static Pattern P_YMD_HMS = Pattern.compile("\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}");
+    private final static Pattern P_YMD_HMS_TZ = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z");
+
+    private static DateFormat getFormat(String pattern, TimeZone timeZone) {
+        DateFormat format = formats.get(pattern + "_" + timeZone.getID());
+        if (null == format) {
+            format = new SimpleDateFormat(pattern);
+            format.setTimeZone(timeZone);
+            formats.put(pattern + "_" + timeZone.getID(), format);
+        }
+        return format;
+    }
+
+    public static String format(Date date, String pattern) {
+        return getFormat(pattern, TimeZone.getDefault()).format(date);
+    }
+
+    public static String format(Date date) {
+        return format(date, "yyyy-MM-dd");
+    }
+
+    public static String formatDateTime(Date date) {
+        return format(date, "yyyy-MM-dd HH:mm:ss");
+    }
+
+    public static Date parse(String date) {
+        try {
+            if (P_YMD.matcher(date).matches()) {
+                return getFormat("yyyy-MM-dd", TimeZone.getDefault()).parse(date);
+            } else if (P_YMD_HMS.matcher(date).matches()) {
+                return getFormat("yyyy-MM-dd HH:mm:ss", TimeZone.getDefault()).parse(date);
+            } else if (P_YMD_HMS_TZ.matcher(date).matches()) {
+                return getFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("UTC")).parse(date);
+            }
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static LocalDate toLocal(Date date) {
+        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+    }
+
+    /**
+     * 今天日期开始(00:00:00)
+     *
+     * @return
+     */
+    public static Date nowDateStart() {
+        return Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 今天日期截止(23:59:59)
+     *
+     * @return
+     */
+    public static Date nowDateEnd() {
+        return Date.from(LocalDateTime.of(LocalDate.now(), LocalTime.MAX)
+                .atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 今天(含时间)
+     *
+     * @return
+     */
+    public static Date nowDateTime() {
+        return new Date();
+    }
+
+    /**
+     * 日期起始(00:00:00)
+     *
+     * @return
+     */
+    public static Date startOfDate(Date date) {
+        return Date.from(date.toInstant().atZone(ZoneId.systemDefault())
+                .toLocalDate().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 日期截止(23:59:59)
+     *
+     * @return
+     */
+    public static Date endOfDate(Date date) {
+        return Date.from(
+                LocalDateTime.of(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), LocalTime.MAX)
+                        .atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 周一
+     *
+     * @return
+     */
+    public static Date nowWeekStart() {
+        LocalDate now = LocalDate.now();
+        TemporalField fieldISO = WeekFields.of(Locale.FRANCE).dayOfWeek();
+        return Date.from(now.with(fieldISO, 1).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 周天
+     *
+     * @return
+     */
+    public static Date nowWeekEnd() {
+        LocalDate now = LocalDate.now();
+        TemporalField fieldISO = WeekFields.of(Locale.FRANCE).dayOfWeek();
+        return Date.from(
+                LocalDateTime.of(now.with(fieldISO, 7), LocalTime.MAX)
+                        .atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 月初
+     *
+     * @return
+     */
+    public static Date nowMonthStart() {
+        LocalDate date = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
+        return Date.from(date.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 月末
+     *
+     * @return
+     */
+    public static Date nowMonthEnd() {
+        LocalDate date = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
+        return Date.from(LocalDateTime.of(date, LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant());
+    }
+}

+ 87 - 0
applications/device/device-core/src/main/java/com/usoftchina/smartschool/device/util/DirtyBean.java

@@ -0,0 +1,87 @@
+package com.usoftchina.smartschool.device.util;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public abstract class DirtyBean {
+
+    private boolean initialized = false;
+    private Map<String, DirtyProperty> modified;
+    private BooleanProperty dirty;
+
+    protected void checkDirty(String property, Object oldValue, Object newValue) {
+        // 标记为init完成后,才开始记录dirty状态
+        if (!initialized) {
+            return;
+        }
+        if (null == modified) {
+            modified = new HashMap<>(1);
+        }
+        if (!modified.containsKey(property)) {
+            modified.put(property, new DirtyProperty(oldValue, newValue));
+        } else {
+            DirtyProperty dirtyProperty = modified.get(property);
+            if (Objects.equals(dirtyProperty.getOriginalValue(), newValue)) {
+                modified.remove(property);
+            } else {
+                dirtyProperty.setValue(newValue);
+            }
+        }
+        dirtyProperty().setValue(!modified.isEmpty());
+    }
+
+    public boolean isDirty() {
+        return dirtyProperty().get();
+    }
+
+    public BooleanProperty dirtyProperty() {
+        if (null == dirty) {
+            dirty = new SimpleBooleanProperty(false);
+        }
+        return dirty;
+    }
+
+    public Map<String, DirtyProperty> getModified() {
+        return modified;
+    }
+
+    public void ready() {
+        initialized = true;
+        modified = null;
+        dirtyProperty().setValue(false);
+    }
+
+    public class DirtyProperty {
+        private Object originalValue;
+        private Object value;
+
+        public DirtyProperty(Object originalValue, Object value) {
+            this.originalValue = originalValue;
+            this.value = value;
+        }
+
+        public Object getOriginalValue() {
+            return originalValue;
+        }
+
+        public void setOriginalValue(Object originalValue) {
+            this.originalValue = originalValue;
+        }
+
+        public Object getValue() {
+            return value;
+        }
+
+        public void setValue(Object value) {
+            this.value = value;
+        }
+    }
+}

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

@@ -33,5 +33,14 @@
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 </project>

+ 3 - 1
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/AccessPo.java → applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/AccessLog.java

@@ -3,10 +3,12 @@ package com.usoftchina.smartschool.device.biometric;
 import java.util.Date;
 
 /**
+ * 过人记录
+ *
  * @author yingp
  * @date 2019/11/14
  */
-public class AccessPo {
+public class AccessLog {
     /**
      * 设备代码
      */

+ 219 - 19
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/BiometricDeviceService.java

@@ -1,15 +1,25 @@
 package com.usoftchina.smartschool.device.biometric;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
 import com.usoftchina.smartschool.device.api.DeviceApi;
 import com.usoftchina.smartschool.device.dto.DeviceInfo;
+import com.usoftchina.smartschool.device.exception.ExceptionCode;
+import com.usoftchina.smartschool.device.util.DateUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Value;
+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.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
 
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @author yingp
@@ -20,27 +30,217 @@ public class BiometricDeviceService implements DeviceApi {
 
     private final Logger logger = LoggerFactory.getLogger(BiometricDeviceService.class);
 
-    @Value("${server.port:8080}")
-    private Integer serverPort;
+    @Autowired
+    private RestTemplate restTemplate;
 
     @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) {
     }
+
+    /**
+     * 清除设备数据
+     *
+     * @param deviceInfo
+     */
+    public void clearData(DeviceInfo deviceInfo) {
+//        String url = String.format("http://%s:%s/DeviceInterface/ClearDeviceData",
+//                deviceInfo.getIp(), deviceInfo.getPort());
+//        personPo.setDelFlag(0);
+//        ResponseEntity<String> response = restTemplate.postForEntity(
+//                url, personPo, String.class);
+//        if (response.getStatusCode() == HttpStatus.OK) {
+//            ResultPo result = JSON.parseObject(response.getBody(), ResultPo.class);
+//            if (!result.wasSuccess()) {
+//                ExceptionCode.ERROR_UNKNOWN.occur(result.getMessage());
+//            }
+//        } else {
+//            ExceptionCode.ERROR_UNKNOWN.occur("下发失败");
+//        }
+    }
+
+    public Object issue(DeviceInfo deviceInfo, PersonPo personPo) {
+        String url = String.format("http://%s:%s/DeviceInterface/UpdatePersonInfo",
+                deviceInfo.getIp(), deviceInfo.getPort());
+        personPo.setDelFlag(0);
+        ResponseEntity<String> response = restTemplate.postForEntity(
+                url, personPo, String.class);
+        if (response.getStatusCode() == HttpStatus.OK) {
+            ResultPo result = JSON.parseObject(response.getBody(), ResultPo.class);
+            if (!result.wasSuccess()) {
+                ExceptionCode.ERROR_UNKNOWN.occur(result.getMessage());
+            }
+        } else {
+            ExceptionCode.ERROR_UNKNOWN.occur("下发失败");
+        }
+        return null;
+    }
+
+    public void cancelIssue(DeviceInfo deviceInfo, Integer issueId) {
+        String url = String.format("http://%s:%s/DeviceInterface/UpdatePersonInfo",
+                deviceInfo.getIp(), deviceInfo.getPort());
+        PersonPo personPo = new PersonPo();
+        personPo.setId(issueId);
+        personPo.setDelFlag(1);
+        ResponseEntity<String> response = restTemplate.postForEntity(
+                url, personPo, String.class);
+        if (response.getStatusCode() == HttpStatus.OK) {
+            ResultPo result = JSON.parseObject(response.getBody(), ResultPo.class);
+            if (!result.wasSuccess()) {
+                ExceptionCode.ERROR_UNKNOWN.occur(result.getMessage());
+            }
+        } else {
+            ExceptionCode.ERROR_UNKNOWN.occur("取消下发失败");
+        }
+    }
+
+    /**
+     * 读取参数设置
+     *
+     * @param deviceInfo
+     * @return
+     */
+    public DeviceSetting getSetting(DeviceInfo deviceInfo) {
+        String url = String.format("http://%s:%s/DeviceInterface/GetParameterValues",
+                deviceInfo.getIp(), deviceInfo.getPort());
+        List<String> items = new ArrayList<>();
+        items.add("DeviceInfo.SoftwareVersion");
+        items.add("DeviceInfo.SystemVersion");
+        items.add("DeviceInfo.DeviceCode");
+        items.add("DeviceInfo.IpAddress");
+        items.add("DeviceInfo.mask");
+        items.add("DeviceInfo.Gateway");
+        items.add("DeviceInfo.DNS");
+        items.add("DeviceInfo.AuthorizeDate");
+        items.add("DeviceInfo.InformInterval");
+        items.add("CompareOption.FaceLevel");
+        items.add("CompareOption.LivingLevel");
+        items.add("CompareOption.FaceSize");
+        items.add("ServerInfo.DataServiceUrl");
+        items.add("ServerInfo.DeviceServiceUrl");
+        items.add("DeviceInfo.capacity");
+        ResponseEntity<String> response = restTemplate.postForEntity(
+                url, new ReqListPo<>(items), String.class);
+        if (response.getStatusCode() == HttpStatus.OK) {
+            ResultListPo<Setting> result = JSON.parseObject(response.getBody(),
+                    new TypeReference<ResultListPo<Setting>>() {
+                    });
+            if (!result.isSuccess()) {
+                ExceptionCode.ERROR_UNKNOWN.occur(result.getMessage());
+            } else {
+                DeviceSetting deviceSetting = new DeviceSetting();
+                if (null != result.getLstParameter()) {
+                    for (Setting setting : result.getLstParameter()) {
+                        switch (setting.getName()) {
+                            case "DeviceInfo.SoftwareVersion":
+                                deviceSetting.setSoftwareVersion((String) setting.getValue());
+                                break;
+                            case "DeviceInfo.SystemVersion":
+                                deviceSetting.setSystemVersion((String) setting.getValue());
+                                break;
+                            case "DeviceInfo.DeviceCode":
+                                deviceSetting.setDeviceCode((String) setting.getValue());
+                                break;
+                            case "DeviceInfo.IpAddress":
+                                deviceSetting.setIpAddress((String) setting.getValue());
+                                break;
+                            case "DeviceInfo.mask":
+                                deviceSetting.setMask((String) setting.getValue());
+                                break;
+                            case "DeviceInfo.Gateway":
+                                deviceSetting.setGateway((String) setting.getValue());
+                                break;
+                            case "DeviceInfo.DNS":
+                                deviceSetting.setDns((String) setting.getValue());
+                                break;
+                            case "DeviceInfo.AuthorizeDate":
+                                String date = (String) setting.getValue();
+                                if (!StringUtils.isEmpty(date)) {
+                                    deviceSetting.setAuthorizeDate(DateUtils.toLocal(DateUtils.parse(date)));
+                                }
+                                break;
+                            case "DeviceInfo.InformInterval":
+                                if (!ObjectUtils.isEmpty(setting.getValue())) {
+                                    deviceSetting.setInformInterval(Integer.parseInt(setting.getValue().toString()));
+                                }
+                                break;
+                            case "CompareOption.FaceLevel":
+                                deviceSetting.setFaceLevel((String) setting.getValue());
+                                break;
+                            case "CompareOption.LivingLevel":
+                                deviceSetting.setLivingLevel((String) setting.getValue());
+                                break;
+                            case "CompareOption.FaceSize":
+                                if (!ObjectUtils.isEmpty(setting.getValue())) {
+                                    deviceSetting.setFaceSize(Integer.parseInt(setting.getValue().toString()));
+                                }
+                                break;
+                            case "ServerInfo.DataServiceUrl":
+                                deviceSetting.setDataServiceUrl((String) setting.getValue());
+                                break;
+                            case "ServerInfo.DeviceServiceUrl":
+                                deviceSetting.setDeviceServiceUrl((String) setting.getValue());
+                                break;
+                            case "DeviceInfo.capacity":
+                                if (!ObjectUtils.isEmpty(setting.getValue())) {
+                                    deviceSetting.setCapacity(Integer.parseInt(setting.getValue().toString()));
+                                }
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                }
+                deviceSetting.ready();
+                return deviceSetting;
+            }
+        } else {
+            ExceptionCode.ERROR_UNKNOWN.occur("查询失败");
+        }
+        return null;
+    }
+
+    /**
+     * 保存参数设置
+     *
+     * @param deviceInfo
+     * @param deviceSetting
+     */
+    public void saveSetting(DeviceInfo deviceInfo, DeviceSetting deviceSetting) {
+        String url = String.format("http://%s:%s/DeviceInterface/SetParameterValues",
+                deviceInfo.getIp(), deviceInfo.getPort());
+        List<Setting> settings = new ArrayList<>();
+        deviceSetting.getModified().forEach((p, v) -> settings.add(new Setting(p, v.getValue())));
+        ResponseEntity<String> response = restTemplate.postForEntity(
+                url, new ReqListPo<>(settings), String.class);
+        if (response.getStatusCode() == HttpStatus.OK) {
+            ResultPo result = JSON.parseObject(response.getBody(), ResultPo.class);
+            if (!result.wasSuccess()) {
+                ExceptionCode.ERROR_UNKNOWN.occur(result.getMessage());
+            }
+        } else {
+            ExceptionCode.ERROR_UNKNOWN.occur("查询失败");
+        }
+    }
+
+    /**
+     * PING检测设备
+     *
+     * @param deviceInfo
+     * @return
+     */
+    public boolean ping(DeviceInfo deviceInfo) {
+        boolean isReachable = false;
+        try (Socket connect = new Socket()) {
+            InetSocketAddress endpointSocketAddr = new InetSocketAddress(deviceInfo.getIp(), deviceInfo.getPort());
+            connect.connect(endpointSocketAddr, 3000);
+            isReachable = connect.isConnected();
+        } catch (Exception e) {
+            logger.warn("connect error", e);
+        }
+        return isReachable;
+    }
 }

+ 17 - 2
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/DeviceManageController.java

@@ -1,9 +1,16 @@
 package com.usoftchina.smartschool.device.biometric;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.usoftchina.smartschool.device.context.SpringContextHolder;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.time.LocalDateTime;
+
 /**
  * @author yingp
  * @date 2019/11/14
@@ -17,10 +24,18 @@ public class DeviceManageController {
      *
      * @param request
      * @return
+     * @Deprecated 设备上传的数据有问题,不使用该接口
      */
     @PostMapping("/ACSServer")
-    public ResultPo deviceHeart(RequestPo<HealthInfo> request) {
-        System.out.println(request);
+    @Deprecated
+    public ResultPo deviceHeart(HttpServletRequest request) throws IOException {
+        System.out.println(LocalDateTime.now() + " ACSServer " + request.getReader().readLine());
+        String body = request.getReader().readLine();
+        if (null != body && body.startsWith("{")) {
+            RequestPo<HealthInfo> req = JSON.parseObject(body, new TypeReference<RequestPo<HealthInfo>>() {
+            });
+            SpringContextHolder.getContext().publishEvent(new HeartbeatEvent(req.getResponseBody().getDeviceCode(), this));
+        }
         return ResultPo.success().setHoldRequests("0");
     }
 }

+ 384 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/DeviceSetting.java

@@ -0,0 +1,384 @@
+package com.usoftchina.smartschool.device.biometric;
+
+import com.usoftchina.smartschool.device.util.DirtyBean;
+import javafx.beans.property.*;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public class DeviceSetting extends DirtyBean implements Serializable {
+    /**
+     * 软件版本
+     */
+    private StringProperty softwareVersion;
+    /**
+     * 固件版本
+     */
+    private StringProperty systemVersion;
+    /**
+     * 设备编号
+     */
+    private StringProperty deviceCode;
+    private StringProperty ipAddress;
+    /**
+     * 子网掩码
+     */
+    private StringProperty mask;
+    /**
+     * 网关
+     */
+    private StringProperty gateway;
+    /**
+     * DNS
+     */
+    private StringProperty dns;
+    /**
+     * 授权截止日期
+     */
+    private ObjectProperty<LocalDate> authorizeDate;
+    /**
+     * 心跳间隔
+     */
+    private IntegerProperty informInterval;
+    /**
+     * 人脸阈值级别
+     */
+    private ObjectProperty<FaceLevel> faceLevel;
+    /**
+     * 活体阈值级别
+     */
+    private ObjectProperty<LivingLevel> livingLevel;
+    /**
+     * 人脸框大小(像素)
+     */
+    private IntegerProperty faceSize;
+    /**
+     * 数据上传地址
+     */
+    private StringProperty dataServiceUrl;
+    /**
+     * 心跳地址
+     */
+    private StringProperty deviceServiceUrl;
+    /**
+     * 已入库人像容量
+     */
+    private IntegerProperty capacity;
+
+    public String getSoftwareVersion() {
+        return softwareVersionProperty().get();
+    }
+
+    public StringProperty softwareVersionProperty() {
+        if (null == softwareVersion) {
+            softwareVersion = new SimpleStringProperty();
+        }
+        return softwareVersion;
+    }
+
+    public void setSoftwareVersion(String softwareVersion) {
+        this.softwareVersionProperty().set(softwareVersion);
+    }
+
+    public String getSystemVersion() {
+        return systemVersionProperty().get();
+    }
+
+    public StringProperty systemVersionProperty() {
+        if (null == systemVersion) {
+            systemVersion = new SimpleStringProperty();
+        }
+        return systemVersion;
+    }
+
+    public void setSystemVersion(String systemVersion) {
+        this.systemVersionProperty().set(systemVersion);
+    }
+
+    public String getDeviceCode() {
+        return deviceCodeProperty().get();
+    }
+
+    public StringProperty deviceCodeProperty() {
+        if (null == deviceCode) {
+            deviceCode = new SimpleStringProperty();
+        }
+        return deviceCode;
+    }
+
+    public void setDeviceCode(String deviceCode) {
+        this.deviceCodeProperty().set(deviceCode);
+    }
+
+    public String getIpAddress() {
+        return ipAddressProperty().get();
+    }
+
+    public StringProperty ipAddressProperty() {
+        if (null == ipAddress) {
+            ipAddress = new SimpleStringProperty();
+            ipAddress.addListener(new ChangeListener<String>() {
+                @Override
+                public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
+                    checkDirty("DeviceInfo.IpAddress", oldValue, newValue);
+                }
+            });
+        }
+        return ipAddress;
+    }
+
+    public void setIpAddress(String ipAddress) {
+        this.ipAddressProperty().set(ipAddress);
+    }
+
+    public String getMask() {
+        return maskProperty().get();
+    }
+
+    public StringProperty maskProperty() {
+        if (null == mask) {
+            mask = new SimpleStringProperty();
+            mask.addListener(new ChangeListener<String>() {
+                @Override
+                public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
+                    checkDirty("DeviceInfo.mask", oldValue, newValue);
+                }
+            });
+        }
+        return mask;
+    }
+
+    public void setMask(String mask) {
+        this.maskProperty().set(mask);
+    }
+
+    public String getGateway() {
+        return gatewayProperty().get();
+    }
+
+    public StringProperty gatewayProperty() {
+        if (null == gateway) {
+            gateway = new SimpleStringProperty();
+            gateway.addListener(new ChangeListener<String>() {
+                @Override
+                public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
+                    checkDirty("DeviceInfo.Gateway", oldValue, newValue);
+                }
+            });
+        }
+        return gateway;
+    }
+
+    public void setGateway(String gateway) {
+        this.gatewayProperty().set(gateway);
+    }
+
+    public String getDns() {
+        return dnsProperty().get();
+    }
+
+    public StringProperty dnsProperty() {
+        if (null == dns) {
+            dns = new SimpleStringProperty();
+            dns.addListener(new ChangeListener<String>() {
+                @Override
+                public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
+                    checkDirty("DeviceInfo.DNS", oldValue, newValue);
+                }
+            });
+        }
+        return dns;
+    }
+
+    public void setDns(String dns) {
+        this.dnsProperty().set(dns);
+    }
+
+    public LocalDate getAuthorizeDate() {
+        return authorizeDateProperty().get();
+    }
+
+    public ObjectProperty<LocalDate> authorizeDateProperty() {
+        if (null == authorizeDate) {
+            authorizeDate = new SimpleObjectProperty<>();
+            authorizeDate.addListener(new ChangeListener<LocalDate>() {
+                @Override
+                public void changed(ObservableValue<? extends LocalDate> observable, LocalDate oldValue, LocalDate newValue) {
+                    checkDirty("DeviceInfo.AuthorizeDate",
+                            (null == oldValue ? null : oldValue.toString()),
+                            (null == newValue ? null : newValue.toString()));
+                }
+            });
+        }
+        return authorizeDate;
+    }
+
+    public void setAuthorizeDate(LocalDate authorizeDate) {
+        this.authorizeDateProperty().set(authorizeDate);
+    }
+
+    public int getInformInterval() {
+        return informIntervalProperty().get();
+    }
+
+    public IntegerProperty informIntervalProperty() {
+        if (null == informInterval) {
+            informInterval = new SimpleIntegerProperty();
+            informInterval.addListener(new ChangeListener<Number>() {
+                @Override
+                public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
+                    checkDirty("DeviceInfo.InformInterval", oldValue, newValue);
+                }
+            });
+        }
+        return informInterval;
+    }
+
+    public void setInformInterval(int informInterval) {
+        this.informIntervalProperty().set(informInterval);
+    }
+
+    public String getFaceLevel() {
+        FaceLevel faceLevel = faceLevelProperty().get();
+        return null == faceLevel ? null : faceLevel.getValue();
+    }
+
+    public ObjectProperty<FaceLevel> faceLevelProperty() {
+        if (null == faceLevel) {
+            faceLevel = new SimpleObjectProperty<>();
+            faceLevel.addListener(new ChangeListener<FaceLevel>() {
+                @Override
+                public void changed(ObservableValue<? extends FaceLevel> observable, FaceLevel oldValue, FaceLevel newValue) {
+                    checkDirty("CompareOption.FaceLevel",
+                            (null == oldValue ? null : oldValue.getValue()),
+                            (null == newValue ? null : newValue.getValue()));
+                }
+            });
+        }
+        return faceLevel;
+    }
+
+    public void setFaceLevel(FaceLevel faceLevel) {
+        this.faceLevelProperty().set(faceLevel);
+    }
+
+    public void setFaceLevel(String faceLevel) {
+        if (null != faceLevel) {
+            faceLevelProperty().setValue(FaceLevel.of(faceLevel));
+        }
+    }
+
+    public String getLivingLevel() {
+        LivingLevel livingLevel = livingLevelProperty().get();
+        return null == livingLevel ? null : livingLevel.getValue();
+    }
+
+    public ObjectProperty<LivingLevel> livingLevelProperty() {
+        if (null == livingLevel) {
+            livingLevel = new SimpleObjectProperty<>();
+            livingLevel.addListener(new ChangeListener<LivingLevel>() {
+                @Override
+                public void changed(ObservableValue<? extends LivingLevel> observable, LivingLevel oldValue, LivingLevel newValue) {
+                    checkDirty("CompareOption.LivingLevel",
+                            (null == oldValue ? null : oldValue.getValue()),
+                            (null == newValue ? null : newValue.getValue()));
+                }
+            });
+        }
+        return livingLevel;
+    }
+
+    public void setLivingLevel(LivingLevel livingLevel) {
+        this.livingLevelProperty().set(livingLevel);
+    }
+
+    public void setLivingLevel(String livingLevel) {
+        if (null != livingLevel) {
+            this.livingLevelProperty().set(LivingLevel.of(livingLevel));
+        }
+    }
+
+    public int getFaceSize() {
+        return faceSizeProperty().get();
+    }
+
+    public IntegerProperty faceSizeProperty() {
+        if (null == faceSize) {
+            faceSize = new SimpleIntegerProperty();
+            faceSize.addListener(new ChangeListener<Number>() {
+                @Override
+                public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
+                    checkDirty("CompareOption.FaceSize", oldValue, newValue);
+                }
+            });
+        }
+        return faceSize;
+    }
+
+    public void setFaceSize(int faceSize) {
+        this.faceSizeProperty().set(faceSize);
+    }
+
+    public String getDataServiceUrl() {
+        return dataServiceUrlProperty().get();
+    }
+
+    public StringProperty dataServiceUrlProperty() {
+        if (null == dataServiceUrl) {
+            dataServiceUrl = new SimpleStringProperty();
+            dataServiceUrl.addListener(new ChangeListener<String>() {
+                @Override
+                public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
+                    checkDirty("ServerInfo.DataServiceUrl", oldValue, newValue);
+                }
+            });
+        }
+        return dataServiceUrl;
+    }
+
+    public void setDataServiceUrl(String dataServiceUrl) {
+        this.dataServiceUrlProperty().set(dataServiceUrl);
+    }
+
+    public String getDeviceServiceUrl() {
+        return deviceServiceUrlProperty().get();
+    }
+
+    public StringProperty deviceServiceUrlProperty() {
+        if (null == deviceServiceUrl) {
+            deviceServiceUrl = new SimpleStringProperty();
+            deviceServiceUrl.addListener(new ChangeListener<String>() {
+                @Override
+                public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
+                    checkDirty("ServerInfo.DeviceServiceUrl", oldValue, newValue);
+                }
+            });
+        }
+        return deviceServiceUrl;
+    }
+
+    public void setDeviceServiceUrl(String deviceServiceUrl) {
+        this.deviceServiceUrlProperty().set(deviceServiceUrl);
+    }
+
+    public int getCapacity() {
+        return capacityProperty().get();
+    }
+
+    public IntegerProperty capacityProperty() {
+        if (null == capacity) {
+            capacity = new SimpleIntegerProperty();
+        }
+        return capacity;
+    }
+
+    public void setCapacity(int capacity) {
+        this.capacityProperty().set(capacity);
+    }
+}

+ 44 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/FaceLevel.java

@@ -0,0 +1,44 @@
+package com.usoftchina.smartschool.device.biometric;
+
+/**
+ * 人脸阈值级别
+ * 0 宽松 1 正常 2 严格
+ *
+ * @author yingp
+ * @date 2019/11/19
+ */
+public enum FaceLevel {
+    LEVEL_0("0", "宽松"),
+    LEVEL_1("1", "正常"),
+    LEVEL_2("2", "严格");
+
+    private final String value;
+    private final String text;
+
+    FaceLevel(String value, String text) {
+        this.value = value;
+        this.text = text;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public static FaceLevel of(String value) {
+        for (FaceLevel faceLevel : values()) {
+            if (faceLevel.getValue().equals(value)) {
+                return faceLevel;
+            }
+        }
+        return LEVEL_0;
+    }
+
+    @Override
+    public String toString() {
+        return text;
+    }
+}

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

@@ -1,6 +1,8 @@
 package com.usoftchina.smartschool.device.biometric;
 
 /**
+ * 设备心跳信息
+ *
  * @author yingp
  * @date 2019/11/14
  */

+ 20 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/HeartbeatEvent.java

@@ -0,0 +1,20 @@
+package com.usoftchina.smartschool.device.biometric;
+
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public class HeartbeatEvent extends ApplicationEvent {
+    private final String deviceCode;
+
+    public HeartbeatEvent(String deviceCode, Object source) {
+        super(source);
+        this.deviceCode = deviceCode;
+    }
+
+    public String getDeviceCode() {
+        return deviceCode;
+    }
+}

+ 43 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/LivingLevel.java

@@ -0,0 +1,43 @@
+package com.usoftchina.smartschool.device.biometric;
+
+/**
+ * 活体阈值级别
+ * 0 宽松 2 严格
+ *
+ * @author yingp
+ * @date 2019/11/19
+ */
+public enum LivingLevel {
+    LEVEL_0("0", "宽松"),
+    LEVEL_2("2", "严格");
+
+    private final String value;
+    private final String text;
+
+    LivingLevel(String value, String text) {
+        this.value = value;
+        this.text = text;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public static LivingLevel of(String value) {
+        for (LivingLevel livingLevel : values()) {
+            if (livingLevel.getValue().equals(value)) {
+                return livingLevel;
+            }
+        }
+        return LEVEL_0;
+    }
+
+    @Override
+    public String toString() {
+        return text;
+    }
+}

+ 139 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/PersonPo.java

@@ -0,0 +1,139 @@
+package com.usoftchina.smartschool.device.biometric;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 人员信息
+ *
+ * @author yingp
+ * @date 2019/11/18
+ */
+public class PersonPo implements Serializable {
+    private Integer id;
+    /**
+     * 数据类型
+     * -1:负样本
+     * 0:员工
+     * 1:访客
+     * 2:自定义人员类型 1
+     * 3:自定义人员类型 2
+     * 8:白名单
+     * 9:黑名单
+     */
+    private Integer dataType;
+    /**
+     * 操作标识
+     * 0:新增/修改 1:删除 (id 不存在视为新增,已存 在视为修改)
+     */
+    private Integer delFlag;
+    /**
+     * 照片
+     * JPG 图片 base64
+     */
+    private String picture;
+    /**
+     * 有效截止时间
+     */
+    private Date endTime;
+    /**
+     * 姓名
+     */
+    private String name;
+    /**
+     * 手机号
+     */
+    private String phone;
+    /**
+     * 性别编码
+     */
+    private String genderCode;
+    /**
+     * 民族编码
+     */
+    private String nationCode;
+    /**
+     * 门禁卡号
+     */
+    private String accessCardNum;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getDataType() {
+        return dataType;
+    }
+
+    public void setDataType(Integer dataType) {
+        this.dataType = dataType;
+    }
+
+    public Integer getDelFlag() {
+        return delFlag;
+    }
+
+    public void setDelFlag(Integer delFlag) {
+        this.delFlag = delFlag;
+    }
+
+    public String getPicture() {
+        return picture;
+    }
+
+    public void setPicture(String picture) {
+        this.picture = picture;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    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 getAccessCardNum() {
+        return accessCardNum;
+    }
+
+    public void setAccessCardNum(String accessCardNum) {
+        this.accessCardNum = accessCardNum;
+    }
+}

+ 23 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/ReqListPo.java

@@ -0,0 +1,23 @@
+package com.usoftchina.smartschool.device.biometric;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public class ReqListPo<T> {
+    private List<T> lstParameter;
+
+    public ReqListPo(List<T> lstParameter) {
+        this.lstParameter = lstParameter;
+    }
+
+    public List<T> getLstParameter() {
+        return lstParameter;
+    }
+
+    public void setLstParameter(List<T> lstParameter) {
+        this.lstParameter = lstParameter;
+    }
+}

+ 51 - 0
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/ResultListPo.java

@@ -0,0 +1,51 @@
+package com.usoftchina.smartschool.device.biometric;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public class ResultListPo<T> implements Serializable {
+    /**
+     * 返回状态
+     */
+    private String status;
+    /**
+     * 返回信息
+     */
+    private String message;
+
+    private List<T> lstParameter;
+
+    public String getStatus() {
+        return status;
+    }
+
+    public ResultListPo setStatus(String status) {
+        this.status = status;
+        return this;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public ResultListPo setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+    public List<T> getLstParameter() {
+        return lstParameter;
+    }
+
+    public void setLstParameter(List<T> lstParameter) {
+        this.lstParameter = lstParameter;
+    }
+
+    public boolean isSuccess() {
+        return "0".equals(status);
+    }
+}

+ 7 - 1
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/ResultPo.java

@@ -1,10 +1,12 @@
 package com.usoftchina.smartschool.device.biometric;
 
+import java.io.Serializable;
+
 /**
  * @author yingp
  * @date 2019/11/14
  */
-public class ResultPo {
+public class ResultPo implements Serializable {
     /**
      * 返回状态
      */
@@ -62,4 +64,8 @@ public class ResultPo {
         this.holdRequests = holdRequests;
         return this;
     }
+
+    public boolean wasSuccess() {
+        return "0".equals(status);
+    }
 }

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

@@ -0,0 +1,34 @@
+package com.usoftchina.smartschool.device.biometric;
+
+/**
+ * @author yingp
+ * @date 2019/11/19
+ */
+public class Setting {
+    private String name;
+    private Object value;
+
+    public Setting() {
+    }
+
+    public Setting(String name, Object value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    public void setValue(Object value) {
+        this.value = value;
+    }
+}

+ 6 - 2
applications/device/device-sdk-biometric/src/main/java/com/usoftchina/smartschool/device/biometric/VerificationController.java

@@ -4,6 +4,10 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.time.LocalDateTime;
+
 /**
  * @author yingp
  * @date 2019/11/14
@@ -18,8 +22,8 @@ public class VerificationController {
      * @return
      */
     @PostMapping("/passlog/passFullLog")
-    public ResultPo passLog(AccessPo access) {
-        System.out.println(access);
+    public ResultPo passLog(HttpServletRequest request) throws IOException {
+        System.out.println(LocalDateTime.now() + " passFullLog " + request.getReader().readLine());
         return ResultPo.success();
     }
 }

+ 6 - 2
applications/device/device-sdk/src/main/java/com/usoftchina/smartschool/device/api/DeviceApi.java

@@ -15,12 +15,16 @@ public interface DeviceApi {
      *
      * @param info
      */
-    void add(DeviceInfo info);
+    default void add(DeviceInfo info) {
+        throw new RuntimeException("Please override method add");
+    }
 
     /**
      * 删除设备
      *
      * @param info
      */
-    void remove(DeviceInfo info);
+    default void remove(DeviceInfo info) {
+        throw new RuntimeException("Please override method remove");
+    }
 }