ether-docs/02-DESIGN/domains/01-SPACE_AND_MDM.md

20 KiB
Raw Blame History

空间与主数据领域技术方案

领域编号: 4.1
微服务: ether-mdm
最后更新: 2026-02-14


一、领域概述

1.1 领域职责

空间与主数据领域是 Ether 平台的物理数字孪生底座,负责管理:

  • 物理空间的层级结构(园区→楼栋→楼层→房间)
  • 房产明细与权属关系
  • 业主信息管理
  • 巡检管理(计划、任务、记录)
  • 访客管理(预约、记录、黑名单)

1.2 核心概念

概念 说明 对应实体
空间节点 物理空间的抽象,支持树形结构 SpaceNode
房间详情 房产的具体属性信息 RoomDetail
产权 房产与业主的关联关系 Ownership
业主 房产的所有者或使用者 Owner
巡检 定期检查任务体系 InspectionPlan/Task/Record
访客 访客预约与通行管理 VisitorAppointment/Record

二、领域模型

2.1 聚合根设计

SpaceNode空间节点

@Entity
@Table(name = "mdm_space_node")
@Data
public class SpaceNode {
    @Id
    private UUID id;

    private UUID projectId;
    private String code;           // 节点编码,唯一
    private String name;           // 节点名称
    private SpaceNodeType nodeType; // PROJECT/BUILDING/FLOOR/ROOM/AREA
    private SpaceNodeStatus status; // ACTIVE/INACTIVE

    // 树形结构
    private UUID parentId;         // 父节点ID
    private String treePath;       // 路径: 1.2.3.4
    private Integer level;         // 层级: 0-4
    private Integer sortOrder;     // 排序

    // 空间属性
    private BigDecimal areaSqm;    // 面积
    private BigDecimal longitude;  // 经度
    private BigDecimal latitude;   // 纬度
    private String address;        // 地址

    // 扩展属性(JSONB)
    private String attributes;

    // 审计字段
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private UUID createdBy;
    private UUID updatedBy;
}

业务规则:

  • code 在同一项目下唯一
  • treePath 自动生成,格式为父路径+当前ID
  • 删除节点时检查是否有子节点
  • 删除节点时检查是否有关联业务数据

RoomDetail房间详情

@Entity
@Table(name = "mdm_room_detail")
@Data
public class RoomDetail {
    @Id
    private UUID id;
    private UUID spaceNodeId;      // 关联SpaceNode

    // 房产属性
    private String roomType;       // 住宅/商铺/办公/仓库
    private BigDecimal buildArea;  // 建筑面积
    private BigDecimal usableArea; // 使用面积
    private String orientation;    // 朝向
    private Integer floor;         // 所在楼层
    private Integer roomCount;     // 房间数
    private Integer hallCount;     // 厅数

    // 状态
    private RoomStatus status;     // 空置/已售/已租/装修中

    // 扩展属性
    private String attributes;

    // 审计字段
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

Ownership产权信息

@Entity
@Table(name = "mdm_ownership")
@Data
public class Ownership {
    @Id
    private UUID id;

    private UUID spaceNodeId;      // 关联房产
    private UUID ownerId;          // 关联业主

    // 产权属性
    private OwnershipType type;    // 产权/使用权/租赁权
    private BigDecimal sharePercent; // 产权份额(0-100)
    private LocalDate startDate;   // 起始日期
    private LocalDate endDate;     // 结束日期(租赁)

    // 证件信息
    private String certType;       // 房产证/购房合同/租赁合同
    private String certNo;         // 证件号码
    private String certFileUrl;    // 证件扫描件

    // 状态
    private OwnershipStatus status; // 有效/过期/注销

    // 审计字段
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

Owner业主

@Entity
@Table(name = "mdm_owner")
@Data
public class Owner {
    @Id
    private UUID id;
    private UUID projectId;

    // 基本信息
    private String name;
    private String phone;
    private String email;
    private OwnerType type;        // 个人/企业

    // 证件信息
    private String idCardType;     // 身份证/护照/营业执照
    private String idCardNo;
    private String idCardFileUrl;

    // 企业信息
    private String companyName;
    private String unifiedSocialCreditCode;

    // 关联账户
    private UUID userId;           // 关联系统用户

    // 状态
    private OwnerStatus status;

    // 审计字段
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

三、巡检管理

3.1 聚合根设计

InspectionPlan巡检计划

@Entity
@Table(name = "mdm_inspection_plan")
@Data
public class InspectionPlan {
    @Id
    private UUID id;
    private UUID projectId;

    private String name;
    private String description;

    // 巡检类型
    private InspectionType type;   // DAILY/REGULAR/SPECIAL

    // 周期配置
    private String cronExpression; // Cron表达式
    private LocalTime executeTime; // 执行时间
    private Integer advanceDays;   // 提前生成任务天数

    // 状态
    private PlanStatus status;     // ENABLED/DISABLED

    // 关联巡检点
    @OneToMany(mappedBy = "plan", cascade = CascadeType.ALL)
    private List<InspectionPoint> points;

    // 审计字段
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

InspectionPoint巡检点

@Entity
@Table(name = "mdm_inspection_point")
@Data
public class InspectionPoint {
    @Id
    private UUID id;
    private UUID planId;

    private UUID spaceNodeId;      // 关联空间节点
    private String name;
    private String description;
    private Integer sortOrder;

    // 检查项(JSONB)
    private String checkItems;     // [{"item":"消防设施","standard":"正常","type":"BOOLEAN"}]

    // 关联设备
    private UUID equipmentId;      // 可选,关联设备
}

InspectionTask巡检任务

@Entity
@Table(name = "mdm_inspection_task")
@Data
public class InspectionTask {
    @Id
    private UUID id;
    private UUID projectId;
    private UUID planId;

    private String taskNo;         // 任务编号
    private LocalDate planDate;    // 计划日期

    // 执行人
    private UUID inspectorId;
    private String inspectorName;

    // 状态
    private TaskStatus status;     // PENDING/IN_PROGRESS/COMPLETED/OVERDUE

    // 时间记录
    private LocalDateTime startTime;
    private LocalDateTime endTime;

    // 结果
    private TaskResult result;     // NORMAL/ABNORMAL
    private String remark;

    // 关联记录
    @OneToMany(mappedBy = "task")
    private List<InspectionRecord> records;

    // 异常处理
    private UUID workOrderId;      // 异常时关联的工单

    // 审计字段
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

InspectionRecord巡检记录

@Entity
@Table(name = "mdm_inspection_record")
@Data
public class InspectionRecord {
    @Id
    private UUID id;
    private UUID taskId;
    private UUID pointId;

    // 巡检结果
    private String checkItem;      // 检查项名称
    private String standard;       // 标准值
    private String actualValue;    // 实际值
    private CheckResult result;    // PASS/FAIL/NA

    // 备注和图片
    private String remark;
    private String images;

    // 定位
    private BigDecimal longitude;
    private BigDecimal latitude;

    // 时间
    private LocalDateTime checkTime;
}

3.2 巡检流程

1. 创建巡检计划
   ↓
2. 定时生成巡检任务(每天凌晨)
   ↓
3. 巡检人接收任务通知
   ↓
4. 现场扫码签到GPS定位校验
   ↓
5. 按巡检点逐项检查
   ↓
6. 提交巡检结果
   ↓
7. 异常自动创建工单(可选)
   ↓
8. 任务完成

四、访客管理

4.1 聚合根设计

VisitorAppointment访客预约

@Entity
@Table(name = "mdm_visitor_appointment")
@Data
public class VisitorAppointment {
    @Id
    private UUID id;
    private UUID projectId;

    // 访客信息
    private String visitorName;
    private String visitorPhone;
    private String visitorIdCard;
    private VisitorType visitorType; // VISITOR/EXPRESS/DELIVERY/MAINTENANCE

    // 被访人信息
    private UUID hostId;
    private String hostName;
    private String hostPhone;
    private UUID spaceNodeId;      // 访问地点

    // 预约信息
    private LocalDate visitDate;
    private LocalTime visitTime;
    private Integer duration;      // 预计时长(分钟)
    private String purpose;
    private Integer visitorCount;  // 访客人数

    // 凭证
    private String qrCode;         // 动态二维码
    private LocalDateTime qrExpireTime;

    // 车牌(车辆访客)
    private String plateNumber;

    // 状态
    private AppointmentStatus status; // PENDING/CONFIRMED/CANCELLED/COMPLETED/EXPIRED

    // 审计字段
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private UUID createdBy;
}

VisitorAccessRecord访客通行记录

@Entity
@Table(name = "mdm_visitor_access_record")
@Data
public class VisitorAccessRecord {
    @Id
    private UUID id;
    private UUID appointmentId;

    // 通行信息
    private LocalDateTime entryTime;
    private LocalDateTime exitTime;
    private String entryGate;      // 入口
    private String exitGate;       // 出口

    // 验证方式
    private VerifyType verifyType; // QR_CODE/ID_CARD/FACE
    private String verifyCode;

    // 现场照片
    private String entryPhoto;
    private String exitPhoto;

    // 设备信息
    private String deviceId;       // 门禁设备ID

    // 状态
    private AccessStatus status;   // ENTERED/EXITED
}

VisitorBlacklist访客黑名单

@Entity
@Table(name = "mdm_visitor_blacklist")
@Data
public class VisitorBlacklist {
    @Id
    private UUID id;
    private UUID projectId;

    // 访客标识(至少填一项)
    private String visitorName;
    private String visitorPhone;
    private String visitorIdCard;

    // 拉黑信息
    private String reason;
    private BlacklistType type;    // TEMPORARY/PERMANENT
    private LocalDateTime blockTime;
    private LocalDateTime expireTime; // null为永久

    // 操作人
    private UUID operatorId;
    private String operatorName;

    // 状态
    private BlacklistStatus status; // ACTIVE/LIFTED
}

4.2 访客流程

1. 业主发起预约APP/小程序)
   ↓
2. 系统生成动态二维码
   ↓
3. 访客收到邀请(短信/微信)
   ↓
4. 访客到达现场
   ↓
5. 扫码/刷身份证通行
   ↓
6. 系统记录入场时间
   ↓
7. 业主收到通知
   ↓
8. 访客离场扫码
   ↓
9. 系统记录离场时间

五、API 接口

5.1 SpaceNode API

@RestController
@RequestMapping("/api/v1/mdm/space-nodes")
@Tag(name = "空间节点管理")
public class SpaceNodeController {

    @PostMapping
    @Operation(summary = "创建空间节点")
    public Result<SpaceNodeVO> create(@RequestBody @Valid SpaceNodeCreateRequest request);

    @GetMapping("/tree")
    @Operation(summary = "获取空间树")
    public Result<List<SpaceNodeTreeVO>> tree(@RequestParam UUID projectId);

    @GetMapping("/{id}")
    @Operation(summary = "获取节点详情")
    public Result<SpaceNodeVO> getById(@PathVariable UUID id);

    @PutMapping("/{id}")
    @Operation(summary = "更新节点")
    public Result<SpaceNodeVO> update(@PathVariable UUID id,
                                       @RequestBody @Valid SpaceNodeUpdateRequest request);

    @DeleteMapping("/{id}")
    @Operation(summary = "删除节点")
    public Result<Void> delete(@PathVariable UUID id);

    @GetMapping("/{id}/children")
    @Operation(summary = "获取子节点")
    public Result<List<SpaceNodeVO>> getChildren(@PathVariable UUID id);

    @GetMapping("/{id}/path")
    @Operation(summary = "获取节点路径")
    public Result<List<SpaceNodeVO>> getPath(@PathVariable UUID id);
}

5.2 Inspection API

@RestController
@RequestMapping("/api/v1/mdm/inspections")
@Tag(name = "巡检管理")
public class InspectionController {

    // 巡检计划
    @PostMapping("/plans")
    @Operation(summary = "创建巡检计划")
    public Result<InspectionPlanVO> createPlan(@RequestBody @Valid InspectionPlanCreateRequest request);

    @GetMapping("/plans")
    @Operation(summary = "分页查询计划")
    public Result<Page<InspectionPlanVO>> pagePlans(InspectionPlanQueryRequest request);

    // 巡检任务
    @GetMapping("/tasks")
    @Operation(summary = "分页查询任务")
    public Result<Page<InspectionTaskVO>> pageTasks(InspectionTaskQueryRequest request);

    @PostMapping("/tasks/{id}/start")
    @Operation(summary = "开始巡检")
    public Result<InspectionTaskVO> startTask(@PathVariable UUID id);

    @PostMapping("/tasks/{id}/complete")
    @Operation(summary = "完成巡检")
    public Result<InspectionTaskVO> completeTask(@PathVariable UUID id,
                                                  @RequestBody InspectionCompleteRequest request);

    @PostMapping("/tasks/{id}/records")
    @Operation(summary = "提交巡检记录")
    public Result<Void> submitRecords(@PathVariable UUID id,
                                       @RequestBody List<InspectionRecordSubmitRequest> records);
}

5.3 Visitor API

@RestController
@RequestMapping("/api/v1/mdm/visitors")
@Tag(name = "访客管理")
public class VisitorController {

    @PostMapping("/appointments")
    @Operation(summary = "创建访客预约")
    public Result<VisitorAppointmentVO> createAppointment(@RequestBody @Valid VisitorAppointmentCreateRequest request);

    @GetMapping("/appointments/{id}/qr-code")
    @Operation(summary = "获取访客二维码")
    public Result<String> getQrCode(@PathVariable UUID id);

    @PostMapping("/access/verify")
    @Operation(summary = "验证访客凭证")
    public Result<VisitorAccessResultVO> verifyAccess(@RequestBody VisitorVerifyRequest request);

    @PostMapping("/access/entry")
    @Operation(summary = "记录访客入场")
    public Result<Void> recordEntry(@RequestBody VisitorEntryRequest request);

    @PostMapping("/access/exit")
    @Operation(summary = "记录访客离场")
    public Result<Void> recordExit(@RequestBody VisitorExitRequest request);

    @PostMapping("/blacklist")
    @Operation(summary = "添加黑名单")
    public Result<VisitorBlacklistVO> addBlacklist(@RequestBody @Valid BlacklistAddRequest request);
}

六、实现状态与差异

6.1 实现状态

功能模块 实现状态 备注
SpaceNode 🟢 已实现 基础CRUD、树形查询
RoomDetail 🟢 已实现 基础CRUD
Ownership 🟢 已实现 基础CRUD
Owner 🟢 已实现 基础CRUD
InspectionPlan 🟢 已实现 基础CRUD
InspectionTask 🟢 已实现 含定时任务生成
VisitorAppointment 🟢 已实现 含二维码生成
VisitorAccessRecord 🟢 已实现 基础记录
VisitorBlacklist 🟢 已实现 基础CRUD

6.2 与设计方案的差异

设计项 设计方案 现有实现 差异分析
空间坐标 PostGIS geometry longitude/latitude ⚠️ 移除了PostGIS依赖简化实现
树形结构 PostgreSQL ltree 字符串treePath ⚠️ 未使用ltree扩展当前数据规模可接受
巡检归属 ether-ops ether-mdm 已确认:巡检点与空间节点紧密关联,归属 ether-mdm 合理
访客归属 ether-ops ether-mdm 已确认:访客与业主/空间紧密关联,归属 ether-mdm 合理
设备管理 曾在 ether-mdm ether-asset 已修正:设备相关功能已迁移至 ether-asset 服务

6.3 待改进项

优先级 改进项 说明
P2 优化树形查询 考虑引入ltree或物化路径优化
P2 空间搜索 基于经纬度的附近空间查询
P3 领域边界调整 评估巡检、访客是否迁移到ether-ops 已确认当前归属合理

七、数据库表结构

-- 空间节点表
CREATE TABLE mdm_space_node (
    id UUID PRIMARY KEY,
    project_id UUID NOT NULL,
    code VARCHAR(50) NOT NULL,
    name VARCHAR(100) NOT NULL,
    node_type VARCHAR(20) NOT NULL,
    status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
    parent_id UUID,
    tree_path VARCHAR(1000),
    level INTEGER,
    sort_order INTEGER DEFAULT 0,
    area_sqm NUMERIC(10,2),
    longitude NUMERIC(10,6),
    latitude NUMERIC(10,6),
    address VARCHAR(255),
    description VARCHAR(500),
    attributes JSONB,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
    created_by UUID,
    updated_by UUID,
    UNIQUE(project_id, code)
);

-- 创建索引
CREATE INDEX idx_space_node_project ON mdm_space_node(project_id);
CREATE INDEX idx_space_node_parent ON mdm_space_node(parent_id);
CREATE INDEX idx_space_node_path ON mdm_space_node(tree_path);
CREATE INDEX idx_space_node_type ON mdm_space_node(node_type);

-- 巡检计划表
CREATE TABLE mdm_inspection_plan (
    id UUID PRIMARY KEY,
    project_id UUID NOT NULL,
    name VARCHAR(100) NOT NULL,
    description VARCHAR(500),
    type VARCHAR(20) NOT NULL,
    cron_expression VARCHAR(50),
    execute_time TIME,
    advance_days INTEGER DEFAULT 1,
    status VARCHAR(20) NOT NULL DEFAULT 'ENABLED',
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
    created_by UUID,
    updated_by UUID
);

-- 巡检任务表
CREATE TABLE mdm_inspection_task (
    id UUID PRIMARY KEY,
    project_id UUID NOT NULL,
    plan_id UUID NOT NULL,
    task_no VARCHAR(32) NOT NULL,
    plan_date DATE NOT NULL,
    inspector_id UUID,
    inspector_name VARCHAR(100),
    status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
    start_time TIMESTAMP,
    end_time TIMESTAMP,
    result VARCHAR(20),
    remark VARCHAR(500),
    work_order_id UUID,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

-- 访客预约表
CREATE TABLE mdm_visitor_appointment (
    id UUID PRIMARY KEY,
    project_id UUID NOT NULL,
    visitor_name VARCHAR(100) NOT NULL,
    visitor_phone VARCHAR(20),
    visitor_id_card VARCHAR(18),
    visitor_type VARCHAR(20) NOT NULL,
    host_id UUID,
    host_name VARCHAR(100),
    host_phone VARCHAR(20),
    space_node_id UUID,
    visit_date DATE NOT NULL,
    visit_time TIME,
    duration INTEGER,
    purpose VARCHAR(200),
    visitor_count INTEGER DEFAULT 1,
    qr_code VARCHAR(255),
    qr_expire_time TIMESTAMP,
    plate_number VARCHAR(20),
    status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
    created_by UUID
);

文档维护: 本领域技术方案由 ether-mdm 服务负责人维护