Skip to content

数据库设计 (Database Design)

YunStack-IoT 使用 PostgreSQL 作为核心业务数据库,利用其强大的关系查询能力和对 JSONB 的良好支持。

1. ER 关系图

2. Prisma Schema 定义

2.1 用户表 (User)

存储平台用户的基础信息。角色通过 RBAC 关联表实现,不再使用简单 role 字段。

prisma
model User {
  id        String     @id @default(uuid())
  username  String     @unique @db.VarChar(50)
  password  String     @db.VarChar(255)
  nickname  String?    @db.VarChar(50)
  email     String?    @unique
  avatar    String?
  status    Int        @default(1)  // 1: 启用, 0: 禁用
  createdAt DateTime   @default(now()) @map("created_at")
  updatedAt DateTime   @updatedAt @map("updated_at")

  devices        Device[]        // 绑定的设备
  roles          UserRole[]      // 多对多角色关联
  organizations  Organization[]  // 创建的组织

  @@map("users")
}

2.2 角色 (Role)

prisma
model Role {
  id          Int       @id @default(autoincrement())
  name        String    @db.VarChar(50)
  code        String    @unique @db.VarChar(50)  // admin, user, operator
  sort        Int       @default(0)
  createdAt   DateTime  @default(now()) @map("created_at")
  updatedAt   DateTime  @updatedAt @map("updated_at")

  users       UserRole[]
  permissions RolePermission[]

  @@map("roles")
}

2.3 权限 (Permission)

prisma
model Permission {
  id          Int       @id @default(autoincrement())
  name        String    @db.VarChar(100)
  code        String    @unique @db.VarChar(100) // system:user:list, device:control
  type        String    @db.VarChar(20)          // menu, api
  parentId    Int?      @map("parent_id")
  sort        Int       @default(0)
  createdAt   DateTime  @default(now()) @map("created_at")

  roles       RolePermission[]

  @@map("permissions")
}

2.4 用户-角色关联 (UserRole)

prisma
model UserRole {
  userId    String
  roleId    Int
  createdAt DateTime @default(now()) @map("created_at")

  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  role      Role     @relation(fields: [roleId], references: [id], onDelete: Cascade)

  @@id([userId, roleId])
  @@map("user_roles")
}

2.5 角色-权限关联 (RolePermission)

prisma
model RolePermission {
  roleId       Int
  permissionId Int

  role         Role       @relation(fields: [roleId], references: [id], onDelete: Cascade)
  permission   Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)

  @@id([roleId, permissionId])
  @@map("role_permissions")
}

2.6 组织 (Organization)

prisma
model Organization {
  id          String    @id @default(uuid()) @db.VarChar(36)
  name        String    @db.VarChar(100)
  description String?   @db.Text
  ownerId     String    @map("owner_id")
  createdAt   DateTime  @default(now()) @map("created_at")
  updatedAt   DateTime  @updatedAt @map("updated_at")

  owner       User      @relation(fields: [ownerId], references: [id], onDelete: Cascade)
  rooms       Room[]

  @@map("organizations")
}

2.7 房间 (Room)

prisma
model Room {
  id             Int       @id @default(autoincrement())
  name           String    @db.VarChar(100)
  organizationId String    @map("organization_id")
  sort           Int       @default(0)
  createdAt      DateTime  @default(now()) @map("created_at")
  updatedAt      DateTime  @updatedAt @map("updated_at")

  organization   Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  devices        Device[]

  @@unique([organizationId, name])  // 同组织下房间名唯一
  @@map("rooms")
}

2.8 产品表 (Product)

定义一类设备,包含物模型和鉴权密钥。

prisma
model Product {
  id          String   @id // ProductKey, e.g., "p_relay_4ch"
  name        String   @db.VarChar(64)
  description String?  @db.Text
  thingModel  Json     @map("thing_model") // 物模型定义 (JSONB)
  transport   String   @default("MQTT") // MQTT, HTTP, CoAP
  secret      String   @default(uuid()) // ProductSecret
  createdAt   DateTime @default(now()) @map("created_at")
  
  devices     Device[]

  @@map("products")
}

2.9 设备表 (Device)

存储具体的设备实例,关联产品和用户。

prisma
model Device {
  id           String    @id @default(uuid()) // 逻辑 ID
  productId    String    @map("product_id")
  name         String    @db.VarChar(64)
  physicalId   String    @map("physical_id") // MAC/SN
  secret       String    @default(uuid()) // DeviceSecret
  ownerId      String?   @map("owner_id")
  roomId       Int?      @map("room_id")
  
  // 状态字段
  isOnline     Boolean   @default(false) @map("is_online")
  lastOnlineAt DateTime? @map("last_online_at")
  shadow       Json      @default("{}") // 设备影子权威数据(desired + reported + version + updatedAt),热缓存见 Redis
  firmwareVersion String? @map("firmware_version")
  
  createdAt    DateTime  @default(now()) @map("created_at")
  updatedAt    DateTime  @updatedAt @map("updated_at")

  // 关联关系
  product      Product   @relation(fields: [productId], references: [id])
  owner        User?     @relation(fields: [ownerId], references: [id])
  room         Room?     @relation(fields: [roomId], references: [id])
  logs         DeviceLog[]
  otaTasks     OtaTask[]

  @@unique([productId, physicalId]) // 联合唯一索引
  @@map("devices")
}

2.10 设备日志表 (DeviceLog)

记录设备的关键行为(报警、上下线、关键操作)。

prisma
model DeviceLog {
  id        BigInt   @id @default(autoincrement())
  deviceId  String   @map("device_id")
  type      String   @db.VarChar(20) // online, offline, alert, ota
  content   Json?    // 日志详情 (JSONB)
  createdAt DateTime @default(now()) @map("created_at")

  device    Device   @relation(fields: [deviceId], references: [id])

  @@index([deviceId, createdAt(sort: Desc)])
  @@map("device_logs")
}

2.11 固件版本 (Firmware)

prisma
model Firmware {
  id          String    @id @default(uuid()) @db.VarChar(36)
  productId   String    @map("product_id")
  version     String    @db.VarChar(20)   // 语义化版本号
  url         String    @db.VarChar(500)  // OSS 下载 URL
  size        Int                         // 文件大小(字节)
  md5         String    @db.VarChar(32)
  changelog   String?   @db.Text
  uploadedAt  DateTime  @default(now()) @map("uploaded_at")

  product     Product   @relation(fields: [productId], references: [id])
  otaTasks    OtaTask[]

  @@unique([productId, version])  // 同产品下版本号唯一
  @@map("firmwares")
}

2.12 升级任务 (OtaTask)

prisma
model OtaTask {
  id          String    @id @default(uuid()) @db.VarChar(36)
  firmwareId  String    @map("firmware_id")
  deviceId    String    @map("device_id")
  status      String    @default("pending") @db.VarChar(20) // pending, notified, downloading, installing, success, failed
  progress    Int       @default(0)
  createdAt   DateTime  @default(now()) @map("created_at")
  completedAt DateTime? @map("completed_at")

  firmware    Firmware  @relation(fields: [firmwareId], references: [id])
  device      Device    @relation(fields: [deviceId], references: [id])

  @@index([deviceId])
  @@index([status])
  @@map("ota_tasks")
}

3. 索引策略

  • users: username 唯一索引,email 唯一索引(已有 @unique)
  • roles: code 唯一索引(已有 @unique)
  • permissions: code 唯一索引(已有 @unique)
  • organizations: idx_org_owner: owner_id,用于查询用户创建的组织列表
  • rooms: idx_room_org: (organization_id, name) 联合唯一索引,idx_room_sort: (organization_id, sort)
  • devices:
    • idx_devices_physical_id: (product_id, physical_id) 联合唯一索引,用于设备认证。
    • idx_devices_owner: owner_id,用于查询用户绑定的设备列表。
  • device_logs:
    • idx_logs_device_time: (device_id, created_at DESC),用于查询某设备的历史日志。
  • firmwares: (product_id, version) 联合唯一索引,用于同产品下版本号去重。
  • ota_tasks:
    • idx_ota_device: device_id,用于查询某设备的所有升级任务。
    • idx_ota_status: status,用于查询待执行/进行中的任务。

4. 时序数据 (InfluxDB)

存储策略

配置项
Bucketyunstack_telemetry
Measurementdevice_properties
写入精度秒级
Retention Policy原始数据 90 天

数据模型

类型字段说明
Tagproduct_id产品 ID
Tagdevice_id逻辑设备 ID
Tagfirmware_version固件版本号(设备上报时携带)
Tagproperty属性名 (如 relay_1, temperature)
Fieldvalue属性值 (float/bool/string)
Timestamptime设备上报时间

写入示例 (Line Protocol)

device_properties,product_id=p_relay_4ch,device_id=d1a2b3,property=relay_1 value=1i 1715846400000000000
device_properties,product_id=p_relay_4ch,device_id=d1a2b3,property=chip_temp value=45.2 1715846400000000000

查询示例

typescript
// Flux 查询: 设备过去 24h 的温度
const query = `
  from(bucket: "yunstack_telemetry")
    |> range(start: -24h)
    |> filter(fn: (r) => r.device_id == "d1a2b3c4-..." and r.property == "chip_temp")
    |> aggregateWindow(every: 1m, fn: mean)
`;
const result = await influxQueryApi.collectRows(query);

降采样策略

精度保留周期聚合方式
原始 (秒级)90 天
1 分钟均值1 年mean/max/min
1 小时均值永久mean/max/min

降采样通过 InfluxDB Task 定时执行,查询历史数据自动选择合适精度。