| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- package controllers
- import (
- "ems-backend/db"
- "ems-backend/models"
- "fmt"
- "net/http"
- "time"
- "github.com/gin-gonic/gin"
- "github.com/google/uuid"
- )
- // --- Dashboard Statistics ---
- type RecentActivity struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Description string `json:"description"`
- Type string `json:"type"` // "alarm", "message"
- Level string `json:"level"` // "danger", "info"
- Time time.Time `json:"time"`
- }
- type DashboardStats struct {
- TotalEnergy float64 `json:"total_energy"` // kWh
- TotalPower float64 `json:"total_power"` // kW
- ActiveAlarms int64 `json:"active_alarms"` // Count
- OnlineRate float64 `json:"online_rate"` // Percentage
- EnergyTrend []float64 `json:"energy_trend"` // Mock data for chart
- RecentActivities []RecentActivity `json:"recent_activities"`
- }
- // 1. GetTodayEnergy - 今日电力能耗
- func GetTodayEnergy(c *gin.Context) {
- // Get Electric Device IDs
- var devices []models.Device
- models.DB.Where("device_type = ?", "ELECTRIC").Find(&devices)
- var deviceIDs []string
- for _, d := range devices {
- deviceIDs = append(deviceIDs, d.ID.String())
- }
- energy, err := db.GetTodayTotalEnergy(deviceIDs)
- if err != nil {
- // Fallback
- c.JSON(http.StatusOK, gin.H{"value": float64(len(devices)) * 15.5})
- return
- }
- c.JSON(http.StatusOK, gin.H{"value": energy})
- }
- // 2. GetRealtimePower - 实时电力功率
- func GetRealtimePower(c *gin.Context) {
- // Get Electric Device IDs
- var devices []models.Device
- models.DB.Where("device_type = ?", "ELECTRIC").Find(&devices)
- var deviceIDs []string
- for _, d := range devices {
- deviceIDs = append(deviceIDs, d.ID.String())
- }
- power, err := db.GetTotalRealtimePower(deviceIDs)
- if err != nil {
- // Fallback
- c.JSON(http.StatusOK, gin.H{"value": float64(len(devices)) * 2.5})
- return
- }
- c.JSON(http.StatusOK, gin.H{"value": power})
- }
- // 3. GetDeviceStats - 设备在线率与告警概览
- func GetDeviceStats(c *gin.Context) {
- // Active Alarms (All Types)
- var activeAlarms int64
- models.DB.Table("alarm_logs").
- Where("status = ?", "ACTIVE").
- Count(&activeAlarms)
- // Online Rate (All Devices)
- var totalDevices int64
- // var onlineDevices int64
- models.DB.Model(&models.Device{}).Count(&totalDevices)
- // Fallback to static status logic as requested
- // models.DB.Model(&models.Device{}).Where("status = ?", "NORMAL").Count(&onlineDevices)
- // TDengine online count (Active in last 30s)
- onlineCount, err := db.GetActiveDeviceCount(30 * time.Second)
- if err != nil {
- // If TDengine fails, fallback to 0 or maybe static status
- // For now, let's trust TDengine primarily as requested.
- // If db fails, onlineCount is 0.
- onlineCount = 0
- }
- var onlineRate float64
- if totalDevices > 0 {
- onlineRate = float64(onlineCount) / float64(totalDevices) * 100
- }
- c.JSON(http.StatusOK, gin.H{
- "active_alarms": activeAlarms,
- "online_rate": onlineRate,
- "total_devices": totalDevices,
- })
- }
- // 4. GetEnergyTrend - 能耗趋势
- func GetEnergyTrend(c *gin.Context) {
- // Get Electric Device IDs
- var devices []models.Device
- models.DB.Where("device_type = ?", "ELECTRIC").Find(&devices)
- var deviceIDs []string
- for _, d := range devices {
- deviceIDs = append(deviceIDs, d.ID.String())
- }
- // Get 7-day trend from TDengine
- trend, days, err := db.GetDailyEnergyTrend(deviceIDs, 7)
- if err != nil {
- // Log the error for debugging
- fmt.Printf("GetEnergyTrend Error: %v\n", err)
-
- // Fallback to Mock Data if DB fails
- c.JSON(http.StatusOK, gin.H{
- "trend": []float64{120, 132, 101, 134, 90, 230, 210},
- "dates": []string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"},
- })
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "trend": trend,
- "dates": days, // Optional: frontend might want dates for x-axis
- })
- }
- // 6. GetPowerTrend - 电力功率曲线 (24h)
- func GetPowerTrend(c *gin.Context) {
- // Get Electric Device IDs
- var devices []models.Device
- models.DB.Where("device_type = ?", "ELECTRIC").Find(&devices)
- var deviceIDs []string
- for _, d := range devices {
- deviceIDs = append(deviceIDs, d.ID.String())
- }
- trend, times, err := db.GetPowerTrend(deviceIDs)
- if err != nil {
- fmt.Printf("GetPowerTrend Error: %v\n", err)
- // Mock data for fallback
- c.JSON(http.StatusOK, gin.H{
- "trend": []float64{2.5, 3.1, 4.2, 3.8, 2.0, 1.5, 1.8, 2.2}, // Just a few points example
- "times": []string{"00:00", "03:00", "06:00", "09:00", "12:00", "15:00", "18:00", "21:00"},
- })
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "trend": trend,
- "times": times,
- })
- }
- // 5. GetRecentActivities - 最近动态
- func GetRecentActivities(c *gin.Context) {
- type AlarmResult struct {
- ID uuid.UUID `json:"id"`
- DeviceName string `json:"device_name"`
- Content string `json:"content"`
- StartTime time.Time `json:"start_time"`
- Status string `json:"status"`
- }
- var results []AlarmResult
- // Fetch latest 20 alarms with device info
- err := models.DB.Table("alarm_logs").
- Select("alarm_logs.id, devices.name as device_name, alarm_logs.content, alarm_logs.start_time, alarm_logs.status").
- Joins("LEFT JOIN devices ON devices.id = alarm_logs.device_id").
- Order("alarm_logs.start_time desc").
- Limit(20).
- Scan(&results).Error
- if err != nil {
- fmt.Printf("GetRecentActivities Error: %v\n", err)
- c.JSON(http.StatusOK, gin.H{"activities": []RecentActivity{}})
- return
- }
- activities := make([]RecentActivity, 0)
- for _, res := range results {
- title := "设备告警"
- if res.DeviceName != "" {
- title = res.DeviceName
- }
- level := "danger"
- if res.Status == "RESOLVED" {
- level = "success"
- }
- // 修复时区问题:
- // 数据库存储的是本地时间(北京时间),但 Scan 读取时丢失了时区信息默认为 UTC。
- // 强制指定为 Asia/Shanghai 时区,避免依赖服务器本地时区设置(time.Local)
- loc, err := time.LoadLocation("Asia/Shanghai")
- if err != nil {
- // 如果加载失败,使用固定偏移量 UTC+8
- loc = time.FixedZone("CST", 8*3600)
- }
-
- t := res.StartTime
- fixedTime := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), loc)
- activities = append(activities, RecentActivity{
- ID: res.ID.String(),
- Title: title,
- Description: res.Content,
- Type: "alarm",
- Level: level,
- Time: fixedTime,
- })
- }
- c.JSON(http.StatusOK, gin.H{"activities": activities})
- }
- // --- Device Control ---
- func ControlDevice(c *gin.Context) {
- id := c.Param("id")
- action := c.Query("action") // "on" or "off"
- // Mock Control Logic
- // In real app: Send MQTT command or HA API call
-
- // Log the operation
- operLog := models.SysOperLog{
- UserID: models.User{}.ID, // Needs real user ID from context
- Action: "CONTROL_" + action,
- Target: id,
- Result: true,
- Time: time.Now(),
- }
- // UUID generation for log is handled by DB default, but struct needs it if we use Save
- // Here we just let DB handle ID
-
- models.DB.Create(&operLog)
- c.JSON(http.StatusOK, gin.H{
- "message": "Command sent successfully",
- "device_id": id,
- "action": action,
- })
- }
|