feat: implement personnel positioning integration

dev
shenzhidan 2026-07-10 17:00:24 +08:00
parent 2cb56eab7f
commit 90cfd9259d
387 changed files with 26879 additions and 997 deletions

5
.gitignore vendored
View File

@ -36,4 +36,7 @@ build/
.vscode/
### Mac OS ###
.DS_Store
.DS_Store
# Local smoke-test logs and extracted dependency metadata
/tmp/

12
pom.xml
View File

@ -5,7 +5,7 @@
<artifactId>jjb-saas-parent</artifactId>
<version>2.2.0-SNAPSHOT</version>
</parent>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>zcloud_gbs_personnel_positioning</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
@ -30,27 +30,27 @@
</dependency>
<!--Project modules-->
<dependency>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>web-adapter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>web-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>web-app</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>web-domain</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>web-infrastructure</artifactId>
<version>${project.version}</version>
</dependency>

View File

@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>zcloud_gbs_personnel_positioning</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
@ -14,7 +14,7 @@
<dependencies>
<dependency>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>web-adapter</artifactId>
</dependency>
@ -39,7 +39,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.zcloud.risk.Application</mainClass>
<mainClass>com.zcloud.personnel.positioning.Application</mainClass>
</configuration>
<executions>
<execution>

View File

@ -1,4 +1,4 @@
package org.example;
package com.zcloud.personnel.positioning;
import com.jjb.saas.base.starter.bootstart.JJBSpringbootApplication;
import org.springframework.boot.SpringApplication;
@ -8,8 +8,8 @@ import org.springframework.boot.SpringApplication;
*
*/
@JJBSpringbootApplication(
scanBasePackages = {"com.zcloud","com.jjb"},
mapperPackages = {"org.example.persistence.mapper"}
scanBasePackages = {"com.zcloud", "com.jjb"},
mapperPackages = {"com.zcloud.personnel.positioning.persistence.mapper"}
)
public class Application {

View File

@ -2,10 +2,10 @@ nacos:
url: 192.168.20.100:30290
namespace: jjb-dragon
application:
name: jjb-saas-zcloud-risk
name: zcloud-gbs-personnel-positioning
version:
gateway: risk
cn-name: 风险中心
gateway: personnelPositioning
cn-name: 人员定位
spring:
application:
name: ${application.name}${application.version}

View File

@ -5,14 +5,14 @@ sdk:
url: ${common.gateway.network.http.intranet}
swagger:
enabled: ${common.swagger.enabled}
title: 例子
description: 这是例子项目
title: 人员定位
description: 人员定位应用
version: ${application.version}
group-name: 例子
group-name: 人员定位
springfox:
documentation:
swagger-ui:
base-url: ${application.gateway}
swagger:
v2:
path: /${application.gateway}/v2/api-docs
path: /${application.gateway}/v2/api-docs

View File

@ -1,23 +0,0 @@
<!doctype html><html lang="zh"><head data-built-info="@cqsjjb/scripts@2.0.0-alpha-1 Env/production (2025/10/17 11:07:53) App/risk"><meta charset="UTF-8"/><meta name="renderer" content="webkit"/><meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1"/><meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><title>--</title><script>(function () {
const APP_ENV = {
antd: {
'ant-prefix': 'micro-temp',
fontFamily: 'PingFangSC-Regular',
colorPrimary: '#1677ff',
borderRadius: parseInt('2')
},
appKey: '',
basename: 'risk',
API_HOST: ''
};
APP_ENV.API_HOST = sessionStorage.API_HOST || APP_ENV.API_HOST || window.location.origin;
window.process = {
env: { app: APP_ENV },
NODE_ENV: 'production'
};
window.__JJB_ENVIRONMENT__ = {
API_HOST: APP_ENV.API_HOST,
redirect: '',
FRAMEWORK: APP_ENV.antd
};
})();</script><script defer="defer" src="/risk/static/js/295.21df88c875577274cecb.js"></script><script defer="defer" src="/risk/static/js/main.4c0885586ad9a06ab444.js"></script><link href="/risk/static/css/main.40fc518bb0818fe8b8ea.css" rel="stylesheet"></head><body style="overflow: hidden"><noscript>此网页需要开启JavaScript功能。</noscript><div id="root" style="width: 100%; height: 100%; position: relative"></div><script type="text/javascript">/* @cqsjjb/script 输出当前应用基本信息、构建时间 */console.log("%c@cqsjjb/scripts@2.0.0-alpha-1 Env/production (2025/10/17 11:07:53) App/risk Version/master Java/<branch-name>", "color: #1890ff; border-radius: 2px; padding: 0 4px; border: 1px solid #1890ff; background: #f9fcff")</script></body></html>

View File

@ -1 +0,0 @@
body,html{height:100%;width:100%}input::-ms-clear,input::-ms-reveal{display:none}*,:after,:before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}@-ms-viewport{width:device-width}body{margin:0}[tabindex="-1"]:focus{outline:none}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{font-weight:500;margin-bottom:.5em;margin-top:0}p{margin-bottom:1em;margin-top:0}abbr[data-original-title],abbr[title]{border-bottom:0;cursor:help;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}address{font-style:normal;line-height:inherit;margin-bottom:1em}input[type=number],input[type=password],input[type=text],textarea{-webkit-appearance:none}dl,ol,ul{margin-bottom:1em;margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}code,kbd,pre,samp{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:1em}pre{margin-bottom:1em;margin-top:0;overflow:auto}figure{margin:0 0 1em}img{border-style:none;vertical-align:middle}[role=button],a,area,button,input:not([type=range]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{caption-side:bottom;padding-bottom:.3em;padding-top:.75em;text-align:left}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{border:0;margin:0;min-width:0;padding:0}legend{color:inherit;display:block;font-size:1.5em;line-height:inherit;margin-bottom:.5em;max-width:100%;padding:0;white-space:normal;width:100%}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:none;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{background-color:#feffe6;padding:.2em}

View File

@ -1 +0,0 @@
module.exports={compact:!1,plugins:[["@babel/plugin-proposal-decorators",{legacy:!0}]],presets:[["@babel/preset-env",{targets:{browsers:["ie >= 10"]}}],["@babel/preset-react",{runtime:"automatic"}]]};

View File

@ -1 +0,0 @@
module.exports={javaGit:"<git-url>",javaGitName:"<git-name>",environment:{development:{javaGitBranch:"<branch-name>",API_HOST:"http://192.168.20.100:30140/demo2"},production:{javaGitBranch:"<branch-name>",API_HOST:""}},appIdentifier:"risk",contextInject:{appKey:""},windowInject:{title:"微应用模板",links:[],element:{root:{id:"root"}},scripts:[]},server:{port:"8080",host:"127.0.0.1",open:!0},framework:{antd:{"ant-prefix":"micro-temp",fontFamily:"PingFangSC-Regular",colorPrimary:"#1677ff",borderRadius:2}},webpackConfig:{htmlWebpackPluginOption:{inject:!0}}};

File diff suppressed because one or more lines are too long

View File

@ -1,135 +0,0 @@
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/
/*!
* isobject <https://github.com/jonschlinkert/isobject>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @preserve
* Counter block mode compatible with Dr Brian Gladman fileenc.c
* derived from CryptoJS.mode.CTR
* Jan Hruby jhruby.web@gmail.com
*/
/** @preserve
(c) 2012 by Cédric Mesnil. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**![caret-down](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTg0MC40IDMwMEgxODMuNmMtMTkuNyAwLTMwLjcgMjAuOC0xOC41IDM1bDMyOC40IDM4MC44YzkuNCAxMC45IDI3LjUgMTAuOSAzNyAwTDg1OC45IDMzNWMxMi4yLTE0LjIgMS4yLTM1LTE4LjUtMzV6IiAvPjwvc3ZnPg==) */
/**![caret-up](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTg1OC45IDY4OUw1MzAuNSAzMDguMmMtOS40LTEwLjktMjcuNS0xMC45LTM3IDBMMTY1LjEgNjg5Yy0xMi4yIDE0LjItMS4yIDM1IDE4LjUgMzVoNjU2LjhjMTkuNyAwIDMwLjctMjAuOCAxOC41LTM1eiIgLz48L3N2Zz4=) */
/**![check](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTkxMiAxOTBoLTY5LjljLTkuOCAwLTE5LjEgNC41LTI1LjEgMTIuMkw0MDQuNyA3MjQuNSAyMDcgNDc0YTMyIDMyIDAgMDAtMjUuMS0xMi4ySDExMmMtNi43IDAtMTAuNCA3LjctNi4zIDEyLjlsMjczLjkgMzQ3YzEyLjggMTYuMiAzNy40IDE2LjIgNTAuMyAwbDQ4OC40LTYxOC45YzQuMS01LjEuNC0xMi44LTYuMy0xMi44eiIgLz48L3N2Zz4=) */
/**![double-left](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI3Mi45IDUxMmwyNjUuNC0zMzkuMWM0LjEtNS4yLjQtMTIuOS02LjMtMTIuOWgtNzcuM2MtNC45IDAtOS42IDIuMy0xMi42IDYuMUwxODYuOCA0OTIuM2EzMS45OSAzMS45OSAwIDAwMCAzOS41bDI1NS4zIDMyNi4xYzMgMy45IDcuNyA2LjEgMTIuNiA2LjFINTMyYzYuNyAwIDEwLjQtNy43IDYuMy0xMi45TDI3Mi45IDUxMnptMzA0IDBsMjY1LjQtMzM5LjFjNC4xLTUuMi40LTEyLjktNi4zLTEyLjloLTc3LjNjLTQuOSAwLTkuNiAyLjMtMTIuNiA2LjFMNDkwLjggNDkyLjNhMzEuOTkgMzEuOTkgMCAwMDAgMzkuNWwyNTUuMyAzMjYuMWMzIDMuOSA3LjcgNi4xIDEyLjYgNi4xSDgzNmM2LjcgMCAxMC40LTcuNyA2LjMtMTIuOUw1NzYuOSA1MTJ6IiAvPjwvc3ZnPg==) */
/**![double-right](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTUzMy4yIDQ5Mi4zTDI3Ny45IDE2Ni4xYy0zLTMuOS03LjctNi4xLTEyLjYtNi4xSDE4OGMtNi43IDAtMTAuNCA3LjctNi4zIDEyLjlMNDQ3LjEgNTEyIDE4MS43IDg1MS4xQTcuOTggNy45OCAwIDAwMTg4IDg2NGg3Ny4zYzQuOSAwIDkuNi0yLjMgMTIuNi02LjFsMjU1LjMtMzI2LjFjOS4xLTExLjcgOS4xLTI3LjkgMC0zOS41em0zMDQgMEw1ODEuOSAxNjYuMWMtMy0zLjktNy43LTYuMS0xMi42LTYuMUg0OTJjLTYuNyAwLTEwLjQgNy43LTYuMyAxMi45TDc1MS4xIDUxMiA0ODUuNyA4NTEuMUE3Ljk4IDcuOTggMCAwMDQ5MiA4NjRoNzcuM2M0LjkgMCA5LjYtMi4zIDEyLjYtNi4xbDI1NS4zLTMyNi4xYzkuMS0xMS43IDkuMS0yNy45IDAtMzkuNXoiIC8+PC9zdmc+) */
/**![ellipsis](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE3NiA1MTFhNTYgNTYgMCAxMDExMiAwIDU2IDU2IDAgMTAtMTEyIDB6bTI4MCAwYTU2IDU2IDAgMTAxMTIgMCA1NiA1NiAwIDEwLTExMiAwem0yODAgMGE1NiA1NiAwIDEwMTEyIDAgNTYgNTYgMCAxMC0xMTIgMHoiIC8+PC9zdmc+) */
/**![eye](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTk0Mi4yIDQ4Ni4yQzg0Ny40IDI4Ni41IDcwNC4xIDE4NiA1MTIgMTg2Yy0xOTIuMiAwLTMzNS40IDEwMC41LTQzMC4yIDMwMC4zYTYwLjMgNjAuMyAwIDAwMCA1MS41QzE3Ni42IDczNy41IDMxOS45IDgzOCA1MTIgODM4YzE5Mi4yIDAgMzM1LjQtMTAwLjUgNDMwLjItMzAwLjMgNy43LTE2LjIgNy43LTM1IDAtNTEuNXpNNTEyIDc2NmMtMTYxLjMgMC0yNzkuNC04MS44LTM2Mi43LTI1NEMyMzIuNiAzMzkuOCAzNTAuNyAyNTggNTEyIDI1OGMxNjEuMyAwIDI3OS40IDgxLjggMzYyLjcgMjU0Qzc5MS41IDY4NC4yIDY3My40IDc2NiA1MTIgNzY2em0tNC00MzBjLTk3LjIgMC0xNzYgNzguOC0xNzYgMTc2czc4LjggMTc2IDE3NiAxNzYgMTc2LTc4LjggMTc2LTE3Ni03OC44LTE3Ni0xNzYtMTc2em0wIDI4OGMtNjEuOSAwLTExMi01MC4xLTExMi0xMTJzNTAuMS0xMTIgMTEyLTExMiAxMTIgNTAuMSAxMTIgMTEyLTUwLjEgMTEyLTExMiAxMTJ6IiAvPjwvc3ZnPg==) */
/**![file](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTg1NC42IDI4OC42TDYzOS40IDczLjRjLTYtNi0xNC4xLTkuNC0yMi42LTkuNEgxOTJjLTE3LjcgMC0zMiAxNC4zLTMyIDMydjgzMmMwIDE3LjcgMTQuMyAzMiAzMiAzMmg2NDBjMTcuNyAwIDMyLTE0LjMgMzItMzJWMzExLjNjMC04LjUtMy40LTE2LjctOS40LTIyLjd6TTc5MC4yIDMyNkg2MDJWMTM3LjhMNzkwLjIgMzI2em0xLjggNTYySDIzMlYxMzZoMzAydjIxNmE0MiA0MiAwIDAwNDIgNDJoMjE2djQ5NHoiIC8+PC9zdmc+) */
/**![filter](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTM0OSA4MzhjMCAxNy43IDE0LjIgMzIgMzEuOCAzMmgyNjIuNGMxNy42IDAgMzEuOC0xNC4zIDMxLjgtMzJWNjQySDM0OXYxOTZ6bTUzMS4xLTY4NEgxNDMuOWMtMjQuNSAwLTM5LjggMjYuNy0yNy41IDQ4bDIyMS4zIDM3NmgzNDguOGwyMjEuMy0zNzZjMTIuMS0yMS4zLTMuMi00OC0yNy43LTQ4eiIgLz48L3N2Zz4=) */
/**![folder-open](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTkyOCA0NDRIODIwVjMzMC40YzAtMTcuNy0xNC4zLTMyLTMyLTMySDQ3M0wzNTUuNyAxODYuMmE4LjE1IDguMTUgMCAwMC01LjUtMi4ySDk2Yy0xNy43IDAtMzIgMTQuMy0zMiAzMnY1OTJjMCAxNy43IDE0LjMgMzIgMzIgMzJoNjk4YzEzIDAgMjQuOC03LjkgMjkuNy0yMGwxMzQtMzMyYzEuNS0zLjggMi4zLTcuOSAyLjMtMTIgMC0xNy43LTE0LjMtMzItMzItMzJ6TTEzNiAyNTZoMTg4LjVsMTE5LjYgMTE0LjRINzQ4VjQ0NEgyMzhjLTEzIDAtMjQuOCA3LjktMjkuNyAyMEwxMzYgNjQzLjJWMjU2em02MzUuMyA1MTJIMTU5bDEwMy4zLTI1Nmg2MTIuNEw3NzEuMyA3Njh6IiAvPjwvc3ZnPg==) */
/**![folder](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTg4MCAyOTguNEg1MjFMNDAzLjcgMTg2LjJhOC4xNSA4LjE1IDAgMDAtNS41LTIuMkgxNDRjLTE3LjcgMC0zMiAxNC4zLTMyIDMydjU5MmMwIDE3LjcgMTQuMyAzMiAzMiAzMmg3MzZjMTcuNyAwIDMyLTE0LjMgMzItMzJWMzMwLjRjMC0xNy43LTE0LjMtMzItMzItMzJ6TTg0MCA3NjhIMTg0VjI1NmgxODguNWwxMTkuNiAxMTQuNEg4NDBWNzY4eiIgLz48L3N2Zz4=) */
/**![holder](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTMwMCAyNzYuNWE1NiA1NiAwIDEwNTYtOTcgNTYgNTYgMCAwMC01NiA5N3ptMCAyODRhNTYgNTYgMCAxMDU2LTk3IDU2IDU2IDAgMDAtNTYgOTd6TTY0MCAyMjhhNTYgNTYgMCAxMDExMiAwIDU2IDU2IDAgMDAtMTEyIDB6bTAgMjg0YTU2IDU2IDAgMTAxMTIgMCA1NiA1NiAwIDAwLTExMiAwek0zMDAgODQ0LjVhNTYgNTYgMCAxMDU2LTk3IDU2IDU2IDAgMDAtNTYgOTd6TTY0MCA3OTZhNTYgNTYgMCAxMDExMiAwIDU2IDU2IDAgMDAtMTEyIDB6IiAvPjwvc3ZnPg==) */
/**![left](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTcyNCAyMTguM1YxNDFjMC02LjctNy43LTEwLjQtMTIuOS02LjNMMjYwLjMgNDg2LjhhMzEuODYgMzEuODYgMCAwMDAgNTAuM2w0NTAuOCAzNTIuMWM1LjMgNC4xIDEyLjkuNCAxMi45LTYuM3YtNzcuM2MwLTQuOS0yLjMtOS42LTYuMS0xMi42bC0zNjAtMjgxIDM2MC0yODEuMWMzLjgtMyA2LjEtNy43IDYuMS0xMi42eiIgLz48L3N2Zz4=) */
/**![minus-square](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTMyOCA1NDRoMzY4YzQuNCAwIDgtMy42IDgtOHYtNDhjMC00LjQtMy42LTgtOC04SDMyOGMtNC40IDAtOCAzLjYtOCA4djQ4YzAgNC40IDMuNiA4IDggOHoiIC8+PHBhdGggZD0iTTg4MCAxMTJIMTQ0Yy0xNy43IDAtMzIgMTQuMy0zMiAzMnY3MzZjMCAxNy43IDE0LjMgMzIgMzIgMzJoNzM2YzE3LjcgMCAzMi0xNC4zIDMyLTMyVjE0NGMwLTE3LjctMTQuMy0zMi0zMi0zMnptLTQwIDcyOEgxODRWMTg0aDY1NnY2NTZ6IiAvPjwvc3ZnPg==) */
/**![plus-square](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTMyOCA1NDRoMTUydjE1MmMwIDQuNCAzLjYgOCA4IDhoNDhjNC40IDAgOC0zLjYgOC04VjU0NGgxNTJjNC40IDAgOC0zLjYgOC04di00OGMwLTQuNC0zLjYtOC04LThINTQ0VjMyOGMwLTQuNC0zLjYtOC04LThoLTQ4Yy00LjQgMC04IDMuNi04IDh2MTUySDMyOGMtNC40IDAtOCAzLjYtOCA4djQ4YzAgNC40IDMuNiA4IDggOHoiIC8+PHBhdGggZD0iTTg4MCAxMTJIMTQ0Yy0xNy43IDAtMzIgMTQuMy0zMiAzMnY3MzZjMCAxNy43IDE0LjMgMzIgMzIgMzJoNzM2YzE3LjcgMCAzMi0xNC4zIDMyLTMyVjE0NGMwLTE3LjctMTQuMy0zMi0zMi0zMnptLTQwIDcyOEgxODRWMTg0aDY1NnY2NTZ6IiAvPjwvc3ZnPg==) */
/**![right](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIGZpbGw9IiNjYWNhY2EiIHZpZXdCb3g9IjY0IDY0IDg5NiA4OTYiIGZvY3VzYWJsZT0iZmFsc2UiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTc2NS43IDQ4Ni44TDMxNC45IDEzNC43QTcuOTcgNy45NyAwIDAwMzAyIDE0MXY3Ny4zYzAgNC45IDIuMyA5LjYgNi4xIDEyLjZsMzYwIDI4MS4xLTM2MCAyODEuMWMtMy45IDMtNi4xIDcuNy02LjEgMTIuNlY4ODNjMCA2LjcgNy43IDEwLjQgMTIuOSA2LjNsNDUwLjgtMzUyLjFhMzEuOTYgMzEuOTYgMCAwMDAtNTAuNHoiIC8+PC9zdmc+) */

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */

View File

@ -1,44 +0,0 @@
{
"name": "micro-app",
"version": "2.0.0",
"description": "建教帮微应用模板",
"author": "JJB",
"license": "MIT",
"main": "index.js",
"scripts": {
"serve": "node node_modules/@cqsjjb/scripts/webpack.dev.server.js",
"build": "node node_modules/@cqsjjb/scripts/webpack.build.js",
"push": "jjb-cmd push java production",
"clean-cache": "rimraf node_modules/.cache/webpack",
"serve:development": "cross-env NODE_ENV=development npm run serve",
"serve:production": "cross-env NODE_ENV=production npm run serve",
"build:development": "cross-env NODE_ENV=development npm run build",
"build:production": "cross-env NODE_ENV=production npm run build",
"code-optimization": "node node_modules/@cqsjjb/scripts/code-optimization.js",
"lint": "eslint --ext .js,.jsx,.tsx --fix src"
},
"dependencies": {
"@ant-design/icons": "latest",
"@cqsjjb/jjb-common-decorator": "latest",
"@cqsjjb/jjb-common-lib": "latest",
"@cqsjjb/jjb-dva-runtime": "latest",
"@cqsjjb/jjb-react-admin-component": "latest",
"ahooks": "^3.9.5",
"antd": "latest",
"dayjs": "^1.11.7",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@antfu/eslint-config": "^5.4.1",
"@babel/plugin-proposal-decorators": "^7.19.3",
"@cqsjjb/scripts": "latest",
"@eslint-react/eslint-plugin": "^2.2.2",
"cross-env": "^7.0.3",
"eslint": "^9.37.0",
"eslint-plugin-format": "^1.0.2",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint-plugin-react-refresh": "^0.4.23",
"typescript": "^5.9.3"
}
}

View File

@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>zcloud_gbs_personnel_positioning</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
@ -17,13 +17,17 @@
<groupId>com.jjb.saas</groupId>
<artifactId>jjb-saas-framework-adapter</artifactId>
</dependency>
<dependency>
<groupId>com.jjb.saas</groupId>
<artifactId>jjb-saas-framework-job</artifactId>
</dependency>
<dependency>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>web-app</artifactId>
</dependency>
<dependency>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>web-client</artifactId>
</dependency>
</dependencies>

View File

@ -0,0 +1,31 @@
package com.zcloud.personnel.positioning.plan;
import com.jjb.saas.framework.job.Job;
import com.jjb.saas.framework.job.annotation.JobRegister;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.zcloud.personnel.positioning.command.AlarmRecordFindsPullService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class FindsAlarmIncrementalSyncJob implements Job {
private final AlarmRecordFindsPullService alarmRecordFindsPullService;
@Override
@JobRegister(cron = "0 */2 * * * ?", jobDesc = "FindS告警增量拉取", author = "申智丹", triggerStatus = 1)
@XxlJob("com.zcloud.personnel.positioning.plan.FindsAlarmIncrementalSyncJob")
public ReturnT<String> execute(String param) {
try {
int pulledCount = alarmRecordFindsPullService.syncIncremental();
String message = "FindS告警增量拉取完成本次处理 " + pulledCount + " 条";
XxlJobHelper.log(message);
return new ReturnT<>(ReturnT.SUCCESS_CODE, message);
} catch (Exception e) {
XxlJobHelper.log("FindS告警增量拉取失败: " + e.getMessage());
return new ReturnT<>(ReturnT.FAIL_CODE, e.getMessage());
}
}
}

View File

@ -0,0 +1,30 @@
package com.zcloud.personnel.positioning.plan;
import com.jjb.saas.framework.job.Job;
import com.jjb.saas.framework.job.annotation.JobRegister;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.zcloud.personnel.positioning.command.FindsPendingSyncService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class FindsPendingRetryJob implements Job {
private final FindsPendingSyncService findsPendingSyncService;
@Override
@JobRegister(cron = "0 */1 * * * ?", jobDesc = "FindS待同步数据重试", author = "申智丹", triggerStatus = 1)
@XxlJob("com.zcloud.personnel.positioning.plan.FindsPendingRetryJob")
public ReturnT<String> execute(String param) {
try {
findsPendingSyncService.retryPending();
XxlJobHelper.log("FindS待同步围栏、告警规则和终端重试完成");
return new ReturnT<>(ReturnT.SUCCESS_CODE, "FindS pending synchronization retry completed");
} catch (Exception e) {
XxlJobHelper.log("FindS待同步数据重试失败: " + e.getMessage());
return new ReturnT<>(ReturnT.FAIL_CODE, e.getMessage());
}
}
}

View File

@ -0,0 +1,101 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.AlarmDisposeRecordServiceI;
import com.zcloud.personnel.positioning.dto.AlarmDisposeRecordAddCmd;
import com.zcloud.personnel.positioning.dto.AlarmDisposeRecordPageQry;
import com.zcloud.personnel.positioning.dto.AlarmDisposeRecordUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmDisposeRecordCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "告警处置记录")
@RequestMapping("/${application.gateway}/alarmDisposeRecord")
@RestController
@AllArgsConstructor
public class AlarmDisposeRecordController {
private final AlarmDisposeRecordServiceI alarmDisposeRecordService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<AlarmDisposeRecordCO> add(@Validated @RequestBody AlarmDisposeRecordAddCmd cmd) {
fillCorpinfo(cmd);
return alarmDisposeRecordService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<AlarmDisposeRecordCO> page(@RequestBody AlarmDisposeRecordPageQry qry) {
return alarmDisposeRecordService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<AlarmDisposeRecordCO> listAll() {
return MultiResponse.of(alarmDisposeRecordService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<AlarmDisposeRecordCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(alarmDisposeRecordService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
alarmDisposeRecordService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
alarmDisposeRecordService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody AlarmDisposeRecordUpdateCmd cmd) {
fillCorpinfo(cmd);
alarmDisposeRecordService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(AlarmDisposeRecordAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(AlarmDisposeRecordUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,114 @@
package com.zcloud.personnel.positioning.web;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import com.zcloud.personnel.positioning.api.AlarmRecordServiceI;
import com.zcloud.personnel.positioning.dto.AlarmRecordAddCmd;
import com.zcloud.personnel.positioning.dto.AlarmRecordPageQry;
import com.zcloud.personnel.positioning.dto.AlarmRecordUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRecordCO;
import com.zcloud.personnel.positioning.dto.clientobject.OptionCO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Alarm record APIs.
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "personnel positioning alarm record")
@RequestMapping("/${application.gateway}/alarmRecord")
@RestController
@AllArgsConstructor
public class AlarmRecordController {
private final AlarmRecordServiceI alarmRecordService;
@ApiOperation("save")
@PostMapping("/save")
public SingleResponse<AlarmRecordCO> add(@Validated @RequestBody AlarmRecordAddCmd cmd) {
fillCorpinfo(cmd);
return alarmRecordService.add(cmd);
}
@ApiOperation("page list")
@PostMapping("/list")
public PageResponse<AlarmRecordCO> page(@RequestBody AlarmRecordPageQry qry) {
return alarmRecordService.listPage(qry);
}
@ApiOperation("all list")
@GetMapping("/listAll")
public MultiResponse<AlarmRecordCO> listAll() {
return MultiResponse.of(alarmRecordService.listAll());
}
@ApiOperation("company options")
@GetMapping("/companyOptions")
public MultiResponse<OptionCO> companyOptions() {
return MultiResponse.of(alarmRecordService.listCompanyOptions());
}
@ApiOperation("alarm type options")
@GetMapping("/alarmTypeOptions")
public MultiResponse<OptionCO> alarmTypeOptions() {
return MultiResponse.of(alarmRecordService.listAlarmTypeOptions());
}
@ApiOperation("detail")
@GetMapping("/{id}")
public SingleResponse<AlarmRecordCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(alarmRecordService.queryById(id));
}
@ApiOperation("remove")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
alarmRecordService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("remove batch")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
alarmRecordService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("edit")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody AlarmRecordUpdateCmd cmd) {
fillCorpinfo(cmd);
alarmRecordService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(AlarmRecordAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(AlarmRecordUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,101 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.AlarmRecordTargetServiceI;
import com.zcloud.personnel.positioning.dto.AlarmRecordTargetAddCmd;
import com.zcloud.personnel.positioning.dto.AlarmRecordTargetPageQry;
import com.zcloud.personnel.positioning.dto.AlarmRecordTargetUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRecordTargetCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "告警主体明细")
@RequestMapping("/${application.gateway}/alarmRecordTarget")
@RestController
@AllArgsConstructor
public class AlarmRecordTargetController {
private final AlarmRecordTargetServiceI alarmRecordTargetService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<AlarmRecordTargetCO> add(@Validated @RequestBody AlarmRecordTargetAddCmd cmd) {
fillCorpinfo(cmd);
return alarmRecordTargetService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<AlarmRecordTargetCO> page(@RequestBody AlarmRecordTargetPageQry qry) {
return alarmRecordTargetService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<AlarmRecordTargetCO> listAll() {
return MultiResponse.of(alarmRecordTargetService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<AlarmRecordTargetCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(alarmRecordTargetService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
alarmRecordTargetService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
alarmRecordTargetService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody AlarmRecordTargetUpdateCmd cmd) {
fillCorpinfo(cmd);
alarmRecordTargetService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(AlarmRecordTargetAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(AlarmRecordTargetUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,130 @@
package com.zcloud.personnel.positioning.web;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import com.zcloud.personnel.positioning.api.AlarmRuleServiceI;
import com.zcloud.personnel.positioning.dto.AlarmRuleAddCmd;
import com.zcloud.personnel.positioning.dto.AlarmRuleCompanyPageQry;
import com.zcloud.personnel.positioning.dto.AlarmRulePageQry;
import com.zcloud.personnel.positioning.dto.AlarmRuleUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRuleCO;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRuleCompanyStatCO;
import com.zcloud.personnel.positioning.dto.clientobject.OptionCO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "人员定位告警规则")
@RequestMapping("/${application.gateway}/alarmRule")
@RestController
@AllArgsConstructor
public class AlarmRuleController {
private final AlarmRuleServiceI alarmRuleService;
@ApiOperation("公司配置统计分页")
@PostMapping("/company/list")
public PageResponse<AlarmRuleCompanyStatCO> companyPage(@RequestBody AlarmRuleCompanyPageQry qry) {
return alarmRuleService.listCompanyStats(qry);
}
@ApiOperation("Company options")
@GetMapping("/company/options")
public MultiResponse<OptionCO> companyOptions(@RequestParam(required = false) String keyword) {
return MultiResponse.of(alarmRuleService.listCompanyOptions(keyword));
}
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<AlarmRuleCO> add(@Validated @RequestBody AlarmRuleAddCmd cmd) {
fillCorpinfo(cmd);
return alarmRuleService.add(cmd);
}
@ApiOperation("配置明细分页")
@PostMapping("/list")
public PageResponse<AlarmRuleCO> page(@RequestBody AlarmRulePageQry qry) {
return alarmRuleService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<AlarmRuleCO> listAll() {
return MultiResponse.of(alarmRuleService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<AlarmRuleCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(alarmRuleService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
alarmRuleService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
alarmRuleService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody AlarmRuleUpdateCmd cmd) {
fillCorpinfo(cmd);
alarmRuleService.edit(cmd);
return SingleResponse.buildSuccess();
}
@ApiOperation("启用")
@PutMapping("/{id}/enable")
public Response enable(@PathVariable("id") Long id) {
alarmRuleService.enable(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("停用")
@PutMapping("/{id}/disable")
public Response disable(@PathVariable("id") Long id) {
alarmRuleService.disable(id);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(AlarmRuleAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (cmd.getCorpinfoId() == null && ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(AlarmRuleUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (cmd.getCorpinfoId() == null && ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,101 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.AlarmRuleFenceRelServiceI;
import com.zcloud.personnel.positioning.dto.AlarmRuleFenceRelAddCmd;
import com.zcloud.personnel.positioning.dto.AlarmRuleFenceRelPageQry;
import com.zcloud.personnel.positioning.dto.AlarmRuleFenceRelUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRuleFenceRelCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "告警规则围栏关联")
@RequestMapping("/${application.gateway}/alarmRuleFenceRel")
@RestController
@AllArgsConstructor
public class AlarmRuleFenceRelController {
private final AlarmRuleFenceRelServiceI alarmRuleFenceRelService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<AlarmRuleFenceRelCO> add(@Validated @RequestBody AlarmRuleFenceRelAddCmd cmd) {
fillCorpinfo(cmd);
return alarmRuleFenceRelService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<AlarmRuleFenceRelCO> page(@RequestBody AlarmRuleFenceRelPageQry qry) {
return alarmRuleFenceRelService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<AlarmRuleFenceRelCO> listAll() {
return MultiResponse.of(alarmRuleFenceRelService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<AlarmRuleFenceRelCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(alarmRuleFenceRelService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
alarmRuleFenceRelService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
alarmRuleFenceRelService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody AlarmRuleFenceRelUpdateCmd cmd) {
fillCorpinfo(cmd);
alarmRuleFenceRelService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(AlarmRuleFenceRelAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(AlarmRuleFenceRelUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,101 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.AlarmRuleItemServiceI;
import com.zcloud.personnel.positioning.dto.AlarmRuleItemAddCmd;
import com.zcloud.personnel.positioning.dto.AlarmRuleItemPageQry;
import com.zcloud.personnel.positioning.dto.AlarmRuleItemUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRuleItemCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "告警规则项")
@RequestMapping("/${application.gateway}/alarmRuleItem")
@RestController
@AllArgsConstructor
public class AlarmRuleItemController {
private final AlarmRuleItemServiceI alarmRuleItemService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<AlarmRuleItemCO> add(@Validated @RequestBody AlarmRuleItemAddCmd cmd) {
fillCorpinfo(cmd);
return alarmRuleItemService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<AlarmRuleItemCO> page(@RequestBody AlarmRuleItemPageQry qry) {
return alarmRuleItemService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<AlarmRuleItemCO> listAll() {
return MultiResponse.of(alarmRuleItemService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<AlarmRuleItemCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(alarmRuleItemService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
alarmRuleItemService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
alarmRuleItemService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody AlarmRuleItemUpdateCmd cmd) {
fillCorpinfo(cmd);
alarmRuleItemService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(AlarmRuleItemAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(AlarmRuleItemUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,101 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.AreaServiceI;
import com.zcloud.personnel.positioning.dto.AreaAddCmd;
import com.zcloud.personnel.positioning.dto.AreaPageQry;
import com.zcloud.personnel.positioning.dto.AreaUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.AreaCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "人员定位区域")
@RequestMapping("/${application.gateway}/area")
@RestController
@AllArgsConstructor
public class AreaController {
private final AreaServiceI areaService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<AreaCO> add(@Validated @RequestBody AreaAddCmd cmd) {
fillCorpinfo(cmd);
return areaService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<AreaCO> page(@RequestBody AreaPageQry qry) {
return areaService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<AreaCO> listAll() {
return MultiResponse.of(areaService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<AreaCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(areaService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
areaService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
areaService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody AreaUpdateCmd cmd) {
fillCorpinfo(cmd);
areaService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(AreaAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(AreaUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,151 @@
package com.zcloud.personnel.positioning.web;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import com.zcloud.personnel.positioning.api.FenceServiceI;
import com.zcloud.personnel.positioning.dto.FenceAddCmd;
import com.zcloud.personnel.positioning.dto.FenceCompanyAreaSaveCmd;
import com.zcloud.personnel.positioning.dto.FenceCompanyPageQry;
import com.zcloud.personnel.positioning.dto.FencePageQry;
import com.zcloud.personnel.positioning.dto.FenceUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.FenceCO;
import com.zcloud.personnel.positioning.dto.clientobject.FenceCompanyAreaCO;
import com.zcloud.personnel.positioning.dto.clientobject.FenceCompanyStatCO;
import com.zcloud.personnel.positioning.dto.clientobject.OptionCO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "Personnel positioning fence")
@RequestMapping("/${application.gateway}/fence")
@RestController
@AllArgsConstructor
public class FenceController {
private final FenceServiceI fenceService;
@ApiOperation("Company fence stats")
@PostMapping("/company/list")
public PageResponse<FenceCompanyStatCO> companyPage(@RequestBody FenceCompanyPageQry qry) {
return fenceService.listCompanyStats(qry);
}
@ApiOperation("Company options")
@GetMapping("/company/options")
public MultiResponse<OptionCO> companyOptions(@RequestParam(required = false) String keyword) {
return MultiResponse.of(fenceService.listCompanyOptions(keyword));
}
@ApiOperation("Company area detail")
@GetMapping("/company-area")
public SingleResponse<FenceCompanyAreaCO> getCompanyArea(@RequestParam(required = false) Long corpinfoId,
@RequestParam(required = false) String corpinfoName) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (corpinfoId == null && ssoUser != null && ssoUser.getCompanyId() != null) {
corpinfoId = ssoUser.getCompanyId();
}
return SingleResponse.of(fenceService.queryCompanyArea(corpinfoId, corpinfoName));
}
@ApiOperation("Save company area")
@PostMapping("/company-area/save")
public SingleResponse<FenceCompanyAreaCO> saveCompanyArea(@Validated @RequestBody FenceCompanyAreaSaveCmd cmd) {
fillCorpinfo(cmd);
return SingleResponse.of(fenceService.saveCompanyArea(cmd));
}
@ApiOperation("Add fence")
@PostMapping("/save")
public SingleResponse<FenceCO> add(@Validated @RequestBody FenceAddCmd cmd) {
fillCorpinfo(cmd);
return fenceService.add(cmd);
}
@ApiOperation("Fence page")
@PostMapping("/list")
public PageResponse<FenceCO> page(@RequestBody FencePageQry qry) {
return fenceService.listPage(qry);
}
@ApiOperation("All fences")
@GetMapping("/listAll")
public MultiResponse<FenceCO> listAll() {
return MultiResponse.of(fenceService.listAll());
}
@ApiOperation("Fence detail")
@GetMapping("/{id}")
public SingleResponse<FenceCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(fenceService.queryById(id));
}
@ApiOperation("Delete fence")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
fenceService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("Delete fences")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
fenceService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("Edit fence")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody FenceUpdateCmd cmd) {
fillCorpinfo(cmd);
fenceService.edit(cmd);
return SingleResponse.buildSuccess();
}
@ApiOperation("Enable fence")
@PutMapping("/{id}/enable")
public Response enable(@PathVariable("id") Long id) {
fenceService.enable(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("Disable fence")
@PutMapping("/{id}/disable")
public Response disable(@PathVariable("id") Long id) {
fenceService.disable(id);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(FenceAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (cmd.getCorpinfoId() == null && ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(FenceUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (cmd.getCorpinfoId() == null && ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(FenceCompanyAreaSaveCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (cmd.getCorpinfoId() == null && ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,101 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.FencePointServiceI;
import com.zcloud.personnel.positioning.dto.FencePointAddCmd;
import com.zcloud.personnel.positioning.dto.FencePointPageQry;
import com.zcloud.personnel.positioning.dto.FencePointUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.FencePointCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "人员定位围栏坐标点")
@RequestMapping("/${application.gateway}/fencePoint")
@RestController
@AllArgsConstructor
public class FencePointController {
private final FencePointServiceI fencePointService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<FencePointCO> add(@Validated @RequestBody FencePointAddCmd cmd) {
fillCorpinfo(cmd);
return fencePointService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<FencePointCO> page(@RequestBody FencePointPageQry qry) {
return fencePointService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<FencePointCO> listAll() {
return MultiResponse.of(fencePointService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<FencePointCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(fencePointService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
fencePointService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
fencePointService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody FencePointUpdateCmd cmd) {
fillCorpinfo(cmd);
fencePointService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(FencePointAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(FencePointUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,101 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.OpenapiConfigServiceI;
import com.zcloud.personnel.positioning.dto.OpenapiConfigAddCmd;
import com.zcloud.personnel.positioning.dto.OpenapiConfigPageQry;
import com.zcloud.personnel.positioning.dto.OpenapiConfigUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.OpenapiConfigCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "人员定位第三方接口配置")
@RequestMapping("/${application.gateway}/openapiConfig")
@RestController
@AllArgsConstructor
public class OpenapiConfigController {
private final OpenapiConfigServiceI openapiConfigService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<OpenapiConfigCO> add(@Validated @RequestBody OpenapiConfigAddCmd cmd) {
fillCorpinfo(cmd);
return openapiConfigService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<OpenapiConfigCO> page(@RequestBody OpenapiConfigPageQry qry) {
return openapiConfigService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<OpenapiConfigCO> listAll() {
return MultiResponse.of(openapiConfigService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<OpenapiConfigCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(openapiConfigService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
openapiConfigService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
openapiConfigService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody OpenapiConfigUpdateCmd cmd) {
fillCorpinfo(cmd);
openapiConfigService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(OpenapiConfigAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(OpenapiConfigUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,86 @@
package com.zcloud.personnel.positioning.web;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.SingleResponse;
import com.zcloud.personnel.positioning.api.PersonnelPositioningBiServiceI;
import com.zcloud.personnel.positioning.dto.BiAreaFenceQry;
import com.zcloud.personnel.positioning.dto.BiCompanyFenceQry;
import com.zcloud.personnel.positioning.dto.BiPersonLocationQry;
import com.zcloud.personnel.positioning.dto.BiPersonTraceQry;
import com.zcloud.personnel.positioning.dto.clientobject.BiAreaFenceCO;
import com.zcloud.personnel.positioning.dto.clientobject.BiCompanyLocationCO;
import com.zcloud.personnel.positioning.dto.clientobject.BiOverviewCO;
import com.zcloud.personnel.positioning.dto.clientobject.BiPersonLocationCO;
import com.zcloud.personnel.positioning.dto.clientobject.BiPersonTraceCO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Personnel positioning BI APIs.
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "人员定位BI")
@RequestMapping("/${application.gateway}/bi")
@RestController
@AllArgsConstructor
public class PersonnelPositioningBiController {
private final PersonnelPositioningBiServiceI personnelPositioningBiService;
@ApiOperation("BI overview")
@GetMapping("/overview")
public SingleResponse<BiOverviewCO> queryOverview() {
return SingleResponse.of(personnelPositioningBiService.queryOverview());
}
@ApiOperation("公司定位点")
@GetMapping("/companyLocation/list")
public MultiResponse<BiCompanyLocationCO> listCompanyLocation() {
return MultiResponse.of(personnelPositioningBiService.listCompanyLocation());
}
@ApiOperation("公司围栏分页")
@PostMapping("/companyFence/list")
public PageResponse<BiAreaFenceCO> listCompanyFence(@RequestBody BiCompanyFenceQry qry) {
return personnelPositioningBiService.listCompanyFence(qry);
}
@ApiOperation("人员实时定位")
@PostMapping("/personLocation/list")
public PageResponse<BiPersonLocationCO> listPersonLocation(@RequestBody BiPersonLocationQry qry) {
return personnelPositioningBiService.listPersonLocation(qry);
}
@ApiOperation("人员轨迹")
@PostMapping("/personTrace")
public SingleResponse<BiPersonTraceCO> queryPersonTrace(@RequestBody BiPersonTraceQry qry) {
return SingleResponse.of(personnelPositioningBiService.queryPersonTrace(qry));
}
@ApiOperation("区域/围栏分页")
@PostMapping("/areaFence/list")
public PageResponse<BiAreaFenceCO> listAreaFence(@RequestBody BiAreaFenceQry qry) {
return personnelPositioningBiService.listAreaFence(qry);
}
@ApiOperation("区域/围栏全量")
@GetMapping("/areaFence/listAll")
public MultiResponse<BiAreaFenceCO> listAllAreaFence() {
return MultiResponse.of(personnelPositioningBiService.listAllAreaFence());
}
@ApiOperation("区域/围栏详情")
@GetMapping("/areaFence/{id}")
public SingleResponse<BiAreaFenceCO> queryAreaFenceById(@PathVariable("id") Long id) {
return SingleResponse.of(personnelPositioningBiService.queryAreaFenceById(id));
}
}

View File

@ -0,0 +1,101 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.SyncLogServiceI;
import com.zcloud.personnel.positioning.dto.SyncLogAddCmd;
import com.zcloud.personnel.positioning.dto.SyncLogPageQry;
import com.zcloud.personnel.positioning.dto.SyncLogUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.SyncLogCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "人员定位同步日志")
@RequestMapping("/${application.gateway}/syncLog")
@RestController
@AllArgsConstructor
public class SyncLogController {
private final SyncLogServiceI syncLogService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<SyncLogCO> add(@Validated @RequestBody SyncLogAddCmd cmd) {
fillCorpinfo(cmd);
return syncLogService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<SyncLogCO> page(@RequestBody SyncLogPageQry qry) {
return syncLogService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<SyncLogCO> listAll() {
return MultiResponse.of(syncLogService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<SyncLogCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(syncLogService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
syncLogService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
syncLogService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody SyncLogUpdateCmd cmd) {
fillCorpinfo(cmd);
syncLogService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(SyncLogAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(SyncLogUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,101 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.SyncTaskServiceI;
import com.zcloud.personnel.positioning.dto.SyncTaskAddCmd;
import com.zcloud.personnel.positioning.dto.SyncTaskPageQry;
import com.zcloud.personnel.positioning.dto.SyncTaskUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.SyncTaskCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "人员定位同步任务")
@RequestMapping("/${application.gateway}/syncTask")
@RestController
@AllArgsConstructor
public class SyncTaskController {
private final SyncTaskServiceI syncTaskService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<SyncTaskCO> add(@Validated @RequestBody SyncTaskAddCmd cmd) {
fillCorpinfo(cmd);
return syncTaskService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<SyncTaskCO> page(@RequestBody SyncTaskPageQry qry) {
return syncTaskService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<SyncTaskCO> listAll() {
return MultiResponse.of(syncTaskService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<SyncTaskCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(syncTaskService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
syncTaskService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
syncTaskService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody SyncTaskUpdateCmd cmd) {
fillCorpinfo(cmd);
syncTaskService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(SyncTaskAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(SyncTaskUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,101 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.TerminalBindServiceI;
import com.zcloud.personnel.positioning.dto.TerminalBindAddCmd;
import com.zcloud.personnel.positioning.dto.TerminalBindPageQry;
import com.zcloud.personnel.positioning.dto.TerminalBindUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.TerminalBindCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "人员定位终端绑定记录")
@RequestMapping("/${application.gateway}/terminalBind")
@RestController
@AllArgsConstructor
public class TerminalBindController {
private final TerminalBindServiceI terminalBindService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<TerminalBindCO> add(@Validated @RequestBody TerminalBindAddCmd cmd) {
fillCorpinfo(cmd);
return terminalBindService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<TerminalBindCO> page(@RequestBody TerminalBindPageQry qry) {
return terminalBindService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<TerminalBindCO> listAll() {
return MultiResponse.of(terminalBindService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<TerminalBindCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(terminalBindService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
terminalBindService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
terminalBindService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody TerminalBindUpdateCmd cmd) {
fillCorpinfo(cmd);
terminalBindService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(TerminalBindAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(TerminalBindUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -0,0 +1,235 @@
package com.zcloud.personnel.positioning.web;
import com.zcloud.personnel.positioning.api.TerminalServiceI;
import com.zcloud.personnel.positioning.dto.TerminalAddCmd;
import com.zcloud.personnel.positioning.dto.TerminalAppCompanyPageQry;
import com.zcloud.personnel.positioning.dto.TerminalAppPageQry;
import com.zcloud.personnel.positioning.dto.TerminalAppSaveCmd;
import com.zcloud.personnel.positioning.dto.TerminalAppSyncCmd;
import com.zcloud.personnel.positioning.dto.TerminalAppUpdateCmd;
import com.zcloud.personnel.positioning.dto.TerminalCardCompanyOptionQry;
import com.zcloud.personnel.positioning.dto.TerminalCardCompanyPageQry;
import com.zcloud.personnel.positioning.dto.TerminalCardPageQry;
import com.zcloud.personnel.positioning.dto.TerminalCardPersonOptionQry;
import com.zcloud.personnel.positioning.dto.TerminalCardSaveCmd;
import com.zcloud.personnel.positioning.dto.TerminalCardSyncCmd;
import com.zcloud.personnel.positioning.dto.TerminalCardUpdateCmd;
import com.zcloud.personnel.positioning.dto.TerminalPageQry;
import com.zcloud.personnel.positioning.dto.TerminalUpdateCmd;
import com.zcloud.personnel.positioning.dto.clientobject.TerminalAppCO;
import com.zcloud.personnel.positioning.dto.clientobject.TerminalAppCompanyCO;
import com.zcloud.personnel.positioning.dto.clientobject.TerminalAppSyncCO;
import com.zcloud.personnel.positioning.dto.clientobject.TerminalCardCO;
import com.zcloud.personnel.positioning.dto.clientobject.TerminalCardCompanyCO;
import com.zcloud.personnel.positioning.dto.clientobject.TerminalCardCompanyOptionCO;
import com.zcloud.personnel.positioning.dto.clientobject.TerminalCardPersonOptionCO;
import com.zcloud.personnel.positioning.dto.clientobject.TerminalCardSyncCO;
import com.zcloud.personnel.positioning.dto.clientobject.TerminalCO;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* web-adapter
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Api(tags = "人员定位终端设备")
@RequestMapping("/${application.gateway}/terminal")
@RestController
@AllArgsConstructor
public class TerminalController {
private final TerminalServiceI terminalService;
@ApiOperation("APP terminal company page")
@PostMapping("/app/company/list")
public PageResponse<TerminalAppCompanyCO> appCompanyPage(@RequestBody TerminalAppCompanyPageQry qry) {
return terminalService.listAppCompanyPage(qry);
}
@ApiOperation("APP terminal company options")
@PostMapping("/app/company/options")
public MultiResponse<TerminalCardCompanyOptionCO> appCompanyOptions(@RequestBody(required = false) TerminalCardCompanyOptionQry qry) {
return MultiResponse.of(terminalService.listCardCompanyOptions(qry));
}
@ApiOperation("APP terminal page")
@PostMapping("/app/list")
public PageResponse<TerminalAppCO> appPage(@RequestBody TerminalAppPageQry qry) {
return terminalService.listAppPage(qry);
}
@ApiOperation("APP terminal sync from FindS")
@PostMapping("/app/sync")
public SingleResponse<TerminalAppSyncCO> appSync(@RequestBody(required = false) TerminalAppSyncCmd cmd) {
return SingleResponse.of(terminalService.syncApps(cmd));
}
@ApiOperation("APP terminal person options")
@PostMapping("/app/person/options")
public PageResponse<TerminalCardPersonOptionCO> appPersonOptions(@RequestBody TerminalCardPersonOptionQry qry) {
return terminalService.listCardPersonOptions(qry);
}
@ApiOperation("APP terminal add")
@PostMapping("/app/save")
public SingleResponse<TerminalAppCO> appAdd(@Validated @RequestBody TerminalAppSaveCmd cmd) {
fillCorpinfo(cmd);
return SingleResponse.of(terminalService.saveApp(cmd));
}
@ApiOperation("APP terminal edit")
@PutMapping("/app/edit")
public SingleResponse<TerminalAppCO> appEdit(@Validated @RequestBody TerminalAppUpdateCmd cmd) {
fillCorpinfo(cmd);
return SingleResponse.of(terminalService.updateApp(cmd));
}
@ApiOperation("APP terminal detail")
@GetMapping("/app/{id}")
public SingleResponse<TerminalAppCO> appDetail(@PathVariable("id") Long id) {
return SingleResponse.of(terminalService.queryAppById(id));
}
@ApiOperation("Terminal card company page")
@PostMapping("/card/company/list")
public PageResponse<TerminalCardCompanyCO> cardCompanyPage(@RequestBody TerminalCardCompanyPageQry qry) {
return terminalService.listCardCompanyPage(qry);
}
@ApiOperation("Terminal card company options")
@PostMapping("/card/company/options")
public MultiResponse<TerminalCardCompanyOptionCO> cardCompanyOptions(@RequestBody(required = false) TerminalCardCompanyOptionQry qry) {
return MultiResponse.of(terminalService.listCardCompanyOptions(qry));
}
@ApiOperation("Terminal card page")
@PostMapping("/card/list")
public PageResponse<TerminalCardCO> cardPage(@RequestBody TerminalCardPageQry qry) {
return terminalService.listCardPage(qry);
}
@ApiOperation("Terminal card sync from FindS")
@PostMapping("/card/sync")
public SingleResponse<TerminalCardSyncCO> cardSync(@RequestBody(required = false) TerminalCardSyncCmd cmd) {
return SingleResponse.of(terminalService.syncCards(cmd));
}
@ApiOperation("Terminal card person options")
@PostMapping("/card/person/options")
public PageResponse<TerminalCardPersonOptionCO> cardPersonOptions(@RequestBody TerminalCardPersonOptionQry qry) {
return terminalService.listCardPersonOptions(qry);
}
@ApiOperation("Terminal card add")
@PostMapping("/card/save")
public SingleResponse<TerminalCardCO> cardAdd(@Validated @RequestBody TerminalCardSaveCmd cmd) {
fillCorpinfo(cmd);
return SingleResponse.of(terminalService.saveCard(cmd));
}
@ApiOperation("Terminal card edit")
@PutMapping("/card/edit")
public SingleResponse<TerminalCardCO> cardEdit(@Validated @RequestBody TerminalCardUpdateCmd cmd) {
fillCorpinfo(cmd);
return SingleResponse.of(terminalService.updateCard(cmd));
}
@ApiOperation("Terminal card detail")
@GetMapping("/card/{id}")
public SingleResponse<TerminalCardCO> cardDetail(@PathVariable("id") Long id) {
return SingleResponse.of(terminalService.queryCardById(id));
}
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<TerminalCO> add(@Validated @RequestBody TerminalAddCmd cmd) {
fillCorpinfo(cmd);
return terminalService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<TerminalCO> page(@RequestBody TerminalPageQry qry) {
return terminalService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<TerminalCO> listAll() {
return MultiResponse.of(terminalService.listAll());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<TerminalCO> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(terminalService.queryById(id));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
terminalService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
terminalService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody TerminalUpdateCmd cmd) {
fillCorpinfo(cmd);
terminalService.edit(cmd);
return SingleResponse.buildSuccess();
}
private void fillCorpinfo(TerminalAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(TerminalUpdateCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(TerminalCardSaveCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (cmd.getCorpinfoId() == null && ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
private void fillCorpinfo(TerminalAppSaveCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
if (cmd.getCorpinfoId() == null && ssoUser != null && ssoUser.getCompanyId() != null) {
cmd.setCorpinfoId(ssoUser.getCompanyId());
}
}
}

View File

@ -1,78 +0,0 @@
package org.example.web;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import org.example.api.RiskPointServiceI;
import org.example.dto.RiskPointAddCmd;
import org.example.dto.RiskPointPageQry;
import org.example.dto.RiskPointUpdateCmd;
import org.example.dto.clientobject.RiskPointCo;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
/**
* @author lin
*/
@Api(tags = "风险点管理")
@RequestMapping("/risk/riskPoint")
@RestController
@AllArgsConstructor
public class RiskPointController {
private final RiskPointServiceI riskPointService;
@ApiOperation("新增")
@PostMapping("/save")
public SingleResponse<RiskPointCo> add(@Validated @RequestBody RiskPointAddCmd cmd) {
SSOUser ssoUser = AuthContext.getCurrentUser();
return riskPointService.add(cmd);
}
@ApiOperation("分页")
@PostMapping("/list")
public PageResponse<RiskPointCo> page(@RequestBody RiskPointPageQry qry) {
return riskPointService.listPage(qry);
}
@ApiOperation("所有数据")
@GetMapping("/listAll")
public MultiResponse<RiskPointCo> listAll() {
return MultiResponse.of(new ArrayList<RiskPointCo>());
}
@ApiOperation("详情")
@GetMapping("/{id}")
public SingleResponse<RiskPointCo> getInfoById(@PathVariable("id") Long id) {
return SingleResponse.of(new RiskPointCo());
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public Response remove(@PathVariable("id") Long id) {
riskPointService.remove(id);
return SingleResponse.buildSuccess();
}
@ApiOperation("删除多个")
@DeleteMapping("/ids")
public Response removeBatch(@RequestParam Long[] ids) {
riskPointService.removeBatch(ids);
return SingleResponse.buildSuccess();
}
@ApiOperation("修改")
@PutMapping("/edit")
public SingleResponse edit(@Validated @RequestBody RiskPointUpdateCmd riskPointUpdateCmd) {
riskPointService.edit(riskPointUpdateCmd);
return SingleResponse.buildSuccess();
}
}

View File

@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>zcloud_gbs_personnel_positioning</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
@ -14,11 +14,11 @@
<dependencies>
<dependency>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>web-client</artifactId>
</dependency>
<dependency>
<groupId>org.example</groupId>
<groupId>com.zcloud.personnel.positioning</groupId>
<artifactId>web-infrastructure</artifactId>
</dependency>

View File

@ -0,0 +1,44 @@
package com.zcloud.personnel.positioning.command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@Component
public class AfterCommitExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(AfterCommitExecutor.class);
private final TaskExecutor taskExecutor;
public AfterCommitExecutor(@Qualifier("findsSyncTaskExecutor") TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void execute(Runnable task) {
if (TransactionSynchronizationManager.isActualTransactionActive()
&& TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
submit(task);
}
});
return;
}
submit(task);
}
private void submit(Runnable task) {
taskExecutor.execute(() -> {
try {
task.run();
} catch (Exception e) {
LOGGER.warn("Execute FindS synchronization after local commit failed", e);
}
});
}
}

View File

@ -0,0 +1,38 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmDisposeRecordGateway;
import com.zcloud.personnel.positioning.domain.model.AlarmDisposeRecordE;
import com.zcloud.personnel.positioning.dto.AlarmDisposeRecordAddCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmDisposeRecordAddExe {
private final AlarmDisposeRecordGateway alarmDisposeRecordGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(AlarmDisposeRecordAddCmd cmd) {
AlarmDisposeRecordE alarmDisposeRecordE = new AlarmDisposeRecordE();
BeanUtils.copyProperties(cmd, alarmDisposeRecordE);
boolean res;
try {
res = alarmDisposeRecordGateway.add(alarmDisposeRecordE);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!res) {
throw new BizException("保存失败");
}
return true;
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmDisposeRecordGateway;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmDisposeRecordRemoveExe {
private final AlarmDisposeRecordGateway alarmDisposeRecordGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = alarmDisposeRecordGateway.deletedAlarmDisposeRecordById(id);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = alarmDisposeRecordGateway.deletedAlarmDisposeRecordByIds(ids);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
}

View File

@ -0,0 +1,32 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmDisposeRecordGateway;
import com.zcloud.personnel.positioning.domain.model.AlarmDisposeRecordE;
import com.zcloud.personnel.positioning.dto.AlarmDisposeRecordUpdateCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmDisposeRecordUpdateExe {
private final AlarmDisposeRecordGateway alarmDisposeRecordGateway;
@Transactional(rollbackFor = Exception.class)
public void execute(AlarmDisposeRecordUpdateCmd cmd) {
AlarmDisposeRecordE alarmDisposeRecordE = new AlarmDisposeRecordE();
BeanUtils.copyProperties(cmd, alarmDisposeRecordE);
boolean res = alarmDisposeRecordGateway.update(alarmDisposeRecordE);
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,38 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRecordGateway;
import com.zcloud.personnel.positioning.domain.model.AlarmRecordE;
import com.zcloud.personnel.positioning.dto.AlarmRecordAddCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRecordAddExe {
private final AlarmRecordGateway alarmRecordGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(AlarmRecordAddCmd cmd) {
AlarmRecordE alarmRecordE = new AlarmRecordE();
BeanUtils.copyProperties(cmd, alarmRecordE);
boolean res;
try {
res = alarmRecordGateway.add(alarmRecordE);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!res) {
throw new BizException("保存失败");
}
return true;
}
}

View File

@ -0,0 +1,569 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRecordDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRecordTargetDO;
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
import com.zcloud.personnel.positioning.persistence.dataobject.SyncTaskDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRecordRepository;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRecordTargetRepository;
import com.zcloud.personnel.positioning.persistence.repository.OpenapiConfigRepository;
import com.zcloud.personnel.positioning.persistence.repository.SyncTaskRepository;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* Incrementally pulls FindS alarm records into local snapshot tables.
*
* @Author fangjiakai
* @Date 2026-07-08
*/
@Component
@AllArgsConstructor
public class AlarmRecordFindsPullService {
private static final Logger LOGGER = LoggerFactory.getLogger(AlarmRecordFindsPullService.class);
private static final String LIST_API = "finds.alarm.list";
private static final String TASK_CODE_PREFIX = "FINDS_ALARM_PULL_";
private static final String TASK_CRON = "0 */2 * * * ?";
private static final int PULL_PAGE_SIZE = 100;
private static final int MAX_PULL_PAGE_COUNT = 200;
private static final Duration INITIAL_LOOKBACK = Duration.ofHours(24);
private static final Duration MAX_QUERY_WINDOW = Duration.ofHours(24);
private static final Duration MIN_SPLIT_WINDOW = Duration.ofMinutes(1);
private static final Duration OVERLAP = Duration.ofMinutes(5);
private final FindsOpenApiClient findsOpenApiClient;
private final OpenapiConfigRepository openapiConfigRepository;
private final AlarmRecordRepository alarmRecordRepository;
private final AlarmRecordTargetRepository alarmRecordTargetRepository;
private final SyncTaskRepository syncTaskRepository;
private final ObjectMapper objectMapper;
private final JdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate;
private final Set<String> inFlight = ConcurrentHashMap.newKeySet();
public int syncIncremental() {
List<OpenapiConfigDO> configs = openapiConfigRepository.list(baseConfigQuery());
int pulledCount = 0;
List<String> failures = new ArrayList<>();
for (OpenapiConfigDO config : configs) {
try {
pulledCount += syncConfigIncremental(config);
} catch (Exception e) {
failures.add(configIdentity(config) + ": " + safeMessage(e));
LOGGER.warn("Incrementally pull FindS alarms failed, config={}", configIdentity(config), e);
}
}
if (!failures.isEmpty()) {
throw new BizException("部分FindS告警增量同步失败: " + String.join("; ", failures));
}
return pulledCount;
}
private int syncConfigIncremental(OpenapiConfigDO config) {
String lockKey = configIdentity(config);
if (!inFlight.add(lockKey)) {
return 0;
}
SyncTaskDO task = null;
try {
task = getOrCreateTask(config);
if (Integer.valueOf(0).equals(task.getEnabled())) {
return 0;
}
LocalDateTime now = LocalDateTime.now();
LocalDateTime startTime = task.getLastExecuteTime() == null
? now.minus(INITIAL_LOOKBACK)
: task.getLastExecuteTime().minus(OVERLAP);
int pulledCount = 0;
while (startTime.isBefore(now)) {
LocalDateTime endTime = min(startTime.plus(MAX_QUERY_WINDOW), now);
int windowCount = refreshConfig(config, startTime, endTime);
pulledCount += windowCount;
updateTaskSuccess(task, endTime, windowCount);
startTime = endTime;
}
return pulledCount;
} catch (Exception e) {
if (task != null) {
updateTaskFailure(task, e);
}
throw e;
} finally {
inFlight.remove(lockKey);
}
}
private int refreshConfig(OpenapiConfigDO config, LocalDateTime startTime, LocalDateTime endTime) {
int pageNo = 1;
long loaded = 0;
long total = Long.MAX_VALUE;
while (loaded < total && pageNo <= MAX_PULL_PAGE_COUNT) {
Map<String, Object> request = new LinkedHashMap<>();
request.put("pageNo", pageNo);
request.put("pageSize", PULL_PAGE_SIZE);
request.put("startTime", toEpochMilli(startTime));
request.put("endTime", toEpochMilli(endTime));
JsonNode root = findsOpenApiClient.post(config, LIST_API, request);
JsonNode data = assertSuccess(root, LIST_API).path("data");
JsonNode rows = extractRows(data);
if (!rows.isArray() || rows.size() == 0) {
if (loaded > 0 && total != Long.MAX_VALUE && loaded < total) {
throw new BizException("FindS告警分页提前结束游标未推进");
}
total = loaded;
break;
}
transactionTemplate.execute(status -> {
for (JsonNode row : rows) {
upsertRecord(config, row);
}
return null;
});
loaded += rows.size();
total = totalValue(data, loaded);
pageNo++;
}
if (loaded < total) {
Duration window = Duration.between(startTime, endTime);
if (window.compareTo(MIN_SPLIT_WINDOW) <= 0) {
throw new BizException("FindS alarm volume exceeds page limit within minimum one-minute window");
}
LocalDateTime midpoint = startTime.plusNanos(window.toNanos() / 2);
LOGGER.info("Split FindS alarm pull window, config={}, start={}, midpoint={}, end={}",
configIdentity(config), startTime, midpoint, endTime);
int leftCount = refreshConfig(config, startTime, midpoint);
int rightCount = refreshConfig(config, midpoint, endTime);
return safeLongToInt((long) leftCount + rightCount);
}
return safeLongToInt(loaded);
}
@Transactional(rollbackFor = Exception.class)
public AlarmRecordDO upsertRecord(OpenapiConfigDO config, JsonNode row) {
Long findsAlarmId = longValue(row.path("id"));
String alarmNo = firstText(row.path("alarmNo"), null);
AlarmRecordDO record = queryExistingRecord(config, findsAlarmId, alarmNo);
boolean isNew = record == null;
if (isNew) {
record = new AlarmRecordDO();
}
record.setFindsAlarmId(findsAlarmId);
record.setAlarmNo(defaultIfBlank(alarmNo, findsAlarmId == null ? record.getAlarmNo() : "FA" + findsAlarmId));
record.setAlarmCode(firstText(row.path("alarmCode"), record.getAlarmCode()));
record.setAlarmName(defaultIfBlank(firstText(row.path("name"), record.getAlarmName()),
alarmNameOf(record.getAlarmCode())));
record.setDescription(firstText(row.path("description"), record.getDescription()));
record.setDisposeStatus(defaultIfBlank(firstText(row.path("disposeStatus"), record.getDisposeStatus()), "INIT"));
record.setDisposeDescription(firstText(row.path("disposeDescription"), record.getDisposeDescription()));
record.setDuration(longValue(row.path("duration")));
record.setStartTime(localDateTimeValue(row.path("startTime")));
record.setEndTime(localDateTimeValue(row.path("endTime")));
record.setStartFencesJson(jsonValue(row.path("startFences")));
record.setEndFencesJson(jsonValue(row.path("endFences")));
fillLocation(record, row.path("location"));
record.setCorpinfoId(config.getCorpinfoId() == null ? 0L : config.getCorpinfoId());
record.setCorpinfoName(defaultIfBlank(config.getCorpinfoName(), resolveCorpinfoName(config.getCorpinfoId())));
fillFirstTrack(record, row.path("trackInfoList"));
record.setTerminalNoListJson(jsonValue(row.path("terminalNoList")));
record.setSyncTime(LocalDateTime.now());
record.setRawJson(row.toString());
if (isNew) {
alarmRecordRepository.save(record);
} else {
alarmRecordRepository.updateById(record);
}
rebuildTargets(record, row);
return record;
}
private AlarmRecordDO queryExistingRecord(OpenapiConfigDO config, Long findsAlarmId, String alarmNo) {
QueryWrapper<AlarmRecordDO> queryWrapper = new QueryWrapper<>();
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
if (findsAlarmId != null) {
queryWrapper.eq("finds_alarm_id", findsAlarmId);
} else if (StringUtils.hasText(alarmNo)) {
queryWrapper.eq("alarm_no", alarmNo);
} else {
return null;
}
if (config.getCorpinfoId() != null) {
queryWrapper.eq("corpinfo_id", config.getCorpinfoId());
} else {
queryWrapper.and(wrapper -> wrapper.isNull("corpinfo_id").or().eq("corpinfo_id", 0));
}
queryWrapper.last("LIMIT 1");
return alarmRecordRepository.getOne(queryWrapper, false);
}
private void fillLocation(AlarmRecordDO record, JsonNode location) {
if (location == null || location.isMissingNode() || location.isNull()) {
return;
}
record.setLon(decimalValue(location.path("lon")));
record.setLat(decimalValue(location.path("lat")));
record.setAlt(decimalValue(location.path("alt")));
record.setSpeed(decimalValue(location.path("speed")));
record.setDirection(decimalValue(location.path("direction")));
record.setGt(localDateTimeValue(location.path("gt")));
}
private void fillFirstTrack(AlarmRecordDO record, JsonNode trackInfoList) {
JsonNode firstTrack = firstStaffTrack(trackInfoList);
if (firstTrack == null) {
return;
}
record.setFirstIdCardNo(firstText(firstTrack.path("idCardNo"), record.getFirstIdCardNo()));
record.setFirstStaffNoSnapshot(firstText(firstTrack.path("trackNo"), record.getFirstStaffNoSnapshot()));
record.setFirstStaffNameSnapshot(firstText(firstTrack.path("trackName"), record.getFirstStaffNameSnapshot()));
}
private JsonNode firstStaffTrack(JsonNode trackInfoList) {
if (!trackInfoList.isArray() || trackInfoList.size() == 0) {
return null;
}
for (JsonNode item : trackInfoList) {
if ("STAFF".equalsIgnoreCase(item.path("trackType").asText())) {
return item;
}
}
return trackInfoList.get(0);
}
private void rebuildTargets(AlarmRecordDO record, JsonNode row) {
QueryWrapper<AlarmRecordTargetDO> removeWrapper = new QueryWrapper<>();
removeWrapper.eq("alarm_record_id", record.getId());
alarmRecordTargetRepository.remove(removeWrapper);
JsonNode trackInfoList = row.path("trackInfoList");
if (!trackInfoList.isArray()) {
return;
}
for (int i = 0; i < trackInfoList.size(); i++) {
JsonNode track = trackInfoList.get(i);
AlarmRecordTargetDO target = new AlarmRecordTargetDO();
target.setAlarmRecordId(record.getId());
target.setCorpinfoId(record.getCorpinfoId());
target.setCorpinfoName(record.getCorpinfoName());
target.setTrackId(longValue(track.path("trackId")));
target.setTrackType(firstText(track.path("trackType"), null));
target.setTrackNo(firstText(track.path("trackNo"), null));
target.setTrackName(firstText(track.path("trackName"), null));
target.setTerminalNo(terminalNoAt(row.path("terminalNoList"), i));
target.setIdCardNo(firstText(track.path("idCardNo"), null));
if ("STAFF".equalsIgnoreCase(target.getTrackType())) {
target.setStaffNoSnapshot(target.getTrackNo());
}
alarmRecordTargetRepository.save(target);
}
}
private SyncTaskDO getOrCreateTask(OpenapiConfigDO config) {
String taskCode = TASK_CODE_PREFIX + configIdentity(config);
QueryWrapper<SyncTaskDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("task_code", taskCode);
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
queryWrapper.last("LIMIT 1");
SyncTaskDO task = syncTaskRepository.getOne(queryWrapper, false);
if (task != null) {
return task;
}
task = new SyncTaskDO();
task.setTaskCode(taskCode);
task.setTaskName("FindS告警增量拉取-" + configIdentity(config));
task.setBizType("ALARM");
task.setCorpinfoId(config.getCorpinfoId() == null ? 0L : config.getCorpinfoId());
task.setCorpinfoName(config.getCorpinfoName());
task.setSyncDirection("PULL");
task.setCronExpr(TASK_CRON);
task.setEnabled(1);
task.setLastStatus("INIT");
task.setLastMessage("等待首次执行");
syncTaskRepository.save(task);
return task;
}
private void updateTaskSuccess(SyncTaskDO task, LocalDateTime cursor, int windowCount) {
task.setLastExecuteTime(cursor);
task.setNextExecuteTime(LocalDateTime.now().plusMinutes(2));
task.setLastStatus("SUCCESS");
task.setLastMessage("游标已推进至 " + cursor + ",本窗口拉取 " + windowCount + " 条");
syncTaskRepository.updateById(task);
}
private void updateTaskFailure(SyncTaskDO task, Exception e) {
task.setNextExecuteTime(LocalDateTime.now().plusMinutes(2));
task.setLastStatus("FAIL");
task.setLastMessage(safeMessage(e));
syncTaskRepository.updateById(task);
}
private String configIdentity(OpenapiConfigDO config) {
if (config.getId() != null) {
return String.valueOf(config.getId());
}
return StringUtils.hasText(config.getConfigCode()) ? config.getConfigCode() : "DEFAULT";
}
private String safeMessage(Throwable e) {
String message = e == null ? null : e.getMessage();
if (!StringUtils.hasText(message)) {
message = e == null ? "FindS alarm incremental synchronization failed" : e.getClass().getSimpleName();
}
return message.length() > 1000 ? message.substring(0, 1000) : message;
}
private LocalDateTime min(LocalDateTime left, LocalDateTime right) {
return left.isBefore(right) ? left : right;
}
private int safeLongToInt(long value) {
return value > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) Math.max(value, 0);
}
private QueryWrapper<OpenapiConfigDO> baseConfigQuery() {
QueryWrapper<OpenapiConfigDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("enabled", 1);
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
queryWrapper.orderByDesc("update_time");
return queryWrapper;
}
private JsonNode assertSuccess(JsonNode root, String apiName) {
if (root == null || !"200".equals(root.path("code").asText())) {
String msg = root == null ? "" : root.path("msg").asText();
String detail = root == null ? "" : root.path("detail").asText();
throw new BizException("FindS OpenAPI call failed: " + apiName + ", " + msg + " " + detail);
}
return root;
}
private JsonNode extractRows(JsonNode data) {
if (data == null || data.isMissingNode() || data.isNull()) {
return objectMapper.createArrayNode();
}
if (data.isArray()) {
return data;
}
JsonNode direct = firstArray(data.path("data"), data.path("rows"), data.path("records"), data.path("list"));
if (direct != null) {
return direct;
}
JsonNode nested = firstArray(data.path("data").path("data"), data.path("data").path("rows"));
return nested == null ? objectMapper.createArrayNode() : nested;
}
private JsonNode firstArray(JsonNode... nodes) {
for (JsonNode node : nodes) {
if (node != null && node.isArray()) {
return node;
}
}
return null;
}
private long totalValue(JsonNode data, long defaultValue) {
for (String field : new String[]{"total", "totalCount", "count"}) {
Long value = longValue(data.path(field));
if (value != null) {
return value;
}
}
return defaultValue;
}
private long toEpochMilli(LocalDateTime value) {
return value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
private Long longValue(JsonNode node) {
if (node == null || node.isMissingNode() || node.isNull()) {
return null;
}
if (node.isNumber()) {
return node.asLong();
}
if (StringUtils.hasText(node.asText())) {
try {
return Long.valueOf(node.asText());
} catch (NumberFormatException ignored) {
return null;
}
}
return null;
}
private BigDecimal decimalValue(JsonNode node) {
if (node == null || node.isMissingNode() || node.isNull() || !StringUtils.hasText(node.asText())) {
return null;
}
try {
return new BigDecimal(node.asText());
} catch (NumberFormatException ignored) {
return null;
}
}
private LocalDateTime localDateTimeValue(JsonNode node) {
if (node == null || node.isMissingNode() || node.isNull() || !StringUtils.hasText(node.asText())) {
return null;
}
Long epoch = longValue(node);
if (epoch != null) {
long millis = epoch > 9999999999L ? epoch : epoch * 1000;
return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault());
}
String value = node.asText().trim();
try {
return OffsetDateTime.parse(value).toLocalDateTime();
} catch (DateTimeParseException ignored) {
}
try {
return LocalDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME);
} catch (DateTimeParseException ignored) {
}
for (DateTimeFormatter formatter : new DateTimeFormatter[]{
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
}) {
try {
return LocalDateTime.parse(value, formatter);
} catch (DateTimeParseException ignored) {
}
}
return null;
}
private String jsonValue(JsonNode node) {
if (node == null || node.isMissingNode() || node.isNull()) {
return null;
}
try {
return objectMapper.writeValueAsString(node);
} catch (IOException e) {
return null;
}
}
private String terminalNoAt(JsonNode terminalNoList, int index) {
if (terminalNoList == null || terminalNoList.isMissingNode() || terminalNoList.isNull()) {
return null;
}
if (terminalNoList.isArray()) {
if (terminalNoList.size() == 1) {
return terminalNoList.get(0).asText();
}
if (index >= 0 && index < terminalNoList.size()) {
return terminalNoList.get(index).asText();
}
List<String> values = new ArrayList<>();
for (JsonNode item : terminalNoList) {
if (StringUtils.hasText(item.asText())) {
values.add(item.asText());
}
}
return values.stream().collect(Collectors.joining(","));
}
return terminalNoList.asText();
}
private String resolveCorpinfoName(Long corpinfoId) {
if (corpinfoId == null) {
return null;
}
try {
List<String> names = jdbcTemplate.queryForList(
"SELECT corp_name FROM corp_info WHERE CAST(corpinfo_id AS CHAR) = ? "
+ "AND (delete_enum IS NULL OR delete_enum = 'FALSE') LIMIT 1",
String.class, String.valueOf(corpinfoId));
return names.isEmpty() ? null : names.get(0);
} catch (Exception ignored) {
return null;
}
}
private String normalizeAlarmCode(String value) {
if (!StringUtils.hasText(value)) {
return null;
}
String code = value.trim();
if ("进入围栏告警".equals(code) || "禁入围栏告警".equals(code)) {
return "CROSS_IN";
}
if ("离开围栏".equals(code) || "离开围栏告警".equals(code) || "区域越界告警".equals(code)) {
return "CROSS_OUT";
}
if ("超时停留告警".equals(code)) {
return "RETENTION";
}
if ("脱岗告警".equals(code)) {
return "LACKED";
}
if ("停留告警".equals(code)) {
return "STAY";
}
if ("碰撞告警".equals(code)) {
return "CRASH";
}
return code;
}
private String alarmNameOf(String alarmCode) {
if ("CROSS_IN".equals(alarmCode)) {
return "进入围栏告警";
}
if ("CROSS_OUT".equals(alarmCode)) {
return "离开围栏告警";
}
if ("RETENTION".equals(alarmCode)) {
return "超时停留告警";
}
if ("LACKED".equals(alarmCode)) {
return "脱岗告警";
}
if ("STAY".equals(alarmCode)) {
return "停留告警";
}
if ("CRASH".equals(alarmCode)) {
return "碰撞告警";
}
return alarmCode;
}
private String firstText(JsonNode node, String defaultValue) {
return node != null && !node.isMissingNode() && !node.isNull() && StringUtils.hasText(node.asText())
? node.asText()
: defaultValue;
}
private String defaultIfBlank(String value, String defaultValue) {
return StringUtils.hasText(value) ? value : defaultValue;
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRecordGateway;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRecordRemoveExe {
private final AlarmRecordGateway alarmRecordGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = alarmRecordGateway.deletedAlarmRecordById(id);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = alarmRecordGateway.deletedAlarmRecordByIds(ids);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
}

View File

@ -0,0 +1,38 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRecordTargetGateway;
import com.zcloud.personnel.positioning.domain.model.AlarmRecordTargetE;
import com.zcloud.personnel.positioning.dto.AlarmRecordTargetAddCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRecordTargetAddExe {
private final AlarmRecordTargetGateway alarmRecordTargetGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(AlarmRecordTargetAddCmd cmd) {
AlarmRecordTargetE alarmRecordTargetE = new AlarmRecordTargetE();
BeanUtils.copyProperties(cmd, alarmRecordTargetE);
boolean res;
try {
res = alarmRecordTargetGateway.add(alarmRecordTargetE);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!res) {
throw new BizException("保存失败");
}
return true;
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRecordTargetGateway;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRecordTargetRemoveExe {
private final AlarmRecordTargetGateway alarmRecordTargetGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = alarmRecordTargetGateway.deletedAlarmRecordTargetById(id);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = alarmRecordTargetGateway.deletedAlarmRecordTargetByIds(ids);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
}

View File

@ -0,0 +1,32 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRecordTargetGateway;
import com.zcloud.personnel.positioning.domain.model.AlarmRecordTargetE;
import com.zcloud.personnel.positioning.dto.AlarmRecordTargetUpdateCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRecordTargetUpdateExe {
private final AlarmRecordTargetGateway alarmRecordTargetGateway;
@Transactional(rollbackFor = Exception.class)
public void execute(AlarmRecordTargetUpdateCmd cmd) {
AlarmRecordTargetE alarmRecordTargetE = new AlarmRecordTargetE();
BeanUtils.copyProperties(cmd, alarmRecordTargetE);
boolean res = alarmRecordTargetGateway.update(alarmRecordTargetE);
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,32 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRecordGateway;
import com.zcloud.personnel.positioning.domain.model.AlarmRecordE;
import com.zcloud.personnel.positioning.dto.AlarmRecordUpdateCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRecordUpdateExe {
private final AlarmRecordGateway alarmRecordGateway;
@Transactional(rollbackFor = Exception.class)
public void execute(AlarmRecordUpdateCmd cmd) {
AlarmRecordE alarmRecordE = new AlarmRecordE();
BeanUtils.copyProperties(cmd, alarmRecordE);
boolean res = alarmRecordGateway.update(alarmRecordE);
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,40 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.zcloud.personnel.positioning.dto.AlarmRuleAddCmd;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRuleAddExe {
private final AlarmRuleRepository alarmRuleRepository;
private final AlarmRuleConfigPersistence alarmRuleConfigPersistence;
private final FindsPendingSyncService findsPendingSyncService;
@Transactional(rollbackFor = Exception.class)
public boolean execute(AlarmRuleAddCmd cmd) {
AlarmRuleDO alarmRuleDO = new AlarmRuleDO();
BeanUtils.copyProperties(cmd, alarmRuleDO);
alarmRuleConfigPersistence.normalizeRule(alarmRuleDO);
alarmRuleDO.setSyncStatus(FindsPendingSyncService.STATUS_WAIT);
alarmRuleDO.setSyncToken(FindsSyncToken.next());
alarmRuleRepository.save(alarmRuleDO);
if (alarmRuleDO.getId() == null) {
throw new BizException("保存失败");
}
alarmRuleConfigPersistence.saveNewChildren(alarmRuleDO, cmd.getFenceRelList(), cmd.getRuleItemList());
findsPendingSyncService.scheduleRule(alarmRuleDO.getId());
return true;
}
}

View File

@ -0,0 +1,268 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zcloud.personnel.positioning.dto.AlarmRuleFenceRelAddCmd;
import com.zcloud.personnel.positioning.dto.AlarmRuleItemAddCmd;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleFenceRelDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleItemDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleFenceRelRepository;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleItemRepository;
import com.zcloud.personnel.positioning.persistence.repository.FenceRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* Page-facing persistence helpers for alarm rule configuration.
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRuleConfigPersistence {
public static final String ATTR_COMPANY_AREA = "COMPANY_AREA";
public static final String ATTR_FENCE = "FENCE";
public static final String STATUS_ENABLED = "ENABLED";
public static final String STATUS_DISABLED = "DISABLED";
public static final String REL_EFFECTIVE = "EFFECTIVE";
public static final String STRATEGY_SPECIFIED = "SPECIFIED";
public static final String FENCE_SCOPE_COMPANY_AREA = "COMPANY_AREA";
private final AlarmRuleFenceRelRepository alarmRuleFenceRelRepository;
private final AlarmRuleItemRepository alarmRuleItemRepository;
private final FenceRepository fenceRepository;
public void normalizeRule(AlarmRuleDO rule) {
rule.setAlarmAttr(normalizeAlarmAttr(rule.getAlarmAttr()));
rule.setAlarmCode(normalizeAlarmCode(rule.getAlarmCode(), rule.getAlarmName()));
if (!StringUtils.hasText(rule.getAlarmName())) {
rule.setAlarmName(alarmNameOf(rule.getAlarmCode()));
}
rule.setRuleStatus(normalizeStatus(rule.getRuleStatus(), rule.getEnable()));
rule.setEnable(STATUS_ENABLED.equals(rule.getRuleStatus()) ? 1 : 0);
if (!StringUtils.hasText(rule.getRuleCode())) {
rule.setRuleCode("AR" + System.currentTimeMillis());
}
if (!StringUtils.hasText(rule.getRuleName())) {
rule.setRuleName(defaultIfBlank(rule.getAlarmName(), rule.getRuleCode()));
}
if (!StringUtils.hasText(rule.getCorpinfoName()) && StringUtils.hasText(rule.getOrgName())) {
rule.setCorpinfoName(rule.getOrgName());
}
if (!StringUtils.hasText(rule.getOrgName()) && StringUtils.hasText(rule.getCorpinfoName())) {
rule.setOrgName(rule.getCorpinfoName());
}
if (!StringUtils.hasText(rule.getTrackTypes())) {
rule.setTrackTypes("STAFF");
}
if (!StringUtils.hasText(rule.getFenceEffectStrategy())) {
rule.setFenceEffectStrategy(STRATEGY_SPECIFIED);
}
}
public void saveNewChildren(AlarmRuleDO rule, List<AlarmRuleFenceRelAddCmd> fenceRelList,
List<AlarmRuleItemAddCmd> ruleItemList) {
rebuildFenceRelations(rule, fenceRelList);
rebuildRuleItems(rule, ruleItemList, true);
}
public void rebuildFenceRelations(AlarmRuleDO rule, List<AlarmRuleFenceRelAddCmd> fenceRelList) {
QueryWrapper<AlarmRuleFenceRelDO> removeWrapper = new QueryWrapper<>();
removeWrapper.eq("rule_id", rule.getId());
alarmRuleFenceRelRepository.remove(removeWrapper);
if (fenceRelList == null || fenceRelList.isEmpty()) {
if (ATTR_COMPANY_AREA.equals(rule.getAlarmAttr())) {
saveCompanyAreaFenceRelations(rule);
return;
}
if (ATTR_FENCE.equals(rule.getAlarmAttr())) {
throw new BizException("电子围栏告警需要选择关联围栏");
}
return;
}
if (ATTR_FENCE.equals(rule.getAlarmAttr()) && fenceRelList.size() > 1) {
throw new BizException("Electronic fence alarm rule can only bind one fence");
}
for (AlarmRuleFenceRelAddCmd relCmd : fenceRelList) {
AlarmRuleFenceRelDO rel = new AlarmRuleFenceRelDO();
BeanUtils.copyProperties(relCmd, rel);
rel.setRuleId(rule.getId());
rel.setCorpinfoId(defaultLong(rel.getCorpinfoId(), rule.getCorpinfoId()));
rel.setCorpinfoName(defaultIfBlank(rel.getCorpinfoName(), rule.getCorpinfoName()));
rel.setRelType(defaultIfBlank(rel.getRelType(), REL_EFFECTIVE));
fillFenceSnapshot(rel);
alarmRuleFenceRelRepository.save(rel);
}
}
public void rebuildRuleItems(AlarmRuleDO rule, List<AlarmRuleItemAddCmd> ruleItemList, boolean createDefaultWhenEmpty) {
QueryWrapper<AlarmRuleItemDO> removeWrapper = new QueryWrapper<>();
removeWrapper.eq("rule_id", rule.getId());
alarmRuleItemRepository.remove(removeWrapper);
if (ruleItemList == null || ruleItemList.isEmpty()) {
if (createDefaultWhenEmpty) {
AlarmRuleItemDO item = new AlarmRuleItemDO();
item.setRuleId(rule.getId());
item.setCorpinfoId(rule.getCorpinfoId());
item.setCorpinfoName(rule.getCorpinfoName());
item.setLevel(1);
item.setEnable(rule.getEnable());
alarmRuleItemRepository.save(item);
}
return;
}
for (AlarmRuleItemAddCmd itemCmd : ruleItemList) {
AlarmRuleItemDO item = new AlarmRuleItemDO();
BeanUtils.copyProperties(itemCmd, item);
item.setRuleId(rule.getId());
item.setCorpinfoId(defaultLong(item.getCorpinfoId(), rule.getCorpinfoId()));
item.setCorpinfoName(defaultIfBlank(item.getCorpinfoName(), rule.getCorpinfoName()));
if (item.getLevel() == null) {
item.setLevel(1);
}
if (item.getEnable() == null) {
item.setEnable(rule.getEnable());
}
alarmRuleItemRepository.save(item);
}
}
public void syncRuleItemEnable(Long ruleId, Integer enable) {
QueryWrapper<AlarmRuleItemDO> wrapper = new QueryWrapper<>();
wrapper.eq("rule_id", ruleId);
List<AlarmRuleItemDO> items = alarmRuleItemRepository.list(wrapper);
for (AlarmRuleItemDO item : items) {
item.setEnable(enable);
alarmRuleItemRepository.updateById(item);
}
}
private void saveCompanyAreaFenceRelations(AlarmRuleDO rule) {
QueryWrapper<FenceDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("fence_scope", FENCE_SCOPE_COMPANY_AREA);
queryWrapper.eq("status", STATUS_ENABLED);
queryWrapper.eq("delete_enum", "FALSE");
if (rule.getCorpinfoId() != null) {
queryWrapper.eq("corpinfo_id", rule.getCorpinfoId());
} else if (StringUtils.hasText(rule.getCorpinfoName())) {
queryWrapper.eq("corpinfo_name", rule.getCorpinfoName());
} else {
throw new BizException("公司区域告警需要所属公司");
}
List<FenceDO> fences = fenceRepository.list(queryWrapper);
if (fences == null || fences.isEmpty()) {
throw new BizException("未找到该公司的公司区域围栏");
}
for (FenceDO fence : fences) {
AlarmRuleFenceRelDO rel = new AlarmRuleFenceRelDO();
rel.setRuleId(rule.getId());
rel.setFenceId(fence.getId());
rel.setFenceName(fence.getFenceName());
rel.setFindsFenceId(fence.getFindsFenceId());
rel.setRelType(REL_EFFECTIVE);
rel.setCorpinfoId(defaultLong(rule.getCorpinfoId(), fence.getCorpinfoId()));
rel.setCorpinfoName(defaultIfBlank(rule.getCorpinfoName(), fence.getCorpinfoName()));
alarmRuleFenceRelRepository.save(rel);
}
}
private void fillFenceSnapshot(AlarmRuleFenceRelDO rel) {
if (rel.getFenceId() == null) {
return;
}
FenceDO fence = fenceRepository.getById(rel.getFenceId());
if (fence == null) {
return;
}
rel.setFenceName(defaultIfBlank(rel.getFenceName(), fence.getFenceName()));
rel.setFindsFenceId(defaultLong(rel.getFindsFenceId(), fence.getFindsFenceId()));
rel.setCorpinfoId(defaultLong(rel.getCorpinfoId(), fence.getCorpinfoId()));
rel.setCorpinfoName(defaultIfBlank(rel.getCorpinfoName(), fence.getCorpinfoName()));
}
private String normalizeAlarmAttr(String value) {
if (!StringUtils.hasText(value)) {
throw new BizException("请选择告警属性");
}
String attr = value.trim();
if ("公司区域告警".equals(attr)) {
return ATTR_COMPANY_AREA;
}
if ("电子围栏告警".equals(attr)) {
return ATTR_FENCE;
}
return attr;
}
private String normalizeAlarmCode(String alarmCode, String alarmName) {
String value = StringUtils.hasText(alarmCode) ? alarmCode.trim() : alarmName;
if (!StringUtils.hasText(value)) {
throw new BizException("请选择告警类型");
}
if ("进入围栏告警".equals(value) || "禁入围栏告警".equals(value)) {
return "CROSS_IN";
}
if ("离开围栏告警".equals(value) || "区域越界告警".equals(value)) {
return "CROSS_OUT";
}
if ("超时停留告警".equals(value)) {
return "RETENTION";
}
if ("脱岗告警".equals(value)) {
return "LACKED";
}
return value.trim();
}
private String alarmNameOf(String alarmCode) {
if ("CROSS_IN".equals(alarmCode)) {
return "进入围栏告警";
}
if ("CROSS_OUT".equals(alarmCode)) {
return "离开围栏告警";
}
if ("RETENTION".equals(alarmCode)) {
return "超时停留告警";
}
if ("LACKED".equals(alarmCode)) {
return "脱岗告警";
}
if ("STAY".equals(alarmCode)) {
return "停留告警";
}
return alarmCode;
}
private String normalizeStatus(String ruleStatus, Integer enable) {
if (StringUtils.hasText(ruleStatus)) {
String status = ruleStatus.trim();
if ("启用".equals(status) || "1".equals(status)) {
return STATUS_ENABLED;
}
if ("停用".equals(status) || "0".equals(status)) {
return STATUS_DISABLED;
}
return status;
}
return enable != null && enable == 0 ? STATUS_DISABLED : STATUS_ENABLED;
}
private String defaultIfBlank(String value, String defaultValue) {
return StringUtils.hasText(value) ? value : defaultValue;
}
private Long defaultLong(Long value, Long defaultValue) {
return value == null ? defaultValue : value;
}
}

View File

@ -0,0 +1,38 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRuleFenceRelGateway;
import com.zcloud.personnel.positioning.domain.model.AlarmRuleFenceRelE;
import com.zcloud.personnel.positioning.dto.AlarmRuleFenceRelAddCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRuleFenceRelAddExe {
private final AlarmRuleFenceRelGateway alarmRuleFenceRelGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(AlarmRuleFenceRelAddCmd cmd) {
AlarmRuleFenceRelE alarmRuleFenceRelE = new AlarmRuleFenceRelE();
BeanUtils.copyProperties(cmd, alarmRuleFenceRelE);
boolean res;
try {
res = alarmRuleFenceRelGateway.add(alarmRuleFenceRelE);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!res) {
throw new BizException("保存失败");
}
return true;
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRuleFenceRelGateway;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRuleFenceRelRemoveExe {
private final AlarmRuleFenceRelGateway alarmRuleFenceRelGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = alarmRuleFenceRelGateway.deletedAlarmRuleFenceRelById(id);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = alarmRuleFenceRelGateway.deletedAlarmRuleFenceRelByIds(ids);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
}

View File

@ -0,0 +1,32 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRuleFenceRelGateway;
import com.zcloud.personnel.positioning.domain.model.AlarmRuleFenceRelE;
import com.zcloud.personnel.positioning.dto.AlarmRuleFenceRelUpdateCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRuleFenceRelUpdateExe {
private final AlarmRuleFenceRelGateway alarmRuleFenceRelGateway;
@Transactional(rollbackFor = Exception.class)
public void execute(AlarmRuleFenceRelUpdateCmd cmd) {
AlarmRuleFenceRelE alarmRuleFenceRelE = new AlarmRuleFenceRelE();
BeanUtils.copyProperties(cmd, alarmRuleFenceRelE);
boolean res = alarmRuleFenceRelGateway.update(alarmRuleFenceRelE);
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,440 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import com.zcloud.personnel.positioning.dto.AlarmRuleCompanyPageQry;
import com.zcloud.personnel.positioning.dto.AlarmRulePageQry;
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleFenceRelDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleItemDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleFenceRelRepository;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleItemRepository;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleRepository;
import com.zcloud.personnel.positioning.persistence.repository.FenceRepository;
import com.zcloud.personnel.positioning.persistence.repository.OpenapiConfigRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Pulls FindS alarm rules into local tables before page queries.
*
* @Author fangjiakai
* @Date 2026-07-08
*/
@Component
@AllArgsConstructor
public class AlarmRuleFindsPullService {
private static final String LIST_API = "finds.alarm.rule.list";
private static final String QUERY_API = "finds.alarm.rule.query";
private static final String STATUS_SUCCESS = "SUCCESS";
private static final int PULL_PAGE_SIZE = 100;
private static final int MAX_PULL_PAGE_COUNT = 20;
private final FindsOpenApiClient findsOpenApiClient;
private final OpenapiConfigRepository openapiConfigRepository;
private final AlarmRuleRepository alarmRuleRepository;
private final AlarmRuleFenceRelRepository alarmRuleFenceRelRepository;
private final AlarmRuleItemRepository alarmRuleItemRepository;
private final FenceRepository fenceRepository;
private final ObjectMapper objectMapper;
public void refreshForCompanyStats(AlarmRuleCompanyPageQry qry) {
Long companyId = qry == null ? null : qry.getEqCorpinfoId();
refreshConfigs(resolveConfigs(companyId), null);
}
public void refreshForList(AlarmRulePageQry qry) {
Long companyId = qry == null ? null : qry.getEqCorpinfoId();
String alarmCode = qry == null ? null : qry.getEqAlarmCode();
refreshConfigs(resolveConfigs(companyId), alarmCode);
}
public void refreshForListAll() {
refreshConfigs(resolveConfigs(null), null);
}
public void refreshForDetail(Long localRuleId) {
AlarmRuleDO local = alarmRuleRepository.getById(localRuleId);
if (local == null || local.getFindsRuleId() == null) {
return;
}
OpenapiConfigDO config = resolveConfigForRule(local);
if (config == null) {
return;
}
Map<String, Object> request = new LinkedHashMap<>();
request.put("id", local.getFindsRuleId());
JsonNode root = findsOpenApiClient.post(config, QUERY_API, request);
JsonNode data = assertSuccess(root, QUERY_API).path("data");
if (!data.isMissingNode() && !data.isNull()) {
upsertRule(config, data, true);
}
}
private void refreshConfigs(List<OpenapiConfigDO> configs, String alarmCode) {
for (OpenapiConfigDO config : configs) {
refreshConfig(config, alarmCode);
}
}
private void refreshConfig(OpenapiConfigDO config, String alarmCode) {
int pageNo = 1;
long loaded = 0;
long total = Long.MAX_VALUE;
while (loaded < total && pageNo <= MAX_PULL_PAGE_COUNT) {
Map<String, Object> request = new LinkedHashMap<>();
request.put("pageNo", pageNo);
request.put("pageSize", PULL_PAGE_SIZE);
if (StringUtils.hasText(alarmCode)) {
request.put("alarmCode", alarmCode);
}
JsonNode root = findsOpenApiClient.post(config, LIST_API, request);
JsonNode data = assertSuccess(root, LIST_API).path("data");
JsonNode rows = extractRows(data);
if (!rows.isArray() || rows.size() == 0) {
break;
}
for (JsonNode row : rows) {
upsertRule(config, row, false);
}
loaded += rows.size();
total = totalValue(data, loaded);
pageNo++;
}
}
private AlarmRuleDO upsertRule(OpenapiConfigDO config, JsonNode row, boolean refreshItems) {
Long findsRuleId = longValue(row.path("id"));
if (findsRuleId == null) {
return null;
}
AlarmRuleDO rule = queryByFindsRuleId(config, findsRuleId);
boolean isNew = rule == null;
if (isNew) {
rule = new AlarmRuleDO();
rule.setRuleCode("ARF" + findsRuleId);
}
rule.setFindsRuleId(findsRuleId);
rule.setRuleName(firstText(row.path("name"), rule.getRuleName()));
rule.setAlarmCode(firstText(row.path("alarmCode"), rule.getAlarmCode()));
rule.setAlarmName(defaultIfBlank(rule.getAlarmName(), alarmNameOf(rule.getAlarmCode())));
rule.setEnable(booleanValue(row.path("enable")) ? 1 : 0);
rule.setRuleStatus(rule.getEnable() == 1
? AlarmRuleConfigPersistence.STATUS_ENABLED
: AlarmRuleConfigPersistence.STATUS_DISABLED);
JsonNode scope = row.path("fenceEffectScope");
String strategy = firstText(scope.path("strategy"), rule.getFenceEffectStrategy());
rule.setFenceEffectStrategy(defaultIfBlank(strategy, AlarmRuleConfigPersistence.STRATEGY_SPECIFIED));
rule.setAlarmAttr(defaultIfBlank(rule.getAlarmAttr(), inferAlarmAttr(scope)));
rule.setTrackTypes(defaultIfBlank(joinTextArray(row.path("trackTypes")), rule.getTrackTypes()));
rule.setTrackTypes(defaultIfBlank(rule.getTrackTypes(), "STAFF"));
rule.setCrashType(firstText(row.path("crashType"), rule.getCrashType()));
rule.setCorpinfoId(config.getCorpinfoId());
rule.setCorpinfoName(config.getCorpinfoName());
rule.setOrgName(defaultIfBlank(rule.getOrgName(), config.getCorpinfoName()));
rule.setSyncStatus(STATUS_SUCCESS);
rule.setLastSyncTime(LocalDateTime.now());
rule.setRawJson(row.toString());
if (isNew) {
alarmRuleRepository.save(rule);
} else {
alarmRuleRepository.updateById(rule);
}
rebuildFenceRelations(rule, scope);
if (refreshItems && row.has("ruleItems")) {
rebuildRuleItems(rule, row.path("ruleItems"));
}
return rule;
}
private AlarmRuleDO queryByFindsRuleId(OpenapiConfigDO config, Long findsRuleId) {
QueryWrapper<AlarmRuleDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("finds_rule_id", findsRuleId);
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
if (config.getCorpinfoId() != null) {
queryWrapper.eq("corpinfo_id", config.getCorpinfoId());
} else {
queryWrapper.and(wrapper -> wrapper.isNull("corpinfo_id").or().eq("corpinfo_id", 0));
}
queryWrapper.last("LIMIT 1");
return alarmRuleRepository.getOne(queryWrapper, false);
}
private void rebuildFenceRelations(AlarmRuleDO rule, JsonNode scope) {
if (scope == null || scope.isMissingNode() || scope.isNull()) {
return;
}
QueryWrapper<AlarmRuleFenceRelDO> removeWrapper = new QueryWrapper<>();
removeWrapper.eq("rule_id", rule.getId());
alarmRuleFenceRelRepository.remove(removeWrapper);
saveFenceRelations(rule, scope.path("effectiveFence"), AlarmRuleConfigPersistence.REL_EFFECTIVE);
saveFenceRelations(rule, scope.path("ignoreFence"), "IGNORE");
}
private void saveFenceRelations(AlarmRuleDO rule, JsonNode ids, String relType) {
if (!ids.isArray()) {
return;
}
for (JsonNode idNode : ids) {
Long findsFenceId = longValue(idNode);
if (findsFenceId == null) {
continue;
}
AlarmRuleFenceRelDO rel = new AlarmRuleFenceRelDO();
rel.setRuleId(rule.getId());
rel.setFindsFenceId(findsFenceId);
rel.setRelType(relType);
rel.setCorpinfoId(rule.getCorpinfoId());
rel.setCorpinfoName(rule.getCorpinfoName());
fillLocalFence(rel);
alarmRuleFenceRelRepository.save(rel);
}
}
private void fillLocalFence(AlarmRuleFenceRelDO rel) {
QueryWrapper<FenceDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("finds_fence_id", rel.getFindsFenceId());
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
queryWrapper.last("LIMIT 1");
FenceDO fence = fenceRepository.getOne(queryWrapper, false);
if (fence != null) {
rel.setFenceId(fence.getId());
rel.setFenceName(fence.getFenceName());
}
}
private void rebuildRuleItems(AlarmRuleDO rule, JsonNode items) {
QueryWrapper<AlarmRuleItemDO> removeWrapper = new QueryWrapper<>();
removeWrapper.eq("rule_id", rule.getId());
alarmRuleItemRepository.remove(removeWrapper);
if (!items.isArray()) {
return;
}
for (JsonNode itemNode : items) {
AlarmRuleItemDO item = new AlarmRuleItemDO();
item.setRuleId(rule.getId());
item.setCorpinfoId(rule.getCorpinfoId());
item.setCorpinfoName(rule.getCorpinfoName());
item.setLevel(integerValue(itemNode.path("level")));
item.setEnable(booleanValue(itemNode.path("enable")) ? 1 : 0);
JsonNode alarmParam = itemNode.path("alarmParam");
item.setCrossDuration(longValue(alarmParam.path("crossDuration")));
item.setRetentionMin(decimalValue(alarmParam.path("retentionMin")));
item.setStayMin(integerValue(alarmParam.path("stayMin")));
item.setStaticDuration(integerValue(alarmParam.path("staticDuration")));
item.setOverSpeed(decimalValue(alarmParam.path("overSpeed")));
item.setGlobalOverSpeed(decimalValue(alarmParam.path("globalOverSpeed")));
item.setLowBattery(decimalValue(alarmParam.path("lowBattery")));
item.setCrowdedNum(integerValue(alarmParam.path("crowdedNum")));
item.setLackedNum(integerValue(alarmParam.path("lackedNum")));
item.setClusterDuration(integerValue(alarmParam.path("clusterDuration")));
item.setClusterQuantity(integerValue(alarmParam.path("clusterQuantity")));
item.setClusterRadius(decimalValue(alarmParam.path("clusterRadius")));
item.setCrashDistance(decimalValue(alarmParam.path("crashDistance")));
item.setCrashSpeed(decimalValue(alarmParam.path("crashSpeed")));
item.setAlarmParamJson(jsonValue(alarmParam));
item.setAlarmActionJson(jsonValue(itemNode.path("alarmAction")));
alarmRuleItemRepository.save(item);
}
}
private List<OpenapiConfigDO> resolveConfigs(Long requestedCorpinfoId) {
SSOUser ssoUser = AuthContext.getCurrentUser();
Long corpinfoId = requestedCorpinfoId != null
? requestedCorpinfoId
: (ssoUser == null ? null : ssoUser.getCompanyId());
QueryWrapper<OpenapiConfigDO> queryWrapper = baseConfigQuery();
if (corpinfoId != null) {
queryWrapper.eq("corpinfo_id", corpinfoId);
}
List<OpenapiConfigDO> configs = openapiConfigRepository.list(queryWrapper);
if ((configs == null || configs.isEmpty()) && corpinfoId != null) {
QueryWrapper<OpenapiConfigDO> defaultWrapper = baseConfigQuery();
defaultWrapper.and(wrapper -> wrapper.isNull("corpinfo_id").or().eq("corpinfo_id", 0));
configs = openapiConfigRepository.list(defaultWrapper);
if (configs != null) {
for (OpenapiConfigDO config : configs) {
config.setCorpinfoId(corpinfoId);
}
}
}
if (configs == null || configs.isEmpty()) {
return Collections.emptyList();
}
return configs;
}
private OpenapiConfigDO resolveConfigForRule(AlarmRuleDO rule) {
List<OpenapiConfigDO> configs = resolveConfigs(rule.getCorpinfoId());
return configs.isEmpty() ? null : configs.get(0);
}
private QueryWrapper<OpenapiConfigDO> baseConfigQuery() {
QueryWrapper<OpenapiConfigDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("enabled", 1);
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
queryWrapper.orderByDesc("update_time");
return queryWrapper;
}
private JsonNode assertSuccess(JsonNode root, String apiName) {
if (root == null || !"200".equals(root.path("code").asText())) {
String msg = root == null ? "" : root.path("msg").asText();
String detail = root == null ? "" : root.path("detail").asText();
throw new BizException("FindS接口调用失败" + apiName + "" + msg + " " + detail);
}
return root;
}
private JsonNode extractRows(JsonNode data) {
if (data == null || data.isMissingNode() || data.isNull()) {
return objectMapper.createArrayNode();
}
if (data.isArray()) {
return data;
}
JsonNode direct = firstArray(data.path("data"), data.path("rows"), data.path("records"), data.path("list"));
if (direct != null) {
return direct;
}
JsonNode nested = firstArray(data.path("data").path("data"), data.path("data").path("rows"));
return nested == null ? objectMapper.createArrayNode() : nested;
}
private JsonNode firstArray(JsonNode... nodes) {
for (JsonNode node : nodes) {
if (node != null && node.isArray()) {
return node;
}
}
return null;
}
private long totalValue(JsonNode data, long defaultValue) {
for (String field : new String[]{"total", "totalCount", "count"}) {
Long value = longValue(data.path(field));
if (value != null) {
return value;
}
}
return defaultValue;
}
private String inferAlarmAttr(JsonNode scope) {
if (scope != null && scope.path("effectiveFence").isArray() && scope.path("effectiveFence").size() > 0) {
return AlarmRuleConfigPersistence.ATTR_FENCE;
}
return AlarmRuleConfigPersistence.ATTR_COMPANY_AREA;
}
private String joinTextArray(JsonNode node) {
if (!node.isArray()) {
return null;
}
List<String> values = new ArrayList<>();
for (JsonNode item : node) {
if (StringUtils.hasText(item.asText())) {
values.add(item.asText());
}
}
return values.stream().collect(Collectors.joining(","));
}
private String alarmNameOf(String alarmCode) {
if ("CROSS_IN".equals(alarmCode)) {
return "进入围栏告警";
}
if ("CROSS_OUT".equals(alarmCode)) {
return "离开围栏告警";
}
if ("RETENTION".equals(alarmCode)) {
return "超时停留告警";
}
if ("LACKED".equals(alarmCode)) {
return "脱岗告警";
}
if ("STAY".equals(alarmCode)) {
return "停留告警";
}
if ("CRASH".equals(alarmCode)) {
return "碰撞告警";
}
return alarmCode;
}
private boolean booleanValue(JsonNode node) {
return node == null || node.isMissingNode() || node.isNull() || node.asBoolean();
}
private Long longValue(JsonNode node) {
if (node == null || node.isMissingNode() || node.isNull()) {
return null;
}
if (node.isNumber()) {
return node.asLong();
}
if (StringUtils.hasText(node.asText())) {
try {
return Long.valueOf(node.asText());
} catch (NumberFormatException ignored) {
return null;
}
}
return null;
}
private Integer integerValue(JsonNode node) {
Long value = longValue(node);
return value == null ? null : value.intValue();
}
private BigDecimal decimalValue(JsonNode node) {
if (node == null || node.isMissingNode() || node.isNull() || !StringUtils.hasText(node.asText())) {
return null;
}
try {
return new BigDecimal(node.asText());
} catch (NumberFormatException ignored) {
return null;
}
}
private String jsonValue(JsonNode node) {
if (node == null || node.isMissingNode() || node.isNull()) {
return null;
}
try {
return objectMapper.writeValueAsString(node);
} catch (IOException e) {
return null;
}
}
private String firstText(JsonNode node, String defaultValue) {
return node != null && !node.isMissingNode() && !node.isNull() && StringUtils.hasText(node.asText())
? node.asText()
: defaultValue;
}
private String defaultIfBlank(String value, String defaultValue) {
return StringUtils.hasText(value) ? value : defaultValue;
}
}

View File

@ -0,0 +1,382 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
import com.zcloud.personnel.positioning.integration.finds.OpenapiConfigCache;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleFenceRelDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleItemDO;
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleFenceRelRepository;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleItemRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Synchronizes local alarm rule configuration to FindS.
*
* @Author fangjiakai
* @Date 2026-07-08
*/
@Component
@AllArgsConstructor
public class AlarmRuleFindsSyncService {
private static final String CREATE_API = "finds.alarm.rule.create";
private static final String UPDATE_API = "finds.alarm.rule.update";
private static final String DELETE_API = "finds.alarm.rule.delete";
private static final String STATUS_SUCCESS = "SUCCESS";
private static final String REL_IGNORE = "IGNORE";
private final FindsOpenApiClient findsOpenApiClient;
private final OpenapiConfigCache openapiConfigCache;
private final FindsSyncStateService findsSyncStateService;
private final AlarmRuleFenceRelRepository alarmRuleFenceRelRepository;
private final AlarmRuleItemRepository alarmRuleItemRepository;
private final ObjectMapper objectMapper;
private final FindsEntityLookupService findsEntityLookupService;
public void create(AlarmRuleDO rule) {
JsonNode response = createWithRecovery(enabledConfig(rule), rule);
markSyncSuccess(rule, response);
}
public void createOrUpdate(AlarmRuleDO rule) {
OpenapiConfigDO config = enabledConfig(rule);
if (rule.getFindsRuleId() == null) {
Long existingId = findsEntityLookupService.findAlarmRuleId(config, rule);
if (existingId != null) {
rule.setFindsRuleId(existingId);
findsSyncStateService.recordAlarmRuleMapping(rule.getId(), existingId);
}
}
assertCurrent(rule);
JsonNode response = rule.getFindsRuleId() == null
? createWithRecovery(config, rule)
: updateOrRecreate(config, rule);
markSyncSuccess(rule, response);
}
public void delete(AlarmRuleDO rule) {
if (rule == null || rule.getFindsRuleId() == null) {
return;
}
Map<String, Object> request = new LinkedHashMap<>();
request.put("id", String.valueOf(rule.getFindsRuleId()));
try {
JsonNode response = findsOpenApiClient.post(enabledConfig(rule), DELETE_API, request);
if (!findsEntityLookupService.isNotFound(response)) {
assertSuccess(response, DELETE_API);
}
} catch (RuntimeException e) {
if (!findsEntityLookupService.isNotFound(e)) {
throw e;
}
}
}
private OpenapiConfigDO enabledConfig(AlarmRuleDO rule) {
OpenapiConfigDO config = openapiConfigCache.getEnabledConfig(rule.getCorpinfoId());
if (config == null) {
throw new BizException("未配置启用的FindS OpenAPI配置");
}
return config;
}
private JsonNode createWithRecovery(OpenapiConfigDO config, AlarmRuleDO rule) {
RuntimeException createError = null;
try {
JsonNode response = findsOpenApiClient.post(config, CREATE_API, buildSaveRequest(rule, true));
if (isSuccess(response)) {
applyCreatedRuleId(rule, response);
findsSyncStateService.recordAlarmRuleMapping(rule.getId(), rule.getFindsRuleId());
return response;
}
createError = apiError(response, CREATE_API);
} catch (RuntimeException e) {
createError = e;
}
Long recoveredId = tryFindAlarmRuleId(config, rule);
if (recoveredId == null) {
throw createError;
}
rule.setFindsRuleId(recoveredId);
findsSyncStateService.recordAlarmRuleMapping(rule.getId(), recoveredId);
assertCurrent(rule);
return updateOrRecreate(config, rule, false);
}
private JsonNode updateOrRecreate(OpenapiConfigDO config, AlarmRuleDO rule) {
return updateOrRecreate(config, rule, true);
}
private JsonNode updateOrRecreate(OpenapiConfigDO config, AlarmRuleDO rule, boolean recreateOnNotFound) {
Long oldFindsId = rule.getFindsRuleId();
Map<String, Object> request = buildSaveRequest(rule, false);
request.put("id", oldFindsId);
try {
JsonNode response = findsOpenApiClient.post(config, UPDATE_API, request);
if (!findsEntityLookupService.isNotFound(response)) {
assertSuccess(response, UPDATE_API);
Long responseRuleId = responseId(response.path("data"));
if (responseRuleId != null) {
rule.setFindsRuleId(responseRuleId);
}
return response;
}
} catch (RuntimeException e) {
if (!findsEntityLookupService.isNotFound(e)) {
throw e;
}
}
if (!findsSyncStateService.clearAlarmRuleMapping(rule, oldFindsId)) {
throw new BizException("FindS alarm rule synchronization snapshot is stale");
}
rule.setFindsRuleId(null);
if (!recreateOnNotFound) {
throw new BizException("FindS alarm rule recovery failed: recovered id does not exist");
}
return createWithRecovery(config, rule);
}
private void assertCurrent(AlarmRuleDO rule) {
if (!findsSyncStateService.isCurrentAlarmRule(rule)) {
throw new BizException("FindS alarm rule synchronization snapshot is stale");
}
}
private Long tryFindAlarmRuleId(OpenapiConfigDO config, AlarmRuleDO rule) {
try {
return findsEntityLookupService.findAlarmRuleId(config, rule);
} catch (RuntimeException ignored) {
return null;
}
}
private void applyCreatedRuleId(AlarmRuleDO rule, JsonNode response) {
Long findsRuleId = responseId(response.path("data"));
if (findsRuleId == null) {
throw new BizException("FindS告警规则创建成功但未返回规则ID");
}
rule.setFindsRuleId(findsRuleId);
}
private Map<String, Object> buildSaveRequest(AlarmRuleDO rule, boolean includeAlarmCode) {
if (!StringUtils.hasText(rule.getRuleName())) {
throw new BizException("同步FindS失败告警规则名称不能为空");
}
Map<String, Object> request = new LinkedHashMap<>();
request.put("name", rule.getRuleName());
request.put("enable", enabled(rule.getEnable()));
if (includeAlarmCode) {
if (!StringUtils.hasText(rule.getAlarmCode())) {
throw new BizException("同步FindS失败告警类型不能为空");
}
request.put("alarmCode", rule.getAlarmCode());
}
request.put("trackTypes", parseTrackTypes(rule.getTrackTypes()));
request.put("fenceEffectScope", buildFenceEffectScope(rule));
request.put("ruleItems", buildRuleItems(rule));
if (StringUtils.hasText(rule.getCrashType())) {
request.put("crashType", rule.getCrashType());
}
return request;
}
private Map<String, Object> buildFenceEffectScope(AlarmRuleDO rule) {
String strategy = StringUtils.hasText(rule.getFenceEffectStrategy())
? rule.getFenceEffectStrategy()
: AlarmRuleConfigPersistence.STRATEGY_SPECIFIED;
Map<String, Object> scope = new LinkedHashMap<>();
scope.put("strategy", strategy);
if (!AlarmRuleConfigPersistence.STRATEGY_SPECIFIED.equals(strategy)) {
return scope;
}
List<AlarmRuleFenceRelDO> rels = listFenceRelations(rule.getId());
List<Long> effectiveFence = new ArrayList<>();
List<Long> ignoreFence = new ArrayList<>();
for (AlarmRuleFenceRelDO rel : rels) {
if (rel.getFindsFenceId() == null) {
throw new BizException("同步FindS失败关联围栏未同步到FindS围栏" + rel.getFenceName());
}
if (REL_IGNORE.equals(rel.getRelType())) {
ignoreFence.add(rel.getFindsFenceId());
} else {
effectiveFence.add(rel.getFindsFenceId());
}
}
if (effectiveFence.isEmpty() && ignoreFence.isEmpty()) {
throw new BizException("同步FindS失败未找到可同步的关联围栏");
}
if (!effectiveFence.isEmpty()) {
scope.put("effectiveFence", effectiveFence);
}
if (!ignoreFence.isEmpty()) {
scope.put("ignoreFence", ignoreFence);
}
return scope;
}
private List<Map<String, Object>> buildRuleItems(AlarmRuleDO rule) {
List<AlarmRuleItemDO> items = listRuleItems(rule.getId());
List<Map<String, Object>> result = new ArrayList<>();
if (items.isEmpty()) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("level", 1);
item.put("enable", enabled(rule.getEnable()));
item.put("alarmParam", new LinkedHashMap<String, Object>());
result.add(item);
return result;
}
for (AlarmRuleItemDO itemDO : items) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("level", itemDO.getLevel() == null ? 1 : itemDO.getLevel());
item.put("enable", itemDO.getEnable() == null ? enabled(rule.getEnable()) : enabled(itemDO.getEnable()));
item.put("alarmParam", buildAlarmParam(itemDO));
Map<String, Object> alarmAction = readJsonMap(itemDO.getAlarmActionJson(), "alarmActionJson");
if (!alarmAction.isEmpty()) {
item.put("alarmAction", alarmAction);
}
result.add(item);
}
return result;
}
private Map<String, Object> buildAlarmParam(AlarmRuleItemDO item) {
Map<String, Object> alarmParam = readJsonMap(item.getAlarmParamJson(), "alarmParamJson");
putIfNotNull(alarmParam, "crossDuration", item.getCrossDuration());
putIfNotNull(alarmParam, "retentionMin", item.getRetentionMin());
putIfNotNull(alarmParam, "stayMin", item.getStayMin());
putIfNotNull(alarmParam, "staticDuration", item.getStaticDuration());
putIfNotNull(alarmParam, "overSpeed", item.getOverSpeed());
putIfNotNull(alarmParam, "globalOverSpeed", item.getGlobalOverSpeed());
putIfNotNull(alarmParam, "lowBattery", item.getLowBattery());
putIfNotNull(alarmParam, "crowdedNum", item.getCrowdedNum());
putIfNotNull(alarmParam, "lackedNum", item.getLackedNum());
putIfNotNull(alarmParam, "clusterDuration", item.getClusterDuration());
putIfNotNull(alarmParam, "clusterQuantity", item.getClusterQuantity());
putIfNotNull(alarmParam, "clusterRadius", item.getClusterRadius());
putIfNotNull(alarmParam, "crashDistance", item.getCrashDistance());
putIfNotNull(alarmParam, "crashSpeed", item.getCrashSpeed());
return alarmParam;
}
private List<AlarmRuleFenceRelDO> listFenceRelations(Long ruleId) {
QueryWrapper<AlarmRuleFenceRelDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("rule_id", ruleId);
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
queryWrapper.orderByAsc("id");
return alarmRuleFenceRelRepository.list(queryWrapper);
}
private List<AlarmRuleItemDO> listRuleItems(Long ruleId) {
QueryWrapper<AlarmRuleItemDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("rule_id", ruleId);
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
queryWrapper.orderByAsc("level", "id");
return alarmRuleItemRepository.list(queryWrapper);
}
private Map<String, Object> readJsonMap(String json, String fieldName) {
if (!StringUtils.hasText(json)) {
return new LinkedHashMap<>();
}
try {
return objectMapper.readValue(json, new TypeReference<LinkedHashMap<String, Object>>() {
});
} catch (IOException e) {
throw new BizException("同步FindS失败" + fieldName + "不是合法JSON对象");
}
}
private List<String> parseTrackTypes(String trackTypes) {
if (!StringUtils.hasText(trackTypes)) {
return new ArrayList<>(Arrays.asList("STAFF"));
}
List<String> result = new ArrayList<>();
for (String item : trackTypes.split(",")) {
String value = item.trim();
if (StringUtils.hasText(value)) {
result.add(value);
}
}
if (result.isEmpty()) {
result.add("STAFF");
}
return result;
}
private void assertSuccess(JsonNode response, String apiName) {
if (!isSuccess(response)) {
throw apiError(response, apiName);
}
}
private boolean isSuccess(JsonNode response) {
String code = response == null ? null : response.path("code").asText();
return "200".equals(code) || "0".equals(code);
}
private BizException apiError(JsonNode response, String apiName) {
String msg = response == null ? "" : response.path("msg").asText();
String detail = response == null ? "" : response.path("detail").asText();
return new BizException("FindS接口调用失败" + apiName + "" + msg + " " + detail);
}
private void markSyncSuccess(AlarmRuleDO rule, JsonNode response) {
findsSyncStateService.completeAlarmRule(rule, rule.getFindsRuleId(), response.toString());
}
private void putIfNotNull(Map<String, Object> target, String key, Object value) {
if (value != null) {
target.put(key, value);
}
}
private boolean enabled(Integer enable) {
return enable == null || enable != 0;
}
private Long longValue(JsonNode node) {
if (node == null || node.isMissingNode() || node.isNull()) {
return null;
}
if (node.isNumber()) {
return node.asLong();
}
if (node.isTextual() && StringUtils.hasText(node.asText())) {
try {
return Long.valueOf(node.asText());
} catch (NumberFormatException ignored) {
return null;
}
}
return null;
}
private Long responseId(JsonNode data) {
Long direct = longValue(data);
if (direct != null || data == null || !data.isObject()) {
return direct;
}
for (String field : new String[]{"id", "ruleId", "findsRuleId"}) {
Long value = longValue(data.get(field));
if (value != null) {
return value;
}
}
return null;
}
}

View File

@ -0,0 +1,38 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRuleItemGateway;
import com.zcloud.personnel.positioning.domain.model.AlarmRuleItemE;
import com.zcloud.personnel.positioning.dto.AlarmRuleItemAddCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRuleItemAddExe {
private final AlarmRuleItemGateway alarmRuleItemGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(AlarmRuleItemAddCmd cmd) {
AlarmRuleItemE alarmRuleItemE = new AlarmRuleItemE();
BeanUtils.copyProperties(cmd, alarmRuleItemE);
boolean res;
try {
res = alarmRuleItemGateway.add(alarmRuleItemE);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!res) {
throw new BizException("保存失败");
}
return true;
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRuleItemGateway;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRuleItemRemoveExe {
private final AlarmRuleItemGateway alarmRuleItemGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = alarmRuleItemGateway.deletedAlarmRuleItemById(id);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = alarmRuleItemGateway.deletedAlarmRuleItemByIds(ids);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
}

View File

@ -0,0 +1,32 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AlarmRuleItemGateway;
import com.zcloud.personnel.positioning.domain.model.AlarmRuleItemE;
import com.zcloud.personnel.positioning.dto.AlarmRuleItemUpdateCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRuleItemUpdateExe {
private final AlarmRuleItemGateway alarmRuleItemGateway;
@Transactional(rollbackFor = Exception.class)
public void execute(AlarmRuleItemUpdateCmd cmd) {
AlarmRuleItemE alarmRuleItemE = new AlarmRuleItemE();
BeanUtils.copyProperties(cmd, alarmRuleItemE);
boolean res = alarmRuleItemGateway.update(alarmRuleItemE);
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,47 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRuleRemoveExe {
private final AlarmRuleRepository alarmRuleRepository;
private final FindsPendingSyncService findsPendingSyncService;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
AlarmRuleDO alarmRuleDO = alarmRuleRepository.getById(id);
if (alarmRuleDO == null) {
return true;
}
alarmRuleDO.setSyncStatus(FindsPendingSyncService.STATUS_DELETE_WAIT);
alarmRuleDO.setSyncToken(FindsSyncToken.next());
if (!alarmRuleRepository.updateById(alarmRuleDO)) {
throw new BizException("删除失败");
}
findsPendingSyncService.scheduleRule(id);
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
if (ids == null || ids.length == 0) {
return true;
}
for (Long id : ids) {
execute(id);
}
return true;
}
}

View File

@ -0,0 +1,87 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.zcloud.personnel.positioning.dto.AlarmRuleUpdateCmd;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.Set;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AlarmRuleUpdateExe {
private final AlarmRuleRepository alarmRuleRepository;
private final AlarmRuleConfigPersistence alarmRuleConfigPersistence;
private final FindsPendingSyncService findsPendingSyncService;
@Transactional(rollbackFor = Exception.class)
public void execute(AlarmRuleUpdateCmd cmd) {
if (cmd.getId() == null) {
throw new BizException("规则ID不能为空");
}
AlarmRuleDO alarmRuleDO = alarmRuleRepository.getById(cmd.getId());
if (alarmRuleDO == null) {
throw new BizException("告警配置不存在");
}
BeanUtils.copyProperties(cmd, alarmRuleDO, getNullPropertyNames(cmd));
alarmRuleConfigPersistence.normalizeRule(alarmRuleDO);
alarmRuleDO.setSyncStatus(FindsPendingSyncService.STATUS_WAIT);
alarmRuleDO.setSyncToken(FindsSyncToken.next());
boolean res = alarmRuleRepository.updateById(alarmRuleDO);
if (!res) {
throw new BizException("修改失败");
}
if (cmd.getFenceRelList() != null || AlarmRuleConfigPersistence.ATTR_COMPANY_AREA.equals(alarmRuleDO.getAlarmAttr())) {
alarmRuleConfigPersistence.rebuildFenceRelations(alarmRuleDO, cmd.getFenceRelList());
}
if (cmd.getRuleItemList() != null) {
alarmRuleConfigPersistence.rebuildRuleItems(alarmRuleDO, cmd.getRuleItemList(), true);
}
findsPendingSyncService.scheduleRule(alarmRuleDO.getId());
}
@Transactional(rollbackFor = Exception.class)
public void changeStatus(Long id, boolean enabled) {
AlarmRuleDO alarmRuleDO = alarmRuleRepository.getById(id);
if (alarmRuleDO == null) {
throw new BizException("告警配置不存在");
}
alarmRuleDO.setEnable(enabled ? 1 : 0);
alarmRuleDO.setRuleStatus(enabled ? AlarmRuleConfigPersistence.STATUS_ENABLED : AlarmRuleConfigPersistence.STATUS_DISABLED);
alarmRuleDO.setSyncStatus(FindsPendingSyncService.STATUS_WAIT);
alarmRuleDO.setSyncToken(FindsSyncToken.next());
boolean res = alarmRuleRepository.updateById(alarmRuleDO);
if (!res) {
throw new BizException("状态修改失败");
}
alarmRuleConfigPersistence.syncRuleItemEnable(id, alarmRuleDO.getEnable());
findsPendingSyncService.scheduleRule(alarmRuleDO.getId());
}
private String[] getNullPropertyNames(Object source) {
BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for (PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) {
emptyNames.add(pd.getName());
}
}
return emptyNames.toArray(new String[0]);
}
}

View File

@ -1,31 +1,32 @@
package org.example.command;
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AreaGateway;
import com.zcloud.personnel.positioning.domain.model.AreaE;
import com.zcloud.personnel.positioning.dto.AreaAddCmd;
import com.alibaba.cola.exception.BizException;
import org.example.domain.gateway.RiskPointGateway;
import org.example.domain.model.RiskPointE;
import org.example.dto.RiskPointAddCmd;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
*
* web-app
*
* @author lin
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class RiskPointAddExe {
private final RiskPointGateway riskPointGateway;
public class AreaAddExe {
private final AreaGateway areaGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(RiskPointAddCmd cmd) {
RiskPointE examTypeE = new RiskPointE();
BeanUtils.copyProperties(cmd, examTypeE);
boolean res = false;
public boolean execute(AreaAddCmd cmd) {
AreaE areaE = new AreaE();
BeanUtils.copyProperties(cmd, areaE);
boolean res;
try {
res = riskPointGateway.add(examTypeE);
res = areaGateway.add(areaE);
} catch (Exception e) {
throw new RuntimeException(e);
}

View File

@ -1,31 +1,35 @@
package org.example.command;
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AreaGateway;
import com.alibaba.cola.exception.BizException;
import org.example.domain.gateway.RiskPointGateway;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
*
* @author lin
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class RiskPointRemoveExe {
private final RiskPointGateway riskPointGateway;
public class AreaRemoveExe {
private final AreaGateway areaGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = riskPointGateway.deletedRiskPointById(id);
if(!res){
boolean res = areaGateway.deletedAreaById(id);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = riskPointGateway.deletedRiskPointByIds(ids);
if(!res){
boolean res = areaGateway.deletedAreaByIds(ids);
if (!res) {
throw new BizException("删除失败");
}
return true;

View File

@ -0,0 +1,32 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.AreaGateway;
import com.zcloud.personnel.positioning.domain.model.AreaE;
import com.zcloud.personnel.positioning.dto.AreaUpdateCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class AreaUpdateExe {
private final AreaGateway areaGateway;
@Transactional(rollbackFor = Exception.class)
public void execute(AreaUpdateCmd cmd) {
AreaE areaE = new AreaE();
BeanUtils.copyProperties(cmd, areaE);
boolean res = areaGateway.update(areaE);
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,35 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.zcloud.personnel.positioning.dto.FenceAddCmd;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.repository.FenceRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@AllArgsConstructor
public class FenceAddExe {
private final FenceRepository fenceRepository;
private final FencePersistenceSupport fencePersistenceSupport;
private final FindsPendingSyncService findsPendingSyncService;
private final FencePointPersistenceService fencePointPersistenceService;
@Transactional(rollbackFor = Exception.class)
public FenceDO execute(FenceAddCmd cmd) {
FenceDO fenceDO = new FenceDO();
BeanUtils.copyProperties(cmd, fenceDO);
fencePersistenceSupport.normalize(fenceDO, true);
fenceDO.setSyncStatus(FindsPendingSyncService.STATUS_WAIT);
fenceDO.setSyncToken(FindsSyncToken.next());
boolean res = fenceRepository.save(fenceDO);
if (!res) {
throw new BizException("Fence save failed");
}
fencePointPersistenceService.rebuild(fenceDO);
findsPendingSyncService.scheduleFence(fenceDO.getId());
return fenceDO;
}
}

View File

@ -0,0 +1,196 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zcloud.personnel.positioning.dto.FenceCompanyAreaSaveCmd;
import com.zcloud.personnel.positioning.dto.clientobject.FenceCompanyAreaCO;
import com.zcloud.personnel.positioning.persistence.dataobject.AreaDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.repository.AreaRepository;
import com.zcloud.personnel.positioning.persistence.repository.FenceRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Component
@AllArgsConstructor
public class FenceCompanyAreaSaveExe {
private static final String AREA_TYPE_COMPANY_AREA = "COMPANY_AREA";
private final AreaRepository areaRepository;
private final FenceRepository fenceRepository;
private final FencePersistenceSupport fencePersistenceSupport;
private final FindsPendingSyncService findsPendingSyncService;
private final FencePointPersistenceService fencePointPersistenceService;
@Transactional(rollbackFor = Exception.class)
public FenceCompanyAreaCO save(FenceCompanyAreaSaveCmd cmd) {
validate(cmd);
AreaDO area = saveArea(cmd);
FenceDO fence = saveFence(cmd, area);
area.setBindFenceId(fence.getId());
area.setFindsFenceId(fence.getFindsFenceId());
areaRepository.updateById(area);
return map(area, fence);
}
public FenceCompanyAreaCO query(Long corpinfoId, String corpinfoName) {
AreaDO area = queryArea(null, corpinfoId, corpinfoName);
FenceDO fence = null;
if (area != null && area.getBindFenceId() != null) {
fence = fenceRepository.getById(area.getBindFenceId());
}
if (fence == null) {
fence = queryFence(null, corpinfoId, corpinfoName);
}
if (area == null && fence == null) {
return null;
}
return map(area, fence);
}
private AreaDO saveArea(FenceCompanyAreaSaveCmd cmd) {
AreaDO area = queryArea(cmd.getAreaId(), cmd.getCorpinfoId(), cmd.getCorpinfoName());
boolean create = area == null;
if (create) {
area = new AreaDO();
area.setAreaCode("CA" + System.currentTimeMillis());
area.setAreaType(AREA_TYPE_COMPANY_AREA);
area.setAreaLevel(1);
}
area.setAreaName(cmd.getAreaName());
area.setCorpinfoId(cmd.getCorpinfoId());
area.setCorpinfoName(cmd.getCorpinfoName());
area.setPolygonJson(cmd.getPolygonJson());
area.setCenterLon(cmd.getCenterLon());
area.setCenterLat(cmd.getCenterLat());
area.setStatus(fencePersistenceSupport.normalizeStatus(cmd.getStatus()));
if (create) {
boolean res = areaRepository.save(area);
if (!res) {
throw new BizException("Company area save failed");
}
} else if (!areaRepository.updateById(area)) {
throw new BizException("Company area update failed");
}
return area;
}
private FenceDO saveFence(FenceCompanyAreaSaveCmd cmd, AreaDO area) {
FenceDO fence = queryFence(cmd.getFenceId(), cmd.getCorpinfoId(), cmd.getCorpinfoName());
boolean create = fence == null;
if (create) {
fence = new FenceDO();
fence.setFenceCode("CF" + System.currentTimeMillis());
}
fence.setFenceName(cmd.getAreaName());
fence.setFenceScope(FencePersistenceSupport.SCOPE_COMPANY_AREA);
fence.setFenceBizName(FencePersistenceSupport.DEFAULT_BIZ_NAME);
fence.setCorpinfoId(cmd.getCorpinfoId());
fence.setCorpinfoName(cmd.getCorpinfoName());
fence.setAreaId(area.getId());
fence.setAreaName(area.getAreaName());
fence.setCoor(cmd.getCoor());
fence.setSpaceType(cmd.getSpaceType());
fence.setBuildingId(cmd.getBuildingId());
fence.setFloor(cmd.getFloor());
fence.setPolygonJson(cmd.getPolygonJson());
fence.setCenterLon(cmd.getCenterLon());
fence.setCenterLat(cmd.getCenterLat());
fence.setSilence(0);
fence.setStatus(cmd.getStatus());
fencePersistenceSupport.normalize(fence, create);
fence.setSyncStatus(FindsPendingSyncService.STATUS_WAIT);
fence.setSyncToken(FindsSyncToken.next());
if (create) {
boolean res = fenceRepository.save(fence);
if (!res) {
throw new BizException("Company area fence save failed");
}
} else if (!fenceRepository.updateById(fence)) {
throw new BizException("Company area fence update failed");
}
fencePointPersistenceService.rebuild(fence);
findsPendingSyncService.scheduleFence(fence.getId());
return fence;
}
private AreaDO queryArea(Long areaId, Long corpinfoId, String corpinfoName) {
if (areaId != null) {
return areaRepository.getById(areaId);
}
QueryWrapper<AreaDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("area_type", AREA_TYPE_COMPANY_AREA);
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
applyCompany(queryWrapper, corpinfoId, corpinfoName);
queryWrapper.orderByDesc("update_time");
queryWrapper.last("LIMIT 1");
return areaRepository.getOne(queryWrapper, false);
}
private FenceDO queryFence(Long fenceId, Long corpinfoId, String corpinfoName) {
if (fenceId != null) {
return fenceRepository.getById(fenceId);
}
QueryWrapper<FenceDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("fence_scope", FencePersistenceSupport.SCOPE_COMPANY_AREA);
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
applyCompany(queryWrapper, corpinfoId, corpinfoName);
queryWrapper.orderByDesc("update_time");
queryWrapper.last("LIMIT 1");
return fenceRepository.getOne(queryWrapper, false);
}
private <T> void applyCompany(QueryWrapper<T> queryWrapper, Long corpinfoId, String corpinfoName) {
if (corpinfoId != null) {
queryWrapper.eq("corpinfo_id", corpinfoId);
return;
}
if (StringUtils.hasText(corpinfoName)) {
queryWrapper.eq("corpinfo_name", corpinfoName);
}
}
private void validate(FenceCompanyAreaSaveCmd cmd) {
if (cmd == null) {
throw new BizException("Company area payload is required");
}
if (cmd.getCorpinfoId() == null && !StringUtils.hasText(cmd.getCorpinfoName())) {
throw new BizException("Company is required");
}
if (!StringUtils.hasText(cmd.getAreaName())) {
throw new BizException("Area name is required");
}
if (!StringUtils.hasText(cmd.getPolygonJson())) {
throw new BizException("Polygon json is required");
}
}
private FenceCompanyAreaCO map(AreaDO area, FenceDO fence) {
FenceCompanyAreaCO co = new FenceCompanyAreaCO();
if (area != null) {
co.setAreaId(area.getId());
co.setAreaName(area.getAreaName());
co.setCorpinfoId(area.getCorpinfoId());
co.setCorpinfoName(area.getCorpinfoName());
co.setPolygonJson(area.getPolygonJson());
co.setCenterLon(area.getCenterLon());
co.setCenterLat(area.getCenterLat());
co.setStatus(area.getStatus());
co.setFindsFenceId(area.getFindsFenceId());
}
if (fence != null) {
BeanUtils.copyProperties(fence, co);
co.setFenceId(fence.getId());
co.setAreaId(fence.getAreaId() == null ? co.getAreaId() : fence.getAreaId());
co.setAreaName(StringUtils.hasText(fence.getAreaName()) ? fence.getAreaName() : co.getAreaName());
co.setFindsFenceId(fence.getFindsFenceId());
co.setFindsFenceCode(fence.getFindsFenceCode());
co.setSyncStatus(fence.getSyncStatus());
co.setLastSyncTime(fence.getLastSyncTime());
}
return co;
}
}

View File

@ -0,0 +1,279 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
import com.zcloud.personnel.positioning.integration.finds.OpenapiConfigCache;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
@AllArgsConstructor
public class FenceFindsSyncService {
private static final String CREATE_API = "finds.fence.create.v2";
private static final String UPDATE_API = "finds.fence.update.v2";
private static final String DELETE_API = "finds.fence.delete";
private static final String STATUS_SUCCESS = "SUCCESS";
private final FindsOpenApiClient findsOpenApiClient;
private final OpenapiConfigCache openapiConfigCache;
private final FindsSyncStateService findsSyncStateService;
private final FindsEntityLookupService findsEntityLookupService;
private final FencePointPersistenceService fencePointPersistenceService;
private final ObjectMapper objectMapper;
public void createOrUpdate(FenceDO fence) {
OpenapiConfigDO config = enabledConfig(fence);
if (fence.getFindsFenceId() == null) {
Long existingId = findsEntityLookupService.findFenceId(config, fence);
if (existingId != null) {
fence.setFindsFenceId(existingId);
findsSyncStateService.recordFenceMapping(fence.getId(), existingId, fence.getFindsFenceCode());
}
}
assertCurrent(fence);
JsonNode response = fence.getFindsFenceId() == null
? createWithRecovery(config, fence)
: updateOrRecreate(config, fence);
markSyncSuccess(fence, response);
}
public void create(FenceDO fence) {
JsonNode response = createWithRecovery(enabledConfig(fence), fence);
markSyncSuccess(fence, response);
}
public void update(FenceDO fence) {
if (fence.getFindsFenceId() == null) {
create(fence);
return;
}
JsonNode response = updateOrRecreate(enabledConfig(fence), fence);
markSyncSuccess(fence, response);
}
public void delete(FenceDO fence) {
if (fence == null || fence.getFindsFenceId() == null) {
return;
}
Map<String, Object> request = new LinkedHashMap<>();
request.put("id", fence.getFindsFenceId());
try {
JsonNode response = findsOpenApiClient.post(enabledConfig(fence), DELETE_API, request);
if (!findsEntityLookupService.isNotFound(response)) {
assertSuccess(response, DELETE_API);
}
} catch (RuntimeException e) {
if (!findsEntityLookupService.isNotFound(e)) {
throw e;
}
}
}
private OpenapiConfigDO enabledConfig(FenceDO fence) {
OpenapiConfigDO config = openapiConfigCache.getEnabledConfig(fence.getCorpinfoId());
if (config == null) {
throw new BizException("未配置启用的FindS OpenAPI配置");
}
return config;
}
private Map<String, Object> buildSaveRequest(FenceDO fence, boolean create) {
if (!StringUtils.hasText(fence.getFenceName())) {
throw new BizException("Fence name is required");
}
Map<String, Object> request = new LinkedHashMap<>();
request.put("name", fence.getFenceName());
if (create) {
request.put("bizName", StringUtils.hasText(fence.getFenceBizName())
? fence.getFenceBizName()
: FencePersistenceSupport.DEFAULT_BIZ_NAME);
}
request.put("coor", StringUtils.hasText(fence.getCoor()) ? fence.getCoor() : FencePersistenceSupport.DEFAULT_COOR);
request.put("silence", fence.getSilence() != null && fence.getSilence() == 1);
request.put("polygon", fencePointPersistenceService.parsePolygonObject(fence.getPolygonJson()));
putIfNotNull(request, "buildingId", fence.getBuildingId());
putIfNotNull(request, "floor", fence.getFloor());
if (StringUtils.hasText(fence.getSpaceType())) {
request.put("spaceType", fence.getSpaceType());
}
Object effectPeriod = parseEffectPeriod(fence.getEffectPeriodJson());
if (effectPeriod != null) {
request.put("effectPeriod", effectPeriod);
}
putIfNotNull(request, "effectStart", toEpochMillis(fence.getEffectStart()));
putIfNotNull(request, "effectEnd", toEpochMillis(fence.getEffectEnd()));
return request;
}
private JsonNode createWithRecovery(OpenapiConfigDO config, FenceDO fence) {
RuntimeException createError = null;
try {
JsonNode response = findsOpenApiClient.post(config, CREATE_API, buildSaveRequest(fence, true));
if (isSuccess(response)) {
applyResponseFence(fence, response);
findsSyncStateService.recordFenceMapping(fence.getId(), fence.getFindsFenceId(), fence.getFindsFenceCode());
return response;
}
createError = apiError(response, CREATE_API);
} catch (RuntimeException e) {
createError = e;
}
Long recoveredId = tryFindFenceId(config, fence);
if (recoveredId == null) {
throw createError;
}
fence.setFindsFenceId(recoveredId);
findsSyncStateService.recordFenceMapping(fence.getId(), recoveredId, fence.getFindsFenceCode());
assertCurrent(fence);
return updateOrRecreate(config, fence, false);
}
private JsonNode updateOrRecreate(OpenapiConfigDO config, FenceDO fence) {
return updateOrRecreate(config, fence, true);
}
private JsonNode updateOrRecreate(OpenapiConfigDO config, FenceDO fence, boolean recreateOnNotFound) {
Long oldFindsId = fence.getFindsFenceId();
Map<String, Object> request = buildSaveRequest(fence, false);
request.put("id", oldFindsId);
try {
JsonNode response = findsOpenApiClient.post(config, UPDATE_API, request);
if (!findsEntityLookupService.isNotFound(response)) {
assertSuccess(response, UPDATE_API);
applyResponseFence(fence, response);
return response;
}
} catch (RuntimeException e) {
if (!findsEntityLookupService.isNotFound(e)) {
throw e;
}
}
if (!findsSyncStateService.clearFenceMapping(fence, oldFindsId)) {
throw new BizException("FindS fence synchronization snapshot is stale");
}
fence.setFindsFenceId(null);
fence.setFindsFenceCode(null);
if (!recreateOnNotFound) {
throw new BizException("FindS fence recovery failed: recovered id does not exist");
}
return createWithRecovery(config, fence);
}
private void assertCurrent(FenceDO fence) {
if (!findsSyncStateService.isCurrentFence(fence)) {
throw new BizException("FindS fence synchronization snapshot is stale");
}
}
private Long tryFindFenceId(OpenapiConfigDO config, FenceDO fence) {
try {
return findsEntityLookupService.findFenceId(config, fence);
} catch (RuntimeException ignored) {
return null;
}
}
private Object parseEffectPeriod(String effectPeriodJson) {
if (!StringUtils.hasText(effectPeriodJson)) {
return null;
}
try {
return objectMapper.readValue(effectPeriodJson, Object.class);
} catch (IOException e) {
throw new BizException("Fence effectPeriodJson is invalid");
}
}
private Long toEpochMillis(LocalDateTime dateTime) {
return dateTime == null ? null : dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
private void applyResponseFence(FenceDO fence, JsonNode response) {
JsonNode data = response == null ? null : response.path("data");
Long responseId = longValue(data);
String responseCode = textValue(child(data, "code"));
if (responseId == null && data != null && data.isObject()) {
responseId = longValue(data.get("id"));
}
if (responseId != null) {
fence.setFindsFenceId(responseId);
}
if (StringUtils.hasText(responseCode)) {
fence.setFindsFenceCode(responseCode);
}
if (fence.getFindsFenceId() == null) {
throw new BizException("FindS fence sync succeeded without fence id");
}
}
private void markSyncSuccess(FenceDO fence, JsonNode response) {
boolean current = findsSyncStateService.completeFence(fence, fence.getFindsFenceId(), fence.getFindsFenceCode(),
response == null ? null : response.toString());
if (current && fence.getAreaId() != null) {
findsSyncStateService.recordAreaFenceMapping(fence.getAreaId(), fence.getId(), fence.getFindsFenceId());
}
}
private void assertSuccess(JsonNode response, String apiName) {
if (!isSuccess(response)) {
throw apiError(response, apiName);
}
}
private boolean isSuccess(JsonNode response) {
String code = response == null ? null : response.path("code").asText();
return "200".equals(code) || "0".equals(code);
}
private BizException apiError(JsonNode response, String apiName) {
String msg = response == null ? "" : response.path("msg").asText();
String detail = response == null ? "" : response.path("detail").asText();
return new BizException("FindS api failed: " + apiName + " " + msg + " " + detail);
}
private JsonNode child(JsonNode node, String fieldName) {
return node == null || node.isMissingNode() || node.isNull() ? null : node.get(fieldName);
}
private Long longValue(JsonNode node) {
if (node == null || node.isMissingNode() || node.isNull()) {
return null;
}
if (node.isNumber()) {
return node.asLong();
}
if (node.isTextual() && StringUtils.hasText(node.asText())) {
try {
return Long.valueOf(node.asText());
} catch (NumberFormatException ignored) {
return null;
}
}
return null;
}
private String textValue(JsonNode node) {
if (node == null || node.isMissingNode() || node.isNull()) {
return null;
}
String value = node.asText();
return StringUtils.hasText(value) ? value : null;
}
private void putIfNotNull(Map<String, Object> target, String key, Object value) {
if (value != null) {
target.put(key, value);
}
}
}

View File

@ -0,0 +1,76 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
@AllArgsConstructor
public class FencePersistenceSupport {
public static final String SCOPE_COMPANY_AREA = "COMPANY_AREA";
public static final String SCOPE_ELECTRONIC = "ELECTRONIC";
public static final String SCOPE_WORK_AREA = "WORK_AREA";
public static final String SCOPE_SILENCE = "SILENCE";
public static final String DEFAULT_COOR = "WGS84";
public static final String DEFAULT_SPACE_TYPE = "OUTDOOR";
public static final String DEFAULT_BIZ_NAME = "\u533a\u57df";
public static final String SILENCE_BIZ_NAME = "\u9759\u9ed8\u533a";
private final FencePointPersistenceService fencePointPersistenceService;
public void normalize(FenceDO fence, boolean create) {
if (fence == null) {
throw new BizException("Fence is required");
}
if (!StringUtils.hasText(fence.getFenceName())) {
throw new BizException("Fence name is required");
}
if (!StringUtils.hasText(fence.getPolygonJson())) {
throw new BizException("Fence polygonJson is required");
}
if (!StringUtils.hasText(fence.getFenceCode())) {
fence.setFenceCode((create ? "F" : "FU") + System.currentTimeMillis());
}
if (!StringUtils.hasText(fence.getFenceScope())) {
fence.setFenceScope(SCOPE_WORK_AREA);
}
if (fence.getSilence() == null) {
fence.setSilence(SCOPE_SILENCE.equals(fence.getFenceScope()) ? 1 : 0);
}
if (!StringUtils.hasText(fence.getFenceBizName())) {
fence.setFenceBizName(fence.getSilence() != null && fence.getSilence() == 1
? SILENCE_BIZ_NAME
: DEFAULT_BIZ_NAME);
}
if (!StringUtils.hasText(fence.getCoor())) {
fence.setCoor(DEFAULT_COOR);
}
if (!StringUtils.hasText(fence.getSpaceType())) {
fence.setSpaceType(DEFAULT_SPACE_TYPE);
}
fence.setStatus(normalizeStatus(fence.getStatus()));
if (!StringUtils.hasText(fence.getCorpinfoName()) && StringUtils.hasText(fence.getOrgName())) {
fence.setCorpinfoName(fence.getOrgName());
}
if (!StringUtils.hasText(fence.getOrgName()) && StringUtils.hasText(fence.getCorpinfoName())) {
fence.setOrgName(fence.getCorpinfoName());
}
fencePointPersistenceService.fillCenter(fence);
}
public String normalizeStatus(String status) {
if (!StringUtils.hasText(status)) {
return AlarmRuleConfigPersistence.STATUS_ENABLED;
}
String value = status.trim();
if ("1".equals(value) || "\u542f\u7528".equals(value)) {
return AlarmRuleConfigPersistence.STATUS_ENABLED;
}
if ("0".equals(value) || "\u505c\u7528".equals(value)) {
return AlarmRuleConfigPersistence.STATUS_DISABLED;
}
return value;
}
}

View File

@ -0,0 +1,38 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.FencePointGateway;
import com.zcloud.personnel.positioning.domain.model.FencePointE;
import com.zcloud.personnel.positioning.dto.FencePointAddCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class FencePointAddExe {
private final FencePointGateway fencePointGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(FencePointAddCmd cmd) {
FencePointE fencePointE = new FencePointE();
BeanUtils.copyProperties(cmd, fencePointE);
boolean res;
try {
res = fencePointGateway.add(fencePointE);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!res) {
throw new BizException("保存失败");
}
return true;
}
}

View File

@ -0,0 +1,180 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FencePointDO;
import com.zcloud.personnel.positioning.persistence.repository.FencePointRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
@Component
@AllArgsConstructor
public class FencePointPersistenceService {
private final FencePointRepository fencePointRepository;
private final ObjectMapper objectMapper;
public void rebuild(FenceDO fence) {
if (fence == null || fence.getId() == null) {
throw new BizException("Fence id is required");
}
List<RingPoint> points = parsePolygon(fence.getPolygonJson());
QueryWrapper<FencePointDO> removeWrapper = new QueryWrapper<>();
removeWrapper.eq("fence_id", fence.getId());
fencePointRepository.remove(removeWrapper);
for (RingPoint point : points) {
FencePointDO pointDO = new FencePointDO();
pointDO.setFenceId(fence.getId());
pointDO.setRingIndex(point.ringIndex);
pointDO.setPointIndex(point.pointIndex);
pointDO.setLon(point.lon);
pointDO.setLat(point.lat);
pointDO.setAlt(point.alt);
pointDO.setCorpinfoId(fence.getCorpinfoId());
pointDO.setCorpinfoName(fence.getCorpinfoName());
fencePointRepository.save(pointDO);
}
}
public void removeByFenceId(Long fenceId) {
if (fenceId == null) {
return;
}
QueryWrapper<FencePointDO> removeWrapper = new QueryWrapper<>();
removeWrapper.eq("fence_id", fenceId);
fencePointRepository.remove(removeWrapper);
}
public void fillCenter(FenceDO fence) {
if (fence == null || (fence.getCenterLon() != null && fence.getCenterLat() != null)) {
return;
}
List<RingPoint> points = parsePolygon(fence.getPolygonJson());
if (points.isEmpty()) {
return;
}
BigDecimal lonSum = BigDecimal.ZERO;
BigDecimal latSum = BigDecimal.ZERO;
for (RingPoint point : points) {
lonSum = lonSum.add(point.lon);
latSum = latSum.add(point.lat);
}
BigDecimal count = BigDecimal.valueOf(points.size());
fence.setCenterLon(lonSum.divide(count, 10, RoundingMode.HALF_UP));
fence.setCenterLat(latSum.divide(count, 10, RoundingMode.HALF_UP));
}
public Object parsePolygonObject(String polygonJson) {
if (!StringUtils.hasText(polygonJson)) {
throw new BizException("Fence polygonJson is required");
}
try {
return objectMapper.readValue(polygonJson, Object.class);
} catch (IOException e) {
throw new BizException("Fence polygonJson is invalid");
}
}
private List<RingPoint> parsePolygon(String polygonJson) {
if (!StringUtils.hasText(polygonJson)) {
throw new BizException("Fence polygonJson is required");
}
try {
JsonNode root = objectMapper.readTree(polygonJson);
List<RingPoint> points = new ArrayList<>();
int[] ringIndex = new int[]{0};
collectRings(root, ringIndex, points);
if (points.size() < 3) {
throw new BizException("Fence polygonJson must contain at least three points");
}
return points;
} catch (IOException e) {
throw new BizException("Fence polygonJson is invalid");
}
}
private void collectRings(JsonNode node, int[] ringIndex, List<RingPoint> points) {
if (isEmpty(node)) {
return;
}
if (isCoordinate(node)) {
RingPoint point = toPoint(node, ringIndex[0], points.size());
points.add(point);
return;
}
if (node.isObject()) {
node.elements().forEachRemaining(child -> collectRings(child, ringIndex, points));
return;
}
if (!node.isArray()) {
return;
}
if (containsCoordinate(node)) {
collectRing(node, ringIndex[0]++, points);
return;
}
for (JsonNode child : node) {
collectRings(child, ringIndex, points);
}
}
private void collectRing(JsonNode ringNode, int ringIndex, List<RingPoint> points) {
int pointIndex = 0;
for (JsonNode child : ringNode) {
if (isCoordinate(child)) {
points.add(toPoint(child, ringIndex, pointIndex++));
}
}
}
private boolean containsCoordinate(JsonNode node) {
if (node == null || !node.isArray()) {
return false;
}
for (JsonNode child : node) {
if (isCoordinate(child)) {
return true;
}
}
return false;
}
private boolean isCoordinate(JsonNode node) {
return node != null
&& node.isArray()
&& node.size() >= 2
&& node.get(0).isNumber()
&& node.get(1).isNumber();
}
private RingPoint toPoint(JsonNode node, int ringIndex, int pointIndex) {
RingPoint point = new RingPoint();
point.ringIndex = ringIndex;
point.pointIndex = pointIndex;
point.lon = node.get(0).decimalValue();
point.lat = node.get(1).decimalValue();
point.alt = node.size() > 2 && node.get(2).isNumber() ? node.get(2).decimalValue() : null;
return point;
}
private boolean isEmpty(JsonNode node) {
return node == null || node.isMissingNode() || node.isNull();
}
private static class RingPoint {
private Integer ringIndex;
private Integer pointIndex;
private BigDecimal lon;
private BigDecimal lat;
private BigDecimal alt;
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.FencePointGateway;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class FencePointRemoveExe {
private final FencePointGateway fencePointGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = fencePointGateway.deletedFencePointById(id);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = fencePointGateway.deletedFencePointByIds(ids);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
}

View File

@ -0,0 +1,32 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.FencePointGateway;
import com.zcloud.personnel.positioning.domain.model.FencePointE;
import com.zcloud.personnel.positioning.dto.FencePointUpdateCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class FencePointUpdateExe {
private final FencePointGateway fencePointGateway;
@Transactional(rollbackFor = Exception.class)
public void execute(FencePointUpdateCmd cmd) {
FencePointE fencePointE = new FencePointE();
BeanUtils.copyProperties(cmd, fencePointE);
boolean res = fencePointGateway.update(fencePointE);
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,51 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleFenceRelDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleFenceRelRepository;
import com.zcloud.personnel.positioning.persistence.repository.FenceRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@AllArgsConstructor
public class FenceRemoveExe {
private final FenceRepository fenceRepository;
private final AlarmRuleFenceRelRepository alarmRuleFenceRelRepository;
private final FindsPendingSyncService findsPendingSyncService;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
FenceDO fenceDO = fenceRepository.getById(id);
if (fenceDO == null) {
return true;
}
QueryWrapper<AlarmRuleFenceRelDO> relWrapper = new QueryWrapper<>();
relWrapper.eq("fence_id", id);
relWrapper.and(value -> value.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
if (alarmRuleFenceRelRepository.count(relWrapper) > 0) {
throw new BizException("围栏已关联告警配置,请先删除关联告警配置");
}
fenceDO.setSyncStatus(FindsPendingSyncService.STATUS_DELETE_WAIT);
fenceDO.setSyncToken(FindsSyncToken.next());
if (!fenceRepository.updateById(fenceDO)) {
throw new BizException("Fence delete failed");
}
findsPendingSyncService.scheduleFence(id);
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
if (ids == null || ids.length == 0) {
return true;
}
for (Long id : ids) {
execute(id);
}
return true;
}
}

View File

@ -0,0 +1,109 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zcloud.personnel.positioning.dto.FenceUpdateCmd;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleFenceRelDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleFenceRelRepository;
import com.zcloud.personnel.positioning.persistence.repository.FenceRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@Component
@AllArgsConstructor
public class FenceUpdateExe {
private final FenceRepository fenceRepository;
private final AlarmRuleFenceRelRepository alarmRuleFenceRelRepository;
private final AlarmRuleUpdateExe alarmRuleUpdateExe;
private final FencePersistenceSupport fencePersistenceSupport;
private final FindsPendingSyncService findsPendingSyncService;
private final FencePointPersistenceService fencePointPersistenceService;
@Transactional(rollbackFor = Exception.class)
public void execute(FenceUpdateCmd cmd) {
if (cmd.getId() == null) {
throw new BizException("Fence id is required");
}
FenceDO fenceDO = fenceRepository.getById(cmd.getId());
if (fenceDO == null) {
throw new BizException("Fence does not exist");
}
BeanUtils.copyProperties(cmd, fenceDO, getNullPropertyNames(cmd));
fencePersistenceSupport.normalize(fenceDO, false);
fenceDO.setSyncStatus(FindsPendingSyncService.STATUS_WAIT);
fenceDO.setSyncToken(FindsSyncToken.next());
boolean res = fenceRepository.updateById(fenceDO);
if (!res) {
throw new BizException("Fence update failed");
}
fencePointPersistenceService.rebuild(fenceDO);
findsPendingSyncService.scheduleFence(fenceDO.getId());
}
@Transactional(rollbackFor = Exception.class)
public void changeStatus(Long id, boolean enabled) {
if (id == null) {
throw new BizException("Fence id is required");
}
FenceDO fenceDO = fenceRepository.getById(id);
if (fenceDO == null) {
throw new BizException("Fence does not exist");
}
fenceDO.setStatus(enabled ? AlarmRuleConfigPersistence.STATUS_ENABLED : AlarmRuleConfigPersistence.STATUS_DISABLED);
fenceDO.setSyncToken(FindsSyncToken.next());
boolean res = fenceRepository.updateById(fenceDO);
if (!res) {
throw new BizException("Fence status update failed");
}
for (Long ruleId : listRelatedRuleIds(fenceDO)) {
alarmRuleUpdateExe.changeStatus(ruleId, enabled);
}
}
private Set<Long> listRelatedRuleIds(FenceDO fenceDO) {
QueryWrapper<AlarmRuleFenceRelDO> queryWrapper = new QueryWrapper<>();
if (fenceDO.getFindsFenceId() == null) {
queryWrapper.eq("fence_id", fenceDO.getId());
} else {
queryWrapper.and(wrapper -> wrapper.eq("fence_id", fenceDO.getId())
.or()
.eq("finds_fence_id", fenceDO.getFindsFenceId()));
}
queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
List<AlarmRuleFenceRelDO> relList = alarmRuleFenceRelRepository.list(queryWrapper);
Set<Long> ruleIds = new LinkedHashSet<>();
if (relList == null) {
return ruleIds;
}
for (AlarmRuleFenceRelDO rel : relList) {
if (rel.getRuleId() != null) {
ruleIds.add(rel.getRuleId());
}
}
return ruleIds;
}
private String[] getNullPropertyNames(Object source) {
BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for (PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) {
emptyNames.add(pd.getName());
}
}
return emptyNames.toArray(new String[0]);
}
}

View File

@ -0,0 +1,228 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalDO;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
public class FindsEntityLookupService {
private static final String TERMINAL_LIST_API = "finds.terminal.list";
private static final String FENCE_LIST_API = "finds.fence.list.v2";
private static final String RULE_LIST_API = "finds.alarm.rule.list";
private static final int PAGE_SIZE = 100;
private static final int MAX_FENCE_PAGES = 20;
private static final int MAX_RULE_PAGES = 20;
private final FindsOpenApiClient findsOpenApiClient;
private final ObjectMapper objectMapper;
public FindsEntityLookupService(FindsOpenApiClient findsOpenApiClient, ObjectMapper objectMapper) {
this.findsOpenApiClient = findsOpenApiClient;
this.objectMapper = objectMapper;
}
public Long findTerminalId(OpenapiConfigDO config, TerminalDO terminal) {
if (terminal == null || !StringUtils.hasText(terminal.getTerminalNo())) {
return null;
}
Map<String, Object> request = new LinkedHashMap<>();
request.put("terminalNo", terminal.getTerminalNo());
request.put("pageNo", 1);
request.put("pageSize", PAGE_SIZE);
JsonNode rows = listRows(config, TERMINAL_LIST_API, request);
Long onlyMatch = null;
int matchCount = 0;
for (JsonNode row : rows) {
String terminalNo = firstText(row, "terminalNo", "no", "terminalCode", "imei");
if (!terminal.getTerminalNo().equals(terminalNo)) {
continue;
}
Long id = firstLong(row, "id", "terminalId", "findsTerminalId");
if (id != null) {
onlyMatch = id;
matchCount++;
}
}
return matchCount == 1 ? onlyMatch : null;
}
public Long findFenceId(OpenapiConfigDO config, FenceDO fence) {
if (fence == null || !StringUtils.hasText(fence.getFenceName())) {
return null;
}
Long onlyMatch = null;
for (int pageNo = 1; pageNo <= MAX_FENCE_PAGES; pageNo++) {
Map<String, Object> request = new LinkedHashMap<>();
request.put("nameLike", fence.getFenceName());
request.put("pageNo", pageNo);
request.put("pageSize", PAGE_SIZE);
JsonNode rows = listRows(config, FENCE_LIST_API, request);
for (JsonNode row : rows) {
if (!fence.getFenceName().equals(text(row.get("name")))) {
continue;
}
Long id = firstLong(row, "id", "fenceId", "findsFenceId");
if (id != null && onlyMatch != null && !onlyMatch.equals(id)) {
return null;
}
if (id != null) {
onlyMatch = id;
}
}
if (rows.size() < PAGE_SIZE) {
return onlyMatch;
}
}
return null;
}
public Long findAlarmRuleId(OpenapiConfigDO config, AlarmRuleDO rule) {
if (rule == null || !StringUtils.hasText(rule.getRuleName())) {
return null;
}
Long onlyMatch = null;
for (int pageNo = 1; pageNo <= MAX_RULE_PAGES; pageNo++) {
Map<String, Object> request = new LinkedHashMap<>();
request.put("pageNo", pageNo);
request.put("pageSize", PAGE_SIZE);
if (StringUtils.hasText(rule.getAlarmCode())) {
request.put("alarmCode", rule.getAlarmCode());
}
JsonNode rows = listRows(config, RULE_LIST_API, request);
for (JsonNode row : rows) {
String name = text(row.get("name"));
String alarmCode = text(row.get("alarmCode"));
if (rule.getRuleName().equals(name)
&& (!StringUtils.hasText(rule.getAlarmCode()) || rule.getAlarmCode().equals(alarmCode))) {
Long id = firstLong(row, "id", "ruleId", "findsRuleId");
if (id != null && onlyMatch != null && !onlyMatch.equals(id)) {
return null;
}
if (id != null) {
onlyMatch = id;
}
}
}
if (rows.size() < PAGE_SIZE) {
return onlyMatch;
}
}
return null;
}
public boolean isNotFound(JsonNode response) {
if (response == null) {
return false;
}
String code = response.path("code").asText("");
if ("404".equals(code) || "40400".equals(code) || "40004".equals(code)) {
return true;
}
return containsNotFound(response.path("msg").asText(""))
|| containsNotFound(response.path("detail").asText(""));
}
public boolean isNotFound(Throwable error) {
Throwable current = error;
while (current != null) {
if (containsNotFound(current.getMessage())) {
return true;
}
current = current.getCause();
}
return false;
}
private JsonNode listRows(OpenapiConfigDO config, String apiName, Map<String, Object> request) {
JsonNode response = findsOpenApiClient.post(config, apiName, request);
assertSuccess(response, apiName);
return extractRows(response.path("data"));
}
private JsonNode extractRows(JsonNode data) {
if (data == null || data.isMissingNode() || data.isNull()) {
return objectMapper.createArrayNode();
}
if (data.isArray()) {
return data;
}
for (String field : new String[]{"data", "rows", "records", "list"}) {
JsonNode rows = data.get(field);
if (rows != null && rows.isArray()) {
return rows;
}
}
JsonNode nested = data.get("data");
if (nested != null && nested.isObject()) {
return extractRows(nested);
}
return objectMapper.createArrayNode();
}
private void assertSuccess(JsonNode response, String apiName) {
int code = response == null ? -1 : response.path("code").asInt(-1);
if (code != 200 && code != 0) {
String message = response == null ? "" : response.path("msg").asText("");
throw new BizException("FindS查询接口调用失败" + apiName + "" + message);
}
}
private String firstText(JsonNode node, String... fields) {
for (String field : fields) {
String value = text(node.get(field));
if (StringUtils.hasText(value)) {
return value;
}
}
return null;
}
private Long firstLong(JsonNode node, String... fields) {
for (String field : fields) {
JsonNode value = node.get(field);
if (value == null || value.isNull()) {
continue;
}
if (value.canConvertToLong()) {
return value.asLong();
}
try {
return Long.valueOf(value.asText());
} catch (NumberFormatException ignored) {
// Try the next compatible field.
}
}
return null;
}
private String text(JsonNode node) {
if (node == null || node.isNull() || node.isMissingNode()) {
return null;
}
String value = node.asText();
return StringUtils.hasText(value) ? value : null;
}
private boolean containsNotFound(String message) {
if (!StringUtils.hasText(message)) {
return false;
}
String lower = message.toLowerCase();
return lower.contains("not found")
|| lower.contains("not exist")
|| lower.contains("no data")
|| message.contains("不存在")
|| message.contains("未找到")
|| message.contains("无此数据");
}
}

View File

@ -0,0 +1,261 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleFenceRelDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleItemDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FencePointDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalBindDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleFenceRelRepository;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleItemRepository;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleRepository;
import com.zcloud.personnel.positioning.persistence.repository.FencePointRepository;
import com.zcloud.personnel.positioning.persistence.repository.FenceRepository;
import com.zcloud.personnel.positioning.persistence.repository.TerminalBindRepository;
import com.zcloud.personnel.positioning.persistence.repository.TerminalRepository;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Component
@AllArgsConstructor
public class FindsPendingSyncService {
public static final String STATUS_WAIT = "WAIT";
public static final String STATUS_FAIL = "FAIL";
public static final String STATUS_DELETE_WAIT = "DELETE_WAIT";
public static final String STATUS_DELETE_FAIL = "DELETE_FAIL";
private static final Logger LOGGER = LoggerFactory.getLogger(FindsPendingSyncService.class);
private static final int RETRY_BATCH_SIZE = 100;
private final FenceRepository fenceRepository;
private final FencePointRepository fencePointRepository;
private final AlarmRuleRepository alarmRuleRepository;
private final AlarmRuleFenceRelRepository alarmRuleFenceRelRepository;
private final AlarmRuleItemRepository alarmRuleItemRepository;
private final TerminalRepository terminalRepository;
private final TerminalBindRepository terminalBindRepository;
private final FenceFindsSyncService fenceFindsSyncService;
private final AlarmRuleFindsSyncService alarmRuleFindsSyncService;
private final TerminalFindsSyncService terminalFindsSyncService;
private final FindsSyncStateService findsSyncStateService;
private final AfterCommitExecutor afterCommitExecutor;
private final TransactionTemplate transactionTemplate;
private final ObjectMapper objectMapper;
private final Set<String> inFlight = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
public void scheduleFence(Long id) {
afterCommitExecutor.execute(() -> syncFence(id));
}
public void scheduleRule(Long id) {
afterCommitExecutor.execute(() -> syncRule(id));
}
public void scheduleTerminal(Long id) {
afterCommitExecutor.execute(() -> syncTerminal(id));
}
public void retryPending() {
retryFences();
retryTerminals();
retryRules();
int failureCount = countFailures();
if (failureCount > 0) {
throw new BizException("FindS待同步数据仍有 " + failureCount + " 条失败记录");
}
}
public void syncFence(Long id) {
withLock("FENCE:" + id, () -> {
FenceDO fence = fenceRepository.getById(id);
if (fence == null) {
return;
}
try {
if (isDeleteStatus(fence.getSyncStatus())) {
fenceFindsSyncService.delete(fence);
transactionTemplate.execute(status -> {
if (findsSyncStateService.deleteFenceIfCurrent(fence)) {
QueryWrapper<FencePointDO> pointWrapper = new QueryWrapper<>();
pointWrapper.eq("fence_id", id);
fencePointRepository.remove(pointWrapper);
}
return null;
});
} else {
fenceFindsSyncService.createOrUpdate(fence);
}
} catch (Exception e) {
findsSyncStateService.failFence(fence, isDeleteStatus(fence.getSyncStatus()), errorJson(e));
LOGGER.warn("Synchronize fence to FindS failed, id={}", id, e);
}
});
}
public void syncRule(Long id) {
withLock("RULE:" + id, () -> {
AlarmRuleDO rule = alarmRuleRepository.getById(id);
if (rule == null) {
return;
}
try {
if (isDeleteStatus(rule.getSyncStatus())) {
alarmRuleFindsSyncService.delete(rule);
transactionTemplate.execute(status -> {
if (findsSyncStateService.deleteAlarmRuleIfCurrent(rule)) {
QueryWrapper<AlarmRuleFenceRelDO> relWrapper = new QueryWrapper<>();
relWrapper.eq("rule_id", id);
alarmRuleFenceRelRepository.remove(relWrapper);
QueryWrapper<AlarmRuleItemDO> itemWrapper = new QueryWrapper<>();
itemWrapper.eq("rule_id", id);
alarmRuleItemRepository.remove(itemWrapper);
}
return null;
});
} else {
alarmRuleFindsSyncService.createOrUpdate(rule);
}
} catch (Exception e) {
findsSyncStateService.failAlarmRule(rule, isDeleteStatus(rule.getSyncStatus()), errorJson(e));
LOGGER.warn("Synchronize alarm rule to FindS failed, id={}", id, e);
}
});
}
public void syncTerminal(Long id) {
withLock("TERMINAL:" + id, () -> {
TerminalDO terminal = terminalRepository.getById(id);
if (terminal == null) {
return;
}
try {
if (isDeleteStatus(terminal.getSyncStatus())) {
terminalFindsSyncService.delete(terminal);
transactionTemplate.execute(status -> {
if (findsSyncStateService.deleteTerminalIfCurrent(terminal)) {
QueryWrapper<TerminalBindDO> bindWrapper = new QueryWrapper<>();
bindWrapper.eq("terminal_id", id);
terminalBindRepository.remove(bindWrapper);
}
return null;
});
} else if (STATUS_WAIT.equals(terminal.getSyncStatus()) || STATUS_FAIL.equals(terminal.getSyncStatus())) {
terminalFindsSyncService.createOrUpdate(terminal);
} else {
terminalFindsSyncService.syncPendingBindings(terminal);
}
} catch (Exception e) {
findsSyncStateService.failTerminal(terminal, isDeleteStatus(terminal.getSyncStatus()), errorJson(e));
LOGGER.warn("Synchronize terminal to FindS failed, id={}", id, e);
}
});
}
private void retryFences() {
QueryWrapper<FenceDO> wrapper = pendingWrapper();
List<FenceDO> rows = fenceRepository.list(wrapper);
for (FenceDO row : rows) {
syncFence(row.getId());
}
}
private void retryRules() {
QueryWrapper<AlarmRuleDO> wrapper = pendingWrapper();
List<AlarmRuleDO> rows = alarmRuleRepository.list(wrapper);
for (AlarmRuleDO row : rows) {
syncRule(row.getId());
}
}
private void retryTerminals() {
QueryWrapper<TerminalDO> wrapper = pendingWrapper();
List<TerminalDO> rows = terminalRepository.list(wrapper);
Set<Long> terminalIds = Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
for (TerminalDO row : rows) {
terminalIds.add(row.getId());
}
QueryWrapper<TerminalBindDO> bindWrapper = new QueryWrapper<>();
bindWrapper.in("sync_status", STATUS_WAIT, STATUS_FAIL);
bindWrapper.and(value -> value.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
bindWrapper.last("LIMIT " + RETRY_BATCH_SIZE);
for (TerminalBindDO binding : terminalBindRepository.list(bindWrapper)) {
if (binding.getTerminalId() != null) {
terminalIds.add(binding.getTerminalId());
}
}
for (Long terminalId : terminalIds) {
syncTerminal(terminalId);
}
}
private <T> QueryWrapper<T> pendingWrapper() {
QueryWrapper<T> wrapper = new QueryWrapper<>();
wrapper.in("sync_status", STATUS_WAIT, STATUS_FAIL, STATUS_DELETE_WAIT, STATUS_DELETE_FAIL);
wrapper.and(value -> value.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
wrapper.orderByAsc("update_time", "id");
wrapper.last("LIMIT " + RETRY_BATCH_SIZE);
return wrapper;
}
private int countFailures() {
return safeCount(fenceRepository.count(failureWrapper()))
+ safeCount(alarmRuleRepository.count(failureWrapper()))
+ safeCount(terminalRepository.count(failureWrapper()))
+ safeCount(terminalBindRepository.count(failureWrapper()));
}
private <T> QueryWrapper<T> failureWrapper() {
QueryWrapper<T> wrapper = new QueryWrapper<>();
wrapper.in("sync_status", STATUS_FAIL, STATUS_DELETE_FAIL);
wrapper.and(value -> value.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
return wrapper;
}
private int safeCount(long count) {
return count > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) count;
}
private boolean isDeleteStatus(String status) {
return STATUS_DELETE_WAIT.equals(status) || STATUS_DELETE_FAIL.equals(status);
}
private String errorJson(Exception e) {
try {
return objectMapper.writeValueAsString(Collections.singletonMap("lastError", safeMessage(e)));
} catch (Exception ignored) {
return "{\"lastError\":\"FindS synchronization failed\"}";
}
}
private String safeMessage(Exception e) {
String message = e == null ? null : e.getMessage();
if (message == null) {
return "FindS synchronization failed";
}
return message.length() > 1000 ? message.substring(0, 1000) : message;
}
private void withLock(String key, Runnable task) {
if (!inFlight.add(key)) {
return;
}
try {
task.run();
} finally {
inFlight.remove(key);
}
}
}

View File

@ -0,0 +1,25 @@
package com.zcloud.personnel.positioning.command;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class FindsSyncAsyncConfiguration {
@Bean(name = "findsSyncTaskExecutor")
public TaskExecutor findsSyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("finds-sync-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(10);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}

View File

@ -0,0 +1,227 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalBindDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalDO;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Component
public class FindsSyncStateService {
private static final String STATUS_SUCCESS = "SUCCESS";
private final JdbcTemplate jdbcTemplate;
public FindsSyncStateService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public boolean completeFence(FenceDO snapshot, Long findsId, String findsCode, String rawJson) {
int updated = guardedUpdate("fence", snapshot.getId(), snapshot.getSyncToken(), false,
"finds_fence_id = ?, finds_fence_code = ?, sync_status = ?, last_sync_time = ?, raw_json = ?",
findsId, findsCode, STATUS_SUCCESS, LocalDateTime.now(), rawJson);
if (updated == 0) {
recordFenceMapping(snapshot.getId(), findsId, findsCode);
}
return updated > 0;
}
public boolean completeAlarmRule(AlarmRuleDO snapshot, Long findsId, String rawJson) {
int updated = guardedUpdate("alarm_rule", snapshot.getId(), snapshot.getSyncToken(), false,
"finds_rule_id = ?, sync_status = ?, last_sync_time = ?, raw_json = ?",
findsId, STATUS_SUCCESS, LocalDateTime.now(), rawJson);
if (updated == 0) {
recordAlarmRuleMapping(snapshot.getId(), findsId);
}
return updated > 0;
}
public boolean completeTerminal(TerminalDO snapshot, Long findsId, String rawJson) {
int updated = guardedUpdate("terminal", snapshot.getId(), snapshot.getSyncToken(), false,
"finds_terminal_id = ?, sync_status = ?, last_sync_time = ?, raw_json = ?",
findsId, STATUS_SUCCESS, LocalDateTime.now(), rawJson);
if (updated == 0) {
recordTerminalMapping(snapshot.getId(), findsId);
}
return updated > 0;
}
public boolean completeTerminalBinding(TerminalBindDO snapshot, Long findsTerminalId) {
return guardedUpdate("terminal_bind", snapshot.getId(), snapshot.getSyncToken(), false,
"finds_terminal_id = ?, sync_status = ?, last_sync_time = ?",
findsTerminalId, STATUS_SUCCESS, LocalDateTime.now()) > 0;
}
public void failFence(FenceDO snapshot, boolean deleting, String rawJson) {
guardedFailure("fence", snapshot.getId(), snapshot.getSyncToken(), deleting, rawJson);
}
public void failAlarmRule(AlarmRuleDO snapshot, boolean deleting, String rawJson) {
guardedFailure("alarm_rule", snapshot.getId(), snapshot.getSyncToken(), deleting, rawJson);
}
public void failTerminal(TerminalDO snapshot, boolean deleting, String rawJson) {
guardedFailure("terminal", snapshot.getId(), snapshot.getSyncToken(), deleting, rawJson);
}
public void failTerminalBinding(TerminalBindDO snapshot) {
guardedFailure("terminal_bind", snapshot.getId(), snapshot.getSyncToken(), false, null);
}
public void recordFenceMapping(Long id, Long findsId, String findsCode) {
if (id == null || findsId == null) {
return;
}
jdbcTemplate.update("UPDATE fence SET finds_fence_id = COALESCE(finds_fence_id, ?), "
+ "finds_fence_code = COALESCE(finds_fence_code, ?) WHERE id = ?", findsId, findsCode, id);
}
public void recordAlarmRuleMapping(Long id, Long findsId) {
if (id == null || findsId == null) {
return;
}
jdbcTemplate.update("UPDATE alarm_rule SET finds_rule_id = COALESCE(finds_rule_id, ?) WHERE id = ?", findsId, id);
}
public void recordTerminalMapping(Long id, Long findsId) {
if (id == null || findsId == null) {
return;
}
jdbcTemplate.update("UPDATE terminal SET finds_terminal_id = COALESCE(finds_terminal_id, ?) WHERE id = ?", findsId, id);
}
public boolean clearFenceMapping(FenceDO snapshot, Long expectedFindsId) {
return snapshot != null && guardedClearMapping("fence", "finds_fence_id = NULL, finds_fence_code = NULL",
"finds_fence_id", snapshot.getId(), snapshot.getSyncToken(), expectedFindsId) > 0;
}
public boolean clearAlarmRuleMapping(AlarmRuleDO snapshot, Long expectedFindsId) {
return snapshot != null && guardedClearMapping("alarm_rule", "finds_rule_id = NULL", "finds_rule_id",
snapshot.getId(), snapshot.getSyncToken(), expectedFindsId) > 0;
}
public boolean clearTerminalMapping(TerminalDO snapshot, Long expectedFindsId) {
return snapshot != null && guardedClearMapping("terminal", "finds_terminal_id = NULL", "finds_terminal_id",
snapshot.getId(), snapshot.getSyncToken(), expectedFindsId) > 0;
}
public boolean isCurrentFence(FenceDO snapshot) {
return snapshot != null && isCurrentChange("fence", snapshot.getId(), snapshot.getSyncToken());
}
public boolean isCurrentAlarmRule(AlarmRuleDO snapshot) {
return snapshot != null && isCurrentChange("alarm_rule", snapshot.getId(), snapshot.getSyncToken());
}
public boolean isCurrentTerminal(TerminalDO snapshot) {
return snapshot != null && isCurrentChange("terminal", snapshot.getId(), snapshot.getSyncToken());
}
public void recordAreaFenceMapping(Long areaId, Long fenceId, Long findsFenceId) {
if (areaId == null || findsFenceId == null) {
return;
}
jdbcTemplate.update("UPDATE area SET bind_fence_id = ?, finds_fence_id = ? WHERE id = ?",
fenceId, findsFenceId, areaId);
}
public boolean deleteFenceIfCurrent(FenceDO snapshot) {
return guardedDelete("fence", snapshot.getId(), snapshot.getSyncToken()) > 0;
}
public boolean deleteAlarmRuleIfCurrent(AlarmRuleDO snapshot) {
return guardedDelete("alarm_rule", snapshot.getId(), snapshot.getSyncToken()) > 0;
}
public boolean deleteTerminalIfCurrent(TerminalDO snapshot) {
return guardedDelete("terminal", snapshot.getId(), snapshot.getSyncToken()) > 0;
}
private void guardedFailure(String table, Long id, String token, boolean deleting, String rawJson) {
String status = deleting ? FindsPendingSyncService.STATUS_DELETE_FAIL : FindsPendingSyncService.STATUS_FAIL;
if (rawJson == null) {
guardedUpdate(table, id, token, deleting,
"sync_status = ?, last_sync_time = ?", status, LocalDateTime.now());
return;
}
guardedUpdate(table, id, token, deleting,
"sync_status = ?, last_sync_time = ?, raw_json = ?", status, LocalDateTime.now(), rawJson);
}
private int guardedUpdate(String table, Long id, String token, boolean deleting,
String setClause, Object... setArgs) {
List<Object> args = new ArrayList<>();
for (Object setArg : setArgs) {
args.add(setArg);
}
args.add(id);
StringBuilder sql = new StringBuilder("UPDATE ").append(table).append(" SET ")
.append(setClause).append(" WHERE id = ? AND sync_status IN (?, ?) AND ");
if (deleting) {
args.add(FindsPendingSyncService.STATUS_DELETE_WAIT);
args.add(FindsPendingSyncService.STATUS_DELETE_FAIL);
} else {
args.add(FindsPendingSyncService.STATUS_WAIT);
args.add(FindsPendingSyncService.STATUS_FAIL);
}
appendTokenCondition(sql, args, token);
return jdbcTemplate.update(sql.toString(), args.toArray());
}
private int guardedDelete(String table, Long id, String token) {
List<Object> args = new ArrayList<>();
args.add(id);
StringBuilder sql = new StringBuilder("DELETE FROM ").append(table)
.append(" WHERE id = ? AND sync_status IN (?, ?) AND ");
args.add(FindsPendingSyncService.STATUS_DELETE_WAIT);
args.add(FindsPendingSyncService.STATUS_DELETE_FAIL);
appendTokenCondition(sql, args, token);
return jdbcTemplate.update(sql.toString(), args.toArray());
}
private int guardedClearMapping(String table, String setClause, String mappingColumn,
Long id, String token, Long expectedFindsId) {
if (id == null || expectedFindsId == null) {
return 0;
}
List<Object> args = new ArrayList<>();
args.add(id);
args.add(expectedFindsId);
args.add(FindsPendingSyncService.STATUS_WAIT);
args.add(FindsPendingSyncService.STATUS_FAIL);
StringBuilder sql = new StringBuilder("UPDATE ").append(table).append(" SET ").append(setClause)
.append(" WHERE id = ? AND ").append(mappingColumn)
.append(" = ? AND sync_status IN (?, ?) AND ");
appendTokenCondition(sql, args, token);
return jdbcTemplate.update(sql.toString(), args.toArray());
}
private boolean isCurrentChange(String table, Long id, String token) {
if (id == null) {
return false;
}
List<Object> args = new ArrayList<>();
args.add(id);
args.add(FindsPendingSyncService.STATUS_WAIT);
args.add(FindsPendingSyncService.STATUS_FAIL);
StringBuilder sql = new StringBuilder("SELECT COUNT(1) FROM ").append(table)
.append(" WHERE id = ? AND sync_status IN (?, ?) AND ");
appendTokenCondition(sql, args, token);
Long count = jdbcTemplate.queryForObject(sql.toString(), args.toArray(), Long.class);
return count != null && count > 0;
}
private void appendTokenCondition(StringBuilder sql, List<Object> args, String token) {
if (token == null) {
sql.append("sync_token IS NULL");
} else {
sql.append("sync_token = ?");
args.add(token);
}
}
}

View File

@ -0,0 +1,12 @@
package com.zcloud.personnel.positioning.command;
import java.util.UUID;
public final class FindsSyncToken {
private FindsSyncToken() {
}
public static String next() {
return UUID.randomUUID().toString().replace("-", "");
}
}

View File

@ -0,0 +1,43 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.OpenapiConfigGateway;
import com.zcloud.personnel.positioning.domain.model.OpenapiConfigE;
import com.zcloud.personnel.positioning.dto.OpenapiConfigAddCmd;
import com.zcloud.personnel.positioning.integration.finds.OpenapiConfigCache;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class OpenapiConfigAddExe {
private final OpenapiConfigGateway openapiConfigGateway;
private final OpenapiConfigCache openapiConfigCache;
@Transactional(rollbackFor = Exception.class)
public boolean execute(OpenapiConfigAddCmd cmd) {
OpenapiConfigE openapiConfigE = new OpenapiConfigE();
BeanUtils.copyProperties(cmd, openapiConfigE);
boolean res;
try {
res = openapiConfigGateway.add(openapiConfigE);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (res) {
openapiConfigCache.evictAll();
}
if (!res) {
throw new BizException("保存失败");
}
return true;
}
}

View File

@ -0,0 +1,45 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.OpenapiConfigGateway;
import com.alibaba.cola.exception.BizException;
import com.zcloud.personnel.positioning.integration.finds.OpenapiConfigCache;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class OpenapiConfigRemoveExe {
private final OpenapiConfigGateway openapiConfigGateway;
private final OpenapiConfigCache openapiConfigCache;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = openapiConfigGateway.deletedOpenapiConfigById(id);
if (res) {
openapiConfigCache.evictAll();
}
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = openapiConfigGateway.deletedOpenapiConfigByIds(ids);
if (res) {
openapiConfigCache.evictAll();
}
if (!res) {
throw new BizException("删除失败");
}
return true;
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.OpenapiConfigGateway;
import com.zcloud.personnel.positioning.domain.model.OpenapiConfigE;
import com.zcloud.personnel.positioning.dto.OpenapiConfigUpdateCmd;
import com.zcloud.personnel.positioning.integration.finds.OpenapiConfigCache;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class OpenapiConfigUpdateExe {
private final OpenapiConfigGateway openapiConfigGateway;
private final OpenapiConfigCache openapiConfigCache;
@Transactional(rollbackFor = Exception.class)
public void execute(OpenapiConfigUpdateCmd cmd) {
OpenapiConfigE openapiConfigE = new OpenapiConfigE();
BeanUtils.copyProperties(cmd, openapiConfigE);
boolean res = openapiConfigGateway.update(openapiConfigE);
if (res) {
openapiConfigCache.evictAll();
}
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,38 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.SyncLogGateway;
import com.zcloud.personnel.positioning.domain.model.SyncLogE;
import com.zcloud.personnel.positioning.dto.SyncLogAddCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class SyncLogAddExe {
private final SyncLogGateway syncLogGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(SyncLogAddCmd cmd) {
SyncLogE syncLogE = new SyncLogE();
BeanUtils.copyProperties(cmd, syncLogE);
boolean res;
try {
res = syncLogGateway.add(syncLogE);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!res) {
throw new BizException("保存失败");
}
return true;
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.SyncLogGateway;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class SyncLogRemoveExe {
private final SyncLogGateway syncLogGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = syncLogGateway.deletedSyncLogById(id);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = syncLogGateway.deletedSyncLogByIds(ids);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
}

View File

@ -0,0 +1,32 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.SyncLogGateway;
import com.zcloud.personnel.positioning.domain.model.SyncLogE;
import com.zcloud.personnel.positioning.dto.SyncLogUpdateCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class SyncLogUpdateExe {
private final SyncLogGateway syncLogGateway;
@Transactional(rollbackFor = Exception.class)
public void execute(SyncLogUpdateCmd cmd) {
SyncLogE syncLogE = new SyncLogE();
BeanUtils.copyProperties(cmd, syncLogE);
boolean res = syncLogGateway.update(syncLogE);
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,38 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.SyncTaskGateway;
import com.zcloud.personnel.positioning.domain.model.SyncTaskE;
import com.zcloud.personnel.positioning.dto.SyncTaskAddCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class SyncTaskAddExe {
private final SyncTaskGateway syncTaskGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(SyncTaskAddCmd cmd) {
SyncTaskE syncTaskE = new SyncTaskE();
BeanUtils.copyProperties(cmd, syncTaskE);
boolean res;
try {
res = syncTaskGateway.add(syncTaskE);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!res) {
throw new BizException("保存失败");
}
return true;
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.SyncTaskGateway;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class SyncTaskRemoveExe {
private final SyncTaskGateway syncTaskGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = syncTaskGateway.deletedSyncTaskById(id);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = syncTaskGateway.deletedSyncTaskByIds(ids);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
}

View File

@ -0,0 +1,32 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.SyncTaskGateway;
import com.zcloud.personnel.positioning.domain.model.SyncTaskE;
import com.zcloud.personnel.positioning.dto.SyncTaskUpdateCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class SyncTaskUpdateExe {
private final SyncTaskGateway syncTaskGateway;
@Transactional(rollbackFor = Exception.class)
public void execute(SyncTaskUpdateCmd cmd) {
SyncTaskE syncTaskE = new SyncTaskE();
BeanUtils.copyProperties(cmd, syncTaskE);
boolean res = syncTaskGateway.update(syncTaskE);
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,40 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.dto.TerminalAddCmd;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalDO;
import com.zcloud.personnel.positioning.persistence.repository.TerminalRepository;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class TerminalAddExe {
private final TerminalRepository terminalRepository;
private final FindsPendingSyncService findsPendingSyncService;
@Transactional(rollbackFor = Exception.class)
public boolean execute(TerminalAddCmd cmd) {
TerminalDO terminal = new TerminalDO();
BeanUtils.copyProperties(cmd, terminal);
terminal.setFindsTerminalId(null);
terminal.setSyncStatus(FindsPendingSyncService.STATUS_WAIT);
terminal.setSyncToken(FindsSyncToken.next());
terminal.setLastSyncTime(null);
terminal.setRawJson(null);
boolean res = terminalRepository.save(terminal);
if (!res) {
throw new BizException("保存失败");
}
findsPendingSyncService.scheduleTerminal(terminal.getId());
return true;
}
}

View File

@ -0,0 +1,38 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.TerminalBindGateway;
import com.zcloud.personnel.positioning.domain.model.TerminalBindE;
import com.zcloud.personnel.positioning.dto.TerminalBindAddCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class TerminalBindAddExe {
private final TerminalBindGateway terminalBindGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(TerminalBindAddCmd cmd) {
TerminalBindE terminalBindE = new TerminalBindE();
BeanUtils.copyProperties(cmd, terminalBindE);
boolean res;
try {
res = terminalBindGateway.add(terminalBindE);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!res) {
throw new BizException("保存失败");
}
return true;
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.TerminalBindGateway;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class TerminalBindRemoveExe {
private final TerminalBindGateway terminalBindGateway;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
boolean res = terminalBindGateway.deletedTerminalBindById(id);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
boolean res = terminalBindGateway.deletedTerminalBindByIds(ids);
if (!res) {
throw new BizException("删除失败");
}
return true;
}
}

View File

@ -0,0 +1,32 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.domain.gateway.TerminalBindGateway;
import com.zcloud.personnel.positioning.domain.model.TerminalBindE;
import com.zcloud.personnel.positioning.dto.TerminalBindUpdateCmd;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class TerminalBindUpdateExe {
private final TerminalBindGateway terminalBindGateway;
@Transactional(rollbackFor = Exception.class)
public void execute(TerminalBindUpdateCmd cmd) {
TerminalBindE terminalBindE = new TerminalBindE();
BeanUtils.copyProperties(cmd, terminalBindE);
boolean res = terminalBindGateway.update(terminalBindE);
if (!res) {
throw new BizException("修改失败");
}
}
}

View File

@ -0,0 +1,258 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
import com.zcloud.personnel.positioning.integration.finds.OpenapiConfigCache;
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalBindDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalDO;
import com.zcloud.personnel.positioning.persistence.repository.TerminalBindRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Component
@AllArgsConstructor
public class TerminalFindsSyncService {
private static final String CREATE_API = "finds.terminal.create";
private static final String UPDATE_API = "finds.terminal.update";
private static final String DELETE_API = "finds.terminal.delete";
private static final String BIND_API = "finds.track.terminal.bind";
private static final String UNBIND_API = "finds.track.terminal.unbind";
private static final String STATUS_SUCCESS = "SUCCESS";
private final FindsOpenApiClient findsOpenApiClient;
private final OpenapiConfigCache openapiConfigCache;
private final TerminalBindRepository terminalBindRepository;
private final FindsSyncStateService findsSyncStateService;
private final FindsEntityLookupService findsEntityLookupService;
public void createOrUpdate(TerminalDO terminal) {
OpenapiConfigDO config = enabledConfig(terminal);
if (terminal.getFindsTerminalId() == null) {
Long existingId = findsEntityLookupService.findTerminalId(config, terminal);
if (existingId != null) {
terminal.setFindsTerminalId(existingId);
findsSyncStateService.recordTerminalMapping(terminal.getId(), existingId);
}
}
assertCurrent(terminal);
JsonNode response = terminal.getFindsTerminalId() == null
? createWithRecovery(config, terminal)
: updateOrRecreate(config, terminal);
boolean current = findsSyncStateService.completeTerminal(terminal, terminal.getFindsTerminalId(), response.toString());
if (current) {
syncPendingBindings(terminal, config);
}
}
public void delete(TerminalDO terminal) {
if (terminal == null || terminal.getFindsTerminalId() == null) {
return;
}
Map<String, Object> request = new LinkedHashMap<>();
request.put("id", terminal.getFindsTerminalId());
try {
JsonNode response = findsOpenApiClient.post(enabledConfig(terminal), DELETE_API, request);
if (!findsEntityLookupService.isNotFound(response)) {
assertSuccess(response, DELETE_API);
}
} catch (RuntimeException e) {
if (!findsEntityLookupService.isNotFound(e)) {
throw e;
}
}
}
public void syncPendingBindings(TerminalDO terminal) {
syncPendingBindings(terminal, enabledConfig(terminal));
}
private void syncPendingBindings(TerminalDO terminal, OpenapiConfigDO config) {
if (terminal.getFindsTerminalId() == null) {
return;
}
QueryWrapper<TerminalBindDO> wrapper = new QueryWrapper<>();
wrapper.eq("terminal_id", terminal.getId());
wrapper.in("sync_status", FindsPendingSyncService.STATUS_WAIT, FindsPendingSyncService.STATUS_FAIL);
wrapper.and(value -> value.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
wrapper.orderByAsc("id");
List<TerminalBindDO> bindings = terminalBindRepository.list(wrapper);
for (TerminalBindDO binding : bindings) {
try {
Map<String, Object> request = buildBindingRequest(terminal, binding);
String api = "UNBOUND".equalsIgnoreCase(binding.getBindStatus()) ? UNBIND_API : BIND_API;
JsonNode response = findsOpenApiClient.post(config, api, request);
assertSuccess(response, api);
findsSyncStateService.completeTerminalBinding(binding, terminal.getFindsTerminalId());
} catch (Exception e) {
findsSyncStateService.failTerminalBinding(binding);
break;
}
}
}
private OpenapiConfigDO enabledConfig(TerminalDO terminal) {
OpenapiConfigDO config = openapiConfigCache.getEnabledConfig(terminal.getCorpinfoId());
if (config == null) {
throw new BizException("未配置启用的FindS OpenAPI配置");
}
return config;
}
private JsonNode createWithRecovery(OpenapiConfigDO config, TerminalDO terminal) {
RuntimeException createError = null;
try {
JsonNode response = findsOpenApiClient.post(config, CREATE_API, buildSaveRequest(terminal, false));
if (isSuccess(response)) {
applyCreatedTerminalId(terminal, response);
findsSyncStateService.recordTerminalMapping(terminal.getId(), terminal.getFindsTerminalId());
return response;
}
createError = apiError(response, CREATE_API);
} catch (RuntimeException e) {
createError = e;
}
Long recoveredId = tryFindTerminalId(config, terminal);
if (recoveredId == null) {
throw createError;
}
terminal.setFindsTerminalId(recoveredId);
findsSyncStateService.recordTerminalMapping(terminal.getId(), recoveredId);
assertCurrent(terminal);
return updateOrRecreate(config, terminal, false);
}
private JsonNode updateOrRecreate(OpenapiConfigDO config, TerminalDO terminal) {
return updateOrRecreate(config, terminal, true);
}
private JsonNode updateOrRecreate(OpenapiConfigDO config, TerminalDO terminal, boolean recreateOnNotFound) {
Long oldFindsId = terminal.getFindsTerminalId();
try {
JsonNode response = findsOpenApiClient.post(config, UPDATE_API, buildSaveRequest(terminal, true));
if (!findsEntityLookupService.isNotFound(response)) {
assertSuccess(response, UPDATE_API);
return response;
}
} catch (RuntimeException e) {
if (!findsEntityLookupService.isNotFound(e)) {
throw e;
}
}
if (!findsSyncStateService.clearTerminalMapping(terminal, oldFindsId)) {
throw new BizException("FindS terminal synchronization snapshot is stale");
}
terminal.setFindsTerminalId(null);
if (!recreateOnNotFound) {
throw new BizException("FindS terminal recovery failed: recovered id does not exist");
}
return createWithRecovery(config, terminal);
}
private void assertCurrent(TerminalDO terminal) {
if (!findsSyncStateService.isCurrentTerminal(terminal)) {
throw new BizException("FindS terminal synchronization snapshot is stale");
}
}
private Long tryFindTerminalId(OpenapiConfigDO config, TerminalDO terminal) {
try {
return findsEntityLookupService.findTerminalId(config, terminal);
} catch (RuntimeException ignored) {
return null;
}
}
private void applyCreatedTerminalId(TerminalDO terminal, JsonNode response) {
Long findsTerminalId = responseId(response.path("data"));
if (findsTerminalId == null) {
throw new BizException("FindS终端创建成功但未返回终端ID");
}
terminal.setFindsTerminalId(findsTerminalId);
}
private Map<String, Object> buildSaveRequest(TerminalDO terminal, boolean update) {
Map<String, Object> request = new LinkedHashMap<>();
if (update) {
request.put("id", terminal.getFindsTerminalId());
request.put("terminalId", terminal.getFindsTerminalId());
}
putIfText(request, "terminalNo", terminal.getTerminalNo());
putIfText(request, "name", terminal.getTerminalName());
putIfText(request, "terminalName", terminal.getTerminalName());
putIfText(request, "terminalModel", terminal.getTerminalModel());
putIfText(request, "simCardNo", terminal.getSimCardNo());
putIfText(request, "checkCode", terminal.getCheckCode());
putIfText(request, "orgCode", terminal.getOrgCode());
putIfText(request, "orgName", terminal.getOrgName());
if ("APP".equalsIgnoreCase(terminal.getDeviceType())) {
request.put("bizName", "手机APP定位");
}
request.put("enable", terminal.getEnable() == null || terminal.getEnable() != 0);
return request;
}
private Map<String, Object> buildBindingRequest(TerminalDO terminal, TerminalBindDO binding) {
Map<String, Object> request = new LinkedHashMap<>();
request.put("trackType", StringUtils.hasText(binding.getTrackType()) ? binding.getTrackType() : "STAFF");
putIfText(request, "terminalNo", terminal.getTerminalNo());
request.put("terminalId", terminal.getFindsTerminalId());
putIfText(request, "idCardNo", binding.getIdCardNo());
putIfText(request, "staffNo", binding.getStaffNoSnapshot());
putIfText(request, "staffName", binding.getStaffNameSnapshot());
return request;
}
private void assertSuccess(JsonNode response, String apiName) {
if (!isSuccess(response)) {
throw apiError(response, apiName);
}
}
private boolean isSuccess(JsonNode response) {
int code = response == null ? -1 : response.path("code").asInt(-1);
return code == 200 || code == 0;
}
private BizException apiError(JsonNode response, String apiName) {
String msg = response == null ? "" : response.path("msg").asText("");
return new BizException("FindS接口调用失败" + apiName + "" + msg);
}
private Long responseId(JsonNode data) {
if (data == null || data.isMissingNode() || data.isNull()) {
return null;
}
if (data.isNumber()) {
return data.asLong();
}
for (String field : new String[]{"id", "terminalId", "findsTerminalId"}) {
JsonNode value = data.get(field);
if (value != null && value.canConvertToLong()) {
return value.asLong();
}
if (value != null && StringUtils.hasText(value.asText())) {
try {
return Long.valueOf(value.asText());
} catch (NumberFormatException ignored) {
// Try the next compatible field.
}
}
}
return null;
}
private void putIfText(Map<String, Object> request, String key, String value) {
if (StringUtils.hasText(value)) {
request.put(key, value);
}
}
}

View File

@ -0,0 +1,48 @@
package com.zcloud.personnel.positioning.command;
import com.alibaba.cola.exception.BizException;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalDO;
import com.zcloud.personnel.positioning.persistence.repository.TerminalRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class TerminalRemoveExe {
private final TerminalRepository terminalRepository;
private final FindsPendingSyncService findsPendingSyncService;
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long id) {
TerminalDO terminal = terminalRepository.getById(id);
if (terminal == null) {
throw new BizException("终端不存在");
}
terminal.setSyncStatus(FindsPendingSyncService.STATUS_DELETE_WAIT);
terminal.setSyncToken(FindsSyncToken.next());
boolean res = terminalRepository.updateById(terminal);
if (!res) {
throw new BizException("删除失败");
}
findsPendingSyncService.scheduleTerminal(id);
return true;
}
@Transactional(rollbackFor = Exception.class)
public boolean execute(Long[] ids) {
if (ids == null || ids.length == 0) {
return true;
}
for (Long id : ids) {
execute(id);
}
return true;
}
}

View File

@ -0,0 +1,44 @@
package com.zcloud.personnel.positioning.command;
import com.zcloud.personnel.positioning.dto.TerminalUpdateCmd;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalDO;
import com.zcloud.personnel.positioning.persistence.repository.TerminalRepository;
import com.alibaba.cola.exception.BizException;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Component
@AllArgsConstructor
public class TerminalUpdateExe {
private final TerminalRepository terminalRepository;
private final FindsPendingSyncService findsPendingSyncService;
@Transactional(rollbackFor = Exception.class)
public void execute(TerminalUpdateCmd cmd) {
if (cmd == null || cmd.getId() == null) {
throw new BizException("终端ID不能为空");
}
TerminalDO terminal = terminalRepository.getById(cmd.getId());
if (terminal == null) {
throw new BizException("终端不存在");
}
Long findsTerminalId = terminal.getFindsTerminalId();
BeanUtils.copyProperties(cmd, terminal, "findsTerminalId", "syncStatus", "syncToken", "lastSyncTime", "rawJson");
terminal.setFindsTerminalId(findsTerminalId);
terminal.setSyncStatus(FindsPendingSyncService.STATUS_WAIT);
terminal.setSyncToken(FindsSyncToken.next());
boolean res = terminalRepository.updateById(terminal);
if (!res) {
throw new BizException("修改失败");
}
findsPendingSyncService.scheduleTerminal(terminal.getId());
}
}

View File

@ -0,0 +1,20 @@
package com.zcloud.personnel.positioning.command.convertor;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmDisposeRecordCO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmDisposeRecordDO;
import org.mapstruct.Mapper;
import java.util.List;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Mapper(componentModel = "spring")
public interface AlarmDisposeRecordCoConvertor {
AlarmDisposeRecordCO converDOToCO(AlarmDisposeRecordDO alarmDisposeRecordDO);
List<AlarmDisposeRecordCO> converDOsToCOs(List<AlarmDisposeRecordDO> alarmDisposeRecordDOs);
}

View File

@ -0,0 +1,20 @@
package com.zcloud.personnel.positioning.command.convertor;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRecordCO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRecordDO;
import org.mapstruct.Mapper;
import java.util.List;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Mapper(componentModel = "spring")
public interface AlarmRecordCoConvertor {
AlarmRecordCO converDOToCO(AlarmRecordDO alarmRecordDO);
List<AlarmRecordCO> converDOsToCOs(List<AlarmRecordDO> alarmRecordDOs);
}

View File

@ -0,0 +1,20 @@
package com.zcloud.personnel.positioning.command.convertor;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRecordTargetCO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRecordTargetDO;
import org.mapstruct.Mapper;
import java.util.List;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Mapper(componentModel = "spring")
public interface AlarmRecordTargetCoConvertor {
AlarmRecordTargetCO converDOToCO(AlarmRecordTargetDO alarmRecordTargetDO);
List<AlarmRecordTargetCO> converDOsToCOs(List<AlarmRecordTargetDO> alarmRecordTargetDOs);
}

View File

@ -0,0 +1,20 @@
package com.zcloud.personnel.positioning.command.convertor;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRuleCO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import org.mapstruct.Mapper;
import java.util.List;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Mapper(componentModel = "spring")
public interface AlarmRuleCoConvertor {
AlarmRuleCO converDOToCO(AlarmRuleDO alarmRuleDO);
List<AlarmRuleCO> converDOsToCOs(List<AlarmRuleDO> alarmRuleDOs);
}

View File

@ -0,0 +1,20 @@
package com.zcloud.personnel.positioning.command.convertor;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRuleFenceRelCO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleFenceRelDO;
import org.mapstruct.Mapper;
import java.util.List;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Mapper(componentModel = "spring")
public interface AlarmRuleFenceRelCoConvertor {
AlarmRuleFenceRelCO converDOToCO(AlarmRuleFenceRelDO alarmRuleFenceRelDO);
List<AlarmRuleFenceRelCO> converDOsToCOs(List<AlarmRuleFenceRelDO> alarmRuleFenceRelDOs);
}

View File

@ -0,0 +1,20 @@
package com.zcloud.personnel.positioning.command.convertor;
import com.zcloud.personnel.positioning.dto.clientobject.AlarmRuleItemCO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleItemDO;
import org.mapstruct.Mapper;
import java.util.List;
/**
* web-app
*
* @Author fangjiakai
* @Date 2026-07-07
*/
@Mapper(componentModel = "spring")
public interface AlarmRuleItemCoConvertor {
AlarmRuleItemCO converDOToCO(AlarmRuleItemDO alarmRuleItemDO);
List<AlarmRuleItemCO> converDOsToCOs(List<AlarmRuleItemDO> alarmRuleItemDOs);
}

Some files were not shown because too many files have changed in this diff Show More