Selaa lähdekoodia

docker for ubuntu

liuq 2 kuukautta sitten
vanhempi
commit
59d7895c9c

+ 3 - 3
AI_DEVELOPMENT_RULES.md

@@ -2,13 +2,13 @@
 
 **请在每次开启新对话或进行功能开发时,首先读取并遵循本文件中的规则。**
 
-## 1. 核心原则:数据库变更必须代码化 (Database Schema Migration)
+## 1. 核心原则:数据库变更必须代码化 (Database Schema & Data Migration)
 
-本项目严格采用 **版本化迁移 (Schema Migration)** 管理数据库结构,**严禁**依赖 ORM 的自动同步 (AutoMigrate) 或手动修改数据库。
+本项目严格采用 **版本化迁移 (Schema & Data Migration)** 管理数据库结构及关键初始化数据,**严禁**依赖 ORM 的自动同步 (AutoMigrate) 或手动修改数据库。
 
 ### ⚠️ 数据库变更流程 (必须严格执行)
 
-当你的开发任务涉及数据库修改(如:新增表、新增字段、修改字段类型、添加索引)时,必须按以下步骤操作:
+当你的开发任务涉及数据库修改(如:新增表、字段、索引)或**关键初始化数据变更**(如:预置角色、管理员账号)时,必须按以下步骤操作:
 
 1.  **检查当前版本**:查看 `backend/db/migrations/` 目录下现有的最大序号文件 (e.g., `000001_init.up.sql`)。
 2.  **创建迁移文件**:

+ 48 - 37
README.md

@@ -1,72 +1,83 @@
 # 企业能耗管理平台 (EMS)
 
-本项目基于 Golang 后端 + Vue 3 前端 + Docker 容器化部署架构构建。
+本项目基于 Golang 后端 + Vue 3 前端 + Docker 容器化部署架构构建,支持企业级电力监控与数据分析
 
 ## 项目结构
 
 ```
 .
 ├── backend/            # Golang 后端 (Gin + GORM)
-│   ├── main.go         # 入口文件
-│   ├── Dockerfile      # 后端容器构建定义
-│   └── go.mod          # Go 模块定义
 ├── frontend/           # Vue 3 前端 (Vite + TypeScript)
-│   ├── src/            # 源代码
-│   └── vite.config.ts  # Vite 配置
-├── nginx/              # Nginx 网关配置
-├── docker-compose.yaml # 容器编排文件
+├── configs/            # 配置文件 (Nginx 等)
+├── data/               # 数据持久化目录 (自动生成)
+├── docker-compose.yml  # 生产环境容器编排
+├── docker-compose.wsl.yml # WSL 开发环境容器编排
 └── README.md           # 说明文档
 ```
 
 ## 快速开始
 
-### 1. 环境要求
+### 1. 环境准备
+确保已安装 Docker & Docker Compose。
 
-*   Docker & Docker Compose
-*   Golang 1.21+
-*   Node.js 18+ & npm/pnpm
+### 2. 开发环境启动 (WSL / 本地调试)
 
-### 2. 访问应用 (Web 体验)
+适用于开发人员,端口全开放,方便数据库工具直连调试。
 
-当您执行 `docker-compose up -d` 启动全部服务后,可以通过以下地址访问:
+```bash
+# 启动开发环境服务栈
+docker-compose -f docker-compose.wsl.yml up -d --build
+```
 
-*   **Web 管理界面:** [http://localhost](http://localhost) (默认 80 端口,通过 Nginx 转发)
-*   **后端 API 接口:** [http://localhost/api/v1/info](http://localhost/api/v1/info)
-*   **TDengine 数据库:** 端口 6030
-*   **PostgreSQL 数据库:** 端口 5433 (宿主机映射端口,容器内为 5432)
+**访问地址:**
+*   **Web 界面:** [http://localhost](http://localhost) (Nginx)
+*   **后端 API:** [http://localhost:8080](http://localhost:8080) (直连后端,方便调试)
+*   **PostgreSQL:** `localhost:5433` (用户: `ems_user` / 密码: `ems_pass`)
+*   **TDengine:** `localhost:6030`
+*   **Redis:** `localhost:6379`
+*   **EMQX (MQTT):** `localhost:1883` (TCP), Dashboard: `localhost:18083` (admin/public)
 
-### 3. 本地开发模式
+### 3. 生产环境部署 (Production)
 
-如果您想在本地单独运行某一部分进行开发:
+适用于正式服务器部署,安全性更高,数据库端口仅限内网访问。
 
-#### 后端开发
+```bash
+# 启动生产环境服务栈
+docker-compose up -d --build
+```
+
+**访问地址:**
+*   **Web 界面:** [http://YOUR_SERVER_IP](http://YOUR_SERVER_IP)
+*   **API 接口:** 统一通过 Nginx 转发,例如 `/api/v1/...`
+*   **数据库:** 默认仅监听 `127.0.0.1`,不对外网暴露端口。
+
+### 4. 本地源码运行 (非 Docker)
+
+如果您想在本地单独运行前后端代码进行调试:
+
+#### 后端 (Golang)
 ```bash
 cd backend
 go mod tidy
+# 确保本地已启动必要的数据库 (Postgres, TDengine, Redis),或修改配置指向 Docker 服务
 go run main.go
-# 服务将启动在 http://localhost:8080
 ```
 
-#### 前端开发
+#### 前端 (Vue 3)
 ```bash
 cd frontend
 npm install
 npm run dev
-# 服务将启动在 http://localhost:3000
-```
-
-### 4. 完整部署 (Docker)
-
-一键启动所有服务栈(数据库、后端、Nginx、中间件):
-
-```bash
-docker-compose up -d --build
+# 开发服务器启动在 http://localhost:5173
 ```
 
 ## 技术栈详情
 
-*   **后端:** Golang, Gin, GORM
-*   **前端:** Vue 3, TypeScript, Element Plus, Pinia, Vite
-*   **关系型数据库:** PostgreSQL (存储设备、用户、元数据)
-*   **时序数据库:** TDengine (存储电力、传感器历史数据)
-*   **基础设施:** Docker Compose, Nginx, Redis, EMQX
+*   **核心服务:** Golang (Gin), Vue 3 (TypeScript, Element Plus)
+*   **数据存储:**
+    *   **PostgreSQL:** 存储用户、设备、告警规则等元数据
+    *   **TDengine:** 高性能时序数据库,存储电力、传感器历史数据
+*   **中间件:**
+    *   **Redis:** 缓存与即时状态
+    *   **EMQX:** MQTT 消息代理,处理物联网设备接入
+    *   **Nginx:** 反向代理与静态资源服务

+ 95 - 0
backend/db/migrations/000003_schema_update.up.sql

@@ -0,0 +1,95 @@
+-- 000003_schema_update.up.sql
+
+-- 1. Create missing tables
+CREATE TABLE IF NOT EXISTS sys_roles (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    name VARCHAR(50),
+    role_key VARCHAR(50) UNIQUE,
+    data_scope VARCHAR(20),
+    menu_check_strictly BOOLEAN DEFAULT TRUE,
+    status VARCHAR(1)
+);
+
+CREATE TABLE IF NOT EXISTS sys_user_roles (
+    user_id UUID NOT NULL,
+    role_id UUID NOT NULL,
+    PRIMARY KEY (user_id, role_id)
+);
+
+CREATE TABLE IF NOT EXISTS sys_configs (
+    config_id SERIAL PRIMARY KEY,
+    config_key VARCHAR(100) UNIQUE,
+    config_value TEXT,
+    config_type VARCHAR(10),
+    remark VARCHAR(500)
+);
+
+CREATE TABLE IF NOT EXISTS sys_menus (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    parent_id UUID,
+    name VARCHAR(50),
+    path VARCHAR(200),
+    component VARCHAR(255),
+    perms VARCHAR(100),
+    icon VARCHAR(100),
+    type VARCHAR(1),
+    order_num INTEGER,
+    visible VARCHAR(1) DEFAULT '0',
+    status VARCHAR(1) DEFAULT '0'
+);
+
+CREATE TABLE IF NOT EXISTS sys_role_menus (
+    role_id UUID NOT NULL,
+    menu_id UUID NOT NULL,
+    PRIMARY KEY (role_id, menu_id)
+);
+
+CREATE TABLE IF NOT EXISTS equipment_cleaning_formula_templates (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    name VARCHAR(100) NOT NULL,
+    equipment_type VARCHAR(100) NOT NULL,
+    formula JSONB,
+    description TEXT,
+    created_at BIGINT,
+    updated_at BIGINT
+);
+
+CREATE TABLE IF NOT EXISTS backup_logs (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    file_name VARCHAR(255),
+    file_path VARCHAR(500),
+    size BIGINT,
+    status VARCHAR(20),
+    message TEXT,
+    start_time TIMESTAMP,
+    end_time TIMESTAMP,
+    upload_status VARCHAR(20)
+);
+
+CREATE TABLE IF NOT EXISTS alarm_rule_bindings (
+    rule_id UUID NOT NULL,
+    target_id UUID NOT NULL,
+    target_type VARCHAR(20),
+    PRIMARY KEY (rule_id, target_id)
+);
+
+CREATE TABLE IF NOT EXISTS ai_analysis_reports (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    report_type VARCHAR(50),
+    title VARCHAR(200),
+    content TEXT,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    status VARCHAR(20),
+    alert_count INTEGER
+);
+
+-- 2. Update existing tables (Users table in 000001 missed some fields)
+ALTER TABLE users ADD COLUMN IF NOT EXISTS email VARCHAR(100) UNIQUE;
+ALTER TABLE users ADD COLUMN IF NOT EXISTS phone_number VARCHAR(20) UNIQUE;
+ALTER TABLE users ADD COLUMN IF NOT EXISTS status VARCHAR(1);
+ALTER TABLE users ADD COLUMN IF NOT EXISTS created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
+
+-- 3. Add Indexes
+CREATE INDEX IF NOT EXISTS idx_sys_menus_parent_id ON sys_menus(parent_id);
+CREATE INDEX IF NOT EXISTS idx_sys_user_roles_user_id ON sys_user_roles(user_id);
+CREATE INDEX IF NOT EXISTS idx_sys_role_menus_role_id ON sys_role_menus(role_id);

+ 33 - 0
backend/db/migrations/000004_seed_admin.up.sql

@@ -0,0 +1,33 @@
+-- 000004_seed_admin.up.sql
+
+-- Use DO block for variable dependency and idempotency
+DO $$
+DECLARE
+    v_role_id UUID;
+    v_user_id UUID;
+BEGIN
+    -- 1. Ensure Admin Role exists
+    INSERT INTO sys_roles (name, role_key, data_scope, status)
+    VALUES ('超级管理员', 'admin', '1', '0')
+    ON CONFLICT DO NOTHING;
+    
+    -- Retrieve role ID (whether inserted or existing)
+    -- Note: Since we don't have a unique constraint on role_key in the schema (schema definition in 000003), 
+    -- we rely on logic. Ideally role_key should be unique.
+    -- Let's try to select it.
+    SELECT id INTO v_role_id FROM sys_roles WHERE role_key = 'admin' LIMIT 1;
+
+    -- 2. Ensure Admin User exists
+    INSERT INTO users (username, password, name, role, status)
+    VALUES ('admin', 'admin123', '系统管理员', 'ADMIN', '0')
+    ON CONFLICT (username) DO NOTHING;
+
+    SELECT id INTO v_user_id FROM users WHERE username = 'admin' LIMIT 1;
+
+    -- 3. Link User and Role
+    IF v_role_id IS NOT NULL AND v_user_id IS NOT NULL THEN
+        INSERT INTO sys_user_roles (user_id, role_id)
+        VALUES (v_user_id, v_role_id)
+        ON CONFLICT DO NOTHING;
+    END IF;
+END $$;

+ 6 - 45
backend/models/init.go

@@ -51,7 +51,10 @@ func InitDB() {
 
 	fmt.Println("Database connected successfully")
 
-	// Auto Migrate
+	// NOTE: AutoMigrate is disabled in favor of SQL migrations (golang-migrate).
+	// Please look into backend/db/migrations/ for schema changes.
+	
+	/*
 	err = DB.AutoMigrate(
 		&IntegrationSource{},
 		&Device{},
@@ -75,51 +78,9 @@ func InitDB() {
 		log.Fatal("Failed to migrate database:", err)
 	}
 	fmt.Println("Database migration completed")
-
-	// Initialize default user and role
-	InitDefaultUser(DB)
+	*/
 
 	// Initialize basic menu data
+	// TODO: Consider moving this to SQL migration as well (e.g. 000005_seed_menus.up.sql)
 	InitSysMenuData(DB)
 }
-
-func InitDefaultUser(db *gorm.DB) {
-	// Find or create admin role
-	var adminRole SysRole
-	if err := db.Where("role_key = ?", "admin").First(&adminRole).Error; err != nil {
-		fmt.Println("Initializing Admin Role...")
-		adminRole = SysRole{
-			Name:      "超级管理员",
-			RoleKey:   "admin",
-			DataScope: "1",
-			Status:    "0",
-		}
-		if err := db.Create(&adminRole).Error; err != nil {
-			log.Printf("Error creating admin role: %v", err)
-			return
-		}
-	}
-
-	// Find or create admin user
-	var adminUser User
-	if err := db.Where("username = ?", "admin").First(&adminUser).Error; err != nil {
-		fmt.Println("Initializing Admin User...")
-		adminUser = User{
-			Username: "admin",
-			Password: "admin123", // Default password
-			Name:     "系统管理员",
-			Role:     "ADMIN",
-			Status:   "0",
-		}
-		if err := db.Create(&adminUser).Error; err != nil {
-			log.Printf("Error creating admin user: %v", err)
-			return
-		}
-
-		// Assign admin role to admin user
-		db.Create(&SysUserRole{
-			UserID: adminUser.ID,
-			RoleID: adminRole.ID,
-		})
-	}
-}

+ 40 - 28
docker-compose.wsl.yml

@@ -6,9 +6,11 @@ services:
     build:
       context: ./frontend
       dockerfile: Dockerfile
+    container_name: ems-wsl-nginx
     ports: ["80:80", "443:443"]
     volumes:
       - ./configs/nginx/conf.d:/etc/nginx/conf.d
+      - ./frontend/dist:/usr/share/nginx/html # 开发环境挂载 dist,方便查看构建结果
     depends_on:
       app-server:
         condition: service_started
@@ -18,6 +20,7 @@ services:
     build: 
       context: ./backend
       dockerfile: Dockerfile
+    container_name: ems-wsl-backend
     restart: always
     healthcheck:
       test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/ping"]
@@ -25,49 +28,58 @@ services:
       timeout: 5s
       retries: 5
     environment:
-      - DB_DSN=host=postgres user=${POSTGRES_USER} password=${DB_PASSWORD} dbname=${POSTGRES_DB} port=${POSTGRES_PORT} sslmode=disable TimeZone=Asia/Shanghai
-      - TD_DSN=root:taosdata@http(${TD_HOST}:6041)/power_db
-      - REDIS_ADDR=${REDIS_HOST}:${REDIS_PORT}
-      - POSTGRES_USER=${POSTGRES_USER}
-      - POSTGRES_PASSWORD=${DB_PASSWORD}
-      - POSTGRES_DB=${POSTGRES_DB}
-      - POSTGRES_HOST=${POSTGRES_HOST}
-      - POSTGRES_PORT=${POSTGRES_PORT}
-      - JWT_SECRET=${JWT_SECRET}
-      - JWT_EXPIRE_HOURS=${JWT_EXPIRE_HOURS}
+      # --- 硬编码配置 (无需 .env) ---
+      - GIN_MODE=debug # 开发环境使用 debug 模式
+      - PORT=8080
+      
+      # 数据库连接
+      - DB_DSN=host=postgres user=ems_user password=ems_pass dbname=ems_db port=5432 sslmode=disable TimeZone=Asia/Shanghai
+      - TD_DSN=root:taosdata@http(tdengine:6041)/power_db
+      - REDIS_ADDR=redis:6379
+      
+      # 独立变量
+      - POSTGRES_USER=ems_user
+      - POSTGRES_PASSWORD=ems_pass
+      - POSTGRES_DB=ems_db
+      - POSTGRES_HOST=postgres
+      - POSTGRES_PORT=5432
+      
+      # 安全配置
+      - JWT_SECRET=change_me_dev_secret
+      - JWT_EXPIRE_HOURS=24
       - TZ=Asia/Shanghai
     volumes:
       - ./backend/logs:/app/logs
     ports:
-      - "${PORT}:8080"
+      - "8080:8080"
     depends_on:
       - postgres
       - tdengine
       - redis
 
-  # 3. Database: PostgreSQL (Relational Data)
+  # 3. Database: PostgreSQL
   postgres:
     image: postgres:15-alpine
-    container_name: ems-postgres
+    container_name: ems-wsl-postgres
     restart: always
     environment:
-      POSTGRES_USER: ${POSTGRES_USER}
-      POSTGRES_PASSWORD: ${DB_PASSWORD}
-      POSTGRES_DB: ${POSTGRES_DB}
+      POSTGRES_USER: ems_user
+      POSTGRES_PASSWORD: ems_pass
+      POSTGRES_DB: ems_db
       TZ: Asia/Shanghai
     volumes:
-      - pg_data:/var/lib/postgresql/data
+      - pg_data_wsl:/var/lib/postgresql/data
     ports:
-      - "5433:5432"
+      - "5433:5432" # 开发环境映射到 5433,避免与本地 PG 冲突
 
-  # 4. Database: TDengine (Time-Series Data)
+  # 4. Database: TDengine
   tdengine:
     image: tdengine/tdengine:3.3.0.0
-    container_name: ems-tdengine
+    container_name: ems-wsl-tdengine
     hostname: tdengine
     privileged: true
     stop_grace_period: 2m
-    ports: ["6030:6030"]
+    ports: ["6030:6030", "6041:6041"]
     volumes:
       - ./data/taos/data:/var/lib/taos
       - ./data/taos/log:/var/log/taos
@@ -79,29 +91,29 @@ services:
   # 5. Cache: Redis
   redis:
     image: redis:7-alpine
-    container_name: ems-redis
+    container_name: ems-wsl-redis
     ports: ["6379:6379"]
     volumes:
       - ./data/redis:/data
     environment:
       - TZ=Asia/Shanghai
 
-  # 6. Message Broker: EMQX (MQTT)
+  # 6. Message Broker: EMQX
   emqx:
     image: emqx/emqx:5.3.0
+    container_name: ems-wsl-emqx
     ports: 
       - "1883:1883"  # MQTT TCP
       - "8083:8083"  # MQTT WebSocket
       - "18083:18083" # Dashboard
-    # volumes:
-    #   - ./configs/emqx:/opt/emqx/etc
     environment:
-      - EMQX_ALLOW_ANONYMOUS=true # For dev only
+      - EMQX_ALLOW_ANONYMOUS=true # 开发环境允许匿名
+      - EMQX_DASHBOARD__DEFAULT_USERNAME=admin
+      - EMQX_DASHBOARD__DEFAULT_PASSWORD=public
       - TZ=Asia/Shanghai
 
-
 volumes:
-  pg_data:
+  pg_data_wsl:
     driver: local
   taos_data:
     driver: local

+ 140 - 0
docker-compose.yml

@@ -0,0 +1,140 @@
+version: '3.8'
+
+services:
+  # 1. Frontend Gateway & Static File Server
+  nginx:
+    build:
+      context: ./frontend
+      dockerfile: Dockerfile
+    container_name: ems-nginx
+    restart: always
+    ports:
+      - "80:80"
+      - "443:443"
+    volumes:
+      - ./configs/nginx/conf.d:/etc/nginx/conf.d
+      - ./logs/nginx:/var/log/nginx
+    depends_on:
+      app-server:
+        condition: service_started
+    networks:
+      - ems-net
+
+  # 2. Backend Service (Golang)
+  app-server:
+    build: 
+      context: ./backend
+      dockerfile: Dockerfile
+    container_name: ems-backend
+    restart: always
+    environment:
+      # --- 应用配置 ---
+      - GIN_MODE=release
+      - TZ=Asia/Shanghai
+      - PORT=8080
+      
+      # --- 数据库连接串 ---
+      # PostgreSQL 连接 (对应下方 postgres 服务配置)
+      - DB_DSN=host=postgres user=ems_user password=ems_pass dbname=ems_db port=5432 sslmode=disable TimeZone=Asia/Shanghai
+      # TDengine 连接 (REST API 端口 6041)
+      - TD_DSN=root:taosdata@http(tdengine:6041)/power_db
+      # Redis 连接
+      - REDIS_ADDR=redis:6379
+      
+      # --- 独立变量 (供程序读取) ---
+      - POSTGRES_USER=ems_user
+      - POSTGRES_PASSWORD=ems_pass
+      - POSTGRES_DB=ems_db
+      - POSTGRES_HOST=postgres
+      - POSTGRES_PORT=5432
+      
+      # --- JWT 认证配置 ---
+      - JWT_SECRET=your_production_secret_key_change_this
+      - JWT_EXPIRE_HOURS=24
+      
+    volumes:
+      - ./logs/backend:/app/logs
+    depends_on:
+      - postgres
+      - tdengine
+      - redis
+    networks:
+      - ems-net
+
+  # 3. Database: PostgreSQL
+  postgres:
+    image: postgres:15-alpine
+    container_name: ems-postgres
+    restart: always
+    environment:
+      POSTGRES_USER: ems_user
+      POSTGRES_PASSWORD: ems_pass
+      POSTGRES_DB: ems_db
+      TZ: Asia/Shanghai
+    volumes:
+      - pg_data:/var/lib/postgresql/data
+    # 仅允许本机连接 (安全)
+    ports:
+      - "127.0.0.1:5432:5432"
+    networks:
+      - ems-net
+
+  # 4. Database: TDengine
+  tdengine:
+    image: tdengine/tdengine:3.3.0.0
+    container_name: ems-tdengine
+    hostname: tdengine
+    restart: always
+    privileged: true
+    environment:
+      - TAOS_FIRST_EP=tdengine:6030
+      - TAOS_FQDN=tdengine
+      - TZ=Asia/Shanghai
+    volumes:
+      - ./data/taos/data:/var/lib/taos
+      - ./data/taos/log:/var/log/taos
+    # 仅允许本机连接
+    ports:
+      - "127.0.0.1:6030:6030"
+      - "127.0.0.1:6041:6041"
+    networks:
+      - ems-net
+
+  # 5. Cache: Redis
+  redis:
+    image: redis:7-alpine
+    container_name: ems-redis
+    restart: always
+    volumes:
+      - ./data/redis:/data
+    environment:
+      - TZ=Asia/Shanghai
+    networks:
+      - ems-net
+
+  # 6. Message Broker: EMQX
+  emqx:
+    image: emqx/emqx:5.3.0
+    container_name: ems-emqx
+    restart: always
+    ports: 
+      - "1883:1883"   # MQTT TCP
+      - "8083:8083"   # MQTT WebSocket
+      - "18083:18083" # Dashboard
+    volumes:
+      - ./data/emqx:/opt/emqx/data
+    environment:
+      - EMQX_ALLOW_ANONYMOUS=false # 禁止匿名连接
+      - EMQX_DASHBOARD__DEFAULT_USERNAME=admin
+      - EMQX_DASHBOARD__DEFAULT_PASSWORD=public
+      - TZ=Asia/Shanghai
+    networks:
+      - ems-net
+
+networks:
+  ems-net:
+    driver: bridge
+
+volumes:
+  pg_data:
+    driver: local