Jelajahi Sumber

featrue(mcs_api): 增加用户导入功能;增加登录加密处理;数据导出增加后台导出任务和查看下载功能

sunmiao 2 minggu lalu
induk
melakukan
de80bc6422

+ 1 - 0
backend/src/dashoo.cn/mcs_api/business/device/channels.go

@@ -111,6 +111,7 @@ type DeviceChannels struct {
 	Switch_2_change   string //
 	Switch_3          string // 智能开关孔3
 	Switch_3_change   string //
+	SwitchType        string // 智能开关型号,1:单控,2:双控,3:三控
 	ElectricalPower   string // 电器功率
 	ElectricalSupply  string // 市电电压
 	ElectricalCurrent string // 市电电流

+ 18 - 0
backend/src/dashoo.cn/mcs_api/business/exporttask/exporttask.go

@@ -0,0 +1,18 @@
+package exporttask
+
+import (
+	"time"
+)
+
+type ExportTask struct {
+	Id           int64  `xorm:"pk autoincr"`
+	UserId       string `xorm:"index"`
+	Type         string `xorm:"index"`     // Task type, e.g., "sensor_data"
+	Status       int    `xorm:"default 0"` // 0: Pending, 1: Processing, 2: Completed, 3: Failed
+	FileName     string
+	FilePath     string
+	CreateTime   time.Time `xorm:"created"`
+	EndTime      time.Time
+	ErrorMessage string
+	Params       string `xorm:"text"` // JSON string of parameters to reproduce the export if needed
+}

+ 95 - 0
backend/src/dashoo.cn/mcs_api/business/exporttask/exporttaskService.go

@@ -0,0 +1,95 @@
+package exporttask
+
+import (
+	"errors"
+	"os"
+	"path/filepath"
+
+	"github.com/astaxie/beego"
+
+	"dashoo.cn/base_common/utils/db"
+	"xorm.io/xorm"
+)
+
+type ExportTaskService struct {
+	db.ServiceBase
+}
+
+func GetExportTaskService(xormEngine *xorm.Engine) *ExportTaskService {
+	s := new(ExportTaskService)
+	s.DBE = xormEngine
+	// Ensure table exists
+	// s.DBE.Sync2(new(Export_Task))
+	return s
+}
+
+func (s *ExportTaskService) AddTask(task *ExportTask) error {
+	_, err := s.DBE.Insert(task)
+	return err
+}
+
+func (s *ExportTaskService) UpdateTask(task *ExportTask) error {
+	_, err := s.DBE.ID(task.Id).Update(task)
+	return err
+}
+
+func (s *ExportTaskService) GetTask(id int64) (*ExportTask, error) {
+	task := new(ExportTask)
+	has, err := s.DBE.ID(id).Get(task)
+	if err != nil {
+		return nil, err
+	}
+	if !has {
+		return nil, nil
+	}
+	return task, nil
+}
+
+func (s *ExportTaskService) GetTasksByUser(userId, taskType string, page, pageSize int) ([]ExportTask, int64, error) {
+	tasks := make([]ExportTask, 0)
+	session := s.DBE.Where("userid = ?", userId)
+	if taskType != "" {
+		session.And("type = ?", taskType)
+	}
+	err := session.Desc("createtime").Limit(pageSize, (page-1)*pageSize).Find(&tasks)
+	if err != nil {
+		return nil, 0, err
+	}
+
+	sessionCount := s.DBE.Where("userid = ?", userId)
+	if taskType != "" {
+		sessionCount.And("type = ?", taskType)
+	}
+	count, err := sessionCount.Count(new(ExportTask))
+	return tasks, count, err
+}
+
+func (s *ExportTaskService) DeleteTask(id int64, userId string) error {
+	task, err := s.GetTask(id)
+	if err != nil {
+		return err
+	}
+	if task == nil {
+		return nil
+	}
+	if task.UserId != userId {
+		return errors.New("permission denied")
+	}
+
+	// Delete file
+	if task.FilePath != "" {
+		err := os.Remove(task.FilePath)
+		if err != nil && os.IsNotExist(err) && !filepath.IsAbs(task.FilePath) {
+			fullPath := filepath.Join(beego.AppPath, task.FilePath)
+			err = os.Remove(fullPath)
+		}
+
+		if err != nil && !os.IsNotExist(err) {
+			return err
+		}
+	}
+
+	// Delete from DB
+	_, err = s.DBE.ID(id).Delete(new(ExportTask))
+	return err
+}

+ 8 - 7
backend/src/dashoo.cn/mcs_api/controllers/base.go

@@ -5,9 +5,9 @@ import (
 	"strings"
 
 	"dashoo.cn/base_common/business/userRole"
-	"dashoo.cn/mcs_api/models"
 	"dashoo.cn/base_common/utils"
 	"dashoo.cn/base_common/utils/redis"
+	"dashoo.cn/mcs_api/models"
 	"github.com/astaxie/beego"
 )
 
@@ -41,7 +41,7 @@ type Status struct {
 	Message string `json:"message"`
 }
 
-//分页信息及数据
+// 分页信息及数据
 type DataInfo struct {
 	CurrentItemCount int64       `json:"currentItemCount,omitempty"` //结果集中的条目数目
 	ItemsPerPage     int64       `json:"itemsPerPage,omitempty"`     //每页记录数目
@@ -88,7 +88,7 @@ func (this *BaseController) ParseToken() (*models.UserInfo, error) {
 	return pUserinfo, err
 }
 
-//获取管理端的帐号密码
+// 获取管理端的帐号密码
 func (this *BaseController) GetuAndp() (string, string) {
 	//labsop
 	return "seedplatform", "seed@platformDASHOO.cn"
@@ -97,12 +97,12 @@ func (this *BaseController) GetuAndp() (string, string) {
 	//	return "coldcloud", "CC@p1a2w3d4word"
 }
 
-//获取账户信息的帐号密码
+// 获取账户信息的帐号密码
 func (this *BaseController) GetAccountuAndp() (string, string) {
 	return "seedplatform", "seed@platformDASHOO.cn"
 }
 
-//获取accode
+// 获取accode
 func (this *BaseController) GetAccode() string {
 	return this.User.AccCode
 }
@@ -131,9 +131,10 @@ func (this *BaseController) GetPageInfoForm() (page PageInfo) {
 	return
 }
 
-//初始化redis
+// 初始化redis
 func InitRedis() {
 	poolnum := 5
 	addr := utils.Cfg.MustValue("redis", "addr")
-	redis.InitRedis(poolnum, addr)
+	password := utils.Cfg.MustValue("redis", "password", "")
+	redis.InitRedis(poolnum, addr, password)
 }

+ 77 - 12
backend/src/dashoo.cn/mcs_api/controllers/channels.go

@@ -75,6 +75,7 @@ type ChannelLast struct {
 	Switch_2_change   string //
 	Switch_3          string // 智能开关孔3
 	Switch_3_change   string //
+	SwitchType        string // 智能开关型号,1:单控,2:双控,3:三控
 	Power             string // 电器功率
 	Supply            string // 市电电压
 	ElectricalCurrent string // 市电电流
@@ -135,7 +136,6 @@ func (this *ChannelsController) List() {
 	if DataItem == "" {
 		DataItem = ChannelItem_HomeList
 	}
-	fmt.Println("---------list--------")
 	svc := device.GetDeviceService(utils.DBE)
 	svceq := equipment.GetEquipmentService(utils.DBE)
 	eids := svceq.GetEquipmentidsByUid(this.User.Id)
@@ -206,7 +206,7 @@ func (this *ChannelsController) BBList() {
 // @Param	serial		path 	string	true		"设备SN"
 // @Success 200 {object} coldcloud.DatapointColdCloud
 // @Failure 403 :serial 为空
-// @router /datavalue/:serial [get]
+// @router /datavalue/:serial[get]
 func (this *ChannelsController) DataValue() {
 	serial := this.Ctx.Input.Param(":serial")
 	lastdata, err := GetChannelLast(serial)
@@ -257,6 +257,7 @@ func (this *ChannelsController) DataValue() {
 		data.Switch_2_change = utils.ToStr(lastdata.Switch_2_change)
 		data.Switch_3 = utils.ToStr(lastdata.Switch_3)
 		data.Switch_3_change = utils.ToStr(lastdata.Switch_3_change)
+		data.SwitchType = utils.ToStr(lastdata.SwitchType) // 添加开关类型支持
 		data.Power = utils.ToStr(lastdata.ElectricalPower)
 		data.Supply = utils.ToStr(lastdata.ElectricalSupply)
 		data.ElectricalCurrent = utils.ToStr(lastdata.ElectricalCurrent)
@@ -264,7 +265,17 @@ func (this *ChannelsController) DataValue() {
 		data.PowerSum = utils.ToStr(lastdata.PowerSum)
 		data.Outage = utils.ToStr(lastdata.Outage)
 
-		if (time.Now().Unix() - 3600*int64(5)) < lasttimeint {
+		// 根据设备类型设置不同的离线判断时间
+		// 无法获取dataItem,所有放弃在服务端判断
+		var offlineHours int64
+		offlineHours = 5
+		// if dataItem == "38" { // 水浸传感器
+		// 	offlineHours = 20 // 设备类型38使用20小时判断
+		// } else {
+		// 	offlineHours = 5 // 其他设备类型保持5小时判断
+		// }
+
+		if (time.Now().Unix() - 3600*offlineHours) < lasttimeint {
 			data.DState = 1
 		} else {
 			data.DState = 2
@@ -928,7 +939,7 @@ func (this *ChannelsController) ChannelManageview(client labsop.LabSopClient, co
 				arrvs = append(arrvs, arr)
 			}
 		}
-	case "13", "44": //功率插座
+	case "13": //功率插座
 		queryGroupCommand = fmt.Sprintf("select max(electricalpower),min(electricalpower),MEAN(electricalpower),max(electricalsupply),min(electricalsupply),MEAN(electricalsupply) from %v where time > %vs and time < %vs", code, start, end)
 		if level == "0" {
 			queryCommand = fmt.Sprintf("select electricalpower,electricalsupply from %v where time > %vs and time < %vs", code, start, end)
@@ -949,6 +960,35 @@ func (this *ChannelsController) ChannelManageview(client labsop.LabSopClient, co
 				arrvs = append(arrvs, arr)
 			}
 		}
+	case "44": //智能插座(带电流)
+		queryGroupCommand = fmt.Sprintf("select max(electricalpower),min(electricalpower),MEAN(electricalpower),max(electricalsupply),min(electricalsupply),MEAN(electricalsupply),max(electricalcurrent),min(electricalcurrent),MEAN(electricalcurrent) from %v where time > %vs and time < %vs", code, start, end)
+		if level == "0" {
+			queryCommand = fmt.Sprintf("select electricalpower,electricalsupply,electricalcurrent from %v where time > %vs and time < %vs", code, start, end)
+		} else {
+			queryCommand = fmt.Sprintf("select MEDIAN(electricalpower) as electricalpower,MEDIAN(electricalsupply) as electricalsupply,MEDIAN(electricalcurrent) as electricalcurrent from %v where time > %vs and time < %vs group by time(%v) fill(none)", code, start, end, level)
+		}
+		for i := 0; i < len(waringhistory.Items); i++ {
+			var arr []float64
+			if waringhistory.Items[i].EventFiled == "electricalpower" {
+				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
+				arr = append(arr, float64(waringhistory.Items[i].Value))
+				arr = append(arr, -9999)
+				arr = append(arr, -9999)
+				arrvs = append(arrvs, arr)
+			} else if waringhistory.Items[i].EventFiled == "electricalsupply" {
+				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
+				arr = append(arr, -9999)
+				arr = append(arr, float64(waringhistory.Items[i].Value))
+				arr = append(arr, -9999)
+				arrvs = append(arrvs, arr)
+			} else if waringhistory.Items[i].EventFiled == "electricalcurrent" {
+				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
+				arr = append(arr, -9999)
+				arr = append(arr, -9999)
+				arr = append(arr, float64(waringhistory.Items[i].Value))
+				arrvs = append(arrvs, arr)
+			}
+		}
 	case "15": //外接设备液位
 		queryGroupCommand = fmt.Sprintf("select max(temperature),min(temperature),MEAN(temperature),max(liquidlevel),min(liquidlevel),MEAN(liquidlevel) from %v where time > %vs and time < %vs", code, start, end)
 		if level == "0" {
@@ -1281,25 +1321,50 @@ func (this *ChannelsController) ChannelManageview(client labsop.LabSopClient, co
 			}
 		}
 	case "43": //43:环境9合1
-		queryGroupCommand = fmt.Sprintf("select max(temperature),min(temperature),MEAN(temperature),max(humidity),min(humidity),MEAN(humidity),max(co2),min(co2),MEAN(co2),max(tvoc),min(tvoc),MEAN(tvoc),max(pir),min(pir),MEAN(pir),max(lightlevel),min(lightlevel),MEAN(lightlevel),max(hcho),min(hcho),MEAN(hcho),max(pm2_5),min(pm2_5),MEAN(pm2_5),max(pm10),min(pm10),MEAN(pm10) from %v where time > %vs and time < %vs", code, start, end)
+		queryGroupCommand = fmt.Sprintf("select max(temperature),min(temperature),MEAN(temperature),max(humidity),min(humidity),MEAN(humidity),max(co2),min(co2),MEAN(co2),max(tvoc),min(tvoc),MEAN(tvoc),max(pressure),min(pressure),MEAN(pressure),max(lightlevel),min(lightlevel),MEAN(lightlevel),max(hcho),min(hcho),MEAN(hcho),max(pm2_5),min(pm2_5),MEAN(pm2_5),max(pm10),min(pm10),MEAN(pm10) from %v where time > %vs and time < %vs", code, start, end)
 		if level == "0" {
-			queryCommand = fmt.Sprintf("select temperature,humidity,Co2,TVOC,PIR,Lightlevel,HCHO,PM2_5,PM10 from %v where time > %vs and time < %vs", code, start, end)
+			queryCommand = fmt.Sprintf("select temperature,humidity,co2,tvoc,pressure,lightlevel,hcho,pm2_5,pm10 from %v where time > %vs and time < %vs", code, start, end)
 		} else {
-			queryCommand = fmt.Sprintf("select MEDIAN(temperature) as temperature,MEDIAN(humidity) as humidity,MEDIAN(co2) as co2,MEDIAN(tvoc) as tvoc,MEDIAN(pir) as pir,MEDIAN(lightlevel) as lightlevel,MEDIAN(hcho) as hcho,MEDIAN(pm2_5) as pm2_5,MEDIAN(pm10) as pm10 from %v where time > %vs and time < %vs group by time(%v) fill(none)", code, start, end, level)
+			queryCommand = fmt.Sprintf("select MEDIAN(temperature) as temperature,MEDIAN(humidity) as humidity,MEDIAN(co2) as co2,MEDIAN(tvoc) as tvoc,MEDIAN(pressure) as pressure,MEDIAN(lightlevel) as lightlevel,MEDIAN(hcho) as hcho,MEDIAN(pm2_5) as pm2_5,MEDIAN(pm10) as pm10 from %v where time > %vs and time < %vs group by time(%v) fill(none)", code, start, end, level)
 		}
 		for i := 0; i < len(waringhistory.Items); i++ {
 			var arr []float64
 			if waringhistory.Items[i].EventFiled == "temperature" {
 				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
 				arr = append(arr, float64(waringhistory.Items[i].Value))
-				arr = append(arr, -9999)
-				arr = append(arr, -9999)
+				arr = append(arr, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999)
 				arrvs = append(arrvs, arr)
 			} else if waringhistory.Items[i].EventFiled == "humidity" {
 				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
-				arr = append(arr, -9999)
-				arr = append(arr, float64(waringhistory.Items[i].Value))
-				arr = append(arr, -9999)
+				arr = append(arr, -9999, float64(waringhistory.Items[i].Value), -9999, -9999, -9999, -9999, -9999, -9999, -9999)
+				arrvs = append(arrvs, arr)
+			} else if waringhistory.Items[i].EventFiled == "co2" {
+				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
+				arr = append(arr, -9999, -9999, float64(waringhistory.Items[i].Value), -9999, -9999, -9999, -9999, -9999, -9999)
+				arrvs = append(arrvs, arr)
+			} else if waringhistory.Items[i].EventFiled == "tvoc" {
+				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
+				arr = append(arr, -9999, -9999, -9999, float64(waringhistory.Items[i].Value), -9999, -9999, -9999, -9999, -9999)
+				arrvs = append(arrvs, arr)
+			} else if waringhistory.Items[i].EventFiled == "pressure" {
+				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
+				arr = append(arr, -9999, -9999, -9999, -9999, float64(waringhistory.Items[i].Value), -9999, -9999, -9999, -9999)
+				arrvs = append(arrvs, arr)
+			} else if waringhistory.Items[i].EventFiled == "lightlevel" {
+				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
+				arr = append(arr, -9999, -9999, -9999, -9999, -9999, float64(waringhistory.Items[i].Value), -9999, -9999, -9999)
+				arrvs = append(arrvs, arr)
+			} else if waringhistory.Items[i].EventFiled == "hcho" {
+				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
+				arr = append(arr, -9999, -9999, -9999, -9999, -9999, -9999, float64(waringhistory.Items[i].Value), -9999, -9999)
+				arrvs = append(arrvs, arr)
+			} else if waringhistory.Items[i].EventFiled == "pm2_5" {
+				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
+				arr = append(arr, -9999, -9999, -9999, -9999, -9999, -9999, -9999, float64(waringhistory.Items[i].Value), -9999)
+				arrvs = append(arrvs, arr)
+			} else if waringhistory.Items[i].EventFiled == "pm10" {
+				arr = append(arr, float64(waringhistory.Items[i].AlarmOn.Unix()*1000))
+				arr = append(arr, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, float64(waringhistory.Items[i].Value))
 				arrvs = append(arrvs, arr)
 			}
 		}

+ 1 - 1
backend/src/dashoo.cn/mcs_api/controllers/common.go

@@ -217,7 +217,7 @@ const (
 	// 20:二氧化氯,21:乙烯,22:氯气,23:臭氧,24:TVOC,25:乙炔,26:纯二氧化碳,27:电导率,28:二氧化硫,29:微压差(压力),30:位移,31:扫码智能锁,32:基点5传感器,33:人脸识别锁
 	// 34:视频摄像头,35:门禁一体机,36:仪器控制终端,37:刷卡智能锁,38:漏水监测,39:氢气,40:烟感,41:噪声,42:浮游菌,43:环境9合1,44:智能插座,45:智能开关面板
 	// 首页中显示的传感器类型
-	ChannelItem_HomeList        = "0,6,7,9,10,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,32,39,41,43,44"
+	ChannelItem_HomeList        = "0,6,7,9,10,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,32,39,41,43,44,38"
 	ChannelItem_Report          = "0,6,7,9,10,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,32,39,41" //sensor报表使用,不需要功率,常温,深低温,o2,co2,特殊(隐藏湿度的常温),外接设备1,外接液位设备,常温带定位,氧气设备
 	ChannelItem_Box             = "1"                                                                      //sensor,box自身数据
 	ChannelItem_TAndH           = "0,14,16"                                                                //sensor,常温,显示温湿度

+ 102 - 109
backend/src/dashoo.cn/mcs_api/controllers/dataexports.go

@@ -12,6 +12,7 @@ import (
 
 	"dashoo.cn/base_common/utils"
 	"dashoo.cn/mcs_api/business/device"
+	"dashoo.cn/mcs_api/business/exporttask"
 	"dashoo.cn/mcs_common/business/equipment"
 )
 
@@ -19,6 +20,81 @@ type DataExportsController struct {
 	BaseController
 }
 
+// @Title 获取导出任务列表
+// @Description 获取导出任务列表
+// @Success 200 {object} exporttask.ExportTask
+// @router /tasks [get]
+func (this *DataExportsController) GetTasks() {
+	svc := exporttask.GetExportTaskService(utils.DBE)
+	page, _ := this.GetInt("page", 1)
+	limit, _ := this.GetInt("limit", 10)
+	taskType := this.GetString("type", "sensor_data")
+	tasks, count, err := svc.GetTasksByUser(utils.ToStr(this.User.Id), taskType, page, limit)
+	if err != nil {
+		this.Data["json"] = &ErrorInfo{Code: 500, Message: err.Error()}
+	} else {
+		this.Data["json"] = &DataInfo{
+			Items:        tasks,
+			TotalItemsx:  count,
+			PageIndex:    int64(page),
+			ItemsPerPage: int64(limit),
+		}
+	}
+	this.ServeJSON()
+}
+
+// @Title 下载导出文件
+// @Description 下载导出文件
+// @Success 200 {file} file
+// @router /download [get]
+func (this *DataExportsController) DownloadFile() {
+	taskId, _ := this.GetInt64("taskId")
+	svc := exporttask.GetExportTaskService(utils.DBE)
+	task, err := svc.GetTask(taskId)
+	if err != nil || task == nil || task.Status != 2 {
+		this.Data["json"] = &ErrorInfo{Code: 500, Message: "文件未找到或未导出完成"}
+		this.ServeJSON()
+		return
+	}
+	this.Ctx.Output.Download(task.FilePath, task.FileName)
+}
+
+// @Title 删除导出任务
+// @Description 删除导出任务
+// @Param id query int64 true "任务ID"
+// @Success 200 {string} success
+// @router /delete [get]
+func (this *DataExportsController) DeleteTask() {
+	taskId, _ := this.GetInt64("id")
+	svc := exporttask.GetExportTaskService(utils.DBE)
+
+	// Check ownership
+	task, err := svc.GetTask(taskId)
+	if err != nil {
+		this.Data["json"] = &ErrorInfo{Code: 500, Message: err.Error()}
+		this.ServeJSON()
+		return
+	}
+	if task == nil {
+		this.Data["json"] = &ErrorInfo{Code: 404, Message: "Task not found"}
+		this.ServeJSON()
+		return
+	}
+	if task.UserId != utils.ToStr(this.User.Id) {
+		this.Data["json"] = &ErrorInfo{Code: 403, Message: "Permission denied"}
+		this.ServeJSON()
+		return
+	}
+
+	err = svc.DeleteTask(taskId, utils.ToStr(this.User.Id))
+	if err != nil {
+		this.Data["json"] = &ErrorInfo{Code: 500, Message: err.Error()}
+	} else {
+		this.Data["json"] = &ErrorInfo{Code: 0, Message: "Success"}
+	}
+	this.ServeJSON()
+}
+
 type dataso2 struct {
 	Timedata        string
 	Temperaturedata string
@@ -82,123 +158,40 @@ type headnameliquidlevel struct {
 // @Success 200 {object} business.device.DeviceChannels
 // @router /excel [get]
 func (this *DataExportsController) GetExcel() {
-
-	timerange := ""
-	svc := device.GetDeviceService(utils.DBE)
-	svceq := equipment.GetEquipmentService(utils.DBE)
-	eids := svceq.GetEquipmentidsByUid(this.User.Id)
-	Uid := utils.ToStr(this.User.Id)
-	timeinterval := this.GetString("timeinterval")
 	start64, _ := this.GetInt64("start")
 	end64, _ := this.GetInt64("end")
-	begin_time := time.Unix(start64/1000, 0).Format("2006-01-02")
-	end_time := time.Unix(end64/1000, 0).Format("2006-01-02")
 
-	if begin_time == "" {
-		timerange = utils.TimeFormat(time.Now().AddDate(0, 0, -7), "2006-01-02")
-	} else {
-		timerange = begin_time
+	params := ExportParams{
+		TimeInterval: this.GetString("timeinterval"),
+		Start:        start64,
+		End:          end64,
+		Type:         this.GetString("type"),
+		Serials:      this.GetString("serials"),
+		AccCode:      this.GetAccode(),
+		RealName:     this.User.Realname,
+		UserId:       utils.ToStr(this.User.Id),
+		Host:         this.Ctx.Request.Host,
 	}
-	if end_time == "" {
-		timerange += " 至 " + utils.TimeFormat(time.Now(), "2006-01-02")
-	} else {
-		timerange += " 至 " + end_time
-	}
-
-	t1, _ := utils.TimeParse(begin_time+" 00:00:00", "2006-1-2 15:4:5")
-	t2, _ := utils.TimeParse(end_time+" 23:59:59", "2006-1-2 15:4:5")
-	start := t1.Unix()
-	end := t2.Unix()
-	u, p := this.GetuAndp()
-	xlsx.PagePrintfooterContant = "第 &P 页"
-	xlsx.PagePrintheadContant = ""
-	f := xlsx.NewFile()
-	if this.GetString("type") == "2" {
-		where := " (a.CreateUserId=" + utils.ToStr(this.User.Id) + " or a.EquipMentId in (" + eids + ")) and a.DataItem in (" + ChannelItem_Report + ") "
-		_, channel := svc.GetChannelsList(-1, 8, where, Uid)
-
-		for i := 0; i < len(channel); i++ {
-			name := channel[i].Title
-			if name == "" {
-				name = channel[i].Code
-			}
-			xlssheetname := utils.ToStr(i+1) + "." + name
-			if DeviceItemContainint(ChannelItem_HaveO2, channel[i].DataItem) {
-				this.Saveo2Xlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_HaveCO2, channel[i].DataItem) {
-				this.Saveco2Xlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_Power, channel[i].DataItem) {
-				this.SavepowerXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_Temperature, channel[i].DataItem) {
-				this.SaveTemperatureXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_LiquidLevel, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_WindSpeed, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_Pressure, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_ClO2, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_C2H4, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_C2H2, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_H2O, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_SO2, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_Cl2, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_O3, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_TVOC, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else {
-				this.SaveXlsx(timeinterval, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			}
 
-		}
-	} else if this.GetString("serials") != "" {
-		serials := strings.Split(this.GetString("serials"), ",")
-		var codestring []string
-		for i := 0; i < len(serials); i++ {
-			codestring = append(codestring, "'c"+serials[i]+"'")
-		}
-		where := " (a.CreateUserId=" + utils.ToStr(this.User.Id) + " or a.EquipMentId in (" + eids + ")) and a.DataItem in (" + ChannelItem_Report + ") and a.Code in(" + strings.Join(codestring, ",") + ")"
-		_, channel := svc.GetChannelsList(-1, 8, where, Uid)
-		for i := 0; i < len(channel); i++ {
-			name := channel[i].Title
-			if name == "" {
-				name = channel[i].Code
-			}
-			xlssheetname := utils.ToStr(i+1) + "." + name
-			if DeviceItemContainint(ChannelItem_HaveO2, channel[i].DataItem) {
-				this.Saveo2Xlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_HaveCO2, channel[i].DataItem) {
-				this.Saveco2Xlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_Power, channel[i].DataItem) {
-				this.SavepowerXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_Temperature, channel[i].DataItem) {
-				this.SaveTemperatureXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_LiquidLevel, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else if DeviceItemContainint(ChannelItem_WindSpeed, channel[i].DataItem) {
-				this.SaveliquidlevelXlsx(xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			} else {
-				this.SaveXlsx(timeinterval, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
-			}
+	// Create task
+	svc := exporttask.GetExportTaskService(utils.DBE)
+	paramBytes, _ := json.Marshal(params)
+	task := &exporttask.ExportTask{
+		UserId:     utils.ToStr(this.User.Id),
+		Type:       "sensor_data",
+		Status:     0,
+		CreateTime: time.Now(),
+		Params:     string(paramBytes),
+	}
+	svc.AddTask(task)
 
-		}
+	// Start async process
+	go ProcessExportTask(task.Id, params)
 
+	this.Data["json"] = &ErrorInfo{
+		Code:    0,
+		Message: "导出任务已创建,请在导出任务列表中查看进度",
 	}
-	SaveDirectory("static/file/excel/report/" + this.GetAccode())
-	f.Save("static/file/excel/report/" + this.GetAccode() + "/devicedata.xlsx")
-	//this.Ctx.Output.Download("static/upload/excel/devicedata.xlsx")
-
-	var errinfo ErrorInfo
-	errinfo.Message = this.Ctx.Request.Host + "/static/file/excel/report/" + this.GetAccode() + "/devicedata.xlsx"
-	errinfo.Code = 0
-	this.Data["json"] = &errinfo
 	this.ServeJSON()
 
 }

+ 501 - 0
backend/src/dashoo.cn/mcs_api/controllers/dataexports_worker.go

@@ -0,0 +1,501 @@
+package controllers
+
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/tealeg/xlsx"
+
+	"dashoo.cn/base_common/utils"
+	"dashoo.cn/mcs_api/business/device"
+	"dashoo.cn/mcs_api/business/exporttask"
+	"dashoo.cn/mcs_common/business/equipment"
+)
+
+type ExportParams struct {
+	TimeInterval string
+	Start        int64
+	End          int64
+	Type         string
+	Serials      string
+	AccCode      string
+	RealName     string
+	UserId       string
+	Host         string
+}
+
+func ProcessExportTask(taskId int64, params ExportParams) {
+	svc := exporttask.GetExportTaskService(utils.DBE)
+	task, err := svc.GetTask(taskId)
+	if err != nil || task == nil {
+		fmt.Println("Task not found or error:", err)
+		return
+	}
+
+	// Update status to processing
+	task.Status = 1
+	svc.UpdateTask(task)
+
+	defer func() {
+		if r := recover(); r != nil {
+			task.Status = 3
+			task.ErrorMessage = fmt.Sprintf("Panic: %v", r)
+			task.EndTime = time.Now()
+			svc.UpdateTask(task)
+		}
+	}()
+
+	timerange := ""
+	svcDevice := device.GetDeviceService(utils.DBE)
+	svceq := equipment.GetEquipmentService(utils.DBE)
+	eids := svceq.GetEquipmentidsByUid(params.UserId)
+	Uid := params.UserId
+	timeinterval := params.TimeInterval
+	start64 := params.Start
+	end64 := params.End
+	begin_time := time.Unix(start64/1000, 0).Format("2006-01-02")
+	end_time := time.Unix(end64/1000, 0).Format("2006-01-02")
+
+	if begin_time == "" {
+		timerange = utils.TimeFormat(time.Now().AddDate(0, 0, -7), "2006-01-02")
+	} else {
+		timerange = begin_time
+	}
+	if end_time == "" {
+		timerange += " 至 " + utils.TimeFormat(time.Now(), "2006-01-02")
+	} else {
+		timerange += " 至 " + end_time
+	}
+
+	t1, _ := utils.TimeParse(begin_time+" 00:00:00", "2006-1-2 15:4:5")
+	t2, _ := utils.TimeParse(end_time+" 23:59:59", "2006-1-2 15:4:5")
+	start := t1.Unix()
+	end := t2.Unix()
+	u, p := "seedplatform", "seed@platformDASHOO.cn"
+	xlsx.PagePrintfooterContant = "第 &P 页"
+	xlsx.PagePrintheadContant = ""
+	f := xlsx.NewFile()
+
+	if params.Type == "2" {
+		where := " (a.CreateUserId=" + params.UserId + " or a.EquipMentId in (" + eids + ")) and a.DataItem in (" + ChannelItem_Report + ") "
+		_, channel := svcDevice.GetChannelsList(-1, 8, where, Uid)
+
+		for i := 0; i < len(channel); i++ {
+			name := channel[i].Title
+			if name == "" {
+				name = channel[i].Code
+			}
+			xlssheetname := utils.ToStr(i+1) + "." + name
+			if DeviceItemContainint(ChannelItem_HaveO2, channel[i].DataItem) {
+				Saveo2Xlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_HaveCO2, channel[i].DataItem) {
+				Saveco2Xlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_Power, channel[i].DataItem) {
+				SavepowerXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_Temperature, channel[i].DataItem) {
+				SaveTemperatureXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_LiquidLevel, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_WindSpeed, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_Pressure, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_ClO2, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_C2H4, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_C2H2, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_H2O, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_SO2, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_Cl2, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_O3, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_TVOC, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else {
+				SaveXlsx(params.RealName, timeinterval, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			}
+
+		}
+	} else if params.Serials != "" {
+		serials := strings.Split(params.Serials, ",")
+		var codestring []string
+		for i := 0; i < len(serials); i++ {
+			codestring = append(codestring, "'c"+serials[i]+"'")
+		}
+		where := " (a.CreateUserId=" + params.UserId + " or a.EquipMentId in (" + eids + ")) and a.DataItem in (" + ChannelItem_Report + ") and a.Code in(" + strings.Join(codestring, ",") + ")"
+		_, channel := svcDevice.GetChannelsList(-1, 8, where, Uid)
+		for i := 0; i < len(channel); i++ {
+			name := channel[i].Title
+			if name == "" {
+				name = channel[i].Code
+			}
+			xlssheetname := utils.ToStr(i+1) + "." + name
+			if DeviceItemContainint(ChannelItem_HaveO2, channel[i].DataItem) {
+				Saveo2Xlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_HaveCO2, channel[i].DataItem) {
+				Saveco2Xlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_Power, channel[i].DataItem) {
+				SavepowerXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_Temperature, channel[i].DataItem) {
+				SaveTemperatureXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_LiquidLevel, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else if DeviceItemContainint(ChannelItem_WindSpeed, channel[i].DataItem) {
+				SaveliquidlevelXlsx(params.RealName, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			} else {
+				SaveXlsx(params.RealName, timeinterval, xlssheetname, channel[i].Code, timerange, u, p, f, start, end)
+			}
+
+		}
+
+	}
+	dirPath := "static/file/excel/report/" + params.AccCode
+	SaveDirectory(dirPath)
+	// Use timestamp to avoid collision if task id is not unique enough (though it is)
+	fileName := fmt.Sprintf("devicedata_%d.xlsx", taskId)
+	filePath := dirPath + "/" + fileName
+	
+	err = f.Save(filePath)
+
+	if err != nil {
+		task.Status = 3
+		task.ErrorMessage = err.Error()
+	} else {
+		task.Status = 2
+		task.FilePath = filePath
+		task.FileName = fileName
+	}
+	task.EndTime = time.Now()
+	svc.UpdateTask(task)
+}
+
+func SaveTemperatureXlsx(realname, name, code, timerange, u, p string, f *xlsx.File, start, end int64) {
+	sheet, _ := f.AddSheet(name)
+	rowname := sheet.AddRow()
+	celln := rowname.AddCell()
+	celln.Value = "名称:" + realname
+
+	rowhead := sheet.AddRow()
+	_, cols, data := GetChannelInfov2(code, u, p, 1, start, end)
+	headsname := headname{
+		"时间",
+		"温度",
+		"电压",
+		"",
+		timerange,
+		"",
+	}
+	rowhead.WriteStruct(&headsname, -1)
+	indext := utils.SliceIndexOf(cols, "temperature")
+	indexv := utils.SliceIndexOf(cols, "voltage")
+	indextime := utils.SliceIndexOf(cols, "time")
+	indexstate := utils.SliceIndexOf(cols, "devicestate") //设备状态
+	for j, l := 0, len(data); j < l; j++ {
+		row := sheet.AddRow()
+		valuetime, _ := data[l-j-1][indextime].(json.Number).Float64()
+		//		valuetime, _ := data[l-j-1][indextime].(float64)
+		tstr := ""
+		if indext > -1 {
+			tstr = utils.ToStr(data[l-j-1][indext])
+			bbstate := ""
+			if indexstate > -1 && utils.ToStr(data[l-j-1][indexstate]) == "2" {
+				bbstate = " (维修)"
+			} else if indexstate > -1 && utils.ToStr(data[l-j-1][indexstate]) == "3" {
+				bbstate = " (停用)"
+			} else if indexstate > -1 && utils.ToStr(data[l-j-1][indexstate]) == "4" {
+				bbstate = " (除霜)"
+			}
+			tstr += bbstate
+		}
+		vstr := ""
+		if indexv > -1 {
+			vstr = utils.ToStr(data[l-j-1][indexv])
+		}
+		testStruct := datas{
+			time.Unix(int64(valuetime), 0).Format("2006-01-02 15:04:05"),
+			tstr,
+			vstr,
+			"",
+		}
+		row.WriteStruct(&testStruct, -1)
+	}
+	sheet.Cols[0].Width = 20 //设置时间单元格的宽度
+}
+
+func SaveXlsx(realname, timeinterval, name, code, timerange, u, p string, f *xlsx.File, start, end int64) {
+	sheet, _ := f.AddSheet(name)
+	rowname := sheet.AddRow()
+	celln := rowname.AddCell()
+	celln.Value = "名称:" + realname
+
+	rowhead := sheet.AddRow()
+	_, cols, data := GetChannelInfov2(code, u, p, 1, start, end)
+	if timeinterval != "" {
+		_, cols, data = GetChannelInfov2with30m(timeinterval, code, u, p, 1, start, end)
+	}
+
+	headsname := headname{
+		"时间",
+		"温度",
+		"湿度",
+		"电压",
+		"",
+		timerange,
+	}
+	rowhead.WriteStruct(&headsname, -1)
+	indext := utils.SliceIndexOf(cols, "temperature")
+	indexh := utils.SliceIndexOf(cols, "humidity")
+	indexv := utils.SliceIndexOf(cols, "voltage")
+	indextime := utils.SliceIndexOf(cols, "time")
+	indexstate := utils.SliceIndexOf(cols, "devicestate") //设备状态
+	for j, l := 0, len(data); j < l; j++ {
+		row := sheet.AddRow()
+		valuetime, _ := data[l-j-1][indextime].(json.Number).Float64()
+		//		valuetime, _ := data[l-j-1][indextime].(float64)
+		tstr := ""
+		if indext > -1 {
+			tstr = utils.ToStr(data[l-j-1][indext])
+			bbstate := ""
+			if indexstate > -1 && utils.ToStr(data[l-j-1][indexstate]) == "2" {
+				bbstate = " (维修)"
+			} else if indexstate > -1 && utils.ToStr(data[l-j-1][indexstate]) == "3" {
+				bbstate = " (停用)"
+			} else if indexstate > -1 && utils.ToStr(data[l-j-1][indexstate]) == "4" {
+				bbstate = " (除霜)"
+			}
+			tstr += bbstate
+			v2, _ := strconv.ParseFloat(tstr, 64)
+			v1 := Decimal(v2)
+			s1 := strconv.FormatFloat(v1, 'f', -2, 64)
+			tstr = s1
+		}
+		hstr := ""
+		if indexh > -1 {
+			hstr = utils.ToStr(data[l-j-1][indexh])
+			v2, _ := strconv.ParseFloat(hstr, 64)
+			v1 := Decimal(v2)
+			s1 := strconv.FormatFloat(v1, 'f', -2, 64)
+			hstr = s1
+		}
+		vstr := ""
+		if indexv > -1 {
+			vstr = utils.ToStr(data[l-j-1][indexv])
+			if vstr == "<nil>" {
+				vstr = "---"
+			}
+
+		}
+		testStruct := datas{
+			time.Unix(int64(valuetime), 0).Format("2006-01-02 15:04:05"),
+			tstr,
+			hstr,
+			vstr,
+		}
+		row.WriteStruct(&testStruct, -1)
+	}
+	sheet.Cols[0].Width = 20 //设置时间单元格的宽度
+}
+
+func Saveo2Xlsx(realname, name, code, timerange, u, p string, f *xlsx.File, start, end int64) {
+	sheet, _ := f.AddSheet(name)
+	rowname := sheet.AddRow()
+	celln := rowname.AddCell()
+	celln.Value = "名称:" + realname
+
+	rowhead := sheet.AddRow()
+	_, cols, data := GetChannelInfov2(code, u, p, 1, start, end)
+	headsname := headnameo2{
+		"时间",
+		"温度",
+		"湿度",
+		"电压",
+		"氧气",
+		"",
+		timerange,
+	}
+	rowhead.WriteStruct(&headsname, -1)
+	indext := utils.SliceIndexOf(cols, "temperature")
+	indexh := utils.SliceIndexOf(cols, "humidity")
+	indexv := utils.SliceIndexOf(cols, "voltage")
+	indextime := utils.SliceIndexOf(cols, "time")
+	indexo2 := utils.SliceIndexOf(cols, "o2")
+
+	for j, l := 0, len(data); j < l; j++ {
+		row := sheet.AddRow()
+		valuetime, _ := data[l-j-1][indextime].(json.Number).Float64()
+		//valuetime, _ := data[l-j-1][indextime].(float64)
+		tstr := ""
+		if indext > -1 {
+			tstr = utils.ToStr(data[l-j-1][indext])
+		}
+		hstr := ""
+		if indexh > -1 {
+			hstr = utils.ToStr(data[l-j-1][indexh])
+		}
+		vstr := ""
+		if indexv > -1 {
+			vstr = utils.ToStr(data[l-j-1][indexv])
+		}
+		o2str := ""
+		if indexo2 > -1 {
+			o2str = utils.ToStr(data[l-j-1][indexo2])
+		}
+
+		testStruct := dataso2{
+			time.Unix(int64(valuetime), 0).Format("2006-01-02 15:04:05"),
+			tstr,
+			hstr,
+			vstr,
+			o2str,
+		}
+		row.WriteStruct(&testStruct, -1)
+	}
+	sheet.Cols[0].Width = 20 //设置时间单元格的宽度
+}
+
+func Saveco2Xlsx(realname, name, code, timerange, u, p string, f *xlsx.File, start, end int64) {
+	sheet, _ := f.AddSheet(name)
+	rowname := sheet.AddRow()
+	celln := rowname.AddCell()
+	celln.Value = "名称:" + realname
+
+	rowhead := sheet.AddRow()
+	_, cols, data := GetChannelInfov2(code, u, p, 1, start, end)
+	headsname := headnameo2{
+		"时间",
+		"温度",
+		"湿度",
+		"电压",
+		"二氧化碳",
+		"",
+		timerange,
+	}
+	rowhead.WriteStruct(&headsname, -1)
+	indext := utils.SliceIndexOf(cols, "temperature")
+	indexh := utils.SliceIndexOf(cols, "humidity")
+	indexv := utils.SliceIndexOf(cols, "voltage")
+	indextime := utils.SliceIndexOf(cols, "time")
+	indexco2 := utils.SliceIndexOf(cols, "co2")
+	for j, l := 0, len(data); j < l; j++ {
+		row := sheet.AddRow()
+		valuetime, _ := data[l-j-1][indextime].(json.Number).Float64()
+		//	valuetime, _ := data[l-j-1][indextime].(float64)
+		tstr := ""
+		if indext > -1 {
+			tstr = utils.ToStr(data[l-j-1][indext])
+		}
+		hstr := ""
+		if indexh > -1 {
+			hstr = utils.ToStr(data[l-j-1][indexh])
+		}
+		vstr := ""
+		if indexv > -1 {
+			vstr = utils.ToStr(data[l-j-1][indexv])
+		}
+		co2str := ""
+		if indexco2 > -1 {
+			co2str = utils.ToStr(data[l-j-1][indexco2])
+		}
+		testStruct := datasco2{
+			time.Unix(int64(valuetime), 0).Format("2006-01-02 15:04:05"),
+			tstr,
+			hstr,
+			vstr,
+			co2str,
+		}
+		row.WriteStruct(&testStruct, -1)
+	}
+	sheet.Cols[0].Width = 20 //设置时间单元格的宽度
+}
+
+func SavepowerXlsx(realname, name, code, timerange, u, p string, f *xlsx.File, start, end int64) {
+	sheet, _ := f.AddSheet(name)
+	rowname := sheet.AddRow()
+	celln := rowname.AddCell()
+	celln.Value = "名称:" + realname
+
+	rowhead := sheet.AddRow()
+	_, cols, data := GetChannelInfov2(code, u, p, 1, start, end)
+	headsname := headnamepower{
+		"时间",
+		"功率",
+		"电量",
+		"",
+		timerange,
+	}
+	rowhead.WriteStruct(&headsname, -1)
+	indexp := utils.SliceIndexOf(cols, "electricalpower")
+	indexs := utils.SliceIndexOf(cols, "electricalsupply")
+	indextime := utils.SliceIndexOf(cols, "time")
+	for j, l := 0, len(data); j < l; j++ {
+		row := sheet.AddRow()
+		valuetime, _ := data[l-j-1][indextime].(json.Number).Float64()
+		//		valuetime, _ := data[l-j-1][indextime].(float64)
+
+		pstr := ""
+		if indexp > -1 {
+			pstr = utils.ToStr(data[l-j-1][indexp])
+		}
+		sstr := ""
+		if indexs > -1 {
+			sstr = utils.ToStr(data[l-j-1][indexs])
+		}
+		testStruct := dataspower{
+			time.Unix(int64(valuetime), 0).Format("2006-01-02 15:04:05"),
+			pstr,
+			sstr,
+		}
+		row.WriteStruct(&testStruct, -1)
+	}
+	sheet.Cols[0].Width = 20 //设置时间单元格的宽度
+}
+
+func SaveliquidlevelXlsx(realname, name, code, timerange, u, p string, f *xlsx.File, start, end int64) {
+	sheet, _ := f.AddSheet(name)
+	rowname := sheet.AddRow()
+	celln := rowname.AddCell()
+	celln.Value = "名称:" + realname
+
+	rowhead := sheet.AddRow()
+	_, cols, data := GetChannelInfov2(code, u, p, 1, start, end)
+	headsname := headnameliquidlevel{
+		"时间",
+		"温度",
+		"液位",
+		"",
+		timerange,
+	}
+	rowhead.WriteStruct(&headsname, -1)
+	indext := utils.SliceIndexOf(cols, "temperature")
+	indexl := utils.SliceIndexOf(cols, "liquidlevel")
+	indextime := utils.SliceIndexOf(cols, "time")
+	for j, l := 0, len(data); j < l; j++ {
+		row := sheet.AddRow()
+		valuetime, _ := data[l-j-1][indextime].(json.Number).Float64()
+		//		valuetime, _ := data[l-j-1][indextime].(float64)
+
+		pstr := ""
+		if indext > -1 {
+			pstr = utils.ToStr(data[l-j-1][indext])
+		}
+		sstr := ""
+		if indexl > -1 {
+			sstr = utils.ToStr(data[l-j-1][indexl])
+		}
+		testStruct := dataspower{
+			time.Unix(int64(valuetime), 0).Format("2006-01-02 15:04:05"),
+			pstr,
+			sstr,
+		}
+		row.WriteStruct(&testStruct, -1)
+	}
+	sheet.Cols[0].Width = 20 //设置时间单元格的宽度
+}

+ 63 - 8
backend/src/dashoo.cn/mcs_api/controllers/equipment.go

@@ -59,19 +59,27 @@ type SortEquipmentModel struct {
 	OrganizeName string
 }
 
-//设备最新数据
+// 设备最新数据
 type EquipMentLast struct {
 	Equipment   equipment.EquipmentSet
 	ChannelLats []ChannelLast
 }
 
-//设备批量设置
+// 设备批量设置
 type EquipMentBatch struct {
 	EquipmentIds   []string
 	EquipmentState int //0不操作,1正常,2维修,3停用,4除霜
 	TriggerState   int //0不操作,1启用,2禁用 (数据库中0禁用,1启用)
 }
 
+// 设备及其channels类型信息
+type EquipmentWithChannels struct {
+	Id           int    `json:"id"`
+	Title        string `json:"title"`
+	ChannelTypes string `json:"channelTypes"`
+	OrganizeId   int    `json:"organizeId"`
+}
+
 // @Title 报警器列表
 // @Description 设备列表
 // @Success 200 {object} business.device.DeviceChannels
@@ -82,18 +90,17 @@ func (this *EquipmentController) List() {
 
 	svc := equipment.GetEquipmentService(utils.DBE)
 	keyword := this.GetString("keyword")
-
 	orgid := this.GetString("selected")
-	total, equipmentlist := svc.GetEquipmentList(page.CurrentPage, page.Size, orgid, keyword, this.User.Id)
+	sensorType := this.GetString("sensorType")
+
+	total, equipmentlist := svc.GetEquipmentList(page.CurrentPage, page.Size, orgid, keyword, sensorType, this.User.Id)
 	//根据设备数据组合设备和传感器数据model
-	fmt.Println("----123123123123123123----", equipmentlist)
 	list := make([]EquipMentLast, 0)
 	for _, v := range equipmentlist {
 		serials := strings.Split(v.CSerial, ",")
 		titles := strings.Split(v.CTitle, ",")
 		dataitems := strings.Split(v.CDataItem, ",")
 		clist := make([]ChannelLast, len(serials))
-		fmt.Println("----------clistclistclistclist----------", clist)
 		for k, _ := range serials {
 			clist[k].Serial = serials[k]
 			clist[k].Title = titles[k]
@@ -101,11 +108,9 @@ func (this *EquipmentController) List() {
 			clist[k].TriggerCount = "0"
 		}
 		list = append(list, EquipMentLast{v, clist})
-		fmt.Println("-------listlistlistlistlist----------", list)
 	}
 	var datainfo DataInfo
 	datainfo.Items = list
-	fmt.Println("---------------datainfo.Items-----------------------", datainfo.Items)
 	datainfo.CurrentItemCount = total
 	this.Data["json"] = &datainfo
 	this.ServeJSON()
@@ -824,3 +829,53 @@ func (this *EquipmentController) AllListaaaa() {
 	this.Data["json"] = &datainfo
 	this.ServeJSON()
 }
+
+// @Title 获取设备及其channels类型列表
+// @Description 获取所有设备的列表,每个设备包含ID、名称和所有channel类型列表
+// @Success 200 {object} EquipmentWithChannels
+// @router /equipmentwithchannels [get]
+func (this *EquipmentController) GetEquipmentWithChannels() {
+	svc := equipment.GetEquipmentService(utils.DBE)
+	deviceSvc := device.GetDeviceService(utils.DBE)
+
+	eids := svc.GetEquipmentidsByUid(this.User.Id)
+	where := " (a.CreateUserId = " + this.User.Id + " or a.Id in (" + eids + "))"
+
+	// 获取所有设备
+	equipmentList := svc.GetEquipmentAll(this.User.Id, where)
+
+	// 构建返回结果
+	result := make([]EquipmentWithChannels, 0)
+
+	for _, equip := range equipmentList {
+		// 获取该设备的所有channels
+		var channels []device.Channels
+		deviceSvc.GetEntities(&channels, "EquipMentId = "+utils.ToStr(equip.Id))
+
+		// 收集所有channel的DataItem类型
+		channelTypes := make(map[int]bool)
+		for _, channel := range channels {
+			channelTypes[channel.DataItem] = true
+		}
+
+		// 将类型转换为逗号分隔的字符串
+		typeList := make([]string, 0)
+		for dataItem := range channelTypes {
+			typeList = append(typeList, utils.ToStr(dataItem))
+		}
+
+		equipmentWithChannels := EquipmentWithChannels{
+			Id:           equip.Id,
+			Title:        equip.Code,
+			ChannelTypes: strings.Join(typeList, ","),
+			OrganizeId:   equip.OrganizeId,
+		}
+
+		result = append(result, equipmentWithChannels)
+	}
+
+	var datainfo DataInfo
+	datainfo.Items = result
+	this.Data["json"] = &datainfo
+	this.ServeJSON()
+}

+ 40 - 4
backend/src/dashoo.cn/mcs_api/controllers/reports.go

@@ -86,6 +86,8 @@ func (this *ReportsController) List() {
 	fmt.Println(this.GetupdbAndHost())
 	timerq, _ := this.GetInt64("timerq")
 	cols := this.GetString("cols")
+	sensorType := this.GetString("sensorType")
+	keyword := this.GetString("keyword")
 	timestr := time.Unix(timerq/1000, 0).Format("2006-01-02")
 	t1, _ := utils.TimeParse(timestr+" 23:59:59", "2006-1-2 15:4:5")
 	t2, _ := utils.TimeParse(timestr+" 00:00:00", "2006-1-2 15:4:5")
@@ -96,9 +98,14 @@ func (this *ReportsController) List() {
 	eids := svceq.GetEquipmentidsByUid(this.User.Id)
 	Uid := this.User.Id
 	where := " (a.CreateUserId=" + utils.ToStr(this.User.Id) + " or a.EquipMentId in (" + eids + ")) and a.DataItem in (" + ChannelItem_Report + ") "
+	if sensorType != "" {
+		where += " and a.DataItem = '" + sensorType + "'"
+	}
+	if keyword != "" {
+		where += " and (a.Title like '%" + keyword + "%' or a.Code like '%" + keyword + "%')"
+	}
 
 	total, channel := svc.GetChannelsList(page.CurrentPage, page.Size, where, Uid)
-	fmt.Println("--------channel---------", channel)
 
 	if cols == "" {
 		timearea = ReportDefaultTimearea
@@ -106,8 +113,7 @@ func (this *ReportsController) List() {
 		//		showxlscols = strings.Split(strings.Replace(ReportDefaultTimearr, ",", ":00,", -1)+":00", ",")
 	} else {
 		timearea = "1h"
-		showcolsstr := cols
-		showcols = strings.Split(showcolsstr, ",")
+		showcols = strings.Split(cols, ",")
 		//		showxlscols = strings.Split(strings.Replace(showcolsstr, ",", ":00,", -1)+":00", ",")
 	}
 	//报警数据
@@ -384,20 +390,35 @@ func (this *ReportsController) GetExcel() {
 	u, p := this.GetuAndp()
 
 	client := labsop.GetLabSopClient(this.GetupdbAndHost())
+
+	// 获取查询参数
 	timerq, _ := this.GetInt64("timerq")
 	cols := this.GetString("cols")
+	sensorType := this.GetString("sensorType") // 添加传感器类型参数
+	keyword := this.GetString("keyword")       // 添加关键字参数
+
 	timestr := time.Unix(timerq/1000, 0).Format("2006-01-02")
 	t1, _ := utils.TimeParse(timestr+" 23:59:59", "2006-1-2 15:4:5")
 	t2, _ := utils.TimeParse(timestr+" 00:00:00", "2006-1-2 15:4:5")
 	end := t1.Unix()
 	begin := t2.Unix()
+
 	var reports = make([]Reportinfo, 0)
 	svceq := equipment.GetEquipmentService(utils.DBE)
 	eids := svceq.GetEquipmentidsByUid(this.User.Id)
 	Uid := this.User.Id
+
+	// 修改查询条件,添加传感器类型和关键字筛选
 	where := " (a.CreateUserId=" + utils.ToStr(this.User.Id) + " or a.EquipMentId in (" + eids + ")) and a.DataItem in (" + ChannelItem_Report + ") "
+	if sensorType != "" {
+		where += " and a.DataItem = '" + sensorType + "'"
+	}
+	if keyword != "" {
+		where += " and (a.Title like '%" + keyword + "%' or a.Code like '%" + keyword + "%')"
+	}
 
 	_, channel := svc.GetChannelsList(-1, 8, where, Uid)
+
 	if cols == "" {
 		timearea = ReportDefaultTimearea
 		showcols = strings.Split(ReportDefaultTimearr, ",")
@@ -604,19 +625,35 @@ func (this *ReportsController) GetPDF() {
 	var timearea string
 	svc := device.GetDeviceService(utils.DBE)
 	client := labsop.GetLabSopClient(this.GetupdbAndHost())
+
+	// 获取查询参数
 	timerq, _ := this.GetInt64("timerq")
 	cols := this.GetString("cols")
+	sensorType := this.GetString("sensorType") // 添加传感器类型参数
+	keyword := this.GetString("keyword")       // 添加关键字参数
+
 	timestr := time.Unix(timerq/1000, 0).Format("2006-01-02")
 	t1, _ := utils.TimeParse(timestr+" 23:59:59", "2006-1-2 15:4:5")
 	t2, _ := utils.TimeParse(timestr+" 00:00:00", "2006-1-2 15:4:5")
 	end := t1.Unix()
 	begin := t2.Unix()
+
 	var reports = make([]Reportinfo, 0)
 	svceq := equipment.GetEquipmentService(utils.DBE)
 	eids := svceq.GetEquipmentidsByUid(this.User.Id)
 	Uid := this.User.Id
+
+	// 修改查询条件,添加传感器类型和关键字筛选
 	where := " (a.CreateUserId=" + utils.ToStr(this.User.Id) + " or a.EquipMentId in (" + eids + ")) and a.DataItem in (" + ChannelItem_Report + ") "
+	if sensorType != "" {
+		where += " and a.DataItem = '" + sensorType + "'"
+	}
+	if keyword != "" {
+		where += " and (a.Title like '%" + keyword + "%' or a.Code like '%" + keyword + "%')"
+	}
+
 	_, channel := svc.GetChannelsList(-1, 8, where, Uid)
+
 	if cols == "" {
 		timearea = ReportDefaultTimearea
 		showcols = strings.Split(ReportDefaultTimearr, ",")
@@ -954,7 +991,6 @@ func show_strlen(s string) int {
 }
 
 func GetpdfContaintext(str string) (texts []string) {
-
 	strs := strings.Split(str, "")
 	fnum, start := 1, 0
 	for k, _ := range strs {

+ 53 - 3
backend/src/dashoo.cn/mcs_api/controllers/token.go

@@ -1,13 +1,16 @@
 package controllers
 
 import (
+	"crypto/aes"
+	"crypto/cipher"
+	"encoding/base64"
 	"encoding/json"
 	"fmt"
 
 	"dashoo.cn/base_common/business/auth"
 	"dashoo.cn/base_common/business/userRole"
-	"dashoo.cn/mcs_api/models"
 	"dashoo.cn/base_common/utils"
+	"dashoo.cn/mcs_api/models"
 	"github.com/astaxie/beego"
 )
 
@@ -16,6 +19,37 @@ type TokenController struct {
 	BaseController
 }
 
+// AES解密函数
+func AesDecrypt(encryptStr string) (string, error) {
+	// AES密钥,需要和前端保持一致
+	key := []byte("dashoo123456abcd") // 16字节密钥
+
+	// Base64解码
+	ciphertext, err := base64.StdEncoding.DecodeString(encryptStr)
+	if err != nil {
+		return "", err
+	}
+
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return "", err
+	}
+
+	// CBC模式需要IV向量
+	iv := key // 使用密钥作为IV(需要与前端保持一致)
+	mode := cipher.NewCBCDecrypter(block, iv)
+
+	// 解密数据
+	plaintext := make([]byte, len(ciphertext))
+	mode.CryptBlocks(plaintext, ciphertext)
+
+	// 去除PKCS7填充
+	padding := int(plaintext[len(plaintext)-1])
+	plaintext = plaintext[:len(plaintext)-padding]
+
+	return string(plaintext), nil
+}
+
 // @Title CreateToken
 // @Description create token
 // @Param	body		body 	models.User4CreateToken		true		"The user info for create token"
@@ -25,8 +59,24 @@ type TokenController struct {
 func (this *TokenController) Post() {
 	var user4CreateToken models.User4CreateToken
 	json.Unmarshal(this.Ctx.Input.RequestBody, &user4CreateToken)
-	fmt.Println("-------22--------", user4CreateToken.Username)
-	// todo 验证用户密码
+
+	//对前端返回的用户名和密码进行解密
+	result, err := AesDecrypt(user4CreateToken.Username)
+	if err!=nil{
+		beego.Error("用户信息解密异常:", err)
+		this.Abort("401")
+	}
+	user4CreateToken.Username = result
+
+	result, err = AesDecrypt(user4CreateToken.Password)
+	if err!=nil{
+		beego.Error("用户信息解密异常:", err)
+		this.Abort("401")
+	}
+	user4CreateToken.Password = result
+
+	fmt.Println("-------登录用户--------", user4CreateToken.Username)
+
 	svc := auth.GetAuthServic(utils.DBE)
 	var user userRole.Base_User
 	if svc.VerifyUser3DES(user4CreateToken.Username, user4CreateToken.Password, &user) {

+ 153 - 6
backend/src/dashoo.cn/mcs_api/controllers/user.go

@@ -3,9 +3,8 @@ package controllers
 import (
 	"encoding/json"
 	"fmt"
-	"github.com/nsqio/go-nsq"
-	"github.com/tealeg/xlsx"
 	"strings"
+	"time"
 
 	"dashoo.cn/base_common/business/auth"
 	"dashoo.cn/base_common/business/permission"
@@ -19,6 +18,9 @@ import (
 	"dashoo.cn/mcs_api/models"
 	"dashoo.cn/mcs_common/business/equipment"
 	"dashoo.cn/mcs_common/business/organize"
+	"github.com/astaxie/beego"
+	"github.com/nsqio/go-nsq"
+	"github.com/tealeg/xlsx"
 )
 
 // Operations about Users
@@ -98,6 +100,7 @@ func (this *UserController) Get() {
 	user.Profile.Host = this.Ctx.Request.Host
 	user.Profile.AccCode = usermodel.AccCode
 	user.Profile.DepartmentId = usermodel.Departmentid
+	user.Profile.Changepassworddate = usermodel.Changepassworddate
 	// todo 从this.User获取用户名,再查询出具体用户
 	//	user := models.User{"user01", "张三", models.Profile{Gender: "male", Age: 20, Address: "china", Email: "123zs@gmail.com", Realname: "ppppppp"}}
 	this.Data["json"] = user
@@ -218,6 +221,7 @@ func (this *UserController) AddUser() {
 	//userentity.Userpassword = fmt.Sprintf("%s$%s", salt, utils.EncodePassword("123456", salt))
 	//更改密码算法2014-11-21
 	pwd, key, errrk := utils.TripleDesEncrypt("123456")
+	//pwd, key, errrk := utils.TripleDesEncrypt("#Hxjy123")
 	if errrk != nil {
 		errinfo.Message = "添加失败!" + utils.AlertProcess(errrk.Error())
 		errinfo.Code = -2
@@ -227,13 +231,15 @@ func (this *UserController) AddUser() {
 	}
 	userentity.Userpassword = pwd
 	userentity.Publickey = key
+	userentity.Changepassworddate = time.Now()
 	userentity.Auditstatus = 1
 	userentity.Email = userentity.Username
 	svc := userRole.GetUserService(utils.DBE)
 	err := svc.AddUser(&userentity)
 
 	if err == nil {
-		errinfo.Message = "添加用户成功,初始密码为123456,请提醒用户登录后自行修改。"
+		//errinfo.Message = "添加用户成功,初始密码为123456,请提醒用户登录后自行修改。"
+		errinfo.Message = "添加用户成功,初始密码已设置,请提醒用户登录后自行修改。"
 		errinfo.Code = 0
 		this.Data["json"] = &errinfo
 		this.ServeJSON()
@@ -481,8 +487,9 @@ func (this *UserController) ResetPassWord() {
 		var entitypaw1, entitypaw2 logsinfo.Userpassword
 		svcauth.UpdateLog(id, &entitypaw1, &entitypaw2, utils.ToStr(this.User.Id), this.User.Realname)
 		errset := svcauth.SetNewPassword3DES(&umodel, "123456")
+		//errset := svcauth.SetNewPassword3DES(&umodel, "#Hxjy123")
 		if errset == nil {
-			errinfo.Message = "密码重置成功!已重置为:123456"
+			errinfo.Message = "密码重置成功!已重置为默认密码"
 			errinfo.Code = 0
 			this.Data["json"] = &errinfo
 			this.ServeJSON()
@@ -583,6 +590,24 @@ func (this *UserController) UserChangePWD() {
 	var model ChangePwdModel
 	var jsonblob = this.Ctx.Input.RequestBody
 	json.Unmarshal(jsonblob, &model)
+
+	fmt.Println(model)
+	//对前端返回的用户名和密码进行解密
+	result, err := AesDecrypt(model.Pwd)
+	if err != nil {
+		beego.Error("用户信息解密异常:", err)
+		this.Abort("401")
+	}
+	model.Pwd = result
+
+	result, err = AesDecrypt(model.NwePwd)
+	if err != nil {
+		beego.Error("用户信息解密异常:", err)
+		this.Abort("401")
+	}
+	model.NwePwd = result
+	fmt.Println(model)
+
 	var errinfo ErrorInfo
 
 	svcauth := auth.GetAuthServic(utils.DBE)
@@ -655,12 +680,12 @@ func (this *UserController) Registerput() {
 
 			if erradduser == nil {
 				u, p := this.GetAccountuAndp()
-				//注册时赠送50条短信
+				//注册时赠送20条短信
 				var accountinfo accountinfo.AccountInfo
 				accountinfo.ProjectSourse = "coldchain"
 				accountinfo.ProjectAccount = userentity.AccCode
 				accountinfo.ProjectAccountName = userentity.Realname
-				accountinfo.SurplusCount = 50
+				accountinfo.SurplusCount = 20
 				accountinfo.ActionType = "sms"
 				strUrl := utils.Cfg.MustValue("server", "apiurl") + "/accountinfos/?u=" + u + "&p=" + p
 				Apipost(strUrl, "POST", accountinfo)
@@ -831,3 +856,125 @@ func (this *UserController) SaveXlsx(list []userRole.Base_User, f *xlsx.File) {
 	sheet.Cols[5].Width = 25
 	sheet.Cols[6].Width = 25
 }
+
+// @Title 批量添加用户
+// @Description 批量导入用户数据
+// @Param	body	body	[]controllers.UserModel	"用户信息数组"
+// @Success	200	{object} controllers.Request
+// @router /batchadduser [post]
+func (this *UserController) BatchAddUser() {
+	var userModels []UserModel
+	var jsonblob = this.Ctx.Input.RequestBody
+	err := json.Unmarshal(jsonblob, &userModels)
+	var errinfo ErrorInfo
+
+	if err != nil {
+		errinfo.Message = "数据格式错误!" + utils.AlertProcess(err.Error())
+		errinfo.Code = -1
+		this.Data["json"] = &errinfo
+		this.ServeJSON()
+		return
+	}
+
+	if len(userModels) == 0 {
+		errinfo.Message = "请至少提供一个用户信息!"
+		errinfo.Code = -2
+		this.Data["json"] = &errinfo
+		this.ServeJSON()
+		return
+	}
+
+	// 记录导入结果
+	var successCount int
+	var failCount int
+	var failDetails []map[string]string
+
+	// 获取组织服务和用户服务
+	svcorg := organize.GetOrganizeService(utils.DBE)
+	svc := userRole.GetUserService(utils.DBE)
+	currentuser := this.User
+
+	// 遍历导入用户
+	for _, model := range userModels {
+		// 验证部门ID
+		departidint, _ := utils.StrTo(model.DepartmentId).Int()
+		if departidint < 1 {
+			failCount++
+			failDetails = append(failDetails, map[string]string{
+				"username": model.Username,
+				"realname": model.Realname,
+				"reason":   "请选择所属组织",
+			})
+			continue
+		}
+
+		// 获取组织名称
+		model.DepartmentName = svcorg.GetNameById(model.DepartmentId)
+
+		// 构建用户实体
+		var userentity userRole.Base_User
+		userentity.Roleid, _ = utils.StrTo(model.Role).Int()
+		if userentity.Roleid == 0 {
+			userentity.Roleid = 10000123 //普通用户
+		}
+		userentity.Username = model.Username
+		userentity.Realname = model.Realname
+		userentity.Telephone = model.Telephone
+		userentity.Mobile = model.Mobile
+		userentity.Description = model.Description
+		userentity.Photo = model.Photo
+		userentity.Code = model.Code
+
+		// 设置创建者信息
+		userentity.Createuserid, _ = utils.StrTo(currentuser.Id).Int()
+		userentity.Createby = currentuser.Realname
+		userentity.AccCode = this.GetAccode()
+
+		userentity.QRCode = utils.GetGuid()
+		userentity.Departmentid = model.DepartmentId
+		userentity.Departmentname = model.DepartmentName
+
+		// 加密密码
+		pwd, key, errrk := utils.TripleDesEncrypt("123456")
+		if errrk != nil {
+			failCount++
+			failDetails = append(failDetails, map[string]string{
+				"username": model.Username,
+				"realname": model.Realname,
+				"reason":   "密码加密失败:" + utils.AlertProcess(errrk.Error()),
+			})
+			continue
+		}
+
+		userentity.Userpassword = pwd
+		userentity.Publickey = key
+		userentity.Changepassworddate = time.Now()
+		userentity.Auditstatus = 1
+		userentity.Email = userentity.Username
+
+		// 添加用户
+		errAdd := svc.AddUser(&userentity)
+		if errAdd == nil {
+			successCount++
+		} else {
+			failCount++
+			failDetails = append(failDetails, map[string]string{
+				"username": model.Username,
+				"realname": model.Realname,
+				"reason":   "添加失败:" + utils.AlertProcess(errAdd.Error()),
+			})
+		}
+	}
+
+	// 返回导入结果
+	result := map[string]interface{}{
+		"successCount": successCount,
+		"failCount":    failCount,
+		"failDetails":  failDetails,
+		"message":      fmt.Sprintf("批量导入完成,成功 %d 个,失败 %d 个", successCount, failCount),
+		"code":         0,
+	}
+
+	this.Data["json"] = &result
+	this.ServeJSON()
+}