初始化

master
aosodo_4812 2026-09-02 13:34:04 +08:00
parent bea35b4088
commit 0d2516111b
40 changed files with 1439 additions and 4 deletions

17
.idea/dataSources.xml Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="测试环境" uuid="1a997504-607b-4542-9f26-c2818d8dd0a5">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://nlb-kd2xz70qhllfet2koj.cn-beijing.nlb.aliyuncsslb.com:33068</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

278
AGENTS.md Normal file
View File

@ -0,0 +1,278 @@
# ZCloud Java 项目公共开发规范与模板
> 本文件可复制到新项目直接使用。服务名、包名、网关前缀、领域边界和业务规则应写在项目自己的文档中,不要写入本公共模板。
## 1. DDD 分层
新项目统一采用 DDD 多模块结构,实际包名前缀、服务名和网关前缀由项目自行定义。
| 模块 | 职责 | 禁止事项 |
| --- | --- | --- |
| `web-client` | API、Cmd、Qry、Co | 依赖 App、Domain、Infrastructure |
| `web-adapter` | Web/App/Socket Controller、Dubbo Facade 等入站适配 | 直接访问 Mapper、编写业务规则 |
| `web-app` | Client 接口实现、执行器、用例编排、事务 | 堆积可复用领域规则、长事务调用远程服务 |
| `web-domain` | 领域实体、值对象、领域服务、Gateway 接口 | 依赖 MyBatis、Dubbo、HTTP、外部 DTO |
| `web-infrastructure` | Gateway 实现、Dubbo/HTTP 调用、DO、Mapper、Repository | 向 Domain 泄漏 DO 或外部 DTO |
| `start` | 启动、配置、XXL-JOB 装配 | 编写业务逻辑 |
标准调用链:
```text
Adapter -> Client接口(App实现) -> Executor -> Domain/Gateway -> Infrastructure
```
公司文档允许执行器或 Adapter 直接调用基础设施能力;采用 DDD 分层的项目优先通过 Domain Gateway。纯协议转换且不进入领域用例的场景Adapter 才可直接引用外部 Facade。
## 2. 命名与对象基类
| 类型 | 命名/基类 | 放置位置 |
| --- | --- | --- |
| 领域实体 | `XxxE` | `web-domain/model` |
| 领域网关 | `XxxGateway` | `web-domain/gateway` |
| 网关实现 | `XxxGatewayImpl` | `web-infrastructure/gatewayimpl` |
| 持久化对象 | `XxxDO extends BaseDO` | `persistence/dataobject` |
| Mapper | `XxxMapper` | `persistence/mapper` |
| 新增/修改命令 | `XxxAddCmd`、`XxxUpdateCmd extends Command` | `web-client` |
| 分页查询 | `XxxPageQry extends BasePageQuery` | `web-client` |
| 返回对象 | `XxxCo extends ClientObject` | `web-client` |
| 执行器 | `XxxAddExe`、`XxxUpdateExe`、`XxxRemoveExe`、`XxxQueryExe` | `web-app` |
- Cmd、Qry、Co 和 Adapter 使用的服务接口统一定义在 `web-client`
- 不得直接返回 DO、领域实体或第三方 DTO。
- 不得在 `persistence.dataobject``persistence.domainobject` 创建同名 DO新增 DO 统一放 `dataobject`
## 3. HTTP 接口模板
- 路径使用 `/{gateway-prefix}/{resource}`,其中网关前缀由具体项目确定。
- GET、POST、PUT、DELETE 按资源语义使用。
- Controller 只做参数接收、校验、权限控制和应用服务调用。
- 新增、修改分别使用 AddCmd、UpdateCmd配合 `@Validated` 和 Bean Validation。
- Swagger 使用 `@Api`、`@ApiOperation`、`@ApiModelProperty`Swagger 的 `required` 不能代替参数校验注解。
- 当前用户通过 `AuthContext.getCurrentUser()` 获取,不信任前端传入的租户、单位、创建人等身份字段。
```java
@ApiOperation("新增")
@PostMapping("/save")
@PreAuthorize("@pms.hasPermission('实际菜单或按钮权限编码')")
public SingleResponse<XxxCo> add(@Validated @RequestBody XxxAddCmd cmd) {
return xxxService.add(cmd);
}
```
接口权限编码必须取自实际菜单/按钮配置,禁止复制示例编码。
### 标准返回类
| 类型 | 场景 |
| --- | --- |
| `SingleResponse<T>` | 单条数据 |
| `PageResponse<T>` | 分页数据 |
| `MultiResponse<T>` | 集合数据 |
| `Response` | 无返回数据 |
禁止新增另一套通用响应外壳。批量操作存在部分成功时,在标准响应承载的 Co 中返回逐条结果。
## 4. 数据权限模板
接口权限控制“能否调用”,数据权限控制“能看到哪些数据”,两者不能互相替代。
```java
@Mapper
@DataScopes({
@DataScope(
method = "selectOne",
menuPerms = "实际菜单权限编码",
tenantAlias = "tenant_id"
)
})
public interface XxxMapper extends BaseMapper<XxxDO> {
}
```
- `method` 与实际 Mapper 方法名一致。
- `menuPerms` 使用实际权限编码。
- `tenantAlias` 与 SQL 中租户列或表别名一致;多表查询必须写明确表别名。
- 定时任务和 Dubbo 等非登录态入口必须明确租户、单位、环境范围,禁止默认跨租户扫描。
## 5. App、Domain 与异常模板
- App 服务实现负责把 Client 请求分派到执行器。
- 执行器负责参数转换、用例编排、事务和 Gateway 调用。
- 可复用业务规则放入领域实体或领域服务。
- 写操作使用 `@Transactional(rollbackFor = Exception.class)`
- 领域模型不得调用基础设施层、Mapper、Repository 实现或 Dubbo Facade。
- 保存失败、业务校验失败及远程调用失败统一转换为项目 `BizException`
- 转换异常时保留原异常日志/异常链;错误信息不能泄露密钥、人脸、证件或完整敏感报文。
## 6. Dubbo 服务模板
Dubbo 公共契约统一维护在组织公共接口项目 `zcloud_gbs_common`。新项目使用公共仓库的实际发布版本,不在规范中固定开发机绝对路径。
Maven 坐标:
```xml
<dependency>
<groupId>com.zcloud.gbscommon</groupId>
<artifactId>zcloud_gbscommon</artifactId>
<version>${zcloud.gbscommon.version}</version>
</dependency>
```
使用已有 Dubbo 能力时,先在 Common 项目中查找对应 Facade、Request、Response禁止在业务项目内重复定义同名公共契约。
### 发布服务
1. 在 Common 项目的 `src/main/java/com/zcloud/gbscommon/{业务域}` 定义公共契约:
- `facade/XxxFacade`
- `request/XxxQry`、`XxxCmd`
- `response/XxxCo`
2. 服务项目在 Adapter 实现 Facade使用 `@DubboService` 发布。
3. Facade 只做校验、协议转换和应用服务调用。
4. 修改 Common 契约属于跨项目变更:字段和方法确认后再修改、发布约定版本,并在消费项目验证依赖更新;未经明确需求不得直接修改 Common 项目。
```java
@DubboService
public class XxxFacadeImpl implements XxxFacade {
// 调用应用服务
}
```
### 调用服务
- 启动类增加 `@EnableFacadeRpcClient`
- 消费方使用公共契约,并通过 `@DubboReference(check = false)` 注入。
- DDD 项目优先在 Infrastructure GatewayImpl 中调用 Facade 并转换外部 DTO。
- `check = false` 只是不在启动时检查提供方,不代表可以忽略运行期失败。
- 写接口自动重试可能造成重复业务,必须结合幂等性设置超时和重试策略。
```java
@DubboReference(check = false)
private XxxFacade xxxFacade;
```
## 7. 用户、岗位、部门、企业翻译模板
按 ID 补充系统内公共信息时使用 `TranslateField`
- 用户:`getZcloudUserInfoById(s)`
- 岗位:`getZcloudPostInfoById(s)`
- 部门:`getZcloudDpetInfoById(s)`
- 企业:`getZcloudCorpInfoById(s)`
- 对象/集合:`translateZcloudCommonInformation(data, XxxCo.class)`
列表必须使用 `ByIds` 或集合翻译,禁止循环逐条兑换造成 N+1 远程调用。
```java
@ZCloudTranslates({
@ZCloudTranslate(
primaryKeyType = TranslateEunm.USER,
primaryKey = "userId",
mappedFields = {"userName:username"}
),
@ZCloudTranslate(
primaryKeyType = TranslateEunm.POST,
primaryKey = "postId",
mappedFields = {"postName:name"}
),
@ZCloudTranslate(
primaryKeyType = TranslateEunm.DEPT,
primaryKey = "deptId",
mappedFields = {"deptName:name"}
),
@ZCloudTranslate(
primaryKeyType = TranslateEunm.CORP,
primaryKey = "corpId",
mappedFields = {"corpName:name"}
)
})
public class XxxCo extends ClientObject {
}
```
- `primaryKeyType`:翻译类型。
- `primaryKey`:当前类中的 ID 属性。
- `mappedFields``当前类目标属性:common Co来源属性`。
- 翻译结果用于展示补充;核心业务判断应显式查询并校验。
## 8. 分页和动态查询模板
- 分页 Qry 继承 `BasePageQuery`,返回 `PageResponse<T>`
- `toHashMap` 查询键使用“操作符前缀 + 驼峰字段”,基础设施层转换为下划线列名。
| 前缀 | SQL 条件 | 示例 |
| --- | --- | --- |
| `Like` | `LIKE` | `LikePersonName` |
| `eq` | `=` | `eqTaskStatus` |
| `gt` | `>` | `gtCreateTime` |
| `lt` | `<` | `ltCreateTime` |
| `le` | `<=` | `leCreateTime` |
| `ge` | `>=` | `geCreateTime` |
| `ne` | `<>` | `neTaskStatus` |
前缀大小写必须与公共 `toHashMap` 解析规则一致;新增条件前先核对公共组件,禁止自行拼接 SQL 运算符。
## 9. 数据库模板
所有新建业务表必须包含以下公共字段:
```sql
`delete_enum` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '删除标识true false',
`remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',
`create_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人姓名',
`update_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人姓名',
`tenant_id` bigint DEFAULT NULL COMMENT '租户id',
`org_id` bigint DEFAULT NULL COMMENT '单位id',
`version` int DEFAULT NULL COMMENT '版本',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`create_id` bigint DEFAULT NULL COMMENT '创建人id',
`update_id` bigint DEFAULT NULL COMMENT '修改人id',
`env` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '环境标识'
```
- 表使用 InnoDB、`utf8mb4`、`utf8mb4_0900_ai_ci`。
- `version` 是本地乐观锁版本,业务版本另设 `data_version`
- `delete_enum` 是本地逻辑删除,不等于业务 `DELETE/DISABLE/REVOKE/EXPIRE`
- 多租户业务索引考虑 `tenant_id`,共享环境同时考虑 `env`
- 任务号、幂等键、外部回执号等必须有唯一约束。
- 状态扫描组合索引应覆盖状态、下次执行时间、优先级等实际条件。
- 不配置跨服务语义的数据库物理外键。
- DDL、DO、Mapper、XML 保持一致;变更表结构同步维护 DDL 和正式迁移脚本。
- 大报文和敏感信息应加密或仅存脱敏快照,禁止写普通日志。
## 10. XXL-JOB 模板
- 生产定时任务统一使用 XXL-JOB不新增仅依赖本地 `@Scheduled` 的任务。
- Handler 使用唯一且稳定的 `@XxlJob` 名称。
- Job 只读取调度参数、触发应用用例、返回结果;业务逻辑放在 App/Domain。
- 通过任务号、唯一键或锁保证业务幂等。
- 失败必须抛出或返回失败,让调度中心感知;禁止吞异常后返回成功。
```java
@Component
@RequiredArgsConstructor
public class XxxJob {
private final XxxApplicationService xxxApplicationService;
@XxlJob("projectXxxJob")
public ReturnT<String> execute(String param) {
xxxApplicationService.execute(param);
return ReturnT.SUCCESS;
}
}
```
## 11. 开发完成检查表
- [ ] Client、Adapter、App、Domain、Infrastructure 职责和依赖方向正确。
- [ ] Cmd/Co/PageQry 分别继承 `Command`、`ClientObject`、`BasePageQuery`。
- [ ] Controller 无 Mapper 调用和业务规则,返回标准 Response 类型。
- [ ] 接口配置真实 `@PreAuthorize` 权限编码,查询配置正确 `@DataScopes`
- [ ] Dubbo 契约来自 `zcloud_gbs_common`,提供方使用 `@DubboService`,消费方启用 RPC Client。
- [ ] 外部调用完成 DTO 防腐转换、超时、异常和幂等处理。
- [ ] 公共信息使用批量翻译,`mappedFields` 方向正确。
- [ ] 写操作事务和 `BizException` 处理正确,不存在远程长事务。
- [ ] DDL、DO、Mapper、XML、公共字段和索引一致。
- [ ] XXL-JOB 名称唯一,任务幂等,失败可被调度中心感知。
- [ ] 日志和响应未泄露人脸、证件、文件、密钥或完整敏感报文。
- [ ] 完成与改动风险匹配的 Maven 编译、测试和 `git diff` 检查。

View File

@ -2,7 +2,7 @@ nacos:
url: prod-nacos:8848
namespace: jjb-dragon
application:
name: jjb-saas-zcloud-edgeguard
name: zcloud_gbs_edgeguard
version:
gateway: edgeguard
cn-name: 边缘人脸区域安全

View File

@ -2,7 +2,7 @@ nacos:
url: 192.168.20.100:30290
namespace: jjb-dragon
application:
name: jjb-saas-zcloud-edgeguard
name: zcloud_gbs_edgeguard
version:
gateway: edgeguard
cn-name: 边缘人脸区域安全

View File

@ -1,6 +1,6 @@
sdk:
server:
app-key: 722091ff53dd4abba078c2a00efd4a42
app-key: 99626aa02f274e43be0f8acbe6f625f8
client:
gateway:
url: ${common.gateway.network.http.external}

View File

@ -1,6 +1,6 @@
sdk:
server:
app-key: 722091ff53dd4abba078c2a00efd4a42
app-key: 99626aa02f274e43be0f8acbe6f625f8
client:
gateway:
url: ${common.gateway.network.http.external}

View File

@ -0,0 +1,9 @@
package com.zcloud.gbs.edgeguard.domain.gateway;
import com.zcloud.gbs.edgeguard.domain.model.AlarmDisposalE;
public interface AlarmDisposalGateway {
Boolean add(AlarmDisposalE disposal);
Boolean update(AlarmDisposalE disposal);
AlarmDisposalE getById(Long id);
}

View File

@ -0,0 +1,9 @@
package com.zcloud.gbs.edgeguard.domain.gateway;
import com.zcloud.gbs.edgeguard.domain.model.AlarmE;
public interface AlarmGateway {
Boolean add(AlarmE alarm);
Boolean update(AlarmE alarm);
AlarmE getById(Long id);
}

View File

@ -0,0 +1,10 @@
package com.zcloud.gbs.edgeguard.domain.gateway;
import com.zcloud.gbs.edgeguard.domain.model.RegionCameraE;
public interface RegionCameraGateway {
Boolean add(RegionCameraE regionCamera);
Boolean update(RegionCameraE regionCamera);
Boolean deleteById(Long id);
RegionCameraE getById(Long id);
}

View File

@ -0,0 +1,10 @@
package com.zcloud.gbs.edgeguard.domain.gateway;
import com.zcloud.gbs.edgeguard.domain.model.RegionE;
public interface RegionGateway {
Boolean add(RegionE region);
Boolean update(RegionE region);
Boolean deleteById(Long id);
RegionE getById(Long id);
}

View File

@ -0,0 +1,30 @@
package com.zcloud.gbs.edgeguard.domain.model;
import com.alibaba.cola.domain.Entity;
import com.jjb.saas.framework.domain.model.BaseE;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/** 报警处置结论领域模型。 */
@Data
@Entity
@EqualsAndHashCode(callSuper = true)
public class AlarmDisposalE extends BaseE {
private Long id;
private Long alarmId;
private Integer isFalseAlarm;
private Integer isHiddenHazard;
private String disposalResultCode;
private String disposalResultName;
private String disposalDescription;
private String hiddenId;
private String hiddenCreateStatus;
private String hiddenCreateError;
private Long handlerUserId;
private String handlerUserName;
private Long handlerDeptId;
private String handlerDeptName;
private Date disposedTime;
}

View File

@ -0,0 +1,32 @@
package com.zcloud.gbs.edgeguard.domain.model;
import com.alibaba.cola.domain.Entity;
import com.jjb.saas.framework.domain.model.BaseE;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/** 第三方报警领域模型。 */
@Data
@Entity
@EqualsAndHashCode(callSuper = true)
public class AlarmE extends BaseE {
private Long id;
private String alarmNo;
private String sourceSystem;
private Date alarmTime;
private Date receivedTime;
private String alarmAddress;
private Long corpId;
private String corpNameSnapshot;
private Long regionId;
private String regionCode;
private String regionNameSnapshot;
private String regionPathSnapshot;
private String cameraSource;
private String cameraId;
private String cameraNameSnapshot;
private String alarmImageUrl;
private String disposalStatus;
}

View File

@ -0,0 +1,36 @@
package com.zcloud.gbs.edgeguard.domain.model;
import com.alibaba.cola.domain.Entity;
import com.jjb.saas.framework.domain.model.BaseE;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/** 区域与第三方摄像头绑定领域模型。 */
@Data
@Entity
@EqualsAndHashCode(callSuper = true)
public class RegionCameraE extends BaseE {
private Long id;
private Long regionId;
private Long corpId;
private String cameraSource;
private String cameraId;
private String cameraName;
private String cameraUnitId;
private String cameraUnitName;
private String cameraTypeCode;
private String cameraTypeName;
private String cameraLevelCode;
private String cameraLevelName;
private String cameraDeptId;
private String cameraDeptName;
private String onlineStatus;
private String cameraSnapshot;
private Date cameraSyncedAt;
private Long leaderUserId;
private String bindStatus;
private Date boundTime;
private Date unboundTime;
}

View File

@ -0,0 +1,26 @@
package com.zcloud.gbs.edgeguard.domain.model;
import com.alibaba.cola.domain.Entity;
import com.jjb.saas.framework.domain.model.BaseE;
import lombok.Data;
import lombok.EqualsAndHashCode;
/** 区域领域模型。 */
@Data
@Entity
@EqualsAndHashCode(callSuper = true)
public class RegionE extends BaseE {
private Long id;
private String regionCode;
private String regionName;
private Long corpId;
private Long parentId;
private String treePath;
private Integer treeLevel;
private Integer sortNo;
private Long leaderUserId;
private String coordinateSystem;
/** 四个有序坐标点的 JSON 数组。 */
private String fencePoints;
private String regionStatus;
}

View File

@ -0,0 +1,45 @@
package com.zcloud.gbs.edgeguard.gatewayimpl;
import com.zcloud.gbs.edgeguard.domain.gateway.AlarmDisposalGateway;
import com.zcloud.gbs.edgeguard.domain.model.AlarmDisposalE;
import com.zcloud.gbs.edgeguard.persistence.dataobject.AlarmDisposalDO;
import com.zcloud.gbs.edgeguard.persistence.repository.AlarmDisposalRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AlarmDisposalGatewayImpl implements AlarmDisposalGateway {
private final AlarmDisposalRepository repository;
@Override
public Boolean add(AlarmDisposalE disposal) {
return repository.save(toDO(disposal));
}
@Override
public Boolean update(AlarmDisposalE disposal) {
return repository.updateById(toDO(disposal));
}
@Override
public AlarmDisposalE getById(Long id) {
return toEntity(repository.getById(id));
}
private AlarmDisposalDO toDO(AlarmDisposalE source) {
AlarmDisposalDO target = new AlarmDisposalDO();
BeanUtils.copyProperties(source, target);
return target;
}
private AlarmDisposalE toEntity(AlarmDisposalDO source) {
if (source == null) {
return null;
}
AlarmDisposalE target = new AlarmDisposalE();
BeanUtils.copyProperties(source, target);
return target;
}
}

View File

@ -0,0 +1,45 @@
package com.zcloud.gbs.edgeguard.gatewayimpl;
import com.zcloud.gbs.edgeguard.domain.gateway.AlarmGateway;
import com.zcloud.gbs.edgeguard.domain.model.AlarmE;
import com.zcloud.gbs.edgeguard.persistence.dataobject.AlarmDO;
import com.zcloud.gbs.edgeguard.persistence.repository.AlarmRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AlarmGatewayImpl implements AlarmGateway {
private final AlarmRepository repository;
@Override
public Boolean add(AlarmE alarm) {
return repository.save(toDO(alarm));
}
@Override
public Boolean update(AlarmE alarm) {
return repository.updateById(toDO(alarm));
}
@Override
public AlarmE getById(Long id) {
return toEntity(repository.getById(id));
}
private AlarmDO toDO(AlarmE source) {
AlarmDO target = new AlarmDO();
BeanUtils.copyProperties(source, target);
return target;
}
private AlarmE toEntity(AlarmDO source) {
if (source == null) {
return null;
}
AlarmE target = new AlarmE();
BeanUtils.copyProperties(source, target);
return target;
}
}

View File

@ -0,0 +1,50 @@
package com.zcloud.gbs.edgeguard.gatewayimpl;
import com.zcloud.gbs.edgeguard.domain.gateway.RegionCameraGateway;
import com.zcloud.gbs.edgeguard.domain.model.RegionCameraE;
import com.zcloud.gbs.edgeguard.persistence.dataobject.RegionCameraDO;
import com.zcloud.gbs.edgeguard.persistence.repository.RegionCameraRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class RegionCameraGatewayImpl implements RegionCameraGateway {
private final RegionCameraRepository repository;
@Override
public Boolean add(RegionCameraE regionCamera) {
return repository.save(toDO(regionCamera));
}
@Override
public Boolean update(RegionCameraE regionCamera) {
return repository.updateById(toDO(regionCamera));
}
@Override
public Boolean deleteById(Long id) {
return repository.removeById(id);
}
@Override
public RegionCameraE getById(Long id) {
return toEntity(repository.getById(id));
}
private RegionCameraDO toDO(RegionCameraE source) {
RegionCameraDO target = new RegionCameraDO();
BeanUtils.copyProperties(source, target);
return target;
}
private RegionCameraE toEntity(RegionCameraDO source) {
if (source == null) {
return null;
}
RegionCameraE target = new RegionCameraE();
BeanUtils.copyProperties(source, target);
return target;
}
}

View File

@ -0,0 +1,50 @@
package com.zcloud.gbs.edgeguard.gatewayimpl;
import com.zcloud.gbs.edgeguard.domain.gateway.RegionGateway;
import com.zcloud.gbs.edgeguard.domain.model.RegionE;
import com.zcloud.gbs.edgeguard.persistence.dataobject.RegionDO;
import com.zcloud.gbs.edgeguard.persistence.repository.RegionRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class RegionGatewayImpl implements RegionGateway {
private final RegionRepository repository;
@Override
public Boolean add(RegionE region) {
return repository.save(toDO(region));
}
@Override
public Boolean update(RegionE region) {
return repository.updateById(toDO(region));
}
@Override
public Boolean deleteById(Long id) {
return repository.removeById(id);
}
@Override
public RegionE getById(Long id) {
return toEntity(repository.getById(id));
}
private RegionDO toDO(RegionE source) {
RegionDO target = new RegionDO();
BeanUtils.copyProperties(source, target);
return target;
}
private RegionE toEntity(RegionDO source) {
if (source == null) {
return null;
}
RegionE target = new RegionE();
BeanUtils.copyProperties(source, target);
return target;
}
}

View File

@ -0,0 +1,30 @@
package com.zcloud.gbs.edgeguard.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jjb.saas.framework.repository.basedo.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edgeguard_alarm")
public class AlarmDO extends BaseDO {
private String alarmNo;
private String sourceSystem;
private Date alarmTime;
private Date receivedTime;
private String alarmAddress;
private Long corpId;
private String corpNameSnapshot;
private Long regionId;
private String regionCode;
private String regionNameSnapshot;
private String regionPathSnapshot;
private String cameraSource;
private String cameraId;
private String cameraNameSnapshot;
private String alarmImageUrl;
private String disposalStatus;
}

View File

@ -0,0 +1,28 @@
package com.zcloud.gbs.edgeguard.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jjb.saas.framework.repository.basedo.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edgeguard_alarm_disposal")
public class AlarmDisposalDO extends BaseDO {
private Long alarmId;
private Integer isFalseAlarm;
private Integer isHiddenHazard;
private String disposalResultCode;
private String disposalResultName;
private String disposalDescription;
private String hiddenId;
private String hiddenCreateStatus;
private String hiddenCreateError;
private Long handlerUserId;
private String handlerUserName;
private Long handlerDeptId;
private String handlerDeptName;
private Date disposedTime;
}

View File

@ -0,0 +1,34 @@
package com.zcloud.gbs.edgeguard.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jjb.saas.framework.repository.basedo.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edgeguard_region_camera")
public class RegionCameraDO extends BaseDO {
private Long regionId;
private Long corpId;
private String cameraSource;
private String cameraId;
private String cameraName;
private String cameraUnitId;
private String cameraUnitName;
private String cameraTypeCode;
private String cameraTypeName;
private String cameraLevelCode;
private String cameraLevelName;
private String cameraDeptId;
private String cameraDeptName;
private String onlineStatus;
private String cameraSnapshot;
private Date cameraSyncedAt;
private Long leaderUserId;
private String bindStatus;
private Date boundTime;
private Date unboundTime;
}

View File

@ -0,0 +1,23 @@
package com.zcloud.gbs.edgeguard.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jjb.saas.framework.repository.basedo.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edgeguard_region")
public class RegionDO extends BaseDO {
private String regionCode;
private String regionName;
private Long corpId;
private Long parentId;
private String treePath;
private Integer treeLevel;
private Integer sortNo;
private Long leaderUserId;
private String coordinateSystem;
private String fencePoints;
private String regionStatus;
}

View File

@ -0,0 +1,7 @@
package com.zcloud.gbs.edgeguard.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zcloud.gbs.edgeguard.persistence.dataobject.AlarmDisposalDO;
public interface AlarmDisposalMapper extends BaseMapper<AlarmDisposalDO> {
}

View File

@ -0,0 +1,7 @@
package com.zcloud.gbs.edgeguard.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zcloud.gbs.edgeguard.persistence.dataobject.AlarmDO;
public interface AlarmMapper extends BaseMapper<AlarmDO> {
}

View File

@ -0,0 +1,7 @@
package com.zcloud.gbs.edgeguard.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zcloud.gbs.edgeguard.persistence.dataobject.RegionCameraDO;
public interface RegionCameraMapper extends BaseMapper<RegionCameraDO> {
}

View File

@ -0,0 +1,7 @@
package com.zcloud.gbs.edgeguard.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zcloud.gbs.edgeguard.persistence.dataobject.RegionDO;
public interface RegionMapper extends BaseMapper<RegionDO> {
}

View File

@ -0,0 +1,7 @@
package com.zcloud.gbs.edgeguard.persistence.repository;
import com.jjb.saas.framework.repository.repo.BaseRepository;
import com.zcloud.gbs.edgeguard.persistence.dataobject.AlarmDisposalDO;
public interface AlarmDisposalRepository extends BaseRepository<AlarmDisposalDO> {
}

View File

@ -0,0 +1,7 @@
package com.zcloud.gbs.edgeguard.persistence.repository;
import com.jjb.saas.framework.repository.repo.BaseRepository;
import com.zcloud.gbs.edgeguard.persistence.dataobject.AlarmDO;
public interface AlarmRepository extends BaseRepository<AlarmDO> {
}

View File

@ -0,0 +1,7 @@
package com.zcloud.gbs.edgeguard.persistence.repository;
import com.jjb.saas.framework.repository.repo.BaseRepository;
import com.zcloud.gbs.edgeguard.persistence.dataobject.RegionCameraDO;
public interface RegionCameraRepository extends BaseRepository<RegionCameraDO> {
}

View File

@ -0,0 +1,7 @@
package com.zcloud.gbs.edgeguard.persistence.repository;
import com.jjb.saas.framework.repository.repo.BaseRepository;
import com.zcloud.gbs.edgeguard.persistence.dataobject.RegionDO;
public interface RegionRepository extends BaseRepository<RegionDO> {
}

View File

@ -0,0 +1,12 @@
package com.zcloud.gbs.edgeguard.persistence.repository.impl;
import com.jjb.saas.framework.repository.repo.impl.BaseRepositoryImpl;
import com.zcloud.gbs.edgeguard.persistence.dataobject.AlarmDisposalDO;
import com.zcloud.gbs.edgeguard.persistence.mapper.AlarmDisposalMapper;
import com.zcloud.gbs.edgeguard.persistence.repository.AlarmDisposalRepository;
import org.springframework.stereotype.Service;
@Service
public class AlarmDisposalRepositoryImpl extends BaseRepositoryImpl<AlarmDisposalMapper, AlarmDisposalDO>
implements AlarmDisposalRepository {
}

View File

@ -0,0 +1,12 @@
package com.zcloud.gbs.edgeguard.persistence.repository.impl;
import com.jjb.saas.framework.repository.repo.impl.BaseRepositoryImpl;
import com.zcloud.gbs.edgeguard.persistence.dataobject.AlarmDO;
import com.zcloud.gbs.edgeguard.persistence.mapper.AlarmMapper;
import com.zcloud.gbs.edgeguard.persistence.repository.AlarmRepository;
import org.springframework.stereotype.Service;
@Service
public class AlarmRepositoryImpl extends BaseRepositoryImpl<AlarmMapper, AlarmDO>
implements AlarmRepository {
}

View File

@ -0,0 +1,12 @@
package com.zcloud.gbs.edgeguard.persistence.repository.impl;
import com.jjb.saas.framework.repository.repo.impl.BaseRepositoryImpl;
import com.zcloud.gbs.edgeguard.persistence.dataobject.RegionCameraDO;
import com.zcloud.gbs.edgeguard.persistence.mapper.RegionCameraMapper;
import com.zcloud.gbs.edgeguard.persistence.repository.RegionCameraRepository;
import org.springframework.stereotype.Service;
@Service
public class RegionCameraRepositoryImpl extends BaseRepositoryImpl<RegionCameraMapper, RegionCameraDO>
implements RegionCameraRepository {
}

View File

@ -0,0 +1,12 @@
package com.zcloud.gbs.edgeguard.persistence.repository.impl;
import com.jjb.saas.framework.repository.repo.impl.BaseRepositoryImpl;
import com.zcloud.gbs.edgeguard.persistence.dataobject.RegionDO;
import com.zcloud.gbs.edgeguard.persistence.mapper.RegionMapper;
import com.zcloud.gbs.edgeguard.persistence.repository.RegionRepository;
import org.springframework.stereotype.Service;
@Service
public class RegionRepositoryImpl extends BaseRepositoryImpl<RegionMapper, RegionDO>
implements RegionRepository {
}

View File

@ -0,0 +1,155 @@
-- 综合平台区域与摄像头绑定表结构MySQL 8.0
-- 摄像头主数据来自第三方,本系统只保存区域、摄像头绑定和负责人。
CREATE TABLE `edgeguard_region` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '区域主键id',
`region_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '区域编码,对外稳定唯一标识',
`region_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '区域名称',
`corp_id` bigint NOT NULL COMMENT '所属企业id企业主数据来自公共服务',
`parent_id` bigint NOT NULL DEFAULT 0 COMMENT '父区域id0表示企业下的顶级区域',
`tree_path` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '/' COMMENT '祖先路径,例如/1/10/',
`tree_level` int NOT NULL DEFAULT 1 COMMENT '区域层级企业下顶级区域为1',
`sort_no` int NOT NULL DEFAULT 0 COMMENT '同级排序号',
`leader_user_id` bigint NOT NULL COMMENT '区域负责人用户id',
`coordinate_system` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '坐标系WGS84/GCJ02/BD09',
`fence_points` json NOT NULL COMMENT '有序电子围栏坐标点一期必须为4个点',
`region_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'ENABLED' COMMENT '区域状态ENABLED/DISABLED',
`delete_enum` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '删除标识true false',
`remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',
`create_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人姓名',
`update_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人姓名',
`tenant_id` bigint DEFAULT NULL COMMENT '租户id',
`org_id` bigint DEFAULT NULL COMMENT '单位id',
`version` int DEFAULT NULL COMMENT '版本',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`create_id` bigint DEFAULT NULL COMMENT '创建人id',
`update_id` bigint DEFAULT NULL COMMENT '修改人id',
`env` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '环境标识',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_region_code` (`region_code`),
KEY `idx_region_tree` (`tenant_id`, `corp_id`, `parent_id`, `delete_enum`, `sort_no`),
KEY `idx_region_path` (`tenant_id`, `corp_id`, `tree_path`(191)),
KEY `idx_region_status` (`tenant_id`, `corp_id`, `region_status`, `delete_enum`),
KEY `idx_region_leader` (`leader_user_id`),
CONSTRAINT `chk_region_fence_four_points` CHECK ((JSON_TYPE(`fence_points`) = 'ARRAY') AND (JSON_LENGTH(`fence_points`) = 4))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='企业区域树表';
CREATE TABLE `edgeguard_region_camera` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '区域摄像头绑定主键id',
`region_id` bigint NOT NULL COMMENT '本地区域id',
`corp_id` bigint NOT NULL COMMENT '所属企业id冗余用于权限和查询校验',
`camera_source` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '摄像头来源系统编码',
`camera_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '第三方摄像头唯一标识',
`camera_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '摄像头名称缓存,第三方为权威数据源',
`camera_unit_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '摄像头所属单位第三方标识缓存',
`camera_unit_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '摄像头所属单位名称缓存',
`camera_type_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '摄像头类型编码缓存',
`camera_type_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '摄像头类型名称缓存',
`camera_level_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '摄像头等级编码缓存',
`camera_level_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '摄像头等级名称缓存',
`camera_dept_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '摄像头主管部门第三方标识缓存',
`camera_dept_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '摄像头主管部门名称缓存',
`online_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '在线状态缓存ONLINE/OFFLINE/UNKNOWN',
`camera_snapshot` json DEFAULT NULL COMMENT '第三方摄像头扩展字段快照',
`camera_synced_at` datetime DEFAULT NULL COMMENT '摄像头缓存最近同步时间',
`leader_user_id` bigint DEFAULT NULL COMMENT '摄像头负责人用户id',
`bind_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'BOUND' COMMENT '绑定状态BOUND/UNBOUND',
`bound_time` datetime NOT NULL COMMENT '最近绑定时间',
`unbound_time` datetime DEFAULT NULL COMMENT '最近解绑时间',
`delete_enum` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '删除标识true false',
`remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',
`create_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人姓名',
`update_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人姓名',
`tenant_id` bigint DEFAULT NULL COMMENT '租户id',
`org_id` bigint DEFAULT NULL COMMENT '单位id',
`version` int DEFAULT NULL COMMENT '版本',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`create_id` bigint DEFAULT NULL COMMENT '创建人id',
`update_id` bigint DEFAULT NULL COMMENT '修改人id',
`env` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '环境标识',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_camera_source_id` (`camera_source`, `camera_id`),
KEY `idx_region_camera` (`tenant_id`, `region_id`, `bind_status`, `delete_enum`),
KEY `idx_camera_corp` (`tenant_id`, `corp_id`, `delete_enum`),
KEY `idx_camera_filter` (`tenant_id`, `region_id`, `camera_level_code`, `online_status`, `delete_enum`),
KEY `idx_camera_name` (`camera_name`),
KEY `idx_camera_leader` (`leader_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='区域与第三方摄像头绑定表';
-- 第三方报警主数据。隐患主流程复用 ZcloudHiddenFacade本系统只保存隐患id。
CREATE TABLE `edgeguard_alarm` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '报警主键id',
`alarm_no` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '第三方报警编号',
`source_system` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '报警来源系统编码',
`alarm_time` datetime NOT NULL COMMENT '第三方报警发生时间',
`received_time` datetime NOT NULL COMMENT '综合平台接收时间',
`alarm_address` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '报警地址',
`corp_id` bigint DEFAULT NULL COMMENT '报警所属企业id',
`corp_name_snapshot` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '报警时企业名称快照',
`region_id` bigint DEFAULT NULL COMMENT '报警所属区域id',
`region_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '报警时区域编码快照',
`region_name_snapshot` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '报警时区域名称快照',
`region_path_snapshot` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '报警时区域完整路径快照',
`camera_source` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '摄像头来源系统编码',
`camera_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '第三方摄像头唯一标识',
`camera_name_snapshot` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '报警时摄像头名称快照',
`alarm_image_url` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '第三方报警截图地址',
`disposal_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'PENDING' COMMENT '处置状态PENDING/DISPOSED',
`delete_enum` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '删除标识true false',
`remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',
`create_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人姓名',
`update_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人姓名',
`tenant_id` bigint DEFAULT NULL COMMENT '租户id',
`org_id` bigint DEFAULT NULL COMMENT '单位id',
`version` int DEFAULT NULL COMMENT '版本',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`create_id` bigint DEFAULT NULL COMMENT '创建人id',
`update_id` bigint DEFAULT NULL COMMENT '修改人id',
`env` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '环境标识',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_source_alarm_no` (`source_system`, `alarm_no`),
KEY `idx_alarm_disposal` (`tenant_id`, `corp_id`, `disposal_status`, `alarm_time`),
KEY `idx_alarm_region` (`tenant_id`, `corp_id`, `region_id`, `disposal_status`, `alarm_time`),
KEY `idx_alarm_pending_timeout` (`tenant_id`, `disposal_status`, `received_time`, `region_id`),
KEY `idx_alarm_camera` (`camera_source`, `camera_id`, `alarm_time`),
KEY `idx_alarm_statistics` (`tenant_id`, `corp_id`, `disposal_status`, `alarm_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='第三方报警主表';
CREATE TABLE `edgeguard_alarm_disposal` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '报警处置主键id',
`alarm_id` bigint NOT NULL COMMENT '报警主键id',
`is_false_alarm` tinyint(1) NOT NULL COMMENT '是否误报0否1是',
`is_hidden_hazard` tinyint(1) DEFAULT NULL COMMENT '是否隐患0否1是非误报时必填',
`disposal_result_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '处置结果字典编码',
`disposal_result_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '处置结果名称快照',
`disposal_description` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '处置说明',
`hidden_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '关联隐患id仅判定为隐患时有值',
`hidden_create_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '隐患创建状态PENDING/SUCCESS/FAILED',
`hidden_create_error` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '最近隐患创建失败信息',
`handler_user_id` bigint NOT NULL COMMENT '处置人用户id',
`handler_user_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '处置人姓名快照',
`handler_dept_id` bigint DEFAULT NULL COMMENT '处置部门id',
`handler_dept_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '处置部门名称快照',
`disposed_time` datetime NOT NULL COMMENT '提交处置时间',
`delete_enum` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '删除标识true false',
`remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',
`create_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人姓名',
`update_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人姓名',
`tenant_id` bigint DEFAULT NULL COMMENT '租户id',
`org_id` bigint DEFAULT NULL COMMENT '单位id',
`version` int DEFAULT NULL COMMENT '版本',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`create_id` bigint DEFAULT NULL COMMENT '创建人id',
`update_id` bigint DEFAULT NULL COMMENT '修改人id',
`env` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '环境标识',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_disposal_alarm` (`alarm_id`),
KEY `idx_disposal_hidden` (`hidden_id`),
KEY `idx_hidden_create_retry` (`hidden_create_status`, `update_time`),
KEY `idx_disposal_handler` (`tenant_id`, `handler_user_id`, `disposed_time`),
KEY `idx_disposal_result` (`tenant_id`, `is_false_alarm`, `is_hidden_hazard`, `disposed_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='报警处置结论表';

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcloud.gbs.edgeguard.persistence.mapper.AlarmDisposalMapper">
</mapper>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcloud.gbs.edgeguard.persistence.mapper.AlarmMapper">
</mapper>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcloud.gbs.edgeguard.persistence.mapper.RegionCameraMapper">
</mapper>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcloud.gbs.edgeguard.persistence.mapper.RegionMapper">
</mapper>

View File

@ -0,0 +1,398 @@
# 边缘人脸识别区域安全设计方案
## 1. 当前需求结论
综合平台维护企业下面的区域树,并向第三方提供区域查询接口。
当前功能范围:
- 一个企业可以维护多棵或多层区域树。
- 区域支持新增同级区域、新增下级区域、修改和删除。
- 区域包含区域名称、负责人和电子围栏坐标。
- 当前原型要求电子围栏由 4 个有顺序的坐标点组成。
- 摄像头主数据来自第三方接口,综合平台不维护摄像头主档。
- 综合平台保存区域与摄像头的绑定关系。
- 每个已绑定摄像头可以设置负责人。
- 选择区域后,右侧列表展示该区域直接绑定的摄像头。
- 人员、公司、部门、人脸和人员区域准入关系不属于本功能维护范围。
- 综合平台不主动推送,由第三方主动调用区域开放接口。
## 2. 领域边界
```text
企业(外部公共数据)
└── 区域(综合平台维护,树结构)
├── 下级区域
└── 摄像头绑定(摄像头主数据来自第三方)
└── 摄像头负责人
```
### 2.1 企业
- 企业作为区域树的根节点和数据隔离维度。
- 企业主数据不在区域表中重复维护,只保存 `corp_id`
- 页面树由“企业虚拟根节点 + 本地区域节点”组合生成。
- 企业名称通过公共信息翻译或企业公共接口获取,不作为区域的权威数据保存。
### 2.2 区域
区域是本功能的聚合根,负责:
- 企业归属。
- 父子层级。
- 区域名称。
- 区域负责人。
- 电子围栏坐标。
- 区域状态和逻辑删除。
### 2.3 摄像头
- 摄像头名称、所属单位、类型、等级、主管部门、在线状态等来自第三方接口。
- 本系统不成为摄像头主数据的权威维护方,但会冗余列表展示和查询所需的摄像头缓存字段。
- 本系统保存第三方摄像头唯一标识、来源系统、区域绑定、摄像头负责人,以及名称、所属单位、类型、等级、主管部门、在线状态等快照。
- 绑定摄像头时写入快照;后续可按查询或定时策略刷新,第三方数据始终是权威来源。
- 第三方临时不可用时可以展示本地最近快照,并显示最近同步时间;不得把缓存快照作为修改第三方摄像头的依据。
## 3. 关键设计决策
### 3.1 公司节点不进入区域表
区域表只存真实区域。顶级区域的 `parent_id` 为空或为 `0`,通过 `corp_id` 归属企业。这样不会把外部企业主数据和本地区域数据混为一种实体。
页面树建议返回统一节点结构:
```text
nodeType = CORP 企业虚拟节点
nodeType = REGION 本地区域节点
```
### 3.2 区域树规则
- 同一企业下,同一父节点中的区域名称建议唯一。
- 子区域必须与父区域属于同一企业、租户和单位。
- 禁止把节点移动到自身或自己的子孙节点下面。
- 删除包含子区域的节点时,默认禁止删除,先迁移或删除子节点。
- 删除仍绑定摄像头的区域时,默认禁止删除,先解除或迁移绑定。
- 查询区域树时使用批量查询后在内存组树,禁止递归逐节点查询数据库。
是否允许跨层级移动区域、区域最大层级和区域排序规则仍需产品确认。
### 3.3 四点电子围栏
建议数据库保存一个有顺序的坐标数组,而不是设计 `point1_x``point4_y` 八个固定字段:
```json
[
{"seq": 1, "longitude": 113.123456, "latitude": 23.123456},
{"seq": 2, "longitude": 113.223456, "latitude": 23.123456},
{"seq": 3, "longitude": 113.223456, "latitude": 23.223456},
{"seq": 4, "longitude": 113.123456, "latitude": 23.223456}
]
```
一期保存时强制校验:
- 必须恰好 4 个点。
- 点序号不可重复,按顺时针或逆时针排序。
- 经度范围 `[-180, 180]`,纬度范围 `[-90, 90]`
- 相邻点和首尾点不能重复。
- 四条边不能自相交。
- 围栏面积不能为 0。
- 必须明确坐标系,例如 WGS84、GCJ-02 或 BD-09坐标系作为字段保存禁止默认猜测。
使用 JSON 可以满足当前 4 点要求,同时允许未来扩展为多边形。如果后续需要“判断坐标是否在区域内”等空间检索,再增加 MySQL `POLYGON` 空间字段和空间索引。
### 3.4 负责人
- 区域负责人保存用户 ID不重复保存用户主档。
- 摄像头负责人保存在区域摄像头绑定记录中。
- 页面通过 `TranslateField` 批量翻译负责人姓名,禁止循环逐人查询。
- 原型中负责人是必填单选;是否允许多个负责人,需要产品确认。
- 人员被停用或删除后,负责人绑定如何处理,需要明确校验或提示规则。
### 3.5 摄像头绑定
- 选择摄像头时调用第三方接口获取可选摄像头列表。
- 保存时只提交选中的第三方摄像头 ID本系统必须再次校验摄像头存在性和调用方数据范围。
- 建议一期约束“一个来源系统中的一个摄像头只能绑定一个区域”。如果业务允许一个摄像头属于多个区域,再取消唯一约束。
- 重复选择已经绑定到当前区域的摄像头应幂等成功。
- 已绑定其他区域时,应返回明确提示,不静默迁移。
- 解绑只删除本地关系,不调用第三方删除摄像头。
- 在线状态等动态字段不写入本地绑定表,以第三方实时结果为准。
## 4. 表结构草案
原型可以确定两张核心业务表。所有表都包含根目录 `AGENTS.md` 规定的公共字段。可执行评审的 MySQL 8.0 DDL 草案见 `web-infrastructure/src/main/resources/RegionManagementDDL.sql`
### 4.1 区域表 `security_region`
| 字段 | 类型建议 | 说明 |
| --- | --- | --- |
| `id` | `bigint` | 区域主键 |
| `region_code` | `varchar(64)` | 区域编码,建议提供稳定的对外标识 |
| `region_name` | `varchar(100)` | 区域名称 |
| `corp_id` | `bigint` | 所属企业 ID |
| `parent_id` | `bigint` | 父区域 ID顶级区域为空或 0 |
| `tree_path` | `varchar(1000)` | 祖先路径,用于子树查询和防循环 |
| `tree_level` | `int` | 区域层级,企业下顶级区域从 1 开始 |
| `sort_no` | `int` | 同级排序 |
| `leader_user_id` | `bigint` | 区域负责人用户 ID |
| `coordinate_system` | `varchar(32)` | 坐标系,例如 GCJ02 |
| `fence_points` | `json` | 有序电子围栏坐标点,一期固定 4 点 |
| `region_status` | `varchar(32)` | `ENABLED/DISABLED` |
建议索引:
- 唯一索引:`region_code`,是否叠加租户维度按编码生成规则确定。
- 同级名称唯一:`tenant_id + corp_id + parent_id + region_name + delete_enum`。由于可空字段会影响 MySQL 唯一约束,正式 DDL 需采用非空默认值或规范化唯一键。
- 树查询索引:`tenant_id + corp_id + parent_id + delete_enum + sort_no`。
- 负责人查询索引:`leader_user_id`,仅在存在反向查询需求时增加。
### 4.2 区域摄像头绑定表 `security_region_camera`
| 字段 | 类型建议 | 说明 |
| --- | --- | --- |
| `id` | `bigint` | 主键 |
| `region_id` | `bigint` | 本地区域 ID |
| `camera_source` | `varchar(64)` | 摄像头来源系统编码 |
| `camera_id` | `varchar(128)` | 第三方摄像头唯一标识 |
| `camera_name` | `varchar(255)` | 摄像头名称缓存 |
| `camera_unit_id/name` | `varchar` | 所属单位标识及名称缓存 |
| `camera_type_code/name` | `varchar` | 类型编码及名称缓存 |
| `camera_level_code/name` | `varchar` | 等级编码及名称缓存 |
| `camera_dept_id/name` | `varchar` | 主管部门标识及名称缓存 |
| `online_status` | `varchar(32)` | 在线状态缓存 |
| `camera_snapshot` | `json` | 第三方其他扩展字段快照 |
| `camera_synced_at` | `datetime` | 缓存最近同步时间 |
| `leader_user_id` | `bigint` | 摄像头负责人用户 ID |
| `bind_status` | `varchar(32)` | `BOUND/UNBOUND`,若逻辑删除足够可不单设 |
| `bound_time` | `datetime` | 绑定时间 |
建议索引:
- 唯一索引:`tenant_id + camera_source + camera_id`,落实一个摄像头只绑定一个区域。
- 区域列表索引:`tenant_id + region_id + delete_enum`。
- 摄像头负责人索引:`leader_user_id`,仅在需要按负责人查询时增加。
当前只缓存已经绑定的摄像头,因此展示字段直接放在绑定表中即可。如果后续要求缓存全部第三方摄像头、未绑定摄像头也要离线搜索,或一个摄像头允许绑定多个区域,则应拆分为“摄像头缓存表 + 区域摄像头关系表”。
## 5. 页面功能拆解
### 5.1 区域树
- 按当前用户数据权限加载企业。
- 批量查询企业下全部区域并组装树。
- 支持新增顶级区域、同级区域和下级区域。
- 支持修改和删除区域。
- 选中区域后显示企业/区域面包屑。
### 5.2 新增或修改区域
入参建议:
- `parentId`:新增顶级区域时为空。
- `corpId`:必填。
- `regionName`:必填。
- `leaderUserId`:按当前原型必填。
- `coordinateSystem`:必填。
- `fencePoints`:必填且恰好 4 点。
- `sortNo`:可选。
### 5.3 选择摄像头
- 显示当前区域路径。
- 调用第三方接口分页查询摄像头。
- 支持视频名称、等级、在线状态等第三方支持的条件。
- 已绑定当前区域的摄像头默认勾选。
- 已绑定其他区域的摄像头应禁选或明确标识。
- 确认后计算新增绑定和解除绑定差异,在一个本地事务中保存。
### 5.4 区域摄像头列表
列表字段来自两部分:
| 字段 | 来源 |
| --- | --- |
| 视频名称、所属单位、类型、等级、主管部门、在线状态 | 本地缓存字段展示,第三方接口负责刷新 |
| 所属区域、摄像头负责人、绑定时间 | 本地绑定字段 |
列表操作:设置摄像头负责人、查看第三方摄像头详情、解除绑定。原型中的“删除”建议在业务文案中改为“解绑”,避免误解为删除第三方摄像头。
## 6. 接口建议
### 6.1 内部区域管理接口
| 能力 | 建议接口语义 |
| --- | --- |
| 区域树 | 按数据权限返回企业虚拟根和区域树 |
| 新增区域 | 新增顶级或下级区域 |
| 修改区域 | 修改名称、负责人、围栏和排序 |
| 删除区域 | 校验子区域和摄像头绑定后逻辑删除 |
| 区域摄像头分页 | 查询选中区域直接绑定的摄像头 |
| 可选摄像头分页 | 代理/调用第三方摄像头查询能力 |
| 保存摄像头绑定 | 批量计算并保存绑定差异 |
| 设置摄像头负责人 | 更新绑定记录负责人 |
| 解除摄像头绑定 | 逻辑删除本地绑定关系 |
### 6.2 第三方区域开放接口
综合平台作为区域数据提供方,建议至少提供:
- 区域分页查询。
- 区域详情查询。
- 区域树查询。
开放接口是否返回区域绑定摄像头需要第三方确认。建议区域和摄像头绑定分别返回,避免区域树报文过大:
- `RegionCo`:区域自身信息、层级、负责人、围栏。
- `RegionCameraCo`:区域 ID、第三方摄像头 ID、摄像头负责人。
第三方开放接口使用独立鉴权和数据范围,不直接复用页面登录权限。
## 7. DDD 落位
| 模块 | 内容 |
| --- | --- |
| `web-client` | Region、RegionCamera 的 Cmd、Qry、Co 和服务接口 |
| `web-adapter` | 区域管理 Controller、第三方区域开放接口 |
| `web-app` | 区域增删改查、组树、摄像头绑定和负责人设置执行器 |
| `web-domain` | `RegionE`、`RegionCameraBindingE`、树及围栏规则、Gateway |
| `web-infrastructure` | DO、Mapper、Repository、GatewayImpl、第三方摄像头 Facade 适配 |
| `start` | RPC Client 和配置装配;当前不需要推送定时任务 |
第三方摄像头访问能力在 Domain 定义 Gateway在 Infrastructure 通过 Dubbo 或 HTTP 实现。外部摄像头 DTO 不进入 Domain 和 Client。
## 8. 待确认问题
### 区域
- 是否需要区域编码,对外唯一标识采用 ID 还是编码?
- 顶级区域 `parent_id` 使用 `NULL` 还是 `0`
- 区域最大层级是多少,是否允许移动节点?
- 区域负责人是否只能有一个?
- 坐标使用哪种坐标系?四点是否永远固定,还是后续允许任意多边形?
- 是否要求判断摄像头坐标或告警坐标是否处于区域内?
- 区域是否需要启用/停用状态?
### 摄像头
- 摄像头第三方接口的系统、协议和 Common Facade 是什么?
- 摄像头唯一主键是什么,是否跨企业唯一?
- 是否支持分页及原型中的名称、等级、在线状态筛选?
- 一个摄像头是否只能绑定一个区域?
- 查询某区域时只展示直接绑定摄像头,还是包含所有子区域摄像头?
- 解绑后负责人是否随绑定记录一起失效?
- 第三方不可用时页面是报错,还是允许展示本地快照?
### 开放接口
- 第三方只查询区域,还是同时查询区域摄像头绑定?
- 需要全量分页、区域树还是更新时间增量查询?
- 是否需要返回逻辑删除区域和已解除绑定关系?
- 鉴权、签名、IP 白名单及租户范围如何确定?
## 9. 当前不开发内容
- 人员、企业、部门、岗位和人脸主数据维护。
- 人员与区域准入关系。
- 摄像头主数据维护和删除。
- 主动推送、推送任务、回执和失败重试。
上一版人员推送 DDL 已删除。当前 `RegionManagementDDL.sql` 是根据原型形成的区域表草案;坐标系、摄像头唯一性和区域删除规则确认后,再作为正式迁移脚本执行。
## 10. 第三方报警接收与处置
### 10.1 接入方式
报警由第三方产生,建议综合平台提供报警接收回调接口,由第三方主动调用:
```text
第三方 -> 鉴权/验签 -> 幂等校验 -> 报警落库 -> 返回受理成功
└-> 用户后续处置
```
`sourceSystem + alarmNo` 作为幂等键。第三方重复推送同一报警编号时返回已受理,不重复新增。接收接口只负责报警可靠落库,不同步执行隐患长流程。
第三方至少传摄像头和区域信息,本系统校验并固化企业、区域及摄像头历史快照。区域或摄像头无法识别时是否拒绝接收,待双方接口协议确定。
报警业务报文只要求当前确认字段:报警编号、报警时间、报警地址、区域、摄像头和一张报警截图。来源系统、接收时间及处置状态属于综合平台接入和管理字段,不要求第三方提供。
### 10.2 图片传输
当前暂定第三方在 JSON 报警报文中直接传单张截图 URL综合平台在 `alarm_image_url` 中保存并用于详情展示,不接收文件流,也不保存 BLOB 或 Base64。
正式联调前需要确认 URL 是否长期有效、是否需要鉴权、是否允许综合平台服务端访问。如果 URL 是短期签名地址,应改为稳定文件 ID或由综合平台接收后转存统一文件服务。
### 10.3 处置状态机
```text
PENDING未处置
├─ 误报=是 -> DISPOSED
└─ 误报=否
├─ 隐患=否 -> 填写结果 -> DISPOSED
└─ 隐患=是 -> 创建隐患 -> DISPOSED
```
- 误报直接闭环,不进入隐患流程。
- 非误报必须选择是否隐患。
- 非隐患必须填写处置结果,提交后闭环。
- 隐患调用 `ZcloudHiddenFacade.aiHiddenAndSave(HiddenAddCmd)`,以报警编号作为 `foreignKey`
- 隐患流程和进度以隐患服务为准,本地不再维护报警闭环状态。
- 当前报警不经过派单、转办或审批,每条报警只提交一次处置结论;处置人、处置时间及公共更新审计字段用于留痕。
### 10.4 数据表
区域、摄像头绑定、报警和处置统一维护在 `web-infrastructure/src/main/resources/RegionManagementDDL.sql`,报警部分包含:
| 表 | 用途 |
| --- | --- |
| `edgeguard_alarm` | 报警事实、区域/摄像头快照、当前状态、统计维度 |
| `edgeguard_alarm_disposal` | 是否误报、是否隐患、直接处置结果及关联隐患 ID |
Common 已有 `ZcloudHiddenFacade``aiHiddenAndSave`、`listByForeignKey` 和 `queryHiddenCheckStatus` 等能力。本系统不复制隐患主表,也不缓存隐患状态、级别和进度。创建成功后只在报警处置表保存 `hidden_id`;详情页根据该 ID 调用隐患服务查询实时详情。
为处理隐患接口临时失败,处置表仅额外保存 `hidden_create_status``hidden_create_error`。它们属于调用可靠性字段,不是隐患业务数据。以报警编号作为 `foreignKey` 保证隐患创建幂等。
### 10.5 统计
一期直接聚合报警主表,不建统计结果表:
- 股份端按 `corp_id` 统计各分公司报警总数、未处置和已处置。
- 分公司端按 `region_id` 统计各区域报警总数、未处置和已处置。
- 可统计误报数、隐患数和非隐患处置数。
报警保存发生时的企业、区域和摄像头快照,后续改名或移动不影响历史台账。数据量较大后再增加按日汇总表。
### 10.6 待确认
- 第三方报警接口的 JSON 报文、鉴权、签名及重试规则。
- 第三方报警 ID 是否在来源系统内永久唯一。
- 报警截图 URL 的有效期、访问鉴权和网络可达性。
- 处置结果字典。
- 误报是否仍需填写处置原因。
- 隐患级别、确认人、整改人和期限由谁填写。
- 是否允许修改处置结论、重新打开报警及是否需要审批。
### 10.7 报警查看权限升级
当前需求只控制报警列表的可见范围,不需要增加业务表:
- 摄像头负责人从报警入库开始即可查看该摄像头产生的报警。
- 报警保持 `PENDING` 且超过配置的升级时长后,所属区域负责人也可以查看。
- 当前升级时长为 10 分钟,必须配置化,禁止硬编码在查询 SQL 中。
- 超时基准使用综合平台 `received_time`,不使用第三方 `alarm_time`,避免延迟推送导致报警刚入库就被升级。
- 报警处置后进入台账的查看范围按页面数据权限确定,不再依赖 10 分钟规则。
查询逻辑:
```text
当前用户是摄像头负责人
OR
当前用户是区域负责人
AND disposal_status = PENDING
AND received_time <= 当前时间 - 配置分钟数
```
摄像头负责人从 `edgeguard_region_camera.leader_user_id` 获取,区域负责人从 `edgeguard_region.leader_user_id` 获取。负责人发生变更后按当前负责人权限查询;如果未来要求报警产生时锁定负责人,再在报警表增加负责人快照字段。
本规则只表示“能够在列表看到”,不表示系统主动通知。若后续要求短信、消息中心或待办提醒,并需要记录发送结果和防止重复通知,再设计通知记录或升级标记。