From ba5d65d438ff351442267478dd00067d997e8f16 Mon Sep 17 00:00:00 2001 From: Jesxion Date: Tue, 14 Apr 2026 16:44:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90API=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1+=E6=95=B0=E6=8D=AE=E5=BA=93=E8=A1=A8?= =?UTF-8?q?=E7=BB=93=E6=9E=84=EF=BC=88=E9=9A=90=E6=82=A3/=E6=96=BD?= =?UTF-8?q?=E5=B7=A5=E6=97=A5=E5=BF=97/AI=E5=88=86=E6=9E=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/api.md: 补充隐患随手拍(5个端点)、施工日志(3个端点)、AI分析(2个端点) - docs/database.md: 全面重写,MySQL+project_id隔离,新增projects/hazards/construction_logs/ai_analysis_results四张表 - SPEC.md: 更新至v0.2.1 --- SPEC.md | 2 +- docs/api.md | 461 ++++++++++++++++++++++++++++++++++++++++++++++- docs/database.md | 242 +++++++++++++++++++------ 3 files changed, 651 insertions(+), 54 deletions(-) diff --git a/SPEC.md b/SPEC.md index 0602dca..98b7620 100644 --- a/SPEC.md +++ b/SPEC.md @@ -52,7 +52,7 @@ | 2026-04-14 | v0.1.2 | 锁定 SQLite 数据库 | | 2026-04-14 | v0.1.3 | 确认设备 API 自带预警判断,移除本地阈值配置 | | 2026-04-14 | v0.1.4 | 确认河南三农推送协议(8个接口,appid/secret部署时配置) | -| 2026-04-14 | v0.2.0 | 技术栈更新(Node.js+MySQL)、补充隐患随手拍/施工日志/AI分析模块、确认隐患分类标准、项目级逻辑租户隔离 | +|| 2026-04-14 | v0.2.1 | API设计补充(隐患随手拍/施工日志/AI分析),数据库表结构完善(MySQL+project_id隔离) | --- diff --git a/docs/api.md b/docs/api.md index 5332c0a..69a36fe 100644 --- a/docs/api.md +++ b/docs/api.md @@ -452,7 +452,466 @@ GET /oss/download-url --- -## 7. 待确认 +## 7. 隐患随手拍接口 + +### 7.1 上报隐患 + +``` +POST /v1/hazards +``` + +**请求体**: +```json +{ + "category": "fall", + "severity": "general", + "description": "A区2楼临边防护缺失", + "photos": ["reports/2026/04/hazard_001_1.jpg", "reports/2026/04/hazard_001_2.jpg"], + "gps_lat": 34.7652, + "gps_lng": 113.6241 +} +``` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| category | string | 是 | 隐患类别代码 | +| severity | string | 是 | 严重程度: `general` / `serious` / `major` | +| description | string | 是 | 隐患描述,2~500字 | +| photos | string[] | 否 | 照片 OSS object_key 列表(先调用 5.1 获取上传凭证) | +| gps_lat | decimal | 否 | GPS 纬度 | +| gps_lng | decimal | 否 | GPS 经度 | + +**自动填充**(无需传入,后台从 JWT Token 解析): +- `reporter_id` — 上报人用户 ID +- `reporter_role` — 上报人身份 +- `project_id` — 所属项目 ID(中间件注入) +- `status` → `pending` +- `reported_at` → 当前服务器时间 + +**响应** (201): +```json +{ + "code": 0, + "message": "success", + "data": { + "id": "hazard_001", + "status": "pending", + "reported_at": "2026-04-14T14:30:00Z" + } +} +``` + +--- + +### 7.2 隐患列表 + +``` +GET /v1/hazards +``` + +**Query 参数**: +| 参数 | 类型 | 说明 | +|------|------|------| +| status | string | 状态: `pending` / `assigned` / `resolved` / 空(全部) | +| category | string | 隐患类别代码 / 空(全部) | +| severity | string | 严重程度: `general` / `serious` / `major` / 空(全部) | +| start_date | date | 筛选开始日期 YYYY-MM-DD | +| end_date | date | 筛选结束日期 YYYY-MM-DD | +| page | int | 页码,默认 1 | +| page_size | int | 每页数量,默认 20,最大 100 | + +**响应** (200): +```json +{ + "code": 0, + "message": "success", + "data": { + "total": 25, + "page": 1, + "page_size": 20, + "items": [ + { + "id": "hazard_001", + "category": "fall", + "category_name": "高处坠落", + "severity": "general", + "severity_name": "一般", + "description": "A区2楼临边防护缺失", + "photo_count": 2, + "gps_lat": 34.7652, + "gps_lng": 113.6241, + "status": "pending", + "reporter": { + "id": 3, + "username": "zhangsan", + "role": "安全员" + }, + "reporter_role": "安全员", + "reported_at": "2026-04-14T14:30:00Z", + "assignee": null, + "resolved_at": null + } + ] + } +} +``` + +**说明**: +- 自动按 `project_id` 过滤(中间件注入) +- 结果按 `reported_at` 倒序 + +--- + +### 7.3 隐患详情 + +``` +GET /v1/hazards/:id +``` + +**响应** (200): +```json +{ + "code": 0, + "message": "success", + "data": { + "id": "hazard_001", + "category": "fall", + "category_name": "高处坠落", + "severity": "general", + "severity_name": "一般", + "description": "A区2楼临边防护缺失", + "photos": [ + "https://jesxion-ai-studio.oss-cn-beijing.aliyuncs.com/reports/2026/04/hazard_001_1.jpg?Signature=...", + "https://jesxion-ai-studio.oss-cn-beijing.aliyuncs.com/reports/2026/04/hazard_001_2.jpg?Signature=..." + ], + "gps_lat": 34.7652, + "gps_lng": 113.6241, + "status": "assigned", + "reporter": { + "id": 3, + "username": "zhangsan", + "role": "安全员" + }, + "reported_at": "2026-04-14T14:30:00Z", + "assignee": { + "id": 5, + "username": "lisi", + "role": "安全负责人" + }, + "assigned_at": "2026-04-14T15:00:00Z", + "resolved_at": null, + "resolve_note": null + } +} +``` + +**说明**: +- 照片返回完整预签名下载 URL(有效 1 小时) +- `resolve_note` 仅 `resolved` 状态时有值 + +--- + +### 7.4 认领隐患 + +``` +POST /v1/hazards/:id/assign +``` + +**前置条件**: 隐患状态为 `pending` + +**请求体**: 空对象 `{}`(操作人从 JWT Token 解析) + +**自动填充**: +- `assignee_id` — 认领人用户 ID +- `assignee_role` — 认领人身份 +- `status` → `assigned` +- `assigned_at` → 当前服务器时间 + +**响应** (200): +```json +{ + "code": 0, + "message": "success", + "data": { + "id": "hazard_001", + "status": "assigned", + "assigned_at": "2026-04-14T15:00:00Z" + } +} +``` + +**错误**: +- `400` 隐患状态不是 `pending`(已认领或已处理) + +--- + +### 7.5 处理完成 + +``` +POST /v1/hazards/:id/resolve +``` + +**前置条件**: 隐患状态为 `assigned` + +**请求体**: +```json +{ + "note": "已重新安装防护栏" +} +``` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| note | string | 否 | 处理说明,0~500字 | + +**自动填充**: +- `resolver_id` — 处理人用户 ID +- `resolver_role` — 处理人身份 +- `status` → `resolved` +- `resolved_at` → 当前服务器时间 + +**响应** (200): +```json +{ + "code": 0, + "message": "success", + "data": { + "id": "hazard_001", + "status": "resolved", + "resolved_at": "2026-04-14T16:00:00Z" + } +} +``` + +**错误**: +- `400` 隐患状态不是 `assigned`(未认领或已处理) + +--- + +## 8. 施工日志接口 + +### 8.1 新建日志 + +``` +POST /v1/logs +``` + +**请求体**: +```json +{ + "date": "2026-04-14", + "part": "A区主体结构", + "content": "3层混凝土浇筑,钢筋绑扎", + "workers": 28, + "equipment": ["tower_crane", "elevator"], + "photos": ["logs/2026/04/log_001_1.jpg"], + "safety_note": "临边作业已挂安全网", + "note": "下午2点停电1小时" +} +``` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| date | date | 是 | 日志日期 YYYY-MM-DD | +| part | string | 是 | 施工部位,2~100字 | +| content | text | 是 | 作业内容,2~2000字 | +| workers | int | 是 | 人员出勤人数,1~9999 | +| equipment | string[] | 否 | 设备运行类型: `tower_crane` / `elevator` | +| photos | string[] | 否 | 现场照片 OSS object_key 列表 | +| safety_note | text | 否 | 安全问题描述,0~1000字 | +| note | text | 否 | 备注,0~500字 | + +**自动填充**: +- `author_id` — 撰写人用户 ID +- `author_role` — 撰写人身份 +- `project_id` — 所属项目 ID +- `created_at` → 当前服务器时间 + +**响应** (201): +```json +{ + "code": 0, + "message": "success", + "data": { + "id": "log_001", + "created_at": "2026-04-14T17:00:00Z" + } +} +``` + +--- + +### 8.2 日志列表 + +``` +GET /v1/logs +``` + +**Query 参数**: +| 参数 | 类型 | 说明 | +|------|------|------| +| start_date | date | 筛选开始日期 YYYY-MM-DD | +| end_date | date | 筛选结束日期 YYYY-MM-DD | +| page | int | 页码,默认 1 | +| page_size | int | 每页数量,默认 20,最大 100 | + +**响应** (200): +```json +{ + "code": 0, + "message": "success", + "data": { + "total": 30, + "page": 1, + "page_size": 20, + "items": [ + { + "id": "log_001", + "date": "2026-04-14", + "part": "A区主体结构", + "content": "3层混凝土浇筑,钢筋绑扎...", + "workers": 28, + "equipment": ["tower_crane", "elevator"], + "photo_count": 1, + "safety_note": "临边作业已挂安全网", + "author": { + "id": 3, + "username": "zhangsan", + "role": "安全员" + }, + "created_at": "2026-04-14T17:00:00Z" + } + ] + } +} +``` + +**说明**: +- 自动按 `project_id` 过滤 +- `content` 列表页截断至 200 字 +- 结果按 `date` 倒序 + +--- + +### 8.3 日志详情 + +``` +GET /v1/logs/:id +``` + +**响应** (200): +```json +{ + "code": 0, + "message": "success", + "data": { + "id": "log_001", + "date": "2026-04-14", + "part": "A区主体结构", + "content": "3层混凝土浇筑,钢筋绑扎", + "workers": 28, + "equipment": ["tower_crane", "elevator"], + "equipment_names": ["塔吊", "升降机"], + "photos": [ + "https://jesxion-ai-studio.oss-cn-beijing.aliyuncs.com/logs/2026/04/log_001_1.jpg?Signature=..." + ], + "safety_note": "临边作业已挂安全网", + "note": "下午2点停电1小时", + "author": { + "id": 3, + "username": "zhangsan", + "role": "安全员" + }, + "created_at": "2026-04-14T17:00:00Z" + } +} +``` + +**说明**: +- 照片返回完整预签名下载 URL(有效 1 小时) +- `equipment_names` 为中文设备名称数组 + +--- + +## 9. AI 智能分析接口 + +### 9.1 分析结果列表 + +``` +GET /v1/ai/analyses +``` + +**Query 参数**: +| 参数 | 类型 | 说明 | +|------|------|------| +| device_id | string | 设备ID / 空(全部) | +| analysis_type | string | 分析类型: `personnel_safety` / `equipment_anomaly` / `environmental_risk` / 空(全部) | +| start_date | datetime | 筛选开始时间 ISO8601 | +| end_date | datetime | 筛选结束时间 ISO8601 | +| page | int | 页码,默认 1 | +| page_size | int | 每页数量,默认 20,最大 100 | + +**响应** (200): +```json +{ + "code": 0, + "message": "success", + "data": { + "total": 15, + "page": 1, + "page_size": 20, + "items": [ + { + "id": "ai_001", + "device_id": "device_001", + "device_name": "1号塔吊", + "analysis_type": "personnel_safety", + "analysis_type_name": "人员安全", + "confidence": 0.92, + "description": "检测到人员靠近吊装区域,触发区域入侵告警", + "triggered_at": "2026-04-14T10:25:00Z" + } + ] + } +} +``` + +**说明**: +- 自动按 `project_id` 过滤(中间件注入) +- 结果按 `triggered_at` 倒序 +- `confidence` 为 0.00~1.00 小数 + +--- + +### 9.2 分析结果详情 + +``` +GET /v1/ai/analyses/:id +``` + +**响应** (200): +```json +{ + "code": 0, + "message": "success", + "data": { + "id": "ai_001", + "device_id": "device_001", + "device_name": "1号塔吊", + "analysis_type": "personnel_safety", + "analysis_type_name": "人员安全", + "confidence": 0.92, + "description": "检测到人员靠近吊装区域,触发区域入侵告警", + "suggestion": "建议立即疏散人员,后续加强吊装区域围挡和警示标志", + "triggered_at": "2026-04-14T10:25:00Z", + "created_at": "2026-04-14T10:25:05Z" + } +} +``` + +--- + +## 10. 待确认 - [x] 预警推送机制 → 轮询(前端每 30s) - [x] JWT 有效期 → 7 天 diff --git a/docs/database.md b/docs/database.md index fd7cf4d..7b7660e 100644 --- a/docs/database.md +++ b/docs/database.md @@ -1,52 +1,80 @@ # 数据库表结构设计 -> 状态: 设计中(MVP 版本,SQLite) +> 状态: 设计中(MVP 版本,MySQL 8.0) + +--- + +## 0. 租户隔离模型 + +> 所有表通过 `project_id` 字段实现项目级逻辑租户隔离。API 中间件在每个请求中自动注入当前用户的 `project_id` 条件,数据查询均以此过滤。 + +| 表名 | 用途 | +|------|------| +| `users` | 用户属于项目 | +| `devices` | 设备属于项目 | +| `device_realtime` | 设备实时数据,属于项目 | +| `device_history` | 设备历史数据,属于项目 | +| `alert_records` | 预警记录,属于项目 | +| `hazards` | 隐患随手拍,属于项目 | +| `construction_logs` | 施工日志,属于项目 | +| `ai_analysis_results` | AI 分析结果,属于项目 | +| `oss_files` | OSS 文件索引,属于项目 | --- ## 1. ER 图概述 ``` -users ─────────┐ - │ 1:N - ▼ - user_device_access (N:M) - │ - ▼ -devices ───────┤ - │ - ├──── 1:N ──── device_realtime (最新状态) - │ - ├──── 1:N ──── device_history (历史数据) - │ - └──── 1:N ──── alert_records (预警记录) +users ──── project_id ──── projects + │ + ┌─────────────────┼─────────────────┐ + │ │ │ + ▼ ▼ ▼ + devices ──────── alert_records hazards + │ │ + ├──── 1:N ──── device_realtime + │ + ├──── 1:N ──── device_history + │ + └──── 1:N ──── ai_analysis_results -alert_records ────── N:1 ──── users (处理人) - -oss_files ──────────────────────────────────────────────────┐ - │ - (其他模块按需关联) │ +construction_logs +oss_files (关联各模块) ``` --- ## 2. 表结构 +### 2.0 projects(项目表) + +| 字段 | 类型 | 约束 | 说明 | +|------|------|------|------| +| id | INT | PRIMARY KEY AUTO_INCREMENT | 项目ID | +| name | VARCHAR(128) | NOT NULL | 项目名称 | +| location | VARCHAR(256) | | 项目地址 | +| status | VARCHAR(32) | DEFAULT 'active' | 状态: active / archived | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | + +--- + ### 2.1 users(用户表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| -| id | SERIAL | PRIMARY KEY | 自增ID | +| id | INT | PRIMARY KEY AUTO_INCREMENT | 用户ID | +| project_id | INT | FOREIGN KEY → projects(id) | 所属项目 | | username | VARCHAR(64) | UNIQUE NOT NULL | 用户名 | | password_hash | VARCHAR(256) | NOT NULL | bcrypt 密码哈希 | -| role | VARCHAR(32) | NOT NULL DEFAULT 'viewer' | 角色: admin / operator / viewer | +| role | VARCHAR(32) | NOT NULL | 身份: 项目经理 / 安全负责人 / 安全员 | | real_name | VARCHAR(128) | | 真实姓名 | | phone | VARCHAR(32) | | 手机号 | | is_active | BOOLEAN | DEFAULT true | 是否启用 | -| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 | -| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 | **索引**: +- `idx_users_project_id` ON (`project_id`) - `idx_users_username` ON (`username`) --- @@ -56,6 +84,7 @@ oss_files ─────────────────────── | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | VARCHAR(64) | PRIMARY KEY | 设备ID(对接已有API的设备编号) | +| project_id | INT | FOREIGN KEY → projects(id) | 所属项目 | | name | VARCHAR(128) | NOT NULL | 设备名称,如 "1号塔吊" | | type | VARCHAR(32) | NOT NULL | 类型: `tower_crane` / `elevator` | | model | VARCHAR(128) | | 设备型号,如 "QTZ500" | @@ -64,11 +93,12 @@ oss_files ─────────────────────── | install_date | DATE | | 安装日期 | | status | VARCHAR(32) | DEFAULT 'offline' | 在线状态: online / offline | | last_seen | TIMESTAMP | | 最后在线时间 | -| config | JSONB | DEFAULT '{}' | 设备配置(预警阈值等) | -| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 | -| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 | +| config | JSON | DEFAULT '{}' | 设备配置 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 | **索引**: +- `idx_devices_project_id` ON (`project_id`) - `idx_devices_type` ON (`type`) - `idx_devices_status` ON (`status`) @@ -78,12 +108,13 @@ oss_files ─────────────────────── | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| -| id | SERIAL | PRIMARY KEY | 自增ID | +| id | INT | PRIMARY KEY AUTO_INCREMENT | 自增ID | +| project_id | INT | FOREIGN KEY → projects(id) | 所属项目 | | device_id | VARCHAR(64) | FOREIGN KEY → devices(id) | 设备ID | | timestamp | TIMESTAMP | NOT NULL | 数据时间戳 | -| raw_data | JSONB | NOT NULL | 原始数据快照 | -| parsed_data | JSONB | NOT NULL | 解析后的结构化数据 | -| created_at | TIMESTAMP | DEFAULT NOW() | 写入时间 | +| raw_data | JSON | NOT NULL | 原始数据快照 | +| parsed_data | JSON | NOT NULL | 解析后的结构化数据 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 写入时间 | **索引**: - `idx_realtime_device_id` ON (`device_id`) @@ -97,18 +128,18 @@ oss_files ─────────────────────── | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| -| id | BIGSERIAL | PRIMARY KEY | 自增ID | +| id | BIGINT | PRIMARY KEY AUTO_INCREMENT | 自增ID | +| project_id | INT | FOREIGN KEY → projects(id) | 所属项目 | | device_id | VARCHAR(64) | FOREIGN KEY → devices(id) | 设备ID | | timestamp | TIMESTAMP | NOT NULL | 数据时间戳 | | metric_name | VARCHAR(64) | NOT NULL | 指标名: load / range / height 等 | | metric_value | DECIMAL(18,4) | NOT NULL | 指标值 | | unit | VARCHAR(32) | | 单位: kN / m / m/s 等 | -| created_at | TIMESTAMP | DEFAULT NOW() | 写入时间 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 写入时间 | **索引**: -- `idx_history_device_time` ON (`device_id`, `timestamp` DESC) +- `idx_history_project_device_time` ON (`project_id`, `device_id`, `timestamp` DESC) - `idx_history_metric` ON (`metric_name`) -- **分区**: 按月分区(`timestamp`),按设备ID + 时间范围查询 > 为节省存储空间,历史数据可定期归档到 OSS(冷存储),DB 仅保留近3个月数据。 @@ -119,19 +150,21 @@ oss_files ─────────────────────── | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | VARCHAR(64) | PRIMARY KEY | 预警ID(UUID) | +| project_id | INT | FOREIGN KEY → projects(id) | 所属项目 | | device_id | VARCHAR(64) | FOREIGN KEY → devices(id) | 设备ID | | device_name | VARCHAR(128) | | 冗余存储,查询方便 | | level | VARCHAR(32) | NOT NULL | 级别: warning / danger | | metric | VARCHAR(64) | NOT NULL | 触发指标: load / wind_speed 等 | -| message | VARCHAR(512) | NOT NULL | 预警消息(设备 API 提供) | +| message | VARCHAR(512) | NOT NULL | 预警消息 | | value | DECIMAL(18,4) | NOT NULL | 触发时的实际值 | | status | VARCHAR(32) | DEFAULT 'unread' | 状态: unread / handled / ignored | -| handled_by | INTEGER | FOREIGN KEY → users(id) | 处理人 | +| handled_by | INT | FOREIGN KEY → users(id) | 处理人 | | handle_note | TEXT | | 处理备注 | -| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | | handled_at | TIMESTAMP | | 处理时间 | **索引**: +- `idx_alerts_project_id` ON (`project_id`) - `idx_alerts_device` ON (`device_id`) - `idx_alerts_status` ON (`status`) - `idx_alerts_level` ON (`level`) @@ -143,22 +176,105 @@ oss_files ─────────────────────── | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| -| id | SERIAL | PRIMARY KEY | 自增ID | +| id | INT | PRIMARY KEY AUTO_INCREMENT | 自增ID | +| project_id | INT | FOREIGN KEY → projects(id) | 所属项目 | | object_key | VARCHAR(512) | UNIQUE NOT NULL | OSS 对象路径 | | filename | VARCHAR(256) | NOT NULL | 原始文件名 | | content_type | VARCHAR(128) | | MIME 类型 | | size | BIGINT | | 文件大小(字节) | -| category | VARCHAR(64) | | 分类: report / certificate / log | -| uploaded_by | INTEGER | FOREIGN KEY → users(id) | 上传人 | -| uploaded_at | TIMESTAMP | DEFAULT NOW() | 上传时间 | -| expires_at | TIMESTAMP | | 过期时间(临时文件) | +| category | VARCHAR(64) | | 分类: hazard / log / ai | +| uploaded_by | INT | FOREIGN KEY → users(id) | 上传人 | +| uploaded_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 上传时间 | **索引**: +- `idx_oss_project_id` ON (`project_id`) - `idx_oss_category` ON (`category`) - `idx_oss_uploaded_by` ON (`uploaded_by`) --- +### 2.7 hazards(隐患随手拍表) + +| 字段 | 类型 | 约束 | 说明 | +|------|------|------|------| +| id | VARCHAR(64) | PRIMARY KEY | 隐患ID(UUID) | +| project_id | INT | FOREIGN KEY → projects(id) | 所属项目 | +| category | VARCHAR(32) | NOT NULL | 隐患类别代码 | +| severity | VARCHAR(32) | NOT NULL | 严重程度: general / serious / major | +| description | TEXT | NOT NULL | 隐患描述 | +| photos | JSON | | 照片 OSS object_key 列表 | +| gps_lat | DECIMAL(10,6) | | GPS 纬度 | +| gps_lng | DECIMAL(10,6) | | GPS 经度 | +| status | VARCHAR(32) | DEFAULT 'pending' | 状态: pending / assigned / resolved | +| reporter_id | INT | FOREIGN KEY → users(id) | 上报人 | +| reporter_role | VARCHAR(32) | | 上报人身份 | +| reported_at | TIMESTAMP | | 上报时间 | +| assignee_id | INT | FOREIGN KEY → users(id) | 认领人 | +| assignee_role | VARCHAR(32) | | 认领人身份 | +| assigned_at | TIMESTAMP | | 认领时间 | +| resolver_id | INT | FOREIGN KEY → users(id) | 处理人 | +| resolver_role | VARCHAR(32) | | 处理人身份 | +| resolve_note | TEXT | | 处理说明 | +| resolved_at | TIMESTAMP | | 处理完成时间 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | + +**索引**: +- `idx_hazards_project_id` ON (`project_id`) +- `idx_hazards_status` ON (`status`) +- `idx_hazards_category` ON (`category`) +- `idx_hazards_severity` ON (`severity`) +- `idx_hazards_reported_at` ON (`reported_at` DESC) + +--- + +### 2.8 construction_logs(施工日志表) + +| 字段 | 类型 | 约束 | 说明 | +|------|------|------|------| +| id | VARCHAR(64) | PRIMARY KEY | 日志ID(UUID) | +| project_id | INT | FOREIGN KEY → projects(id) | 所属项目 | +| date | DATE | NOT NULL | 日志日期 | +| part | VARCHAR(128) | NOT NULL | 施工部位 | +| content | TEXT | NOT NULL | 作业内容 | +| workers | INT | NOT NULL | 人员出勤人数 | +| equipment | JSON | | 设备运行类型数组 | +| photos | JSON | | 现场照片 OSS object_key 列表 | +| safety_note | TEXT | | 安全问题描述 | +| note | TEXT | | 备注 | +| author_id | INT | FOREIGN KEY → users(id) | 撰写人 | +| author_role | VARCHAR(32) | | 撰写人身份 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | + +**索引**: +- `idx_logs_project_id` ON (`project_id`) +- `idx_logs_date` ON (`date` DESC) +- `idx_logs_author_id` ON (`author_id`) + +--- + +### 2.9 ai_analysis_results(AI 智能分析结果表) + +| 字段 | 类型 | 约束 | 说明 | +|------|------|------|------| +| id | VARCHAR(64) | PRIMARY KEY | 分析ID(UUID) | +| project_id | INT | FOREIGN KEY → projects(id) | 所属项目 | +| device_id | VARCHAR(64) | FOREIGN KEY → devices(id) | 关联设备 | +| device_name | VARCHAR(128) | | 设备名称(冗余) | +| analysis_type | VARCHAR(64) | NOT NULL | 分析类型 | +| confidence | DECIMAL(5,4) | | 置信度 0.0000~1.0000 | +| description | TEXT | NOT NULL | AI 分析描述 | +| suggestion | TEXT | | 建议措施 | +| triggered_at | TIMESTAMP | NOT NULL | 触发时间 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 记录创建时间 | + +**索引**: +- `idx_ai_project_id` ON (`project_id`) +- `idx_ai_device_id` ON (`device_id`) +- `idx_ai_type` ON (`analysis_type`) +- `idx_ai_triggered_at` ON (`triggered_at` DESC) + +--- + ## 3. 初始数据 ### 3.1 超级管理员 @@ -167,27 +283,49 @@ oss_files ─────────────────────── |----------|----------|------| | admin | (首次部署时设置) | admin | +### 3.2 隐患类别字典(写入代码枚举,非数据库表) + +| 代码 | 名称 | +|------|------| +| fall | 高处坠落 | +| object_strike | 物体打击 | +| mechanical | 机械伤害 | +| electric | 触电 | +| collapse | 坍塌 | +| fire | 火灾 | +| lifting | 起重伤害 | +| vehicle | 车辆伤害 | +| blasting | 放炮 | +| drowning | 淹溺 | +| burn | 灼烫 | +| construction | 现场临建设施 | +| other | 其他伤害 | + +### 3.3 AI 分析类型字典 + +| 代码 | 名称 | +|------|------| +| personnel_safety | 人员安全 | +| equipment_anomaly | 设备异常 | +| environmental_risk | 环境风险 | + --- ## 4. 数据库初始化 -MVP 阶段使用 SQLite,`schema.sql` 定义建表语句,启动时自动初始化: +MVP 阶段使用 MySQL 8.0,`schema.sql` 定义建表语句: -```bash -# 手动初始化(可选,生产环境自动执行) -sqlite3 smart_project.db < schema.sql - -# 或通过 Python 脚本 -python -m src.db.init_db +```sql +-- 手动初始化 +mysql -u root -p smart_project < schema.sql ``` -> SQLite 无需迁移工具,每次表结构变更手动更新 `schema.sql` 并重启服务即可。 +> 每次表结构变更手动更新 `schema.sql` 并重启服务。 --- ## 5. 待确认 +- [x] 数据库 → MySQL 8.0(MVP) - [x] OSS Bucket → `jesxion-ai-studio`(开发测试阶段) -- [x] 数据库 → SQLite(MVP) - [ ] 设备 API 协议格式(塔吊/升降机数据字段)— 厂家文档后续提供 -- [ ] 多租户隔离方案(MVP 单租户,后续扩展)