浏览代码

2023-07-20 数据生成图片

zoie 1 年之前
父节点
当前提交
97afde7b3a
共有 8 个文件被更改,包括 802 次插入76 次删除
  1. 1 1
      ColdVerify_local.go
  2. 2 30
      Nats/Nats.go
  3. 1 0
      conf/app.conf
  4. 474 17
      controllers/TaskData.go
  5. 20 9
      go.mod
  6. 62 12
      go.sum
  7. 234 2
      models/Task/TaskData.go
  8. 8 5
      routers/TaskData.go

+ 1 - 1
ColdVerify_local.go

@@ -60,7 +60,7 @@ func main() {
 	beego.BConfig.ServerName = conf.AppName + conf.HTTPPort //server  名称
 	beego.BConfig.RunMode = "dev"                           //  应用的运行模式
 	beego.BConfig.Listen.HTTPPort = HTTPPort_int            //监听端口  本地:8518  线上:8528
-
+	beego.BConfig.WebConfig.AutoRender = false
 	beego.Run()
 
 }

+ 2 - 30
Nats/Nats.go

@@ -8,7 +8,6 @@ import (
 	"ColdVerify_local/models/System"
 	"ColdVerify_local/models/Task"
 	"fmt"
-	orm2 "github.com/beego/beego/v2/client/orm"
 	"github.com/nats-io/nats.go"
 	"github.com/vmihailenco/msgpack/v5"
 	"os"
@@ -28,7 +27,7 @@ func init() {
 	}
 	logs.Println("nats OK!")
 
-	go NatsInit()
+	//go NatsInit()
 }
 
 type Up_TaskData_Back struct {
@@ -346,7 +345,7 @@ func NatsInit() {
 		//删除导出的sql文件
 		_ = os.Remove(sql_file)
 
-		DeleteDeduplicate(T_task_id)
+		Task.DeleteDeduplicate(T_task_id)
 
 		Task_r.T_collection_state = 1
 		err = NatsServer.Update_Task(Task_r)
@@ -388,30 +387,3 @@ func ChangeTableName(sql_file, T_task_id string) {
 
 	fmt.Printf("已将SQL文件 %s 中的表名 %s 替换为 %s\n", sqlFile, oldTableName, newTableName)
 }
-
-// 删除导入表的重复数据
-func DeleteDeduplicate(T_task_id string) error {
-	localOrm := orm2.NewOrmUsingDB(conf.Local_AliasName)
-	tb_name := "z_task_data_" + T_task_id
-
-	// 创建临时表
-	sqlCreate := "CREATE TABLE `tmp_table` AS (SELECT MAX(`ID`) AS `max_id` FROM " + tb_name + " GROUP BY `t_sn`,`t_id`,`t_time`);"
-	_, err := localOrm.Raw(sqlCreate).Exec()
-	if err != nil {
-		logs.Error(lib.FuncName(), err)
-		return err
-	}
-	sqlDelete := "DELETE FROM " + tb_name + "  WHERE `ID` NOT IN (SELECT `max_id` FROM `tmp_table`);"
-	_, err = localOrm.Raw(sqlDelete).Exec()
-	if err != nil {
-		logs.Error(lib.FuncName(), err)
-		return err
-	}
-	sqlDrop := "DROP TABLE `tmp_table`;"
-	_, err = localOrm.Raw(sqlDrop).Exec()
-	if err != nil {
-		logs.Error(lib.FuncName(), err)
-		return err
-	}
-	return nil
-}

+ 1 - 0
conf/app.conf

@@ -19,6 +19,7 @@ Redis_dbNum = "2"
 # Mysql 本地
 Local_AliasName = "default"
 MysqlServer_UrlPort = "192.168.11.23:3306"
+# MysqlServer_UrlPort = "111.85.177.202:13306"
 MysqlServer_Database = "coldverify_local"
 MysqlServer_Username = "coldverify_local"
 MysqlServer_Password = "hmZbXNWdpXKB3K8x"

+ 474 - 17
controllers/TaskData.go

@@ -8,10 +8,17 @@ import (
 	"ColdVerify_local/logs"
 	"ColdVerify_local/models/System"
 	"ColdVerify_local/models/Task"
+	"errors"
 	"fmt"
+	"github.com/beego/beego/v2/client/orm"
 	beego "github.com/beego/beego/v2/server/web"
 	"github.com/vmihailenco/msgpack/v5"
 	"github.com/xuri/excelize/v2"
+	"gonum.org/v1/plot"
+	"gonum.org/v1/plot/plotter"
+	"gonum.org/v1/plot/vg"
+	"gonum.org/v1/plot/vg/draw"
+	"image/color"
 	"math"
 	"os"
 	"strconv"
@@ -112,6 +119,29 @@ func (c *TaskDataController) TaskDataClass_Edit() {
 		return
 	}
 
+	sn, err := Task.Read_TaskData_ByT_id(Task_r.T_task_id, T_id)
+	if err != nil && !errors.Is(err, orm.ErrNoRows) {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "修改失败!"}
+		c.ServeJSON()
+		return
+	}
+	if len(sn) > 0 && sn != T_sn {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("编号[%s]已被sn[%s]关联,请修改后重试!", T_id, sn)}
+		c.ServeJSON()
+		return
+	}
+	//id, err := Task.Read_TaskData_ByT_sn(Task_r.T_task_id, T_sn)
+	//if err != nil && !errors.Is(err, orm.ErrNoRows) {
+	//	c.Data["json"] = lib.JSONS{Code: 202, Msg: "修改失败!"}
+	//	c.ServeJSON()
+	//	return
+	//}
+	//if len(id) > 0 && id != T_id {
+	//	c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("sn[%s]已被编号[%s]关联,请修改后重试!", T_sn, id)}
+	//	c.ServeJSON()
+	//	return
+	//}
+
 	err = Task.Update_TaskData_ByT_sn(Task_r.T_task_id, T_sn, T_id)
 	if err != nil {
 		c.Data["json"] = lib.JSONS{Code: 202, Msg: "修改失败!"}
@@ -145,7 +175,7 @@ func (c *TaskDataController) TaskDataClass_Del() {
 		return
 	}
 
-	System.Add_UserLogs_T(T_uuid, "本地版-任务分", "删除分类数据"+Task_r.T_name, Task_r.T_task_id+"|"+T_sn)
+	System.Add_UserLogs_T(T_uuid, "本地版-任务分", "删除分类数据"+Task_r.T_name, Task_r.T_task_id+"|"+T_sn)
 
 	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
 	c.ServeJSON()
@@ -177,6 +207,10 @@ func (c *TaskDataController) TaskData_AddS() {
 	T_Data_list := strings.Split(T_Data, "?")
 	println(len(T_Data_list), "len(T_Data_list)")
 	var T_Data_list_x = 0
+
+	snMaps := make(map[string]string)
+	var valueStrings []string
+
 	for _, v := range T_Data_list {
 		// 132|132|23.9|72.1|2023-04-30 07:03:00
 		if len(v) < 5 {
@@ -184,7 +218,54 @@ func (c *TaskDataController) TaskData_AddS() {
 			continue
 		}
 		v_list := strings.Split(v, "|")
+		id, sn := v_list[1], v_list[0]
+		sn1, ok := snMaps[id]
+		if !ok {
+			snMaps[id] = sn
+		} else if sn1 != sn {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("编号[%s]已被sn[%s]关联,请修改后重试!", id, sn1)}
+			c.ServeJSON()
+			return
+		}
 		t, _ := lib.ReplaceSeconds(v_list[4])
+		valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%v')", v_list[0], v_list[1], v_list[2], v_list[3], t))
+		T_Data_list_x += 1
+	}
+
+	for T_id, T_sn := range snMaps {
+
+		sn, err := Task.Read_TaskData_ByT_id(Task_r.T_task_id, T_id)
+		if err != nil && !errors.Is(err, orm.ErrNoRows) {
+			continue
+		}
+		if len(sn) > 0 && sn != T_sn {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("编号[%s]已被sn[%s]关联,请修改后重试!", T_id, sn)}
+			c.ServeJSON()
+			return
+		}
+
+		id, err := Task.Read_TaskData_ByT_sn(Task_r.T_task_id, T_sn)
+		if err != nil && !errors.Is(err, orm.ErrNoRows) {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: "修改失败!"}
+			c.ServeJSON()
+			return
+		}
+		if len(id) > 0 && id != T_id {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("sn[%s]已被编号[%s]关联,请修改后重试!", T_sn, id)}
+			c.ServeJSON()
+			return
+		}
+	}
+
+	for _, v := range T_Data_list {
+		// 132|132|23.9|72.1|2023-04-30 07:03:00
+		if len(v) < 5 {
+			println(v, "len(v) < 5")
+			continue
+		}
+		v_list := strings.Split(v, "|")
+		t, _ := lib.ReplaceSeconds(v_list[4])
+
 		is := Task.Add_TaskData(Task_r.T_task_id, v_list[0], v_list[1], v_list[2], v_list[3], t)
 		if is {
 			T_Data_list_x += 1
@@ -198,13 +279,113 @@ func (c *TaskDataController) TaskData_AddS() {
 	return
 }
 
+func (c *TaskDataController) TaskData_AddS_Excel() {
+	// 获取登录用户的uuid
+	T_uuid, _ := lib.GetAdminT_Uuid(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
+
+	T_task_id := c.GetString("T_task_id")
+	Task_r, err := NatsServer.Read_Task(T_task_id)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "T_task_id 错误!"}
+		c.ServeJSON()
+		return
+	}
+
+	// 获取上传的文件
+	file, _, err := c.GetFile("file")
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "文件上传失败!"}
+		c.ServeJSON()
+		return
+	}
+
+	// 解析 Excel 文件
+	xlFile, err := excelize.OpenReader(file)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "文件解析失败!"}
+		c.ServeJSON()
+		return
+	}
+
+	rows, err := xlFile.GetRows(xlFile.GetSheetName(0))
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "获取工作表失败!"}
+		c.ServeJSON()
+		return
+	}
+	var T_Data_list_x = 0
+	snMaps := make(map[string]string)
+	var valueStrings []string
+	for k, val := range rows {
+		// 跳过第一行
+		if k == 0 {
+			continue
+		}
+		if len(val) < 5 {
+			println(val, "len(v) < 5")
+			continue
+		}
+		fmt.Println(val)
+		id, sn := val[1], val[0]
+		sn1, ok := snMaps[id]
+		if !ok {
+			snMaps[id] = sn
+		} else if sn1 != sn {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("编号[%s]已被sn[%s]关联,请修改后重试!", id, sn1)}
+			c.ServeJSON()
+			return
+		}
+		t, _ := lib.ReplaceSeconds(val[4])
+		valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%v')", val[0], val[1], val[2], val[3], t))
+		T_Data_list_x += 1
+	}
+
+	for T_id, T_sn := range snMaps {
+
+		sn, err := Task.Read_TaskData_ByT_id(Task_r.T_task_id, T_id)
+		if err != nil && !errors.Is(err, orm.ErrNoRows) {
+			continue
+		}
+		if len(sn) > 0 && sn != T_sn {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("编号[%s]已被sn[%s]关联,请修改后重试!", T_id, sn)}
+			c.ServeJSON()
+			return
+		}
+
+		id, err := Task.Read_TaskData_ByT_sn(Task_r.T_task_id, T_sn)
+		if err != nil && !errors.Is(err, orm.ErrNoRows) {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: "修改失败!"}
+			c.ServeJSON()
+			return
+		}
+		if len(id) > 0 && id != T_id {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("sn[%s]已被编号[%s]关联,请修改后重试!", T_sn, id)}
+			c.ServeJSON()
+			return
+		}
+	}
+
+	_, is := Task.Adds_TaskData(Task_r.T_task_id, valueStrings)
+	if !is {
+		c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: 0}
+		c.ServeJSON()
+		return
+	}
+
+	System.Add_UserLogs_T(T_uuid, "本地版-任务数据", "添加数据"+Task_r.T_name, Task_r.T_task_id+"结果:"+string(T_Data_list_x))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: strconv.Itoa(T_Data_list_x)}
+	c.ServeJSON()
+	return
+}
+
 // 添加-
 func (c *TaskDataController) TaskData_Add() {
 	// 获取登录用户的uuid
 	T_uuid, _ := lib.GetAdminT_Uuid(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
 
 	T_sn := c.GetString("T_sn")
-	T_id, _ := c.GetInt("T_id")
+	T_id := c.GetString("T_id")
 	T_t, _ := c.GetFloat("T_t")
 	T_rh, _ := c.GetFloat("T_rh")
 	T_time := c.GetString("T_time")
@@ -217,14 +398,36 @@ func (c *TaskDataController) TaskData_Add() {
 		return
 	}
 
-	is := Task.Add_TaskData(Task_r.T_task_id, T_sn, strconv.Itoa(T_id), fmt.Sprintf("%.2f", T_t), fmt.Sprintf("%.2f", T_rh), T_time)
+	sn, err := Task.Read_TaskData_ByT_id(Task_r.T_task_id, T_id)
+	if err != nil && !errors.Is(err, orm.ErrNoRows) {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "添加失败!"}
+		c.ServeJSON()
+		return
+	}
+	if len(sn) > 0 && sn != T_sn {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("编号[%s]已被sn[%s]关联,请修改后重试!", T_id, sn)}
+		c.ServeJSON()
+		return
+	}
+	id, err := Task.Read_TaskData_ByT_sn(Task_r.T_task_id, T_sn)
+	if err != nil && !errors.Is(err, orm.ErrNoRows) {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "添加失败!"}
+		c.ServeJSON()
+		return
+	}
+	if len(id) > 0 && id != T_id {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: fmt.Sprintf("sn[%s]已被编号[%s]关联,请修改后重试!", T_sn, id)}
+		c.ServeJSON()
+		return
+	}
+	is := Task.Add_TaskData(Task_r.T_task_id, T_sn, T_id, fmt.Sprintf("%.2f", T_t), fmt.Sprintf("%.2f", T_rh), T_time)
 	if !is {
 		c.Data["json"] = lib.JSONS{Code: 202, Msg: "添加失败!"}
 		c.ServeJSON()
 		return
 	}
 
-	System.Add_UserLogs_T(T_uuid, "本地版-任务数据", "添加数据"+Task_r.T_name, Task_r.T_task_id+"|"+T_sn+"|"+strconv.Itoa(T_id)+"|"+fmt.Sprintf("%.2f", T_t)+"|"+fmt.Sprintf("%.2f", T_rh)+T_time)
+	System.Add_UserLogs_T(T_uuid, "本地版-任务数据", "添加数据"+Task_r.T_name, Task_r.T_task_id+"|"+T_sn+"|"+T_id+"|"+fmt.Sprintf("%.2f", T_t)+"|"+fmt.Sprintf("%.2f", T_rh)+T_time)
 
 	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
 	c.ServeJSON()
@@ -316,12 +519,7 @@ func (c *TaskDataController) TaskData_Del_t_id() {
 	// 获取登录用户的uuid
 	T_uuid, _ := lib.GetAdminT_Uuid(c.Ctx.GetCookie("User_tokey"), c.GetString("User_tokey"))
 
-	Id, err := c.GetInt("Id")
-	if err != nil {
-		c.Data["json"] = lib.JSONS{Code: 201, Msg: "Id 错误!"}
-		c.ServeJSON()
-		return
-	}
+	Id := c.GetString("Id")
 
 	T_task_id := c.GetString("T_task_id")
 	Task_r, err := NatsServer.Read_Task(T_task_id)
@@ -331,13 +529,13 @@ func (c *TaskDataController) TaskData_Del_t_id() {
 		return
 	}
 
-	is := Task.Del_TaskData_t_id(Task_r.T_task_id, strconv.Itoa(Id))
+	is := Task.Del_TaskData_t_id(Task_r.T_task_id, Id)
 	if !is {
 		c.Data["json"] = lib.JSONS{Code: 202, Msg: "删除失败!"}
 		c.ServeJSON()
 		return
 	}
-	System.Add_UserLogs_T(T_uuid, "本地版-任务数据", "删除"+Task_r.T_name, Task_r.T_task_id+"|"+strconv.Itoa(Id))
+	System.Add_UserLogs_T(T_uuid, "本地版-任务数据", "删除"+Task_r.T_name, Task_r.T_task_id+"|"+Id)
 
 	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
 	c.ServeJSON()
@@ -419,10 +617,10 @@ func (c *TaskDataController) Export_Data_Excel() {
 		return
 	}
 	//删除目录
-	//err = os.Remove("ofile/" + timeStr + ".xlsx")
-	//if err != nil {
-	//	fmt.Println(err)
-	//}
+	err = os.Remove("ofile/" + timeStr + ".xlsx")
+	if err != nil {
+		fmt.Println(err)
+	}
 
 	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: "https://bzdcoldverifyoss.baozhida.cn/" + "ofile/" + timeStr + ".xlsx"}
 	c.ServeJSON()
@@ -454,6 +652,7 @@ func (c *TaskDataController) Check() {
 		Result_Time_start  string //  2022-8-05 21:01
 		Result_Time_defect int64  //  缺失时间间隔
 	}
+
 	var r_jsons []R_JSONS
 
 	List := Task.Read_TaskData_ById_ClassList(Task_r.T_task_id)
@@ -545,7 +744,6 @@ func (c *TaskDataController) Check_Asyn() {
 		Result_Time_start  string //  2022-8-05 21:01
 		Result_Time_defect int64  //  缺失时间间隔
 	}
-
 	List := Task.Read_TaskData_ById_ClassList(Task_r.T_task_id)
 
 	r_jsons := make([]R_JSONS, len(List))
@@ -1737,3 +1935,262 @@ func (c *TaskDataController) TaskDataCopy_Recover() {
 	return
 
 }
+
+func (c *TaskDataController) TaskData_JPG() {
+	StartTime := c.GetString("StartTime")
+	if len(StartTime) > 0 {
+		_, ok := lib.TimeStrToTime(StartTime)
+		if !ok {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: "时间格式错误!"}
+			c.ServeJSON()
+			return
+		}
+	}
+
+	EndTime := c.GetString("EndTime")
+	if len(EndTime) > 0 {
+		_, ok := lib.TimeStrToTime(EndTime)
+		if !ok {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: "时间格式错误!"}
+			c.ServeJSON()
+			return
+		}
+	}
+
+	TemperatureMin, _ := c.GetFloat("TemperatureMin") // 最低温度
+	TemperatureMax, _ := c.GetFloat("TemperatureMax") // 最高温度
+	if TemperatureMin == 0 {
+		TemperatureMin = 2
+	}
+	if TemperatureMax == 0 {
+		TemperatureMax = 8
+	}
+
+	T_task_id := c.GetString("T_task_id")
+	SN_List := c.GetString("SN_List")
+	Task_r, err := NatsServer.Read_Task(T_task_id)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "T_task_id 错误!"}
+		c.ServeJSON()
+		return
+	}
+	if Task_r.T_collection_state == 2 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "数据采集中,请稍后!"}
+		c.ServeJSON()
+		return
+	}
+	jpg, is := Task.Redis_TaskDataJPG_Get(T_task_id)
+	if !is {
+		// 生成图片
+		go Gen_JPG(StartTime, EndTime, T_task_id, SN_List, TemperatureMin, TemperatureMax)
+		c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: Task.TaskDataJPG{State: 1}}
+		c.ServeJSON()
+		return
+	}
+	if jpg.State == 3 {
+		Task.Redis_TaskDataJPG_Del(T_task_id)
+		c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: jpg}
+		c.ServeJSON()
+		return
+	}
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: jpg}
+	c.ServeJSON()
+	return
+}
+
+// 存档生成图片
+func Gen_JPG(StartTime, EndTime, T_task_id, snList string, TemperatureMin, TemperatureMax float64) {
+	Task.Redis_TaskDataJPG_Set(T_task_id, Task.TaskDataJPG{
+		State: 1,
+		Url:   "",
+	})
+
+	state := 2
+
+	if TemperatureMin == 0 {
+		TemperatureMin = 2
+	}
+	if TemperatureMax == 0 {
+		TemperatureMax = 8
+	}
+
+	SN_List := strings.Split(strings.Trim(snList, "|"), "|")
+	if len(snList) == 0 {
+		List := Task.Read_TaskData_ById_ClassList(T_task_id)
+		for _, v := range List {
+			SN_List = append(SN_List, fmt.Sprintf("%s,%s", v.T_sn, v.T_id))
+		}
+	}
+	id_List := []string{}
+	sn_List := []string{}
+	if len(snList) > 0 {
+		for _, v := range SN_List {
+			sn_id := strings.Split(v, ",")
+			if len(sn_id) != 2 {
+				return
+			}
+			id_List = append(id_List, fmt.Sprintf("'%s'", sn_id[1]))
+			sn_List = append(sn_List, fmt.Sprintf("'%s'", sn_id[0]))
+		}
+	}
+	ymin, ymax, minTime, maxTime := Task.Read_TaskData_T_Min_Max_Time_Min_Max(T_task_id, sn_List, id_List, StartTime, EndTime)
+	xmin, xmax := float64(minTime.Unix()), float64(maxTime.Unix())
+	// 创建一个新的绘图
+	p := plot.New()
+
+	// 设置绘图标题和标签
+	p.Title.Text = "温度折线图"
+	//p.Legend.ThumbnailWidth = 5 * vg.Inch
+	p.X.Label.Text = "时间"
+	p.Y.Label.Text = "温度"
+
+	// 添加最高,最低标准线 用红色虚线标识
+	p.Add(horizontalLine(xmin, xmax, TemperatureMin))
+
+	p.Add(horizontalLine(xmin, xmax, TemperatureMax))
+
+	var chData = make(chan int, 10)
+	var jobGroup sync.WaitGroup
+
+	// 创建温度线
+	for i := 0; i < len(SN_List); i++ {
+		chData <- 1
+		jobGroup.Add(1)
+		go func(index int) {
+			//go func(index int, wg *sync.WaitGroup, p *plot.Plot) {
+			defer func() {
+				<-chData        // 完成时chan取出1个
+				jobGroup.Done() // 完成时将等待组值减1
+			}()
+
+			sn_id := strings.Split(SN_List[index], ",")
+			if len(sn_id) != 2 {
+				return
+			}
+			sn, id := sn_id[0], sn_id[1]
+
+			list, _ := Task.Read_TaskData_ById_List_AES(T_task_id, sn, id, StartTime, EndTime, 0, 9999)
+			if len(list) == 0 {
+				return
+			}
+
+			pts := make(plotter.XYs, len(list))
+			for j, d := range list {
+				t, _ := lib.TimeStrToTime(d.T_time)
+				pts[j].X = float64(t.Unix())
+				pts[j].Y = float64(d.T_t)
+			}
+
+			line, err := plotter.NewLine(pts)
+			if err != nil {
+				return
+			}
+			line.Color = randomColor(index)
+			p.Add(line)
+		}(i)
+	}
+	jobGroup.Wait()
+
+	if ymax < 8 {
+		ymax = 8
+	}
+	if ymin > 0 {
+		ymin = 0
+	}
+	p.Y.Min, p.Y.Max = ymin, ymax
+
+	p.X.Min, p.X.Max = xmin, xmax
+	p.Y.Tick.Marker = commaTicks{}
+	//p.X.Tick.Marker = plot.TimeTicks{Format: "2006-01-02 15:04:05"}
+	p.X.Tick.Marker = timeTicks{}
+	p.X.Tick.Label.Rotation = math.Pi / 5
+	p.X.Tick.Label.YAlign = draw.YCenter
+	p.X.Tick.Label.XAlign = draw.XRight
+
+	filename := "jpg" + time.Now().Format("20060102150405")
+	// 保存文件
+	if err := p.Save(10*vg.Inch, 4*vg.Inch, "ofile/"+filename+".jpg"); err != nil {
+		state = 3
+		logs.Error(lib.FuncName(), "生成图片失败", err)
+	}
+	if !lib.Pload_qiniu("ofile/"+filename+".jpg", "ofile/"+filename+".jpg") {
+		state = 3
+		logs.Error(lib.FuncName(), "上传七牛云失败")
+	}
+	//删除目录
+	os.Remove("ofile/" + filename + ".jpg")
+
+	Task.Redis_TaskDataJPG_Set(T_task_id, Task.TaskDataJPG{
+		State: state,
+		Url:   "https://bzdcoldverifyoss.baozhida.cn/" + "ofile/" + filename + ".jpg",
+	})
+
+}
+
+func horizontalLine(xmin, xmax, y float64) *plotter.Line {
+	pts := make(plotter.XYs, 2)
+	pts[0].X = xmin
+	pts[0].Y = y
+	pts[1].X = xmax
+	pts[1].Y = y
+	line, err := plotter.NewLine(pts)
+	if err != nil {
+		panic(err)
+	}
+	line.LineStyle.Dashes = []vg.Length{vg.Points(8), vg.Points(5), vg.Points(1), vg.Points(5)}
+	line.Color = color.RGBA{R: 255, A: 255}
+	return line
+}
+
+type timeTicks struct{}
+
+func (timeTicks) Ticks(min, max float64) []plot.Tick {
+	tks := plot.TimeTicks{}.Ticks(min, max)
+	for i, t := range tks {
+		//if t.Label == "" { // Skip minor ticks, they are fine.
+		//	continue
+		//}
+		tks[i].Label = time.Unix(int64(t.Value), 0).Format("2006-01-02 15:04:05")
+	}
+
+	return tks
+}
+
+type commaTicks struct{}
+
+// Ticks computes the default tick marks, but inserts commas
+// into the labels for the major tick marks.
+func (commaTicks) Ticks(min, max float64) []plot.Tick {
+	tks := plot.DefaultTicks{}.Ticks(min, max)
+	for i, t := range tks {
+		//if t.Label == "" { // Skip minor ticks, they are fine.
+		//	continue
+		//}
+		tks[i].Label = fmt.Sprintf("%.0f", t.Value)
+	}
+
+	return tks
+}
+
+// 生成随机颜色的辅助函数
+func randomColor(i int) color.RGBA {
+	var colors []color.RGBA
+	colors = append(colors,
+		color.RGBA{R: 52, G: 152, B: 219, A: 255},
+		color.RGBA{R: 230, G: 126, B: 34, A: 255},
+		color.RGBA{R: 142, G: 68, B: 173, A: 255},
+		color.RGBA{R: 211, G: 84, B: 0, A: 255},
+		color.RGBA{R: 231, G: 76, B: 60, A: 255},
+		color.RGBA{R: 26, G: 188, B: 156, A: 255},
+		color.RGBA{R: 243, G: 156, B: 18, A: 255},
+		color.RGBA{R: 22, G: 160, B: 133, A: 255},
+		color.RGBA{R: 46, G: 204, B: 113, A: 255},
+		color.RGBA{R: 39, G: 174, B: 96, A: 255},
+		color.RGBA{R: 41, G: 128, B: 185, A: 255},
+		color.RGBA{R: 155, G: 89, B: 182, A: 255},
+		color.RGBA{R: 192, G: 57, B: 43, A: 255},
+		color.RGBA{R: 241, G: 196, B: 15, A: 255},
+	)
+
+	return colors[i%len(colors)]
+}

+ 20 - 9
go.mod

@@ -6,25 +6,36 @@ require (
 	github.com/astaxie/beego v1.12.3
 	github.com/beego/beego/v2 v2.0.7
 	github.com/go-sql-driver/mysql v1.7.0
+	github.com/nats-io/nats.go v1.27.0
 	github.com/qiniu/go-sdk/v7 v7.14.0
 	github.com/satori/go.uuid v1.2.0
 	github.com/signintech/gopdf v0.15.1
+	github.com/tealeg/xlsx v1.0.5
+	github.com/vmihailenco/msgpack/v5 v5.3.5
 	github.com/xuri/excelize/v2 v2.7.0
+	gonum.org/v1/plot v0.13.0
 )
 
 require (
+	git.sr.ht/~sbinet/gg v0.4.1 // indirect
+	github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
+	github.com/go-fonts/liberation v0.3.1 // indirect
+	github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 // indirect
+	github.com/go-pdf/fpdf v0.8.0 // indirect
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/gomodule/redigo v2.0.0+incompatible // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
+	github.com/klauspost/compress v1.16.5 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
-	github.com/nats-io/nats.go v1.24.0 // indirect
-	github.com/nats-io/nkeys v0.3.0 // indirect
+	github.com/nats-io/nats-server/v2 v2.9.20 // indirect
+	github.com/nats-io/nkeys v0.4.4 // indirect
 	github.com/nats-io/nuid v1.0.1 // indirect
-	github.com/phpdave11/gofpdi v1.0.11 // indirect
+	github.com/phpdave11/gofpdi v1.0.13 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.14.0 // indirect
 	github.com/prometheus/client_model v0.3.0 // indirect
@@ -33,15 +44,15 @@ require (
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.3 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
-	github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
 	github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
 	github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
-	golang.org/x/crypto v0.5.0 // indirect
-	golang.org/x/net v0.5.0 // indirect
-	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
-	golang.org/x/sys v0.4.0 // indirect
-	golang.org/x/text v0.6.0 // indirect
+	golang.org/x/crypto v0.9.0 // indirect
+	golang.org/x/image v0.7.0 // indirect
+	golang.org/x/net v0.10.0 // indirect
+	golang.org/x/sync v0.1.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect

+ 62 - 12
go.sum

@@ -31,9 +31,16 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo=
+git.sr.ht/~sbinet/gg v0.4.1 h1:YccqPPS57/TpqX2fFnSRlisrqQ43gEdqVm3JtabPrp0=
+git.sr.ht/~sbinet/gg v0.4.1/go.mod h1:xKrQ22W53kn8Hlq+gzYeyyohGMwR8yGgSMlVpY/mHGc=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
+github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
+github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
+github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -81,6 +88,10 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
+github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ=
+github.com/go-fonts/latin-modern v0.3.1 h1:/cT8A7uavYKvglYXvrdDw4oS5ZLkcOU22fa2HJ1/JVM=
+github.com/go-fonts/liberation v0.3.1 h1:9RPT2NhUpxQ7ukUvz3jeUckmN42T9D9TpjtQcqK/ceM=
+github.com/go-fonts/liberation v0.3.1/go.mod h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -88,10 +99,14 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs=
+github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
+github.com/go-pdf/fpdf v0.8.0 h1:IJKpdaagnWUeSkUFUjTcSzTppFxmv8ucGQyNPQWxYOQ=
+github.com/go-pdf/fpdf v0.8.0/go.mod h1:gfqhcNwXrsd3XYKte9a7vM3smvU/jB4ZRDrmWSxpfdc=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
@@ -104,6 +119,8 @@ github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ
 github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -177,6 +194,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
+github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -196,6 +215,7 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK
 github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -207,10 +227,13 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/nats-io/nats.go v1.24.0 h1:CRiD8L5GOQu/DcfkmgBcTTIQORMwizF+rPk6T0RaHVQ=
-github.com/nats-io/nats.go v1.24.0/go.mod h1:dVQF+BK3SzUZpwyzHedXsvH3EO38aVKuOPkkHlv5hXA=
-github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
-github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
+github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
+github.com/nats-io/nats-server/v2 v2.9.20 h1:bt1dW6xsL1hWWwv7Hovm+EJt5L6iplyqlgEFkoEUk0k=
+github.com/nats-io/nats-server/v2 v2.9.20/go.mod h1:aTb/xtLCGKhfTFLxP591CMWfkdgBmcUUSkiSOe5A3gw=
+github.com/nats-io/nats.go v1.27.0 h1:3o9fsPhmoKm+yK7rekH2GtWoE+D9jFbw8N3/ayI1C00=
+github.com/nats-io/nats.go v1.27.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
+github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
+github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
 github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@@ -220,8 +243,9 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
 github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
-github.com/phpdave11/gofpdi v1.0.11 h1:wsBNx+3S0wy1dEp6fzv281S74ogZGgIdYWV2PugWgho=
 github.com/phpdave11/gofpdi v1.0.11/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
+github.com/phpdave11/gofpdi v1.0.13 h1:o61duiW8M9sMlkVXWlvP92sZJtGKENvW3VExs6dZukQ=
+github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -295,6 +319,8 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
+github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
+github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
 github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
 github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
 github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
@@ -310,6 +336,7 @@ github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -323,11 +350,11 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
 golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -338,10 +365,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
 golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
+golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
+golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -361,6 +390,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -390,13 +420,16 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -412,9 +445,11 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -448,7 +483,9 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -459,11 +496,14 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
 golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -471,11 +511,14 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
 golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -516,11 +559,16 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
 golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM=
+gonum.org/v1/plot v0.13.0 h1:yb2Z/b8bY5h/xC4uix+ujJ+ixvPUvBmUOtM73CJzpsw=
+gonum.org/v1/plot v0.13.0/go.mod h1:mV4Bpu4PWTgN2CETURNF8hCMg7EtlZqJYCcmYo/t4Co=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -628,6 +676,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

+ 234 - 2
models/Task/TaskData.go

@@ -4,7 +4,9 @@ import (
 	"ColdVerify_local/conf"
 	"ColdVerify_local/lib"
 	"ColdVerify_local/logs"
+	"encoding/json"
 	"fmt"
+	"github.com/astaxie/beego/cache"
 	_ "github.com/astaxie/beego/cache/redis"
 	"github.com/beego/beego/v2/adapter/orm"
 	orm2 "github.com/beego/beego/v2/client/orm"
@@ -30,8 +32,58 @@ func (t *TaskData) TableName() string {
 	return "task_data" // 数据库名称   // ************** 替换 FormulaList **************
 }
 
+var redisCache_TaskData cache.Cache
+
 func init() {
 	orm2.Debug = true
+	config := fmt.Sprintf(`{"key":"%s","conn":"%s","dbNum":"%s","password":"%s"}`,
+		"redis_"+"TaskData", conf.Redis_address, conf.Redis_dbNum, conf.Redis_password)
+	logs.Println(config)
+	var err error
+	redisCache_TaskData, err = cache.NewCache("redis", config)
+	if err != nil || redisCache_TaskData == nil {
+		errMsg := "failed to init redis"
+		logs.Println(errMsg, err)
+	}
+}
+
+type TaskDataJPG struct {
+	State int    `json:"state"` //1:生成中 2:已完成 3:失败
+	Url   string `json:"url"`   //url
+}
+
+// ---------------- Redis -------------------
+// Redis_Set(m.T_sn,m) // Redis 更新缓存
+func Redis_TaskDataJPG_Set(key string, r TaskDataJPG) (err error) {
+	//json序列化
+	str, err := json.Marshal(r)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return
+	}
+	err = redisCache_TaskData.Put(key, str, 5*time.Minute)
+	if err != nil {
+		logs.Println("set key:", key, ",value:", str, err)
+	}
+	return
+}
+
+// if r,is :=Redis_Get(T_sn);is{
+// return r,nil
+// }
+func Redis_TaskDataJPG_Get(key string) (r TaskDataJPG, is bool) {
+	if redisCache_TaskData.IsExist(key) {
+		logs.Println("找到key:", key)
+		v := redisCache_TaskData.Get(key)
+		json.Unmarshal(v.([]byte), &r)
+		return r, true
+	}
+	logs.Println("没有 找到key:", key)
+	return TaskDataJPG{}, false
+}
+func Redis_TaskDataJPG_Del(key string) (err error) {
+	err = redisCache_TaskData.Delete(key)
+	return
 }
 
 // 创建数据库  Device.CREATE_TaskData("")
@@ -243,7 +295,7 @@ func Read_TaskData_ById_ClassList(T_task_id string) []TaskDataClass_ {
 	//fmt.Println("maps_z;",maps_z[0][0])
 	//sql := "SELECT DISTINCT t_sn,t_id FROM z_task_data_" + T_task_id + " ORDER BY t_id "
 	//sql := "SELECT DISTINCT t_sn FROM z_task_data_" + T_task_id + " ORDER BY t_id "
-	sql := "SELECT t_sn,t_id FROM z_task_data_" + T_task_id + "  GROUP BY t_sn ORDER BY t_id+0 "
+	sql := "SELECT t_sn,t_id FROM z_task_data_" + T_task_id + "  GROUP BY t_sn,t_id ORDER BY t_id+0 "
 
 	fmt.Println(sql)
 	_, err := o.Raw(sql).QueryRows(&maps)
@@ -346,6 +398,51 @@ func Add_TaskData(T_task_id string, T_sn string, T_id string, T_t string, T_rh s
 	return true
 }
 
+func Adds_TaskData(T_task_id string, valueStrings []string) (int64, bool) {
+
+	o := orm.NewOrm()
+
+	// 删除重复数据
+	err := DeleteDeduplicate(T_task_id)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return 0, false
+	}
+
+	// 创建索引
+	createIndexSql := "ALTER TABLE z_task_data_" + T_task_id + " ADD CONSTRAINT unique_index_t_sn_t_id_t_time UNIQUE (t_sn, t_id, t_time);"
+	//fmt.Println(sql)
+	_, err = o.Raw(createIndexSql).Exec()
+	if err != nil && !strings.Contains(err.Error(), "Duplicate key name 'unique_index_t_sn_t_id_t_time'") {
+		logs.Error(lib.FuncName(), err)
+		return 0, false
+	}
+
+	// 插入数据
+	sql := "INSERT INTO z_task_data_" + T_task_id + " (`t_sn`, `t_id`, `t_t`, `t_rh`, `t_time`) VALUES"
+	sql += strings.Join(valueStrings, ",")
+	sql += " ON DUPLICATE KEY UPDATE t_t = VALUES(t_t), t_rh = VALUES(t_rh)"
+	//fmt.Println(sql)
+	res, err := o.Raw(sql).Exec()
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return 0, false
+	}
+
+	// 删除索引
+	deleteIndexSql := "ALTER TABLE z_task_data_" + T_task_id + " DROP INDEX unique_index_t_sn_t_id_t_time;"
+	//fmt.Println(sql)
+	_, err = o.Raw(deleteIndexSql).Exec()
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return 0, false
+	}
+	num, _ := res.RowsAffected()
+
+	//fmt.Println("mysql row affected nums: ", num)
+	return num, true
+}
+
 // 修改
 func Up_TaskData(T_task_id string, Id string, T_t string, T_rh string, T_time string) bool {
 
@@ -400,13 +497,48 @@ func Del_TaskData(T_task_id string, Id string) bool {
 	return true
 }
 
+func Read_TaskData_ByT_id(T_task_id string, Id string) (t_sn string, err error) {
+
+	o := orm.NewOrm()
+
+	// 开始插入数据 UPDATE `cold_verify`.`Z_TaskData_d8qMyeXLzIxn` SET `t_t` = 20.2 WHERE `ID` = 69
+	sql := "SELECT t_sn FROM z_task_data_" + T_task_id + " WHERE t_id = '" + Id + "'"
+	//  这里有时间优化  用于一次 prepare 多次 exec,以提高批量执行的速度
+	logs.Println(sql)
+
+	err = o.Raw(sql).QueryRow(&t_sn)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return t_sn, err
+	}
+	//fmt.Println("mysql row affected nums: ", num)
+	return t_sn, err
+}
+func Read_TaskData_ByT_sn(T_task_id string, sn string) (t_id string, err error) {
+
+	o := orm.NewOrm()
+
+	// 开始插入数据 UPDATE `cold_verify`.`Z_TaskData_d8qMyeXLzIxn` SET `t_t` = 20.2 WHERE `ID` = 69
+	sql := "SELECT t_id FROM z_task_data_" + T_task_id + " WHERE t_sn = '" + sn + "'"
+	//  这里有时间优化  用于一次 prepare 多次 exec,以提高批量执行的速度
+	logs.Println(sql)
+
+	err = o.Raw(sql).QueryRow(&t_id)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return t_id, err
+	}
+	//fmt.Println("mysql row affected nums: ", num)
+	return t_id, err
+}
+
 // 添加
 func Del_TaskData_t_id(T_task_id string, Id string) bool {
 
 	o := orm.NewOrm()
 
 	// 开始插入数据 UPDATE `cold_verify`.`Z_TaskData_d8qMyeXLzIxn` SET `t_t` = 20.2 WHERE `ID` = 69
-	sql := "DELETE FROM z_task_data_" + T_task_id + " WHERE t_id = " + Id
+	sql := "DELETE FROM z_task_data_" + T_task_id + " WHERE t_id = '" + Id + "'"
 	//  这里有时间优化  用于一次 prepare 多次 exec,以提高批量执行的速度
 	logs.Println(sql)
 	res, err := o.Raw(sql).Exec()
@@ -726,3 +858,103 @@ func UpdateTaskDataTemperatureAndHumidityByGeometric(T_task_id, sn, id, startTim
 	}
 	fmt.Printf("影响了%d行\n", affected)
 }
+
+func Read_TaskData_T_Min_Max_Time_Min_Max(T_task_id string, SN []string, T_id []string, Time_start_ string, Time_end_ string) (minT, maxT float64, minTime, maxTime time.Time) {
+	o := orm.NewOrm()
+
+	sql_condition := ""
+
+	if len(Time_start_) > 1 {
+		sql_condition += " AND t_time >= '" + Time_start_ + "'"
+	}
+
+	if len(Time_end_) > 1 {
+		sql_condition += " AND t_time <= '" + Time_end_ + "'"
+	}
+
+	if len(T_id) > 0 || len(SN) > 0 {
+		sql_condition += " AND t_id in (" + strings.Join(T_id, ",") + ") OR t_sn in (" + strings.Join(SN, ",") + ")"
+	}
+
+	if len(sql_condition) > 0 {
+		sql_condition = " WHERE " + strings.TrimLeft(sql_condition, " AND ")
+	}
+	//fmt.Println("maps_z;",maps_z[0][0])
+	sql := "SELECT MIN(t_t) AS min_t, MAX(t_t) AS max_t,MIN(t_time) AS min_time, MAX(t_time) AS max_time FROM z_task_data_" + T_task_id + sql_condition
+
+	fmt.Println(sql)
+	err := o.Raw(sql).QueryRow(&minT, &maxT, &minTime, &maxTime)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+	}
+	return
+
+}
+
+func Read_TaskData_ByIds_List(T_task_id string, SN []string, T_id []string, Time_start_ string, Time_end_ string) []TaskData {
+	o := orm.NewOrm()
+	var maps []TaskData
+
+	sql_condition := ""
+	if len(Time_start_) > 1 {
+		sql_condition += " AND t_time >= '" + Time_start_ + "'"
+	}
+
+	if len(Time_end_) > 1 {
+		sql_condition += " AND t_time <= '" + Time_end_ + "'"
+	}
+
+	if len(T_id) > 0 || len(SN) > 0 {
+		sql_condition += " AND (t_id in (" + strings.Join(T_id, ",") + ") OR t_sn in (" + strings.Join(SN, ",") + "))"
+	}
+	if len(sql_condition) > 0 {
+		sql_condition = " WHERE " + strings.TrimLeft(sql_condition, " AND ")
+	}
+
+	//fmt.Println("maps_z;",maps_z[0][0])
+	sql := "SELECT ID,t_sn,t_id,t_t,t_rh,t_time FROM z_task_data_" + T_task_id + sql_condition + " ORDER BY t_time"
+
+	fmt.Println(sql)
+	_, err := o.Raw(sql).QueryRows(&maps)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+	}
+
+	return maps
+}
+
+// 按照 t_id 分组数据
+func GroupDataByTID(data []TaskData) map[string][]TaskData {
+	groupedData := make(map[string][]TaskData)
+	for _, d := range data {
+		groupedData[d.T_id] = append(groupedData[d.T_id], d)
+	}
+	return groupedData
+}
+
+// 删除的重复数据
+func DeleteDeduplicate(T_task_id string) error {
+	localOrm := orm2.NewOrmUsingDB(conf.Local_AliasName)
+	tb_name := "z_task_data_" + T_task_id
+
+	// 创建临时表
+	sqlCreate := "CREATE TABLE `tmp_table` AS (SELECT MAX(`ID`) AS `max_id` FROM " + tb_name + " GROUP BY `t_sn`,`t_id`,`t_time`);"
+	_, err := localOrm.Raw(sqlCreate).Exec()
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return err
+	}
+	sqlDelete := "DELETE FROM " + tb_name + "  WHERE `ID` NOT IN (SELECT `max_id` FROM `tmp_table`);"
+	_, err = localOrm.Raw(sqlDelete).Exec()
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return err
+	}
+	sqlDrop := "DROP TABLE `tmp_table`;"
+	_, err = localOrm.Raw(sqlDrop).Exec()
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return err
+	}
+	return nil
+}

+ 8 - 5
routers/TaskData.go

@@ -12,11 +12,12 @@ func init() {
 	beego.Router("/TaskData/TaskDataClass_Edit", &controllers.TaskDataController{}, "*:TaskDataClass_Edit")
 	beego.Router("/TaskData/TaskDataClass_Del", &controllers.TaskDataController{}, "*:TaskDataClass_Del")
 	beego.Router("/TaskData/List", &controllers.TaskDataController{}, "*:TaskData_List")
-	beego.Router("/TaskData/Add", &controllers.TaskDataController{}, "*:TaskData_Add")         //
-	beego.Router("/TaskData/AddS", &controllers.TaskDataController{}, "*:TaskData_AddS")       //
-	beego.Router("/TaskData/Up", &controllers.TaskDataController{}, "*:TaskData_Up")           //
-	beego.Router("/TaskData/Del", &controllers.TaskDataController{}, "*:TaskData_Del")         //
-	beego.Router("/TaskData/DelTid", &controllers.TaskDataController{}, "*:TaskData_Del_t_id") //
+	beego.Router("/TaskData/Add", &controllers.TaskDataController{}, "*:TaskData_Add")               //
+	beego.Router("/TaskData/AddS", &controllers.TaskDataController{}, "*:TaskData_AddS")             //
+	beego.Router("/TaskData/Up", &controllers.TaskDataController{}, "*:TaskData_Up")                 //
+	beego.Router("/TaskData/Del", &controllers.TaskDataController{}, "*:TaskData_Del")               //
+	beego.Router("/TaskData/DelTid", &controllers.TaskDataController{}, "*:TaskData_Del_t_id")       //
+	beego.Router("/TaskData/AddS_Excel", &controllers.TaskDataController{}, "*:TaskData_AddS_Excel") //
 
 	beego.Router("/TaskData/Export_Data_Excel", &controllers.TaskDataController{}, "*:Export_Data_Excel") // 设置 设备参数
 
@@ -44,4 +45,6 @@ func init() {
 	beego.Router("/TaskDataCopy/Add", &controllers.TaskDataController{}, "*:TaskDataCopy_Add")         // 添加数据存档
 	beego.Router("/TaskDataCopy/Del", &controllers.TaskDataController{}, "*:TaskDataCopy_Del")         // 删除数据存档
 	beego.Router("/TaskDataCopy/Recover", &controllers.TaskDataController{}, "*:TaskDataCopy_Recover") // 数据存档恢复
+
+	beego.Router("/TaskData/jpg", &controllers.TaskDataController{}, "*:TaskData_JPG") // 生成图片
 }