系统采用 B/S 架构,前后端分离,后端采用模块化单体 (Modular Monolith) 架构(便于部署与维护,内部模块解耦),数据层采用冷热分离策略。
| 领域 | 技术选型 | 版本/备注 |
|---|---|---|
| 后端语言 | Golang | 1.21+ |
| Web 框架 | Gin | 高性能 HTTP 框架 |
| ORM 框架 | GORM | 支持 PG JSONB 与 关联查询 |
| 前端框架 | Vue 3 | Composition API + TypeScript |
| 构建工具 | Vite 5 | + Pinia (状态管理) |
| UI 组件 | Element Plus | + ECharts 5 (图表) + AntV L7 (GIS) |
| 关系型库 | PostgreSQL | 15+ (利用 JSONB, CTE 递归特性) |
| 时序数据库 | TDengine | 3.0+ (核心,存储电力数据) |
| 缓存/队列 | Redis | 7.0 (缓存/PubSub) |
| 消息中间件 | EMQX | 5.0 (MQTT Broker) |
| 反向代理 | Nginx | 1.24+ (静态托管/网关) |
利用 PG 的高级特性处理复杂属性与层级。
-- 1. 接入源配置 (存敏感信息与异构配置)
CREATE TABLE integration_source (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
driver_type VARCHAR(32) NOT NULL, -- 'HOME_ASSISTANT', 'MODBUS_TCP', 'MQTT'
config JSONB NOT NULL, -- e.g. {"ip":"192.168.1.10", "token":"eyJ...", "port":502}
status SMALLINT DEFAULT 0, -- 0:Offline, 1:Online
sync_policy JSONB, -- 同步策略配置
updated_at TIMESTAMP
);
-- 2. 统一设备表 (核心资产)
CREATE TABLE device (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source_id UUID REFERENCES integration_source(id),
external_id VARCHAR(128) NOT NULL,
-- 属性
name VARCHAR(128),
device_type VARCHAR(32), -- 'SWITCH', 'METER', 'SENSOR'
-- 计量核心逻辑
metering_mode VARCHAR(16), -- 'REAL', 'VIRTUAL', 'COMPOUND'
rated_power NUMERIC(10, 2), -- 虚拟计量额定功率 (W)
attribute_mapping JSONB, -- 复合映射: {"power": "sensor.p1", "temp": "sensor.t1"}
-- 关联关系
location_id UUID REFERENCES sys_location(id),
dept_id UUID REFERENCES sys_dept(id),
is_active BOOLEAN DEFAULT TRUE,
UNIQUE (source_id, external_id) -- 防止重复导入
);
-- 3. 空间拓扑表 (支持无限层级)
CREATE TABLE sys_location (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
parent_id UUID,
name VARCHAR(64),
type VARCHAR(16), -- 'PARK', 'BUILDING', 'FLOOR', 'ROOM'
ancestors TEXT, -- 祖级列表 "0,uuid1,uuid2" (便于快速搜索子孙)
sort INT
);
-- 索引优化: CREATE INDEX idx_loc_ancestors ON sys_location(ancestors);
利用超级表 (Super Table) 实现降维聚合。
-- 电力数据超级表
CREATE STABLE power_readings (
ts TIMESTAMP,
power FLOAT, -- 瞬时功率 (W)
voltage FLOAT, -- 电压 (V)
current FLOAT, -- 电流 (A)
energy_acc DOUBLE, -- 累计能耗 (kWh, 可选,若设备自身有读数)
is_virtual BOOL -- 是否为虚拟计算值
) TAGS (
device_id BINARY(36), -- 对应 PG device.id
location_id BINARY(36),-- 冗余存储,加速空间聚合查询
dept_id BINARY(36) -- 冗余存储,加速部门聚合查询
);
-- 自动建表策略: 写入时自动根据 device_id 创建子表
-- INSERT INTO d_uuid123 USING power_readings TAGS ('uuid123', 'loc01', 'dept01') VALUES (NOW, 100.5, 220, 0.5, 0, false);
Go 语言中使用 Interface 实现多协议适配。
// 定义标准驱动接口
type IDriver interface {
// 连通性测试
TestConnection(config string) error
// 服务发现 (返回标准化的临时设备结构)
Discover() ([]DiscoveredDevice, error)
// 启动监听 (长连接或轮询)
StartWatch(dataChan chan<- StandardDataPoint)
}
// 统一数据点结构 (归一化)
type StandardDataPoint struct {
DeviceExternalID string
Timestamp int64
Type string // "STATE", "POWER", "TEMP"
Value interface{}
}
// 工厂模式获取实例
func NewDriver(type string, configJSON string) IDriver { ... }
逻辑流程:
StandardDataPoint。metering_mode。device.rated_power -> 写入 TDengine (Power=Rated)。attribute_mapping 解析,将多个 sensor 消息合并或分别写入。利用 GORM 的 Scope 或 Gin 中间件实现 SQL 注入。
实现逻辑:
ScopeSQL 并存入 Redis。location_id IN ('uuid-a-building', 'uuid-room-101', ...)Level 4 (个人): location_id = 'uuid-my-office'
GORM Hook: ```go // 定义 GORM Scope func DataScope(user *User) func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB {
if user.IsSuperAdmin {
return db
}
// 自动注入 WHERE 条件
return db.Where(user.DataScopeSQL)
} }
// 业务代码调用 (完全无感) db.Scopes(DataScope(currentUser)).Find(&devices)
### 3.4 实时状态推送 (WebSocket Manager)
用于前端大屏实时跳变、告警弹窗。
* **技术:** `github.com/gorilla/websocket`
* **架构:**
* **Hub:** 维护所有 Client 连接。
* **Channel:** 基于 Redis Pub/Sub 订阅后端 Go 服务的消息。
* **Filter:** 推送前判断 Client 的 `DataScope`,A栋的用户不推B栋的告警。
---
## 4. API 接口设计规范
采用 RESTful 风格,统一响应封装。
**Request Header:**
`Authorization: Bearer <JWT_TOKEN>`
**Response Struct:**
```json
{
"code": 200, // 业务状态码
"msg": "success", // 提示信息
"data": { ... } // 业务数据
}
Core API Definitions:
POST /api/v1/auth/login -> JWT TokenGET /api/v1/sources/{id}/scan -> []DiscoveredDevice (带 Filter 逻辑)POST /api/v1/devices/import -> (批量 Upsert 逻辑)GET /api/v1/analysis/energy -> (查询 TDengine 聚合数据)Query Params: group_by=location, interval=1d, start_ts=...
GET /api/v1/monitor/dashboard -> (混合数据:Redis在线数 + TDengine实时功率)
基于 Docker Compose 的单机微服务化部署。
docker-compose.yaml 配置摘要:
version: '3.8'
services:
# 1. 前端网关
nginx:
image: nginx:alpine
ports: ["80:80", "443:443"]
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./dist:/usr/share/nginx/html # Vue Build Output
depends_on:
- app-server
# 2. Go 后端服务
app-server:
build: .
restart: always
environment:
- DB_DSN=host=postgres user=ems password=pass dbname=ems port=5432 sslmode=disable
- TD_DSN=root:root@tcp(tdengine:6030)/power_db
- REDIS_ADDR=redis:6379
volumes:
- ./logs:/app/logs
# 3. 基础组件
postgres:
image: postgres:15-alpine
volumes:
- pg_data:/var/lib/postgresql/data
tdengine:
image: tdengine/tdengine:3.0
ports: ["6030:6030"]
volumes:
- taos_data:/var/lib/taos
redis:
image: redis:7-alpine
emqx:
image: emqx/emqx:5.0
ports: ["1883:1883", "8083:8083"]
volumes:
pg_data:
taos_data: