Explorar o código

Merge remote-tracking branch 'origin/master' into pool/xy_login

# Conflicts:
#	main.go
#	services/user_login.go
Roc hai 1 ano
pai
achega
d3c98e8e45
Modificáronse 55 ficheiros con 1967 adicións e 417 borrados
  1. 51 57
      controllers/ai/ai_file.go
  2. 1 1
      controllers/company_permission.go
  3. 20 0
      controllers/data_manage/chart_classify.go
  4. 10 1
      controllers/data_manage/chart_info.go
  5. 86 0
      controllers/data_manage/cross_variety/chart_info.go
  6. 29 27
      controllers/data_manage/edb_info.go
  7. 10 39
      controllers/data_manage/excel/excel_classify.go
  8. 1 1
      controllers/data_manage/excel/excel_info.go
  9. 8 1
      controllers/data_manage/future_good/future_good_chart_info.go
  10. 4 4
      controllers/data_manage/line_feature/chart_info.go
  11. 4 0
      controllers/data_manage/multiple_graph_config.go
  12. 11 4
      controllers/data_manage/smm_api.go
  13. 25 1
      controllers/data_manage/yongyi_data.go
  14. 32 15
      controllers/sys_admin.go
  15. 25 3
      controllers/sys_menu.go
  16. 1 1
      controllers/target.go
  17. 131 47
      controllers/user_login.go
  18. 2 0
      go.mod
  19. 4 0
      go.sum
  20. 2 0
      main.go
  21. 35 11
      models/business_conf.go
  22. 2 1
      models/data_manage/base_from_yongyi_classify.go
  23. 8 0
      models/data_manage/chart_classify.go
  24. 43 3
      models/data_manage/chart_info.go
  25. 30 23
      models/data_manage/cross_variety/request/chart.go
  26. 8 0
      models/data_manage/edb_classify.go
  27. 8 0
      models/data_manage/edb_info.go
  28. 6 0
      models/data_manage/excel/excel_classify.go
  29. 1 0
      models/data_manage/line_feature/request/line_feature.go
  30. 2 0
      models/data_manage/multiple_graph_config.go
  31. 1 1
      models/data_manage/request/meeting_probabilities.go
  32. 1 1
      models/db.go
  33. 2 2
      models/sandbox/sandbox_classify.go
  34. 1 0
      models/system/admin_verify_code_record.go
  35. 3 0
      models/system/sys_admin.go
  36. 1 0
      models/system/sys_user.go
  37. 18 0
      routers/commentsRouter.go
  38. 2 1
      services/data/base_edb_lib.go
  39. 3 0
      services/data/chart_info.go
  40. 141 37
      services/data/cross_variety/chart.go
  41. 12 0
      services/data/edb_classify.go
  42. 1 1
      services/data/excel/custom_analysis.go
  43. 46 0
      services/data/excel/excel_classify.go
  44. 81 31
      services/data/line_feature/chart_info.go
  45. 62 0
      services/email.go
  46. 4 0
      services/minio.go
  47. 131 0
      services/nanhua_sms.go
  48. 108 0
      services/sms.go
  49. 161 95
      services/user_login.go
  50. 18 7
      utils/config.go
  51. 14 0
      utils/constants.go
  52. 1 1
      utils/jwt.go
  53. 33 0
      utils/redis.go
  54. 265 0
      utils/redis/cluster_redis.go
  55. 257 0
      utils/redis/standalone_redis.go

+ 51 - 57
controllers/ai/ai_file.go

@@ -269,12 +269,12 @@ func (this *AiFileController) FileRetrieve() {
 
 	//根据提问,获取信息
 	askUuid := utils.MD5(req.Ask)
-	chatMode, err := aimod.GetAiChatByAsk(askUuid)
-	if err != nil && err.Error() != utils.ErrNoRow() {
-		br.Msg = "获取数据失败!"
-		br.ErrMsg = "获取数据失败,GetAiChatByAsk,Err:" + err.Error()
-		return
-	}
+	//chatMode, err := aimod.GetAiChatByAsk(askUuid)
+	//if err != nil && err.Error() != utils.ErrNoRow() {
+	//	br.Msg = "获取数据失败!"
+	//	br.ErrMsg = "获取数据失败,GetAiChatByAsk,Err:" + err.Error()
+	//	return
+	//}
 
 	var assistantId, threadId string
 	if req.AiChatTopicId > 0 {
@@ -297,63 +297,57 @@ func (this *AiFileController) FileRetrieve() {
 
 	resp := new(aimod.ChatResp)
 	var answer string
-	if chatMode != nil && chatMode.Answer != "" {
-		answer = chatMode.Answer
-	} else {
-		//获取主题下的所有信息
-		//AiChatTopicId
-		historyList, err := aimod.GetAiChatList(req.AiChatTopicId)
-		if err != nil && err.Error() != utils.ErrNoRow() {
-			br.Msg = "获取主题历史数据失败!"
-			br.ErrMsg = "获取主题历史数据失败,Err:" + err.Error()
-			return
-		}
+	//获取主题下的所有信息
+	//AiChatTopicId
+	historyList, err := aimod.GetAiChatList(req.AiChatTopicId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取主题历史数据失败!"
+		br.ErrMsg = "获取主题历史数据失败,Err:" + err.Error()
+		return
+	}
 
-		frList := make([]aimod.HistoryChat, 0)
-		tmpFileIdList := make([]string, 0)
+	frList := make([]aimod.HistoryChat, 0)
+	tmpFileIdList := make([]string, 0)
 
-		// 历史消息
-		for _, v := range historyList {
-			if v.OpenaiFileId != "" {
-				tmpFileIdList = append(tmpFileIdList, v.OpenaiFileId)
-			} else {
-				historyFr := new(aimod.HistoryChat)
-				historyFr.Ask = v.Ask
-				historyFr.Answer = v.Answer
-				historyFr.OpenaiFileId = tmpFileIdList
-				frList = append(frList, *historyFr)
-				tmpFileIdList = []string{}
-			}
-		}
-		// 当前的消息
-		{
-			frItem := new(aimod.HistoryChat)
-			frItem.Ask = req.Ask
-			frItem.Answer = ""
-			frItem.OpenaiFileId = tmpFileIdList
-			frList = append(frList, *frItem)
+	// 历史消息
+	for _, v := range historyList {
+		if v.OpenaiFileId != "" {
+			tmpFileIdList = append(tmpFileIdList, v.OpenaiFileId)
+		} else {
+			historyFr := new(aimod.HistoryChat)
+			historyFr.Ask = v.Ask
+			historyFr.Answer = v.Answer
+			historyFr.OpenaiFileId = tmpFileIdList
+			frList = append(frList, *historyFr)
+			tmpFileIdList = []string{}
 		}
+	}
+	// 当前的消息
+	{
+		frItem := new(aimod.HistoryChat)
+		frItem.Ask = req.Ask
+		frItem.Answer = ""
+		frItem.OpenaiFileId = tmpFileIdList
+		frList = append(frList, *frItem)
+	}
 
-		//var assistantId,threadId string
-		fileRetrieveResp, err := aiser.FileRetrieve(assistantId, threadId, frList, req.OpenaiFileId)
-		//fileRetrieveResp, err := aiser.FileRetrieve(assistantId, threadId, frList, []string{})
-		if err != nil {
+	fileRetrieveResp, err := aiser.FileRetrieve(assistantId, threadId, frList, req.OpenaiFileId)
+	if err != nil {
+		br.Msg = "获取数据失败!"
+		br.ErrMsg = "获取数据失败,FileRetrieve,Err:" + err.Error()
+		return
+	}
+
+	if fileRetrieveResp != nil {
+		if fileRetrieveResp.Ret == 200 {
+			assistantId = fileRetrieveResp.Data.AssistantId
+			threadId = fileRetrieveResp.Data.ThreadId
+			answer = fileRetrieveResp.Data.Answer
+		} else {
 			br.Msg = "获取数据失败!"
-			br.ErrMsg = "获取数据失败,FileRetrieve,Err:" + err.Error()
+			br.ErrMsg = fileRetrieveResp.Msg
 			return
 		}
-
-		if fileRetrieveResp != nil {
-			if fileRetrieveResp.Ret == 200 {
-				assistantId = fileRetrieveResp.Data.AssistantId
-				threadId = fileRetrieveResp.Data.ThreadId
-				answer = fileRetrieveResp.Data.Answer
-			} else {
-				br.Msg = "获取数据失败!"
-				br.ErrMsg = fileRetrieveResp.Msg
-				return
-			}
-		}
 	}
 
 	if req.AiChatTopicId <= 0 { //新增
@@ -392,7 +386,7 @@ func (this *AiFileController) FileRetrieve() {
 		chatItem := new(aimod.AiChat)
 		chatItem.AiChatTopicId = req.AiChatTopicId
 		chatItem.Ask = req.Ask
-		chatItem.AskUuid = utils.MD5(req.Ask)
+		chatItem.AskUuid = askUuid
 		chatItem.Answer = answer
 		chatItem.Model = "gpt-4-1106-preview"
 		chatItem.SysUserId = this.SysUser.AdminId

+ 1 - 1
controllers/company_permission.go

@@ -101,7 +101,7 @@ func (this *CompanyPermissionController) PermissionVariety() {
 		br.Ret = 408
 		return
 	}
-	if utils.BusinessCode != utils.BusinessCodeRelease && utils.BusinessCode != utils.BusinessCodeSandbox {
+	if utils.BusinessCode != utils.BusinessCodeRelease && utils.BusinessCode != utils.BusinessCodeSandbox && utils.BusinessCode != utils.BusinessCodeDebug {
 		br.Ret = 200
 		br.Success = true
 		br.Msg = "获取成功"

+ 20 - 0
controllers/data_manage/chart_classify.go

@@ -747,6 +747,9 @@ func (this *ChartClassifyController) DeleteChartClassify() {
 func (this *ChartClassifyController) ChartClassifyMove() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
@@ -775,10 +778,27 @@ func (this *ChartClassifyController) ChartClassifyMove() {
 	//判断分类是否存在
 	chartClassifyInfo, err := data_manage.GetChartClassifyById(req.ClassifyId)
 	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在,请刷新页面"
+			return
+		}
 		br.Msg = "移动失败"
 		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
 		return
 	}
+
+	// 校验移动的父级目录下是否有重名分类
+	exists, e := data_manage.GetChartClassifyByParentIdAndName(req.ParentClassifyId, chartClassifyInfo.ChartClassifyName, req.ClassifyId)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		br.Msg = "移动失败"
+		br.ErrMsg = "获取父级目录下的同名分类失败, Err: " + e.Error()
+		return
+	}
+	if exists != nil {
+		br.Msg = "移动失败,分类名称已存在"
+		return
+	}
+
 	// 权限校验
 	{
 		button := data.GetChartClassifyOpButton(this.SysUser, chartClassifyInfo.SysUserId)

+ 10 - 1
controllers/data_manage/chart_info.go

@@ -2271,7 +2271,14 @@ func (this *ChartInfoController) ChartInfoBase64Upload() {
 	select {
 	case <-time.After(30 * time.Second):
 		utils.FileLog.Info("执行超过30秒 杀死超时进程")
-		cmd.Process.Kill()
+		e := cmd.Process.Kill()
+		if e != nil {
+			fmt.Println("cmd kill err: ", e.Error())
+			utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
+			br.Msg = "图片生成失败"
+			br.ErrMsg = "图片生成失败, 执行超时" + e.Error()
+			return
+		}
 		fmt.Println("timeout kill process")
 	case <-doneChannel:
 		fmt.Println("done")
@@ -2527,6 +2534,8 @@ func (this *ChartInfoController) CopyChartInfo() {
 		LeftMax:           oldChartInfo.LeftMax,
 		RightMin:          oldChartInfo.RightMin,
 		RightMax:          oldChartInfo.RightMax,
+		Right2Min:         oldChartInfo.Right2Min,
+		Right2Max:         oldChartInfo.Right2Max,
 		Disabled:          oldChartInfo.Disabled,
 		Source:            oldChartInfo.Source,
 		ExtraConfig:       oldChartInfo.ExtraConfig,

+ 86 - 0
controllers/data_manage/cross_variety/chart_info.go

@@ -1586,3 +1586,89 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	isOk = true
 	return
 }
+
+// Save
+// @Title 保存图表
+// @Description 保存图表
+// @Param	request	body request.SaveChartReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /chart_info/save [post]
+func (c *ChartInfoController) Save() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = true
+		}
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.SaveChartReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartInfoId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ChartInfoId: %d", req.ChartInfoId)
+		return
+	}
+
+	chartItem, e := data_manage.GetChartInfoById(req.ChartInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "保存失败"
+		br.ErrMsg = "获取图表信息失败, Err: " + e.Error()
+		return
+	}
+
+	// 更新图表上下限
+	chartItem.LeftMin = req.LeftMin
+	chartItem.LeftMax = req.LeftMax
+	chartItem.XMin = req.XMin
+	chartItem.XMax = req.XMax
+	chartUpdateCols := []string{"ChartName", "ExtraConfig", "ModifyTime", "LeftMin", "LeftMax", "XMin", "XMax"}
+	if e = chartItem.Update(chartUpdateCols); e != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "更新图表上下限失败, Err: " + e.Error()
+		return
+	}
+
+	resp := new(data_manage.AddChartInfoResp)
+	resp.ChartInfoId = chartItem.ChartInfoId
+	resp.UniqueCode = chartItem.UniqueCode
+
+	// 新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartName = chartItem.ChartName
+		chartLog.ChartInfoId = req.ChartInfoId
+		chartLog.ChartClassifyId = chartItem.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartItem.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(c.Ctx.Input.RequestBody)
+		chartLog.Status = "保存跨品种分析图表"
+		chartLog.Method = c.Ctx.Input.URL()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}

+ 29 - 27
controllers/data_manage/edb_info.go

@@ -37,6 +37,7 @@ type EdbInfoController struct {
 // @Param   SubSource   query   int  true       "子数据来源:0:经济数据库,1:日期序列"
 // @Param   EdbCode   query   string  false       "指标编码/指标代码"
 // @Param   StockCode   query   string  false       "证券代码"
+// @Param   Frequency   query   string  false       "频度"
 // @Success 200 {object} data_manage.EdbInfoSearchResp
 // @router /edb_info/search [get]
 func (this *EdbInfoController) EdbInfoSearch() {
@@ -52,6 +53,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 	edbCode = strings.Replace(edbCode, "\t", "", -1)
 	subSource, _ := this.GetInt("SubSource")
 	stockCode := this.GetString("StockCode")
+	frequency := this.GetString("Frequency")
 
 	if source <= 0 {
 		br.Msg = "无效的数据来源"
@@ -193,7 +195,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 					//	br.ErrMsg = "获取失败,Err:" + err.Error()
 					//	return
 					//}
-					respItem, err := data.AddEdbData(source, edbCode)
+					respItem, err := data.AddEdbData(source, edbCode, frequency)
 					if err != nil {
 						br.Msg = "获取失败"
 						br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -266,7 +268,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 					//	br.ErrMsg = "wind 获取失败,Err:" + err.Error()
 					//	return
 					//}
-					respItem, err := data.AddEdbData(source, edbCode)
+					respItem, err := data.AddEdbData(source, edbCode, frequency)
 					if err != nil {
 						br.Msg = "获取失败"
 						br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -345,7 +347,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	br.ErrMsg = "彭博数据获取失败,Err:" + err.Error()
 				//	return
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -386,7 +388,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	br.ErrMsg = "获取失败,Err:" + err.Error()
 				//	return
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -441,7 +443,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	br.ErrMsg = "获取隆众数据失败,Err:" + err.Error()
 				//	return
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -514,7 +516,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	searchItem.StartDate = minDate
 				//	searchItem.EndDate = maxDate
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -568,7 +570,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	br.ErrMsg = "获取钢联数据失败,Err:" + err.Error()
 				//	return
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -635,7 +637,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	searchItem.StartDate = minDate
 				//	searchItem.EndDate = maxDate
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -714,7 +716,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	searchItem.StartDate = minDate
 				//	searchItem.EndDate = maxDate
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -793,7 +795,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	searchItem.StartDate = minDate
 				//	searchItem.EndDate = maxDate
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -872,7 +874,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	searchItem.StartDate = minDate
 				//	searchItem.EndDate = maxDate
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -951,7 +953,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	searchItem.StartDate = minDate
 				//	searchItem.EndDate = maxDate
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1046,7 +1048,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	searchItem.EndDate = maxDate
 				//}
 
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1149,7 +1151,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	br.ErrMsg = "lt 获取失败,Err:" + err.Error()
 				//	return
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1209,7 +1211,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	searchItem.StartDate = minDate
 				//	searchItem.EndDate = maxDate
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1261,7 +1263,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 					searchItem.EndDate = item.MaxDate
 				}
 			} else {
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1301,7 +1303,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	br.ErrMsg = "获取钢联数据失败,Err:" + err.Error()
 				//	return
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1346,7 +1348,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				searchItem.StartDate = item.MinDate
 				searchItem.EndDate = item.MaxDate
 			} else {
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1391,7 +1393,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				searchItem.StartDate = item.MinDate
 				searchItem.EndDate = item.MaxDate
 			} else {
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1458,7 +1460,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	searchItem.StartDate = minDate
 				//	searchItem.EndDate = maxDate
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1518,7 +1520,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				//	searchItem.StartDate = minDate
 				//	searchItem.EndDate = maxDate
 				//}
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1565,7 +1567,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				searchItem.StartDate = item.MinDate
 				searchItem.EndDate = item.MaxDate
 			} else {
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1612,7 +1614,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				searchItem.StartDate = minDate
 				searchItem.EndDate = maxDate
 			} else {
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败," + respItem.Msg
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1645,7 +1647,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				searchItem.StartDate = minDate
 				searchItem.EndDate = maxDate
 			} else {
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1692,7 +1694,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				searchItem.StartDate = minDate
 				searchItem.EndDate = maxDate
 			} else {
-				respItem, err := data.AddEdbData(source, edbCode)
+				respItem, err := data.AddEdbData(source, edbCode, frequency)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1750,7 +1752,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				}
 			} else {
 				// 新增指标数据
-				addRes, e := data.AddEdbData(source, edbCode)
+				addRes, e := data.AddEdbData(source, edbCode, frequency)
 				if e != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + e.Error()
@@ -5810,7 +5812,7 @@ func (this *EdbInfoController) EdbInfoSmmSearch() {
 	}
 	if len(needAddCodeArr) > 0 {
 		edbCode = strings.Join(needAddCodeArr, ",")
-		respItem, err := data.AddEdbData(utils.DATA_SOURCE_YS, edbCode)
+		respItem, err := data.AddEdbData(utils.DATA_SOURCE_YS, edbCode, "")
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取失败,Err:" + err.Error()

+ 10 - 39
controllers/data_manage/excel/excel_classify.go

@@ -78,31 +78,17 @@ func (this *ExcelClassifyController) List() {
 			classifyMap[v.ParentId] = append(classifyMap[v.ParentId], v)
 		}
 	}
-	// todo 整理第三层
 	//组装三级分类
 	for key, classify := range classifyList {
 		subList, ok := classifyMap[classify.ExcelClassifyId]
-		if ok && classify.Level == 3 {
-			classifyList[key].Children = append(classifyList[key].Children, subList...)
-		}
-	}
-	// todo 整理第二层
-	for key, classify := range classifyList {
-		subList, ok := classifyMap[classify.ExcelClassifyId]
-		// 调用sort.Slice函数,传入切片、比较函数作为参数
-		sort.Slice(subList, func(i, j int) bool { return excel.ExcelClassifyItemBySort(subList[i], subList[j]) })
-		if ok && classify.Level == 2 {
-			classifyList[key].Children = append(classifyList[key].Children, subList...)
-		}
-	}
-	// todo 整理第一层
-	for key, classify := range classifyList {
-		subList, ok := classifyMap[classify.ExcelClassifyId]
-		sort.Slice(subList, func(i, j int) bool { return excel.ExcelClassifyItemBySort(subList[i], subList[j]) })
-		if ok && classify.Level == 1 {
+		if ok {
 			classifyList[key].Children = append(classifyList[key].Children, subList...)
+			sort.Slice(classifyList[key].Children, func(i, j int) bool {
+				return excel.ExcelClassifyItemBySort(classifyList[key].Children[i], classifyList[key].Children[j])
+			})
 		}
 	}
+
 	nodeAll := make([]*excel.ExcelClassifyItems, 0)
 	for _, v := range classifyList {
 		if v.ParentId == 0 {
@@ -151,28 +137,14 @@ func (this *ExcelClassifyController) ExcelClassifyItems() {
 			classifyMap[v.ParentId] = append(classifyMap[v.ParentId], v)
 		}
 	}
-	// todo 整理第三层
 	//组装三级分类
 	for key, classify := range classifyList {
 		subList, ok := classifyMap[classify.ExcelClassifyId]
-		if ok && classify.Level == 3 {
-			classifyList[key].Children = append(classifyList[key].Children, subList...)
-		}
-	}
-	// todo 整理第二层
-	for key, classify := range classifyList {
-		subList, ok := classifyMap[classify.ExcelClassifyId]
-		if ok && classify.Level == 2 {
-			classifyList[key].Children = append(classifyList[key].Children, subList...)
-		}
-	}
-	// todo 整理第一层
-	for key, classify := range classifyList {
-		subList, ok := classifyMap[classify.ExcelClassifyId]
-		if ok && classify.Level == 1 {
+		if ok {
 			classifyList[key].Children = append(classifyList[key].Children, subList...)
 		}
 	}
+
 	nodeAll := make([]*excel.ExcelClassifyItems, 0)
 	for _, v := range classifyList {
 		if v.ParentId == 0 {
@@ -242,9 +214,8 @@ func (this *ExcelClassifyController) AddExcelClassify() {
 		br.ErrMsg = "查询排序信息失败,Err:" + err.Error()
 		return
 	}
-
+	level := 1
 	// 查询父级分类是否存在
-
 	if req.ParentId > 0 {
 		var parent *excel.ExcelClassify
 		parent, err = excel.GetExcelClassifyById(req.ParentId)
@@ -257,7 +228,7 @@ func (this *ExcelClassifyController) AddExcelClassify() {
 			br.ErrMsg = "查询父级分类信息失败,Err:" + err.Error()
 			return
 		}
-		req.Level = parent.Level + 1
+		level = parent.Level + 1
 	}
 	// 入库
 	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
@@ -268,7 +239,7 @@ func (this *ExcelClassifyController) AddExcelClassify() {
 		Source:            source,
 		SysUserId:         this.SysUser.AdminId,
 		SysUserRealName:   this.SysUser.RealName,
-		Level:             req.Level,
+		Level:             level,
 		UniqueCode:        utils.MD5(utils.EXCEL_DATA_PREFIX + "_" + timestamp),
 		Sort:              maxSort + 1,
 		CreateTime:        time.Now(),

+ 1 - 1
controllers/data_manage/excel/excel_info.go

@@ -355,7 +355,7 @@ func (c *ExcelInfoController) List() {
 			return
 		}
 
-		childClassify, e := excel3.GetChildClassifyById(excelClassifyId)
+		childClassify, e, _ := excel2.GetChildClassifyByClassifyId(excelClassifyId, source)
 		if e != nil && e.Error() != utils.ErrNoRow() {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取分类信息失败, GetEdbClassify,Err:" + e.Error()

+ 8 - 1
controllers/data_manage/future_good/future_good_chart_info.go

@@ -2419,7 +2419,14 @@ func (this *FutureGoodChartInfoController) ChartInfoBase64Upload() {
 	select {
 	case <-time.After(30 * time.Second):
 		utils.FileLog.Info("执行超过30秒 杀死超时进程")
-		cmd.Process.Kill()
+		e := cmd.Process.Kill()
+		if e != nil {
+			fmt.Println("cmd kill err: ", e.Error())
+			utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
+			br.Msg = "图片生成失败"
+			br.ErrMsg = "图片生成失败, 执行超时" + e.Error()
+			return
+		}
 		fmt.Println("timeout kill process")
 	case <-doneChannel:
 		fmt.Println("done")

+ 4 - 4
controllers/data_manage/line_feature/chart_info.go

@@ -282,7 +282,7 @@ func (this *LineFeaturesChartInfoController) MultipleGraphPreview() {
 			tmpChartInfo.ChartName = fmt.Sprintf("%s%d%s百分位", edbInfoMapping.EdbName, req.Percentile.CalculateValue, req.Percentile.CalculateUnit)
 
 			// 获取图表中的指标数据
-			edbList, dataResp, err, errMsg := lineFeatureServ.GetPercentileData(0, startDate, endDate, edbInfoMapping, req.Percentile.CalculateValue, req.Percentile.CalculateUnit)
+			edbList, dataResp, err, errMsg := lineFeatureServ.GetPercentileData(0, startDate, endDate, edbInfoMapping, req.Percentile.CalculateValue, req.Percentile.CalculateUnit, req.Percentile.PercentType)
 			if err != nil && errMsg != `` {
 				br.Msg = errMsg
 				br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
@@ -694,7 +694,7 @@ func (this *LineFeaturesChartInfoController) MultipleGraphConfigSaveChart() {
 			extraConfig = string(extraConfigByte)
 
 			// 获取图表中的指标数据
-			_, dataResp, err, errMsg := lineFeatureServ.GetPercentileData(0, startDate, endDate, edbInfoMapping, req.Percentile.CalculateValue, req.Percentile.CalculateUnit)
+			_, dataResp, err, errMsg := lineFeatureServ.GetPercentileData(0, startDate, endDate, edbInfoMapping, req.Percentile.CalculateValue, req.Percentile.CalculateUnit, req.Percentile.PercentType)
 			if err != nil && errMsg != `` {
 				br.Msg = errMsg
 				br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
@@ -1849,7 +1849,7 @@ func (this *LineFeaturesChartInfoController) Detail() {
 			maxYear = latestDateT.Year()
 		}
 		startDate, endDate := utils.GetDateByDateTypeV2(chartInfo.DateType, chartInfo.StartDate, chartInfo.EndDate, chartInfo.StartYear, maxYear)
-		edbList, resultResp, err, errMsg = lineFeatureServ.GetPercentileData(0, startDate, endDate, edbMapping, percentileConfig.CalculateValue, percentileConfig.CalculateUnit)
+		edbList, resultResp, err, errMsg = lineFeatureServ.GetPercentileData(0, startDate, endDate, edbMapping, percentileConfig.CalculateValue, percentileConfig.CalculateUnit, percentileConfig.PercentType)
 	case utils.CHART_SOURCE_LINE_FEATURE_FREQUENCY:
 		var frequencyDistributionConfig request.FrequencyDistribution
 		err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &frequencyDistributionConfig)
@@ -2346,7 +2346,7 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 			maxYear = latestDateT.Year()
 		}
 		startDate, endDate := utils.GetDateByDateTypeV2(chartInfo.DateType, chartInfo.StartDate, chartInfo.EndDate, chartInfo.StartYear, maxYear)
-		edbList, resultResp, err, msg = lineFeatureServ.GetPercentileData(0, startDate, endDate, edbMapping, percentileConfig.CalculateValue, percentileConfig.CalculateUnit)
+		edbList, resultResp, err, msg = lineFeatureServ.GetPercentileData(0, startDate, endDate, edbMapping, percentileConfig.CalculateValue, percentileConfig.CalculateUnit, percentileConfig.PercentType)
 	case utils.CHART_SOURCE_LINE_FEATURE_FREQUENCY:
 		var frequencyDistributionConfig request.FrequencyDistribution
 		err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &frequencyDistributionConfig)

+ 4 - 0
controllers/data_manage/multiple_graph_config.go

@@ -742,6 +742,8 @@ func (this *ChartInfoController) MultipleGraphConfigSaveChart() {
 				LeftMax:          fmt.Sprint(curveConf.LeftMax),
 				RightMin:         fmt.Sprint(curveConf.RightMin),
 				RightMax:         fmt.Sprint(curveConf.RightMax),
+				Right2Min:        fmt.Sprint(curveConf.Right2Min),
+				Right2Max:        fmt.Sprint(curveConf.Right2Max),
 			}
 			chartInfo, err, errMsg, isSendEmail = data.AddChartInfo(addChartReq, sysUser.AdminId, sysUser.RealName)
 		} else {
@@ -760,6 +762,8 @@ func (this *ChartInfoController) MultipleGraphConfigSaveChart() {
 				LeftMax:          fmt.Sprint(curveConf.LeftMax),
 				RightMin:         fmt.Sprint(curveConf.RightMin),
 				RightMax:         fmt.Sprint(curveConf.RightMax),
+				Right2Min:        fmt.Sprint(curveConf.Right2Min),
+				Right2Max:        fmt.Sprint(curveConf.Right2Max),
 			}
 			chartInfo, err, errMsg, isSendEmail = data.EditChartInfo(editChartReq, sysUser)
 			if err != nil {

+ 11 - 4
controllers/data_manage/smm_api.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
 	"strings"
 )
 
@@ -104,13 +105,20 @@ func (this *EdbInfoController) SmmApiList() {
 		}
 	}
 
+	sortStr := ``
+
 	if keyword != "" {
 		keyWordArr := strings.Split(keyword, " ")
 		if len(keyWordArr) > 0 {
 			condition += " AND ( "
-			for _, v := range keyWordArr {
+			keywordStr := strings.Replace(keyword, " ", "", -1)
+			condition += ` CONCAT(index_name,index_code) LIKE '%` + keywordStr + `%' OR `
+			sortStr += ` CASE WHEN CONCAT(index_name,index_code) LIKE '%` + keywordStr + `%' THEN 1 `
+			for i, v := range keyWordArr {
 				condition += ` CONCAT(index_name,index_code) LIKE '%` + v + `%' OR`
+				sortStr += ` WHEN CONCAT(index_name,index_code) LIKE '%` + v + `%' THEN  ` + strconv.Itoa(i+2) + ` `
 			}
+			sortStr += ` END, `
 			condition = strings.TrimRight(condition, "OR")
 			condition += " ) "
 		}
@@ -126,11 +134,10 @@ func (this *EdbInfoController) SmmApiList() {
 		condition += " AND index_code IN (" + indexCodeStr + ") "
 	}
 
-	sortStr := ``
 	if sortParam != `` {
-		sortStr = fmt.Sprintf("%s %s,modify_time desc ", utils.PascalToSnake(sortParam), sortType)
+		sortStr += fmt.Sprintf("%s %s,modify_time desc ", utils.PascalToSnake(sortParam), sortType)
 	} else {
-		sortStr = " modify_time desc "
+		sortStr += " modify_time desc "
 	}
 
 	total, err := data_manage.GetSmmIndexDataListCount(condition, pars)

+ 25 - 1
controllers/data_manage/yongyi_data.go

@@ -38,9 +38,33 @@ func (this *EdbInfoController) YongyiClassify() {
 		br.ErrMsg = "获取数据失败,Err:" + err.Error()
 		return
 	}
+	//组装一级分类
+	rootMap := make(map[int][]*data_manage.BaseFromYongyiClassifyItems)
+	list := make([]*data_manage.BaseFromYongyiClassifyItems, 0)
+	for _, classify := range classifyAll {
+		if classify.ParentId == 0 {
+			if _, ok := rootMap[classify.ClassifyId]; !ok {
+				rootMap[classify.ClassifyId] = make([]*data_manage.BaseFromYongyiClassifyItems, 0)
+				list = append(list, classify)
+			}
+		} else {
+			child, ok := rootMap[classify.ParentId]
+			if ok {
+				child = append(child, classify)
+				rootMap[classify.ParentId] = child
+			}
+		}
+	}
 
+	for k, v := range list {
+		child, ok := rootMap[v.ClassifyId]
+		if ok {
+			list[k].Children = child
+		}
+	}
+	//组装二级分类
 	var ret data_manage.BaseFromYongyiClassifyResp
-	ret.List = classifyAll
+	ret.List = list
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"

+ 32 - 15
controllers/sys_admin.go

@@ -442,21 +442,36 @@ func (this *SysAdminController) Add() {
 		}
 	}
 
-	pwdByte, err := base64.StdEncoding.DecodeString(req.Password)
-	if err != nil {
-		br.Msg = "解析数据失败"
-		br.ErrMsg = "解析数据失败,Err:" + err.Error()
-		return
+	var originPass, pass string
+	if req.Password != "" {
+		pwdByte, e := base64.StdEncoding.DecodeString(req.Password)
+		if e != nil {
+			br.Msg = "解析数据失败"
+			br.ErrMsg = "解析数据失败,Err:" + e.Error()
+			return
+		}
+		originPass = string(pwdByte)
 	}
-	pwdStr := string(pwdByte)
-	//pwdStr = strings.ToLower(pwdStr)
-	if pwdStr == "" {
-		br.Msg = "请输入密码"
-		return
+	// 系统用户-密码必填且需要校验密码格式
+	if req.IsLdap == 0 {
+		if originPass == "" {
+			br.Msg = "请输入密码"
+			return
+		}
+		if !utils.CheckPwd(originPass) {
+			br.Msg = "密码格式错误,请重新输入"
+			return
+		}
+		pass = utils.MD5(originPass)
 	}
-	if !utils.CheckPwd(pwdStr) {
-		br.Msg = "密码格式错误,请重新输入"
-		return
+	// 域用户-密码非必填(实际登录用不到这个密码)
+	if req.IsLdap == 1 {
+		if originPass != "" {
+			pass = utils.MD5(originPass)
+		} else {
+			// 未填写的话给个初始密码即可
+			pass = utils.MD5(utils.LdapInitPassword)
+		}
 	}
 
 	// 员工工号
@@ -465,7 +480,7 @@ func (this *SysAdminController) Add() {
 	admin := new(system.Admin)
 	admin.AdminName = req.AdminName
 	admin.RealName = req.RealName
-	admin.Password = utils.MD5(pwdStr)
+	admin.Password = pass
 	admin.LastUpdatedPasswordTime = time.Now().Format(utils.FormatDateTime)
 	admin.Enabled = 1
 	admin.LastLoginTime = time.Now().Format(utils.FormatDateTime)
@@ -519,6 +534,7 @@ func (this *SysAdminController) Add() {
 	admin.City = req.City
 	admin.CityCode = req.CityCode
 	admin.TelAreaCode = req.TelAreaCode
+	admin.IsLdap = req.IsLdap
 	err = system.AddAdmin(admin)
 	if err != nil {
 		br.Msg = "新增失败"
@@ -737,9 +753,10 @@ func (this *SysAdminController) Edit() {
 	adminInfo.EmployeeId = req.EmployeeId
 	adminInfo.Email = req.Email
 	adminInfo.TelAreaCode = req.TelAreaCode
+	adminInfo.IsLdap = req.IsLdap
 	cols := []string{
 		"AdminName", "RealName", "LastUpdatedTime", "Mobile", "RoleId", "RoleName", "Enabled", "Authority",
-		"Position", "RoleTypeCode", "Province", "ProvinceCode", "City", "CityCode", "EmployeeId", "Email", "TelAreaCode",
+		"Position", "RoleTypeCode", "Province", "ProvinceCode", "City", "CityCode", "EmployeeId", "Email", "TelAreaCode", "IsLdap",
 	}
 	if e := adminInfo.Update(cols); e != nil {
 		br.Msg = "编辑失败"

+ 25 - 3
controllers/sys_menu.go

@@ -65,6 +65,16 @@ func (this *SysRoleController) SysMenuList() {
 		shareSellerMap[admin.AdminId] = true
 	}
 
+	// 审批配置
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
+		return
+	}
+	openMap := map[string]bool{"false": false, "true": true}
+	openApprove := openMap[confMap[models.BusinessConfIsReportApprove]]
+
 	list, err := system.GetMenuListByRoleIds(roleIds)
 	if err != nil {
 		br.Msg = "获取失败"
@@ -72,8 +82,14 @@ func (this *SysRoleController) SysMenuList() {
 		return
 	}
 	lenList := len(list)
+
+	menuList := make([]*system.MenuList, 0)
 	for i := 0; i < lenList; i++ {
 		item := list[i]
+		// 审批关闭时隐藏审批菜单(这需求大可不必=_=!)
+		if !openApprove && item.Name == "审批管理" {
+			continue
+		}
 		child, err := system.GetMenuByParentIdRoleIds(roleIds, item.MenuId)
 		if err != nil {
 			br.Msg = "获取失败"
@@ -90,14 +106,19 @@ func (this *SysRoleController) SysMenuList() {
 				}
 			}
 		}
-		for i, menu := range child {
+		for mi, menu := range child {
 			if menu.Name == "正式客户共享" && sysUser.RoleTypeCode != utils.ROLE_TYPE_CODE_ADMIN &&
 				sysUser.AdminId != 66 && sysUser.AdminId != 15 {
 				//如果不是admin角色、共享客户分组下用户、楼颖丹账号、王沛账号 就不显示该页面
 				if ok, _ := shareSellerMap[sysUser.AdminId]; !ok {
-					child = append(child[:i], child[i+1:]...)
+					child = append(child[:mi], child[mi+1:]...)
 				}
 			}
+
+			// 审批关闭时隐藏系统管理下的审批流配置
+			if menu.Name == "审批流配置" && !openApprove {
+				child = append(child[:mi], child[mi+1:]...)
+			}
 		}
 
 		if strings.Contains(item.Name, "出差管理") && sysUser.AdminId == 66 {
@@ -117,9 +138,10 @@ func (this *SysRoleController) SysMenuList() {
 		//	list[i].LevelPath = ""
 		//}
 		list[i].Children = child
+		menuList = append(menuList, list[i])
 	}
 	resp := new(system.MenuListResp)
-	resp.List = list
+	resp.List = menuList
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"

+ 1 - 1
controllers/target.go

@@ -1682,7 +1682,7 @@ func (this *TargetController) TargetCheck() {
 		return
 	}
 	resp := new(models.DataCheckResp)
-	count, err := models.GetTargetInfoCount(tradeCode)
+	count, err := data_manage.GetEdbInfoCount(utils.DATA_SOURCE_MANUAL,tradeCode)
 	if err != nil && err.Error() != utils.ErrNoRow() {
 		br.Msg = "获取数据失败"
 		br.Msg = "获取数据失败,Err:" + err.Error()

+ 131 - 47
controllers/user_login.go

@@ -144,7 +144,7 @@ func (this *UserLoginController) GetVerifyCode() {
 			br.Msg = "请输入手机号"
 			return
 		}
-		if req.TelAreaCode == "86" && !utils.ValidateMobileFormatat(req.Mobile) {
+		if req.TelAreaCode == utils.TelAreaCodeHome && !utils.ValidateMobileFormatat(req.Mobile) {
 			br.Msg = "您的手机号输入有误, 请检查"
 			return
 		}
@@ -251,13 +251,14 @@ func (this *UserLoginController) Login() {
 
 	// 入参
 	type UserLoginReq struct {
-		LoginType  int    `description:"登录方式: 1-账号; 2-手机号; 3-邮箱"`
-		Username   string `description:"账号"`
-		Password   string `description:"密码"`
-		Mobile     string `description:"手机号"`
-		Email      string `description:"邮箱"`
-		VerifyCode string `description:"验证码"`
-		ReqTime    string `description:"登录时间戳"`
+		LoginType   int    `description:"登录方式: 1-账号; 2-手机号; 3-邮箱"`
+		Username    string `description:"账号"`
+		Password    string `description:"密码"`
+		Mobile      string `description:"手机号"`
+		Email       string `description:"邮箱"`
+		VerifyCode  string `description:"验证码"`
+		ReqTime     string `description:"登录时间戳"`
+		TelAreaCode string `description:"区号"`
 	}
 	var req UserLoginReq
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
@@ -334,45 +335,71 @@ func (this *UserLoginController) Login() {
 			return
 		}
 
-		// 账号密码校验
-		dbPass := utils.MD5(fmt.Sprintf("%s%s%s", accountUser.Password, utils.UserLoginSalt, req.ReqTime))
-		if req.Password != dbPass {
-			br.Ret = models.BaseRespCodeLoginErr
-			br.Msg = "登录失败, 账号或密码错误"
-			// 错误密码计数, 超过6次标记异常
-			if !utils.Rc.IsExist(errPassKey) {
-				_ = utils.Rc.Put(errPassKey, 1, utils.GetTodayLastSecond())
+		// 系统用户-账号密码校验
+		if accountUser.IsLdap == 0 {
+			dbPass := utils.MD5(fmt.Sprintf("%s%s%s", accountUser.Password, utils.UserLoginSalt, req.ReqTime))
+			if req.Password != dbPass {
+				br.Ret = models.BaseRespCodeLoginErr
+				br.Msg = "登录失败, 账号或密码错误"
+				// 错误密码计数, 超过6次标记异常
+				if !utils.Rc.IsExist(errPassKey) {
+					_ = utils.Rc.Put(errPassKey, 1, utils.GetTodayLastSecond())
+					return
+				}
+				errNum, _ := utils.Rc.RedisInt(errPassKey)
+				errNum += 1
+				if errNum < 6 {
+					_ = utils.Rc.Put(errPassKey, errNum, utils.GetTodayLastSecond())
+					return
+				}
+				// 标记异常登录, 重置计数
+				br.Ret = models.BaseRespCodeAbnormalLogin
+				br.Msg = "账号异常, 请进行手机号/邮箱校验"
+				_ = utils.Rc.Put(abnormalKey, "true", utils.GetTodayLastSecond())
+				_ = utils.Rc.Delete(errPassKey)
 				return
 			}
-			errNum, _ := utils.Rc.RedisInt(errPassKey)
-			errNum += 1
-			if errNum < 6 {
-				_ = utils.Rc.Put(errPassKey, errNum, utils.GetTodayLastSecond())
+
+			if accountUser.Enabled == 0 {
+				br.Msg = "您的账号已被禁用, 如需登录, 请联系管理员"
+				br.ErrMsg = fmt.Sprintf("账号已被禁用, 登录账号: %s, 账户名称: %s", accountUser.AdminName, accountUser.RealName)
 				return
 			}
-			// 标记异常登录, 重置计数
-			br.Ret = models.BaseRespCodeAbnormalLogin
-			br.Msg = "账号异常, 请进行手机号/邮箱校验"
-			_ = utils.Rc.Put(abnormalKey, "true", utils.GetTodayLastSecond())
-			_ = utils.Rc.Delete(errPassKey)
-			return
-		}
 
-		if accountUser.Enabled == 0 {
-			br.Msg = "您的账号已被禁用, 如需登录, 请联系管理员"
-			br.ErrMsg = fmt.Sprintf("账号已被禁用, 登录账号: %s, 账户名称: %s", accountUser.AdminName, accountUser.RealName)
-			return
+			// 异常登录-是否登录间隔大于60天
+			if isAbnormal == "" {
+				abnormalTime := time.Now().AddDate(0, 0, -60)
+				lastLogin, _ := time.ParseInLocation(utils.FormatDateTime, accountUser.LastLoginTime, time.Local)
+				if !lastLogin.IsZero() && lastLogin.Before(abnormalTime) {
+					br.Msg = "请进行异常登录校验"
+					br.Ret = models.BaseRespCodeAbnormalLogin
+					// 标记异常登录
+					_ = utils.Rc.Put(abnormalKey, "true", utils.GetTodayLastSecond())
+					return
+				}
+			}
 		}
 
-		// 异常登录-是否登录间隔大于60天
-		if isAbnormal == "" {
-			abnormalTime := time.Now().AddDate(0, 0, -60)
-			lastLogin, _ := time.ParseInLocation(utils.FormatDateTime, accountUser.LastLoginTime, time.Local)
-			if !lastLogin.IsZero() && lastLogin.Before(abnormalTime) {
-				br.Msg = "请进行异常登录校验"
-				br.Ret = models.BaseRespCodeAbnormalLogin
-				// 标记异常登录
-				_ = utils.Rc.Put(abnormalKey, "true", utils.GetTodayLastSecond())
+		// 如果是域账号, 那么取出原始密码走AD域校验
+		if accountUser.IsLdap == 1 {
+			passDecode, e := base64.StdEncoding.DecodeString(req.Password)
+			if e != nil {
+				br.Ret = models.BaseRespCodeLoginErr
+				br.Msg = "登录失败, 账号或密码错误"
+				br.ErrMsg = "Pass Decode err: " + e.Error()
+				return
+			}
+			originPass := strings.Replace(string(passDecode), utils.UserLoginSalt, "", 1)
+			pass, e := services.LdapUserCheck(req.Username, originPass)
+			if e != nil {
+				br.Ret = models.BaseRespCodeLoginErr
+				br.Msg = "登录失败, 账号或密码错误"
+				br.ErrMsg = "LdapLogin err: " + e.Error()
+				return
+			}
+			if !pass {
+				br.Ret = models.BaseRespCodeLoginErr
+				br.Msg = "登录失败, 账号或密码错误"
 				return
 			}
 		}
@@ -386,7 +413,8 @@ func (this *UserLoginController) Login() {
 			br.Msg = "请输入手机号"
 			return
 		}
-		if !utils.ValidateMobileFormatat(req.Mobile) {
+		// 大陆校验区号
+		if req.TelAreaCode == utils.TelAreaCodeHome && !utils.ValidateMobileFormatat(req.Mobile) {
 			br.Msg = "您的手机号输入有误, 请检查"
 			return
 		}
@@ -675,11 +703,12 @@ func (this *UserLoginController) ForgetCodeVerify() {
 	}()
 
 	type ForgetCodeVerifyReq struct {
-		FindType   int    `description:"密码找回方式: 1-手机号; 2-邮箱"`
-		VerifyCode string `description:"验证码"`
-		UserName   string `description:"用户名"`
-		Mobile     string `description:"手机号"`
-		Email      string `description:"邮箱"`
+		FindType    int    `description:"密码找回方式: 1-手机号; 2-邮箱"`
+		VerifyCode  string `description:"验证码"`
+		UserName    string `description:"用户名"`
+		Mobile      string `description:"手机号"`
+		Email       string `description:"邮箱"`
+		TelAreaCode string `description:"区号"`
 	}
 	var req ForgetCodeVerifyReq
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
@@ -706,7 +735,7 @@ func (this *UserLoginController) ForgetCodeVerify() {
 			br.Msg = "请输入手机号"
 			return
 		}
-		if !utils.ValidateMobileFormatat(req.Mobile) {
+		if req.TelAreaCode == utils.TelAreaCodeHome && !utils.ValidateMobileFormatat(req.Mobile) {
 			br.Msg = "您的手机号输入有误, 请检查"
 			return
 		}
@@ -920,6 +949,61 @@ func (this *UserLoginController) AreaCodeList() {
 	br.Msg = "获取成功"
 }
 
+// CheckUserLdap
+// @Title 校验用户是否为域用户
+// @Description 校验用户是否为域用户
+// @Param	request	body CheckUserLdapReq true "type json string"
+// @Success 200 Ret=200 获取成功
+// @router /ldap/user_check [post]
+func (this *UserLoginController) CheckUserLdap() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	type CheckUserLdapReq struct {
+		UserName string `description:"用户名"`
+	}
+	var req CheckUserLdapReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.UserName = strings.TrimSpace(req.UserName)
+	if req.UserName == "" {
+		br.Msg = "请输入账号"
+		return
+	}
+
+	isLdap := false
+	accountUser, e := system.GetSysUserByAdminName(req.UserName)
+	if e != nil {
+		// 无该用户视作普通用户
+		if e.Error() == utils.ErrNoRow() {
+			br.Data = false
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取用户信息失败, Err: " + e.Error()
+		return
+	}
+	if accountUser.IsLdap == 1 {
+		isLdap = true
+	}
+
+	br.Data = isLdap
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
 // ICPLicense
 // @Title icp备案信息
 // @Description icp备案信息

+ 2 - 0
go.mod

@@ -18,6 +18,7 @@ require (
 	github.com/beego/beego/v2 v2.0.7
 	github.com/beevik/etree v1.2.0
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
+	github.com/go-ldap/ldap v3.0.3+incompatible
 	github.com/go-sql-driver/mysql v1.7.0
 	github.com/go-xorm/xorm v0.7.9
 	github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b
@@ -115,6 +116,7 @@ require (
 	golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+	gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect

+ 4 - 0
go.sum

@@ -148,6 +148,8 @@ github.com/go-ego/gse v0.80.2 h1:3LRfkaBuwlsHsmkOZvnhTcsYPXUAhiP06Sqcid7mO1M=
 github.com/go-ego/gse v0.80.2/go.mod h1:kesekpZfcFQ/kwd9b27VZHUOH5dQUjaaQUZ4OGt4Hj4=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
+github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
 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.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
@@ -622,6 +624,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

+ 2 - 0
main.go

@@ -26,7 +26,9 @@ func main() {
 	// 异常处理
 	web.ErrorController(&controllers.ErrorController{})
 
+	// 内存调整
 	web.BConfig.MaxMemory = 1024 * 1024 * 128
+
 	web.BConfig.RecoverFunc = Recover
 	web.Run()
 }

+ 35 - 11
models/business_conf.go

@@ -9,22 +9,46 @@ import (
 )
 
 const (
-	BusinessConfUseXf             = "UseXf"
-	BusinessConfXfAppid           = "XfAppid"
-	BusinessConfXfApiKey          = "XfApiKey"
-	BusinessConfXfApiSecret       = "XfApiSecret"
-	BusinessConfXfVcn             = "XfVcn"
-	BusinessConfEnPptCoverImgs    = "EnPptCoverImgs"
-	BusinessConfIsReportApprove   = "IsReportApprove"
-	BusinessConfReportApproveType = "ReportApproveType"
-	BusinessConfCompanyName       = "CompanyName"
-	BusinessConfCompanyWatermark  = "CompanyWatermark"
-	BusinessConfWatermarkChart    = "WatermarkChart"
+	BusinessConfUseXf                     = "UseXf"
+	BusinessConfXfAppid                   = "XfAppid"
+	BusinessConfXfApiKey                  = "XfApiKey"
+	BusinessConfXfApiSecret               = "XfApiSecret"
+	BusinessConfXfVcn                     = "XfVcn"
+	BusinessConfEnPptCoverImgs            = "EnPptCoverImgs"
+	BusinessConfIsReportApprove           = "IsReportApprove"
+	BusinessConfReportApproveType         = "ReportApproveType"
+	BusinessConfCompanyName               = "CompanyName"
+	BusinessConfCompanyWatermark          = "CompanyWatermark"
+	BusinessConfWatermarkChart            = "WatermarkChart"
+	BusinessConfLoginSmsTpId              = "LoginSmsTpId"
+	BusinessConfLoginSmsGjTpId            = "LoginSmsGjTpId"
+	BusinessConfSmsJhgnAppKey             = "SmsJhgnAppKey"
+	BusinessConfSmsJhgjAppKey             = "SmsJhgjAppKey"
+	BusinessConfLdapHost                  = "LdapHost"
+	BusinessConfLdapBase                  = "LdapBase"
+	BusinessConfLdapPort                  = "LdapPort"
+	BusinessConfEmailClient               = "EmailClient"
+	BusinessConfEmailServerHost           = "EmailServerHost"
+	BusinessConfEmailServerPort           = "EmailServerPort"
+	BusinessConfEmailSender               = "EmailSender"
+	BusinessConfEmailSenderUserName       = "EmailSenderUserName"
+	BusinessConfEmailSenderPassword       = "EmailSenderPassword"
+	BusinessConfSmsClient                 = "SmsClient"
+	BusinessConfNanHuaSmsAppKey           = "NanHuaSmsAppKey"
+	BusinessConfNanHuaSmsAppSecret        = "NanHuaSmsAppSecret"
+	BusinessConfNanHuaSmsApiHost          = "NanHuaSmsApiHost"
+	BusinessConfLoginSmsTplContent        = "LoginSmsTplContent"
+	BusinessConfLoginEmailTemplateSubject = "LoginEmailTemplateSubject"
+	BusinessConfLoginEmailTemplateContent = "LoginEmailTemplateContent"
+	BusinessConfLdapBindUserSuffix        = "LdapBindUserSuffix"
+	BusinessConfLdapUserFilter            = "LdapUserFilter"
 )
 
 const (
 	BusinessConfReportApproveTypeEta   = "eta"
 	BusinessConfReportApproveTypeOther = "other"
+	BusinessConfClientFlagNanHua       = "nhqh" // 南华标记
+	BusinessConfEmailClientSmtp        = "smtp" // 普通邮箱标记
 )
 
 // BusinessConf 商户配置表

+ 2 - 1
models/data_manage/base_from_yongyi_classify.go

@@ -67,6 +67,7 @@ type BaseFromYongyiClassifyItems struct {
 	ParentId        int    `description:"父级id"`
 	Level           int    `description:"层级"`
 	Sort            int    `description:"排序字段,越小越靠前,默认值:10"`
+	Children        []*BaseFromYongyiClassifyItems
 }
 
 type BaseFromYongyiClassifyNameItems struct {
@@ -101,7 +102,7 @@ func GetBaseFromYongyiClassifyByParentId(parentId int) (items []*BaseFromYongyiC
 // GetAllBaseFromYongyiClassify 获取所有的分类列表数据
 func GetAllBaseFromYongyiClassify() (items []*BaseFromYongyiClassifyItems, err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := ` SELECT * FROM base_from_yongyi_classify order by sort asc,classify_id asc`
+	sql := ` SELECT * FROM base_from_yongyi_classify order by parent_id asc, sort asc,classify_id asc`
 	_, err = o.Raw(sql).QueryRows(&items)
 	return
 }

+ 8 - 0
models/data_manage/chart_classify.go

@@ -317,3 +317,11 @@ func GetCrossVarietyChartClassifyBySysUserId(sysUserId int) (item *ChartClassify
 	err = o.Raw(sql, utils.CHART_SOURCE_CROSS_HEDGING, sysUserId).QueryRow(&item)
 	return
 }
+
+// GetChartClassifyByParentIdAndName 根据父级ID和名称获取分类
+func GetChartClassifyByParentIdAndName(parentId int, classifyName string, classifyId int) (item *ChartClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_classify WHERE parent_id = ? AND chart_classify_name = ? AND chart_classify_id <> ? LIMIT 1`
+	err = o.Raw(sql, parentId, classifyName, classifyId).QueryRow(&item)
+	return
+}

+ 43 - 3
models/data_manage/chart_info.go

@@ -32,10 +32,15 @@ type ChartInfo struct {
 	SeasonEndDate     string `description:"季节性图开始日期"`
 	ChartImage        string `description:"图表图片"`
 	Sort              int    `description:"排序字段,数字越小越排前面"`
+	XMin              string `description:"图表X轴最小值"`
+	XMax              string `description:"图表X轴最大值"`
 	LeftMin           string `description:"图表左侧最小值"`
 	LeftMax           string `description:"图表左侧最大值"`
 	RightMin          string `description:"图表右侧最小值"`
 	RightMax          string `description:"图表右侧最大值"`
+	Right2Min         string `description:"图表右侧2最小值"`
+	Right2Max         string `description:"图表右侧2最大值"`
+	MinMaxSave        int    `description:"是否手动保存过上下限:0-否;1-是"`
 	Disabled          int    `description:"是否禁用,0:启用,1:禁用,默认:0"`
 	BarConfig         string `description:"柱方图的配置,json数据"`
 	Source            int    `description:"1:ETA图库;2:商品价格曲线"`
@@ -158,6 +163,9 @@ type SaveChartInfoReq struct {
 	LeftMax          string           `description:"图表左侧最大值"`
 	RightMin         string           `description:"图表右侧最小值"`
 	RightMax         string           `description:"图表右侧最大值"`
+	Right2Min        string           `description:"图表右侧最小值"`
+	Right2Max        string           `description:"图表右侧最大值"`
+	MinMaxSave       int              `description:"是否手动保存过上下限:0-否;1-是"`
 	ExtraConfig      string           `description:"图表额外配置,json数据"`
 	StartYear        int              `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
 }
@@ -232,6 +240,9 @@ type EditChartInfoReq struct {
 	LeftMax              string                  `description:"图表左侧最大值"`
 	RightMin             string                  `description:"图表右侧最小值"`
 	RightMax             string                  `description:"图表右侧最大值"`
+	Right2Min            string                  `description:"图表右侧最小值"`
+	Right2Max            string                  `description:"图表右侧最大值"`
+	MinMaxSave           int                     `description:"是否手动保存过上下限:0-否;1-是"`
 	BarChartInfo         BarChartInfoReq         `description:"柱方图的配置"`
 	CorrelationChartInfo CorrelationChartInfoReq `description:"相关性图表配置"`
 	ExtraConfig          string                  `description:"图表额外配置信息,json字符串"`
@@ -572,9 +583,12 @@ func ModifyChartInfoAndMapping(edbInfoIdStr string, req *SaveChartInfoReq, chart
  			  left_min=?,
 			  left_max=?,
 			  right_min=?,
-			  right_max=?, 
+			  right_max=?,
+			  right2_min=?,
+			  right2_max=?,
+			  min_max_save=?,
               start_year=?`
-		pars := []interface{}{edbInfoIdStr, req.DateType, req.StartDate, req.EndDate, req.LeftMin, req.LeftMax, req.RightMin, req.RightMax, req.StartYear}
+		pars := []interface{}{edbInfoIdStr, req.DateType, req.StartDate, req.EndDate, req.LeftMin, req.LeftMax, req.RightMin, req.RightMax, req.Right2Min, req.Right2Max, req.MinMaxSave, req.StartYear}
 		if req.ExtraConfig != `` {
 			updateStr += `,extra_config=? `
 			pars = append(pars, req.ExtraConfig)
@@ -602,9 +616,12 @@ func ModifyChartInfoAndMapping(edbInfoIdStr string, req *SaveChartInfoReq, chart
 			  left_max=?,
 			  right_min=?,
 			  right_max=?,
+			  right2_min=?,
+			  right2_max=?,
+			  min_max_save=?,
 			  start_year=?
 			WHERE chart_info_id = ?`
-		_, err = to.Raw(sql, edbInfoIdStr, req.Calendar, req.DateType, req.StartDate, req.EndDate, req.StartDate, req.EndDate, req.LeftMin, req.LeftMax, req.RightMin, req.RightMax, req.StartYear, req.ChartInfoId).Exec()
+		_, err = to.Raw(sql, edbInfoIdStr, req.Calendar, req.DateType, req.StartDate, req.EndDate, req.StartDate, req.EndDate, req.LeftMin, req.LeftMax, req.RightMin, req.RightMax, req.Right2Min, req.Right2Max, req.MinMaxSave, req.StartYear, req.ChartInfoId).Exec()
 		if err != nil {
 			fmt.Println("UPDATE  chart_info Err:", err.Error())
 			return err
@@ -771,6 +788,15 @@ func EditChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr string, calenda
 	sql += `,right_max = ? `
 	pars = append(pars, req.RightMax)
 
+	sql += `,right2_min = ? `
+	pars = append(pars, req.Right2Min)
+
+	sql += `,right2_max = ? `
+	pars = append(pars, req.Right2Max)
+
+	sql += `,min_max_save = ? `
+	pars = append(pars, req.MinMaxSave)
+
 	sql += `WHERE chart_info_id = ?`
 
 	pars = append(pars, req.ChartInfoId)
@@ -928,6 +954,12 @@ func EditFutureGoodChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr strin
 	sql += `,right_max = ? `
 	pars = append(pars, req.RightMax)
 
+	sql += `,right2_min = ? `
+	pars = append(pars, req.Right2Min)
+
+	sql += `,right2_max = ? `
+	pars = append(pars, req.Right2Max)
+
 	sql += `WHERE chart_info_id = ?`
 
 	pars = append(pars, req.ChartInfoId)
@@ -1087,6 +1119,9 @@ type AddChartInfoReq struct {
 	LeftMax              string                  `description:"图表左侧最大值"`
 	RightMin             string                  `description:"图表右侧最小值"`
 	RightMax             string                  `description:"图表右侧最大值"`
+	Right2Min            string                  `description:"图表右侧2最小值"`
+	Right2Max            string                  `description:"图表右侧2最大值"`
+	MinMaxSave           int                     `description:"是否手动保存过上下限:0-否;1-是"`
 	BarChartInfo         BarChartInfoReq         `description:"柱方图的配置"`
 	CorrelationChartInfo CorrelationChartInfoReq `description:"相关性图表配置"`
 	ExtraConfig          string                  `description:"图表额外配置信息,json字符串"`
@@ -1389,10 +1424,15 @@ type ChartInfoView struct {
 	MyChartClassifyId string `description:"我的图表分类,多个用逗号隔开"`
 	ChartClassify     []*ChartClassifyView
 	EdbEndDate        string `description:"指标最新更新日期"`
+	XMin              string `description:"图表X轴最小值"`
+	XMax              string `description:"图表X轴最大值"`
 	LeftMin           string `description:"图表左侧最小值"`
 	LeftMax           string `description:"图表左侧最大值"`
 	RightMin          string `description:"图表右侧最小值"`
 	RightMax          string `description:"图表右侧最大值"`
+	Right2Min         string `description:"图表右侧最小值"`
+	Right2Max         string `description:"图表右侧最大值"`
+	MinMaxSave        int    `description:"是否手动保存过上下限:0-否;1-是"`
 	IsEdit            bool   `description:"是否有编辑权限"`
 	IsEnChart         bool   `description:"是否展示英文标识"`
 	WarnMsg           string `description:"错误信息"`

+ 30 - 23
models/data_manage/cross_variety/request/chart.go

@@ -9,28 +9,28 @@ type ChartConfigReq struct {
 	CalculateUnit  string            `description:"计算频度"`
 	DateConfigList []ChartConfigDate `description:"日期配置列表"`
 	VarietyList    []int             `description:"品种id列表"`
+	PercentType    int               `description:"百分位:0-数据区间(兼容历史数据); 1-数据个数;"`
 }
 
 // ChartConfigDate
 // @Description: 跨品种分析的日期配置
 type ChartConfigDate struct {
-	DateType int `description:"日期类型,,1:最新日期;2:N天前"`
-	Num      int
+	DateType int    `description:"日期类型:1-最新日期;2-N天前;3-固定日期"`
+	Num      int    `description:"N天前的N值"`
+	FixDate  string `description:"固定日期的日期"`
+	ShowTips int    `description:"是否显示标注:0-否;1-是"`
 }
 
 // AddChartReq
 // @Description: 添加图表的请求
 type AddChartReq struct {
-	ChartName      string            `description:"图表名称"`
-	LeftMin        string            `description:"图表左侧最小值"`
-	LeftMax        string            `description:"图表左侧最大值"`
-	ChartImage     string            `description:"图表截图,复制的时候才用到" json:"-"`
-	TagX           int               `description:"X轴的标签ID"`
-	TagY           int               `description:"Y轴的标签ID"`
-	CalculateValue int               `description:"计算窗口"`
-	CalculateUnit  string            `description:"计算频度"`
-	DateConfigList []ChartConfigDate `description:"日期配置列表"`
-	VarietyList    []int
+	ChartName      string `description:"图表名称"`
+	XMin           string `description:"图表X轴最小值"`
+	XMax           string `description:"图表X轴最大值"`
+	LeftMin        string `description:"图表左侧最小值"`
+	LeftMax        string `description:"图表左侧最大值"`
+	ChartImage     string `description:"图表截图,复制的时候才用到" json:"-"`
+	ChartConfigReq `description:"跨品种分析的图表配置"`
 
 	// 主题相关
 	ChartThemeId int    `description:"图表应用主题ID"`
@@ -43,17 +43,14 @@ type AddChartReq struct {
 // EditChartReq
 // @Description: 编辑图表的请求
 type EditChartReq struct {
-	ChartInfoId    int               `description:"图表id"`
-	ChartName      string            `description:"图表名称"`
-	LeftMin        string            `description:"图表左侧最小值"`
-	LeftMax        string            `description:"图表左侧最大值"`
-	ChartImage     string            `description:"图表截图,复制的时候才用到" json:"-"`
-	TagX           int               `description:"X轴的标签ID"`
-	TagY           int               `description:"Y轴的标签ID"`
-	CalculateValue int               `description:"计算窗口"`
-	CalculateUnit  string            `description:"计算频度"`
-	DateConfigList []ChartConfigDate `description:"日期配置列表"`
-	VarietyList    []int
+	ChartInfoId    int    `description:"图表id"`
+	ChartName      string `description:"图表名称"`
+	XMin           string `description:"图表X轴最小值"`
+	XMax           string `description:"图表X轴最大值"`
+	LeftMin        string `description:"图表左侧最小值"`
+	LeftMax        string `description:"图表左侧最大值"`
+	ChartImage     string `description:"图表截图,复制的时候才用到" json:"-"`
+	ChartConfigReq `description:"跨品种分析的图表配置"`
 }
 
 // CopyAddChartInfoReq
@@ -85,3 +82,13 @@ type VarietyNameEnReq struct {
 	ChartVarietyId int    `json:"ChartVarietyId"`
 	VarietyNameEn  string `json:"VarietyNameEn"`
 }
+
+// SaveChartReq
+// @Description: 保存图表的请求
+type SaveChartReq struct {
+	ChartInfoId int    `description:"图表ID"`
+	XMin        string `description:"图表X轴最小值"`
+	XMax        string `description:"图表X轴最大值"`
+	LeftMin     string `description:"图表左侧最小值"`
+	LeftMax     string `description:"图表左侧最大值"`
+}

+ 8 - 0
models/data_manage/edb_classify.go

@@ -544,3 +544,11 @@ where classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `)`
 	}
 	return
 }
+
+// GetEdbClassifyByParentIdAndName 根据父级ID和名称获取分类
+func GetEdbClassifyByParentIdAndName(parentId int, classifyName string, classifyId int) (item *EdbClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM edb_classify WHERE parent_id = ? AND classify_name = ? AND classify_id <> ? LIMIT 1`
+	err = o.Raw(sql, parentId, classifyName, classifyId).QueryRow(&item)
+	return
+}

+ 8 - 0
models/data_manage/edb_info.go

@@ -1838,3 +1838,11 @@ func ModifyEdbInfoUpdateStatus(edbIdList []int, isStop int) (err error) {
 
 	return
 }
+
+func GetEdbInfoCount(source int, edbCode string) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count FROM edb_info 
+           WHERE source=? AND edb_code = ?`
+	err = o.Raw(sql, source, edbCode).QueryRow(&count)
+	return
+}

+ 6 - 0
models/data_manage/excel/excel_classify.go

@@ -70,6 +70,12 @@ func GetExcelClassifyBySource(source int) (items []*ExcelClassifyItems, err erro
 	return
 }
 
+func GetExcelClassifyBySourceOrderByLevel(source int) (items []*ExcelClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_classify WHERE  source = ? AND is_delete=0 order by level asc, sort asc,excel_classify_id asc`
+	_, err = o.Raw(sql, source).QueryRows(&items)
+	return
+}
 func GetExcelClassifyAll() (items []*ExcelClassifyItems, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT * FROM excel_classify WHERE parent_id<>0 AND is_delete=0 order by sort asc,excel_classify_id asc`

+ 1 - 0
models/data_manage/line_feature/request/line_feature.go

@@ -35,6 +35,7 @@ type StandardDeviation struct {
 type Percentile struct {
 	CalculateValue int    `description:"时间长度期数"`
 	CalculateUnit  string `description:"时间长度频度"`
+	PercentType    int    `description:"百分位:0-数据区间(兼容历史数据); 1-数据个数;"`
 }
 
 type FrequencyDistribution struct {

+ 2 - 0
models/data_manage/multiple_graph_config.go

@@ -56,6 +56,8 @@ type CurveConfig struct {
 	LeftMax     float64 `description:"图表左侧最大值"`
 	RightMin    float64 `description:"图表右侧最小值"`
 	RightMax    float64 `description:"图表右侧最大值"`
+	Right2Min   float64 `description:"图表右侧最小值"`
+	Right2Max   float64 `description:"图表右侧最大值"`
 	IsOrder     bool    `description:"true:正序,false:逆序"`
 	EdbInfoType bool    `description:"true:标准指标,false:领先指标"`
 	LeadValue   int     `description:"领先值"`

+ 1 - 1
models/data_manage/request/meeting_probabilities.go

@@ -22,7 +22,7 @@ type ISheet struct {
 	ChWidth                  int              `json:"ch_width"`
 	RhHeight                 int              `json:"rh_height"`
 	LuckySheetSelectionRange []int            `json:"luckysheet_selection_range"`
-	ZoomRatio                int              `json:"zoomRatio"`
+	ZoomRatio                float64          `json:"zoomRatio"`
 	CellData                 []ISheetCellData `json:"celldata"`
 }
 type ISheetData struct {

+ 1 - 1
models/db.go

@@ -64,7 +64,7 @@ func init() {
 	gl.SetConnMaxLifetime(10 * time.Minute)
 
 	// 用户主库
-	if utils.MYSQL_WEEKLY_URL != `` && (utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox) {
+	if utils.MYSQL_WEEKLY_URL != `` && (utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox || utils.BusinessCode == utils.BusinessCodeDebug) {
 		_ = orm.RegisterDataBase("weekly", "mysql", utils.MYSQL_WEEKLY_URL)
 		orm.SetMaxIdleConns("weekly", 50)
 		orm.SetMaxOpenConns("weekly", 100)

+ 2 - 2
models/sandbox/sandbox_classify.go

@@ -123,9 +123,9 @@ func GetSandboxInfoCountByClassifyId(classifyId int) (count int, err error) {
 					 (SELECT @pid := ?) pd 
 				WHERE FIND_IN_SET(parent_id, @pid) > 0 
 				  AND @pid := CONCAT(@pid, ',', sandbox_classify_id) 
-				UNION SELECT * FROM sandbox_classify WHERE sandbox_classify_id = @pid
+				UNION SELECT * FROM sandbox_classify WHERE sandbox_classify_id = @pid 
 				)AS t
-				) `
+				) AND a.is_delete = 0 `
 	err = o.Raw(sql, classifyId).QueryRow(&count)
 	return
 }

+ 1 - 0
models/system/admin_verify_code_record.go

@@ -30,6 +30,7 @@ type AdminVerifyCodeRecord struct {
 	ExpiredTime time.Time `description:"验证码过期时间"`
 	SendResult  string    `description:"发送结果"`
 	SendStatus  int       `description:"发送状态:0-待发送;1-已发送;2-发送失败"`
+	RequestId   string    `description:"请求ID"`
 	CreateTime  time.Time `description:"创建时间"`
 	ModifyTime  time.Time `description:"更新时间"`
 }

+ 3 - 0
models/system/sys_admin.go

@@ -41,6 +41,7 @@ type AdminItem struct {
 	CityCode                string `description:"市编码"`
 	EmployeeId              string `description:"员工工号(钉钉/每刻报销)"`
 	TelAreaCode             string `description:"手机区号"`
+	IsLdap                  int    `description:"是否为域用户:0-系统账户;1-域用户"`
 }
 
 func GetSysuserList(condition string, pars []interface{}, startSize, pageSize int) (items []*AdminItem, err error) {
@@ -117,6 +118,7 @@ type SysuserAddReq struct {
 	EmployeeId       string `description:"员工工号(钉钉/每刻报销)"`
 	Email            string `description:"邮箱"`
 	TelAreaCode      string `description:"手机区号"`
+	IsLdap           int    `description:"是否为域用户:0-系统账户;1-域用户"`
 }
 
 func GetSysAdminCount(adminName string) (count int, err error) {
@@ -163,6 +165,7 @@ type SysuserEditReq struct {
 	EmployeeId       string `description:"员工工号(钉钉/每刻报销)"`
 	Email            string `description:"邮箱"`
 	TelAreaCode      string `description:"手机区号"`
+	IsLdap           int    `description:"是否为域用户:0-系统账户;1-域用户"`
 }
 
 type SysUserMoveReq struct {

+ 1 - 0
models/system/sys_user.go

@@ -63,6 +63,7 @@ type Admin struct {
 	CityCode                  string    `description:"市编码"`
 	EmployeeId                string    `description:"员工工号(钉钉/每刻报销)"`
 	TelAreaCode               string    `description:"手机区号"`
+	IsLdap                    int       `description:"是否为域用户:0-系统账户;1-域用户"`
 }
 
 // Update 更新用户基础信息

+ 18 - 0
routers/commentsRouter.go

@@ -340,6 +340,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "Save",
+            Router: `/chart_info/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
         beego.ControllerComments{
             Method: "SearchByEs",
@@ -8494,6 +8503,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:UserLoginController"],
+        beego.ControllerComments{
+            Method: "CheckUserLdap",
+            Router: `/ldap/user_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:UserLoginController"],
         beego.ControllerComments{
             Method: "Login",

+ 2 - 1
services/data/base_edb_lib.go

@@ -13,10 +13,11 @@ import (
 )
 
 // AddEdbData 新增指标数据
-func AddEdbData(source int, edbCode string) (resp *models.BaseResponse, err error) {
+func AddEdbData(source int, edbCode, frequency string) (resp *models.BaseResponse, err error) {
 	param := make(map[string]interface{})
 	param["EdbCode"] = edbCode
 	param["Source"] = source
+	param["Frequency"] = frequency
 	urlStr := ``
 	switch source {
 	case utils.DATA_SOURCE_THS:

+ 3 - 0
services/data/chart_info.go

@@ -2156,6 +2156,9 @@ func AddChartInfo(req data_manage.AddChartInfoReq, sysUserId int, sysUserRealNam
 	chartInfo.LeftMax = req.LeftMax
 	chartInfo.RightMin = req.RightMin
 	chartInfo.RightMax = req.RightMax
+	chartInfo.Right2Min = req.Right2Min
+	chartInfo.Right2Max = req.Right2Max
+	chartInfo.MinMaxSave = req.MinMaxSave
 	chartInfo.Disabled = disableVal
 	chartInfo.BarConfig = barChartConf
 	chartInfo.ExtraConfig = extraConfig

+ 141 - 37
services/data/cross_variety/chart.go

@@ -66,6 +66,9 @@ type CoordinatePoint struct {
 	YEdbInfoId int
 	XDate      string
 	YDate      string
+	DateType   int `description:"日期类型:1-最新日期;2-N天前;3-固定日期"`
+	DaysAgo    int `description:"N天前的N值"`
+	ShowTips   int `description:"是否显示标注:0-否;1-是"`
 }
 
 // GetChartData
@@ -161,7 +164,14 @@ func GetChartData(chartInfoId int, config request.ChartConfigReq) (edbList []*da
 
 	dataMap := make(map[string]float64)
 	dateMap := make(map[string]string)
+	dateTypeMap := make(map[int]int) // 日期配置key对应的日期类型
+	daysAgoMap := make(map[int]int)  // 日期配置key对应的N天前的N值
+	showTipsMap := make(map[int]int) // 日期配置key对应点是否显示标注
+
 	for dateIndex, dateConfig := range config.DateConfigList {
+		dateTypeMap[dateIndex] = dateConfig.DateType
+		daysAgoMap[dateIndex] = dateConfig.Num
+		showTipsMap[dateIndex] = dateConfig.ShowTips
 		for _, edbInfoMapping := range mappingList {
 			// 数据会是正序的
 			dataList, ok := edbDataListMap[edbInfoMapping.EdbInfoId]
@@ -193,8 +203,8 @@ func GetChartData(chartInfoId int, config request.ChartConfigReq) (edbList []*da
 				endDateStr = dataEndDateStr
 				endDate = dataEndDate
 				currVal = dataList[k].Value
-			case 2: // 2:N天前
-				tmpEndDate := dataEndDate.AddDate(0, 0, -dateConfig.Num)
+			case 2: // 2:N天前(原为指标最新日期的N天前, 现为系统日期的N天)
+				tmpEndDate := time.Now().AddDate(0, 0, -dateConfig.Num)
 				tmpEndDateStr := tmpEndDate.Format(utils.FormatDate)
 
 				for i := k; i >= 0; i-- {
@@ -222,6 +232,45 @@ func GetChartData(chartInfoId int, config request.ChartConfigReq) (edbList []*da
 					currVal = dataList[i].Value
 					break
 				}
+			case 3: // 固定日期
+				if dateConfig.FixDate == "" {
+					errMsg = "固定日期不可为空"
+					err = fmt.Errorf("固定日期为空")
+					return
+				}
+				strFixDate := dateConfig.FixDate
+				fixDate, e := time.ParseInLocation(utils.FormatDate, strFixDate, time.Local)
+				if e != nil {
+					errMsg = "固定日期格式有误"
+					err = fmt.Errorf("固定日期有误, FixDate: %s", dateConfig.FixDate)
+					return
+				}
+
+				for i := k; i >= 0; i-- {
+					strThisDate := dataList[i].DataTime
+					// 如果正好是这一天,那么就直接break了
+					if strFixDate == strThisDate {
+						k = i
+						endDateStr = strThisDate
+						endDate = fixDate
+						currVal = dataList[i].Value
+						break
+					}
+					// 若固定日期无值, 则取固定日期之前, 能找到的第一个值(同上N天前的逻辑)
+					thisDate, e := time.ParseInLocation(utils.FormatDate, strThisDate, time.Local)
+					if e != nil {
+						err = fmt.Errorf("数据日期格式有误: %s", e.Error())
+						return
+					}
+					if thisDate.After(fixDate) {
+						continue
+					}
+					k = i
+					endDateStr = strThisDate
+					endDate = thisDate
+					currVal = dataList[i].Value
+					break
+				}
 			}
 
 			// 没有找到日期,那么就不处理
@@ -233,51 +282,85 @@ func GetChartData(chartInfoId int, config request.ChartConfigReq) (edbList []*da
 			earliestDate := endDate.AddDate(0, 0, -config.CalculateValue*moveUnitDays)
 			earliestDateStr := earliestDate.Format(utils.FormatDate)
 
-			var minVal, maxVal float64
-			var isNotFirst bool // 是否是第一条数据
-			for i := k; i >= 0; i-- {
-				tmpData := dataList[i]
-				if !isNotFirst {
-					maxVal = tmpData.Value
-					minVal = tmpData.Value
-					isNotFirst = true
-					continue
-				}
+			var percentVal float64 // 百分位计算值
+			// 百分位数据区间算法
+			if config.PercentType == utils.PercentCalculateTypeRange {
+				var minVal, maxVal float64
+				var isNotFirst bool // 是否是第一条数据
+				for i := k; i >= 0; i-- {
+					tmpData := dataList[i]
+					if !isNotFirst {
+						maxVal = tmpData.Value
+						minVal = tmpData.Value
+						isNotFirst = true
+						continue
+					}
 
-				tmpDateStr := dataList[i].DataTime
-				// 如果正好是这一天,那么就直接break了
-				if earliestDateStr == tmpDateStr {
-					break
-				}
-				tmpDate, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDateStr, time.Local)
-				if tmpErr != nil {
-					err = tmpErr
-					return
+					tmpDateStr := dataList[i].DataTime
+					// 如果正好是这一天,那么就直接break了
+					if earliestDateStr == tmpDateStr {
+						break
+					}
+					tmpDate, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDateStr, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					// 如果这期的日期早于选择的日期,那么继续停止遍历
+					if tmpDate.Before(earliestDate) {
+						continue
+					}
+
+					if tmpData.Value > maxVal {
+						maxVal = tmpData.Value
+					}
+					if tmpData.Value < minVal {
+						minVal = tmpData.Value
+					}
 				}
-				// 如果这期的日期早于选择的日期,那么继续停止遍历
-				if tmpDate.Before(earliestDate) {
+
+				// 最大值等于最小值,说明计算结果无效
+				if maxVal == minVal {
 					continue
 				}
+				// 数据区间百分位=(现值-Min)/(Max-Min)
+				tmpV := (currVal - minVal) / (maxVal - minVal) * 100
+				percentVal, _ = decimal.NewFromFloat(tmpV).Round(4).Float64()
+			}
 
-				if tmpData.Value > maxVal {
-					maxVal = tmpData.Value
+			// 百分位数据个数算法
+			// 数据区间第一个和最后一个数据点的时间和数据分别为(T1,S1)(T2,S2); N=T1到T2指标数据个数, n=小于等于S2的数据个数
+			// 个数百分位=(n-1)/(N-1)
+			if config.PercentType == utils.PercentCalculateTypeNum {
+				// T1为earliestDate, T2为endDate, S2为currVal
+				var tinyN, bigN int
+				lastVal := decimal.NewFromFloat(currVal)
+				for i := k; i >= 0; i-- {
+					date, e := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
+					if e != nil {
+						err = fmt.Errorf("数据日期格式有误: %s", e.Error())
+						return
+					}
+					if !date.Before(earliestDate) && !date.After(endDate) {
+						bigN += 1
+						dateVal := decimal.NewFromFloat(dataList[i].Value)
+						if dateVal.LessThanOrEqual(lastVal) {
+							tinyN += 1
+						}
+					}
 				}
-				if tmpData.Value < minVal {
-					minVal = tmpData.Value
+				// N=1时说明计算无效
+				if bigN == 1 {
+					continue
 				}
+				numerator := decimal.NewFromInt(int64(tinyN - 1))
+				denominator := decimal.NewFromInt(int64(bigN - 1))
+				percentVal, _ = numerator.Div(denominator).Mul(decimal.NewFromFloat(100)).Round(4).Float64()
 			}
 
-			// 最大值等于最小值,说明计算结果无效
-			if maxVal == minVal {
-				continue
-			}
-			//百分位=(现值-Min)/(Max-Min)
-			tmpV := (currVal - minVal) / (maxVal - minVal) * 100
-			tmpV, _ = decimal.NewFromFloat(tmpV).Round(4).Float64()
-
 			// key的生成(日期配置下标+指标id)
 			key := fmt.Sprint(dateIndex, "_", edbInfoMapping.EdbInfoId)
-			dataMap[key] = tmpV
+			dataMap[key] = percentVal
 			dateMap[key] = endDateStr
 		}
 	}
@@ -341,6 +424,9 @@ func GetChartData(chartInfoId int, config request.ChartConfigReq) (edbList []*da
 				YEdbInfoId: yEdbInfoId,
 				XDate:      dateMap[key1],
 				YDate:      dateMap[key2],
+				DateType:   dateTypeMap[dateIndex], // 日期类型
+				DaysAgo:    daysAgoMap[dateIndex],  // N天前的N值
+				ShowTips:   showTipsMap[dateIndex], // 是否显示标注
 			})
 		}
 
@@ -512,6 +598,12 @@ func AddChartInfo(req request.AddChartReq, sysUser *system.Admin) (chartInfo *da
 		isSendEmail = false
 		return
 	}
+	if req.PercentType != utils.PercentCalculateTypeRange && req.PercentType != utils.PercentCalculateTypeNum {
+		errMsg = "请选择百分位"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
 	// 品种配置
 	if len(req.VarietyList) < 0 {
 		errMsg = "请选择品种!"
@@ -610,6 +702,8 @@ func AddChartInfo(req request.AddChartReq, sysUser *system.Admin) (chartInfo *da
 	//chartInfo.SeasonEndDate = req.EndDate
 	chartInfo.LeftMin = req.LeftMin
 	chartInfo.LeftMax = req.LeftMax
+	chartInfo.XMin = req.XMin
+	chartInfo.XMax = req.XMax
 	//chartInfo.RightMin = req.RightMin
 	//chartInfo.RightMax = req.RightMax
 	//chartInfo.Disabled = disableVal
@@ -724,6 +818,12 @@ func EditChartInfo(req request.EditChartReq, sysUser *system.Admin) (chartItem *
 		isSendEmail = false
 		return
 	}
+	if req.PercentType != utils.PercentCalculateTypeRange && req.PercentType != utils.PercentCalculateTypeNum {
+		errMsg = "请选择百分位"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
 	// 品种配置
 	if len(req.VarietyList) < 0 {
 		errMsg = "请选择品种!"
@@ -783,7 +883,11 @@ func EditChartInfo(req request.EditChartReq, sysUser *system.Admin) (chartItem *
 	chartItem.ChartName = req.ChartName
 	chartItem.ExtraConfig = string(extraConfigByte)
 	chartItem.ModifyTime = time.Now()
-	chartUpdateCols := []string{"ChartName", "ExtraConfig", "ModifyTime"}
+	chartItem.LeftMin = req.LeftMin
+	chartItem.LeftMax = req.LeftMax
+	chartItem.XMin = req.XMin
+	chartItem.XMax = req.XMax
+	chartUpdateCols := []string{"ChartName", "ExtraConfig", "ModifyTime", "LeftMin", "LeftMax", "XMin", "XMax"}
 
 	// 跨品种分析配置
 	chartInfoCrossVariety, err := cross_varietyModel.GetChartInfoCrossVarietyByChartInfoId(chartItem.ChartInfoId)

+ 12 - 0
services/data/edb_classify.go

@@ -797,6 +797,18 @@ func MoveEdbClassify(req data_manage.MoveEdbClassifyReq, sysUser *system.Admin,
 			err = errors.New(errMsg)
 			return
 		}
+		// 如果是移动目录, 那么校验一下父级目录下是否有重名目录
+		exists, e := data_manage.GetEdbClassifyByParentIdAndName(parentClassifyId, edbClassifyInfo.ClassifyName, classifyId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取父级分类下的同名分类失败, Err: %s", e.Error())
+			return
+		}
+		if exists != nil {
+			errMsg = "移动失败,分类名称已存在"
+			return
+		}
+
 		// 权限校验
 		{
 			if edbClassifyInfo.ClassifyType == 0 { // 普通指标

+ 1 - 1
services/data/excel/custom_analysis.go

@@ -405,7 +405,7 @@ type LuckySheet struct {
 	Config           interface{}
 	Index            string               `json:"index"`
 	Order            int                  `json:"order"`
-	ZoomRatio        int                  `json:"zoomRatio"`
+	ZoomRatio        float64              `json:"zoomRatio"`
 	ShowGridLines    string               `json:"showGridLines"`
 	DefaultColWidth  int                  `json:"defaultColWidth"`
 	DefaultRowHeight int                  `json:"defaultRowHeight"`

+ 46 - 0
services/data/excel/excel_classify.go

@@ -427,3 +427,49 @@ func moveExcelClassify(parentExcelClassifyInfo, excelClassifyInfo, prevClassify,
 	}
 	return
 }
+
+func GetChildClassifyByClassifyId(targetClassifyId int, source int) (targetList []*excel.ExcelClassifyItems, err error, errMsg string) {
+	//判断是否是挂在顶级目录下
+	targetClassify, err := excel.GetExcelClassifyById(targetClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "当前分类不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		errMsg = "获取失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	tmpList, err := excel.GetExcelClassifyBySourceOrderByLevel(source)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		err = errors.New("获取数据失败,Err:" + err.Error())
+		return
+	}
+	idMap := make(map[int]struct{})
+	if len(tmpList) > 0 {
+		for _, v := range tmpList {
+			if v.ExcelClassifyId == targetClassify.ExcelClassifyId {
+				idMap[v.ExcelClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ParentId]; ok {
+				idMap[v.ExcelClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ExcelClassifyId]; ok {
+				targetItem := new(excel.ExcelClassifyItems)
+				targetItem.ExcelClassifyId = v.ExcelClassifyId
+				targetItem.ParentId = v.ParentId
+				targetItem.UniqueCode = v.UniqueCode
+				targetItem.Level = v.Level
+				targetList = append(targetList, targetItem)
+			}
+		}
+	}
+
+	return
+}

+ 81 - 31
services/data/line_feature/chart_info.go

@@ -82,7 +82,7 @@ func GetStandardDeviationData(chartInfoId int, startDate, endDate string, mappin
 }
 
 // GetPercentileData 获取百分位图表的指标数据
-func GetPercentileData(chartInfoId int, startDate, endDate string, mappingInfo *data_manage.ChartEdbInfoMapping, calculateValue int, calculateUnit string) (edbList []*data_manage.ChartEdbInfoMapping, dataResp response.LineFeatureDataResp, err error, errMsg string) {
+func GetPercentileData(chartInfoId int, startDate, endDate string, mappingInfo *data_manage.ChartEdbInfoMapping, calculateValue int, calculateUnit string, percentType int) (edbList []*data_manage.ChartEdbInfoMapping, dataResp response.LineFeatureDataResp, err error, errMsg string) {
 	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
 	moveUnitDays, ok := utils.FrequencyDaysMap[calculateUnit]
 	if !ok {
@@ -115,41 +115,91 @@ func GetPercentileData(chartInfoId int, startDate, endDate string, mappingInfo *
 	}
 
 	//百分位:对所选指标滚动地取对应时间长度的数据值,取最大值Max,最小值Min,计算Max-Min,百分位=(现值-Min)/(Max-Min),Max=Min时不予计算。
-	for i, tmpData := range dataList {
-		currDateTime, _ := time.ParseInLocation(utils.FormatDate, tmpData.DataTime, time.Local)
-		maxVal := tmpData.Value
-		minVal := tmpData.Value
-		for k := 0; k < calculateDay; k++ {
-			preVal, ok2 := dataMap[currDateTime.AddDate(0, 0, -k)]
-			if ok2 {
-				if preVal > maxVal {
-					maxVal = preVal
-				}
-				if preVal < minVal {
-					minVal = preVal
+	if percentType == utils.PercentCalculateTypeRange {
+		for i, tmpData := range dataList {
+			currDateTime, _ := time.ParseInLocation(utils.FormatDate, tmpData.DataTime, time.Local)
+			maxVal := tmpData.Value
+			minVal := tmpData.Value
+			for k := 0; k < calculateDay; k++ {
+				preVal, ok2 := dataMap[currDateTime.AddDate(0, 0, -k)]
+				if ok2 {
+					if preVal > maxVal {
+						maxVal = preVal
+					}
+					if preVal < minVal {
+						minVal = preVal
+					}
 				}
 			}
-		}
 
-		if maxVal == minVal {
-			continue
-		}
-		//百分位=(现值-Min)/(Max-Min)
-		tmpV := (tmpData.Value - minVal) / (maxVal - minVal) * 100
-		tmpV, _ = decimal.NewFromFloat(tmpV).Round(4).Float64()
-		newDataList = append(newDataList, data_manage.EdbDataList{
-			EdbDataId:     i,
-			EdbInfoId:     edb.EdbInfoId,
-			DataTime:      dataList[i].DataTime,
-			DataTimestamp: dataList[i].DataTimestamp,
-			Value:         tmpV,
-		})
+			if maxVal == minVal {
+				continue
+			}
+			//百分位=(现值-Min)/(Max-Min)
+			tmpV := (tmpData.Value - minVal) / (maxVal - minVal) * 100
+			tmpV, _ = decimal.NewFromFloat(tmpV).Round(4).Float64()
+			newDataList = append(newDataList, data_manage.EdbDataList{
+				EdbDataId:     i,
+				EdbInfoId:     edb.EdbInfoId,
+				DataTime:      dataList[i].DataTime,
+				DataTimestamp: dataList[i].DataTimestamp,
+				Value:         tmpV,
+			})
 
-		if tmpV < edbMinVal {
-			edbMinVal = tmpV
+			if tmpV < edbMinVal {
+				edbMinVal = tmpV
+			}
+			if tmpV > edbMaxVal {
+				edbMaxVal = tmpV
+			}
 		}
-		if tmpV > edbMaxVal {
-			edbMaxVal = tmpV
+	}
+
+	// 百分位数据个数算法
+	// 数据区间第一个和最后一个数据点的时间和数据分别为(T1,S1)(T2,S2); N=T1到T2指标数据个数, n=小于等于S2的数据个数
+	// 个数百分位=(n-1)/(N-1)
+	if percentType == utils.PercentCalculateTypeNum {
+		for i, d := range dataList {
+			// T2为当前日期
+			s2 := decimal.NewFromFloat(d.Value)
+			t2, _ := time.ParseInLocation(utils.FormatDate, d.DataTime, time.Local)
+
+			// 计算N和n
+			var bigN, tinyN int
+			for k := 0; k < calculateDay; k++ {
+				preVal, preOk := dataMap[t2.AddDate(0, 0, -k)]
+				if !preOk {
+					continue
+				}
+				bigN += 1
+				if decimal.NewFromFloat(preVal).LessThanOrEqual(s2) {
+					tinyN += 1
+				}
+			}
+
+			// N=1时说明计算无效
+			if bigN == 1 {
+				continue
+			}
+			numerator := decimal.NewFromInt(int64(tinyN - 1))
+			denominator := decimal.NewFromInt(int64(bigN - 1))
+			// 因为是百分位所以这里是要*100, 跟之前的算法保持同步
+			percentVal, _ := numerator.Div(denominator).Mul(decimal.NewFromFloat(100)).Round(4).Float64()
+
+			// 写进数组并判断指标最大最小值
+			newDataList = append(newDataList, data_manage.EdbDataList{
+				EdbDataId:     i,
+				EdbInfoId:     edb.EdbInfoId,
+				DataTime:      dataList[i].DataTime,
+				DataTimestamp: dataList[i].DataTimestamp,
+				Value:         percentVal,
+			})
+			if percentVal < edbMinVal {
+				edbMinVal = percentVal
+			}
+			if percentVal > edbMaxVal {
+				edbMaxVal = percentVal
+			}
 		}
 	}
 

+ 62 - 0
services/email.go

@@ -1,9 +1,12 @@
 package services
 
 import (
+	"crypto/tls"
 	"eta/eta_api/models"
 	"eta/eta_api/utils"
 	"fmt"
+	"gopkg.in/gomail.v2"
+	"strconv"
 	"strings"
 )
 
@@ -223,3 +226,62 @@ func SendEmailToCompany() {
 		}
 	}
 }
+
+type SendEmailReq struct {
+	Title   string   `description:"标题"`
+	Content string   `description:"内容"`
+	ToUser  []string `description:"收信人邮箱"`
+}
+
+func SendEmail(req SendEmailReq) (success bool, err error) {
+	if req.Title == "" {
+		err = fmt.Errorf("邮件主题不可为空")
+		return
+	}
+	if req.Content == "" {
+		err = fmt.Errorf("邮件内容不可为空")
+		return
+	}
+	if len(req.ToUser) <= 0 {
+		err = fmt.Errorf("收信人不可为空")
+		return
+	}
+
+	// 邮箱配置
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
+	}
+	checkArr := []string{
+		models.BusinessConfEmailServerHost, models.BusinessConfEmailServerPort,
+		models.BusinessConfEmailSender, models.BusinessConfEmailSenderUserName,
+		models.BusinessConfEmailSenderPassword,
+	}
+	for _, v := range checkArr {
+		if confMap[v] == "" {
+			err = fmt.Errorf("%s配置有误", v)
+			return
+		}
+	}
+
+	port, _ := strconv.Atoi(confMap[models.BusinessConfEmailServerPort])
+	if port <= 0 {
+		port = 587 // 默认587端口
+	}
+	m := gomail.NewMessage()
+	m.SetHeader("From", confMap[models.BusinessConfEmailSender])
+	m.SetHeader("To", req.ToUser...)
+	m.SetHeader("Subject", req.Title)
+	m.SetBody("text/html", req.Content)
+	d := gomail.NewDialer(confMap[models.BusinessConfEmailServerHost], port, confMap[models.BusinessConfEmailSenderUserName], confMap[models.BusinessConfEmailSenderPassword])
+	// 解决x509报错的问题。证书不通过。跳过证书验证
+	config := &tls.Config{ServerName: confMap[models.BusinessConfEmailServerHost], InsecureSkipVerify: true}
+	d.TLSConfig = config
+	if e = d.DialAndSend(m); e != nil {
+		err = fmt.Errorf("邮件发送失败, Err: %s", e.Error())
+		return
+	}
+	success = true
+	return
+}

+ 4 - 0
services/minio.go

@@ -409,7 +409,11 @@ func (m *MinioOss) UploadFile(fileName, filePath, savePath string) (string, erro
 	}
 
 	ctx := context.Background()
+	// 此处兼容一下前后端endpoint不一致的情况, 前端用minio_endpoint后端用minio_back_endpoint, minio_back_endpoint为空则都取前者
 	endpoint := utils.MinIoEndpoint
+	if utils.MinIoBackEndpoint != "" {
+		endpoint = utils.MinIoBackEndpoint
+	}
 	accessKeyID := utils.MinIoAccessKeyId
 	secretAccessKey := utils.MinIoAccessKeySecret
 	useSSL := false

+ 131 - 0
services/nanhua_sms.go

@@ -0,0 +1,131 @@
+package services
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/utils"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strconv"
+	"strings"
+)
+
+// NanHuaSms 南华短信服务
+type NanHuaSms struct{}
+
+func (cli *NanHuaSms) SendUserLoginCode(req UserLoginSmsCodeReq) (result UserLoginSmsCodeResult, err error) {
+	// 短信内容
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
+	}
+	tpl := confMap[models.BusinessConfLoginSmsTplContent]
+	if tpl == "" {
+		err = fmt.Errorf("短信模板有误")
+		return
+	}
+	smsContent := strings.Replace(tpl, "{{VERIFY_CODE}}", req.VerifyCode, 1)
+	smsContent = strings.Replace(smsContent, "{{EXPIRED_MINUTE}}", strconv.Itoa(utils.VerifyCodeExpireMinute), 1)
+
+	var params NanHuaSmsApiParams
+	var mobileRequest NanHuaSmsApiRequest
+	mobileRequest.Mobile = req.Mobile
+	params.ApiHost = confMap[models.BusinessConfNanHuaSmsApiHost]
+	params.AppKey = confMap[models.BusinessConfNanHuaSmsAppKey]
+	params.AppSecret = confMap[models.BusinessConfNanHuaSmsAppSecret]
+	params.Content = smsContent
+	params.TypeCode = 1
+	params.Requests = append(params.Requests, mobileRequest)
+
+	// 请求接口
+	res, e := CurlNanHuaSmsApi(params)
+	if e != nil {
+		err = fmt.Errorf("CurlNanHuaSmsApi err: %s", e.Error())
+		return
+	}
+	result.Success = res.Success
+	result.Message = res.Message
+	if len(res.Data.SmsResponses) > 0 {
+		result.RequestId = res.Data.SmsResponses[0].RecId
+	}
+	return
+}
+
+// NanHuaSmsApiParams 南华短信接口请求参数
+type NanHuaSmsApiParams struct {
+	ApiHost   string                `json:"-" description:"api-host"`
+	AppKey    string                `json:"-" description:"app-key"`
+	AppSecret string                `json:"-" description:"app-secret"`
+	Content   string                `json:"content" description:"短信内容,必填"`
+	Username  string                `json:"username" description:"使用人南华域账号,非必填"`
+	TypeCode  int                   `json:"typeCode" description:"1-普通短信;2-营销短信"`
+	Requests  []NanHuaSmsApiRequest `json:"requests" description:"手机号、内容信息"`
+}
+
+// NanHuaSmsApiRequest 南华短信接口请求-手机号信息
+type NanHuaSmsApiRequest struct {
+	Content        string `json:"content" description:"短信内容,非必填,填写则该手机号使用此内容单独发送"`
+	Mobile         string `json:"mobile" description:"手机号,必填"`
+	OrgId          string `json:"orgId" description:"客户所属ctp机构ID,非必填,用来计费(建议填写)"`
+	OaDepartmentId string `json:"oaDepartmentId" description:"客户所属oa部门ID,非必填,用来计费"`
+}
+
+// NanHuaSmsApiResult 南华短信接口-响应体
+type NanHuaSmsApiResult struct {
+	ErrorCode int    `json:"errorcode" description:"状态码:0-成功;400-失败"`
+	Success   bool   `json:"success" description:"true-成功;false-失败"`
+	Message   string `json:"message" description:"请求失败提示"`
+	Data      struct {
+		SmsResponses []struct {
+			Mobile string `json:"mobile" description:"手机号"`
+			RecId  string `json:"recId" description:"短信id,用来回查发送状态"`
+		} `json:"smsResponses"`
+	} `json:"data"`
+}
+
+// CurlNanHuaSmsApi 请求南华短信接口
+func CurlNanHuaSmsApi(params NanHuaSmsApiParams) (result NanHuaSmsApiResult, err error) {
+	url := fmt.Sprint(params.ApiHost)
+	byteParams, e := json.Marshal(params)
+	if e != nil {
+		err = fmt.Errorf("data json marshal err: %s", e.Error())
+		return
+	}
+
+	body := ioutil.NopCloser(strings.NewReader(string(byteParams)))
+	client := &http.Client{}
+	req, e := http.NewRequest("POST", url, body)
+	if e != nil {
+		err = fmt.Errorf("http create request err: %s", e.Error())
+		return
+	}
+
+	contentType := "application/json;charset=utf-8"
+	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("App-Key", params.AppKey)
+	req.Header.Set("App-Secret", params.AppSecret)
+	resp, e := client.Do(req)
+	if e != nil {
+		err = fmt.Errorf("http client do err: %s", e.Error())
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+	b, e := ioutil.ReadAll(resp.Body)
+	if e != nil {
+		err = fmt.Errorf("resp body read err: %s", e.Error())
+		return
+	}
+	if len(b) == 0 {
+		err = fmt.Errorf("resp body is empty")
+		return
+	}
+	if e = json.Unmarshal(b, &result); e != nil {
+		err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+		return
+	}
+	return
+}

+ 108 - 0
services/sms.go

@@ -188,3 +188,111 @@ func sendSmsGj(jhGjAppKey, mobile, code, areaNum, tplId string) (rs []byte, err
 	utils.FileLog.Info("sendSmsGj:result:" + string(body))
 	return body, err
 }
+
+type SmsClient interface {
+	SendUserLoginCode(UserLoginSmsCodeReq) (UserLoginSmsCodeResult, error)
+}
+
+func NewSmsClient() (cli SmsClient, err error) {
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
+	}
+	if confMap[models.BusinessConfSmsClient] == models.BusinessConfClientFlagNanHua {
+		return new(NanHuaSms), nil
+	}
+	return new(HzSms), nil
+}
+
+type HzSms struct{}
+
+type UserLoginSmsCodeReq struct {
+	TelAreaCode string `description:"区号"`
+	Mobile      string `description:"手机号"`
+	VerifyCode  string `description:"短信验证码"`
+}
+
+type UserLoginSmsCodeResult struct {
+	Success   bool   `description:"发送是否成功"`
+	Message   string `description:"提示信息"`
+	RequestId string `description:"发送ID,用于回查发送状态"`
+}
+
+// SendUserLoginCode 发送用户登录验证码
+func (cli *HzSms) SendUserLoginCode(req UserLoginSmsCodeReq) (result UserLoginSmsCodeResult, err error) {
+	if req.Mobile == "" || req.VerifyCode == "" {
+		err = fmt.Errorf("参数有误, Mobile: %s, VerifyCode: %s", req.Mobile, req.VerifyCode)
+		return
+	}
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
+	}
+
+	// 国内短信
+	var netReturn map[string]interface{}
+	if req.TelAreaCode == utils.TelAreaCodeHome {
+		tplId := confMap[models.BusinessConfLoginSmsTpId]
+		if tplId == "" {
+			err = fmt.Errorf("请先配置短信模板")
+			return
+		}
+		appKey := confMap[models.BusinessConfSmsJhgnAppKey]
+		if appKey == "" {
+			err = fmt.Errorf("请先配置聚合短信AppKey")
+			return
+		}
+
+		smsRes, e := sendSms(appKey, req.Mobile, tplId, req.VerifyCode)
+		if e != nil {
+			err = fmt.Errorf("send sms err: %s", e.Error())
+			return
+		}
+		if e = json.Unmarshal(smsRes, &netReturn); e != nil {
+			err = fmt.Errorf("json unmarshal err: %s", e.Error())
+			return
+		}
+	}
+
+	// 国际短信
+	if req.TelAreaCode != utils.TelAreaCodeHome {
+		tplId := confMap[models.BusinessConfLoginSmsGjTpId]
+		if tplId == "" {
+			err = fmt.Errorf("请先配置短信模板")
+			return
+		}
+		appKey := confMap[models.BusinessConfSmsJhgjAppKey]
+		if appKey == "" {
+			err = fmt.Errorf("请先配置聚合短信AppKey")
+			return
+		}
+
+		smsRes, e := sendSmsGj(appKey, req.Mobile, req.VerifyCode, req.TelAreaCode, tplId)
+		if e != nil {
+			err = fmt.Errorf("send gj sms err: %s", e.Error())
+			return
+		}
+		if e = json.Unmarshal(smsRes, &netReturn); e != nil {
+			err = fmt.Errorf("json unmarshal err: %s", e.Error())
+			return
+		}
+	}
+
+	errCode, ok := netReturn["error_code"].(float64)
+	if !ok {
+		err = fmt.Errorf("result code err")
+		return
+	}
+	// 忽略错误的手机号码这种错误
+	if errCode != 0 && errCode != 205401 {
+		err = fmt.Errorf("err code %f", errCode)
+		return
+	}
+	// 发送成功
+	if errCode == 0 {
+		result.Success = true
+	}
+	return
+}

+ 161 - 95
services/user_login.go

@@ -8,6 +8,7 @@ import (
 	"eta/eta_api/models/system"
 	"eta/eta_api/utils"
 	"fmt"
+	"github.com/go-ldap/ldap"
 	"io"
 	"net/http"
 	"strconv"
@@ -17,6 +18,19 @@ import (
 
 // SendAdminMobileVerifyCode 发送用户手机验证码
 func SendAdminMobileVerifyCode(source int, mobile, areaCode string) (ok bool, err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("SendAdminMobileVerifyCode ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
+		}
+	}()
+	smsClient, e := NewSmsClient()
+	if e != nil {
+		err = fmt.Errorf("NewSmsClient err: %s", e.Error())
+		return
+	}
+
 	verifyCode := utils.GetRandDigit(6)
 	record := new(system.AdminVerifyCodeRecord)
 	record.VerifyType = system.AdminVerifyCodeRecordTypeMobile
@@ -30,49 +44,24 @@ func SendAdminMobileVerifyCode(source int, mobile, areaCode string) (ok bool, er
 		err = fmt.Errorf("新增验证码记录失败, Err: %s", e.Error())
 		return
 	}
-	// 获取配置好的短信模版
-	smsCond := ` AND conf_key in (?,?) `
-	smsPars := make([]interface{}, 0)
-	smsPars = append(smsPars, "LoginSmsTpId", "LoginSmsGjTpId")
-	conf := new(models.BusinessConf)
-	confList, e := conf.GetItemsByCondition(smsCond, smsPars, []string{"conf_key", "conf_val"}, "")
+
+	var smsReq UserLoginSmsCodeReq
+	smsReq.Mobile = mobile
+	smsReq.TelAreaCode = areaCode
+	smsReq.VerifyCode = verifyCode
+	smsResult, e := smsClient.SendUserLoginCode(smsReq)
 	if e != nil {
-		if e.Error() == utils.ErrNoRow() {
-			err = fmt.Errorf("请先配置短信模版")
-			return
-		}
-		err = fmt.Errorf("获取短信模版失败, Err: %s", e.Error())
+		err = fmt.Errorf("SendUserLoginCode err: %s", e.Error())
 		return
 	}
+	ok = smsResult.Success
 
-	tplId := ""
-	gjTplId := ""
-	for _, v := range confList {
-		if v.ConfKey == "LoginSmsTpId" {
-			tplId = v.ConfVal
-		} else if v.ConfKey == "LoginSmsGjTpId" {
-			gjTplId = v.ConfVal
-		}
-	}
-
-	if tplId == "" {
-		err = fmt.Errorf("请先配置短信模版")
-		return
-	}
-	if areaCode == "86" {
-		ok = SendSmsCode(mobile, verifyCode, tplId)
-	} else {
-		if gjTplId == "" {
-			err = fmt.Errorf("请先配置国际短信模版")
-			return
-		}
-		ok = SendSmsCodeGj(mobile, verifyCode, areaCode, gjTplId)
-	}
 	record.SendStatus = system.AdminVerifyCodeRecordStatusSuccess
 	if !ok {
 		record.SendStatus = system.AdminVerifyCodeRecordStatusFail
 	}
-	cols := []string{"SendStatus"}
+	record.RequestId = smsResult.RequestId
+	cols := []string{"SendStatus", "RequestId"}
 	if e := record.Update(cols); e != nil {
 		err = fmt.Errorf("更新验证码记录失败, Err: %s", e.Error())
 	}
@@ -81,7 +70,37 @@ func SendAdminMobileVerifyCode(source int, mobile, areaCode string) (ok bool, er
 
 // SendAdminEmailVerifyCode 发送用户邮箱验证码
 func SendAdminEmailVerifyCode(source int, email string) (ok bool, err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("SendAdminEmailVerifyCode ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
+		}
+	}()
+	// 读取配置
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
+	}
+	subjectConf := confMap[models.BusinessConfLoginEmailTemplateSubject]
+	contentConf := confMap[models.BusinessConfLoginEmailTemplateContent]
+	if subjectConf == "" {
+		err = fmt.Errorf("请先配置邮件模版主题")
+		return
+	}
+	if contentConf == "" {
+		err = fmt.Errorf("请先配置邮件模版内容")
+		return
+	}
 	verifyCode := utils.GetRandDigit(6)
+	t := time.Now().Format("2006年01月02日")
+	emailContent := contentConf
+	emailContent = strings.Replace(emailContent, "{{VERIFY_CODE}}", verifyCode, 1)
+	emailContent = strings.Replace(emailContent, "{{EXPIRED_MINUTE}}", strconv.Itoa(utils.VerifyCodeExpireMinute), 1)
+	emailContent = strings.Replace(emailContent, "{{DATE_TIME}}", t, 1)
+
+	// 验证码记录
 	record := new(system.AdminVerifyCodeRecord)
 	record.VerifyType = system.AdminVerifyCodeRecordTypeEmail
 	record.Email = email
@@ -95,85 +114,132 @@ func SendAdminEmailVerifyCode(source int, email string) (ok bool, err error) {
 		return
 	}
 
-	// 获取邮件配置
-	authKey := "english_report_email_conf"
-	emailConf, e := company.GetConfigDetailByCode(authKey)
-	if e != nil {
-		err = fmt.Errorf("获取群发邮件权限失败, Err: %s", e.Error())
-		return
+	var result string
+	if confMap[models.BusinessConfEmailClient] == models.BusinessConfEmailClientSmtp {
+		// 普通邮箱
+		var emailReq SendEmailReq
+		emailReq.Title = subjectConf
+		emailReq.Content = emailContent
+		emailReq.ToUser = append(emailReq.ToUser, email)
+		ok, e = SendEmail(emailReq)
+		if e != nil {
+			err = fmt.Errorf("邮箱推送失败, Err: %s", e.Error())
+			return
+		}
+	} else {
+		// 默认阿里云邮箱
+		// 读取发信人昵称配置...后面可以优化一下
+		authKey := "english_report_email_conf"
+		emailConf, e := company.GetConfigDetailByCode(authKey)
+		if e != nil {
+			err = fmt.Errorf("获取群发邮件权限失败, Err: %s", e.Error())
+			return
+		}
+		if emailConf.ConfigValue == "" {
+			err = fmt.Errorf("邮件配置为空, 不可推送")
+			return
+		}
+		conf := new(models.EnglishReportEmailConf)
+		if e = json.Unmarshal([]byte(emailConf.ConfigValue), &conf); e != nil {
+			err = fmt.Errorf("邮件配置有误, 不可推送")
+			return
+		}
+
+		req := new(EnglishReportSendEmailRequest)
+		req.Subject = subjectConf
+		req.Email = email
+		req.FromAlias = conf.FromAlias // 发信人昵称
+		req.HtmlBody = emailContent
+
+		aliEmail := new(AliyunEmail)
+		o, r, e := aliEmail.SendEmail(req)
+		if e != nil {
+			err = fmt.Errorf("阿里云邮箱推送失败, Err: %s", e.Error())
+			return
+		}
+		ok = o
+		result = r
 	}
-	if emailConf.ConfigValue == "" {
-		err = fmt.Errorf("邮件配置为空, 不可推送")
-		return
+
+	record.SendStatus = system.AdminVerifyCodeRecordStatusSuccess
+	if !ok {
+		record.SendStatus = system.AdminVerifyCodeRecordStatusFail
 	}
-	conf := new(models.EnglishReportEmailConf)
-	if e = json.Unmarshal([]byte(emailConf.ConfigValue), &conf); e != nil {
-		err = fmt.Errorf("邮件配置有误, 不可推送")
-		return
+	record.SendResult = result
+	cols := []string{"SendStatus", "SendResult"}
+	if e = record.Update(cols); e != nil {
+		err = fmt.Errorf("更新验证码记录失败, Err: %s", e.Error())
 	}
+	return
+}
 
-	// 获取邮箱模板
-	// 获取配置好的短信模版
-	cond := ` AND (conf_key = ? OR conf_key = ?)`
-	pars := make([]interface{}, 0)
-	pars = append(pars, "LoginEmailTemplateSubject", "LoginEmailTemplateContent")
-	busiConf := new(models.BusinessConf)
-	emailConfList, e := busiConf.GetItemsByCondition(cond, pars, []string{"conf_key, conf_val"}, "")
-	if e != nil {
-		if e.Error() == utils.ErrNoRow() {
-			err = fmt.Errorf("请先配置邮件模版")
-			return
+// LdapUserCheck AD域用户校验
+func LdapUserCheck(userName, password string) (pass bool, err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("LdapUserCheck ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
 		}
-		err = fmt.Errorf("获取邮件模版失败, Err: %s", e.Error())
+	}()
+	if userName == "" || password == "" {
+		err = fmt.Errorf("账号密码有误")
 		return
 	}
-	var emaiContent, emailSubject string
-	for _, v := range emailConfList {
-		if v.ConfKey == "LoginEmailTemplateContent" {
-			emaiContent = v.ConfVal
-		} else if v.ConfKey == "LoginEmailTemplateSubject" {
-			emailSubject = v.ConfVal
-		}
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
 	}
-	if emailSubject == "" {
-		err = fmt.Errorf("请先配置邮件模版主题")
+	if confMap[models.BusinessConfLdapHost] == "" || confMap[models.BusinessConfLdapBase] == "" {
+		err = fmt.Errorf("AD域配置有误")
 		return
 	}
-	if emaiContent == "" {
-		err = fmt.Errorf("请先配置邮件模版内容")
+	ldapPort, _ := strconv.Atoi(confMap[models.BusinessConfLdapPort])
+	if ldapPort <= 0 {
+		err = fmt.Errorf("AD域端口号有误, Port: %d", ldapPort)
 		return
 	}
 
-	req := new(EnglishReportSendEmailRequest)
-	req.Subject = emailSubject
-	req.Email = email
-	// todo 发信人昵称
-	req.FromAlias = conf.FromAlias
-	// 填充模板
-	t := time.Now().Format("2006年01月02日")
-	ct := emaiContent
-	ct = strings.Replace(ct, "{{VERIFY_CODE}}", verifyCode, 1)
-	ct = strings.Replace(ct, "{{EXPIRED_MINUTE}}", strconv.Itoa(utils.VerifyCodeExpireMinute), 1)
-	ct = strings.Replace(ct, "{{DATE_TIME}}", t, 1)
-	req.HtmlBody = ct
-
-	aliEmail := new(AliyunEmail)
-	o, result, e := aliEmail.SendEmail(req)
+	// 连接ldap
+	addr := fmt.Sprintf("%s:%d", confMap[models.BusinessConfLdapHost], ldapPort)
+	conn, e := ldap.Dial("tcp", addr)
 	if e != nil {
-		err = fmt.Errorf("邮箱推送失败, Err: %s", e.Error())
+		err = fmt.Errorf("ldap Dial err: %s", e.Error())
 		return
 	}
-	ok = o
+	defer conn.Close()
 
-	record.SendStatus = system.AdminVerifyCodeRecordStatusSuccess
-	if !ok {
-		record.SendStatus = system.AdminVerifyCodeRecordStatusFail
+	// 绑定用户
+	bindUserName := fmt.Sprintf("%s%s", userName, confMap[models.BusinessConfLdapBindUserSuffix])
+	if e = conn.Bind(bindUserName, password); e != nil {
+		err = fmt.Errorf("ldap Bind err: %s", e.Error())
+		return
 	}
-	record.SendResult = result
-	cols := []string{"SendStatus", "SendResult"}
-	if e = record.Update(cols); e != nil {
-		err = fmt.Errorf("更新验证码记录失败, Err: %s", e.Error())
+
+	// 鉴权操作
+	searchRequest := ldap.NewSearchRequest(
+		confMap[models.BusinessConfLdapBase],
+		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+		fmt.Sprintf(confMap[models.BusinessConfLdapUserFilter], userName),
+		[]string{"dn"},
+		nil,
+	)
+	//b, _ := json.Marshal(searchRequest)
+	//fmt.Println("searchRequest: ", string(b))
+
+	sr, e := conn.Search(searchRequest)
+	if e != nil {
+		err = fmt.Errorf("ldap Search err: %s", e.Error())
+		return
+	}
+
+	// 验证结果
+	if len(sr.Entries) != 1 {
+		utils.FileLog.Info("ldap check fail: user does not exist or too many entries returned")
+		return
 	}
+	pass = true
 	return
 }
 

+ 18 - 7
utils/config.go

@@ -4,7 +4,6 @@ import (
 	"fmt"
 	beeLogger "github.com/beego/bee/v2/logger"
 	"github.com/beego/beego/v2/server/web"
-	"github.com/rdlucklib/rdluck_tools/cache"
 	"strconv"
 )
 
@@ -19,9 +18,10 @@ var (
 	MYSQL_WEEKLY_URL string //用户主库
 	MYSQL_AI_URL     string //ETA-AI 数据库
 
-	REDIS_CACHE string       //缓存地址
-	Rc          *cache.Cache //redis缓存
-	Re          error        //redis错误
+	REDIS_CACHE string //缓存地址
+	//Rc          *cache.Cache //redis缓存
+	Re error       //redis错误
+	Rc RedisClient //redis缓存
 )
 
 // 基础配置
@@ -197,6 +197,7 @@ var (
 var (
 	MinIoBucketname       string
 	MinIoEndpoint         string
+	MinIoBackEndpoint     string
 	MinIoImghost          string
 	MinIoUploadDir        string
 	MinIoUpload_Audio_Dir string
@@ -283,12 +284,21 @@ func init() {
 	if len(REDIS_CACHE) <= 0 {
 		panic(any("redis链接参数没有配置"))
 	}
-	Rc, Re = cache.NewCache(REDIS_CACHE) //初始化缓存
-	if Re != nil {
-		fmt.Println("redis链接异常:", Re)
+	//Rc, Re = cache.NewCache(REDIS_CACHE) //初始化缓存
+	//if Re != nil {
+	//	fmt.Println("redis链接异常:", Re)
+	//	panic(any(Re))
+	//}
+
+	// 初始化缓存
+	redisClient, err := initRedis(config["redis_type"], config["beego_cache"])
+	if err != nil {
+		fmt.Println("redis链接异常:", err)
 		panic(any(Re))
 	}
 
+	Rc = redisClient
+
 	// 项目中文名称
 	appNameCn, err := web.AppConfig.String("app_name_cn")
 	if err != nil {
@@ -493,6 +503,7 @@ func init() {
 	// MinIo相关
 	{
 		MinIoEndpoint = config["minio_endpoint"]
+		MinIoBackEndpoint = config["minio_back_endpoint"]
 		MinIoBucketname = config["minio_bucket_name"]
 		MinIoImghost = config["minio_img_host"]
 		MinIoUploadDir = config["minio_upload_dir"]

+ 14 - 0
utils/constants.go

@@ -334,6 +334,7 @@ const BusinessCodeSalt = "dr7WY0OZgGR7upw1"
 const (
 	BusinessCodeSandbox = "E2023080700" // 试用平台
 	BusinessCodeRelease = "E2023080900" // 生产环境
+	BusinessCodeDebug   = "E2023080901" // 测试环境
 )
 
 // 验证码
@@ -405,3 +406,16 @@ var DataSourceEnMap = map[int]string{
 	DATA_SOURCE_MYSTEEL_CHEMICAL: "Horizon Insights",
 	DATA_SOURCE_FUBAO:            "FuBao",
 }
+
+const (
+	TelAreaCodeHome = "86" // 大陆区号
+)
+
+const (
+	LdapInitPassword = "123456a" // 域用户初始密码
+)
+
+const (
+	PercentCalculateTypeRange = 0 // 百分位算法类型-数据区间
+	PercentCalculateTypeNum   = 1 // 百分位算法类型-数据个数
+)

+ 1 - 1
utils/jwt.go

@@ -16,7 +16,7 @@ var (
 func GenToken(account string) string {
 	token := jwt.New(jwt.SigningMethodHS256)
 	token.Claims = &jwt.StandardClaims{
-		NotBefore: int64(time.Now().Unix()),
+		NotBefore: int64(time.Now().Unix()) - int64(1*time.Second),
 		ExpiresAt: int64(time.Now().Unix() + 90*24*60*60),
 		Issuer:    "eta_api",
 		Subject:   account,

+ 33 - 0
utils/redis.go

@@ -0,0 +1,33 @@
+package utils
+
+import (
+	"eta/eta_api/utils/redis"
+	"time"
+)
+
+type RedisClient interface {
+	Get(key string) interface{}
+	RedisBytes(key string) (data []byte, err error)
+	RedisString(key string) (data string, err error)
+	RedisInt(key string) (data int, err error)
+	Put(key string, val interface{}, timeout time.Duration) error
+	SetNX(key string, val interface{}, timeout time.Duration) bool
+	Delete(key string) error
+	IsExist(key string) bool
+	LPush(key string, val interface{}) error
+	Brpop(key string, callback func([]byte))
+	GetRedisTTL(key string) time.Duration
+	Incrby(key string, num int) (interface{}, error)
+	Do(commandName string, args ...interface{}) (reply interface{}, err error)
+}
+
+func initRedis(redisType string, conf string) (redisClient RedisClient, err error) {
+	switch redisType {
+	case "cluster": // 集群
+		redisClient, err = redis.InitClusterRedis(conf)
+	default: // 默认走单机
+		redisClient, err = redis.InitStandaloneRedis(conf)
+	}
+
+	return
+}

+ 265 - 0
utils/redis/cluster_redis.go

@@ -0,0 +1,265 @@
+package redis
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/go-redis/redis/v8"
+	"strings"
+	"time"
+)
+
+// ClusterRedisClient
+// @Description: 集群的redis客户端
+type ClusterRedisClient struct {
+	redisClient *redis.ClusterClient
+}
+
+var DefaultKey = "zcmRedis"
+
+// InitClusterRedis
+// @Description: 初始化集群redis客户端
+// @param config
+// @return clusterRedisClient
+// @return err
+func InitClusterRedis(config string) (clusterRedisClient *ClusterRedisClient, err error) {
+	var cf map[string]string
+	err = json.Unmarshal([]byte(config), &cf)
+	if err != nil {
+		return
+	}
+	//if _, ok := cf["key"]; !ok {
+	//	cf["key"] = DefaultKey
+	//}
+
+	// 集群地址
+	connList := make([]string, 0)
+	if _, ok := cf["conn"]; !ok {
+		err = errors.New("config has no conn key")
+		return
+	} else {
+		connList = strings.Split(cf["conn"], ",")
+		if len(connList) <= 1 {
+			err = errors.New("conn address less than or equal to 1")
+			return
+		}
+	}
+
+	// 密码
+	if _, ok := cf["password"]; !ok {
+		cf["password"] = ""
+	}
+
+	// 创建 Redis 客户端配置对象
+	clusterOptions := &redis.ClusterOptions{
+		Addrs:    connList, // 设置 Redis 节点的 IP 地址和端口号
+		Password: cf["password"],
+	}
+
+	// 创建 Redis 集群客户端
+	client := redis.NewClusterClient(clusterOptions)
+
+	// 测试连接并获取信息
+	_, err = client.Ping(context.TODO()).Result()
+	if err != nil {
+		err = errors.New("redis 链接失败:" + err.Error())
+		return
+	}
+
+	clusterRedisClient = &ClusterRedisClient{redisClient: client}
+
+	return
+}
+
+// Get
+// @Description: 根据key获取数据(其实是返回的字节编码)
+// @receiver rc
+// @param key
+// @return interface{}
+func (rc *ClusterRedisClient) Get(key string) interface{} {
+	data, err := rc.redisClient.Get(context.TODO(), key).Bytes()
+	if err != nil {
+		return nil
+	}
+
+	return data
+}
+
+// RedisBytes
+// @Description: 根据key获取字节编码数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *ClusterRedisClient) RedisBytes(key string) (data []byte, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Bytes()
+
+	return
+}
+
+// RedisString
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *ClusterRedisClient) RedisString(key string) (data string, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Result()
+
+	return
+}
+
+// RedisInt
+// @Description: 根据key获取int数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *ClusterRedisClient) RedisInt(key string) (data int, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Int()
+
+	return
+}
+
+// Put
+// @Description: put一个数据到redis
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return error
+func (rc *ClusterRedisClient) Put(key string, val interface{}, timeout time.Duration) error {
+	var err error
+	err = rc.redisClient.SetEX(context.TODO(), key, val, timeout).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HSet(context.TODO(), DefaultKey, key, true).Err()
+
+	return err
+}
+
+// SetNX
+// @Description: 设置一个会过期时间的值
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return bool
+func (rc *ClusterRedisClient) SetNX(key string, val interface{}, timeout time.Duration) bool {
+	result, err := rc.redisClient.SetEX(context.TODO(), key, val, timeout).Result()
+	if err != nil || result != "OK" {
+		return false
+	}
+
+	return true
+}
+
+// Delete
+// @Description: 删除redis中的键值对
+// @receiver rc
+// @param key
+// @return error
+func (rc *ClusterRedisClient) Delete(key string) error {
+	var err error
+
+	err = rc.redisClient.Del(context.TODO(), key).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+
+	return err
+}
+
+// IsExist
+// @Description: 根据key判断是否写入缓存中
+// @receiver rc
+// @param key
+// @return bool
+func (rc *ClusterRedisClient) IsExist(key string) bool {
+	result, err := rc.redisClient.Exists(context.TODO(), key).Result()
+	if err != nil {
+		return false
+	}
+	if result == 0 {
+		_ = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+		return false
+	}
+
+	return true
+}
+
+// LPush
+// @Description: 写入list
+// @receiver rc
+// @param key
+// @param val
+// @return error
+func (rc *ClusterRedisClient) LPush(key string, val interface{}) error {
+	data, _ := json.Marshal(val)
+	err := rc.redisClient.LPush(context.TODO(), key, data).Err()
+
+	return err
+}
+
+// Brpop
+// @Description: 从list中读取
+// @receiver rc
+// @param key
+// @param callback
+func (rc *ClusterRedisClient) Brpop(key string, callback func([]byte)) {
+	values, err := rc.redisClient.BRPop(context.TODO(), 1*time.Second, key).Result()
+	if err != nil {
+		return
+	}
+	if len(values) < 2 {
+		fmt.Println("assert is wrong")
+		return
+	}
+
+	callback([]byte(values[1]))
+
+}
+
+// GetRedisTTL
+// @Description: 获取key的过期时间
+// @receiver rc
+// @param key
+// @return time.Duration
+func (rc *ClusterRedisClient) GetRedisTTL(key string) time.Duration {
+	value, err := rc.redisClient.TTL(context.TODO(), key).Result()
+	if err != nil {
+		return 0
+	}
+
+	return value
+
+}
+
+// Incrby
+// @Description: 设置自增值
+// @receiver rc
+// @param key
+// @param num
+// @return interface{}
+// @return error
+func (rc *ClusterRedisClient) Incrby(key string, num int) (interface{}, error) {
+	return rc.redisClient.IncrBy(context.TODO(), key, int64(num)).Result()
+}
+
+// Do
+// @Description: cmd执行redis命令
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *ClusterRedisClient) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
+	newArgs := []interface{}{commandName}
+	newArgs = append(newArgs, args...)
+	return rc.redisClient.Do(context.TODO(), newArgs...).Result()
+}

+ 257 - 0
utils/redis/standalone_redis.go

@@ -0,0 +1,257 @@
+package redis
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/go-redis/redis/v8"
+	"strconv"
+	"time"
+)
+
+// StandaloneRedisClient
+// @Description: 单机redis客户端
+type StandaloneRedisClient struct {
+	redisClient *redis.Client
+}
+
+func InitStandaloneRedis(config string) (standaloneRedis *StandaloneRedisClient, err error) {
+	var cf map[string]string
+	err = json.Unmarshal([]byte(config), &cf)
+	if err != nil {
+		return
+	}
+	//if _, ok := cf["key"]; !ok {
+	//	cf["key"] = DefaultKey
+	//}
+
+	if _, ok := cf["conn"]; !ok {
+		err = errors.New("config has no conn key")
+		return
+	}
+
+	// db库
+	dbNum := 0
+	// 如果指定了db库
+	if _, ok := cf["dbNum"]; ok {
+		dbNum, err = strconv.Atoi(cf["dbNum"])
+		if err != nil {
+			return
+		}
+	}
+
+	// 密码
+	if _, ok := cf["password"]; !ok {
+		cf["password"] = ""
+	}
+
+	client := redis.NewClient(&redis.Options{
+		Addr:     cf["conn"],
+		Password: cf["password"],
+		DB:       dbNum,
+		//PoolSize: 10, //连接池最大socket连接数,默认为10倍CPU数, 10 * runtime.NumCPU(暂不配置)
+	})
+
+	_, err = client.Ping(context.TODO()).Result()
+	if err != nil {
+		err = errors.New("redis 链接失败:" + err.Error())
+		return
+	}
+
+	standaloneRedis = &StandaloneRedisClient{redisClient: client}
+
+	return
+}
+
+// Get
+// @Description: 根据key获取数据(其实是返回的字节编码)
+// @receiver rc
+// @param key
+// @return interface{}
+func (rc *StandaloneRedisClient) Get(key string) interface{} {
+	data, err := rc.redisClient.Get(context.TODO(), key).Bytes()
+	if err != nil {
+		return nil
+	}
+
+	return data
+}
+
+// RedisBytes
+// @Description: 根据key获取字节编码数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *StandaloneRedisClient) RedisBytes(key string) (data []byte, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Bytes()
+
+	return
+}
+
+// RedisString
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *StandaloneRedisClient) RedisString(key string) (data string, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Result()
+
+	return
+}
+
+// RedisInt
+// @Description: 根据key获取int数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *StandaloneRedisClient) RedisInt(key string) (data int, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Int()
+
+	return
+}
+
+// Put
+// @Description: put一个数据到redis
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return error
+func (rc *StandaloneRedisClient) Put(key string, val interface{}, timeout time.Duration) error {
+	var err error
+	err = rc.redisClient.SetEX(context.TODO(), key, val, timeout).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HSet(context.TODO(), DefaultKey, key, true).Err()
+
+	return err
+}
+
+// SetNX
+// @Description: 设置一个会过期时间的值
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return bool
+func (rc *StandaloneRedisClient) SetNX(key string, val interface{}, timeout time.Duration) bool {
+	result, err := rc.redisClient.SetEX(context.TODO(), key, val, timeout).Result()
+	if err != nil || result != "OK" {
+		return false
+	}
+
+	return true
+}
+
+// Delete
+// @Description: 删除redis中的键值对
+// @receiver rc
+// @param key
+// @return error
+func (rc *StandaloneRedisClient) Delete(key string) error {
+	var err error
+
+	err = rc.redisClient.Del(context.TODO(), key).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+
+	return err
+}
+
+// IsExist
+// @Description: 根据key判断是否写入缓存中
+// @receiver rc
+// @param key
+// @return bool
+func (rc *StandaloneRedisClient) IsExist(key string) bool {
+	result, err := rc.redisClient.Exists(context.TODO(), key).Result()
+	if err != nil {
+		return false
+	}
+	if result == 0 {
+		_ = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+		return false
+	}
+
+	return true
+}
+
+// LPush
+// @Description: 写入list
+// @receiver rc
+// @param key
+// @param val
+// @return error
+func (rc *StandaloneRedisClient) LPush(key string, val interface{}) error {
+	data, _ := json.Marshal(val)
+	err := rc.redisClient.LPush(context.TODO(), key, data).Err()
+
+	return err
+}
+
+// Brpop
+// @Description: 从list中读取
+// @receiver rc
+// @param key
+// @param callback
+func (rc *StandaloneRedisClient) Brpop(key string, callback func([]byte)) {
+	values, err := rc.redisClient.BRPop(context.TODO(), 1*time.Second, key).Result()
+	if err != nil {
+		return
+	}
+	if len(values) < 2 {
+		fmt.Println("assert is wrong")
+		return
+	}
+
+	callback([]byte(values[1]))
+
+}
+
+// GetRedisTTL
+// @Description: 获取key的过期时间
+// @receiver rc
+// @param key
+// @return time.Duration
+func (rc *StandaloneRedisClient) GetRedisTTL(key string) time.Duration {
+	value, err := rc.redisClient.TTL(context.TODO(), key).Result()
+	if err != nil {
+		return 0
+	}
+
+	return value
+
+}
+
+// Incrby
+// @Description: 设置自增值
+// @receiver rc
+// @param key
+// @param num
+// @return interface{}
+// @return error
+func (rc *StandaloneRedisClient) Incrby(key string, num int) (interface{}, error) {
+	return rc.redisClient.IncrBy(context.TODO(), key, int64(num)).Result()
+}
+
+// Do
+// @Description: cmd执行redis命令
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *StandaloneRedisClient) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
+	newArgs := []interface{}{commandName}
+	newArgs = append(newArgs, args...)
+	return rc.redisClient.Do(context.TODO(), newArgs...).Result()
+}