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

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

---

## 一、领域概述

### 1.1 领域职责

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

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

### 1.2 核心概念

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

---

## 二、领域模型

### 2.1 聚合根设计

#### SpaceNode（空间节点）

```java
@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（房间详情）

```java
@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（产权信息）

```java
@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（业主）

```java
@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（巡检计划）

```java
@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（巡检点）

```java
@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（巡检任务）

```java
@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（巡检记录）

```java
@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（访客预约）

```java
@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（访客通行记录）

```java
@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（访客黑名单）

```java
@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

```java
@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

```java
@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

```java
@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~~ 已确认当前归属合理 |

---

## 七、数据库表结构

```sql
-- 空间节点表
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 服务负责人维护
