Parcourir la source

Merge branch 'custom' into hotfix/jinbo_test_0329

# Conflicts:
#	go.mod
#	utils/common.go
xyxie il y a 11 mois
Parent
commit
27b5ec8670
51 fichiers modifiés avec 9810 ajouts et 505 suppressions
  1. 268 0
      controllers/chart_permission.go
  2. 212 81
      controllers/classify.go
  3. 8 1
      controllers/company_permission.go
  4. 9 0
      controllers/data_manage/edb_info.go
  5. 166 25
      controllers/english_report/en_permission.go
  6. 193 67
      controllers/english_report/english_classify.go
  7. 44 32
      controllers/report.go
  8. 239 57
      controllers/report_chapter_type.go
  9. 1141 0
      controllers/speech_recognition/speech_recognition.go
  10. 534 0
      controllers/speech_recognition/speech_recognition_menu.go
  11. 384 0
      controllers/speech_recognition/speech_recognition_tag.go
  12. 535 0
      controllers/speech_recognition/speech_recognition_tag_menu.go
  13. 10 5
      go.mod
  14. 29 8
      go.sum
  15. 5 1
      models/business_conf.go
  16. 195 27
      models/chart_permission.go
  17. 130 43
      models/classify.go
  18. 9 9
      models/company/company_permission.go
  19. 23 0
      models/data_manage/edb_config.go
  20. 21 0
      models/data_manage/edb_terminal.go
  21. 17 0
      models/db.go
  22. 12 1
      models/en_classify_permission.go
  23. 75 7
      models/en_permission.go
  24. 58 0
      models/english_classify.go
  25. 55 37
      models/english_report.go
  26. 13 6
      models/permission.go
  27. 100 36
      models/report_chapter_type.go
  28. 10 2
      models/report_chapter_type_permission.go
  29. 561 0
      models/speech_recognition/speech_recognition.go
  30. 159 0
      models/speech_recognition/speech_recognition_api_log.go
  31. 211 0
      models/speech_recognition/speech_recognition_content.go
  32. 256 0
      models/speech_recognition/speech_recognition_menu.go
  33. 225 0
      models/speech_recognition/speech_recognition_tag.go
  34. 148 0
      models/speech_recognition/speech_recognition_tag_mapping.go
  35. 250 0
      models/speech_recognition/speech_recognition_tag_menu.go
  36. 398 2
      routers/commentsRouter.go
  37. 15 0
      routers/router.go
  38. 478 0
      services/chart_permission.go
  39. 206 0
      services/chart_permission_sync.go
  40. 179 0
      services/classify.go
  41. 119 35
      services/data/edb_info.go
  42. 169 0
      services/english_classify.go
  43. 170 0
      services/english_permission.go
  44. 25 23
      services/report.go
  45. 153 0
      services/report_chapter_type.go
  46. 51 0
      services/report_chapter_type_sync.go
  47. 3 0
      services/report_classify.go
  48. 1404 0
      services/speech_recognition.go
  49. 90 0
      services/tencent_asr.go
  50. BIN
      static/SimHei.ttf
  51. 45 0
      utils/common.go

+ 268 - 0
controllers/chart_permission.go

@@ -0,0 +1,268 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/services"
+	"eta/eta_api/utils"
+)
+
+// ChartPermissionController 品种列表
+type ChartPermissionController struct {
+	BaseAuthController
+}
+
+// List
+// @Title 品种列表
+// @Description 品种列表
+// @Param   Keyword  query  string  false  "关键词"
+// @Success 200 Ret=200 操作成功
+// @router /list [get]
+func (this *ChartPermissionController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	cond := ` and product_id=1`
+	pars := make([]interface{}, 0)
+	list, e := services.GetChartPermissionList(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取品种列表失败, Err: " + e.Error()
+		return
+	}
+
+	// 品种树
+	resp := make([]*models.ChartPermissionItem, 0)
+	childMap := make(map[int][]*models.ChartPermissionItem)
+
+	for _, v := range list {
+		t := new(models.ChartPermissionItem)
+		t.PermissionId = v.ChartPermissionId
+		t.PermissionName = v.PermissionName
+		t.ParentId = v.ParentId
+		t.IsPublic = v.IsPublic
+		t.Enabled = v.Enabled
+		t.Sort = v.Sort
+		t.CreateTime = v.CreatedTime.Format(utils.FormatDateTime)
+		t.Child = make([]*models.ChartPermissionItem, 0)
+		if v.ParentId == 0 {
+			resp = append(resp, t)
+			continue
+		}
+		if v.ParentId > 0 {
+			if childMap[v.ParentId] == nil {
+				childMap[v.ParentId] = make([]*models.ChartPermissionItem, 0)
+			}
+			childMap[v.ParentId] = append(childMap[v.ParentId], t)
+		}
+	}
+	for _, r := range resp {
+		r.Child = childMap[r.PermissionId]
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Add
+// @Title 新增品种列表
+// @Description 新增品种列表
+// @Param  request body models.PermissionAddReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /add [post]
+func (this *ChartPermissionController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.PermissionAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.PermissionName == "" {
+		br.Msg = "请输入品种名称"
+		return
+	}
+	e, msg := services.AddChartPermission(req)
+	if e != nil {
+		br.Msg = msg
+		br.ErrMsg = "新增品种失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Edit
+// @Title 编辑品种
+// @Description 编辑品种
+// @Param	request	body models.PermissionEditReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /edit [post]
+func (this *ChartPermissionController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.PermissionEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.PermissionId <= 0 {
+		br.Msg = "请选择要编辑的品种"
+		return
+	}
+
+	if req.PermissionName == "" {
+		br.Msg = "请输入品种名称"
+		return
+	}
+
+	e, msg := services.EditChartPermission(req)
+	if e != nil {
+		br.Msg = msg
+		br.ErrMsg = "编辑品种失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Move
+// @Title 移动品种
+// @Description 移动品种
+// @Param	request	body models.PermissionMoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /move [post]
+func (this *ChartPermissionController) Move() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.PermissionMoveReq
+	// todo 限制修改一级品种
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	if req.PermissionId == 0 {
+		br.Msg = "请选择要移动的品种"
+		return
+	}
+	e, msg := services.MoveChartPermission(req)
+	if e != nil {
+		br.Msg = msg
+		br.ErrMsg = "移动品种失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// SetEnabled
+// @Title 启用/禁用品种
+// @Description 启用/禁用品种
+// @Param	request	body models.PermissionEnabledReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /enabled/set [post]
+func (this *ChartPermissionController) SetEnabled() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.PermissionEnabledReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.PermissionId <= 0 {
+		br.Msg = "请选择要编辑的品种"
+		return
+	}
+	if req.Enabled != 0 && req.Enabled != 1 {
+		br.Msg = "请选择正确的启用禁用状态"
+		return
+	}
+
+	e, msg := services.SetEnabledChartPermission(req)
+	if e != nil {
+		br.Msg = msg
+		br.ErrMsg = "编辑品种失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 212 - 81
controllers/classify.go

@@ -4,11 +4,9 @@ import (
 	"encoding/json"
 	"eta/eta_api/models"
 	"eta/eta_api/models/report_approve"
-	"eta/eta_api/models/system"
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
 )
 
@@ -37,7 +35,7 @@ func (this *ClassifyController) Add() {
 	}
 
 	// 获取系统菜单, 如果没有对应的字段的特殊处理项, 则忽略必填
-	menus, e := system.GetSysMenuItemsByCondition(` AND hidden = 0`, make([]interface{}, 0), []string{}, ``)
+	/*menus, e := system.GetSysMenuItemsByCondition(` AND hidden = 0`, make([]interface{}, 0), []string{}, ``)
 	if e != nil {
 		br.Msg = "保存失败"
 		br.ErrMsg = "获取菜单列表失败, Err: " + e.Error()
@@ -48,13 +46,13 @@ func (this *ClassifyController) Add() {
 		if m.ButtonCode != "" {
 			menuMap[m.ButtonCode] = true
 		}
-	}
+	}*/
 
 	if req.ClassifyName == "" {
 		br.Msg = "分类名称不可为空"
 		return
 	}
-	if menuMap[system.MenuSpecialHandleClassifyShowType] && req.ParentId != 0 && req.ShowType == 0 {
+	/*if menuMap[system.MenuSpecialHandleClassifyShowType] && req.ParentId != 0 && req.ShowType == 0 {
 		br.Msg = "展示类型不可为空"
 		return
 	}
@@ -65,7 +63,7 @@ func (this *ClassifyController) Add() {
 	if menuMap[system.MenuSpecialHandleClassifyReportImgs] && (req.ShowType == 1 || req.ShowType == 3) && req.YbRightBanner == "" && req.ParentId == 0 { //当一级报告分类为列表、品种时,增加“报告合集配图”的配置项
 		br.Msg = "报告合集配图不可为空"
 		return
-	}
+	}*/
 
 	item, err := models.GetClassifyByName(req.ClassifyName, req.ParentId)
 	if err != nil && err.Error() != utils.ErrNoRow() {
@@ -77,13 +75,25 @@ func (this *ClassifyController) Add() {
 		br.Msg = "分类名称:" + req.ClassifyName + "已存在"
 		return
 	}
+
 	nowTime := time.Now().Local()
 	classify := new(models.Classify)
+
+	maxSort, e := classify.GetMaxSort()
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "查询品种排序失败, Err: " + e.Error()
+		return
+	}
 	classify.ClassifyName = req.ClassifyName
 	classify.ParentId = req.ParentId
 	classify.CreateTime = nowTime
 	classify.ModifyTime = nowTime
-	classify.Abstract = req.Abstract
+	classify.Sort = maxSort + 1
+	classify.Enabled = 1
+	classify.ShowType = 1 //默认列表格式
+	classify.IsShow = 1
+	/*classify.Abstract = req.Abstract
 	classify.Descript = req.Descript
 	classify.Abstract = req.Abstract
 	classify.Descript = req.Descript
@@ -98,7 +108,7 @@ func (this *ClassifyController) Add() {
 	classify.ShowType = req.ShowType
 	classify.HasTeleconference = req.HasTeleconference
 	classify.VipTitle = req.VipTitle
-	classify.Sort = req.Sort
+
 	classify.IsShow = req.IsShow
 	classify.YbFiccSort = req.YbFiccSort
 	classify.YbFiccIcon = req.YbFiccIcon
@@ -156,7 +166,7 @@ func (this *ClassifyController) Add() {
 				return
 			}
 		}
-	}
+	}*/
 	err = models.AddClassify(classify)
 	if err != nil {
 		br.Msg = "新增失败"
@@ -165,7 +175,7 @@ func (this *ClassifyController) Add() {
 	}
 
 	// 一级分类-新增子目录
-	if classify.ParentId == 0 && len(req.MenuList) > 0 {
+	/*if classify.ParentId == 0 && len(req.MenuList) > 0 {
 		menus := make([]*models.ClassifyMenu, 0)
 		for i := range req.MenuList {
 			menus = append(menus, &models.ClassifyMenu{
@@ -190,6 +200,18 @@ func (this *ClassifyController) Add() {
 			br.ErrMsg = "新增子目录关联失败, Err:" + e.Error()
 			return
 		}
+	}*/
+	//获取报告分类权限列表
+	if classify.ParentId > 0 { //二级分类才能修改权限
+		err = models.EditChartPermissionSearchKeyWordMappingMulti(req.ClassifyName, req.ChartPermissionIdList)
+		if err != nil {
+			br.Msg = "修改分类权限失败"
+			br.ErrMsg = "修改分类权限失败,Err:" + err.Error()
+			return
+		}
+		go func() {
+			_ = services.EditClassifyChartPermissionSync(req.ClassifyName)
+		}()
 	}
 
 	// 新增关联了电话会的二级分类时, 同步FICC活动分类
@@ -386,7 +408,7 @@ func (this *ClassifyController) Edit() {
 	}
 
 	// 获取系统菜单, 如果没有对应的字段的特殊处理项, 则忽略必填
-	menus, e := system.GetSysMenuItemsByCondition(` AND hidden = 0`, make([]interface{}, 0), []string{}, ``)
+	/*menus, e := system.GetSysMenuItemsByCondition(` AND hidden = 0`, make([]interface{}, 0), []string{}, ``)
 	if e != nil {
 		br.Msg = "保存失败"
 		br.ErrMsg = "获取菜单列表失败, Err: " + e.Error()
@@ -397,7 +419,7 @@ func (this *ClassifyController) Edit() {
 		if m.ButtonCode != "" {
 			menuMap[m.ButtonCode] = true
 		}
-	}
+	}*/
 
 	if req.ClassifyId <= 0 {
 		br.Msg = "参数错误"
@@ -407,7 +429,7 @@ func (this *ClassifyController) Edit() {
 		br.Msg = "分类名称不可为空"
 		return
 	}
-	if menuMap[system.MenuSpecialHandleClassifyShowType] && req.ParentId != 0 && req.ShowType == 0 {
+	/*if menuMap[system.MenuSpecialHandleClassifyShowType] && req.ParentId != 0 && req.ShowType == 0 {
 		br.Msg = "展示类型不可为空"
 		return
 	}
@@ -418,7 +440,7 @@ func (this *ClassifyController) Edit() {
 	if menuMap[system.MenuSpecialHandleClassifyReportImgs] && (req.ShowType == 1 || req.ShowType == 3) && req.YbRightBanner == "" && req.ParentId == 0 { //当一级报告分类为列表、品种时,增加“报告合集配图”的配置项
 		br.Msg = "报告合集配图不可为空"
 		return
-	}
+	}*/
 
 	item, err := models.GetClassifyById(req.ClassifyId)
 	if err != nil {
@@ -446,59 +468,78 @@ func (this *ClassifyController) Edit() {
 		br.Msg = "分类名称:" + req.ClassifyName + "已存在"
 		return
 	}
-
 	item.ClassifyName = req.ClassifyName
-	item.Abstract = req.Abstract
 	item.ParentId = req.ParentId
-	item.Descript = req.Descript
-	item.ReportAuthor = req.ReportAuthor
-	item.AuthorDescript = req.AuthorDescript
-	item.ColumnImgUrl = req.ColumnImgUrl
-	item.HeadImgUrl = req.HeadImgUrl
-	item.AvatarImgUrl = req.AvatarImgUrl
-	item.ReportImgUrl = req.ReportImgUrl
-	item.HomeImgUrl = req.HomeImgUrl
-	item.ClassifyLabel = req.ClassifyLabel
-	item.ShowType = req.ShowType
-	item.HasTeleconference = req.HasTeleconference
-	item.VipTitle = req.VipTitle
-	item.Sort = req.Sort
-	item.IsShow = req.IsShow
-	item.YbFiccSort = req.YbFiccSort
-	item.YbFiccIcon = req.YbFiccIcon
-	item.YbFiccPcIcon = req.YbFiccPcIcon
-	item.YbIconUrl = req.YbIconUrl
-	item.YbBgUrl = req.YbBgUrl
-	item.YbListImg = req.YbListImg
-	item.YbShareBgImg = req.YbShareBgImg
-	item.YbRightBanner = req.YbRightBanner
-	item.RelateTel = req.RelateTel
-	item.RelateVideo = req.RelateVideo
 	item.ModifyTime = time.Now().Local()
 	cols := make([]string, 0)
-	cols = append(cols, "ClassifyName", "Abstract", "ParentId", "Descript", "ReportAuthor", "AuthorDescript", "ColumnImgUrl",
-		"HeadImgUrl", "AvatarImgUrl", "ReportImgUrl", "HomeImgUrl", "ClassifyLabel", "ShowType", "HasTeleconference", "VipTitle", "Sort",
-		"IsShow", "YbFiccSort", "YbFiccIcon", "YbFiccPcIcon", "YbIconUrl", "YbBgUrl", "YbListImg", "YbShareBgImg", "YbRightBanner",
-		"RelateTel", "RelateVideo", "ModifyTime")
+	cols = append(cols, "ClassifyName", "ParentId", "ModifyTime")
 	if e := item.UpdateClassify(cols); e != nil {
 		br.Msg = "修改失败"
 		br.ErrMsg = "修改失败,Err:" + e.Error()
 		return
 	}
-	// 一级分类关联设置会强制修改二级分类的所有关联设置
-	if item.ParentId == 0 {
-		if e = models.UpdateChildClassifyRelateSetting(item.Id, req.RelateTel, req.RelateVideo); e != nil {
-			br.Msg = "更新二级分类关联设置失败"
-			br.ErrMsg = "更新二级分类关联设置失败, Err: " + e.Error()
+
+	/*
+		item.Abstract = req.Abstract
+		item.Descript = req.Descript
+		item.ReportAuthor = req.ReportAuthor
+		item.AuthorDescript = req.AuthorDescript
+		item.ColumnImgUrl = req.ColumnImgUrl
+		item.HeadImgUrl = req.HeadImgUrl
+		item.AvatarImgUrl = req.AvatarImgUrl
+		item.ReportImgUrl = req.ReportImgUrl
+		item.HomeImgUrl = req.HomeImgUrl
+		item.ClassifyLabel = req.ClassifyLabel
+		item.ShowType = req.ShowType
+		item.HasTeleconference = req.HasTeleconference
+		item.VipTitle = req.VipTitle
+		//	item.Sort = req.Sort
+		item.IsShow = req.IsShow
+		item.YbFiccSort = req.YbFiccSort
+		item.YbFiccIcon = req.YbFiccIcon
+		item.YbFiccPcIcon = req.YbFiccPcIcon
+		item.YbIconUrl = req.YbIconUrl
+		item.YbBgUrl = req.YbBgUrl
+		item.YbListImg = req.YbListImg
+		item.YbShareBgImg = req.YbShareBgImg
+		item.YbRightBanner = req.YbRightBanner
+		item.RelateTel = req.RelateTel
+		item.RelateVideo = req.RelateVideo
+		item.ModifyTime = time.Now().Local()
+		cols := make([]string, 0)
+		cols = append(cols, "ClassifyName", "Abstract", "ParentId", "Descript", "ReportAuthor", "AuthorDescript", "ColumnImgUrl",
+			"HeadImgUrl", "AvatarImgUrl", "ReportImgUrl", "HomeImgUrl", "ClassifyLabel", "ShowType", "HasTeleconference", "VipTitle", "Sort",
+			"IsShow", "YbFiccSort", "YbFiccIcon", "YbFiccPcIcon", "YbIconUrl", "YbBgUrl", "YbListImg", "YbShareBgImg", "YbRightBanner",
+			"RelateTel", "RelateVideo", "ModifyTime")
+		if e := item.UpdateClassify(cols); e != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改失败,Err:" + e.Error()
 			return
 		}
-	}
-
+		// 一级分类关联设置会强制修改二级分类的所有关联设置
+		if item.ParentId == 0 {
+			if e = models.UpdateChildClassifyRelateSetting(item.Id, req.RelateTel, req.RelateVideo); e != nil {
+				br.Msg = "更新二级分类关联设置失败"
+				br.ErrMsg = "更新二级分类关联设置失败, Err: " + e.Error()
+				return
+			}
+		}
+	*/
 	// 为二级分类时, 更新父级分类是否含电话会字段
 	if req.ParentId > 0 {
+		//二级分类才能修改权限
+		err = models.EditChartPermissionSearchKeyWordMappingMulti(item.ClassifyName, req.ChartPermissionIdList)
+		if err != nil {
+			br.Msg = "修改分类权限失败"
+			br.ErrMsg = "修改分类权限失败,Err:" + err.Error()
+			return
+		}
 		go func() {
-			_ = services.UpdateParentClassifyHasTel(req.ClassifyId, req.ParentId, req.HasTeleconference)
+			_ = services.EditClassifyChartPermissionSync(item.ClassifyName)
 		}()
+		/*go func() {
+			_ = services.UpdateParentClassifyHasTel(req.ClassifyId, req.ParentId, req.HasTeleconference)
+		}()*/
 	}
 
 	// 更新报告分类名称/父级分类后
@@ -507,7 +548,7 @@ func (this *ClassifyController) Edit() {
 	}()
 
 	// 获取编辑前子目录列表
-	classifyId := item.Id
+	/*classifyId := item.Id
 	var menuCond string
 	var menuPars []interface{}
 	menuCond += ` AND classify_id = ?`
@@ -567,7 +608,7 @@ func (this *ClassifyController) Edit() {
 			br.ErrMsg = "新增子目录关联失败, Err:" + e.Error()
 			return
 		}
-	}
+	}*/
 
 	// 关联电话会选项被更改时, 同步FICC活动分类
 	//if originRelateTel != req.RelateTel {
@@ -675,8 +716,6 @@ func (this *ClassifyController) FindByIdClassify() {
 
 // @Title 获取分类列表
 // @Description 获取分类列表
-// @Param   PageSize   query   int  true       "每页数据条数"
-// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
 // @Param   KeyWord   query   string  true       "检索关键词"
 // @Param   CompanyType   query   string  false       "产品类型,枚举值:'ficc','权益';不传默认返回全部"
 // @Param   HideDayWeek   query   int  false       "是否隐藏晨周报"
@@ -688,32 +727,22 @@ func (this *ClassifyController) ListClassify() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-	pageSize, _ := this.GetInt("PageSize")
-	currentIndex, _ := this.GetInt("CurrentIndex")
+
 	keyWord := this.GetString("KeyWord")
 	companyType := this.GetString("CompanyType")
 	hideDayWeek, _ := this.GetInt("HideDayWeek")
+
+	reqEnabled, _ := this.GetInt("Enabled", -1)
 	// 商家不隐藏晨周报
 	if utils.BusinessCode != utils.BusinessCodeRelease {
 		hideDayWeek = 0
 	}
-
-	var startSize int
-	if pageSize <= 0 {
-		pageSize = utils.PageSize20
-	}
-	if currentIndex <= 0 {
-		currentIndex = 1
+	enabled := -1
+	if reqEnabled == 1 {
+		enabled = reqEnabled
 	}
 
-	startSize = utils.StartIndex(currentIndex, pageSize)
-	list, err := models.GetClassifyList(startSize, pageSize, keyWord, companyType, hideDayWeek)
-	if err != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取失败,Err:" + err.Error()
-		return
-	}
-	total, err := models.GetClassifyListCount(keyWord, companyType, hideDayWeek)
+	list, err := models.GetClassifyList(keyWord, companyType, hideDayWeek, enabled)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -727,8 +756,7 @@ func (this *ClassifyController) ListClassify() {
 	parentIdLen := len(parentIds)
 	if parentIdLen == 0 {
 		resp := &models.ClassifyListResp{
-			List:   list,
-			Paging: paging.GetPaging(currentIndex, pageSize, 0),
+			List: list,
 		}
 		br.Data = resp
 		br.Ret = 200
@@ -757,7 +785,7 @@ func (this *ClassifyController) ListClassify() {
 	}
 
 	// 获取子分类
-	children, e := models.GetClassifyChildByParentIds(parentIds, keyWord)
+	children, e := models.GetClassifyChildByParentIds(parentIds, keyWord, enabled)
 	if e != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取子分类失败"
@@ -787,16 +815,29 @@ func (this *ClassifyController) ListClassify() {
 		}
 	}
 
+	// 查询分类绑定的权限
+	permissionList, _ := models.GetAllPermissionMapping()
+	classifyPermissionMap := make(map[string][]int, 0)
+	if len(permissionList) > 0 {
+		for _, v := range permissionList {
+			classifyPermissionMap[v.KeyWord] = append(classifyPermissionMap[v.KeyWord], v.ChartPermissionId)
+		}
+	}
 	// 二级分类
 	childrenMap := make(map[int][]*models.ClassifyItem, 0)
 	for i := range children {
+
 		if childrenMap[children[i].ParentId] == nil {
 			childrenMap[children[i].ParentId] = make([]*models.ClassifyItem, 0)
 		}
-		childrenMap[children[i].ParentId] = append(childrenMap[children[i].ParentId], &models.ClassifyItem{
+		tmp := &models.ClassifyItem{
 			Classify:       *children[i],
 			ClassifyMenuId: relateMap[children[i].Id],
-		})
+		}
+		if permissionIds, ok := classifyPermissionMap[children[i].ClassifyName]; ok {
+			tmp.ChartPermissionIdList = permissionIds
+		}
+		childrenMap[children[i].ParentId] = append(childrenMap[children[i].ParentId], tmp)
 	}
 
 	// 一级分类
@@ -805,10 +846,8 @@ func (this *ClassifyController) ListClassify() {
 		list[i].Child = childrenMap[list[i].Id]
 	}
 
-	page := paging.GetPaging(currentIndex, pageSize, total)
 	resp := new(models.ClassifyListResp)
 	resp.List = list
-	resp.Paging = page
 	br.Data = resp
 	br.Ret = 200
 	br.Success = true
@@ -900,7 +939,9 @@ func (this *ClassifyController) EditClassifyPermission() {
 		br.ErrMsg = "修改分类权限失败,Err:" + err.Error()
 		return
 	}
-
+	go func() {
+		_ = services.EditClassifyChartPermissionSync(classifyInfo.ClassifyName)
+	}()
 	// 是关联电话会二级分类修改了权限, 同步关联报告电话会的分类
 	//if classifyInfo.ParentId > 0 && classifyInfo.RelateTel == 1 {
 	//	go func() {
@@ -955,3 +996,93 @@ func (this *ClassifyController) TelListClassify() {
 	br.Success = true
 	br.Msg = "获取成功"
 }
+
+// Move
+// @Title 移动分类接口
+// @Description 移动分类
+// @Param	request	body models.ClassifyMoveReq true "type json string"
+// @Success 200 新增成功
+// @router /move [post]
+func (this *ClassifyController) Move() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.ClassifyMoveReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	e, msg := services.MoveReportClassify(req)
+	if e != nil {
+		br.Msg = msg
+		br.ErrMsg = "移动分类失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// SetEnabled
+// @Title 启用/禁用分类接口
+// @Description 启用/禁用分类
+// @Param	request	body models.ClassifyMoveReq true "type json string"
+// @Success 200 新增成功
+// @router /enabled/set [post]
+func (this *ClassifyController) SetEnabled() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.ClassifySetEnabledReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	if req.Enabled != 0 && req.Enabled != 1 {
+		br.Msg = "请选择正确的启用禁用状态"
+		return
+	}
+	item, err := models.GetClassifyById(req.ClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在"
+			return
+		}
+		br.Msg = "获取信息失败"
+		br.ErrMsg = "获取信息失败,Err:" + err.Error()
+		return
+	}
+	if item == nil {
+		br.Msg = "分类不存在"
+		return
+	}
+	ob := new(models.Classify)
+	//设置分类启用、禁用状态
+	err = ob.SetEnabled(req.ClassifyId, req.Enabled)
+	if err != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "操作失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 8 - 1
controllers/company_permission.go

@@ -52,7 +52,7 @@ func (this *CompanyPermissionController) List() {
 
 	// FICC
 	if productId == utils.COMPANY_PRODUCT_FICC_ID || companyType == utils.COMPANY_PRODUCT_FICC_NAME {
-		for _, v := range utils.PermissionFiccClassifyArr {
+		/*for _, v := range utils.PermissionFiccClassifyArr {
 			checkList := make([]int, 0)
 			p := new(company.PermissionSetList)
 			p.ClassifyName = v
@@ -68,7 +68,14 @@ func (this *CompanyPermissionController) List() {
 			}
 			p.CheckList = checkList
 			resp.List = append(resp.List, p)
+		}*/
+		list, err := services.GetChartPermissionEnabledList()
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取权限信息失败,Err:" + err.Error()
+			return
 		}
+		resp.List = list
 		br.Ret = 200
 		br.Success = true
 		br.Msg = "获取成功"

+ 9 - 0
controllers/data_manage/edb_info.go

@@ -5372,6 +5372,13 @@ func (this *EdbInfoController) EdbInfoBatchAdd() {
 			return
 		}
 
+		terminalCode, serverUrl, _, e := data.GetEdbTerminalCodeBySource(v.Source, v.EdbCode, v.StockCode)
+		if e != nil {
+			br.Msg = "获取可以使用的终端地址失败"
+			br.ErrMsg = "获取可以使用的终端地址失败,Err:" + e.Error()
+			return
+		}
+
 		edbInfoItem := new(data_manage.EdbInfo)
 		edbInfoItem.Source = v.Source
 		switch v.Source {
@@ -5392,6 +5399,8 @@ func (this *EdbInfoController) EdbInfoBatchAdd() {
 		edbInfoItem.SysUserRealName = sysUser.RealName
 		edbInfoItem.IndicatorCode = v.EdbCode
 		edbInfoItem.StockCode = v.StockCode
+		edbInfoItem.TerminalCode = terminalCode
+		edbInfoItem.ServerUrl = serverUrl
 
 		// 指标入库
 		edbInfo, err, errMsg, isSendEmail := data.EdbInfoWsdAdd(edbInfoItem)

+ 166 - 25
controllers/english_report/en_permission.go

@@ -2,10 +2,11 @@ package english_report
 
 import (
 	"encoding/json"
-	"fmt"
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
+	"eta/eta_api/services"
 	"eta/eta_api/utils"
+	"fmt"
 	"strings"
 	"time"
 )
@@ -44,17 +45,22 @@ func (this *EnPermissionController) Add() {
 		br.ErrMsg = "参数解析失败,Err:" + e.Error()
 		return
 	}
-	req.EnPermissionName = strings.TrimSpace(req.EnPermissionName)
-	if req.EnPermissionName == "" {
+	req.PermissionName = strings.TrimSpace(req.PermissionName)
+	if req.PermissionName == "" {
 		br.Msg = "请输入品种名称"
 		return
 	}
 
+	if req.Enabled != 1 && req.Enabled != 0 {
+		br.Msg = "请设置正确的状态"
+		return
+	}
+
 	// 重名校验
 	existOB := new(models.EnPermission)
 	existCond := fmt.Sprintf(` AND %s = ? AND %s = ?`, models.EnPermissionColumns.EnPermissionName, models.EnPermissionColumns.ParentId)
 	existPars := make([]interface{}, 0)
-	existPars = append(existPars, req.EnPermissionName, req.ParentId)
+	existPars = append(existPars, req.PermissionName, req.ParentId)
 	exist, e := existOB.GetItemByCondition(existCond, existPars)
 	if e != nil && e.Error() != utils.ErrNoRow() {
 		br.Msg = "操作失败"
@@ -65,12 +71,19 @@ func (this *EnPermissionController) Add() {
 		br.Msg = "品种名称已存在"
 		return
 	}
-
-	// 新增
+	// 获取最大的排序值
 	item := new(models.EnPermission)
-	item.EnPermissionName = req.EnPermissionName
+	maxSort, e := item.GetMaxSort()
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "查询品种排序失败, Err: " + e.Error()
+		return
+	}
+	// 新增
+	item.EnPermissionName = req.PermissionName
 	item.ParentId = req.ParentId
-	item.Sort = req.Sort
+	item.Sort = maxSort + 1
+	item.Enabled = req.Enabled
 	item.CreateTime = time.Now().Local()
 	item.ModifyTime = time.Now().Local()
 	if e = item.Create(); e != nil {
@@ -114,12 +127,12 @@ func (this *EnPermissionController) Edit() {
 		br.ErrMsg = "参数解析失败,Err:" + e.Error()
 		return
 	}
-	if req.EnPermissionId <= 0 {
+	if req.PermissionId <= 0 {
 		br.Msg = "参数有误"
 		return
 	}
-	req.EnPermissionName = strings.TrimSpace(req.EnPermissionName)
-	if req.EnPermissionName == "" {
+	req.PermissionName = strings.TrimSpace(req.PermissionName)
+	if req.PermissionName == "" {
 		br.Msg = "请输入品种名称"
 		return
 	}
@@ -128,7 +141,7 @@ func (this *EnPermissionController) Edit() {
 	ob := new(models.EnPermission)
 	existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s <> ?`, models.EnPermissionColumns.EnPermissionName, models.EnPermissionColumns.ParentId, models.EnPermissionColumns.EnPermissionId)
 	existPars := make([]interface{}, 0)
-	existPars = append(existPars, req.EnPermissionName, req.ParentId, req.EnPermissionId)
+	existPars = append(existPars, req.PermissionName, req.ParentId, req.PermissionId)
 	exist, e := ob.GetItemByCondition(existCond, existPars)
 	if e != nil && e.Error() != utils.ErrNoRow() {
 		br.Msg = "操作失败"
@@ -140,7 +153,7 @@ func (this *EnPermissionController) Edit() {
 		return
 	}
 
-	item, e := ob.GetItemById(req.EnPermissionId)
+	item, e := ob.GetItemById(req.PermissionId)
 	if e != nil {
 		if e.Error() == utils.ErrNoRow() {
 			br.Msg = "品种不存在, 请刷新页面"
@@ -151,11 +164,10 @@ func (this *EnPermissionController) Edit() {
 		return
 	}
 	originPid := item.ParentId
-	item.EnPermissionName = req.EnPermissionName
+	item.EnPermissionName = req.PermissionName
 	item.ParentId = req.ParentId
-	item.Sort = req.Sort
 	item.ModifyTime = time.Now().Local()
-	if e = item.Update([]string{"EnPermissionName", "ParentId", "Sort", "ModifyTime"}); e != nil {
+	if e = item.Update([]string{"EnPermissionName", "ParentId", "ModifyTime"}); e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "更新品种权限失败, Err: " + e.Error()
 		return
@@ -212,8 +224,8 @@ func (this *EnPermissionController) ParentList() {
 	resp := make([]*models.EnPermissionItem, 0)
 	for _, v := range list {
 		t := new(models.EnPermissionItem)
-		t.EnPermissionId = v.EnPermissionId
-		t.EnPermissionName = v.EnPermissionName
+		t.PermissionId = v.EnPermissionId
+		t.PermissionName = v.EnPermissionName
 		t.CnPermissionName = v.CnPermissionName
 		t.ParentId = v.ParentId
 		t.Sort = v.Sort
@@ -255,6 +267,8 @@ func (this *EnPermissionController) List() {
 	keyword := this.GetString("Keyword", "")
 	keyword = strings.TrimSpace(keyword)
 
+	enabled, _ := this.GetInt("Enabled", -1)
+
 	// 禁用指定报告、指定路演无权限的品种
 	reportId, _ := this.GetInt("ReportId", 0)
 	videoId, _ := this.GetInt("VideoId", 0)
@@ -297,9 +311,10 @@ func (this *EnPermissionController) List() {
 	childMap := make(map[int][]*models.EnPermissionItem)
 	for _, v := range list {
 		t := new(models.EnPermissionItem)
-		t.EnPermissionId = v.EnPermissionId
-		t.EnPermissionName = v.EnPermissionName
+		t.PermissionId = v.EnPermissionId
+		t.PermissionName = v.EnPermissionName
 		t.CnPermissionName = v.CnPermissionName
+		t.Enabled = v.Enabled
 		t.ParentId = v.ParentId
 		t.Sort = v.Sort
 		t.CreateTime = v.CreateTime.Format(utils.FormatDateTime)
@@ -309,20 +324,24 @@ func (this *EnPermissionController) List() {
 			continue
 		}
 		if v.ParentId > 0 {
+			if v.Enabled == 0 && enabled == 1 {
+				continue
+			}
 			if childMap[v.ParentId] == nil {
 				childMap[v.ParentId] = make([]*models.EnPermissionItem, 0)
 			}
 			// 无权限则隐藏
-			if (reportId > 0 || videoId > 0) && !utils.InArrayByInt(limitIds, t.EnPermissionId) {
+			if (reportId > 0 || videoId > 0) && !utils.InArrayByInt(limitIds, t.PermissionId) {
 				continue
 			}
 			childMap[v.ParentId] = append(childMap[v.ParentId], t)
 		}
 	}
 	for _, r := range resp {
-		r.Child = childMap[r.EnPermissionId]
+		r.Child = childMap[r.PermissionId]
 	}
 
+	// todo 过滤禁用品种的一级分类,如果一级分类没有二级分类
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
@@ -357,13 +376,13 @@ func (this *EnPermissionController) Remove() {
 		br.ErrMsg = "参数解析失败,Err:" + e.Error()
 		return
 	}
-	if req.EnPermissionId <= 0 {
+	if req.PermissionId <= 0 {
 		br.Msg = "参数有误"
 		return
 	}
 
 	ob := new(models.EnPermission)
-	item, e := ob.GetItemById(req.EnPermissionId)
+	item, e := ob.GetItemById(req.PermissionId)
 	if e != nil {
 		if e.Error() == utils.ErrNoRow() {
 			br.Msg = "品种不存在, 请刷新页面"
@@ -377,7 +396,7 @@ func (this *EnPermissionController) Remove() {
 	// 校验是否有子品种
 	childCond := fmt.Sprintf(` AND %s = ?`, models.EnPermissionColumns.ParentId)
 	childPars := make([]interface{}, 0)
-	childPars = append(childPars, req.EnPermissionId)
+	childPars = append(childPars, req.PermissionId)
 	num, e := ob.GetCountByCondition(childCond, childPars)
 	if e != nil {
 		br.Msg = "操作失败"
@@ -418,3 +437,125 @@ func (this *EnPermissionController) Remove() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
+
+// Move
+// @Title 移动品种权限
+// @Description 移动品种权限
+// @Param	request	body models.EnPermissionMoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /move [post]
+func (this *EnPermissionController) Move() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.EnPermissionMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.PermissionId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	e, msg := services.MoveEnPermission(req)
+	if e != nil {
+		br.Msg = msg
+		br.ErrMsg = "移动品种失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// SetEnabled
+// @Title 启用/禁用品种
+// @Description 启用/禁用品种
+// @Param	request	body models.EnPermissionEnabledReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /enabled/set [post]
+func (this *EnPermissionController) SetEnabled() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EnPermissionEnabledReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.PermissionId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	// 重名校验
+	ob := new(models.EnPermission)
+
+	item, e := ob.GetItemById(req.PermissionId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "品种不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取品种权限失败, Err: " + e.Error()
+		return
+	}
+	item.Enabled = req.Enabled
+	item.ModifyTime = time.Now().Local()
+	if e = item.Update([]string{"Enabled", "ModifyTime"}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新品种权限失败, Err: " + e.Error()
+		return
+	}
+
+	// 如果是一级品种被启用,则所有二级设置成启用, 如果一级品种被禁用,则所有二级设置成禁用
+	if item.ParentId == 0 {
+		if e = item.SetEnabled(item.EnPermissionId, req.Enabled); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新英文分类权限失败, Err: " + e.Error()
+			return
+		}
+	} else if item.ParentId > 0 && req.Enabled == 1 {
+		// 如果二级品种被启用,则他的上级品种设置成启用
+		if e = item.SetEnabledByPermissionId(item.ParentId, req.Enabled); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新英文分类权限失败, Err: " + e.Error()
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.Data = item.EnPermissionId
+}

+ 193 - 67
controllers/english_report/english_classify.go

@@ -8,7 +8,6 @@ import (
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"sort"
 	"time"
 )
@@ -33,46 +32,24 @@ func (this *EnglishReportController) ListClassify() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-	pageSize, _ := this.GetInt("PageSize")
-	currentIndex, _ := this.GetInt("CurrentIndex")
-	keyWord := this.GetString("KeyWord")
 
-	var startSize int
-	if pageSize <= 0 {
-		pageSize = utils.PageSize20
-	}
-	if currentIndex <= 0 {
-		currentIndex = 1
+	keyWord := this.GetString("KeyWord")
+	reqEnabled, _ := this.GetInt("Enabled", -1)
+	enabled := -1
+	if reqEnabled == 1 {
+		enabled = reqEnabled
 	}
-
-	startSize = utils.StartIndex(currentIndex, pageSize)
-
-	page := paging.GetPaging(currentIndex, pageSize, 0)
 	resp := new(models.EnglishClassifyListResp)
 
 	// 处理一级分类分页的情况
-	rootList, err := models.GetEnglishClassifyRootId(startSize, pageSize, keyWord)
+	rootList, err := models.GetEnglishClassifyRootId(keyWord, enabled)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
 		return
 	}
-	var ids []int
-	var rootIds []int
-	rootMap := make(map[int]struct{}, 0)
-	for _, v := range rootList {
-		rootIds = append(rootIds, v.Id)
-		rootMap[v.Id] = struct{}{}
-	}
-	total, err := models.GetEnglishClassifyListCount(keyWord)
-	if err != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取失败,Err:" + err.Error()
-		return
-	}
-	if total == 0 {
+	if len(rootList) == 0 {
 		resp.List = make([]*models.EnglishClassifyList, 0)
-		resp.Paging = page
 
 		br.Data = resp
 		br.Ret = 200
@@ -80,10 +57,16 @@ func (this *EnglishReportController) ListClassify() {
 		br.Msg = "获取成功"
 		return
 	}
-	page = paging.GetPaging(currentIndex, pageSize, total)
+	var ids []int
+	var rootIds []int
+	rootMap := make(map[int]struct{}, 0)
+	for _, v := range rootList {
+		rootIds = append(rootIds, v.Id)
+		rootMap[v.Id] = struct{}{}
+	}
 
 	//获取相关的分类ID
-	idList, err := models.GetEnglishClassifyListByRootId(rootIds, keyWord)
+	idList, err := models.GetEnglishClassifyListByRootId(rootIds, keyWord, enabled)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -177,7 +160,6 @@ func (this *EnglishReportController) ListClassify() {
 	sort.Sort(sortList)
 
 	resp.List = sortList
-	resp.Paging = page
 
 	br.Data = resp
 	br.Ret = 200
@@ -191,17 +173,25 @@ func (this *EnglishReportController) ListClassify() {
 // @Param   ParentId   query   int  true       "父级Id 添加父级时为0"
 // @Param   Sort   query   string  false       "排序"
 // @Success 200 新增成功
-// @router /classify/add [get]
+// @router /classify/add [post]
 func (this *EnglishReportController) AddClassify() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-	classifyName := this.GetString("ClassifyName")
-	parentId, _ := this.GetInt("ParentId")
-	sort, _ := this.GetInt("Sort")
-
+	//classifyName := this.GetString("ClassifyName")
+	//parentId, _ := this.GetInt("ParentId")
+	var req models.EnClassifyAddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	classifyName := req.ClassifyName
+	parentId := req.ParentId
+	ob := new(models.EnglishClassify)
 	// 查新父级分类是否存在
 	rootId := 0
 	if parentId > 0 {
@@ -221,15 +211,23 @@ func (this *EnglishReportController) AddClassify() {
 			rootId = parentClassify.RootId
 		}
 	}
+	maxSort, e := ob.GetMaxSort()
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "查询品种排序失败, Err: " + e.Error()
+		return
+	}
 
+	nowTime := time.Now().Local()
 	item := &models.EnglishClassify{
 		ClassifyName: classifyName,
-		Sort:         sort,
+		Sort:         maxSort + 1,
 		ParentId:     parentId,
 		RootId:       rootId,
-		CreateTime:   time.Now(),
-		ModifyTime:   time.Now(),
+		CreateTime:   nowTime,
+		ModifyTime:   nowTime,
 		IsShow:       1,
+		Enabled:      1,
 	}
 	counts, err := models.GetEnglishClassifyCountsByName(classifyName, parentId)
 	if err != nil {
@@ -249,8 +247,8 @@ func (this *EnglishReportController) AddClassify() {
 		br.ErrMsg = "保存失败,Err:" + err.Error()
 		return
 	}
+	item.Id = int(newId)
 	if parentId == 0 { //一级目录的rootId等于自己本身
-		item.Id = int(newId)
 		item.RootId = int(newId)
 		err = item.UpdateEnglishClassify([]string{"RootId"})
 		if err != nil {
@@ -260,11 +258,28 @@ func (this *EnglishReportController) AddClassify() {
 		}
 	}
 
+	if item.ParentId != item.RootId && item.ParentId > 0 { //三级分类才能绑定品种权限
+		permissions := make([]*models.EnClassifyPermission, 0)
+		for _, p := range req.EnPermissions {
+			v := new(models.EnClassifyPermission)
+			v.EnPermissionId = p
+			v.EnClassifyId = item.Id
+			v.CreateTime = nowTime
+			permissions = append(permissions, v)
+		}
+		if e = models.CreateEnClassifyPermissions(permissions); e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "更新分类品种权限失败, Err: " + e.Error()
+			return
+		}
+	}
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "新增成功"
 }
 
+// EditClassify
 // @Title 编辑分类
 // @Description 编辑分类接口
 // @Param   ClassifyId   int  true       "分类Id"
@@ -272,17 +287,25 @@ func (this *EnglishReportController) AddClassify() {
 // @Param   ParentId   query   int  true       "父级Id 添加父级时为0"
 // @Param   Sort   query   string  false       "排序"
 // @Success 200 保存成功
-// @router /classify/edit [get]
+// @router /classify/edit [post]
 func (this *EnglishReportController) EditClassify() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-	classifyId, _ := this.GetInt("ClassifyId")
-	classifyName := this.GetString("ClassifyName")
-	parentId, _ := this.GetInt("ParentId")
-	sort, _ := this.GetInt("Sort")
+
+	var req models.EnClassifyEditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	classifyId := req.ClassifyId
+	classifyName := req.ClassifyName
+	parentId := req.ParentId
+	//ob := new(models.EnglishClassify)
 
 	if parentId == classifyId {
 		br.Msg = "上级分类不能选择自己"
@@ -359,14 +382,24 @@ func (this *EnglishReportController) EditClassify() {
 	if parentId == 0 { //一级分类的顶级分类为自己的ID
 		rootId = oldItem.Id
 	}
+
+	/*maxSort, e := ob.GetMaxSortByParentId(parentId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "查询品种排序失败, Err: " + e.Error()
+		return
+	}*/
+
+	nowTime := time.Now().Local()
+
 	item := &models.EnglishClassify{
 		Id:           oldItem.Id,
 		ClassifyName: classifyName,
-		Sort:         sort,
-		ParentId:     parentId,
-		RootId:       rootId,
-		ModifyTime:   time.Now(),
-		IsShow:       1,
+		//	Sort:         maxSort + 1,
+		ParentId:   parentId,
+		RootId:     rootId,
+		ModifyTime: nowTime,
+		IsShow:     1,
 	}
 	{
 		// 更新研报里的分类名称
@@ -389,6 +422,23 @@ func (this *EnglishReportController) EditClassify() {
 		br.ErrMsg = "保存失败,Err:" + err.Error()
 		return
 	}
+	// 更新权限
+	if item.ParentId != item.RootId && item.ParentId > 0 { //三级分类才能编辑品种
+		permissions := make([]*models.EnClassifyPermission, 0)
+		for _, p := range req.EnPermissions {
+			v := new(models.EnClassifyPermission)
+			v.EnPermissionId = p
+			v.EnClassifyId = req.ClassifyId
+			v.CreateTime = nowTime
+			permissions = append(permissions, v)
+		}
+		if e = models.ClearAndCreateEnClassifyPermissions(req.ClassifyId, permissions); e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "更新分类品种权限失败, Err: " + e.Error()
+			return
+		}
+	}
+
 	err = models.UpdateEnglishReportClassifyByFirstSecondClassifyId(classifyId, parentId)
 	if err != nil {
 		br.Msg = "保存失败"
@@ -511,19 +561,8 @@ func (this *EnglishReportController) FistListClassify() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-	pageSize, _ := this.GetInt("PageSize")
-	currentIndex, _ := this.GetInt("CurrentIndex")
-
-	var startSize int
-	if pageSize <= 0 {
-		pageSize = utils.PageSize20
-	}
-	if currentIndex <= 0 {
-		currentIndex = 1
-	}
 
-	startSize = utils.StartIndex(currentIndex, pageSize)
-	rootList, err := models.GetEnglishFirstClassifyList(startSize, pageSize)
+	rootList, err := models.GetEnglishFirstClassifyList()
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -535,12 +574,10 @@ func (this *EnglishReportController) FistListClassify() {
 		br.ErrMsg = "获取失败,Err:" + err.Error()
 		return
 	}
-	page := paging.GetPaging(currentIndex, pageSize, total)
 	resp := new(models.EnglishClassifyListResp)
 
 	if total == 0 {
 		resp.List = make([]*models.EnglishClassifyList, 0)
-		resp.Paging = page
 
 		br.Data = resp
 		br.Ret = 200
@@ -583,7 +620,6 @@ func (this *EnglishReportController) FistListClassify() {
 	sort.Sort(sortList)
 
 	resp.List = sortList
-	resp.Paging = page
 
 	br.Data = resp
 	br.Ret = 200
@@ -644,3 +680,93 @@ func (this *EnglishClassifyController) PermissionEdit() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
+
+// MoveClassify
+// @Title 移动分类接口
+// @Description 移动分类
+// @Param	request	body models.EnglishClassifyMoveReq true "type json string"
+// @Success 200 新增成功
+// @router /classify/move [post]
+func (this *EnglishReportController) MoveClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.EnglishClassifyMoveReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	e, msg := services.MoveEnglishReportClassify(req)
+	if e != nil {
+		br.Msg = msg
+		br.ErrMsg = "移动分类失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// SetEnabled
+// @Title 启用/禁用分类接口
+// @Description 启用/禁用分类
+// @Param	request	body models.ClassifyMoveReq true "type json string"
+// @Success 200 新增成功
+// @router /classify/enabled/set [post]
+func (this *EnglishReportController) SetEnabled() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.EnglishClassifySetEnabledReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	if req.Enabled != 0 && req.Enabled != 1 {
+		br.Msg = "请选择正确的启用禁用状态"
+		return
+	}
+	item, err := models.GetEnglishReportClassifyById(req.ClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在"
+			return
+		}
+		br.Msg = "获取信息失败"
+		br.ErrMsg = "获取信息失败,Err:" + err.Error()
+		return
+	}
+	if item == nil {
+		br.Msg = "分类不存在"
+		return
+	}
+	ob := new(models.EnglishClassify)
+	//设置分类启用、禁用状态
+	err = ob.SetEnabled(req.ClassifyId, req.Enabled)
+	if err != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "操作失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 44 - 32
controllers/report.go

@@ -592,22 +592,24 @@ func (this *ReportController) Add() {
 	}
 
 	//处理权限
-	if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
-		go func() {
-			permissionItems, e := models.GetPermission(req.ClassifyNameSecond)
+	//if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
+	go func() {
+		permissionItems, e := models.GetPermission(req.ClassifyNameSecond)
+		if e != nil {
+			alarm_msg.SendAlarmMsg("获取权限失败,Err:"+e.Error(), 3)
+			return
+		}
+		for _, v := range permissionItems {
+			e = models.AddChartPermissionChapterMapping(v.ChartPermissionId, newReportId)
 			if e != nil {
-				alarm_msg.SendAlarmMsg("获取权限失败,Err:"+e.Error(), 3)
+				alarm_msg.SendAlarmMsg("新增权限失败,Err:"+e.Error(), 3)
 				return
 			}
-			for _, v := range permissionItems {
-				e = models.AddChartPermissionChapterMapping(v.ChartPermissionId, newReportId)
-				if e != nil {
-					alarm_msg.SendAlarmMsg("新增权限失败,Err:"+e.Error(), 3)
-					return
-				}
-			}
-		}()
-	}
+		}
+		// 同步crm权限
+		_ = services.EditReportPermissionSync(newReportId, req.ClassifyNameSecond)
+	}()
+	//}
 
 	recordItem := &models.ReportStateRecord{
 		ReportId:   int(newReportId),
@@ -755,27 +757,29 @@ func (this *ReportController) Edit() {
 	}
 
 	//处理权限
-	if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
-		go func() {
-			e := models.RemoveChartPermissionChapterMapping(req.ReportId)
-			if e != nil {
-				alarm_msg.SendAlarmMsg("修改删除报告权限失败,Err:"+e.Error(), 3)
-				return
-			}
-			permissionItems, e := models.GetPermission(req.ClassifyNameSecond)
+	//if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
+	go func() {
+		e := models.RemoveChartPermissionChapterMapping(req.ReportId)
+		if e != nil {
+			alarm_msg.SendAlarmMsg("修改删除报告权限失败,Err:"+e.Error(), 3)
+			return
+		}
+		permissionItems, e := models.GetPermission(req.ClassifyNameSecond)
+		if e != nil {
+			alarm_msg.SendAlarmMsg("获取权限失败,Err:"+e.Error(), 3)
+			return
+		}
+		for _, v := range permissionItems {
+			e = models.AddChartPermissionChapterMapping(v.ChartPermissionId, req.ReportId)
 			if e != nil {
-				alarm_msg.SendAlarmMsg("获取权限失败,Err:"+e.Error(), 3)
+				alarm_msg.SendAlarmMsg("新增权限失败,Err:"+e.Error(), 3)
 				return
 			}
-			for _, v := range permissionItems {
-				e = models.AddChartPermissionChapterMapping(v.ChartPermissionId, req.ReportId)
-				if e != nil {
-					alarm_msg.SendAlarmMsg("新增权限失败,Err:"+e.Error(), 3)
-					return
-				}
-			}
-		}()
-	}
+		}
+		// 同步crm权限
+		_ = services.EditReportPermissionSync(req.ReportId, req.ClassifyNameSecond)
+	}()
+	//}
 
 	reportCode := utils.MD5(strconv.Itoa(int(req.ReportId)))
 	resp := new(models.EditResp)
@@ -1821,7 +1825,11 @@ func (this *ReportController) SetDayWeekReportUpdateRule() {
 		br.ErrMsg = "设置暂停时间失败, Err: " + err.Error()
 		return
 	}
-
+	// 同步到crm数据库
+	go func() {
+		var syncReq services.ChapterTypeSyncReq
+		_, _ = services.ReportChapterTypeSync(&syncReq)
+	}()
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "操作成功"
@@ -1876,6 +1884,10 @@ func (this *ReportController) SetDayWeekReportEnableRule() {
 		br.ErrMsg = "设置永久停更失败, Err: " + err.Error()
 		return
 	}
+	go func() {
+		var syncReq services.ChapterTypeSyncReq
+		_, _ = services.ReportChapterTypeSync(&syncReq)
+	}()
 
 	br.Ret = 200
 	br.Success = true

+ 239 - 57
controllers/report_chapter_type.go

@@ -2,8 +2,8 @@ package controllers
 
 import (
 	"encoding/json"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"eta/eta_api/models"
+	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"time"
 )
@@ -39,35 +39,29 @@ func (this *ReportChapterTypeController) List() {
 		br.Msg = "请选择报告类型"
 		return
 	}
-
-	pageSize, _ := this.GetInt("PageSize")
-	currentIndex, _ := this.GetInt("CurrentIndex")
-	var startSize int
-	if pageSize <= 0 {
-		pageSize = utils.PageSize20
-	}
-	if currentIndex <= 0 {
-		currentIndex = 1
-	}
-	startSize = paging.StartIndex(currentIndex, pageSize)
-
 	cond := ` AND research_type = ?`
 	pars := make([]interface{}, 0)
 	pars = append(pars, reportType)
-	total, e := models.GetReportChapterTypeCount(cond, pars)
+	list, e := models.GetReportChapterTypePageList(cond, pars)
 	if e != nil {
 		br.Msg = "获取失败"
-		br.ErrMsg = "获取报告章节列表总数失败, Err: " + e.Error()
+		br.ErrMsg = "获取报告章节列表失败, Err: " + e.Error()
 		return
 	}
-	list, e := models.GetReportChapterTypePageList(cond, pars, startSize, pageSize)
+	mappingList, e := models.GetChapterTypePermissionByResearchType(reportType)
 	if e != nil {
 		br.Msg = "获取失败"
-		br.ErrMsg = "获取报告章节列表失败, Err: " + e.Error()
+		br.ErrMsg = "获取章节类型权限列表失败, Err: " + e.Error()
 		return
 	}
+	mappingMap := make(map[int][]int)
+	for _, v := range mappingList {
+		mappingMap[v.ReportChapterTypeId] = append(mappingMap[v.ReportChapterTypeId], v.ChartPermissionId)
+	}
+
 	respList := make([]*models.ReportChapterTypeListItem, 0)
 	for i := range list {
+		permissionIds, _ := mappingMap[list[i].ReportChapterTypeId]
 		respList = append(respList, &models.ReportChapterTypeListItem{
 			ReportChapterTypeId:   list[i].ReportChapterTypeId,
 			ReportChapterTypeName: list[i].ReportChapterTypeName,
@@ -79,13 +73,13 @@ func (this *ReportChapterTypeController) List() {
 			WordsImage:            list[i].YbBottomIcon, // 此处的不一样
 			EditImgUrl:            list[i].EditImgUrl,
 			IsShow:                list[i].IsShow,
+			Enabled:               list[i].Enabled,
+			ChartPermissionIdList: permissionIds,
 		})
 	}
 
-	resp := new(models.ReportChapterTypePageListResp)
-	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(models.ReportChapterTypeListResp)
 	resp.List = respList
-	resp.Paging = page
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
@@ -144,38 +138,83 @@ func (this *ReportChapterTypeController) Add() {
 
 	nowTime := time.Now().Local()
 	item := new(models.ReportChapterType)
+	maxSort, e := item.GetMaxSort()
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取章节最大排序失败, Err:" + e.Error()
+		return
+	}
 	item.ReportChapterTypeName = req.ReportChapterTypeName
-	item.Sort = req.Sort
+	item.Sort = maxSort + 1
 	item.Enabled = 1
 	item.CreatedTime = nowTime
 	item.LastUpdatedTime = nowTime
 	item.ResearchType = req.ResearchType
-	item.ReportChapterTypeName = req.ReportChapterTypeName
-	item.SelectedImage = req.SelectedImage
-	item.UnselectedImage = req.UnselectedImage
-	item.PcSelectedImage = req.SelectedImage
-	item.PcUnselectedImage = req.UnselectedImage
-	item.EditImgUrl = req.EditImgUrl
 	item.IsSet = 0
-	item.YbIconUrl = req.UnselectedImage
-	item.YbBottomIcon = req.WordsImage
-	item.IsShow = req.IsShow
-	item.ReportChapterTypeThumb = req.EditImgUrl
-	item.BannerUrl = req.UnselectedImage
 	item.ReportChapterTypeKey = req.ReportChapterTypeName
 	item.TickerTitle = req.ReportChapterTypeName
+
 	if e = item.Create(); e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "新增报告章节失败, Err:" + e.Error()
 		return
 	}
 
+	// todo 更新章节权限
+	cond = ` and product_id=1`
+	pars = make([]interface{}, 0)
+	permissionList, e := services.GetChartPermissionList(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取品种列表失败, Err: " + e.Error()
+		return
+	}
+	permissionIdName := make(map[int]string)
+	for i := range permissionList {
+		permissionIdName[permissionList[i].ChartPermissionId] = permissionList[i].PermissionName
+	}
+
+	researchType := item.ResearchType
+	newPermissions := make([]*models.ReportChapterTypePermission, 0)       // 报告章节权限表(新)
+	newWeekPermissions := make([]*models.ChartPermissionChapterMapping, 0) // 报告章节权限表(老)
+	for i := range req.ChartPermissionIdList {
+		newPermissions = append(newPermissions, &models.ReportChapterTypePermission{
+			ReportChapterTypeId:   item.ReportChapterTypeId,
+			ReportChapterTypeName: item.ReportChapterTypeName,
+			ChartPermissionId:     req.ChartPermissionIdList[i],
+			PermissionName:        permissionIdName[req.ChartPermissionIdList[i]],
+			ResearchType:          researchType,
+			CreatedTime:           nowTime,
+		})
+		if researchType == utils.REPORT_TYPE_WEEK {
+			newWeekPermissions = append(newWeekPermissions, &models.ChartPermissionChapterMapping{
+				ChartPermissionId:   req.ChartPermissionIdList[i],
+				ReportChapterTypeId: item.ReportChapterTypeId,
+				ResearchType:        researchType,
+			})
+		}
+	}
+
+	// 设置权限
+	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, researchType, newPermissions, newWeekPermissions)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "设置章节类型权限失败, Err: " + e.Error()
+		return
+	}
 	// 清除小程序端的章节缓存
 	{
 		key := "hongze_yb:report_chapter_type:GetEffectTypeID"
 		_ = utils.Rc.Delete(key)
 	}
 
+	//todo 同步更新crm章节权限和章节类型
+	go func() {
+		var syncReq services.ChapterTypeSyncReq
+		syncReq.ResearchType = researchType
+		syncReq.ReportChapterTypeId = item.ReportChapterTypeId
+		_, _ = services.ReportChapterTypeSync(&syncReq)
+	}()
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "操作成功"
@@ -242,36 +281,54 @@ func (this *ReportChapterTypeController) Edit() {
 		return
 	}
 	originName := item.ReportChapterTypeName
+	//更新章节权限
+	// todo 更新章节权限
+	cond = ` and product_id=1`
+	pars = make([]interface{}, 0)
+	permissionList, e := services.GetChartPermissionList(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取品种列表失败, Err: " + e.Error()
+		return
+	}
+	permissionIdName := make(map[int]string)
+	nowTime := time.Now().Local()
+	for i := range permissionList {
+		permissionIdName[permissionList[i].ChartPermissionId] = permissionList[i].PermissionName
+	}
 
-	item.ReportChapterTypeName = req.ReportChapterTypeName
-	item.Sort = req.Sort
-	item.ResearchType = req.ResearchType
-	item.SelectedImage = req.SelectedImage
-	item.UnselectedImage = req.UnselectedImage
-	item.PcSelectedImage = req.SelectedImage
-	item.PcUnselectedImage = req.UnselectedImage
-	item.EditImgUrl = req.EditImgUrl
-	item.YbIconUrl = req.UnselectedImage
-	item.YbBottomIcon = req.WordsImage
-	item.IsShow = req.IsShow
-	item.ReportChapterTypeThumb = req.EditImgUrl
-	item.BannerUrl = req.UnselectedImage
-	item.ReportChapterTypeKey = req.ReportChapterTypeName
-	item.TickerTitle = req.ReportChapterTypeName
-	updateCols := []string{"ReportChapterTypeName", "Sort", "ResearchType", "SelectedImage", "UnselectedImage",
-		"PcSelectedImage", "PcUnselectedImage", "EditImgUrl", "YbIconUrl", "YbBottomIcon", "IsShow",
-		"ReportChapterTypeThumb", "BannerUrl", "ReportChapterTypeKey", "TickerTitle",
+	researchType := item.ResearchType
+	newPermissions := make([]*models.ReportChapterTypePermission, 0)       // 报告章节权限表(新)
+	newWeekPermissions := make([]*models.ChartPermissionChapterMapping, 0) // 报告章节权限表(老)
+	for i := range req.ChartPermissionIdList {
+		newPermissions = append(newPermissions, &models.ReportChapterTypePermission{
+			ReportChapterTypeId:   item.ReportChapterTypeId,
+			ReportChapterTypeName: item.ReportChapterTypeName,
+			ChartPermissionId:     req.ChartPermissionIdList[i],
+			PermissionName:        permissionIdName[req.ChartPermissionIdList[i]],
+			ResearchType:          researchType,
+			CreatedTime:           nowTime,
+		})
+		if researchType == utils.REPORT_TYPE_WEEK {
+			newWeekPermissions = append(newWeekPermissions, &models.ChartPermissionChapterMapping{
+				ChartPermissionId:   req.ChartPermissionIdList[i],
+				ReportChapterTypeId: item.ReportChapterTypeId,
+				ResearchType:        researchType,
+			})
+		}
 	}
-	if e = item.Update(updateCols); e != nil {
+
+	// 设置权限
+	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, researchType, newPermissions, newWeekPermissions)
+	if e != nil {
 		br.Msg = "操作失败"
-		br.ErrMsg = "更新报告章节失败, Err:" + e.Error()
+		br.ErrMsg = "设置章节类型权限失败, Err: " + e.Error()
 		return
 	}
-
 	// 更新研报章节表冗余
 	if originName != req.ReportChapterTypeName {
 		go func() {
-			_ = models.UpdateReportChapterTypeNameByTypeId(item.ReportChapterTypeId, req.ReportChapterTypeName)
+			_ = models.UpdateReportChapterTypeNameByTypeId(req.ReportChapterTypeId, req.ReportChapterTypeName)
 		}()
 	}
 
@@ -281,6 +338,12 @@ func (this *ReportChapterTypeController) Edit() {
 		_ = utils.Rc.Delete(key)
 	}
 
+	go func() {
+		var syncReq services.ChapterTypeSyncReq
+		syncReq.ResearchType = researchType
+		syncReq.ReportChapterTypeId = item.ReportChapterTypeId
+		_, _ = services.ReportChapterTypeSync(&syncReq)
+	}()
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "操作成功"
@@ -398,10 +461,12 @@ func (this *ReportChapterTypeController) AuthSetting() {
 		br.Msg = "章节不存在或已被删除"
 		return
 	}
-	permissionList, e := models.GetChartPermissionList()
+	cond := ` and product_id=1`
+	pars := make([]interface{}, 0)
+	permissionList, e := services.GetChartPermissionList(cond, pars)
 	if e != nil {
-		br.Msg = "操作失败"
-		br.ErrMsg = "获取权限列表失败, Err: " + e.Error()
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取品种列表失败, Err: " + e.Error()
 		return
 	}
 	permissionIdName := make(map[int]string)
@@ -500,3 +565,120 @@ func (this *ReportChapterTypeController) PermissionList() {
 	br.Msg = "获取成功"
 	br.Data = respList
 }
+
+// Move
+// @Title 移动章节类型
+// @Description 移动章节类型
+// @Param	request	body models.PermissionMoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /chapter_type/move [post]
+func (this *ReportChapterTypeController) Move() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.ReportChapterTypeMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	if req.ReportChapterTypeId == 0 {
+		br.Msg = "请选择要移动的章节类型"
+		return
+	}
+	e, msg := services.MoveReportChapterType(&req)
+	if e != nil {
+		br.Msg = msg
+		br.ErrMsg = "移动品种失败, Err: " + e.Error()
+		return
+	}
+	go func() {
+		var syncReq services.ChapterTypeSyncReq
+		_, _ = services.ReportChapterTypeSync(&syncReq)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// SetEnabled
+// @Title 设置启用/禁用
+// @Description 移动章节类型
+// @Param	request	body models.PermissionMoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /chapter_type/enabled/set [post]
+func (this *ReportChapterTypeController) SetEnabled() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.ReportChapterTypeEnabledReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	if req.ReportChapterTypeId == 0 {
+		br.Msg = "请选择正确的章节类型"
+		return
+	}
+	if req.Enabled != 0 && req.Enabled != 1 {
+		br.Msg = "请选择正确的启用禁用状态"
+		return
+	}
+	ob := new(models.ReportChapterType)
+	item, err := models.GetReportChapterTypeById(req.ReportChapterTypeId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "章节不存在"
+			br.ErrMsg = "章节不存在, Err:" + err.Error()
+			return
+		}
+		br.Msg = "获取章节信息失败"
+		br.ErrMsg = "获取章节信息失败, Err:" + err.Error()
+		return
+	}
+	if item == nil {
+		br.Msg = "章节不存在"
+		return
+	}
+	//设置分类启用、禁用状态
+	err = ob.SetEnabled(req.ReportChapterTypeId, req.Enabled)
+	if err != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "设置报告章节状态失败, Err: " + err.Error()
+		return
+	}
+	go func() {
+		var syncReq services.ChapterTypeSyncReq
+		_, _ = services.ReportChapterTypeSync(&syncReq)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 1141 - 0
controllers/speech_recognition/speech_recognition.go

@@ -0,0 +1,1141 @@
+package speech_recognition
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/speech_recognition"
+	"eta/eta_api/services"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"os"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type SpeechRecognitionController struct {
+	controllers.BaseAuthController
+}
+
+type SpeechRecognitionCommonController struct {
+	controllers.BaseCommonController
+}
+
+// RecTaskCallback
+// @Title 语音识别回调
+// @Description 语音识别回调
+// @Param	request	body services.TencentRecTaskCallback true "type json string"
+// @Success 200 string "操作成功"
+// @router /rec_task/callback [post]
+func (this *SpeechRecognitionCommonController) RecTaskCallback() {
+	// 此接口返回指定响应体
+	br := new(services.TencentRecTaskCallbackResp)
+	var errMsg string
+	defer func() {
+		if errMsg != "" {
+			br.Code = 403
+			br.Message = "回调失败"
+			go alarm_msg.SendAlarmMsg(fmt.Sprintf("语音识别回调失败, ErrMsg: %s", errMsg), 1)
+		} else {
+			br.Code = 0
+			br.Message = "success"
+		}
+		_ = this.JSON(br, false, false)
+	}()
+
+	code, _ := this.GetInt("code", -1)
+	requestId, _ := this.GetInt("requestId", 0)
+	detail := this.GetString("resultDetail")
+	utils.FileLog.Info(fmt.Sprintf("RecTaskCallback, requestId: %d", requestId))
+
+	// 获取taskId对应的API请求及语音识别
+	logOb := new(speech_recognition.SpeechRecognitionApiLog)
+	cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionApiLogCols.RequestId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, requestId)
+	apiLog, e := logOb.GetItemByCondition(cond, pars, "")
+	if e != nil {
+		errMsg = "获取API记录失败"
+		utils.FileLog.Info("API回调-获取请求记录失败, Err: " + e.Error())
+		return
+	}
+	speechOb := new(speech_recognition.SpeechRecognition)
+	speechItem, e := speechOb.GetItemById(apiLog.SpeechRecognitionId)
+	if e != nil {
+		errMsg = "获取语音识别失败"
+		utils.FileLog.Info("获取语音识别失败, Err: " + e.Error())
+		return
+	}
+
+	// API结果返回有误
+	nowTime := time.Now().Local()
+	if code != speech_recognition.ApiRequestCodeSuccess {
+		convertRemark := speech_recognition.ApiErrMsgMapping[code]
+		if convertRemark == "" {
+			convertRemark = fmt.Sprintf("未知错误: %d", code)
+		}
+		speechItem.ConvertRemark = convertRemark
+		speechItem.State = speech_recognition.SpeechRecognitionStateFail
+		speechItem.ModifyTime = nowTime
+		speechCols := []string{speech_recognition.SpeechRecognitionCols.ConvertRemark, speech_recognition.SpeechRecognitionCols.State, speech_recognition.SpeechRecognitionCols.ModifyTime}
+
+		apiLog.RequestCode = code
+		apiLog.RequestResult = convertRemark
+		apiLog.ModifyTime = nowTime
+		apiLogCols := []string{speech_recognition.SpeechRecognitionApiLogCols.RequestCode, speech_recognition.SpeechRecognitionApiLogCols.RequestResult, speech_recognition.SpeechRecognitionApiLogCols.ModifyTime}
+
+		// 更新语音识别及API记录
+		if e := speech_recognition.UpdateSpeechAndApiLog(speechItem, speechCols, apiLog, apiLogCols); e != nil {
+			errMsg = "更新API返回结果失败"
+			utils.FileLog.Info("更新API返回结果失败, Err: " + e.Error())
+		}
+		return
+	}
+
+	// 解析转写段落内容
+	sentences := make([]*services.TencentRecTaskSentenceDetail, 0)
+	if e := json.Unmarshal([]byte(detail), &sentences); e != nil {
+		errMsg = "解析语音识别内容失败"
+		utils.FileLog.Info("解析语音识别内容失败, Err: " + e.Error())
+		return
+	}
+	contents := make([]*speech_recognition.SpeechRecognitionContent, 0)
+	sorts := 0 // API返回的结果本身是已排过序的
+	var abstract string
+	var abstractLimit int
+	for _, v := range sentences {
+		sorts += 1
+		t := new(speech_recognition.SpeechRecognitionContent)
+		t.SpeechRecognitionId = speechItem.SpeechRecognitionId
+		t.Sort = sorts
+		t.Content = v.FinalSentence
+		t.StartMs = v.StartMs
+		t.EndMs = v.EndMs
+		t.CreateTime = nowTime
+		t.ModifyTime = nowTime
+		contents = append(contents, t)
+		// 取前几段作为摘要保存
+		if abstractLimit < 5 {
+			abstractLimit += 1
+			abstract += v.FinalSentence
+		}
+	}
+
+	speechItem.Abstract = abstract
+	speechItem.State = speech_recognition.SpeechRecognitionStateSuccess
+	speechItem.ModifyTime = nowTime
+	speechCols := []string{speech_recognition.SpeechRecognitionCols.Abstract, speech_recognition.SpeechRecognitionCols.State, speech_recognition.SpeechRecognitionCols.ModifyTime}
+
+	apiLog.RequestCode = code
+	apiLog.RequestResult = detail
+	apiLog.ModifyTime = time.Now().Local()
+	apiLogCols := []string{speech_recognition.SpeechRecognitionApiLogCols.RequestCode, speech_recognition.SpeechRecognitionApiLogCols.RequestResult, speech_recognition.SpeechRecognitionApiLogCols.ModifyTime}
+
+	// 新增解析内容并更新语音识别及API记录
+	if e := speech_recognition.CreateContentAndUpdateSpeechAndApiLog(contents, speechItem, speechCols, apiLog, apiLogCols); e != nil {
+		errMsg = "新增API返回结果失败"
+		utils.FileLog.Info("新增API返回结果失败, Err: " + e.Error())
+	}
+}
+
+// Convert
+// @Title 语音转换
+// @Description 语音转换
+// @Param	request	body speech_recognition.SpeechRecognitionConvertReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /convert [post]
+func (this *SpeechRecognitionController) Convert() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionConvertReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MenuId <= 0 {
+		br.Msg = "请选择目录"
+		return
+	}
+	if len(req.Files) == 0 {
+		br.Msg = "请上传转写文件"
+		return
+	}
+	for _, r := range req.Files {
+		if r.FileName == "" && r.ResourceUrl == "" {
+			br.Msg = "转写文件有误,请检查"
+			return
+		}
+	}
+	sortMax, e := services.GetSpeechMenuMaxSort(req.MenuId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取语音识别目录下最大排序失败, Err: " + e.Error()
+		return
+	}
+
+	speeches := make([]*speech_recognition.SpeechRecognition, 0)
+	nowTime := time.Now().Local()
+	for _, v := range req.Files {
+		sortMax += 1
+		t := new(speech_recognition.SpeechRecognition)
+		t.FileName = v.FileName
+		t.ResourceUrl = v.ResourceUrl
+		t.MenuId = req.MenuId
+		timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+		t.UniqueCode = utils.MD5(fmt.Sprintf("%s_%s", t.TableName(), timestamp))
+		t.SysUserId = sysUser.AdminId
+		t.SysUserName = sysUser.RealName
+		t.State = speech_recognition.SpeechRecognitionStateWait
+		t.Sort = sortMax
+		t.FileSecond = v.FileSecond
+		t.FileSize = v.FileSize
+		t.CreateTime = nowTime
+		t.ModifyTime = nowTime
+		// CreateMulti拿不到主键, 此处用循环新增获取
+		if e := t.Create(); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "批量新增转写文件失败, Err: " + e.Error()
+			return
+		}
+		speeches = append(speeches, t)
+	}
+
+	// 批量转写语音
+	go func() {
+		services.BatchConvertSpeech(speeches)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// ConvertList
+// @Title 转换列表
+// @Description 转换列表
+// @Success 200 {object} speech_recognition.SpeechRecognitionItem
+// @router /convert_list [get]
+func (this *SpeechRecognitionController) ConvertList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	// 仅取待转换和转换失败的
+	states := []int{speech_recognition.SpeechRecognitionStateWait, speech_recognition.SpeechRecognitionStateFail}
+	speechOb := new(speech_recognition.SpeechRecognition)
+	cond := fmt.Sprintf(` AND %s = ? AND %s IN (%s)`, speech_recognition.SpeechRecognitionCols.SysUserId, speech_recognition.SpeechRecognitionCols.State, utils.GetOrmInReplace(len(states)))
+	pars := make([]interface{}, 0)
+	pars = append(pars, sysUser.AdminId, states)
+	list, e := speechOb.GetItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取转写文件列表失败, Err: " + e.Error()
+		return
+	}
+	resp := make([]*speech_recognition.SpeechRecognitionItem, 0)
+	for _, v := range list {
+		resp = append(resp, speech_recognition.FormatSpeechRecognition2Item(v))
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Save
+// @Title 保存内容
+// @Description 保存内容
+// @Param	request	body speech_recognition.SpeechRecognitionSaveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /save [post]
+func (this *SpeechRecognitionController) Save() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionSaveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.SpeechRecognitionId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SpeechRecognitionId: %d", req.SpeechRecognitionId)
+		return
+	}
+	if len(req.Contents) == 0 {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+	req.FileName = strings.TrimSpace(req.FileName)
+	if req.FileName == "" {
+		br.Msg = "请输入文件名称"
+		return
+	}
+
+	speechOb := new(speech_recognition.SpeechRecognition)
+	speechItem, e := speechOb.GetItemById(req.SpeechRecognitionId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "转写文件不存在,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取转写文件失败, Err: " + e.Error()
+		return
+	}
+	if req.FileName != speechItem.FileName {
+		// 校验重名
+		{
+			cond := fmt.Sprintf(` AND %s = ? AND %s <> ?`, speech_recognition.SpeechRecognitionCols.FileName, speech_recognition.SpeechRecognitionCols.SpeechRecognitionId)
+			pars := make([]interface{}, 0)
+			pars = append(pars, req.FileName, req.SpeechRecognitionId)
+			exists, e := speechOb.GetItemByCondition(cond, pars, "")
+			if e != nil && e.Error() != utils.ErrNoRow() {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取同名转写文件失败, Err: " + e.Error()
+				return
+			}
+			if exists != nil && exists.SpeechRecognitionId > 0 {
+				br.Msg = "文件名称已存在,请重新输入"
+				return
+			}
+		}
+	}
+
+	// 取前几段作为摘要保存
+	var abstractNum int
+	var abstract string
+	for _, v := range req.Contents {
+		if abstractNum < 5 {
+			abstractNum += 1
+			abstract += v.Content
+		}
+	}
+	speechItem.FileName = req.FileName
+	speechItem.Abstract = abstract
+	speechItem.ModifyTime = time.Now().Local()
+	speechCols := []string{speech_recognition.SpeechRecognitionCols.FileName, speech_recognition.SpeechRecognitionCols.Abstract, speech_recognition.SpeechRecognitionCols.ModifyTime}
+
+	// 标签
+	tagMappings := make([]*speech_recognition.SpeechRecognitionTagMapping, 0)
+	for _, v := range req.TagIds {
+		tagMappings = append(tagMappings, &speech_recognition.SpeechRecognitionTagMapping{
+			SpeechRecognitionId: req.SpeechRecognitionId,
+			TagId:               v,
+		})
+	}
+
+	// 保存修改
+	if e = speechOb.SpeechSave(speechItem, speechCols, req.Contents, tagMappings); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "语音识别保存失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// RemoveFile
+// @Title (软)删除文件
+// @Description (软)删除文件
+// @Param	request	body speech_recognition.SpeechRecognitionRemoveFileReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /remove_file [post]
+func (this *SpeechRecognitionController) RemoveFile() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionRemoveFileReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.SpeechRecognitionId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SpeechRecognitionId: %d", req.SpeechRecognitionId)
+		return
+	}
+
+	speechOb := new(speech_recognition.SpeechRecognition)
+	speechItem, e := speechOb.GetItemById(req.SpeechRecognitionId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "转写文件不存在,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取转写文件失败, Err: " + e.Error()
+		return
+	}
+
+	speechItem.FileState = speech_recognition.SpeechRecognitionFileRemoveFlag
+	speechItem.ModifyTime = time.Now().Local()
+	updateCols := []string{speech_recognition.SpeechRecognitionCols.FileState, speech_recognition.SpeechRecognitionCols.ModifyTime}
+	if e = speechItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "软删除转写文件失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Rename
+// @Title 重命名
+// @Description 重命名
+// @Param	request	body speech_recognition.SpeechRecognitionRenameReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /rename [post]
+func (this *SpeechRecognitionController) Rename() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionRenameReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.SpeechRecognitionId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SpeechRecognitionId: %d", req.SpeechRecognitionId)
+		return
+	}
+	req.FileName = strings.TrimSpace(req.FileName)
+	if req.FileName == "" {
+		br.Msg = "请输入文件名称"
+		return
+	}
+
+	speechOb := new(speech_recognition.SpeechRecognition)
+	speechItem, e := speechOb.GetItemById(req.SpeechRecognitionId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "转写文件不存在,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取转写文件失败, Err: " + e.Error()
+		return
+	}
+
+	// 重名校验
+	{
+		cond := fmt.Sprintf(` AND %s = ? AND %s <> ?`, speech_recognition.SpeechRecognitionCols.FileName, speech_recognition.SpeechRecognitionCols.SpeechRecognitionId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.FileName, req.SpeechRecognitionId)
+		exists, e := speechOb.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取同名转写文件失败, Err: " + e.Error()
+			return
+		}
+		if exists != nil && exists.SpeechRecognitionId > 0 {
+			br.Msg = "文件名称已存在,请重新输入"
+			return
+		}
+	}
+
+	speechItem.FileName = req.FileName
+	speechItem.ModifyTime = time.Now().Local()
+	updateCols := []string{speech_recognition.SpeechRecognitionCols.FileName, speech_recognition.SpeechRecognitionCols.ModifyTime}
+	if e = speechItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "转写文件重命名失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Remove
+// @Title 删除
+// @Description 删除
+// @Param	request	body speech_recognition.SpeechRecognitionRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /remove [post]
+func (this *SpeechRecognitionController) Remove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.SpeechRecognitionId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SpeechRecognitionId: %d", req.SpeechRecognitionId)
+		return
+	}
+
+	speechOb := new(speech_recognition.SpeechRecognition)
+	speechItem, e := speechOb.GetItemById(req.SpeechRecognitionId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取转写文件失败, Err: " + e.Error()
+		return
+	}
+
+	if e = speechItem.Del(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "删除转写文件失败, Err: " + e.Error()
+		return
+	}
+
+	// 清除关联
+	go func() {
+		contentOb := new(speech_recognition.SpeechRecognitionContent)
+		_ = contentOb.ClearContentBySpeechId(req.SpeechRecognitionId)
+		mappingOb := new(speech_recognition.SpeechRecognitionTagMapping)
+		_ = mappingOb.ClearMappingBySpeechId(req.SpeechRecognitionId)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// SaveTag
+// @Title 保存标签
+// @Description 保存标签
+// @Param	request	body speech_recognition.SpeechRecognitionSaveTagReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /save_tag [post]
+func (this *SpeechRecognitionController) SaveTag() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionSaveTagReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.SpeechRecognitionId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SpeechRecognitionId: %d", req.SpeechRecognitionId)
+		return
+	}
+
+	speechOb := new(speech_recognition.SpeechRecognition)
+	_, e := speechOb.GetItemById(req.SpeechRecognitionId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "转写文件不存在,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取转写文件失败, Err: " + e.Error()
+		return
+	}
+
+	// 清除原标签
+	mappingOb := new(speech_recognition.SpeechRecognitionTagMapping)
+	if e = mappingOb.ClearMappingBySpeechId(req.SpeechRecognitionId); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "清除转写文件标签失败, Err: " + e.Error()
+		return
+	}
+
+	// 保存新标签
+	if len(req.TagIds) > 0 {
+		mappings := make([]*speech_recognition.SpeechRecognitionTagMapping, 0)
+		for _, v := range req.TagIds {
+			if v <= 0 {
+				continue
+			}
+			mappings = append(mappings, &speech_recognition.SpeechRecognitionTagMapping{
+				TagId:               v,
+				SpeechRecognitionId: req.SpeechRecognitionId,
+			})
+		}
+		if e = mappingOb.CreateMulti(mappings); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "批量新增转写文件标签失败, Err: " + e.Error()
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// List
+// @Title 语音识别列表
+// @Description 语音识别列表
+// @Param   FileName  query  string  false  "文件名称"
+// @Param   StartTime  query  string  false  "开始时间"
+// @Param   EndTime  query  string  false  "结束时间"
+// @Param   CreateUserIds  query  string  false  "创建人ID"
+// @Param   TagId  query  int  false  "标签ID"
+// @Param   TagIds  query  string  false  "标签ID"
+// @Param   MenuId  query  int  false  "目录ID"
+// @Param   IsTagMenu  query  bool  false  "是否为标签目录"
+// @Success 200 {object} speech_recognition.SpeechRecognitionListResp
+// @router /list [get]
+func (this *SpeechRecognitionController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	params := new(speech_recognition.SpeechRecognitionListReq)
+	if e := this.ParseForm(params); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	params.FileName = strings.TrimSpace(params.FileName)
+
+	dataResp := new(speech_recognition.SpeechRecognitionListResp)
+	cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionCols.State)
+	pars := make([]interface{}, 0)
+	pars = append(pars, speech_recognition.SpeechRecognitionStateSuccess)
+
+	// 筛选项
+	{
+		if params.FileName != "" {
+			cond += fmt.Sprintf(` AND %s LIKE ?`, speech_recognition.SpeechRecognitionCols.FileName)
+			pars = append(pars, fmt.Sprint("%", params.FileName, "%"))
+		}
+		if params.StartTime != "" && params.EndTime != "" {
+			_, e := time.Parse(utils.FormatDate, params.StartTime)
+			if e != nil {
+				br.Msg = "开始时间格式有误"
+				return
+			}
+			_, e = time.Parse(utils.FormatDate, params.EndTime)
+			if e != nil {
+				br.Msg = "结束时间格式有误"
+				return
+			}
+			st := fmt.Sprintf("%s 00:00:00", params.StartTime)
+			ed := fmt.Sprintf("%s 23:59:59", params.EndTime)
+			cond += fmt.Sprintf(` AND (%s BETWEEN ? AND ?)`, speech_recognition.SpeechRecognitionCols.CreateTime)
+			pars = append(pars, st, ed)
+		}
+		if params.CreateUserId != "" {
+			userArr := strings.Split(strings.TrimSpace(params.CreateUserId), ",")
+			if len(userArr) > 0 {
+				userIds := make([]int, 0)
+				for _, v := range userArr {
+					t, _ := strconv.Atoi(v)
+					userIds = append(userIds, t)
+				}
+				if len(userIds) == 0 {
+					br.Data = dataResp
+					br.Ret = 200
+					br.Success = true
+					br.Msg = "获取成功"
+					return
+				}
+				cond += fmt.Sprintf(` AND %s IN (%s)`, speech_recognition.SpeechRecognitionCols.SysUserId, utils.GetOrmInReplace(len(userIds)))
+				pars = append(pars, userIds)
+			}
+		}
+		// 语音识别目录-筛选子目录集合
+		if params.MenuId > 0 && !params.IsTagMenu {
+			{
+				menuOb := new(speech_recognition.SpeechRecognitionMenu)
+				menus, e := menuOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+				if e != nil {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取语音识别目录列表失败, Err: " + e.Error()
+					return
+				}
+				childIds := services.GetSpeechRecognitionMenuChildrenRecursive(menus, params.MenuId)
+				menuIds := make([]int, 0)
+				menuIds = append(menuIds, params.MenuId)
+				if len(childIds) > 0 {
+					menuIds = append(menuIds, childIds...)
+				}
+				cond += fmt.Sprintf(` AND %s IN (%s)`, speech_recognition.SpeechRecognitionCols.MenuId, utils.GetOrmInReplace(len(menuIds)))
+				pars = append(pars, menuIds)
+			}
+		}
+		// 标签目录-筛选目录下所有标签
+		tagIds := make([]int, 0)
+		if params.MenuId > 0 && params.IsTagMenu {
+			{
+				menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+				menus, e := menuOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+				if e != nil {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取标签目录列表失败, Err: " + e.Error()
+					return
+				}
+				childIds := services.GetSpeechRecognitionTagMenuChildrenRecursive(menus, params.MenuId)
+				menuIds := make([]int, 0)
+				menuIds = append(menuIds, params.MenuId)
+				if len(childIds) > 0 {
+					menuIds = append(menuIds, childIds...)
+				}
+				// 获取目录下所有标签
+				tagOb := new(speech_recognition.SpeechRecognitionTag)
+				ids, e := tagOb.GetTagIdsByMenuIds(menuIds)
+				if e != nil {
+					br.Msg = "获取失败"
+					br.ErrMsg = "通过目录IDs获取标签IDs失败, Err: " + e.Error()
+					return
+				}
+				// 此处查询无结果直接返回
+				if len(ids) == 0 {
+					br.Data = dataResp
+					br.Ret = 200
+					br.Success = true
+					br.Msg = "获取成功"
+					return
+				}
+				tagIds = ids
+			}
+		}
+
+		// 标签筛选
+		if params.TagId > 0 && params.TagIds == "" {
+			tagIds = append(tagIds, params.TagId)
+		}
+		if params.TagId <= 0 && params.TagIds != "" {
+			tagArr := strings.Split(params.TagIds, ",")
+			if len(tagArr) > 0 {
+				for _, v := range tagArr {
+					t, _ := strconv.Atoi(v)
+					tagIds = append(tagIds, t)
+				}
+			}
+		}
+		if len(tagIds) > 0 {
+			mappingOb := new(speech_recognition.SpeechRecognitionTagMapping)
+			tagSpeechIds, e := mappingOb.GetSpeechIdsByTagIds(tagIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取标签关联语音识别失败, Err: " + e.Error()
+				return
+			}
+			if len(tagSpeechIds) == 0 {
+				br.Data = dataResp
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "获取成功"
+				return
+			}
+			cond += fmt.Sprintf(` AND %s IN (%s)`, speech_recognition.SpeechRecognitionCols.SpeechRecognitionId, utils.GetOrmInReplace(len(tagSpeechIds)))
+			pars = append(pars, tagSpeechIds)
+		}
+	}
+
+	// 分页列表
+	speechOb := new(speech_recognition.SpeechRecognition)
+	total, e := speechOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取语音识别列表总数失败, Err: " + e.Error()
+		return
+	}
+	var startSize int
+	if params.PageSize <= 0 {
+		params.PageSize = utils.PageSize20
+	}
+	if params.CurrentIndex <= 0 {
+		params.CurrentIndex = 1
+	}
+	startSize = utils.StartIndex(params.CurrentIndex, params.PageSize)
+	list, e := speechOb.GetPageItemsByCondition(cond, pars, []string{}, "", startSize, params.PageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取语音识别列表失败, Err: " + e.Error()
+		return
+	}
+
+	// 获取标签
+	speechIds := make([]int, 0)
+	for _, v := range list {
+		speechIds = append(speechIds, v.SpeechRecognitionId)
+	}
+	mappingTags, e := speech_recognition.GetSpeechRecognitionTagsBySpeechIds(speechIds)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取语音识别列表标签失败, Err: " + e.Error()
+		return
+	}
+	speechDetailTags := make(map[int][]*speech_recognition.SpeechRecognitionDetailTag)
+	for _, v := range mappingTags {
+		if speechDetailTags[v.SpeechRecognitionId] == nil {
+			speechDetailTags[v.SpeechRecognitionId] = make([]*speech_recognition.SpeechRecognitionDetailTag, 0)
+		}
+		speechDetailTags[v.SpeechRecognitionId] = append(speechDetailTags[v.SpeechRecognitionId], &speech_recognition.SpeechRecognitionDetailTag{
+			TagId:   v.TagId,
+			TagName: v.TagName,
+		})
+	}
+
+	respList := make([]*speech_recognition.SpeechRecognitionDetailItem, 0)
+	for _, v := range list {
+		t := speech_recognition.FormatSpeechRecognition2DetailItem(v, make([]*speech_recognition.SpeechRecognitionContentItem, 0), speechDetailTags[v.SpeechRecognitionId], make([]*speech_recognition.SpeechRecognitionMenuItem, 0))
+		respList = append(respList, t)
+	}
+
+	page := paging.GetPaging(params.CurrentIndex, params.PageSize, total)
+	dataResp.Paging = page
+	dataResp.List = respList
+	br.Data = dataResp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Detail
+// @Title 语音识别详情
+// @Description 语音识别详情
+// @Param   SpeechRecognitionId  query  int  true  "语音识别ID"
+// @Success 200 {object} speech_recognition.SpeechRecognitionDetailItem
+// @router /detail [get]
+func (this *SpeechRecognitionController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	speechId, _ := this.GetInt("SpeechRecognitionId")
+	if speechId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SpeechRecognitionId: %d", speechId)
+		return
+	}
+
+	speechOb := new(speech_recognition.SpeechRecognition)
+	speechItem, e := speechOb.GetItemById(speechId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "转写文件不存在,请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取转写文件失败, Err: " + e.Error()
+		return
+	}
+
+	// 获取内容
+	contents := make([]*speech_recognition.SpeechRecognitionContentItem, 0)
+	{
+		contentOb := new(speech_recognition.SpeechRecognitionContent)
+		cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionContentCols.SpeechRecognitionId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, speechId)
+		list, e := contentOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", speech_recognition.SpeechRecognitionContentCols.Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取语音识别内容失败, Err: " + e.Error()
+			return
+		}
+		for _, v := range list {
+			if v.Content == "" {
+				continue
+			}
+			contents = append(contents, speech_recognition.FormatSpeechRecognitionContent2Item(v))
+		}
+	}
+
+	// 跟踪目录路径
+	menuPath := make([]*speech_recognition.SpeechRecognitionMenuItem, 0)
+	{
+		menuOb := new(speech_recognition.SpeechRecognitionMenu)
+		menus, e := menuOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, fmt.Sprintf("%s ASC, %s ASC", speech_recognition.SpeechRecognitionMenuCols.ParentId, speech_recognition.SpeechRecognitionMenuCols.Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取目录列表失败, Err: " + e.Error()
+			return
+		}
+		menuPath = services.GetSpeechRecognitionMenuPathRecursive(menus, speechItem.MenuId)
+		sort.Slice(menuPath, func(i, j int) bool {
+			return menuPath[i].Level < menuPath[j].Level
+		})
+	}
+
+	// 获取标签
+	tags, e := speech_recognition.GetSpeechRecognitionTagBySpeechId(speechId)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取语音识别标签失败, Err: " + e.Error()
+		return
+	}
+	detail := speech_recognition.FormatSpeechRecognition2DetailItem(speechItem, contents, tags, menuPath)
+
+	br.Data = detail
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Export
+// @Title 导出内容
+// @Description 导出内容
+// @Param	request	body speech_recognition.SpeechRecognitionContentExportReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /export [get]
+func (this *SpeechRecognitionController) Export() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	req := new(speech_recognition.SpeechRecognitionContentExportReq)
+	if e := this.ParseForm(req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.SpeechRecognitionId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SpeechRecognitionId: %d", req.SpeechRecognitionId)
+		return
+	}
+
+	speechOb := new(speech_recognition.SpeechRecognition)
+	speechItem, e := speechOb.GetItemById(req.SpeechRecognitionId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "转写文件不存在,请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取转写文件失败, Err: " + e.Error()
+		return
+	}
+
+	contentOb := new(speech_recognition.SpeechRecognitionContent)
+	cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionContentCols.SpeechRecognitionId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.SpeechRecognitionId)
+	contents, e := contentOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", speech_recognition.SpeechRecognitionContentCols.Sort))
+	if e != nil {
+		br.Msg = "导出失败"
+		br.ErrMsg = "获取语音识别内容失败, Err: " + e.Error()
+		return
+	}
+	if len(contents) == 0 {
+		br.Msg = "无内容导出"
+		return
+	}
+	if req.ExportType != services.SpeechRecognitionExportTypeTxt && req.ExportType != services.SpeechRecognitionExportTypeDocx && req.ExportType != services.SpeechRecognitionExportTypePdf {
+		br.Msg = "导出类型有误"
+		return
+	}
+
+	suffixMap := map[int]string{services.SpeechRecognitionExportTypeTxt: ".txt", services.SpeechRecognitionExportTypeDocx: ".docx", services.SpeechRecognitionExportTypePdf: ".pdf"}
+	suffix := suffixMap[req.ExportType]
+	if suffix == "" {
+		suffix = ".txt"
+	}
+	downloadPath, e := services.SpeechRecognitionContentExport(req.ExportType, req.Timestamp, speechItem.FileName, contents)
+	if e != nil {
+		br.Msg = "导出文件失败"
+		br.ErrMsg = "导出语音识别内容文件失败, Err: " + e.Error()
+		_ = os.Remove(downloadPath)
+		return
+	}
+	defer func() {
+		_ = os.Remove(downloadPath)
+	}()
+
+	downloadName := fmt.Sprintf("%s%s", speechItem.FileName, suffix)
+	this.Ctx.Output.Download(downloadPath, downloadName)
+}
+
+// CheckFileName
+// @Title 文件重名校验
+// @Description 文件重名校验
+// @Param	request	body speech_recognition.SpeechRecognitionConvertCheckNameReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /convert/check_name [post]
+func (this *SpeechRecognitionController) CheckFileName() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionConvertCheckNameReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	req.FileName = strings.TrimSpace(req.FileName)
+	if req.FileName == "" {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, FileName: %s", req.FileName)
+		return
+	}
+
+	checkResult := true
+	speechOb := new(speech_recognition.SpeechRecognition)
+	cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionCols.FileName)
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.FileName)
+	exists, e := speechOb.GetItemByCondition(cond, pars, "")
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取同名转写文件失败, Err: " + e.Error()
+		return
+	}
+	if exists != nil && exists.SpeechRecognitionId > 0 {
+		checkResult = false
+		br.Data = checkResult
+		br.Msg = fmt.Sprintf("相同文件名称已存在: %s", req.FileName)
+		return
+	}
+
+	br.Data = checkResult
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 534 - 0
controllers/speech_recognition/speech_recognition_menu.go

@@ -0,0 +1,534 @@
+package speech_recognition
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/speech_recognition"
+	"eta/eta_api/services"
+	"eta/eta_api/utils"
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type SpeechRecognitionMenuController struct {
+	controllers.BaseAuthController
+}
+
+// Add
+// @Title 新增分类
+// @Description 新增分类
+// @Param	request	body speech_recognition.SpeechRecognitionMenuAddReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /menu/add [post]
+func (this *SpeechRecognitionMenuController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionMenuAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	req.MenuName = strings.TrimSpace(req.MenuName)
+	if req.MenuName == "" {
+		br.Msg = "请输入目录名称"
+		return
+	}
+
+	// 校验同级目录是否有重名
+	menuOb := new(speech_recognition.SpeechRecognitionMenu)
+	{
+		cond := fmt.Sprintf(` AND %s = ? AND %s = ?`, speech_recognition.SpeechRecognitionMenuCols.MenuName, speech_recognition.SpeechRecognitionMenuCols.ParentId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.MenuName, req.ParentId)
+		exists, e := menuOb.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取同名目录失败, Err: " + e.Error()
+			return
+		}
+		if exists != nil && exists.SpeechRecognitionMenuId > 0 {
+			br.Msg = "分类名称已存在,请重新输入"
+			return
+		}
+	}
+
+	// 获取目录层级和同级最大排序
+	level := 1
+	rootId := 0
+	{
+		if req.ParentId > 0 {
+			parentMenu, e := menuOb.GetItemById(req.ParentId)
+			if e != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取父级目录失败, Err: " + e.Error()
+				return
+			}
+			level += parentMenu.Level
+			rootId = parentMenu.RootId
+		}
+	}
+	sortMax, e := services.GetSpeechMenuMaxSort(req.ParentId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取语音识别目录下最大排序失败, Err: " + e.Error()
+		return
+	}
+
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	menuOb.UniqueCode = utils.MD5(fmt.Sprintf("%s_%s", menuOb.TableName(), timestamp))
+	menuOb.MenuName = req.MenuName
+	menuOb.ParentId = req.ParentId
+	menuOb.Level = level
+	menuOb.Sort = sortMax + 1
+	menuOb.RootId = rootId
+	menuOb.CreateTime = time.Now().Local()
+	menuOb.ModifyTime = time.Now().Local()
+	if e = menuOb.Create(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "新增目录失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Edit
+// @Title 编辑分类
+// @Description 编辑分类
+// @Param	request	body speech_recognition.SpeechRecognitionMenuEditReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /menu/edit [post]
+func (this *SpeechRecognitionMenuController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionMenuEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MenuId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, MenuId: %d", req.MenuId)
+		return
+	}
+	req.MenuName = strings.TrimSpace(req.MenuName)
+	if req.MenuName == "" {
+		br.Msg = "请输入目录名称"
+		return
+	}
+
+	menuOb := new(speech_recognition.SpeechRecognitionMenu)
+	menuItem, e := menuOb.GetItemById(req.MenuId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "目录不存在,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取目录失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验同级目录是否有重名
+	{
+		cond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s <> ?`, speech_recognition.SpeechRecognitionMenuCols.MenuName, speech_recognition.SpeechRecognitionMenuCols.ParentId, speech_recognition.SpeechRecognitionMenuCols.SpeechRecognitionMenuId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.MenuName, menuItem.ParentId, req.MenuId)
+		exists, e := menuOb.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取同名目录失败, Err: " + e.Error()
+			return
+		}
+		if exists != nil && exists.SpeechRecognitionMenuId > 0 {
+			br.Msg = "分类名称已存在,请重新输入"
+			return
+		}
+	}
+
+	menuItem.MenuName = req.MenuName
+	menuItem.ModifyTime = time.Now().Local()
+	updateCols := []string{speech_recognition.SpeechRecognitionMenuCols.MenuName, speech_recognition.SpeechRecognitionMenuCols.ModifyTime}
+	if e = menuItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新目录失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Remove
+// @Title 删除分类
+// @Description 删除分类
+// @Param	request	body speech_recognition.SpeechRecognitionMenuRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /menu/remove [post]
+func (this *SpeechRecognitionMenuController) Remove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionMenuRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MenuId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, MenuId: %d", req.MenuId)
+		return
+	}
+
+	menuOb := new(speech_recognition.SpeechRecognitionMenu)
+	menuItem, e := menuOb.GetItemById(req.MenuId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取目录失败, Err: " + e.Error()
+		return
+	}
+
+	// 删除校验
+	checkResult, menuIds, e := services.CheckSpeechRecognitionMenuRemove(menuItem.SpeechRecognitionMenuId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "目录删除校验失败, Err: " + e.Error()
+		return
+	}
+
+	switch checkResult.CheckResult {
+	case services.SpeechMenuCheckRemoveTypePass:
+		// 可删除
+		if e = menuItem.Del(); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "删除目录失败, Err: " + e.Error()
+			return
+		}
+	case services.SpeechMenuCheckRemoveTypeRefused:
+		// 不可删除
+		br.Msg = checkResult.Tips
+		return
+	case services.SpeechMenuCheckRemoveTypeWarning:
+		// 删除目录及子目录
+		if e = menuOb.MultiDel(menuIds); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "批量删除目录失败, Err: " + e.Error()
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// RemoveCheck
+// @Title 删除校验
+// @Description 删除校验
+// @Param	request	body speech_recognition.SpeechRecognitionMenuRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /menu/remove_check [post]
+func (this *SpeechRecognitionMenuController) RemoveCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionMenuRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MenuId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, MenuId: %d", req.MenuId)
+		return
+	}
+
+	menuOb := new(speech_recognition.SpeechRecognitionMenu)
+	menuItem, e := menuOb.GetItemById(req.MenuId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取目录失败, Err: " + e.Error()
+		return
+	}
+
+	resp, _, e := services.CheckSpeechRecognitionMenuRemove(menuItem.SpeechRecognitionMenuId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "目录删除校验失败, Err: " + e.Error()
+		return
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// List
+// @Title 目录列表
+// @Description 目录列表
+// @Param   ParentId  query  int  false  "父级ID"
+// @Success 200 {object} speech_recognition.SpeechRecognitionMenuNodeItem
+// @router /menu/list [get]
+func (this *SpeechRecognitionMenuController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	// 前端采用懒加载, 所以只查询子目录和内容
+	parentId, _ := this.GetInt("ParentId")
+
+	level := 0
+	menuOb := new(speech_recognition.SpeechRecognitionMenu)
+	if parentId > 0 {
+		parentMenu, e := menuOb.GetItemById(parentId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取父级目录失败, Err: " + e.Error()
+			return
+		}
+		level = parentMenu.Level + 1
+	}
+	menus := make([]*speech_recognition.SpeechRecognitionMenu, 0)
+	{
+		cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionMenuCols.ParentId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, parentId)
+		list, e := menuOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", speech_recognition.SpeechRecognitionMenuCols.Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取目录列表失败, Err: " + e.Error()
+			return
+		}
+		menus = list
+	}
+
+	speeches := make([]*speech_recognition.SpeechRecognition, 0)
+	{
+		speechOb := new(speech_recognition.SpeechRecognition)
+		cond := fmt.Sprintf(` AND %s = ? AND %s = ?`, speech_recognition.SpeechRecognitionCols.MenuId, speech_recognition.SpeechRecognitionCols.State)
+		pars := make([]interface{}, 0)
+		pars = append(pars, parentId, speech_recognition.SpeechRecognitionStateSuccess)
+		list, e := speechOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC, %s DESC", speech_recognition.SpeechRecognitionCols.Sort, speech_recognition.SpeechRecognitionCols.CreateTime))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取语音识别列表失败, Err: " + e.Error()
+			return
+		}
+		speeches = list
+	}
+
+	resp := make([]*speech_recognition.SpeechRecognitionMenuNodeItem, 0)
+	if len(menus) == 0 && len(speeches) == 0 {
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	for _, v := range menus {
+		resp = append(resp, &speech_recognition.SpeechRecognitionMenuNodeItem{
+			UniqueCode: v.UniqueCode,
+			NodeType:   speech_recognition.SpeechRecognitionMenuNodeTypeDefault,
+			MenuId:     v.SpeechRecognitionMenuId,
+			MenuName:   v.MenuName,
+			ParentId:   v.ParentId,
+			Level:      v.Level,
+			Sort:       v.Sort,
+			CreateTime: utils.TimeTransferString(utils.FormatDateTime, v.CreateTime),
+		})
+	}
+	for _, v := range speeches {
+		resp = append(resp, &speech_recognition.SpeechRecognitionMenuNodeItem{
+			UniqueCode:            v.UniqueCode,
+			NodeType:              speech_recognition.SpeechRecognitionMenuNodeTypeSpeech,
+			SpeechRecognitionId:   v.SpeechRecognitionId,
+			SpeechRecognitionName: v.FileName,
+			ParentId:              v.MenuId,
+			Level:                 level,
+			Sort:                  v.Sort,
+			CreateTime:            utils.TimeTransferString(utils.FormatDateTime, v.CreateTime),
+		})
+	}
+	sort.Slice(resp, func(i, j int) bool {
+		return resp[i].Sort < resp[j].Sort
+	})
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Tree
+// @Title 目录树
+// @Description 目录树
+// @Success 200 {object} speech_recognition.SpeechRecognitionMenuItem
+// @router /menu/tree [get]
+func (this *SpeechRecognitionMenuController) Tree() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	menus := make([]*speech_recognition.SpeechRecognitionMenu, 0)
+	{
+		menuOb := new(speech_recognition.SpeechRecognitionMenu)
+		list, e := menuOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, fmt.Sprintf("%s ASC, %s ASC", speech_recognition.SpeechRecognitionMenuCols.ParentId, speech_recognition.SpeechRecognitionMenuCols.Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取目录列表失败, Err: " + e.Error()
+			return
+		}
+		menus = list
+	}
+
+	// 递归处理成目录树
+	resp := services.GetSpeechRecognitionMenuTreeRecursive(menus, 0)
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Move
+// @Title 移动目录/语音识别
+// @Description 移动目录/语音识别
+// @Param	request	body speech_recognition.SpeechRecognitionMenuMoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /menu/move [post]
+func (this *SpeechRecognitionMenuController) Move() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionMenuMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MenuId <= 0 && req.SpeechId <= 0 {
+		br.Msg = "请选择目录或语音识别"
+		return
+	}
+
+	e, _ := services.MoveSpeechMenu(req)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "移动目录/语音识别失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 384 - 0
controllers/speech_recognition/speech_recognition_tag.go

@@ -0,0 +1,384 @@
+package speech_recognition
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/speech_recognition"
+	"eta/eta_api/services"
+	"eta/eta_api/utils"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type SpeechRecognitionTagController struct {
+	controllers.BaseAuthController
+}
+
+// Add
+// @Title 新增标签
+// @Description 新增标签
+// @Param	request	body speech_recognition.SpeechRecognitionTagAddReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /tag/add [post]
+func (this *SpeechRecognitionTagController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionTagAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MenuId <= 0 {
+		br.Msg = "请选择标签目录"
+		return
+	}
+	req.TagName = strings.TrimSpace(req.TagName)
+	if req.TagName == "" {
+		br.Msg = "请输入目录名称"
+		return
+	}
+
+	// 校验标签是否有重名
+	tagOb := new(speech_recognition.SpeechRecognitionTag)
+	{
+		cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionTagCols.TagName)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.TagName)
+		exists, e := tagOb.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取同名标签失败, Err: " + e.Error()
+			return
+		}
+		if exists != nil && exists.SpeechRecognitionTagId > 0 {
+			br.Msg = "标签名称已存在,请重新输入"
+			return
+		}
+	}
+	sortMax, e := services.GetSpeechTagMenuMaxSort(req.MenuId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取标签目录下最大排序失败, Err: " + e.Error()
+		return
+	}
+
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	tagOb.UniqueCode = utils.MD5(fmt.Sprintf("%s_%s", tagOb.TableName(), timestamp))
+	tagOb.TagName = req.TagName
+	tagOb.MenuId = req.MenuId
+	tagOb.Sort = sortMax + 1
+	tagOb.CreateTime = time.Now().Local()
+	tagOb.ModifyTime = time.Now().Local()
+	if e := tagOb.Create(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "新增标签失败, Err: " + e.Error()
+		return
+	}
+
+	br.Data = tagOb.SpeechRecognitionTagId
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Edit
+// @Title 编辑标签
+// @Description 编辑标签
+// @Param	request	body speech_recognition.SpeechRecognitionTagEditReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /tag/edit [post]
+func (this *SpeechRecognitionTagController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionTagEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.TagId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, TagId: %d", req.TagId)
+		return
+	}
+	req.TagName = strings.TrimSpace(req.TagName)
+	if req.TagName == "" {
+		br.Msg = "请输入标签名称"
+		return
+	}
+
+	tagOb := new(speech_recognition.SpeechRecognitionTag)
+	tagItem, e := tagOb.GetItemById(req.TagId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "标签不存在,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取标签失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验标签是否有重名
+	{
+		cond := fmt.Sprintf(` AND %s = ? AND %s <> ?`, speech_recognition.SpeechRecognitionTagCols.TagName, speech_recognition.SpeechRecognitionTagCols.SpeechRecognitionTagId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.TagName, req.TagId)
+		exists, e := tagOb.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取同名标签失败, Err: " + e.Error()
+			return
+		}
+		if exists != nil && exists.SpeechRecognitionTagId > 0 {
+			br.Msg = "标签名称已存在,请重新输入"
+			return
+		}
+	}
+
+	tagItem.TagName = req.TagName
+	tagItem.ModifyTime = time.Now().Local()
+	updateCols := []string{speech_recognition.SpeechRecognitionTagCols.TagName, speech_recognition.SpeechRecognitionTagCols.ModifyTime}
+	if e = tagItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新标签失败, Err: " + e.Error()
+		return
+	}
+
+	br.Data = tagItem.SpeechRecognitionTagId
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Remove
+// @Title 删除标签
+// @Description 删除标签
+// @Param	request	body speech_recognition.SpeechRecognitionTagRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /tag/remove [post]
+func (this *SpeechRecognitionTagController) Remove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionTagRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.TagId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, TagId: %d", req.TagId)
+		return
+	}
+
+	tagOb := new(speech_recognition.SpeechRecognitionTag)
+	tagItem, e := tagOb.GetItemById(req.TagId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取标签失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验标签是否关联转写文件
+	{
+		mappingOb := new(speech_recognition.SpeechRecognitionTagMapping)
+		cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionTagMappingCols.TagId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.TagId)
+		count, e := mappingOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取标签关联转写文件数失败, Err: " + e.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "标签关联转写文件,删除失败!"
+			return
+		}
+	}
+
+	if e = tagItem.Del(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "删除标签失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// RemoveCheck
+// @Title 删除标签校验
+// @Description 删除标签校验
+// @Param	request	body speech_recognition.SpeechRecognitionTagRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /tag/remove_check [post]
+func (this *SpeechRecognitionTagController) RemoveCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionTagRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.TagId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, TagId: %d", req.TagId)
+		return
+	}
+
+	tagOb := new(speech_recognition.SpeechRecognitionTag)
+	_, e := tagOb.GetItemById(req.TagId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取标签失败, Err: " + e.Error()
+		return
+	}
+
+	resp := new(speech_recognition.SpeechRecognitionMenuRemoveCheckResp)
+	// 校验标签是否关联转写文件
+	mappingOb := new(speech_recognition.SpeechRecognitionTagMapping)
+	cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionTagMappingCols.TagId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.TagId)
+	count, e := mappingOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取标签关联转写文件数失败, Err: " + e.Error()
+		return
+	}
+	if count > 0 {
+		resp.CheckResult = 1
+		resp.Tips = "标签关联转写文件,删除失败!"
+	} else {
+		resp.Tips = "校验通过,可以删除"
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// List
+// @Title 标签列表
+// @Description 标签列表
+// @Param   Keywords  query  string  false  "标签名称"
+// @Success 200 {object} speech_recognition.SpeechRecognitionTagItem
+// @router /tag/list [get]
+func (this *SpeechRecognitionTagController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	keywords := this.GetString("Keywords")
+	keywords = strings.TrimSpace(keywords)
+
+	tagOb := new(speech_recognition.SpeechRecognitionTag)
+	cond := ``
+	pars := make([]interface{}, 0)
+	if keywords != "" {
+		kw := fmt.Sprint("%", keywords, "%")
+		cond = fmt.Sprintf(` AND %s LIKE ?`, speech_recognition.SpeechRecognitionTagCols.TagName)
+		pars = append(pars, kw)
+	}
+	list, e := tagOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s DESC", speech_recognition.SpeechRecognitionTagCols.CreateTime))
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取标签列表失败, Err: " + e.Error()
+		return
+	}
+
+	resp := make([]*speech_recognition.SpeechRecognitionTagItem, 0)
+	for _, v := range list {
+		resp = append(resp, speech_recognition.FormatSpeechRecognitionTag2Item(v))
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 535 - 0
controllers/speech_recognition/speech_recognition_tag_menu.go

@@ -0,0 +1,535 @@
+package speech_recognition
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/speech_recognition"
+	"eta/eta_api/services"
+	"eta/eta_api/utils"
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// SpeechRecognitionTagMenuController 标签目录
+type SpeechRecognitionTagMenuController struct {
+	controllers.BaseAuthController
+}
+
+// Add
+// @Title 新增分类
+// @Description 新增分类
+// @Param	request	body speech_recognition.SpeechRecognitionTagMenuAddReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /tag/menu/add [post]
+func (this *SpeechRecognitionTagMenuController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionTagMenuAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	req.MenuName = strings.TrimSpace(req.MenuName)
+	if req.MenuName == "" {
+		br.Msg = "请输入目录名称"
+		return
+	}
+
+	// 校验同级目录是否有重名
+	menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+	{
+		cond := fmt.Sprintf(` AND %s = ? AND %s = ?`, speech_recognition.SpeechRecognitionTagMenuCols.MenuName, speech_recognition.SpeechRecognitionTagMenuCols.ParentId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.MenuName, req.ParentId)
+		exists, e := menuOb.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取同名目录失败, Err: " + e.Error()
+			return
+		}
+		if exists != nil && exists.SpeechRecognitionTagMenuId > 0 {
+			br.Msg = "分类名称已存在,请重新输入"
+			return
+		}
+	}
+
+	// 获取目录层级和同级最大排序
+	level := 1
+	rootId := 0
+	{
+		if req.ParentId > 0 {
+			parentMenu, e := menuOb.GetItemById(req.ParentId)
+			if e != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取父级目录失败, Err: " + e.Error()
+				return
+			}
+			level += parentMenu.Level
+			rootId = parentMenu.RootId
+		}
+	}
+	sortMax, e := services.GetSpeechTagMenuMaxSort(req.ParentId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取标签目录下最大排序失败, Err: " + e.Error()
+		return
+	}
+
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	menuOb.UniqueCode = utils.MD5(fmt.Sprintf("%s_%s", menuOb.TableName(), timestamp))
+	menuOb.MenuName = req.MenuName
+	menuOb.ParentId = req.ParentId
+	menuOb.Level = level
+	menuOb.Sort = sortMax + 1
+	menuOb.RootId = rootId
+	menuOb.CreateTime = time.Now().Local()
+	menuOb.ModifyTime = time.Now().Local()
+	if e = menuOb.Create(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "新增目录失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Edit
+// @Title 编辑分类
+// @Description 编辑分类
+// @Param	request	body speech_recognition.SpeechRecognitionTagMenuEditReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /tag/menu/edit [post]
+func (this *SpeechRecognitionTagMenuController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionTagMenuEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MenuId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, MenuId: %d", req.MenuId)
+		return
+	}
+	req.MenuName = strings.TrimSpace(req.MenuName)
+	if req.MenuName == "" {
+		br.Msg = "请输入目录名称"
+		return
+	}
+
+	menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+	menuItem, e := menuOb.GetItemById(req.MenuId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "目录不存在,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取目录失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验同级目录是否有重名
+	{
+		cond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s <> ?`, speech_recognition.SpeechRecognitionTagMenuCols.MenuName, speech_recognition.SpeechRecognitionTagMenuCols.ParentId, speech_recognition.SpeechRecognitionTagMenuCols.SpeechRecognitionTagMenuId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.MenuName, menuItem.ParentId, req.MenuId)
+		exists, e := menuOb.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取同名目录失败, Err: " + e.Error()
+			return
+		}
+		if exists != nil && exists.SpeechRecognitionTagMenuId > 0 {
+			br.Msg = "分类名称已存在,请重新输入"
+			return
+		}
+	}
+
+	menuItem.MenuName = req.MenuName
+	menuItem.ModifyTime = time.Now().Local()
+	updateCols := []string{speech_recognition.SpeechRecognitionTagMenuCols.MenuName, speech_recognition.SpeechRecognitionTagMenuCols.ModifyTime}
+	if e = menuItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新目录失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Remove
+// @Title 删除分类
+// @Description 删除分类
+// @Param	request	body speech_recognition.SpeechRecognitionTagMenuRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /tag/menu/remove [post]
+func (this *SpeechRecognitionTagMenuController) Remove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionTagMenuRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MenuId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, MenuId: %d", req.MenuId)
+		return
+	}
+
+	menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+	menuItem, e := menuOb.GetItemById(req.MenuId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取目录失败, Err: " + e.Error()
+		return
+	}
+
+	// 删除校验
+	checkResult, menuIds, e := services.CheckSpeechRecognitionTagMenuRemove(menuItem.SpeechRecognitionTagMenuId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "目录删除校验失败, Err: " + e.Error()
+		return
+	}
+
+	switch checkResult.CheckResult {
+	case services.SpeechMenuCheckRemoveTypePass:
+		// 可删除
+		if e = menuItem.Del(); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "删除目录失败, Err: " + e.Error()
+			return
+		}
+	case services.SpeechMenuCheckRemoveTypeRefused:
+		// 不可删除
+		br.Msg = checkResult.Tips
+		return
+	case services.SpeechMenuCheckRemoveTypeWarning:
+		// 删除目录及子目录
+		if e = menuOb.MultiDel(menuIds); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "批量删除目录失败, Err: " + e.Error()
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// RemoveCheck
+// @Title 删除校验
+// @Description 删除校验
+// @Param	request	body speech_recognition.SpeechRecognitionMenuRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /tag/menu/remove_check [post]
+func (this *SpeechRecognitionTagMenuController) RemoveCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionMenuRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MenuId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, MenuId: %d", req.MenuId)
+		return
+	}
+
+	menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+	menuItem, e := menuOb.GetItemById(req.MenuId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取目录失败, Err: " + e.Error()
+		return
+	}
+
+	resp, _, e := services.CheckSpeechRecognitionTagMenuRemove(menuItem.SpeechRecognitionTagMenuId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "目录删除校验失败, Err: " + e.Error()
+		return
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// List
+// @Title 目录列表
+// @Description 目录列表
+// @Param   ParentId  query  int  false  "父级ID"
+// @Success 200 {object} speech_recognition.SpeechRecognitionTagMenuNodeItem
+// @router /tag/menu/list [get]
+func (this *SpeechRecognitionTagMenuController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	// 前端采用懒加载, 所以只查询子目录和内容
+	parentId, _ := this.GetInt("ParentId")
+
+	level := 0
+	menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+	if parentId > 0 {
+		parentMenu, e := menuOb.GetItemById(parentId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取父级目录失败, Err: " + e.Error()
+			return
+		}
+		level = parentMenu.Level + 1
+	}
+	menus := make([]*speech_recognition.SpeechRecognitionTagMenu, 0)
+	{
+		cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionTagMenuCols.ParentId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, parentId)
+		list, e := menuOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", speech_recognition.SpeechRecognitionTagMenuCols.Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取目录列表失败, Err: " + e.Error()
+			return
+		}
+		menus = list
+	}
+
+	tags := make([]*speech_recognition.SpeechRecognitionTag, 0)
+	{
+		tagOb := new(speech_recognition.SpeechRecognitionTag)
+		cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionTagCols.MenuId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, parentId)
+		list, e := tagOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC, %s DESC", speech_recognition.SpeechRecognitionTagMenuCols.Sort, speech_recognition.SpeechRecognitionTagMenuCols.CreateTime))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取标签列表失败, Err: " + e.Error()
+			return
+		}
+		tags = list
+	}
+
+	resp := make([]*speech_recognition.SpeechRecognitionTagMenuNodeItem, 0)
+	if len(menus) == 0 && len(tags) == 0 {
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	for _, v := range menus {
+		resp = append(resp, &speech_recognition.SpeechRecognitionTagMenuNodeItem{
+			UniqueCode: v.UniqueCode,
+			NodeType:   speech_recognition.SpeechRecognitionMenuNodeTypeDefault,
+			MenuId:     v.SpeechRecognitionTagMenuId,
+			MenuName:   v.MenuName,
+			ParentId:   v.ParentId,
+			Level:      v.Level,
+			Sort:       v.Sort,
+			CreateTime: utils.TimeTransferString(utils.FormatDateTime, v.CreateTime),
+		})
+	}
+	for _, v := range tags {
+		resp = append(resp, &speech_recognition.SpeechRecognitionTagMenuNodeItem{
+			UniqueCode: v.UniqueCode,
+			NodeType:   speech_recognition.SpeechRecognitionTagMenuNodeTypeTag,
+			TagId:      v.SpeechRecognitionTagId,
+			TagName:    v.TagName,
+			ParentId:   v.MenuId,
+			Level:      level,
+			Sort:       v.Sort,
+			CreateTime: utils.TimeTransferString(utils.FormatDateTime, v.CreateTime),
+		})
+	}
+	sort.Slice(resp, func(i, j int) bool {
+		return resp[i].Sort < resp[j].Sort
+	})
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Tree
+// @Title 目录树
+// @Description 目录树
+// @Success 200 {object} speech_recognition.SpeechRecognitionTagMenuItem
+// @router /tag/menu/tree [get]
+func (this *SpeechRecognitionTagMenuController) Tree() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	menus := make([]*speech_recognition.SpeechRecognitionTagMenu, 0)
+	{
+		menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+		list, e := menuOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, fmt.Sprintf("%s ASC, %s ASC", speech_recognition.SpeechRecognitionTagMenuCols.ParentId, speech_recognition.SpeechRecognitionTagMenuCols.Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取目录列表失败, Err: " + e.Error()
+			return
+		}
+		menus = list
+	}
+
+	// 递归处理成目录树
+	resp := services.GetSpeechRecognitionTagMenuTreeRecursive(menus, 0)
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Move
+// @Title 移动标签/目录
+// @Description 移动标签/目录
+// @Param	request	body speech_recognition.SpeechRecognitionTagMenuMoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /tag/menu/move [post]
+func (this *SpeechRecognitionTagMenuController) Move() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req speech_recognition.SpeechRecognitionTagMenuMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MenuId <= 0 && req.TagId <= 0 {
+		br.Msg = "请选择目录或标签"
+		return
+	}
+
+	e, _ := services.MoveSpeechTagMenu(req)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "移动目录/标签失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 10 - 5
go.mod

@@ -3,6 +3,7 @@ module eta/eta_api
 go 1.21.7
 
 require (
+	baliance.com/gooxml v1.0.1
 	github.com/PuerkitoBio/goquery v1.9.1
 	github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.2
 	github.com/alibabacloud-go/alimt-20181012/v2 v2.2.0
@@ -19,12 +20,13 @@ require (
 	github.com/beevik/etree v1.3.0
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/go-ldap/ldap v3.0.3+incompatible
-	github.com/go-redis/redis/v8 v8.11.5
+	github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc
 	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
 	github.com/gorilla/websocket v1.5.1
 	github.com/h2non/filetype v1.1.3
+	github.com/jung-kurt/gofpdf v1.16.2
 	github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53
 	github.com/microcosm-cc/bluemonday v1.0.26
 	github.com/minio/minio-go/v7 v7.0.69
@@ -35,6 +37,7 @@ require (
 	github.com/shopspring/decimal v1.3.1
 	github.com/silenceper/wechat/v2 v2.1.6
 	github.com/tealeg/xlsx v1.0.5
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.873
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.880
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.880
 	github.com/xuri/excelize/v2 v2.8.1
@@ -65,6 +68,7 @@ require (
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/dustin/go-humanize v1.0.1 // indirect
 	github.com/fatih/structs v1.1.0 // indirect
+	github.com/fsnotify/fsnotify v1.6.0 // indirect
 	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac // indirect
@@ -99,8 +103,8 @@ require (
 	github.com/richardlehane/msoleps v1.0.3 // indirect
 	github.com/rs/xid v1.5.0 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
-	github.com/sirupsen/logrus v1.9.0 // indirect
-	github.com/spf13/cast v1.4.1 // indirect
+	github.com/sirupsen/logrus v1.9.3 // indirect
+	github.com/spf13/cast v1.5.0 // indirect
 	github.com/tidwall/gjson v1.14.1 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.0 // indirect
@@ -108,10 +112,11 @@ require (
 	github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect
 	github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect
 	golang.org/x/crypto v0.19.0 // indirect
-	golang.org/x/image v0.14.0 // indirect
+	golang.org/x/image v0.15.0 // indirect
+	golang.org/x/net v0.21.0 // indirect
 	golang.org/x/sys v0.17.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
-	golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect
+	golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
 	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 	gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect

+ 29 - 8
go.sum

@@ -1,3 +1,5 @@
+baliance.com/gooxml v1.0.1 h1:fG5lmxmjEVFfbKQ2NuyCuU3hMuuOb5avh5a38SZNO1o=
+baliance.com/gooxml v1.0.1/go.mod h1:+gpUgmkAF4zCtwOFPNRLDAvpVRWoKs5EeQTSv/HYFnw=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
@@ -103,6 +105,7 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
 github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
 github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
@@ -148,9 +151,12 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
+github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
 github.com/garyburd/redigo v1.6.3/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=
 github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -161,8 +167,9 @@ github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp
 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-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
 github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
+github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc h1:jZY+lpZB92nvBo2f31oPC/ivGll6NcsnEOORm8Fkr4M=
+github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc/go.mod h1:25mL1NKxbJhB63ihiK8MnNeTRd+xAizd6bOdydrTLUQ=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -264,7 +271,10 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
+github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
 github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53 h1:+8X3HMX8A2QhvNg3dImiQTCiVUt6BQXz1mW+/DrWI+k=
 github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53/go.mod h1:E61jD6q4yJ6Cu9uDGRAfiENM1G5TVZhOog0Y3+GgTpQ=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -339,14 +349,16 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
-github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
 github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
 github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
+github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -390,6 +402,7 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR
 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
 github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
 github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
@@ -404,13 +417,15 @@ github.com/silenceper/wechat/v2 v2.1.6 h1:2br2DxNzhksmvIBJ+PfMqjqsvoZmd/5BnMIfjK
 github.com/silenceper/wechat/v2 v2.1.6/go.mod h1:7Iu3EhQYVtDUJAj+ZVRy8yom75ga7aDWv8RurLkVm0s=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
 github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
+github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
 github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -427,6 +442,9 @@ github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2K
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
 github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.873 h1:CbeN5Fdzq3xea36+ZKPQcuRwwJk0ZYQRxcyWkyK5768=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.873/go.mod h1:vzSh5OxbOCyFt+SdlEd9oCQGBb1oObkD7Xfod/UPvVk=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.873/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.880 h1:0Ok1pZ06/zZMCiW8Dm8wYOGJK1HCU5OXwNSyE5UVOAM=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.880/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.880 h1:PnzU5KS7x3LQGE0yetGLEGwJtLb6Uzsd79mbCiRh1rw=
@@ -490,9 +508,10 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
-golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
-golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
+golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
+golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -569,6 +588,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -598,8 +618,9 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
+golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

+ 5 - 1
models/business_conf.go

@@ -42,7 +42,11 @@ const (
 	BusinessConfLoginEmailTemplateContent = "LoginEmailTemplateContent"
 	BusinessConfLdapBindUserSuffix        = "LdapBindUserSuffix"
 	BusinessConfLdapUserFilter            = "LdapUserFilter"
-	BusinessConfSmsJhgjVariable           = "SmsJhgjVariable" // 聚合国际短信变量
+
+	BusinessConfTencentApiSecretId           = "TencentApiSecretId"           // 腾讯云API-密钥对
+	BusinessConfTencentApiSecretKey          = "TencentApiSecretKey"          // 腾讯云API-密钥对
+	BusinessConfTencentApiRecTaskCallbackUrl = "TencentApiRecTaskCallbackUrl" // 腾讯云API-语音识别回调地址
+	BusinessConfSmsJhgjVariable              = "SmsJhgjVariable"              // 聚合国际短信变量
 )
 
 const (

+ 195 - 27
models/chart_permission.go

@@ -1,44 +1,212 @@
 package models
 
 import (
+	"eta/eta_api/utils"
+	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
 )
 
 // ChartPermission 报告权限表
 type ChartPermission struct {
-	ChartPermissionId   int       `orm:"column(chart_permission_id);pk" description:"问题ID"`
-	ChartPermissionName string    `description:"名称"`
-	PermissionName      string    `description:"权限名"`
-	Sort                int       `description:"排序"`
-	Enabled             int       `description:"是否可用"`
-	CreatedTime         time.Time `description:"创建时间"`
-	LastUpdatedTime     time.Time `description:"更新时间"`
-	TeleconferenceSort  int       `description:"电话会类型排序"`
-	Remark              string    `description:"备注"`
-	ClassifyName        string    `description:"分类名称"`
-	ProductName         string    `description:"产品名称"`
-	ProductId           int       `description:"产品ID"`
-	ImageURL            string    `description:"图片地址"`
-	ShowType            int       `description:"1:查研观向小程序展示"`
-	IsOther             int       `description:"是否是其他,用于查研观向小程序后台展示"`
-	IsReport            int       `description:"是否是报告,用于查研观向小程序前台报告展示"`
-	CygxAuth            int       `description:"是否是权限,用于查研观向小程序前台权限校验"`
-	YbImgUrl            string    `description:"研报小程序报告列表icon"`
-	PriceDrivenState    int       `description:"品种价格驱动开启状态 0-关闭 1-开启"`
+	ChartPermissionId     int       `orm:"column(chart_permission_id);pk" description:"问题ID" json:"chart_permission_id"`
+	ChartPermissionName   string    `description:"名称" json:"chart_permission_name"`
+	PermissionName        string    `description:"权限名" json:"permission_name"`
+	Sort                  int       `description:"排序" json:"sort"`
+	Enabled               int       `description:"是否可用" json:"enabled"`
+	CreatedTime           time.Time `description:"创建时间" json:"created_time"`
+	LastUpdatedTime       time.Time `description:"更新时间" json:"last_updated_time"`
+	TeleconferenceSort    int       `description:"电话会类型排序" json:"teleconference_sort"`
+	Remark                string    `description:"备注" json:"remark"`
+	ClassifyName          string    `description:"分类名称" json:"classify_name"`
+	ProductName           string    `description:"产品名称" json:"product_name"`
+	ProductId             int       `description:"产品ID" json:"product_id"`
+	ImageURL              string    `orm:"column(image_url);" description:"图片地址" json:"image_url"`
+	ShowType              int       `description:"1:查研观向小程序展示" json:"show_type"`
+	IsOther               int       `description:"是否是其他,用于查研观向小程序后台展示" json:"is_other"`
+	IsReport              int       `description:"是否是报告,用于查研观向小程序前台报告展示" json:"is_report"`
+	CygxAuth              int       `description:"是否是权限,用于查研观向小程序前台权限校验" json:"cygx_auth"`
+	PermissionType        int       `description:"1主观,2客观" json:"permission_type"`
+	YbImgUrl              string    `description:"研报小程序报告列表icon" json:"yb_img_url"`
+	ProductPermissionName string    `description:"种类权限名称" json:"product_permission_name"`
+	PriceDrivenState      int       `description:"品种价格驱动开启状态 0-关闭 1-开启" json:"price_driven_state"`
+	ImageUrlM             string    `description:"图片地址(查研观向移动端)" json:"image_url_m"`
+	ParentId              int       `description:"父级权限id" json:"parent_id"`
+	IsPublic              int       `description:"是否是公有权限1:公有权限,0私有权限" json:"is_public"`
+}
+
+type ChartPermissionItem struct {
+	PermissionId   int    `description:"品种权限ID"`
+	PermissionName string `description:"品种权限名称"`
+	ParentId       int    `description:"父级ID"`
+	IsPublic       int    `description:"是否是公有权限1:公有权限,0私有权限" `
+	Enabled        int    `description:"是否可用:1可用,0不可用" `
+	Sort           int    `description:"排序"`
+	CreateTime     string `description:"创建时间"`
+	Child          []*ChartPermissionItem
 }
 
 // Update 更新
-func (chartPermissionInfo *ChartPermission) Update(cols []string) (err error) {
-	o := orm.NewOrmUsingDB("weekly")
-	_, err = o.Update(chartPermissionInfo, cols...)
+func (c *ChartPermission) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(c, cols...)
+	return
+}
+
+type PermissionAddReq struct {
+	PermissionName string `description:"品种权限名称"`
+	ParentId       int    `description:"父级ID"`
+	Enabled        int    `description:"是否可用 0禁用, 1启用"` //启用,禁用操作会关联二级品种
+	IsPublic       int    `description:"是否是公有权限1:公有权限,0私有权限"`
+}
+
+type PermissionEditReq struct {
+	PermissionId        int    `description:"品种权限Id"` // 如果ID存在,则是更新操作,否则是新增操作
+	PermissionName      string `description:"品种权限名称"`
+	ParentId            int    `description:"父级ID"`
+	Enabled             int    `description:"是否可用 0禁用, 1启用"` //启用,禁用操作会关联二级品种
+	IsPublic            int    `description:"是否是公有权限1:公有权限,0私有权限"`
+	PublicPermissionIds []int  `description:"公有权限的ID列表"` //一级品种没有公有私有属性
+}
+
+type PermissionEnabledReq struct {
+	PermissionId int `description:"品种权限Id"`        // 如果ID存在,则是更新操作,否则是新增操作
+	Enabled      int `description:"是否可用 0禁用, 1启用"` //启用,禁用操作会关联二级品种
+}
+
+type PermissionMoveReq struct {
+	PermissionId int `description:"品种id"`
+	//	ParentChartPermissionId int `description:"父级品种id"`
+	PrevPermissionId int `description:"上一个兄弟节点品种id"`
+	NextPermissionId int `description:"下一个兄弟节点品种id"`
+}
+
+func (c *ChartPermission) SetEnabled(id, enabled int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	sql := ` UPDATE chart_permission SET enabled =?  WHERE id = ?`
+	_, err = to.Raw(sql, enabled, id).Exec()
+	if err != nil {
+		return
+	}
+	sql = ` UPDATE chart_permission SET enabled =?  WHERE parent_id = ?`
+	_, err = to.Raw(sql, enabled, id).Exec()
+	if err != nil {
+		return
+	}
+	return
+}
+
+// Create 新增权限
+func (c *ChartPermission) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(c)
+	if err != nil {
+		return
+	}
+	c.ChartPermissionId = int(id)
+	return
+}
+
+// SetIsPublic 更新公有私有权限
+func (c *ChartPermission) SetIsPublic(ids []int, parentId, isPublic int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `update chart_permission set is_public=? WHERE parent_id = ?  and  chart_permission_id IN (` + utils.GetOrmInReplace(len(ids)) + `)`
+	_, err = o.Raw(sql, isPublic, parentId, ids).Exec()
+	return
+}
+
+// UpdatesByParentId 更新启动禁用
+func (c *ChartPermission) UpdateClassifyNameByParentId(parentId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `update chart_permission set classify_name=? WHERE parent_id = ?`
+	_, err = o.Raw(sql, classifyName, parentId).Exec()
+	return
+}
+
+// SetEnabledByParentId 更新启动禁用
+func (c *ChartPermission) SetEnabledByParentId(parentId, enabled int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` UPDATE chart_permission SET enabled =?  WHERE parent_id = ?`
+	_, err = o.Raw(sql, enabled, parentId).Exec()
+	return
+}
+
+// SetEnabledByChartPermissionId 更新启动禁用
+func (c *ChartPermission) SetEnabledByChartPermissionId(chartPermissionId, enabled int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` UPDATE chart_permission SET enabled =?  WHERE chart_permission_id = ?`
+	_, err = o.Raw(sql, enabled, chartPermissionId).Exec()
+	return
+}
+
+// GetItemById 查询品种
+func (c *ChartPermission) GetItemById(chartPermissionId int) (item *ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select * from chart_permission WHERE chart_permission_id = ?`
+	err = o.Raw(sql, chartPermissionId).QueryRow(&item)
+	return
+}
+
+// GetItemsByCondition 查询列表
+func (c *ChartPermission) GetItemsByCondition(condition string, pars []interface{}) (items []*ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select * from chart_permission WHERE 1=1 ` + condition + ` order by sort asc, chart_permission_id asc`
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// GetItemByCondition 查询列表
+func (c *ChartPermission) GetItemByCondition(condition string, pars []interface{}) (item *ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select * from chart_permission WHERE 1=1 ` + condition
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+// UpdateChartPermissionSortByParentId 根据父类id更新排序
+func UpdateChartPermissionSortByParentId(parentId, chartPermissionId, nowSort int, updateSort string, productId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` update chart_permission set sort = ` + updateSort + ` WHERE parent_id=? AND product_id = ? AND (sort > ? `
+	if chartPermissionId > 0 {
+		sql += ` or ( chart_permission_id > ` + fmt.Sprint(chartPermissionId) + ` and sort = ` + fmt.Sprint(nowSort) + `))`
+	} else {
+		sql += `)`
+	}
+	_, err = o.Raw(sql, parentId, productId, nowSort).Exec()
+	return
+}
+
+// GetMaxSort 获取最大的排序值
+func (c *ChartPermission) GetMaxSort() (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select max(sort) from chart_permission `
+	err = o.Raw(sql).QueryRow(&maxSort)
+	return
+}
+
+// GetMaxSortByParentId 获取最大的排序值
+func (c *ChartPermission) GetMaxSortByParentId(parentId int) (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select max(sort) from chart_permission WHERE parent_id=? `
+	err = o.Raw(sql, parentId).QueryRow(&maxSort)
 	return
 }
 
-// GetChartPermissionList 获取品种权限列表
-func GetChartPermissionList() (list []*ChartPermission, err error) {
-	o := orm.NewOrmUsingDB("weekly")
-	sql := `SELECT * FROM chart_permission ORDER BY product_id ASC, sort ASC`
-	_, err = o.Raw(sql).QueryRows(&list)
+// GetFirstChartPermissionByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func (c *ChartPermission) GetFirstChartPermissionByParentId(parentId int) (item *ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select * from chart_permission WHERE parent_id=? ORDER BY sort ASC, chart_permission_id ASC LIMIT 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
 	return
 }

+ 130 - 43
models/classify.go

@@ -2,6 +2,7 @@ package models
 
 import (
 	"eta/eta_api/utils"
+	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
@@ -39,38 +40,40 @@ type Classify struct {
 	RelateTel         int       `description:"是否在电话会中可选: 0-否; 1-是"`
 	RelateVideo       int       `description:"是否在路演视频中可选: 0-否; 1-是"`
 	IsMassSend        int       `description:"1:群发,0:非群发"`
+	Enabled           int       `description:"是否可用,1可用,0禁用"`
 }
 
 type ClassifyAddReq struct {
-	ClassifyName      string                 `description:"分类名称"`
-	ParentId          int                    `description:"父级分类id,没有父级分类传0"`
-	Abstract          string                 `description:"栏目简介"`
-	Descript          string                 `description:"分享描述"`
-	ReportAuthor      string                 `description:"栏目作者"`
-	AuthorDescript    string                 `description:"作者简介"`
-	ColumnImgUrl      string                 `description:"栏目配图"`
-	ReportImgUrl      string                 `description:"报告配图"`
-	HeadImgUrl        string                 `description:"头部banner"`
-	AvatarImgUrl      string                 `description:"头像"`
-	HomeImgUrl        string                 `description:"首页配图"`
-	ClassifyLabel     string                 `description:"分类标签"`
-	ShowType          int                    `description:"展示类型:1-列表 2-专栏"`
-	HasTeleconference int                    `description:"是否有电话会:0-否 1-是"`
-	VipTitle          string                 `description:"研究员头衔"`
-	Sort              int                    `description:"后台排序"`
-	IsShow            int                    `description:"是否在小程序显示:1-显示 0-隐藏"`
-	YbFiccSort        int                    `description:"小程序FICC页排序"`
-	YbFiccIcon        string                 `description:"小程序FICC页icon"`
-	YbFiccPcIcon      string                 `description:"小程序PC端FICC页背景图"`
-	YbIconUrl         string                 `description:"小程序已购页icon"`
-	YbBgUrl           string                 `description:"小程序已购详情背景图"`
-	YbListImg         string                 `description:"小程序研报列表封面图"`
-	YbShareBgImg      string                 `description:"小程序研报详情分享背景图"`
-	YbRightBanner     string                 `description:"Pc端详情页,右侧,报告合集背景图"`
-	MenuList          []*ClassifyMenuSaveReq `description:"子目录列表"`
-	ClassifyMenuId    int                    `description:"二级分类-子目录ID"`
-	RelateTel         int                    `description:"是否在电话会中可选: 0-否; 1-是"`
-	RelateVideo       int                    `description:"是否在路演视频中可选: 0-否; 1-是"`
+	ClassifyName          string `description:"分类名称"`
+	ParentId              int    `description:"父级分类id,没有父级分类传0"`
+	ChartPermissionIdList []int  `description:"权限id数组"`
+	/*Abstract              string                 `description:"栏目简介"`
+	Descript              string                 `description:"分享描述"`
+	ReportAuthor          string                 `description:"栏目作者"`
+	AuthorDescript        string                 `description:"作者简介"`
+	ColumnImgUrl          string                 `description:"栏目配图"`
+	ReportImgUrl          string                 `description:"报告配图"`
+	HeadImgUrl            string                 `description:"头部banner"`
+	AvatarImgUrl          string                 `description:"头像"`
+	HomeImgUrl            string                 `description:"首页配图"`
+	ClassifyLabel         string                 `description:"分类标签"`
+	ShowType              int                    `description:"展示类型:1-列表 2-专栏"`
+	HasTeleconference     int                    `description:"是否有电话会:0-否 1-是"`
+	VipTitle              string                 `description:"研究员头衔"`
+	Sort                  int                    `description:"后台排序"`
+	IsShow                int                    `description:"是否在小程序显示:1-显示 0-隐藏"`
+	YbFiccSort            int                    `description:"小程序FICC页排序"`
+	YbFiccIcon            string                 `description:"小程序FICC页icon"`
+	YbFiccPcIcon          string                 `description:"小程序PC端FICC页背景图"`
+	YbIconUrl             string                 `description:"小程序已购页icon"`
+	YbBgUrl               string                 `description:"小程序已购详情背景图"`
+	YbListImg             string                 `description:"小程序研报列表封面图"`
+	YbShareBgImg          string                 `description:"小程序研报详情分享背景图"`
+	YbRightBanner         string                 `description:"Pc端详情页,右侧,报告合集背景图"`
+	MenuList              []*ClassifyMenuSaveReq `description:"子目录列表"`
+	ClassifyMenuId        int                    `description:"二级分类-子目录ID"`
+	RelateTel             int                    `description:"是否在电话会中可选: 0-否; 1-是"`
+	RelateVideo           int                    `description:"是否在路演视频中可选: 0-否; 1-是"`*/
 }
 
 func GetClassifyByName(classifyName string, parentId int) (item *Classify, err error) {
@@ -140,8 +143,11 @@ func DeleteClassify(classifyId int) (err error) {
 // 修改分类
 func EditClassify(req *EditClassifyReq) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `UPDATE classify SET classify_name = ?,abstract=?, parent_id= ?,descript=?,report_author=?,author_descript=?,column_img_url=?,head_img_url=?,avatar_img_url=?,report_img_url=?,home_img_url=?,classify_label=?,show_type=?,has_teleconference=?,vip_title=?,modify_time= NOW() WHERE id = ? `
-	_, err = o.Raw(sql, req.ClassifyName, req.Abstract, req.ParentId, req.Descript, req.ReportAuthor, req.AuthorDescript, req.ColumnImgUrl, req.HeadImgUrl, req.AvatarImgUrl, req.ReportImgUrl, req.HomeImgUrl, req.ClassifyLabel, req.ShowType, req.HasTeleconference, req.VipTitle, req.ClassifyId).Exec()
+	//sql := `UPDATE classify SET classify_name = ?,abstract=?, parent_id= ?,descript=?,report_author=?,author_descript=?,column_img_url=?,head_img_url=?,avatar_img_url=?,report_img_url=?,home_img_url=?,classify_label=?,show_type=?,has_teleconference=?,vip_title=?,modify_time= NOW() WHERE id = ? `
+	//_, err = o.Raw(sql, req.ClassifyName, req.Abstract, req.ParentId, req.Descript, req.ReportAuthor, req.AuthorDescript, req.ColumnImgUrl, req.HeadImgUrl, req.AvatarImgUrl, req.ReportImgUrl, req.HomeImgUrl, req.ClassifyLabel, req.ShowType, req.HasTeleconference, req.VipTitle, req.ClassifyId).Exec()
+	sql := `UPDATE classify SET classify_name = ?,parent_id= ?,modify_time= NOW() WHERE id = ? `
+	_, err = o.Raw(sql, req.ClassifyName, req.ParentId, req.ClassifyId).Exec()
+
 	return
 }
 
@@ -185,19 +191,20 @@ type ClassifyList struct {
 	YbRightBanner     string    `description:"Pc端详情页,右侧,报告合集背景图"`
 	RelateTel         int       `description:"是否在电话会中可选: 0-否; 1-是"`
 	RelateVideo       int       `description:"是否在路演视频中可选: 0-否; 1-是"`
+	Enabled           int       `description:"是否可用,1可用,0禁用"`
 	Child             []*ClassifyItem
 	ClassifyMenuList  []*ClassifyMenu
 }
 
 type ClassifyItem struct {
 	Classify
-	ClassifyMenuId   int `description:"二级分类-子目录ID"`
-	ClassifyMenuList []*ClassifyMenu
+	ClassifyMenuId        int `description:"二级分类-子目录ID"`
+	ClassifyMenuList      []*ClassifyMenu
+	ChartPermissionIdList []int `description:"绑定的权限ID"`
 }
 
 type ClassifyListResp struct {
-	List   []*ClassifyList
-	Paging *paging.PagingItem `description:"分页数据"`
+	List []*ClassifyList
 }
 
 type ClassifyPermissionListResp struct {
@@ -206,7 +213,7 @@ type ClassifyPermissionListResp struct {
 }
 
 // 获取分类列表
-func GetClassifyList(startSize, pageSize int, keyWord, companyType string, hideDayWeek int) (items []*ClassifyList, err error) {
+func GetClassifyList(keyWord, companyType string, hideDayWeek, enabled int) (items []*ClassifyList, err error) {
 	sql := ``
 	companyTypeSqlStr := ``
 	if companyType == "ficc" {
@@ -214,6 +221,9 @@ func GetClassifyList(startSize, pageSize int, keyWord, companyType string, hideD
 	} else if companyType == "权益" {
 		companyTypeSqlStr = " AND (id = 40 or parent_id = 40)  "
 	}
+	if enabled == 1 {
+		companyTypeSqlStr += ` AND enabled = 1 `
+	}
 	pars := make([]interface{}, 0)
 	if keyWord != "" {
 		sql = `SELECT * FROM (
@@ -224,17 +234,17 @@ func GetClassifyList(startSize, pageSize int, keyWord, companyType string, hideD
                    WHERE id IN( SELECT parent_id FROM classify
                    WHERE parent_id>0 ` + companyTypeSqlStr + `  AND classify_name LIKE ? )
                    )AS t
-                   ORDER BY sort ASC,create_time ASC
-                   LIMIT ?,? `
+                   ORDER BY sort ASC,create_time ASC`
 		pars = utils.GetLikeKeywordPars(pars, keyWord, 2)
 	} else {
 		sql = `SELECT * FROM classify WHERE parent_id=0 ` + companyTypeSqlStr
 		if hideDayWeek == 1 {
 			sql += ` AND classify_name <> '晨报' AND classify_name <> '周报' `
 		}
-		sql += ` ORDER BY sort ASC, create_time ASC LIMIT ?,? `
+
+		sql += ` ORDER BY sort ASC, create_time ASC`
 	}
-	pars = append(pars, startSize, pageSize)
+	pars = append(pars)
 
 	o := orm.NewOrmUsingDB("rddp")
 	_, err = o.Raw(sql, pars...).QueryRows(&items)
@@ -312,7 +322,7 @@ func GetClassifyChild(parentId int, keyWord string) (items []*Classify, err erro
 	return
 }
 
-func GetClassifyChildByParentIds(parentId []int, keyWord string) (items []*Classify, err error) {
+func GetClassifyChildByParentIds(parentId []int, keyWord string, enabled int) (items []*Classify, err error) {
 	parentIdLen := len(parentId)
 	if parentIdLen == 0 {
 		return
@@ -322,11 +332,16 @@ func GetClassifyChildByParentIds(parentId []int, keyWord string) (items []*Class
 	pars := make([]interface{}, 0)
 	pars = append(pars, parentId)
 	if keyWord != "" {
-		sql = `SELECT * FROM classify WHERE parent_id IN (` + utils.GetOrmInReplace(parentIdLen) + `) AND classify_name LIKE ? ORDER BY create_time ASC `
+		sql = `SELECT * FROM classify WHERE parent_id IN (` + utils.GetOrmInReplace(parentIdLen) + `) AND classify_name LIKE ? `
 		pars = append(pars, utils.GetLikeKeyword(keyWord))
 	} else {
-		sql = `SELECT * FROM classify WHERE parent_id IN (` + utils.GetOrmInReplace(parentIdLen) + `) ORDER BY create_time ASC `
+		sql = `SELECT * FROM classify WHERE parent_id IN (` + utils.GetOrmInReplace(parentIdLen) + `) `
 	}
+
+	if enabled == 1 {
+		sql += ` AND enabled=1 `
+	}
+	sql += ` ORDER BY create_time ASC `
 	_, err = o.Raw(sql, pars...).QueryRows(&items)
 
 	return
@@ -401,3 +416,75 @@ type RelateTelSecClassifyWithPermissions struct {
 	ClassifyName       string `description:"分类名称"`
 	ChartPermissionIds string `description:"权限IDs"`
 }
+
+// UpdateClassifySortByParentId 根据父类id更新排序
+func UpdateClassifySortByParentId(parentId, permissionId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` update classify set sort = ` + updateSort + ` WHERE parent_id=? AND sort > ? `
+	if permissionId > 0 {
+		sql += ` or ( id > ` + fmt.Sprint(permissionId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetMaxSortByParentId 获取最大的排序值
+func (classifyInfo *Classify) GetMaxSortByParentId(parentId int) (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT max(sort) AS sort FROM classify WHERE parent_id = ? `
+	err = o.Raw(sql, parentId).QueryRow(&maxSort)
+	return
+}
+
+// GetMaxSort 获取最大的排序值
+func (classifyInfo *Classify) GetMaxSort() (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT max(sort) AS sort FROM classify`
+	err = o.Raw(sql).QueryRow(&maxSort)
+	return
+}
+
+// GetFirstClassifyByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func (classifyInfo *Classify) GetFirstClassifyByParentId(parentId int) (item *Classify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM classify WHERE parent_id = ? order by sort asc, id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+type ClassifyMoveReq struct {
+	ClassifyId     int `description:"分类ID"`
+	PrevClassifyId int `description:"上一个兄弟节点分类id"`
+	NextClassifyId int `description:"下一个兄弟节点分类id"`
+}
+
+type ClassifySetEnabledReq struct {
+	ClassifyId int `description:"分类ID"`
+	Enabled    int `description:"是否可用,1可用,0禁用"`
+}
+
+func (classifyInfo *Classify) SetEnabled(id, enabled int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	sql := ` UPDATE classify SET enabled =?  WHERE id = ?`
+	_, err = to.Raw(sql, enabled, id).Exec()
+	if err != nil {
+		return
+	}
+	sql = ` UPDATE classify SET enabled =?  WHERE parent_id = ?`
+	_, err = to.Raw(sql, enabled, id).Exec()
+	if err != nil {
+		return
+	}
+	return
+}

+ 9 - 9
models/company/company_permission.go

@@ -37,15 +37,15 @@ type PermissionSetItemType struct {
 }
 
 type PermissionSetList struct {
-	ClassifyName string `description:"分类"`
-	Items        []*PermissionSetItem
-	CheckList    []int
+	PermissionName string `description:"分类"`
+	Child          []*PermissionSetItem
+	CheckList      []int
 }
 
 type PermissionSetListType struct {
-	ClassifyName string `description:"分类"`
-	Items        []*PermissionSetItemType
-	CheckList    []int
+	PermissionName string `description:"分类"`
+	Child          []*PermissionSetItemType
+	CheckList      []int
 }
 
 type PermissionSetResp struct {
@@ -55,7 +55,7 @@ type PermissionSetResp struct {
 }
 
 func GetPermissionSetItems(productId int, classifyName string) (items []*PermissionSetItem, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM chart_permission WHERE enabled=1 AND product_id=? AND classify_name=?  AND permission_type=0 ORDER BY sort ASC `
 	_, err = o.Raw(sql, productId, classifyName).QueryRows(&items)
 	return
@@ -95,7 +95,7 @@ type PermissionVarietyList struct {
 }
 
 func GetPermissionVarietyItems(productId int, classifyName string) (items []*PermissionVarietyItem, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM chart_permission WHERE enabled=1 AND product_id=? AND classify_name=? GROUP BY permission_name ORDER BY sort ASC `
 	_, err = o.Raw(sql, productId, classifyName).QueryRows(&items)
 	return
@@ -103,7 +103,7 @@ func GetPermissionVarietyItems(productId int, classifyName string) (items []*Per
 
 // GetChartPermissionListById 根据权限id获取产品权限详情
 func GetChartPermissionListById(chartPermissionId int) (item *ChartPermission, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := `SELECT * FROM chart_permission WHERE chart_permission_id =? `
 	err = o.Raw(sql, chartPermissionId).QueryRow(&item)
 	return

+ 23 - 0
models/data_manage/edb_config.go

@@ -0,0 +1,23 @@
+package data_manage
+
+import "github.com/beego/beego/v2/client/orm"
+
+type EdbConfig struct {
+	ConfigValue string `description:"详情"`
+}
+
+// EdbConfigUpdate 修改配置
+func EdbConfigUpdate(newValue, configCode string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE edb_config SET  config_value=?   WHERE config_code=  ?`
+	_, err = o.Raw(sql, newValue, configCode).Exec()
+	return
+}
+
+// GetConfigDetailByCode 根据配置编码获取配置的值
+func GetConfigDetailByCode(configCode string) (item *EdbConfig, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT config_value FROM edb_config WHERE config_code=? `
+	err = o.Raw(sql, configCode).QueryRow(&item)
+	return
+}

+ 21 - 0
models/data_manage/edb_terminal.go

@@ -127,3 +127,24 @@ func (item *EdbTerminal) Update(cols []string) (err error) {
 	_, err = o.Update(item, cols...)
 	return
 }
+
+// GetEdbTerminalByCode 根据终端编码获取终端信息
+func GetEdbTerminalByCode(terminalCode string) (item *EdbTerminal, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM edb_terminal WHERE terminal_code = ? `
+	err = o.Raw(sql, terminalCode).QueryRow(&item)
+	return
+}
+
+type TerminalCodeCountGroup struct {
+	TerminalCode string
+	Total        int
+}
+
+// GetEdbCountGroupByTerminal 获取终端code分组总数
+func GetEdbCountGroupByTerminal(source int) (list []TerminalCodeCountGroup, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `select terminal_code,count(1) total from edb_info where source = ? AND no_update=0 AND terminal_code != "" group by terminal_code; `
+	_, err = o.Raw(sql, source).QueryRows(&list)
+	return
+}

+ 17 - 0
models/db.go

@@ -17,6 +17,7 @@ import (
 	"eta/eta_api/models/sandbox"
 	"eta/eta_api/models/semantic_analysis"
 	"eta/eta_api/models/smart_report"
+	"eta/eta_api/models/speech_recognition"
 	"eta/eta_api/models/system"
 	"eta/eta_api/models/yb"
 	"eta/eta_api/utils"
@@ -186,6 +187,9 @@ func init() {
 	// 初始化指标刷新
 	initEdbRefresh()
 
+	// 语音识别
+	initSpeechRecognition()
+
 	// 初始化部分数据表变量(直接init会有顺序问题=_=!)
 	data_manage.InitEdbSourceVar()
 }
@@ -549,3 +553,16 @@ func initEdbRefresh() {
 		new(edb_refresh.EdbRefreshMapping),       // 指标刷新时间配置关系表
 	)
 }
+
+// initSpeechRecognition 语音识别
+func initSpeechRecognition() {
+	orm.RegisterModel(
+		new(speech_recognition.SpeechRecognition),           // 语音识别表
+		new(speech_recognition.SpeechRecognitionApiLog),     // 语音识别-API请求日志
+		new(speech_recognition.SpeechRecognitionContent),    // 语音识别-转换内容表
+		new(speech_recognition.SpeechRecognitionMenu),       // 语音识别-目录表
+		new(speech_recognition.SpeechRecognitionTag),        // 语音识别-标签表
+		new(speech_recognition.SpeechRecognitionTagMenu),    // 语音识别-标签目录表
+		new(speech_recognition.SpeechRecognitionTagMapping), // 语音识别-标签关联表
+	)
+}

+ 12 - 1
models/en_classify_permission.go

@@ -1,9 +1,9 @@
 package models
 
 import (
+	"eta/eta_api/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
-	"eta/eta_api/utils"
 	"strings"
 	"time"
 )
@@ -128,6 +128,17 @@ func ClearAndCreateEnClassifyPermissions(classifyId int, permissions []*EnClassi
 	return
 }
 
+func CreateEnClassifyPermissions(permissions []*EnClassifyPermission) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	if len(permissions) > 0 {
+		if _, e := o.InsertMulti(len(permissions), permissions); e != nil {
+			err = fmt.Errorf("insert multi err: %s", e.Error())
+			return
+		}
+	}
+	return
+}
+
 type EnClassifyPermissionEditReq struct {
 	ClassifyId    int   `description:"分类ID"`
 	EnPermissions []int `description:"权限IDs"`

+ 75 - 7
models/en_permission.go

@@ -16,6 +16,7 @@ type EnPermission struct {
 	Sort             int       `description:"排序"`
 	CreateTime       time.Time `description:"创建时间"`
 	ModifyTime       time.Time `description:"修改时间"`
+	Enabled          int       `description:"是否可用,1可用,0禁用"`
 }
 
 var EnPermissionColumns = struct {
@@ -26,6 +27,7 @@ var EnPermissionColumns = struct {
 	Sort             string
 	CreateTime       string
 	ModifyTime       string
+	Enabled          string
 }{
 	EnPermissionId:   "en_permission_id",
 	EnPermissionName: "en_permission_name",
@@ -34,6 +36,7 @@ var EnPermissionColumns = struct {
 	Sort:             "sort",
 	CreateTime:       "create_time",
 	ModifyTime:       "modify_time",
+	Enabled:          "enabled",
 }
 
 func (m *EnPermission) TableName() string {
@@ -122,19 +125,26 @@ func (m *EnPermission) GetPageItemsByCondition(startSize, pageSize int, conditio
 }
 
 type EnPermissionAddReq struct {
-	EnPermissionName string `description:"品种权限名称"`
+	PermissionName   string `description:"品种权限名称"`
 	CnPermissionName string `description:"对应的中文权限名称"`
 	ParentId         int    `description:"父级ID"`
-	Sort             int    `description:"排序"`
+	Enabled          int    `description:"是否可用,1可用,0禁用"`
 }
 
 type EnPermissionEditReq struct {
-	EnPermissionId int `description:"英文品种权限ID"`
-	EnPermissionAddReq
+	PermissionId     int    `description:"英文品种权限ID"`
+	PermissionName   string `description:"品种权限名称"`
+	CnPermissionName string `description:"对应的中文权限名称"`
+	ParentId         int    `description:"父级ID"`
+}
+
+type EnPermissionEnabledReq struct {
+	PermissionId int `description:"英文品种权限ID"`
+	Enabled      int `description:"是否可用,1可用,0禁用"`
 }
 
 type EnPermissionRemoveReq struct {
-	EnPermissionId int `description:"英文品种权限ID"`
+	PermissionId int `description:"英文品种权限ID"`
 }
 
 type EnPermissionPageListResp struct {
@@ -143,13 +153,20 @@ type EnPermissionPageListResp struct {
 }
 
 type EnPermissionItem struct {
-	EnPermissionId   int    `description:"英文品种权限ID"`
-	EnPermissionName string `description:"品种权限名称"`
+	PermissionId     int    `description:"英文品种权限ID"`
+	PermissionName   string `description:"品种权限名称"`
 	CnPermissionName string `description:"对应的中文权限名称"`
 	ParentId         int    `description:"父级ID"`
 	Sort             int    `description:"排序"`
 	CreateTime       string `description:"创建时间"`
 	Child            []*EnPermissionItem
+	Enabled          int `description:"是否可用,1可用,0禁用"`
+}
+
+type EnPermissionMoveReq struct {
+	PermissionId     int `description:"品种id"`
+	PrevPermissionId int `description:"上一个兄弟节点品种id"`
+	NextPermissionId int `description:"下一个兄弟节点品种id"`
 }
 
 func GetEnPermissionUnionList(condition string, pars []interface{}) (items []*EnPermission, err error) {
@@ -203,3 +220,54 @@ func ClearEnPermissionsByPermissionId(permissionId int) (err error) {
 	}
 	return
 }
+
+// UpdateEnPermissionSortByParentId 根据父类id更新排序
+func UpdateEnPermissionSortByParentId(parentId, permissionId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` update en_permission set sort = ` + updateSort + ` WHERE parent_id=? AND sort > ? `
+	if permissionId > 0 {
+		sql += ` or ( en_permission_id > ` + fmt.Sprint(permissionId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetMaxSortByParentId 获取最大的排序值
+func (m *EnPermission) GetMaxSortByParentId(parentId int) (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT max(%s) AS sort FROM %s WHERE %s = ? `, EnPermissionColumns.Sort, m.TableName(), EnPermissionColumns.ParentId)
+	err = o.Raw(sql, parentId).QueryRow(&maxSort)
+	return
+}
+
+// GetMaxSort 获取最大的排序值
+func (m *EnPermission) GetMaxSort() (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT max(%s) AS sort FROM %s`, EnPermissionColumns.Sort, m.TableName())
+	err = o.Raw(sql).QueryRow(&maxSort)
+	return
+}
+
+// GetFirstEnPermissionByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func (m *EnPermission) GetFirstEnPermissionByParentId(parentId int) (item *EnPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? order by sort asc, en_permission_id asc limit 1`, m.TableName(), EnPermissionColumns.ParentId)
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+// SetEnabled 更新启动禁用
+func (m *EnPermission) SetEnabled(parentId, enabled int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ?  WHERE %s = ?`, m.TableName(), EnPermissionColumns.Enabled, EnPermissionColumns.ParentId)
+	_, err = o.Raw(sql, enabled, parentId).Exec()
+	return
+}
+
+// SetEnabledByPermissionId 更新启动禁用
+func (m *EnPermission) SetEnabledByPermissionId(permissionId, enabled int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ?  WHERE %s = ?`, m.TableName(), EnPermissionColumns.Enabled, EnPermissionColumns.EnPermissionId)
+	_, err = o.Raw(sql, enabled, permissionId).Exec()
+	return
+}

+ 58 - 0
models/english_classify.go

@@ -0,0 +1,58 @@
+package models
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+)
+
+// UpdateEnglishClassifySortByParentId 根据父类id更新排序
+func UpdateEnglishClassifySortByParentId(parentId, permissionId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` update english_classify set sort = ` + updateSort + ` WHERE parent_id=? AND sort > ? `
+	if permissionId > 0 {
+		sql += ` or ( id > ` + fmt.Sprint(permissionId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetMaxSortByParentId 获取最大的排序值
+func (classifyInfo *EnglishClassify) GetMaxSortByParentId(parentId int) (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT max(sort) AS sort FROM english_classify WHERE parent_id = ? `
+	err = o.Raw(sql, parentId).QueryRow(&maxSort)
+	return
+}
+
+// GetMaxSort 获取最大的排序值
+func (classifyInfo *EnglishClassify) GetMaxSort() (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT max(sort) AS sort FROM english_classify`
+	err = o.Raw(sql).QueryRow(&maxSort)
+	return
+}
+
+// GetFirstClassifyByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func (classifyInfo *EnglishClassify) GetFirstClassifyByParentId(parentId int) (item *Classify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_classify WHERE parent_id = ? order by sort asc, id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+type EnglishClassifyMoveReq struct {
+	ClassifyId     int `description:"分类ID"`
+	PrevClassifyId int `description:"上一个兄弟节点分类id"`
+	NextClassifyId int `description:"下一个兄弟节点分类id"`
+}
+
+type EnClassifyAddReq struct {
+	EnPermissions []int  `description:"权限IDs"`
+	ClassifyName  string `description:"分类名称"`
+	ParentId      int    `description:"父级分类id"`
+}
+
+type EnClassifyEditReq struct {
+	ClassifyId int `description:"分类ID"`
+	EnClassifyAddReq
+}

+ 55 - 37
models/english_report.go

@@ -422,63 +422,49 @@ type EnglishClassifyList struct {
 	IsShow        int       `description:"是否在小程序显示:1-显示 0-隐藏"`
 	//ClassifyType  int       `description:"分类类型:0英文报告,1英文线上路演"`
 	EnPermissions []int `description:"英文权限IDs"`
+	Enabled       int   `description:"是否可用,1可用,0禁用"`
 	Child         []*EnglishClassifyList
 }
 
 type EnglishClassifyListResp struct {
-	List   []*EnglishClassifyList
-	Paging *paging.PagingItem `description:"分页数据"`
+	List []*EnglishClassifyList
 }
 
 // GetEnglishClassifyRootId 获取一级分类列表
-func GetEnglishClassifyRootId(startSize, pageSize int, keyword string) (items []*EnglishClassifyList, err error) {
+func GetEnglishClassifyRootId(keyword string, enabled int) (items []*EnglishClassifyList, err error) {
 	sql := ``
 	o := orm.NewOrmUsingDB("rddp")
+	cond := ""
+	if enabled == 1 {
+		cond = " AND enabled=1 "
+	}
 	if keyword != "" {
 		sql = `SELECT * FROM (
 			                   SELECT * FROM english_classify
-                   WHERE parent_id=0 AND classify_name ?
+                   WHERE parent_id=0 ` + cond + ` AND classify_name LIKE ? 
                    UNION
                    SELECT * FROM english_classify
                    WHERE id IN(SELECT parent_id FROM english_classify
-                   WHERE parent_id>0 AND classify_name LIKE ?)
+                   WHERE parent_id>0 ` + cond + ` AND classify_name LIKE ? )
                    )AS t
-                   ORDER BY sort ASC,create_time ASC
-                   LIMIT ?,? `
-		_, err = o.Raw(sql, utils.GetLikeKeyword(keyword), utils.GetLikeKeyword(keyword), startSize, pageSize).QueryRows(&items)
+                   ORDER BY sort ASC,create_time ASC`
+		_, err = o.Raw(sql, utils.GetLikeKeyword(keyword), utils.GetLikeKeyword(keyword)).QueryRows(&items)
 	} else {
-		sql = `SELECT * FROM english_classify WHERE parent_id=0 ORDER BY sort ASC,create_time ASC LIMIT ?,? `
-		_, err = o.Raw(sql, startSize, pageSize).QueryRows(&items)
+		sql = `SELECT * FROM english_classify WHERE parent_id=0 ` + cond + ` ORDER BY sort ASC,create_time ASC `
+		_, err = o.Raw(sql).QueryRows(&items)
 	}
 	return
 }
 
-func GetEnglishClassifyListCount(keyword string) (count int, err error) {
-	sqlCount := ``
-	o := orm.NewOrmUsingDB("rddp")
-	if keyword != "" {
-		sqlCount = `SELECT  COUNT(1) AS count FROM (
-               SELECT * FROM english_classify
-               WHERE parent_id=0 AND classify_name LIKE ?
-               UNION
-               SELECT * FROM english_classify
-               WHERE id IN(SELECT parent_id FROM english_classify
-               WHERE parent_id>0 AND classify_name LIKE ?)
-               )AS t `
-		err = o.Raw(sqlCount, utils.GetLikeKeyword(keyword), utils.GetLikeKeyword(keyword)).QueryRow(&count)
-	} else {
-		sqlCount = `SELECT COUNT(1) AS count FROM english_classify WHERE parent_id=0`
-		err = o.Raw(sqlCount).QueryRow(&count)
-	}
-
-	return
-}
-
-func GetEnglishClassifyListByRootId(rootIds []int, keyword string) (items []*EnglishClassifyList, err error) {
+func GetEnglishClassifyListByRootId(rootIds []int, keyword string, enabled int) (items []*EnglishClassifyList, err error) {
 	sql := ``
 	pars := make([]interface{}, 0)
 
 	o := orm.NewOrmUsingDB("rddp")
+	cond := ""
+	if enabled == 1 {
+		cond = " AND enabled=1 "
+	}
 	if keyword != "" {
 		sql = `SELECT
 	a.*
@@ -486,12 +472,12 @@ FROM
 	english_classify a
 	LEFT JOIN english_classify b ON a.root_id = b.id
 	LEFT JOIN english_classify c ON a.parent_id = c.id
-	WHERE a.parent_id>0 and a.classify_name LIKE ? and a.root_id IN (` + utils.GetOrmInReplace(len(rootIds)) + `)`
+	WHERE a.parent_id>0  ` + cond + ` and a.classify_name LIKE ? and a.root_id IN (` + utils.GetOrmInReplace(len(rootIds)) + `)`
 		pars = append(pars, utils.GetLikeKeyword(keyword))
 		pars = append(pars, rootIds)
 		_, err = o.Raw(sql, pars).QueryRows(&items)
 	} else {
-		sql = `SELECT * FROM english_classify WHERE parent_id>0 and root_id IN (` + utils.GetOrmInReplace(len(rootIds)) + `) `
+		sql = `SELECT * FROM english_classify WHERE parent_id>0  ` + cond + ` and root_id IN (` + utils.GetOrmInReplace(len(rootIds)) + `) `
 		_, err = o.Raw(sql, rootIds).QueryRows(&items)
 	}
 	return
@@ -548,6 +534,7 @@ type EnglishClassify struct {
 	ShowType      int       `description:"展示类型:1-列表 2-专栏"`
 	IsShow        int       `description:"是否在小程序显示:1-显示 0-隐藏"`
 	//ClassifyType  int       `description:"分类类型:0英文报告,1英文线上路演"`
+	Enabled int `description:"是否可用,1可用,0禁用"`
 }
 
 func AddEnglishClassify(item *EnglishClassify) (lastId int64, err error) {
@@ -621,10 +608,10 @@ func GetEnglishClassifyCountsByName(name string, parentId int) (count int, err e
 }
 
 // GetEnglishFirstClassifyList 获取一级、二级分类列表
-func GetEnglishFirstClassifyList(startSize, pageSize int) (items []*EnglishClassifyList, err error) {
+func GetEnglishFirstClassifyList() (items []*EnglishClassifyList, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT * FROM english_classify WHERE parent_id=0  ORDER BY sort ASC,create_time ASC LIMIT ?,? `
-	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&items)
+	sql := `SELECT * FROM english_classify WHERE parent_id=0  ORDER BY sort ASC,create_time`
+	_, err = o.Raw(sql).QueryRows(&items)
 	return
 }
 
@@ -895,3 +882,34 @@ func UpdateEnglishReportsStateBySecondIds(oldState, newState int, secondIds []in
 	_, err = o.Raw(sql, oldState, newState, secondIds).Exec()
 	return
 }
+
+type EnglishClassifySetEnabledReq struct {
+	ClassifyId int `description:"分类ID"`
+	Enabled    int `description:"是否可用,1可用,0禁用"`
+}
+
+func (classifyInfo *EnglishClassify) SetEnabled(id, enabled int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	sql := ` UPDATE english_classify SET enabled =?  WHERE id = ?`
+	_, err = to.Raw(sql, enabled, id).Exec()
+	if err != nil {
+		return
+	}
+	sql = ` UPDATE english_classify SET enabled =?  WHERE parent_id = ? or root_id = ?`
+	_, err = to.Raw(sql, enabled, id, id).Exec()
+	if err != nil {
+		return
+	}
+	return
+}

+ 13 - 6
models/permission.go

@@ -13,15 +13,22 @@ type ChartPermissionSearchKeyWordMapping struct {
 }
 
 func GetPermission(classifyNameSecond string) (items []*ChartPermissionSearchKeyWordMapping, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := `SELECT * FROM chart_permission_search_key_word_mapping AS a WHERE a.from='rddp' AND a.key_word=? `
 	_, err = o.Raw(sql, classifyNameSecond).QueryRows(&items)
 	return
 }
 
+func GetAllPermissionMapping() (items []*ChartPermissionSearchKeyWordMapping, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission_search_key_word_mapping AS a WHERE a.from='rddp'`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
 // EditChartPermissionSearchKeyWordMappingMulti 修改报告报告权限(先删除原有的权限,再添加新的权限)
 func EditChartPermissionSearchKeyWordMappingMulti(keyword string, permissionIdList []int) (err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	to, err := o.Begin()
 	if err != nil {
 		return
@@ -59,14 +66,14 @@ func EditChartPermissionSearchKeyWordMappingMulti(keyword string, permissionIdLi
 func AddChartPermissionChapterMapping(chartPermissionId int, reportId int64) (err error) {
 	sql := `INSERT INTO chart_permission_chapter_mapping (chart_permission_id, report_chapter_type_id,research_type)
            VALUES(?,?,?)`
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	_, err = o.Raw(sql, chartPermissionId, reportId, "rddp").Exec()
 	return
 }
 
 func RemoveChartPermissionChapterMapping(reportId int64) (err error) {
 	sql := ` DELETE FROM chart_permission_chapter_mapping WHERE research_type=? AND report_chapter_type_id=? `
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	_, err = o.Raw(sql, "rddp", reportId).Exec()
 	return
 }
@@ -77,7 +84,7 @@ type ChartPermissionMappingIdName struct {
 }
 
 func GetChartPermissionNameFromMappingByKeyword(keyword string, source string) (list []*ChartPermissionMappingIdName, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := " SELECT b.chart_permission_id AS permission_id,b.permission_name FROM chart_permission_search_key_word_mapping AS a INNER JOIN chart_permission AS b ON a.chart_permission_id = b.chart_permission_id WHERE a.`from` = ? AND a.key_word = ? "
 	_, err = o.Raw(sql, source, keyword).QueryRows(&list)
 
@@ -86,7 +93,7 @@ func GetChartPermissionNameFromMappingByKeyword(keyword string, source string) (
 
 // UpdateChartPermissionNameFromMappingByKeyword 根据关键词及来源更新新关键词
 func UpdateChartPermissionNameFromMappingByKeyword(newKeyword, keyword, source string) (err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := " UPDATE chart_permission_search_key_word_mapping SET key_word = ? WHERE key_word = ? AND `from` = ? "
 	_, err = o.Raw(sql, newKeyword, keyword, source).Exec()
 	return

+ 100 - 36
models/report_chapter_type.go

@@ -2,8 +2,8 @@ package models
 
 import (
 	"eta/eta_api/utils"
+	"fmt"
 	"github.com/beego/beego/v2/client/orm"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
 )
 
@@ -32,25 +32,25 @@ type ReportChapterType struct {
 	YbBottomIcon           string    `description:"研报小程序详情底部icon"`
 }
 
-func (item *ReportChapterType) Create() (err error) {
-	o := orm.NewOrmUsingDB("weekly")
-	id, err := o.Insert(item)
+func (r *ReportChapterType) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(r)
 	if err != nil {
 		return
 	}
-	item.ReportChapterTypeId = int(id)
+	r.ReportChapterTypeId = int(id)
 	return
 }
 
-func (item *ReportChapterType) Update(cols []string) (err error) {
-	o := orm.NewOrmUsingDB("weekly")
-	_, err = o.Update(item, cols...)
+func (r *ReportChapterType) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(r, cols...)
 	return
 }
 
 // DeleteReportChapterType 删除章节类型及相关权限
 func DeleteReportChapterType(typeId int, reportType string) (err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	to, err := o.Begin()
 	if err != nil {
 		return
@@ -81,7 +81,7 @@ func DeleteReportChapterType(typeId int, reportType string) (err error) {
 
 // GetReportChapterTypeById 获取章节类型
 func GetReportChapterTypeById(reportChapterTypeId int) (item *ReportChapterType, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM report_chapter_type WHERE report_chapter_type_id = ? `
 	err = o.Raw(sql, reportChapterTypeId).QueryRow(&item)
 	return
@@ -89,7 +89,7 @@ func GetReportChapterTypeById(reportChapterTypeId int) (item *ReportChapterType,
 
 // GetReportChapterTypeList 获取章节类型列表
 func GetReportChapterTypeList() (list []*ReportChapterType, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM report_chapter_type WHERE enabled = 1 `
 	_, err = o.Raw(sql).QueryRows(&list)
 	return
@@ -97,7 +97,7 @@ func GetReportChapterTypeList() (list []*ReportChapterType, err error) {
 
 // GetReportChapterTypeListByResearchType 通过报告类型获取章节类型列表
 func GetReportChapterTypeListByResearchType(researchType string) (list []*ReportChapterType, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM report_chapter_type WHERE research_type = ? AND enabled = 1`
 	_, err = o.Raw(sql, researchType).QueryRows(&list)
 	return
@@ -105,7 +105,7 @@ func GetReportChapterTypeListByResearchType(researchType string) (list []*Report
 
 // GetAllReportChapterTypeListByResearchType 通过报告类型获取章节类型列表
 func GetAllReportChapterTypeListByResearchType(researchType string) (list []*ReportChapterType, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM report_chapter_type WHERE research_type = ?`
 	_, err = o.Raw(sql, researchType).QueryRows(&list)
 	return
@@ -113,7 +113,7 @@ func GetAllReportChapterTypeListByResearchType(researchType string) (list []*Rep
 
 // GetAllReportChapterTypeList 通过传入的条件获取所有的章节类型列表
 func GetAllReportChapterTypeList(condition string, pars []interface{}) (list []*ReportChapterType, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM report_chapter_type WHERE 1=1 `
 	sql += condition
 	_, err = o.Raw(sql, pars).QueryRows(&list)
@@ -122,7 +122,7 @@ func GetAllReportChapterTypeList(condition string, pars []interface{}) (list []*
 
 // GetEnableReportChapterTypeList 获取未暂停的章节类型列表
 func GetEnableReportChapterTypeList(researchType string) (list []*ReportChapterType, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT
 				*
 			FROM
@@ -151,7 +151,7 @@ type DayWeekReportPauseTime struct {
 
 // GetDayWeekReportPauseTimeList 获取晨报周报暂停时间
 func GetDayWeekReportPauseTimeList() (list []*DayWeekReportPauseTime, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT research_type, MAX(a.pause_start_time) AS pause_start_time, MAX(a.pause_end_time) AS pause_end_time FROM report_chapter_type AS a WHERE a.is_set = 1 GROUP BY a.research_type`
 	_, err = o.Raw(sql).QueryRows(&list)
 	return
@@ -177,7 +177,7 @@ type SetDayWeekReportEnableUpdateRuleReq struct {
 
 // ResetDayWeekReportUpdateRule 重置章节类型的暂停时间
 func ResetDayWeekReportUpdateRule(researchType string) (err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` UPDATE report_chapter_type SET pause_start_time = null, pause_end_time = null, is_set = 0 WHERE research_type = ?`
 	_, err = o.Raw(sql, researchType).Exec()
 	return
@@ -185,7 +185,7 @@ func ResetDayWeekReportUpdateRule(researchType string) (err error) {
 
 // SetDayWeekReportUpdateRule 设置章节类型的暂停时间
 func SetDayWeekReportUpdateRule(researchType string, list []DayWeekReportUpdateRule) (err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	to, err := o.Begin()
 	if err != nil {
 		return
@@ -221,7 +221,7 @@ func SetDayWeekReportUpdateRule(researchType string, list []DayWeekReportUpdateR
 
 // SetDayWeekReportEnableUpdateRule 设置章节类型的禁用状态
 func SetDayWeekReportEnableUpdateRule(dayReportChapterTypeIdList, weekReportChapterTypeIdList []string) (err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	to, err := o.Begin()
 	if err != nil {
 		return
@@ -279,7 +279,7 @@ type StopUpdateReportChapterTypeResp struct {
 
 // GetStopUpdateReportChapterTypeListByResearchType 获取暂停更新章节类型列表
 func GetStopUpdateReportChapterTypeListByResearchType() (list []*ReportChapterType, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM report_chapter_type WHERE is_set = 1 AND pause_end_time >= ? AND enabled = 1 `
 	_, err = o.Raw(sql, time.Now().Format(utils.FormatDate)).QueryRows(&list)
 	return
@@ -287,7 +287,7 @@ func GetStopUpdateReportChapterTypeListByResearchType() (list []*ReportChapterTy
 
 // GetDisableUpdateReportChapterTypeListByResearchType 获取停止更新的章节类型列表
 func GetDisableUpdateReportChapterTypeListByResearchType() (list []*ReportChapterType, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM report_chapter_type WHERE enabled = 0`
 	_, err = o.Raw(sql).QueryRows(&list)
 	return
@@ -299,14 +299,13 @@ type UpdateReportChapterTypeResp struct {
 	Week []*ReportChapterType `description:"所有周报品种"`
 }
 
-type ReportChapterTypePageListResp struct {
-	List   []*ReportChapterTypeListItem
-	Paging *paging.PagingItem `description:"分页数据"`
+type ReportChapterTypeListResp struct {
+	List []*ReportChapterTypeListItem
 }
 
 // GetReportChapterTypeCount 获取章节类型总数
 func GetReportChapterTypeCount(condition string, pars []interface{}) (count int, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT COUNT(1) FROM report_chapter_type WHERE 1 = 1 `
 	sql += condition
 	err = o.Raw(sql, pars).QueryRow(&count)
@@ -314,13 +313,12 @@ func GetReportChapterTypeCount(condition string, pars []interface{}) (count int,
 }
 
 // GetReportChapterTypePageList 获取章节类型列表
-func GetReportChapterTypePageList(condition string, pars []interface{}, startSize, pageSize int) (list []*ReportChapterType, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+func GetReportChapterTypePageList(condition string, pars []interface{}) (list []*ReportChapterType, err error) {
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM report_chapter_type WHERE 1 = 1 `
 	sql += condition
 	sql += ` ORDER BY sort ASC, created_time DESC`
-	sql += ` LIMIT ?,?`
-	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&list)
+	_, err = o.Raw(sql, pars).QueryRows(&list)
 	return
 }
 
@@ -336,31 +334,33 @@ type ReportChapterTypeListItem struct {
 	WordsImage            string `description:"带字的icon"`
 	EditImgUrl            string `description:"管理后台编辑时选用的图"`
 	IsShow                int    `description:"显示隐藏: 1-显示; 0-隐藏"`
+	ChartPermissionIdList []int  `description:"权限id数组"`
+	Enabled               int    `description:"是否可用,1可用,0禁用"`
 }
 
 // ReportChapterTypeAddReq 新增章节类型请求体
 type ReportChapterTypeAddReq struct {
 	ReportChapterTypeName string `description:"报告章节类型名称"`
-	Sort                  int    `description:"排序字段"`
 	ResearchType          string `description:"研报类型"`
-	SelectedImage         string `description:"选中时的icon"`
+	ChartPermissionIdList []int  `description:"权限id数组"`
+	/*SelectedImage         string `description:"选中时的icon"`
 	UnselectedImage       string `description:"未选中时的icon"`
 	WordsImage            string `description:"带字的icon"`
 	EditImgUrl            string `description:"管理后台编辑时选用的图"`
-	IsShow                int    `description:"显示隐藏: 1-显示; 0-隐藏"`
+	IsShow                int    `description:"显示隐藏: 1-显示; 0-隐藏"`*/
 }
 
 // ReportChapterTypeEditReq 编辑章节类型请求体
 type ReportChapterTypeEditReq struct {
 	ReportChapterTypeId   int    `description:"报告章节类型id"`
 	ReportChapterTypeName string `description:"报告章节类型名称"`
-	Sort                  int    `description:"排序字段"`
 	ResearchType          string `description:"研报类型"`
-	SelectedImage         string `description:"选中时的icon"`
+	ChartPermissionIdList []int  `description:"权限id数组"`
+	/*SelectedImage         string `description:"选中时的icon"`
 	UnselectedImage       string `description:"未选中时的icon"`
 	WordsImage            string `description:"带字的icon"`
 	EditImgUrl            string `description:"管理后台编辑时选用的图"`
-	IsShow                int    `description:"显示隐藏: 1-显示; 0-隐藏"`
+	IsShow                int    `description:"显示隐藏: 1-显示; 0-隐藏"`*/
 }
 
 // ReportChapterTypeDelReq 删除章节类型请求体
@@ -374,12 +374,76 @@ type ReportChapterTypeAuthSettingReq struct {
 	ChartPermissionIdList []int `description:"权限id数组"`
 }
 
+type ReportChapterTypeMoveReq struct {
+	ReportChapterTypeId int `description:"报告章节类型id"`
+	//	ParentChartPermissionId int `description:"父级品种id"`
+	PrevReportChapterTypeId int `description:"上一个兄弟节点报告章节类型id"`
+	NextReportChapterTypeId int `description:"下一个兄弟节点报告章节类型id"`
+}
+
+type ReportChapterTypeEnabledReq struct {
+	ReportChapterTypeId int `description:"报告章节类型id"`
+	Enabled             int `description:"是否可用,1可用,0禁用"`
+}
+
 // GetReportChapterTypeByCondition 获取章节类型
 func GetReportChapterTypeByCondition(condition string, pars []interface{}) (item *ReportChapterType, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM report_chapter_type WHERE 1 = 1 `
 	sql += condition
 	sql += ` LIMIT 1`
 	err = o.Raw(sql, pars).QueryRow(&item)
 	return
 }
+
+// GetMaxSort 获取最大的排序值
+func (r *ReportChapterType) GetMaxSort() (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT max(sort) AS sort FROM report_chapter_type`
+	err = o.Raw(sql).QueryRow(&maxSort)
+	return
+}
+
+// GetMaxSortByResearchType 获取最大的排序值
+func (r *ReportChapterType) GetMaxSortByResearchType(researchType string) (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT max(sort) AS sort FROM report_chapter_type WHERE research_type = ?`
+	err = o.Raw(sql, researchType).QueryRow(&maxSort)
+	return
+}
+
+// UpdateReportChapterTypeSortByResearchType 根据父类id更新排序
+func UpdateReportChapterTypeSortByResearchType(researchType string, reportChapterTypeId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` update report_chapter_type set sort = ` + updateSort + ` WHERE research_type=? AND sort > ?`
+	if reportChapterTypeId > 0 {
+		sql += ` or ( report_chapter_type_id > ` + fmt.Sprint(reportChapterTypeId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+
+	_, err = o.Raw(sql, researchType, nowSort).Exec()
+	return
+}
+
+// GetFirstReportChapterTypeByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func (r *ReportChapterType) GetFirstReportChapterTypeByResearchType(researchType string) (item *ReportChapterType, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type WHERE 1 = 1 AND research_type = ? ORDER BY sort ASC, report_chapter_type_id ASC LIMIT 1`
+	err = o.Raw(sql, researchType).QueryRow(&item)
+	return
+}
+
+// GetReportChapterTypeById 获取章节类型
+func (r *ReportChapterType) GetReportChapterTypeById(reportChapterTypeId int) (item *ReportChapterType, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type WHERE report_chapter_type_id = ?`
+	err = o.Raw(sql, reportChapterTypeId).QueryRow(&item)
+	return
+}
+
+// SetEnabled 更新启动禁用
+func (r *ReportChapterType) SetEnabled(id, enabled int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` update report_chapter_type set enabled = ? WHERE report_chapter_type_id = ?`
+	_, err = o.Raw(sql, enabled, id).Exec()
+	return
+}

+ 10 - 2
models/report_chapter_type_permission.go

@@ -18,7 +18,7 @@ type ReportChapterTypePermission struct {
 
 // GetChapterTypePermissionByTypeIdAndResearchType 根据章节类型ID及研报类型获取章节类型权限列表
 func GetChapterTypePermissionByTypeIdAndResearchType(typeId int, researchType string) (list []*ReportChapterTypePermission, err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	sql := ` SELECT * FROM report_chapter_type_permission WHERE report_chapter_type_id = ? AND research_type = ? ORDER BY chart_permission_id ASC `
 	_, err = o.Raw(sql, typeId, researchType).QueryRows(&list)
 	return
@@ -26,7 +26,7 @@ func GetChapterTypePermissionByTypeIdAndResearchType(typeId int, researchType st
 
 // SetReportChapterTypePermission 设置报告章节类型权限
 func SetReportChapterTypePermission(chapterTypeId int, researchType string, newPermissions []*ReportChapterTypePermission, newWeekPermissions []*ChartPermissionChapterMapping) (err error) {
-	o := orm.NewOrmUsingDB("weekly")
+	o := orm.NewOrmUsingDB("rddp")
 	to, err := o.Begin()
 	if err != nil {
 		return
@@ -73,3 +73,11 @@ func SetReportChapterTypePermission(chapterTypeId int, researchType string, newP
 	}
 	return
 }
+
+// GetChapterTypePermissionByResearchType 根据章节类型ID及研报类型获取章节类型权限列表
+func GetChapterTypePermissionByResearchType(researchType string) (list []*ReportChapterTypePermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type_permission WHERE research_type = ? ORDER BY chart_permission_id ASC `
+	_, err = o.Raw(sql, researchType).QueryRows(&list)
+	return
+}

+ 561 - 0
models/speech_recognition/speech_recognition.go

@@ -0,0 +1,561 @@
+package speech_recognition
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+const (
+	SpeechRecognitionFileRemoveFlag = 1 // 文件删除标记
+
+	SpeechRecognitionStateWait    = 1
+	SpeechRecognitionStateSuccess = 2
+	SpeechRecognitionStateFail    = 3
+)
+
+// SpeechRecognition 语音识别主表
+type SpeechRecognition struct {
+	SpeechRecognitionId int       `orm:"column(speech_recognition_id);pk"`
+	UniqueCode          string    `description:"唯一编码"`
+	FileName            string    `description:"文件名称"`
+	ResourceUrl         string    `description:"文件路径"`
+	MenuId              int       `description:"目录ID"`
+	SysUserId           int       `description:"创建人ID"`
+	SysUserName         string    `description:"创建人姓名"`
+	State               int       `description:"状态:1-待转换;2-转换完成;3-转换失败"`
+	Abstract            string    `description:"摘要,取前几段内容"`
+	Sort                int       `description:"目录下的排序"`
+	FileState           int       `description:"文件(非语音识别)删除状态:0-正常;1-删除(该字段作为软删标识)"`
+	FileSecond          int       `description:"文件时长(秒)"`
+	FileSize            int       `description:"文件大小(byte)"`
+	ConvertRemark       string    `description:"转写备注-失败原因"`
+	CreateTime          time.Time `description:"创建时间"`
+	ModifyTime          time.Time `description:"修改时间"`
+}
+
+var SpeechRecognitionCols = struct {
+	SpeechRecognitionId string
+	UniqueCode          string
+	FileName            string
+	ResourceUrl         string
+	MenuId              string
+	MenuPath            string
+	SysUserId           string
+	SysUserName         string
+	State               string
+	Abstract            string
+	Sort                string
+	FileState           string
+	FileSecond          string
+	FileSize            string
+	ConvertRemark       string
+	CreateTime          string
+	ModifyTime          string
+}{
+	SpeechRecognitionId: "speech_recognition_id",
+	UniqueCode:          "unique_code",
+	FileName:            "file_name",
+	ResourceUrl:         "resource_url",
+	MenuId:              "menu_id",
+	MenuPath:            "menu_path",
+	SysUserId:           "sys_user_id",
+	SysUserName:         "sys_user_name",
+	State:               "state",
+	Abstract:            "abstract",
+	Sort:                "sort",
+	FileState:           "file_state",
+	FileSecond:          "file_second",
+	FileSize:            "file_size",
+	ConvertRemark:       "convert_remark",
+	CreateTime:          "create_time",
+	ModifyTime:          "modify_time",
+}
+
+func (m *SpeechRecognition) TableName() string {
+	return "speech_recognition"
+}
+
+func (m *SpeechRecognition) PrimaryId() string {
+	return SpeechRecognitionCols.SpeechRecognitionId
+}
+
+func (m *SpeechRecognition) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.SpeechRecognitionId = int(id)
+	return
+}
+
+func (m *SpeechRecognition) CreateMulti(items []*SpeechRecognition) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *SpeechRecognition) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *SpeechRecognition) Del() (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.SpeechRecognitionId).Exec()
+	return
+}
+
+func (m *SpeechRecognition) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *SpeechRecognition) GetItemById(id int) (item *SpeechRecognition, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognition) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *SpeechRecognition, err error) {
+	o := orm.NewOrm()
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognition) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *SpeechRecognition) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SpeechRecognition, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *SpeechRecognition) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*SpeechRecognition, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// SpeechRecognitionItem 语音识别信息
+type SpeechRecognitionItem struct {
+	SpeechRecognitionId int
+	UniqueCode          string `description:"唯一编码"`
+	FileName            string `description:"文件名称"`
+	ResourceUrl         string `description:"文件路径"`
+	MenuId              int    `description:"目录ID"`
+	SysUserId           int    `description:"创建人ID"`
+	SysUserName         string `description:"创建人姓名"`
+	State               int    `description:"状态:1-待转换;2-转换完成;3-转换失败"`
+	Abstract            string `description:"摘要,取前几段内容"`
+	Sort                int    `description:"目录下的排序"`
+	ConvertRemark       string `description:"转写备注-失败原因"`
+	CreateTime          string `description:"创建时间"`
+	ModifyTime          string `description:"修改时间"`
+}
+
+func FormatSpeechRecognition2Item(origin *SpeechRecognition) (item *SpeechRecognitionItem) {
+	if origin == nil {
+		return
+	}
+	item = new(SpeechRecognitionItem)
+	item.SpeechRecognitionId = origin.SpeechRecognitionId
+	item.UniqueCode = origin.UniqueCode
+	item.FileName = origin.FileName
+	item.ResourceUrl = origin.ResourceUrl
+	if origin.FileState == SpeechRecognitionFileRemoveFlag {
+		item.ResourceUrl = ""
+	}
+	item.MenuId = origin.MenuId
+	item.SysUserId = origin.SysUserId
+	item.SysUserName = origin.SysUserName
+	item.State = origin.State
+	item.Abstract = origin.Abstract
+	item.Sort = origin.Sort
+	item.ConvertRemark = origin.ConvertRemark
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	return
+}
+
+// SpeechRecognitionSaveReq 保存请求体
+type SpeechRecognitionSaveReq struct {
+	SpeechRecognitionId int                               `description:"语音识别ID"`
+	FileName            string                            `description:"文件名称"`
+	TagIds              []int                             `description:"标签IDs"`
+	Contents            []SpeechRecognitionSaveContentReq `description:"保存内容"`
+}
+
+// SpeechRecognitionSaveContentReq 保存内容
+type SpeechRecognitionSaveContentReq struct {
+	SpeechRecognitionContentId int    `description:"语音识别内容ID"`
+	Content                    string `description:"段落内容"`
+}
+
+// SpeechRecognitionRenameReq 重命名
+type SpeechRecognitionRenameReq struct {
+	SpeechRecognitionId int    `description:"语音识别ID"`
+	FileName            string `description:"文件名称"`
+}
+
+// SpeechRecognitionRemoveReq 删除
+type SpeechRecognitionRemoveReq struct {
+	SpeechRecognitionId int `description:"语音识别ID"`
+}
+
+// SpeechRecognitionRemoveFileReq 删除文件
+type SpeechRecognitionRemoveFileReq struct {
+	SpeechRecognitionId int `description:"语音识别ID"`
+}
+
+// SpeechRecognitionSaveTagReq 保存标签
+type SpeechRecognitionSaveTagReq struct {
+	SpeechRecognitionId int   `description:"语音识别ID"`
+	TagIds              []int `description:"标签IDs"`
+}
+
+// SpeechRecognitionConvertReq 批量转写
+type SpeechRecognitionConvertReq struct {
+	MenuId int                             `description:"目录ID"`
+	Files  []SpeechRecognitionConvertFiles `description:"转写文件"`
+}
+
+// SpeechRecognitionConvertFiles 批量转写文件
+type SpeechRecognitionConvertFiles struct {
+	FileName    string `description:"文件名称"`
+	ResourceUrl string `description:"文件地址"`
+	FileSecond  int    `description:"文件时长(秒)"`
+	FileSize    int    `description:"文件大小(byte)"`
+}
+
+// UpdateSpeechAndApiLog 更新语音识别及API记录
+func UpdateSpeechAndApiLog(speechItem *SpeechRecognition, speechCols []string, apiLogItem *SpeechRecognitionApiLog, logCols []string) (err error) {
+	if speechItem == nil || apiLogItem == nil {
+		err = fmt.Errorf("speechItem nil or apiLogItem nil")
+		return
+	}
+	o := orm.NewOrm()
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+		} else {
+			_ = tx.Commit()
+		}
+	}()
+
+	_, e := tx.Update(speechItem, speechCols...)
+	if e != nil {
+		err = fmt.Errorf("update speech err: %s", e.Error())
+		return
+	}
+	_, e = tx.Update(apiLogItem, logCols...)
+	if e != nil {
+		err = fmt.Errorf("update api log err: %s", e.Error())
+		return
+	}
+	return
+}
+
+// CreateContentAndUpdateSpeechAndApiLog 新增语音识别内容并更新语音识别及API记录
+func CreateContentAndUpdateSpeechAndApiLog(contents []*SpeechRecognitionContent, speechItem *SpeechRecognition, speechCols []string, apiLogItem *SpeechRecognitionApiLog, logCols []string) (err error) {
+	if speechItem == nil || apiLogItem == nil {
+		err = fmt.Errorf("speechItem nil or apiLogItem nil")
+		return
+	}
+	o := orm.NewOrm()
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+		} else {
+			_ = tx.Commit()
+		}
+	}()
+
+	_, e := tx.Update(speechItem, speechCols...)
+	if e != nil {
+		err = fmt.Errorf("update speech err: %s", e.Error())
+		return
+	}
+	_, e = tx.Update(apiLogItem, logCols...)
+	if e != nil {
+		err = fmt.Errorf("update api log err: %s", e.Error())
+		return
+	}
+	if len(contents) > 0 {
+		_, e = tx.InsertMulti(len(contents), contents)
+		if e != nil {
+			err = fmt.Errorf("insert multi contents err: %s", e.Error())
+			return
+		}
+	}
+	return
+}
+
+// SpeechRecognitionDetailItem 语音识别详情信息
+type SpeechRecognitionDetailItem struct {
+	SpeechRecognitionId int
+	UniqueCode          string                          `description:"唯一编码"`
+	FileName            string                          `description:"文件名称"`
+	FileExt             string                          `description:"文件后缀名"`
+	ResourceUrl         string                          `description:"文件地址"`
+	MenuId              int                             `description:"目录ID"`
+	MenuPath            []*SpeechRecognitionMenuItem    `description:"目录全路径, 多级并列为一个数组"`
+	SysUserId           int                             `description:"创建人ID"`
+	SysUserName         string                          `description:"创建人姓名"`
+	Abstract            string                          `description:"摘要,取前几段内容"`
+	Sort                int                             `description:"目录下的排序"`
+	FileSecond          string                          `description:"文件时长(HH:MM:SS/MM:SS)"`
+	FileSize            string                          `description:"文件大小(MB)"`
+	CreateTime          string                          `description:"创建时间"`
+	ModifyTime          string                          `description:"修改时间"`
+	Contents            []*SpeechRecognitionContentItem `description:"语音识别内容"`
+	Tags                []*SpeechRecognitionDetailTag   `description:"标签"`
+}
+
+func FormatSpeechRecognition2DetailItem(origin *SpeechRecognition, contents []*SpeechRecognitionContentItem, tags []*SpeechRecognitionDetailTag, menuPath []*SpeechRecognitionMenuItem) (item *SpeechRecognitionDetailItem) {
+	if origin == nil {
+		return
+	}
+	item = new(SpeechRecognitionDetailItem)
+	item.SpeechRecognitionId = origin.SpeechRecognitionId
+	item.UniqueCode = origin.UniqueCode
+	item.FileName = origin.FileName
+	item.ResourceUrl = origin.ResourceUrl
+	if item.ResourceUrl != "" {
+		pointArr := strings.Split(item.ResourceUrl, ".")
+		if len(pointArr) > 0 {
+			item.FileExt = pointArr[len(pointArr)-1]
+		}
+	}
+	item.ResourceUrl = origin.ResourceUrl
+	if origin.FileState == SpeechRecognitionFileRemoveFlag {
+		item.ResourceUrl = ""
+	}
+	item.MenuId = origin.MenuId
+	item.MenuPath = menuPath
+	item.SysUserId = origin.SysUserId
+	item.SysUserName = origin.SysUserName
+	item.Abstract = origin.Abstract
+	item.Sort = origin.Sort
+	item.FileSecond = utils.SecondsToHHMMSS(origin.FileSecond)
+	item.FileSize = fmt.Sprintf("%.2fM", utils.ByteToMB(origin.FileSize))
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	item.Contents = contents
+	item.Tags = tags
+	return
+}
+
+// SpeechRecognitionDetailTag 语音识别详情标签
+type SpeechRecognitionDetailTag struct {
+	TagId   int    `description:"标签ID"`
+	TagName string `description:"标签名称"`
+}
+
+// GetSpeechRecognitionTagBySpeechId 获取语音识别标签
+func GetSpeechRecognitionTagBySpeechId(speechId int) (items []*SpeechRecognitionDetailTag, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT a.speech_recognition_tag_id AS tag_id, a.tag_name FROM speech_recognition_tag AS a
+		JOIN speech_recognition_tag_mapping AS b ON a.speech_recognition_tag_id = b.tag_id
+		WHERE b.speech_recognition_id = ?`
+	_, err = o.Raw(sql, speechId).QueryRows(&items)
+	return
+}
+
+// SpeechRecognitionMappingTags 语音识别标签
+type SpeechRecognitionMappingTags struct {
+	SpeechRecognitionId int    `description:"语音识别ID"`
+	TagId               int    `description:"标签ID"`
+	TagName             string `description:"标签名称"`
+}
+
+// GetSpeechRecognitionTagsBySpeechIds 根据语音识别IDs获取标签
+func GetSpeechRecognitionTagsBySpeechIds(speechIds []int) (items []*SpeechRecognitionMappingTags, err error) {
+	if len(speechIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT a.speech_recognition_id, a.tag_id, b.tag_name FROM speech_recognition_tag_mapping AS a
+		JOIN speech_recognition_tag AS b ON a.tag_id = b.speech_recognition_tag_id
+		WHERE a.speech_recognition_id IN (%s)`, utils.GetOrmInReplace(len(speechIds)))
+	_, err = o.Raw(sql, speechIds).QueryRows(&items)
+	return
+}
+
+// SpeechRecognitionListReq 语音识别列表请求体
+type SpeechRecognitionListReq struct {
+	PageSize     int    `form:"PageSize"`
+	CurrentIndex int    `form:"CurrentIndex"`
+	FileName     string `form:"FileName" description:"文件名称"`
+	StartTime    string `form:"StartTime" description:"开始时间"`
+	EndTime      string `form:"EndTime" description:"结束时间"`
+	CreateUserId string `form:"CreateUserId" description:"创建人IDs"`
+	TagId        int    `form:"TagId" description:"标签ID"`
+	TagIds       string `form:"TagIds" description:"标签IDs, 英文逗号分隔"`
+	MenuId       int    `form:"MenuId" description:"目录ID"`
+	IsTagMenu    bool   `form:"IsTagMenu" description:"是否为标签目录"`
+}
+
+// SpeechRecognitionListResp 语音识别列表响应体
+type SpeechRecognitionListResp struct {
+	List   []*SpeechRecognitionDetailItem
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+// SpeechRecognitionContentExportReq 导出内容
+type SpeechRecognitionContentExportReq struct {
+	SpeechRecognitionId int  `description:"语音识别ID"`
+	ExportType          int  `description:"导出类型:1-txt;2-doc;3-pdf"`
+	Timestamp           bool `description:"是否显示时间戳"`
+}
+
+// UpdateSortByMenuId 根据分类ID更新排序
+func (m *SpeechRecognition) UpdateSortByMenuId(menuId, nowSort int, prevSpeechId int, updateSort string) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`UPDATE %s SET %s = %s WHERE %s = ? `, m.TableName(), SpeechRecognitionCols.Sort, updateSort, SpeechRecognitionCols.MenuId)
+	if prevSpeechId > 0 {
+		sql += fmt.Sprintf(` AND (%s > ? OR (%s > %d AND %s = %d))`, SpeechRecognitionCols.Sort, SpeechRecognitionCols.SpeechRecognitionId, prevSpeechId, SpeechRecognitionCols.Sort, nowSort)
+	} else {
+		sql += fmt.Sprintf(` AND %s > ?`, SpeechRecognitionCols.Sort)
+	}
+	_, err = o.Raw(sql, menuId, nowSort).Exec()
+	return
+}
+
+// GetMaxSortByMenuId 获取分类下最大Sort
+func (m *SpeechRecognition) GetMaxSortByMenuId(menuId int) (sort int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT MAX(sort) AS sort FROM %s WHERE %s = ?`, m.TableName(), SpeechRecognitionCols.MenuId)
+	err = o.Raw(sql, menuId).QueryRow(&sort)
+	return
+}
+
+// GetFirstByMenuId 获取目录下排序第一的数据
+func (m *SpeechRecognition) GetFirstByMenuId(menuId int) (item *SpeechRecognition, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? ORDER BY %s ASC, %s ASC LIMIT 1`, m.TableName(), SpeechRecognitionCols.MenuId, SpeechRecognitionCols.Sort, SpeechRecognitionCols.SpeechRecognitionId)
+	err = o.Raw(sql, menuId).QueryRow(&item)
+	return
+}
+
+// SpeechRecognitionConvertCheckNameReq 校验文件重名请求体
+type SpeechRecognitionConvertCheckNameReq struct {
+	FileName string `description:"文件名称"`
+}
+
+// SpeechSave 更新内容、摘要及标签
+func (m *SpeechRecognition) SpeechSave(speechItem *SpeechRecognition, speechCols []string, contents []SpeechRecognitionSaveContentReq, tagMappings []*SpeechRecognitionTagMapping) (err error) {
+	if speechItem == nil {
+		err = fmt.Errorf("speech nil")
+		return
+	}
+	o := orm.NewOrm()
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("transaction begin err: %s", e.Error())
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	// 转写文件
+	if len(speechCols) > 0 {
+		_, e = tx.Update(speechItem, speechCols...)
+		if e != nil {
+			err = fmt.Errorf("update speech err: %s", e.Error())
+			return
+		}
+	}
+
+	// 转写内容
+	if len(contents) > 0 {
+		sql := fmt.Sprintf(`UPDATE %s SET %s = ?, %s = 1, %s = NOW() WHERE %s = ?`, "speech_recognition_content", SpeechRecognitionContentCols.Content, SpeechRecognitionContentCols.IsUpdate, SpeechRecognitionContentCols.ModifyTime, SpeechRecognitionContentCols.SpeechRecognitionContentId)
+		p, e := tx.Raw(sql).Prepare()
+		if e != nil {
+			err = fmt.Errorf("update prepare err: %s", e.Error())
+			return
+		}
+		defer func() {
+			_ = p.Close()
+		}()
+		for _, v := range contents {
+			_, e = p.Exec(v.Content, v.SpeechRecognitionContentId)
+			if e != nil {
+				err = fmt.Errorf("update exec err: %s", e.Error())
+				return
+			}
+		}
+	}
+
+	// 标签
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ?`, "speech_recognition_tag_mapping", SpeechRecognitionTagMappingCols.SpeechRecognitionId)
+	_, e = tx.Raw(sql, speechItem.SpeechRecognitionId).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove tag mappings err: %s", e.Error())
+		return
+	}
+	if len(tagMappings) > 0 {
+		_, e = tx.InsertMulti(len(tagMappings), tagMappings)
+		if e != nil {
+			err = fmt.Errorf("insert tag mappings err: %s", e.Error())
+			return
+		}
+	}
+	return
+}

+ 159 - 0
models/speech_recognition/speech_recognition_api_log.go

@@ -0,0 +1,159 @@
+package speech_recognition
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	ApiRequestCodeSuccess = 0 // API成功状态码
+)
+
+// ApiErrMsgMapping API请求结果状态码对应错误提示
+var ApiErrMsgMapping = map[int]string{
+	10000: "转码失败,请确认音频格式是否符合标准",
+	10001: "识别失败",
+	10002: "语音时长太短",
+	10003: "语音时长太长",
+	10004: "无效的语音文件",
+	10005: "其他失败",
+	10006: "音轨个数不匹配",
+	10007: "音频下载失败",
+}
+
+// SpeechRecognitionApiLog 语音识别-API请求日志
+type SpeechRecognitionApiLog struct {
+	Id                  int       `orm:"column(id);pk"`
+	SpeechRecognitionId int       `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	RequestId           string    `description:"API请求的唯一标识TaskId"`
+	RequestCode         int       `description:"API请求结果状态码:-1-待请求;0-成功;其他-失败"`
+	RequestResult       string    `description:"API请求结果-JSON"`
+	CreateTime          time.Time `description:"创建时间"`
+	ModifyTime          time.Time `description:"修改时间"`
+}
+
+var SpeechRecognitionApiLogCols = struct {
+	Id                  string
+	SpeechRecognitionId string
+	RequestId           string
+	RequestCode         string
+	RequestResult       string
+	CreateTime          string
+	ModifyTime          string
+}{
+	Id:                  "id",
+	SpeechRecognitionId: "speech_recognition_id",
+	RequestId:           "request_id",
+	RequestCode:         "request_code",
+	RequestResult:       "request_result",
+	CreateTime:          "create_time",
+	ModifyTime:          "modify_time",
+}
+
+func (m *SpeechRecognitionApiLog) TableName() string {
+	return "speech_recognition_api_log"
+}
+
+func (m *SpeechRecognitionApiLog) PrimaryId() string {
+	return SpeechRecognitionApiLogCols.Id
+}
+
+func (m *SpeechRecognitionApiLog) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *SpeechRecognitionApiLog) CreateMulti(items []*SpeechRecognitionApiLog) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *SpeechRecognitionApiLog) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *SpeechRecognitionApiLog) Del() (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.Id).Exec()
+	return
+}
+
+func (m *SpeechRecognitionApiLog) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *SpeechRecognitionApiLog) GetItemById(id int) (item *SpeechRecognitionApiLog, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionApiLog) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *SpeechRecognitionApiLog, err error) {
+	o := orm.NewOrm()
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionApiLog) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *SpeechRecognitionApiLog) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SpeechRecognitionApiLog, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *SpeechRecognitionApiLog) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*SpeechRecognitionApiLog, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}

+ 211 - 0
models/speech_recognition/speech_recognition_content.go

@@ -0,0 +1,211 @@
+package speech_recognition
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// SpeechRecognitionContent 语音识别-内容表
+type SpeechRecognitionContent struct {
+	SpeechRecognitionContentId int       `orm:"column(speech_recognition_content_id);pk"`
+	SpeechRecognitionId        int       `description:"语音识别ID"`
+	Sort                       int       `description:"段落排序"`
+	Content                    string    `description:"段落内容"`
+	StartMs                    int       `description:"单句开始时间(毫秒)"`
+	EndMs                      int       `description:"单句结束时间(毫秒)"`
+	IsUpdate                   int       `description:"是否手动修改过:0-否;1-是"`
+	CreateTime                 time.Time `description:"创建时间"`
+	ModifyTime                 time.Time `description:"修改时间"`
+}
+
+var SpeechRecognitionContentCols = struct {
+	SpeechRecognitionContentId string
+	SpeechRecognitionId        string
+	Sort                       string
+	Content                    string
+	StartMs                    string
+	EndMs                      string
+	IsUpdate                   string
+	CreateTime                 string
+	ModifyTime                 string
+}{
+	SpeechRecognitionContentId: "speech_recognition_content_id",
+	SpeechRecognitionId:        "speech_recognition_id",
+	Sort:                       "sort",
+	Content:                    "content",
+	StartMs:                    "start_ms",
+	EndMs:                      "end_ms",
+	IsUpdate:                   "is_update",
+	CreateTime:                 "create_time",
+	ModifyTime:                 "modify_time",
+}
+
+func (m *SpeechRecognitionContent) TableName() string {
+	return "speech_recognition_content"
+}
+
+func (m *SpeechRecognitionContent) PrimaryId() string {
+	return SpeechRecognitionContentCols.SpeechRecognitionContentId
+}
+
+func (m *SpeechRecognitionContent) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.SpeechRecognitionContentId = int(id)
+	return
+}
+
+func (m *SpeechRecognitionContent) CreateMulti(items []*SpeechRecognitionContent) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *SpeechRecognitionContent) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *SpeechRecognitionContent) Del() (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.SpeechRecognitionContentId).Exec()
+	return
+}
+
+func (m *SpeechRecognitionContent) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *SpeechRecognitionContent) GetItemById(id int) (item *SpeechRecognitionContent, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionContent) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *SpeechRecognitionContent, err error) {
+	o := orm.NewOrm()
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionContent) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *SpeechRecognitionContent) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SpeechRecognitionContent, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *SpeechRecognitionContent) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*SpeechRecognitionContent, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+type SpeechRecognitionContentItem struct {
+	SpeechRecognitionContentId int
+	SpeechRecognitionId        int    `description:"语音识别ID"`
+	Sort                       int    `description:"段落排序"`
+	Content                    string `description:"段落内容"`
+	StartMs                    int    `description:"单句开始时间(毫秒)"`
+	EndMs                      int    `description:"单句结束时间(毫秒)"`
+	StartTime                  string `description:"开始时间, 格式HH:MM:SS"`
+	CreateTime                 string `description:"创建时间"`
+	ModifyTime                 string `description:"修改时间"`
+	HideTimestamp              bool   `description:"前端用的, 默认false就行"`
+	IsHover                    bool   `description:"前端用的, 默认false就行"`
+}
+
+func FormatSpeechRecognitionContent2Item(origin *SpeechRecognitionContent) (item *SpeechRecognitionContentItem) {
+	if origin == nil {
+		return
+	}
+	item = new(SpeechRecognitionContentItem)
+	item.SpeechRecognitionContentId = origin.SpeechRecognitionContentId
+	item.SpeechRecognitionId = origin.SpeechRecognitionId
+	item.Sort = origin.Sort
+	item.Content = origin.Content
+	item.StartMs = origin.StartMs
+	item.EndMs = origin.EndMs
+	item.StartTime = utils.MillisecondsToHHMMSS(origin.StartMs)
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	return
+}
+
+// BatchUpdateContents 批量更新语音识别内容
+func (m *SpeechRecognitionContent) BatchUpdateContents(contents []SpeechRecognitionSaveContentReq) (err error) {
+	if len(contents) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ?, %s = 1, %s = NOW() WHERE %s = ?`, m.TableName(), SpeechRecognitionContentCols.Content, SpeechRecognitionContentCols.IsUpdate, SpeechRecognitionContentCols.ModifyTime, SpeechRecognitionContentCols.SpeechRecognitionContentId)
+	p, err := o.Raw(sql).Prepare()
+	if err != nil {
+		return
+	}
+	defer func() {
+		_ = p.Close()
+	}()
+	for _, v := range contents {
+		_, err = p.Exec(v.Content, v.SpeechRecognitionContentId)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+// ClearContentBySpeechId 清除转写文件内容
+func (m *SpeechRecognitionContent) ClearContentBySpeechId(speechId int) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ?`, m.TableName(), SpeechRecognitionContentCols.SpeechRecognitionId)
+	_, err = o.Raw(sql, speechId).Exec()
+	return
+}

+ 256 - 0
models/speech_recognition/speech_recognition_menu.go

@@ -0,0 +1,256 @@
+package speech_recognition
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	SpeechRecognitionMenuNodeTypeDefault = 0
+	SpeechRecognitionMenuNodeTypeSpeech  = 1
+)
+
+// SpeechRecognitionMenu 语音识别-目录表
+type SpeechRecognitionMenu struct {
+	SpeechRecognitionMenuId int       `orm:"column(speech_recognition_menu_id);pk"`
+	UniqueCode              string    `description:"唯一编码"`
+	MenuName                string    `description:"目录名称"`
+	ParentId                int       `description:"父级ID"`
+	Level                   int       `description:"目录层级"`
+	Sort                    int       `description:"排序"`
+	RootId                  int       `description:"顶级ID"`
+	CreateTime              time.Time `description:"创建时间"`
+	ModifyTime              time.Time `description:"修改时间"`
+}
+
+var SpeechRecognitionMenuCols = struct {
+	SpeechRecognitionMenuId string
+	UniqueCode              string
+	MenuName                string
+	ParentId                string
+	Level                   string
+	Sort                    string
+	RootId                  string
+	CreateTime              string
+	ModifyTime              string
+}{
+	SpeechRecognitionMenuId: "speech_recognition_menu_id",
+	UniqueCode:              "unique_code",
+	MenuName:                "menu_name",
+	ParentId:                "parent_id",
+	Level:                   "level",
+	Sort:                    "sort",
+	RootId:                  "root_id",
+	CreateTime:              "create_time",
+	ModifyTime:              "modify_time",
+}
+
+func (m *SpeechRecognitionMenu) TableName() string {
+	return "speech_recognition_menu"
+}
+
+func (m *SpeechRecognitionMenu) PrimaryId() string {
+	return SpeechRecognitionMenuCols.SpeechRecognitionMenuId
+}
+
+func (m *SpeechRecognitionMenu) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.SpeechRecognitionMenuId = int(id)
+	return
+}
+
+func (m *SpeechRecognitionMenu) CreateMulti(items []*SpeechRecognitionMenu) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *SpeechRecognitionMenu) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *SpeechRecognitionMenu) Del() (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.SpeechRecognitionMenuId).Exec()
+	return
+}
+
+func (m *SpeechRecognitionMenu) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *SpeechRecognitionMenu) GetItemById(id int) (item *SpeechRecognitionMenu, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionMenu) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *SpeechRecognitionMenu, err error) {
+	o := orm.NewOrm()
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionMenu) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *SpeechRecognitionMenu) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SpeechRecognitionMenu, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *SpeechRecognitionMenu) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*SpeechRecognitionMenu, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// SpeechRecognitionMenuItem 目录
+type SpeechRecognitionMenuItem struct {
+	UniqueCode string                       `description:"唯一编码"`
+	MenuId     int                          `description:"目录ID"`
+	MenuName   string                       `description:"目录名称"`
+	ParentId   int                          `description:"父级ID"`
+	Level      int                          `description:"目录层级"`
+	Sort       int                          `description:"排序"`
+	CreateTime string                       `description:"创建时间"`
+	Children   []*SpeechRecognitionMenuItem `description:"子目录"`
+}
+
+// SpeechRecognitionMenuNodeItem 语音识别目录节点
+type SpeechRecognitionMenuNodeItem struct {
+	UniqueCode            string                           `description:"唯一编码"`
+	NodeType              int                              `description:"节点类型:0-目录;1-语音识别"`
+	MenuId                int                              `description:"目录ID"`
+	MenuName              string                           `description:"目录名称"`
+	SpeechRecognitionId   int                              `description:"语音识别ID"`
+	SpeechRecognitionName string                           `description:"语音识别名称"`
+	ParentId              int                              `description:"父级ID"`
+	Level                 int                              `description:"目录层级"`
+	Sort                  int                              `description:"排序"`
+	CreateTime            string                           `description:"创建时间"`
+	Children              []*SpeechRecognitionMenuNodeItem `description:"子节点"`
+}
+
+// SpeechRecognitionMenuAddReq 新增语音识别目录
+type SpeechRecognitionMenuAddReq struct {
+	ParentId int    `description:"父级ID"`
+	MenuName string `description:"目录名称"`
+}
+
+// SpeechRecognitionMenuEditReq 编辑语音识别目录
+type SpeechRecognitionMenuEditReq struct {
+	MenuId   int    `description:"目录ID"`
+	MenuName string `description:"目录名称"`
+}
+
+// SpeechRecognitionMenuRemoveReq 删除语音识别目录
+type SpeechRecognitionMenuRemoveReq struct {
+	MenuId int `description:"目录ID"`
+}
+
+// SpeechRecognitionMenuRemoveCheckResp 删除语音识别目录-响应体
+type SpeechRecognitionMenuRemoveCheckResp struct {
+	CheckResult int    `description:"校验结果:0-可删除;1-关联内容;2-关联空目录"`
+	Tips        string `description:"提示信息"`
+}
+
+// SpeechRecognitionMenuMoveReq 移动目录请求体
+type SpeechRecognitionMenuMoveReq struct {
+	MenuId       int `description:"目录ID"`
+	ParentMenuId int `description:"父级目录ID"`
+	PrevMenuId   int `description:"上一个兄弟节点目录ID"`
+	NextMenuId   int `description:"下一个兄弟节点目录ID"`
+	SpeechId     int `description:"语音识别ID, 大于0则表示移动语音识别"`
+	PrevSpeechId int `description:"上一个语音识别ID"`
+	NextSpeechId int `description:"下一个语音识别ID"`
+}
+
+// UpdateSortByParentId 根据父级ID更新排序
+func (m *SpeechRecognitionMenu) UpdateSortByParentId(parentId, menuId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`UPDATE %s SET %s = %s WHERE %s = ? AND %s > ?`, m.TableName(), SpeechRecognitionMenuCols.Sort, updateSort, SpeechRecognitionMenuCols.ParentId, SpeechRecognitionMenuCols.Sort)
+	if menuId > 0 {
+		sql += fmt.Sprintf(` OR (%s > %d AND %s = %d)`, SpeechRecognitionMenuCols.SpeechRecognitionMenuId, menuId, SpeechRecognitionMenuCols.Sort, nowSort)
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetMaxSortByParentId 获取父级分类下最大Sort
+func (m *SpeechRecognitionMenu) GetMaxSortByParentId(parentId int) (sort int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT MAX(sort) AS sort FROM %s WHERE %s = ?`, m.TableName(), SpeechRecognitionMenuCols.ParentId)
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+// GetFirstByParentId 获取父级目录下排序第一的目录
+func (m *SpeechRecognitionMenu) GetFirstByParentId(parentId int) (item *SpeechRecognitionMenu, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? ORDER BY %s ASC, %s ASC LIMIT 1`, m.TableName(), SpeechRecognitionMenuCols.ParentId, SpeechRecognitionMenuCols.Sort, SpeechRecognitionMenuCols.SpeechRecognitionMenuId)
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+// UpdateChildByParentMenuId 通过父级目录ID更新子目录
+func (m *SpeechRecognitionMenu) UpdateChildByParentMenuId(menuIds []int, rootId int, levelStep int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	var pars []interface{}
+	pars = append(pars, rootId, levelStep)
+	pars = append(pars, menuIds)
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ?, %s = %s + ? WHERE %s IN (%s)`, m.TableName(), SpeechRecognitionMenuCols.RootId, SpeechRecognitionMenuCols.Level, SpeechRecognitionMenuCols.Level, SpeechRecognitionMenuCols.SpeechRecognitionMenuId, utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}

+ 225 - 0
models/speech_recognition/speech_recognition_tag.go

@@ -0,0 +1,225 @@
+package speech_recognition
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// SpeechRecognitionTag 语音识别-标签表
+type SpeechRecognitionTag struct {
+	SpeechRecognitionTagId int       `orm:"column(speech_recognition_tag_id);pk"`
+	UniqueCode             string    `description:"唯一编码"`
+	TagName                string    `description:"标签名称"`
+	MenuId                 int       `description:"目录ID"`
+	Sort                   int       `description:"排序"`
+	CreateTime             time.Time `description:"创建时间"`
+	ModifyTime             time.Time `description:"修改时间"`
+}
+
+var SpeechRecognitionTagCols = struct {
+	SpeechRecognitionTagId string
+	UniqueCode             string
+	TagName                string
+	MenuId                 string
+	MenuPath               string
+	Sort                   string
+	CreateTime             string
+	ModifyTime             string
+}{
+	SpeechRecognitionTagId: "speech_recognition_tag_id",
+	UniqueCode:             "unique_code",
+	TagName:                "tag_name",
+	MenuId:                 "menu_id",
+	MenuPath:               "menu_path",
+	Sort:                   "sort",
+	CreateTime:             "create_time",
+	ModifyTime:             "modify_time",
+}
+
+func (m *SpeechRecognitionTag) TableName() string {
+	return "speech_recognition_tag"
+}
+
+func (m *SpeechRecognitionTag) PrimaryId() string {
+	return SpeechRecognitionTagCols.SpeechRecognitionTagId
+}
+
+func (m *SpeechRecognitionTag) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.SpeechRecognitionTagId = int(id)
+	return
+}
+
+func (m *SpeechRecognitionTag) CreateMulti(items []*SpeechRecognitionTag) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *SpeechRecognitionTag) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *SpeechRecognitionTag) Del() (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.SpeechRecognitionTagId).Exec()
+	return
+}
+
+func (m *SpeechRecognitionTag) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *SpeechRecognitionTag) GetItemById(id int) (item *SpeechRecognitionTag, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionTag) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *SpeechRecognitionTag, err error) {
+	o := orm.NewOrm()
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionTag) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *SpeechRecognitionTag) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SpeechRecognitionTag, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *SpeechRecognitionTag) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*SpeechRecognitionTag, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// SpeechRecognitionTagItem 语音识别标签
+type SpeechRecognitionTagItem struct {
+	UniqueCode string `description:"唯一编码"`
+	TagId      int    `description:"标签ID"`
+	TagName    string `description:"标签名称"`
+	MenuId     int    `description:"目录ID"`
+	Sort       int    `description:"排序"`
+	CreateTime string `description:"创建时间"`
+}
+
+func FormatSpeechRecognitionTag2Item(origin *SpeechRecognitionTag) (item *SpeechRecognitionTagItem) {
+	if origin == nil {
+		return
+	}
+	item = new(SpeechRecognitionTagItem)
+	item.UniqueCode = origin.UniqueCode
+	item.TagId = origin.SpeechRecognitionTagId
+	item.TagName = origin.TagName
+	item.MenuId = origin.MenuId
+	item.Sort = origin.Sort
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	return
+}
+
+// SpeechRecognitionTagAddReq 新增标签
+type SpeechRecognitionTagAddReq struct {
+	MenuId  int    `description:"目录ID"`
+	TagName string `description:"标签名称"`
+}
+
+// SpeechRecognitionTagEditReq 编辑标签
+type SpeechRecognitionTagEditReq struct {
+	TagId   int    `description:"标签ID"`
+	TagName string `description:"标签名称"`
+}
+
+// SpeechRecognitionTagRemoveReq 删除标签
+type SpeechRecognitionTagRemoveReq struct {
+	TagId int `description:"标签ID"`
+}
+
+// UpdateSortByMenuId 根据分类ID更新排序
+func (m *SpeechRecognitionTag) UpdateSortByMenuId(menuId, nowSort int, prevTagId int, updateSort string) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`UPDATE %s SET %s = %s WHERE %s = ? `, m.TableName(), SpeechRecognitionTagCols.Sort, updateSort, SpeechRecognitionTagCols.MenuId)
+	if prevTagId > 0 {
+		sql += fmt.Sprintf(` AND (%s > ? OR (%s > %d AND %s = %d))`, SpeechRecognitionTagCols.Sort, SpeechRecognitionTagCols.SpeechRecognitionTagId, prevTagId, SpeechRecognitionTagCols.Sort, nowSort)
+	} else {
+		sql += fmt.Sprintf(` AND %s > ?`, SpeechRecognitionTagCols.Sort)
+	}
+	_, err = o.Raw(sql, menuId, nowSort).Exec()
+	return
+}
+
+// GetMaxSortByMenuId 获取分类下最大Sort
+func (m *SpeechRecognitionTag) GetMaxSortByMenuId(menuId int) (sort int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT MAX(sort) AS sort FROM %s WHERE %s = ?`, m.TableName(), SpeechRecognitionTagCols.MenuId)
+	err = o.Raw(sql, menuId).QueryRow(&sort)
+	return
+}
+
+// GetFirstByMenuId 获取目录下排序第一的数据
+func (m *SpeechRecognitionTag) GetFirstByMenuId(menuId int) (item *SpeechRecognitionTag, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? ORDER BY %s ASC, %s ASC LIMIT 1`, m.TableName(), SpeechRecognitionTagCols.MenuId, SpeechRecognitionTagCols.Sort, SpeechRecognitionTagCols.SpeechRecognitionTagId)
+	err = o.Raw(sql, menuId).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionTag) GetTagIdsByMenuIds(menuIds []int) (tagIds []int, err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE %s IN (%s) GROUP BY %s`, SpeechRecognitionTagCols.SpeechRecognitionTagId, m.TableName(), SpeechRecognitionTagCols.MenuId, utils.GetOrmInReplace(len(menuIds)), SpeechRecognitionTagCols.SpeechRecognitionTagId)
+	_, err = o.Raw(sql, menuIds).QueryRows(&tagIds)
+	return
+}

+ 148 - 0
models/speech_recognition/speech_recognition_tag_mapping.go

@@ -0,0 +1,148 @@
+package speech_recognition
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+)
+
+// SpeechRecognitionTagMapping 语音识别-标签关联表
+type SpeechRecognitionTagMapping struct {
+	Id                  int `orm:"column(id);pk"`
+	SpeechRecognitionId int `description:"语音识别ID"`
+	TagId               int `description:"标签ID"`
+}
+
+var SpeechRecognitionTagMappingCols = struct {
+	Id                  string
+	SpeechRecognitionId string
+	TagId               string
+}{
+	Id:                  "id",
+	SpeechRecognitionId: "speech_recognition_id",
+	TagId:               "tag_id",
+}
+
+func (m *SpeechRecognitionTagMapping) TableName() string {
+	return "speech_recognition_tag_mapping"
+}
+
+func (m *SpeechRecognitionTagMapping) PrimaryId() string {
+	return SpeechRecognitionTagMappingCols.Id
+}
+
+func (m *SpeechRecognitionTagMapping) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *SpeechRecognitionTagMapping) CreateMulti(items []*SpeechRecognitionTagMapping) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *SpeechRecognitionTagMapping) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *SpeechRecognitionTagMapping) Del() (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.Id).Exec()
+	return
+}
+
+func (m *SpeechRecognitionTagMapping) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *SpeechRecognitionTagMapping) GetItemById(id int) (item *SpeechRecognitionTagMapping, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionTagMapping) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *SpeechRecognitionTagMapping, err error) {
+	o := orm.NewOrm()
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionTagMapping) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *SpeechRecognitionTagMapping) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SpeechRecognitionTagMapping, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *SpeechRecognitionTagMapping) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*SpeechRecognitionTagMapping, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// ClearMappingBySpeechId 清除转写文件标签关联
+func (m *SpeechRecognitionTagMapping) ClearMappingBySpeechId(speechId int) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ?`, m.TableName(), SpeechRecognitionTagMappingCols.SpeechRecognitionId)
+	_, err = o.Raw(sql, speechId).Exec()
+	return
+}
+
+func (m *SpeechRecognitionTagMapping) GetSpeechIdsByTagIds(tagIds []int) (speechIds []int, err error) {
+	if len(tagIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE %s IN (%s) GROUP BY %s`, SpeechRecognitionTagMappingCols.SpeechRecognitionId, m.TableName(), SpeechRecognitionTagMappingCols.TagId, utils.GetOrmInReplace(len(tagIds)), SpeechRecognitionTagMappingCols.SpeechRecognitionId)
+	_, err = o.Raw(sql, tagIds).QueryRows(&speechIds)
+	return
+}

+ 250 - 0
models/speech_recognition/speech_recognition_tag_menu.go

@@ -0,0 +1,250 @@
+package speech_recognition
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	SpeechRecognitionTagMenuNodeTypeDefault = 0
+	SpeechRecognitionTagMenuNodeTypeTag     = 1
+)
+
+// SpeechRecognitionTagMenu 标签目录表
+type SpeechRecognitionTagMenu struct {
+	SpeechRecognitionTagMenuId int       `orm:"column(speech_recognition_tag_menu_id);pk"`
+	UniqueCode                 string    `description:"唯一编码"`
+	MenuName                   string    `description:"目录名称"`
+	ParentId                   int       `description:"父级ID"`
+	Level                      int       `description:"目录层级"`
+	Sort                       int       `description:"排序"`
+	RootId                     int       `description:"顶级ID"`
+	CreateTime                 time.Time `description:"创建时间"`
+	ModifyTime                 time.Time `description:"修改时间"`
+}
+
+var SpeechRecognitionTagMenuCols = struct {
+	SpeechRecognitionTagMenuId string
+	UniqueCode                 string
+	MenuName                   string
+	ParentId                   string
+	Level                      string
+	Sort                       string
+	RootId                     string
+	CreateTime                 string
+	ModifyTime                 string
+}{
+	SpeechRecognitionTagMenuId: "speech_recognition_tag_menu_id",
+	UniqueCode:                 "unique_code",
+	MenuName:                   "menu_name",
+	ParentId:                   "parent_id",
+	Level:                      "level",
+	Sort:                       "sort",
+	RootId:                     "root_id",
+	CreateTime:                 "create_time",
+	ModifyTime:                 "modify_time",
+}
+
+func (m *SpeechRecognitionTagMenu) TableName() string {
+	return "speech_recognition_tag_menu"
+}
+
+func (m *SpeechRecognitionTagMenu) PrimaryId() string {
+	return SpeechRecognitionTagMenuCols.SpeechRecognitionTagMenuId
+}
+
+func (m *SpeechRecognitionTagMenu) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.SpeechRecognitionTagMenuId = int(id)
+	return
+}
+
+func (m *SpeechRecognitionTagMenu) CreateMulti(items []*SpeechRecognitionTagMenu) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *SpeechRecognitionTagMenu) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *SpeechRecognitionTagMenu) Del() (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.SpeechRecognitionTagMenuId).Exec()
+	return
+}
+
+func (m *SpeechRecognitionTagMenu) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *SpeechRecognitionTagMenu) GetItemById(id int) (item *SpeechRecognitionTagMenu, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionTagMenu) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *SpeechRecognitionTagMenu, err error) {
+	o := orm.NewOrm()
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *SpeechRecognitionTagMenu) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *SpeechRecognitionTagMenu) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SpeechRecognitionTagMenu, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *SpeechRecognitionTagMenu) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*SpeechRecognitionTagMenu, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// SpeechRecognitionTagMenuItem 标签目录
+type SpeechRecognitionTagMenuItem struct {
+	UniqueCode string                          `description:"唯一编码"`
+	MenuId     int                             `description:"目录ID"`
+	MenuName   string                          `description:"目录名称"`
+	ParentId   int                             `description:"父级ID"`
+	Level      int                             `description:"目录层级"`
+	Sort       int                             `description:"排序"`
+	CreateTime string                          `description:"创建时间"`
+	Children   []*SpeechRecognitionTagMenuItem `description:"子目录"`
+}
+
+// SpeechRecognitionTagMenuNodeItem 标签目录树节点
+type SpeechRecognitionTagMenuNodeItem struct {
+	UniqueCode string                              `description:"唯一编码"`
+	NodeType   int                                 `description:"节点类型:0-目录;1-标签"`
+	MenuId     int                                 `description:"目录ID"`
+	MenuName   string                              `description:"目录名称"`
+	TagId      int                                 `description:"标签ID"`
+	TagName    string                              `description:"标签名称"`
+	ParentId   int                                 `description:"父级ID"`
+	Level      int                                 `description:"目录层级"`
+	Sort       int                                 `description:"排序"`
+	CreateTime string                              `description:"创建时间"`
+	Children   []*SpeechRecognitionTagMenuNodeItem `description:"子节点"`
+}
+
+// SpeechRecognitionTagMenuAddReq 新增标签目录
+type SpeechRecognitionTagMenuAddReq struct {
+	ParentId int    `description:"父级ID"`
+	MenuName string `description:"目录名称"`
+}
+
+// SpeechRecognitionTagMenuEditReq 编辑标签目录
+type SpeechRecognitionTagMenuEditReq struct {
+	MenuId   int    `description:"目录ID"`
+	MenuName string `description:"目录名称"`
+}
+
+// SpeechRecognitionTagMenuRemoveReq 删除标签目录
+type SpeechRecognitionTagMenuRemoveReq struct {
+	MenuId int `description:"目录ID"`
+}
+
+// SpeechRecognitionTagMenuMoveReq 移动目录请求体
+type SpeechRecognitionTagMenuMoveReq struct {
+	MenuId       int `description:"目录ID"`
+	ParentMenuId int `description:"父级目录ID"`
+	PrevMenuId   int `description:"上一个兄弟节点目录ID"`
+	NextMenuId   int `description:"下一个兄弟节点目录ID"`
+	TagId        int `description:"标签ID, 大于0则表示移动标签"`
+	PrevTagId    int `description:"上一个标签ID"`
+	NextTagId    int `description:"下一个标签ID"`
+}
+
+// UpdateSortByParentId 根据父级ID更新排序
+func (m *SpeechRecognitionTagMenu) UpdateSortByParentId(parentId, menuId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`UPDATE %s SET %s = %s WHERE %s = ? AND %s > ?`, m.TableName(), SpeechRecognitionTagMenuCols.Sort, updateSort, SpeechRecognitionTagMenuCols.ParentId, SpeechRecognitionTagMenuCols.Sort)
+	if menuId > 0 {
+		sql += fmt.Sprintf(` OR (%s > %d AND %s = %d)`, SpeechRecognitionTagMenuCols.SpeechRecognitionTagMenuId, menuId, SpeechRecognitionTagMenuCols.Sort, nowSort)
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetMaxSortByParentId 获取父级分类下最大Sort
+func (m *SpeechRecognitionTagMenu) GetMaxSortByParentId(parentId int) (sort int, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT MAX(sort) AS sort FROM %s WHERE %s = ?`, m.TableName(), SpeechRecognitionTagMenuCols.ParentId)
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+// GetFirstByParentId 获取父级目录下排序第一的目录
+func (m *SpeechRecognitionTagMenu) GetFirstByParentId(parentId int) (item *SpeechRecognitionTagMenu, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? ORDER BY %s ASC, %s ASC LIMIT 1`, m.TableName(), SpeechRecognitionTagMenuCols.ParentId, SpeechRecognitionTagMenuCols.Sort, SpeechRecognitionTagMenuCols.SpeechRecognitionTagMenuId)
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+// UpdateChildByParentMenuId 通过父级目录ID更新子目录
+func (m *SpeechRecognitionTagMenu) UpdateChildByParentMenuId(menuIds []int, rootId int, levelStep int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	var pars []interface{}
+	pars = append(pars, rootId, levelStep)
+	pars = append(pars, menuIds)
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ?, %s = %s + ? WHERE %s IN (%s)`, m.TableName(), SpeechRecognitionTagMenuCols.RootId, SpeechRecognitionTagMenuCols.Level, SpeechRecognitionTagMenuCols.Level, SpeechRecognitionTagMenuCols.SpeechRecognitionTagMenuId, utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}

+ 398 - 2
routers/commentsRouter.go

@@ -4615,6 +4615,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"],
+        beego.ControllerComments{
+            Method: "SetEnabled",
+            Router: `/enabled/set`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"],
         beego.ControllerComments{
             Method: "List",
@@ -4624,6 +4633,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"],
         beego.ControllerComments{
             Method: "ParentList",
@@ -4907,7 +4925,7 @@ func init() {
         beego.ControllerComments{
             Method: "AddClassify",
             Router: `/classify/add`,
-            AllowHTTPMethods: []string{"get"},
+            AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
@@ -4925,7 +4943,16 @@ func init() {
         beego.ControllerComments{
             Method: "EditClassify",
             Router: `/classify/edit`,
-            AllowHTTPMethods: []string{"get"},
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "SetEnabled",
+            Router: `/classify/enabled/set`,
+            AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
@@ -4948,6 +4975,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "MoveClassify",
+            Router: `/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"],
         beego.ControllerComments{
             Method: "ClassifyIdDetail",
@@ -6028,6 +6064,285 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionCommonController"],
+        beego.ControllerComments{
+            Method: "RecTaskCallback",
+            Router: `/rec_task/callback`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "Convert",
+            Router: `/convert`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "CheckFileName",
+            Router: `/convert/check_name`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "ConvertList",
+            Router: `/convert_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "Export",
+            Router: `/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "RemoveFile",
+            Router: `/remove_file`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "Rename",
+            Router: `/rename`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "Save",
+            Router: `/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionController"],
+        beego.ControllerComments{
+            Method: "SaveTag",
+            Router: `/save_tag`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/menu/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/menu/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/menu/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/menu/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/menu/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"],
+        beego.ControllerComments{
+            Method: "RemoveCheck",
+            Router: `/menu/remove_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionMenuController"],
+        beego.ControllerComments{
+            Method: "Tree",
+            Router: `/menu/tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/tag/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/tag/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/tag/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/tag/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagController"],
+        beego.ControllerComments{
+            Method: "RemoveCheck",
+            Router: `/tag/remove_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/tag/menu/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/tag/menu/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/tag/menu/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/tag/menu/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/tag/menu/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"],
+        beego.ControllerComments{
+            Method: "RemoveCheck",
+            Router: `/tag/menu/remove_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/speech_recognition:SpeechRecognitionTagMenuController"],
+        beego.ControllerComments{
+            Method: "Tree",
+            Router: `/tag/menu/tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
         beego.ControllerComments{
             Method: "GetClassifyName",
@@ -6082,6 +6397,51 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "SetEnabled",
+            Router: `/enabled/set`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"],
         beego.ControllerComments{
             Method: "Add",
@@ -6118,6 +6478,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"],
+        beego.ControllerComments{
+            Method: "SetEnabled",
+            Router: `/enabled/set`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"],
         beego.ControllerComments{
             Method: "FindByIdClassify",
@@ -6136,6 +6505,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"],
         beego.ControllerComments{
             Method: "ParentClassify",
@@ -7036,6 +7414,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportChapterTypeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportChapterTypeController"],
+        beego.ControllerComments{
+            Method: "SetEnabled",
+            Router: `/chapter_type/enabled/set`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportChapterTypeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportChapterTypeController"],
         beego.ControllerComments{
             Method: "List",
@@ -7045,6 +7432,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportChapterTypeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportChapterTypeController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/chapter_type/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportChapterTypeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportChapterTypeController"],
         beego.ControllerComments{
             Method: "PermissionList",

+ 15 - 0
routers/router.go

@@ -27,6 +27,7 @@ import (
 	"eta/eta_api/controllers/sandbox"
 	"eta/eta_api/controllers/semantic_analysis"
 	"eta/eta_api/controllers/smart_report"
+	"eta/eta_api/controllers/speech_recognition"
 	"eta/eta_api/controllers/trade_analysis"
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web/filter/cors"
@@ -344,6 +345,20 @@ func init() {
 				&data_source.DataSourceController{},
 			),
 		),
+		web.NSNamespace("/permission",
+			web.NSInclude(
+				&controllers.ChartPermissionController{},
+			),
+		),
+		web.NSNamespace("/speech_recognition",
+			web.NSInclude(
+				&speech_recognition.SpeechRecognitionCommonController{},
+				&speech_recognition.SpeechRecognitionController{},
+				&speech_recognition.SpeechRecognitionMenuController{},
+				&speech_recognition.SpeechRecognitionTagController{},
+				&speech_recognition.SpeechRecognitionTagMenuController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 478 - 0
services/chart_permission.go

@@ -0,0 +1,478 @@
+package services
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/models/company"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// 获取权限列表
+func GetChartPermissionList(cond string, pars []interface{}) (permissionList []*models.ChartPermission, err error) {
+	ob := new(models.ChartPermission)
+	permissionList, err = ob.GetItemsByCondition(cond, pars)
+	if err != nil {
+		err = fmt.Errorf("获取权限列表失败, Err: %s", err.Error())
+		return
+	}
+
+	return
+}
+
+// 新增权限
+func AddChartPermission(req models.PermissionAddReq) (err error, errMsg string) {
+	// 重名校验
+	ob := new(models.ChartPermission)
+	existCond := ` AND permission_name = ? AND parent_id = ?`
+	existPars := make([]interface{}, 0)
+	existPars = append(existPars, req.PermissionName, req.ParentId)
+	exist, e := ob.GetItemByCondition(existCond, existPars)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		errMsg = "操作失败"
+		err = fmt.Errorf("获取重名品种权限失败, Err: " + e.Error())
+		return
+	}
+	if exist != nil && exist.ChartPermissionId > 0 {
+		errMsg = "品种名称已存在"
+		err = fmt.Errorf("品种名称已存在")
+		return
+	}
+
+	// 获取最大的排序值
+	maxSort, e := ob.GetMaxSort()
+	if e != nil {
+		errMsg = "查询品种排序失败"
+		err = fmt.Errorf("查询品种排序失败, Err: " + e.Error())
+		return
+	}
+	parentPermissionName := ""
+	if req.ParentId > 0 {
+		//查询父级是否存在
+		parent, e := ob.GetItemById(req.ParentId)
+		if e != nil {
+			errMsg = "查询父级品种失败"
+			err = fmt.Errorf("查询父级品种失败, Err: " + e.Error())
+			return
+		}
+		if parent.ChartPermissionId == 0 {
+			errMsg = "请选择正确的父级品种"
+			err = fmt.Errorf("请选择正确的父级品种")
+			return
+		}
+		if parent.ParentId != 0 {
+			errMsg = "只能选择一级品种作为父级品种"
+			err = fmt.Errorf("只能选择一级品种作为父级品种")
+			return
+		}
+		parentPermissionName = parent.PermissionName
+	} else {
+		parentPermissionName = req.PermissionName
+	}
+	// 新增
+	ob.ChartPermissionName = req.PermissionName
+	ob.PermissionName = req.PermissionName
+	ob.Remark = req.PermissionName
+	ob.ParentId = req.ParentId
+	ob.ProductId = 1
+	ob.IsPublic = req.IsPublic
+	ob.Enabled = req.Enabled
+	ob.Sort = maxSort + 1
+
+	ob.CreatedTime = time.Now()
+	ob.LastUpdatedTime = time.Now()
+	if ob.ProductId == 1 {
+		ob.ProductName = "ficc"
+	} else {
+		ob.ProductName = "权益"
+	}
+	ob.ProductPermissionName = ob.ProductName + ob.PermissionName
+	ob.ClassifyName = parentPermissionName
+	if e = ob.Create(); e != nil {
+		errMsg = "操作失败"
+		err = fmt.Errorf("新增品种权限失败, Err: " + e.Error())
+		return
+	}
+	if req.ParentId > 0 && req.Enabled == 1 {
+		//  更新父级品种的启用禁用
+		err = ob.SetEnabledByChartPermissionId(req.ParentId, req.Enabled)
+		if err != nil {
+			errMsg = "更新品种启用禁用状态失败"
+			err = fmt.Errorf("更新品种启用禁用状态失败, Err: " + err.Error())
+			return
+		}
+	}
+	// 同步本次变更的数据
+	go func() {
+		_, _ = ChartFiccPermissionSync()
+	}()
+	return
+}
+
+func EditChartPermission(req models.PermissionEditReq) (err error, errMsg string) {
+	//查询是否存在品种
+	ob := new(models.ChartPermission)
+	item, e := ob.GetItemById(req.PermissionId)
+	if e != nil {
+		errMsg = "查询品种信息失败"
+		err = fmt.Errorf("查询品种信息失败, Err: " + e.Error())
+		return
+	}
+	if item.ChartPermissionId == 0 {
+		errMsg = "请选择正确的品种"
+		err = fmt.Errorf("请选择正确的品种")
+		return
+	}
+	// 查询品种名称是否修改
+	if item.PermissionName != req.PermissionName {
+		existCond := ` AND permission_name = ? AND parent_id = ? AND chart_permission_id !=?`
+		existPars := make([]interface{}, 0)
+		existPars = append(existPars, req.PermissionName, item.ParentId, req.PermissionId)
+		exist, e := ob.GetItemByCondition(existCond, existPars)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			errMsg = "操作失败"
+			err = fmt.Errorf("获取重名品种权限失败, Err: " + e.Error())
+			return
+		}
+		if exist != nil && exist.ChartPermissionId > 0 {
+			errMsg = "品种名称已存在"
+			err = fmt.Errorf("品种名称已存在")
+			return
+		}
+	}
+
+	//判断父级品种是否存在
+	parentPermissionName := ""
+	if req.ParentId > 0 {
+		//查询父级是否存在
+		parent, e := ob.GetItemById(req.ParentId)
+		if e != nil {
+			errMsg = "查询父级品种失败"
+			err = fmt.Errorf("查询父级品种失败, Err: " + e.Error())
+			return
+		}
+		if parent.ChartPermissionId == 0 {
+			errMsg = "请选择正确的父级品种"
+			err = fmt.Errorf("请选择正确的父级品种")
+			return
+		}
+		if parent.ParentId != 0 {
+			errMsg = "只能选择一级品种作为父级品种"
+			err = fmt.Errorf("只能选择一级品种作为父级品种")
+			return
+		}
+		parentPermissionName = parent.PermissionName
+	} else {
+		parentPermissionName = req.PermissionName
+	}
+	updateCol := []string{"PermissionName", "IsPublic", "ParentId", "ClassifyName", "LastUpdatedTime", "ProductPermissionName"}
+	if item.ProductId == 1 {
+		item.Remark = req.PermissionName //remark 在权益品种中用来区分主客观
+		updateCol = append(updateCol, "Remark")
+	}
+	item.PermissionName = req.PermissionName
+	item.IsPublic = req.IsPublic
+	item.ParentId = req.ParentId
+	item.ClassifyName = parentPermissionName
+	item.ProductPermissionName = item.ProductName + item.PermissionName
+	item.LastUpdatedTime = time.Now()
+	err = item.Update(updateCol)
+	if err != nil {
+		errMsg = "更新失败"
+		err = fmt.Errorf("更新品种失败, Err: " + err.Error())
+		return
+	}
+
+	//判断是否是一级品种
+	if item.ParentId == 0 {
+		// 判断是否设置公有的二级品种
+		if len(req.PublicPermissionIds) > 0 {
+			// 更新二级品种的公有权限
+			err = ob.SetIsPublic(req.PublicPermissionIds, item.ChartPermissionId, 1)
+			if err != nil {
+				errMsg = "更新品种公有权限失败"
+				err = fmt.Errorf("更新品种公有权限失败, Err: " + err.Error())
+				return
+			}
+		}
+		// 更新二级的启动禁用
+		err = ob.UpdateClassifyNameByParentId(item.ChartPermissionId, req.PermissionName)
+		if err != nil {
+			errMsg = "更新品种启用禁用状态失败"
+			err = fmt.Errorf("更新品种启用禁用状态失败, Err: " + err.Error())
+			return
+		}
+	} else if item.ParentId > 0 {
+		if item.Enabled == 1 {
+			//  更新父级品种的启用禁用
+			err = ob.SetEnabledByChartPermissionId(item.ParentId, item.Enabled)
+			if err != nil {
+				errMsg = "更新品种启用禁用状态失败"
+				err = fmt.Errorf("更新品种启用禁用状态失败, Err: " + err.Error())
+				return
+			}
+		}
+	}
+	// 同步本次变更的数据
+	go func() {
+		_, _ = ChartFiccPermissionSync()
+	}()
+	return
+}
+
+// SetEnabledChartPermission  设置启用禁用
+func SetEnabledChartPermission(req models.PermissionEnabledReq) (err error, errMsg string) {
+	//查询是否存在品种
+	ob := new(models.ChartPermission)
+	item, e := ob.GetItemById(req.PermissionId)
+	if e != nil {
+		errMsg = "查询品种信息失败"
+		err = fmt.Errorf("查询品种信息失败, Err: " + e.Error())
+		return
+	}
+	if item.ChartPermissionId == 0 {
+		errMsg = "请选择正确的品种"
+		err = fmt.Errorf("请选择正确的品种")
+		return
+	}
+	// 查询品种名称是否修改
+	item.Enabled = req.Enabled
+	item.LastUpdatedTime = time.Now()
+	err = item.Update([]string{"Enabled", "LastUpdatedTime"})
+	if err != nil {
+		errMsg = "更新失败"
+		err = fmt.Errorf("更新品种失败, Err: " + err.Error())
+		return
+	}
+
+	//判断是否是一级品种
+	if item.ParentId == 0 {
+		// 判断是否设置公有的二级品种
+		// 更新二级的启动禁用
+		err = ob.SetEnabledByParentId(item.ChartPermissionId, req.Enabled)
+		if err != nil {
+			errMsg = "更新品种启用禁用状态失败"
+			err = fmt.Errorf("更新品种启用禁用状态失败, Err: " + err.Error())
+			return
+		}
+
+		//
+	} else if item.ParentId > 0 {
+		if req.Enabled == 1 {
+			//  更新父级品种的启用禁用
+			err = ob.SetEnabledByChartPermissionId(item.ParentId, req.Enabled)
+			if err != nil {
+				errMsg = "更新品种启用禁用状态失败"
+				err = fmt.Errorf("更新品种启用禁用状态失败, Err: " + err.Error())
+				return
+			}
+		}
+	}
+	// 同步本次变更的数据
+	go func() {
+		_, _ = ChartFiccPermissionSync()
+	}()
+	return
+}
+
+// MoveChartPermission 移动品种
+func MoveChartPermission(req models.PermissionMoveReq) (err error, errMsg string) {
+	ob := new(models.ChartPermission)
+	chartPermissionId := req.PermissionId
+	prevChartPermissionId := req.PrevPermissionId
+	nextChartPermissionId := req.NextPermissionId
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		permissionInfo *models.ChartPermission
+		prevPermission *models.ChartPermission
+		nextPermission *models.ChartPermission
+
+		prevSort int
+		nextSort int
+	)
+
+	// 移动对象为分类, 判断权限
+	permissionInfo, err = ob.GetItemById(chartPermissionId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "当前品种不存在"
+			err = fmt.Errorf("获取品种信息失败,Err:" + err.Error())
+			return
+		}
+		errMsg = "移动失败"
+		err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+		return
+	} else if permissionInfo.ChartPermissionId == 0 {
+		errMsg = "当前品种不存在"
+		err = fmt.Errorf("获取品种信息失败,Err:" + err.Error())
+		return
+	}
+
+	parentChartPermissionId := permissionInfo.ParentId
+	productId := permissionInfo.ProductId
+	if prevChartPermissionId > 0 {
+		prevPermission, err = ob.GetItemById(prevChartPermissionId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevPermission.Sort
+	}
+
+	if nextChartPermissionId > 0 {
+		//下一个兄弟节点
+		nextPermission, err = ob.GetItemById(nextChartPermissionId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextPermission.Sort
+	}
+
+	err, errMsg = moveChartPermission(permissionInfo, prevPermission, nextPermission, parentChartPermissionId, prevSort, nextSort, productId)
+	return
+}
+
+// moveChartPermission 移动指标分类
+func moveChartPermission(permissionInfo, prevPermission, nextPermission *models.ChartPermission, parentId, prevSort, nextSort, productId int) (err error, errMsg string) {
+	ob := new(models.ChartPermission)
+	updateCol := make([]string, 0)
+
+	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	if permissionInfo.ParentId != parentId {
+		errMsg = "移动失败"
+		err = fmt.Errorf("不支持目录层级变更")
+		return
+	}
+
+	if prevSort > 0 {
+		//如果是移动在两个兄弟节点之间
+		if nextSort > 0 {
+			//下一个兄弟节点
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevSort == nextSort || prevSort == permissionInfo.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+
+				//变更分类
+				if prevPermission != nil {
+					_ = models.UpdateChartPermissionSortByParentId(parentId, prevPermission.ChartPermissionId, prevPermission.Sort, updateSortStr, productId)
+				} else {
+					_ = models.UpdateChartPermissionSortByParentId(parentId, 0, prevSort, updateSortStr, productId)
+				}
+
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextSort-prevSort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+
+					//变更分类
+					if prevPermission != nil {
+						_ = models.UpdateChartPermissionSortByParentId(parentId, prevPermission.ChartPermissionId, prevSort, updateSortStr, productId)
+					} else {
+						_ = models.UpdateChartPermissionSortByParentId(parentId, 0, prevSort, updateSortStr, productId)
+					}
+
+				}
+			}
+		}
+
+		permissionInfo.Sort = prevSort + 1
+		permissionInfo.LastUpdatedTime = time.Now()
+		updateCol = append(updateCol, "Sort", "LastUpdatedTime")
+	} else if prevPermission == nil && nextPermission == nil && parentId > 0 {
+		//处理只拖动到目录里,默认放到目录底部的情况
+		var maxSort int
+		maxSort, err = ob.GetMaxSortByParentId(parentId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("查询组内排序信息失败,Err:" + err.Error())
+			return
+		}
+		permissionInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+		permissionInfo.LastUpdatedTime = time.Now()
+		updateCol = append(updateCol, "Sort", "LastUpdatedTime")
+	} else {
+		// 拖动到父级分类的第一位
+		firstPermission, tmpErr := ob.GetFirstChartPermissionByParentId(parentId)
+		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstPermission != nil && firstPermission.ChartPermissionId != 0 && firstPermission.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = models.UpdateChartPermissionSortByParentId(parentId, firstPermission.ChartPermissionId-1, 0, updateSortStr, productId)
+		}
+
+		permissionInfo.Sort = 0 //那就是排在第一位
+		permissionInfo.LastUpdatedTime = time.Now()
+		updateCol = append(updateCol, "Sort", "LastUpdatedTime")
+	}
+
+	//更新
+	if len(updateCol) > 0 {
+		err = permissionInfo.Update(updateCol)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("修改失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	// 同步本次变更的数据
+	go func() {
+		_, _ = ChartFiccPermissionSync()
+	}()
+	return
+}
+
+// GetChartPermissionEnabledList 查询已启用的FICC权限列表
+func GetChartPermissionEnabledList() (list []*company.PermissionSetList, err error) {
+	dataList, err := GetChartPermissionList(" AND enabled = 1", []interface{}{})
+	if err != nil {
+		return
+	}
+	//enabledList := make([]*models.ChartPermission, 0)
+	parentList := make([]*models.ChartPermission, 0)
+	enabledMap := make(map[int][]*company.PermissionSetItem)
+	listMap := make(map[int]*models.ChartPermission)
+	for _, v := range dataList {
+		listMap[v.ChartPermissionId] = v
+		if v.ParentId == 0 {
+			parentList = append(parentList, v)
+		}
+		if v.Enabled == 1 && v.ParentId > 0 && v.PermissionType == 0 {
+			//enabledList = append(enabledList, v)
+			item := &company.PermissionSetItem{
+				ChartPermissionId: v.ChartPermissionId,
+				PermissionName:    v.PermissionName,
+				PermissionType:    v.PermissionType,
+			}
+
+			enabledMap[v.ParentId] = append(enabledMap[v.ParentId], item)
+		}
+	}
+	for _, v := range parentList {
+		items, ok := enabledMap[v.ChartPermissionId]
+		if !ok {
+			continue
+		}
+		checkList := make([]int, 0)
+		p := new(company.PermissionSetList)
+		p.PermissionName = v.PermissionName
+		p.Child = items
+		if v.PermissionName == "宏观经济" {
+			checkList = append(checkList, 1)
+		}
+		p.CheckList = checkList
+		list = append(list, p)
+	}
+	return
+}

+ 206 - 0
services/chart_permission_sync.go

@@ -0,0 +1,206 @@
+package services
+
+import (
+	"encoding/json"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/utils"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+)
+
+type ChartPermissionResp struct {
+	Code   int    `json:"code" description:"状态码"`
+	Msg    string `json:"msg" description:"提示信息"`
+	ErrMsg string `json:"-" description:"错误信息,不用返回给前端,只是做日志记录"`
+}
+
+func crmEtaPost(url string, pars interface{}) (respBody []byte, err error) {
+	params, e := json.Marshal(pars)
+	if e != nil {
+		err = fmt.Errorf("data json marshal err: %s", e.Error())
+		return
+	}
+
+	body := ioutil.NopCloser(strings.NewReader(string(params)))
+	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("Authorization", utils.CrmEtaAuthorization)
+	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 utils.RunMode == "release" {
+		str := string(b)
+		str = strings.Trim(str, `"`)
+		b = utils.DesBase64Decrypt([]byte(str), utils.CrmEtaServerDes3Key)
+	}
+	respBody = b
+	return
+}
+
+func ChartFiccPermissionSync() (err error, errMsg string) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info("同步品种权限数据失败, Err: " + err.Error() + errMsg)
+			alarm_msg.SendAlarmMsg("同步品种权限数据失败,Err:"+err.Error(), 3)
+		}
+	}()
+	if utils.CrmEtaServerUrl == "" {
+		return
+	}
+	url := fmt.Sprint(utils.CrmEtaServerUrl, "/api/crm/chart_permission/sync")
+	pars := make(map[string]interface{})
+	b, err := crmEtaPost(url, pars)
+	if err != nil {
+		errMsg = "同步品种失败"
+		err = fmt.Errorf("url:%s err: %s", url, err.Error())
+		return
+	}
+	//result := new(models.ResultData)
+	result := new(ChartPermissionResp)
+	if e := json.Unmarshal(b, &result); e != nil {
+		errMsg = "同步品种失败"
+		err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+		return
+	}
+	utils.FileLog.Info("%s", string(b))
+	if result.Code != 200 {
+		err = fmt.Errorf("result: %s, err: %s", string(b), result.ErrMsg)
+		errMsg = result.Msg
+		return
+	}
+	return
+}
+
+type EditClassifyPermissionReq struct {
+	Keyword string
+}
+
+// EditClassifyChartPermissionSync 设置报告分类权限
+func EditClassifyChartPermissionSync(keyword string) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info("同步设置报告分类权限失败, Err: " + err.Error())
+			alarm_msg.SendAlarmMsg("同步设置报告分类权限失败,Err:"+err.Error(), 3)
+		}
+	}()
+	if utils.CrmEtaServerUrl == "" {
+		return
+	}
+	req := &EditClassifyPermissionReq{Keyword: keyword}
+	url := fmt.Sprint(utils.CrmEtaServerUrl, "/api/crm/chart_permission/classify/sync")
+	b, err := crmEtaPost(url, req)
+	if err != nil {
+		err = fmt.Errorf("url:%s err: %s", url, err.Error())
+		return
+	}
+	//result := new(models.ResultData)
+	result := new(ChartPermissionResp)
+	if e := json.Unmarshal(b, &result); e != nil {
+		err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+		return
+	}
+	utils.FileLog.Info("%s", string(b))
+	if result.Code != 200 {
+		err = fmt.Errorf("result: %s", string(b))
+		return
+	}
+	return
+}
+
+type EditReportPermissionSyncReq struct {
+	ReportId           int64  `description:"报告id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+}
+
+// EditReportPermissionSync 设置报告权限
+func EditReportPermissionSync(reportId int64, classifyNameSecond string) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info("同步设置报告权限失败, Err: " + err.Error())
+			alarm_msg.SendAlarmMsg("同步设置报告权限失败,Err:"+err.Error(), 3)
+		}
+	}()
+	if utils.CrmEtaServerUrl == "" {
+		return
+	}
+	req := &EditReportPermissionSyncReq{ReportId: reportId, ClassifyNameSecond: classifyNameSecond}
+	url := fmt.Sprint(utils.CrmEtaServerUrl, "/api/crm/chart_permission/report/sync")
+	b, err := crmEtaPost(url, req)
+	if err != nil {
+		err = fmt.Errorf("url:%s err: %s", url, err.Error())
+		return
+	}
+	//result := new(models.ResultData)
+	result := new(ChartPermissionResp)
+	if e := json.Unmarshal(b, &result); e != nil {
+		err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+		return
+	}
+	utils.FileLog.Info("%s", string(b))
+	if result.Code != 200 {
+		err = fmt.Errorf("result: %s", string(b))
+		return
+	}
+	return
+}
+
+type EditKeywordPermissionSyncReq struct {
+	NewKeyword string
+	Keyword    string
+}
+
+// EditKeywordPermissionSync 设置报告权限分类名称
+func EditKeywordPermissionSync(newKeyword, keyword string) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info("同步设置报告权限分类名称失败, Err: " + err.Error())
+			alarm_msg.SendAlarmMsg("同步设置报告权限分类名称失败,Err:"+err.Error(), 3)
+		}
+	}()
+	if utils.CrmEtaServerUrl == "" {
+		return
+	}
+	req := &EditKeywordPermissionSyncReq{NewKeyword: newKeyword, Keyword: keyword}
+	url := fmt.Sprint(utils.CrmEtaServerUrl, "/api/crm/chart_permission/keyword/sync")
+	b, err := crmEtaPost(url, req)
+	if err != nil {
+		err = fmt.Errorf("url:%s err: %s", url, err.Error())
+		return
+	}
+	//result := new(models.ResultData)
+	result := new(ChartPermissionResp)
+	if e := json.Unmarshal(b, &result); e != nil {
+		err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+		return
+	}
+	utils.FileLog.Info("%s", string(b))
+	if result.Code != 200 {
+		err = fmt.Errorf("result: %s", string(b))
+		return
+	}
+	return
+}

+ 179 - 0
services/classify.go

@@ -0,0 +1,179 @@
+package services
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// MoveReportClassify 移动分类
+func MoveReportClassify(req models.ClassifyMoveReq) (err error, errMsg string) {
+	classifyId := req.ClassifyId
+	prevClassifyId := req.PrevClassifyId
+	nextClassifyId := req.NextClassifyId
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		classifyInfo *models.Classify
+		prevClassify *models.Classify
+		nextClassify *models.Classify
+
+		prevSort int
+		nextSort int
+	)
+
+	// 移动对象为分类, 判断权限
+	classifyInfo, err = models.GetClassifyById(classifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在, 请刷新页面"
+			err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		errMsg = "移动失败"
+		err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+		return
+	} else if classifyInfo.Id == 0 {
+		errMsg = "分类不存在, 请刷新页面"
+		err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+
+	parentClassifyId := classifyInfo.ParentId
+	if prevClassifyId > 0 {
+		prevClassify, err = models.GetClassifyById(prevClassifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "上一个分类不存在, 请刷新页面"
+				err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		if prevClassify.ParentId != parentClassifyId {
+			errMsg = "禁止拖动到其他节点"
+			err = fmt.Errorf(errMsg)
+			return
+		}
+		prevSort = prevClassify.Sort
+	}
+
+	if nextClassifyId > 0 {
+		//下一个兄弟节点
+		nextClassify, err = models.GetClassifyById(nextClassifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "下一个分类不存在, 请刷新页面"
+				err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		if nextClassify.ParentId != parentClassifyId {
+			errMsg = "禁止拖动到其他节点"
+			err = fmt.Errorf(errMsg)
+			return
+		}
+		nextSort = nextClassify.Sort
+	}
+
+	err, errMsg = moveReportClassify(classifyInfo, prevClassify, nextClassify, parentClassifyId, prevSort, nextSort)
+	return
+}
+
+// moveReportClassify 移动分类
+func moveReportClassify(classifyInfo, prevClassify, nextClassify *models.Classify, parentId, prevSort, nextSort int) (err error, errMsg string) {
+	ob := new(models.Classify)
+	updateCol := make([]string, 0)
+
+	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	if classifyInfo.ParentId != parentId {
+		errMsg = "移动失败"
+		err = fmt.Errorf("不支持目录层级变更")
+		return
+	}
+
+	if prevSort > 0 {
+		//如果是移动在两个兄弟节点之间
+		if nextSort > 0 {
+			//下一个兄弟节点
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevSort == nextSort || prevSort == classifyInfo.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+
+				//变更分类
+				if prevClassify != nil {
+					_ = models.UpdateClassifySortByParentId(parentId, prevClassify.Id, prevClassify.Sort, updateSortStr)
+				} else {
+					_ = models.UpdateClassifySortByParentId(parentId, 0, prevSort, updateSortStr)
+				}
+
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextSort-prevSort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = models.UpdateClassifySortByParentId(parentId, prevClassify.Id, prevSort, updateSortStr)
+					} else {
+						_ = models.UpdateClassifySortByParentId(parentId, 0, prevSort, updateSortStr)
+					}
+
+				}
+			}
+		}
+
+		classifyInfo.Sort = prevSort + 1
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else if prevClassify == nil && nextClassify == nil && parentId > 0 {
+		//处理只拖动到目录里,默认放到目录底部的情况
+		var maxSort int
+		maxSort, err = ob.GetMaxSortByParentId(parentId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("查询组内排序信息失败,Err:" + err.Error())
+			return
+		}
+		classifyInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else {
+		// 拖动到父级分类的第一位
+		firstPermission, tmpErr := ob.GetFirstClassifyByParentId(parentId)
+		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstPermission != nil && firstPermission.Id != 0 && firstPermission.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = models.UpdateClassifySortByParentId(parentId, firstPermission.Id-1, 0, updateSortStr)
+		}
+
+		classifyInfo.Sort = 0 //那就是排在第一位
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	}
+
+	//更新
+	if len(updateCol) > 0 {
+		err = classifyInfo.UpdateClassify(updateCol)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("修改失败,Err:" + err.Error())
+			return
+		}
+	}
+	return
+}

+ 119 - 35
services/data/edb_info.go

@@ -2331,29 +2331,12 @@ func EdbInfoAdd(source, subSource, classifyId int, edbCode, edbName, frequency,
 	if source == utils.DATA_SOURCE_STOCK_PLANT {
 		edbType = 2 //计算指标
 	}
-	// todo 如果缓存中的终端信息丢了是否需要调整 从缓存中获取
-	serverUrl := ``
-	if edbInfo.Source == utils.DATA_SOURCE_WIND {
-		windCacheKey := utils.CACHE_WIND_URL + ":" + edbCode
-		serverUrl, _ = utils.Rc.RedisString(windCacheKey)
-		if serverUrl == `` {
-			if len(utils.Hz_Data_WIND_Url_List) >= 1 {
-				serverUrl = utils.Hz_Data_WIND_Url_List[len(utils.Hz_Data_WIND_Url_List)-1] //默认是最后一个服务器地址
-			}
-		}
-	}
-	// 获取终端信息
-	terminalCodeCacheKey := utils.CACHE_EDB_TERMINAL_CODE_URL + edbCode
-	terminalCode, tE := utils.Rc.RedisString(terminalCodeCacheKey)
-	if tE != nil {
-		utils.FileLog.Info(fmt.Sprintf("从缓存中获取数据源终端信息失败, Err: %s", tE))
-	}
-	var sourceIndexName string
-	if terminalCode == "" {
-		terminalCode, sourceIndexName, tE = GetTerminalFromBaseIndex(source, edbCode)
-		if tE != nil {
-			utils.FileLog.Info(fmt.Sprintf("获取数据源终端信息失败, Err: %s", tE))
-		}
+	// 从缓存中获取
+	terminalCode, serverUrl, sourceIndexName, e := GetEdbTerminalCodeBySource(edbInfo.Source, edbInfo.EdbCode, edbInfo.StockCode)
+	if e != nil {
+		errMsg = "获取可以使用的终端地址失败"
+		err = errors.New("获取可以使用的终端地址失败,Err:" + e.Error())
+		return
 	}
 	//获取该层级下最大的排序数
 	maxSort, err := GetEdbClassifyMaxSort(classifyId, 0)
@@ -3003,17 +2986,7 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 	if source == utils.DATA_SOURCE_STOCK_PLANT {
 		edbType = 2 //计算指标
 	}
-	//从缓存中获取
-	serverUrl := ``
-	if edbInfo.Source == utils.DATA_SOURCE_WIND {
-		windCacheKey := utils.CACHE_WIND_URL + ":" + item.EdbCode
-		serverUrl, _ = utils.Rc.RedisString(windCacheKey)
-		if serverUrl == `` {
-			if len(utils.Hz_Data_WIND_Url_List) >= 1 {
-				serverUrl = utils.Hz_Data_WIND_Url_List[len(utils.Hz_Data_WIND_Url_List)-1] //默认是最后一个服务器地址
-			}
-		}
-	}
+
 	//获取该层级下最大的排序数
 	maxSort, err := GetEdbClassifyMaxSort(item.ClassifyId, 0)
 	if err != nil {
@@ -3032,7 +3005,7 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 	edbInfo.SysUserRealName = item.SysUserRealName
 	edbInfo.CreateTime = time.Now()
 	edbInfo.ModifyTime = time.Now()
-	edbInfo.ServerUrl = serverUrl
+	edbInfo.ServerUrl = item.ServerUrl
 	edbInfo.Sort = maxSort + 1
 	edbInfo.DataDateType = `交易日`
 	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
@@ -3042,6 +3015,7 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 	edbInfo.SubSourceName = "日期序列"
 	edbInfo.IndicatorCode = item.IndicatorCode
 	edbInfo.StockCode = item.StockCode
+	edbInfo.TerminalCode = item.TerminalCode
 	edbInfoId, err := data_manage.AddEdbInfo(edbInfo)
 	if err != nil {
 		errMsg = "保存失败"
@@ -3257,3 +3231,113 @@ func EdbInfoSmmApiAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo,
 	AddOrEditEdbInfoToEs(int(edbInfoId))
 	return
 }
+
+// GetEdbTerminal 获取终端信息
+func GetEdbTerminal(source int, oldTerminalCode string) (edbTerminal *data_manage.EdbTerminal, err error) {
+	if source == utils.DATA_SOURCE_WIND && oldTerminalCode == "" {
+		tmpConfig := new(data_manage.EdbConfig)
+		tmpConfig, err = data_manage.GetConfigDetailByCode("wind_terminal_code")
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			err = errors.New("获取配置的wind地址失败,err:" + err.Error())
+			return
+		}
+		err = nil
+		if tmpConfig != nil {
+			oldTerminalCode = tmpConfig.ConfigValue
+			//return
+		}
+	}
+	if oldTerminalCode != "" {
+		edbTerminal, err = data_manage.GetEdbTerminalByCode(oldTerminalCode)
+		if err != nil {
+			return
+		}
+		return
+	}
+
+	// 配置中没有的话,那么就从分组获取,
+	list, err := data_manage.GetEdbCountGroupByTerminal(source)
+	if err != nil {
+		return
+	}
+	//windUrlNum := 0
+
+	//获取对应的配置url
+	terminalNumMap := make(map[string]int)
+	for _, v := range list {
+		terminalNumMap[v.TerminalCode] = v.Total
+	}
+
+	terminalList, err := data_manage.GetEdbTerminalListBySource(source)
+	if err != nil {
+		return
+	}
+	if len(terminalList) == 0 {
+		err = errors.New("终端地址未配置")
+		return
+	}
+	num := 0
+	for _, v := range terminalList {
+		tmpNum := terminalNumMap[v.TerminalCode]
+		if edbTerminal == nil {
+			edbTerminal = v
+			num = tmpNum
+		} else if tmpNum < num {
+			edbTerminal = v
+			num = tmpNum
+		}
+	}
+	if edbTerminal == nil {
+		err = errors.New("获取配置的终端地址失败")
+		return
+	}
+	/*if edbTerminal != nil {
+		windUrl = edbTerminal.ServerUrl
+		terminalCode = edbTerminal.TerminalCode
+	}*/
+	return
+}
+
+func GetEdbTerminalCodeBySource(source int, edbCode, stockCode string) (terminalCode, serverUrl, sourceIndexName string, err error) {
+	var e error
+	if stockCode != "" {
+		terminalCodeCacheKey := utils.CACHE_EDB_TERMINAL_CODE_URL + stockCode
+		terminalCode, e = utils.Rc.RedisString(terminalCodeCacheKey)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("从缓存中获取数据源终端信息失败, Err: %s", e))
+		}
+
+		terminalInfo, e := GetEdbTerminal(source, terminalCode)
+		if e != nil {
+			err = errors.New("获取可以使用的终端失败,Err:" + e.Error())
+			return
+		}
+		serverUrl = terminalInfo.ServerUrl
+		terminalCode = terminalInfo.TerminalCode
+		return
+	}
+	// 获取终端信息
+	terminalCodeCacheKey := utils.CACHE_EDB_TERMINAL_CODE_URL + edbCode
+	terminalCode, tE := utils.Rc.RedisString(terminalCodeCacheKey)
+	if tE != nil {
+		utils.FileLog.Info(fmt.Sprintf("从缓存中获取数据源终端信息失败, Err: %s", tE))
+	}
+	var terminalCodeOrigin string
+	terminalCodeOrigin, sourceIndexName, tE = GetTerminalFromBaseIndex(source, edbCode)
+	if tE != nil {
+		utils.FileLog.Info(fmt.Sprintf("获取数据源终端信息失败, Err: %s", tE))
+	}
+	if terminalCodeOrigin != "" {
+		terminalCode = terminalCodeOrigin
+	}
+	if source == utils.DATA_SOURCE_WIND || source == utils.DATA_SOURCE_THS {
+		terminalInfo, e := GetEdbTerminal(source, terminalCode)
+		if e != nil {
+			err = errors.New("获取可以使用的终端失败,Err:" + e.Error())
+			return
+		}
+		serverUrl = terminalInfo.ServerUrl
+		terminalCode = terminalInfo.TerminalCode
+	}
+	return
+}

+ 169 - 0
services/english_classify.go

@@ -0,0 +1,169 @@
+package services
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// MoveEnglishReportClassify 移动分类
+func MoveEnglishReportClassify(req models.EnglishClassifyMoveReq) (err error, errMsg string) {
+	classifyId := req.ClassifyId
+	prevClassifyId := req.PrevClassifyId
+	nextClassifyId := req.NextClassifyId
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		classifyInfo *models.EnglishClassify
+		prevClassify *models.EnglishClassify
+		nextClassify *models.EnglishClassify
+
+		prevSort int
+		nextSort int
+	)
+
+	// 移动对象为分类, 判断权限
+	classifyInfo, err = models.GetEnglishReportClassifyById(classifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在, 请刷新页面"
+			err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		errMsg = "移动失败"
+		err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+		return
+	} else if classifyInfo.Id == 0 {
+		errMsg = "分类不存在, 请刷新页面"
+		err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+
+	parentClassifyId := classifyInfo.ParentId
+	if prevClassifyId > 0 {
+		prevClassify, err = models.GetEnglishReportClassifyById(prevClassifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "上一个分类不存在, 请刷新页面"
+				err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevClassify.Sort
+	}
+
+	if nextClassifyId > 0 {
+		//下一个兄弟节点
+		nextClassify, err = models.GetEnglishReportClassifyById(nextClassifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "下一个分类不存在, 请刷新页面"
+				err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextClassify.Sort
+	}
+
+	err, errMsg = moveEnglishReportClassify(classifyInfo, prevClassify, nextClassify, parentClassifyId, prevSort, nextSort)
+	return
+}
+
+// moveEnglishReportClassify 移动分类
+func moveEnglishReportClassify(classifyInfo, prevClassify, nextClassify *models.EnglishClassify, parentId, prevSort, nextSort int) (err error, errMsg string) {
+	ob := new(models.EnglishClassify)
+	updateCol := make([]string, 0)
+
+	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	if classifyInfo.ParentId != parentId {
+		errMsg = "移动失败"
+		err = fmt.Errorf("不支持目录层级变更")
+		return
+	}
+
+	if prevSort > 0 {
+		//如果是移动在两个兄弟节点之间
+		if nextSort > 0 {
+			//下一个兄弟节点
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevSort == nextSort || prevSort == classifyInfo.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+
+				//变更分类
+				if prevClassify != nil {
+					_ = models.UpdateEnglishClassifySortByParentId(parentId, prevClassify.Id, prevClassify.Sort, updateSortStr)
+				} else {
+					_ = models.UpdateEnglishClassifySortByParentId(parentId, 0, prevSort, updateSortStr)
+				}
+
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextSort-prevSort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = models.UpdateEnglishClassifySortByParentId(parentId, prevClassify.Id, prevSort, updateSortStr)
+					} else {
+						_ = models.UpdateEnglishClassifySortByParentId(parentId, 0, prevSort, updateSortStr)
+					}
+
+				}
+			}
+		}
+
+		classifyInfo.Sort = prevSort + 1
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else if prevClassify == nil && nextClassify == nil && parentId > 0 {
+		//处理只拖动到目录里,默认放到目录底部的情况
+		var maxSort int
+		maxSort, err = ob.GetMaxSortByParentId(parentId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("查询组内排序信息失败,Err:" + err.Error())
+			return
+		}
+		classifyInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else {
+		// 拖动到父级分类的第一位
+		firstPermission, tmpErr := ob.GetFirstClassifyByParentId(parentId)
+		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstPermission != nil && firstPermission.Id != 0 && firstPermission.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = models.UpdateEnglishClassifySortByParentId(parentId, firstPermission.Id-1, 0, updateSortStr)
+		}
+
+		classifyInfo.Sort = 0 //那就是排在第一位
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	}
+
+	//更新
+	if len(updateCol) > 0 {
+		err = classifyInfo.UpdateEnglishClassify(updateCol)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("修改失败,Err:" + err.Error())
+			return
+		}
+	}
+	return
+}

+ 170 - 0
services/english_permission.go

@@ -0,0 +1,170 @@
+package services
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// MoveEnPermission 移动品种
+func MoveEnPermission(req models.EnPermissionMoveReq) (err error, errMsg string) {
+	ob := new(models.EnPermission)
+	permissionId := req.PermissionId
+	prevPermissionId := req.PrevPermissionId
+	nextPermissionId := req.NextPermissionId
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		permissionInfo *models.EnPermission
+		prevPermission *models.EnPermission
+		nextPermission *models.EnPermission
+
+		prevSort int
+		nextSort int
+	)
+
+	// 移动对象为分类, 判断权限
+	permissionInfo, err = ob.GetItemById(permissionId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "品种不存在, 请刷新页面"
+			err = fmt.Errorf("获取品种信息失败,Err:" + err.Error())
+			return
+		}
+		errMsg = "移动失败"
+		err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+		return
+	} else if permissionInfo.EnPermissionId == 0 {
+		errMsg = "品种不存在, 请刷新页面"
+		err = fmt.Errorf("获取品种信息失败,Err:" + err.Error())
+		return
+	}
+
+	parentPermissionId := permissionInfo.ParentId
+	if prevPermissionId > 0 {
+		prevPermission, err = ob.GetItemById(prevPermissionId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "上一个品种不存在, 请刷新页面"
+				err = fmt.Errorf("获取品种信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevPermission.Sort
+	}
+
+	if nextPermissionId > 0 {
+		//下一个兄弟节点
+		nextPermission, err = ob.GetItemById(nextPermissionId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "下一个品种不存在, 请刷新页面"
+				err = fmt.Errorf("获取品种信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextPermission.Sort
+	}
+
+	err, errMsg = moveEnPermission(permissionInfo, prevPermission, nextPermission, parentPermissionId, prevSort, nextSort)
+	return
+}
+
+// moveEnPermission 移动品种
+func moveEnPermission(permissionInfo, prevPermission, nextPermission *models.EnPermission, parentId, prevSort, nextSort int) (err error, errMsg string) {
+	ob := new(models.EnPermission)
+	updateCol := make([]string, 0)
+
+	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	if permissionInfo.ParentId != parentId {
+		errMsg = "移动失败"
+		err = fmt.Errorf("不支持目录层级变更")
+		return
+	}
+
+	if prevSort > 0 {
+		//如果是移动在两个兄弟节点之间
+		if nextSort > 0 {
+			//下一个兄弟节点
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevSort == nextSort || prevSort == permissionInfo.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+
+				//变更分类
+				if prevPermission != nil {
+					_ = models.UpdateEnPermissionSortByParentId(parentId, prevPermission.EnPermissionId, prevPermission.Sort, updateSortStr)
+				} else {
+					_ = models.UpdateEnPermissionSortByParentId(parentId, 0, prevSort, updateSortStr)
+				}
+
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextSort-prevSort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+
+					//变更分类
+					if prevPermission != nil {
+						_ = models.UpdateEnPermissionSortByParentId(parentId, prevPermission.EnPermissionId, prevSort, updateSortStr)
+					} else {
+						_ = models.UpdateEnPermissionSortByParentId(parentId, 0, prevSort, updateSortStr)
+					}
+
+				}
+			}
+		}
+
+		permissionInfo.Sort = prevSort + 1
+		permissionInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else if prevPermission == nil && nextPermission == nil && parentId > 0 {
+		//处理只拖动到目录里,默认放到目录底部的情况
+		var maxSort int
+		maxSort, err = ob.GetMaxSortByParentId(parentId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("查询组内排序信息失败,Err:" + err.Error())
+			return
+		}
+		permissionInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+		permissionInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else {
+		// 拖动到父级分类的第一位
+		firstPermission, tmpErr := ob.GetFirstEnPermissionByParentId(parentId)
+		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstPermission != nil && firstPermission.EnPermissionId != 0 && firstPermission.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = models.UpdateEnPermissionSortByParentId(parentId, firstPermission.EnPermissionId-1, 0, updateSortStr)
+		}
+
+		permissionInfo.Sort = 0 //那就是排在第一位
+		permissionInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	}
+
+	//更新
+	if len(updateCol) > 0 {
+		err = permissionInfo.Update(updateCol)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("修改失败,Err:" + err.Error())
+			return
+		}
+	}
+	return
+}

+ 25 - 23
services/report.go

@@ -408,18 +408,18 @@ func UpdateReportEs(reportId int, publishState int) (err error) {
 			}
 		}
 	} else {
-		if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
-			permissionList, tmpErr := models.GetChartPermissionNameFromMappingByKeyword(reportInfo.ClassifyNameSecond, "rddp")
-			if tmpErr != nil {
-				return
-			}
-			categoryArr := make([]string, 0)
-			for i := 0; i < len(permissionList); i++ {
-				categoryArr = append(categoryArr, permissionList[i].PermissionName)
-			}
-			aliasArr, _ := addCategoryAliasToArr(categoryArr)
-			categories = strings.Join(aliasArr, ",")
+		//if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
+		permissionList, tmpErr := models.GetChartPermissionNameFromMappingByKeyword(reportInfo.ClassifyNameSecond, "rddp")
+		if tmpErr != nil {
+			return
+		}
+		categoryArr := make([]string, 0)
+		for i := 0; i < len(permissionList); i++ {
+			categoryArr = append(categoryArr, permissionList[i].PermissionName)
 		}
+		aliasArr, _ := addCategoryAliasToArr(categoryArr)
+		categories = strings.Join(aliasArr, ",")
+		//}
 	}
 
 	// 新增报告ES
@@ -991,20 +991,22 @@ func CreateNewReport(req models.AddReq, adminInfo *system.Admin) (newReportId in
 	}
 
 	// 处理权限
-	if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
-		go func() {
-			permissionItems, e := models.GetPermission(req.ClassifyNameSecond)
+	//if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
+	go func() {
+		permissionItems, e := models.GetPermission(req.ClassifyNameSecond)
+		if e != nil {
+			alarm_msg.SendAlarmMsg("获取权限失败,Err:"+err.Error(), 3)
+		}
+		for _, v := range permissionItems {
+			e = models.AddChartPermissionChapterMapping(v.ChartPermissionId, newReportId)
 			if e != nil {
-				alarm_msg.SendAlarmMsg("获取权限失败,Err:"+err.Error(), 3)
-			}
-			for _, v := range permissionItems {
-				e = models.AddChartPermissionChapterMapping(v.ChartPermissionId, newReportId)
-				if e != nil {
-					alarm_msg.SendAlarmMsg("新增权限失败,Err:"+err.Error(), 3)
-				}
+				alarm_msg.SendAlarmMsg("新增权限失败,Err:"+err.Error(), 3)
 			}
-		}()
-	}
+		}
+		// 同步crm权限
+		_ = EditReportPermissionSync(newReportId, req.ClassifyNameSecond)
+	}()
+	//}
 
 	reportCode = utils.MD5(strconv.Itoa(int(newReportId)))
 	//修改唯一编码

+ 153 - 0
services/report_chapter_type.go

@@ -0,0 +1,153 @@
+package services
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// MoveReportChapterType 移动报告章节
+func MoveReportChapterType(req *models.ReportChapterTypeMoveReq) (err error, errMsg string) {
+	ob := new(models.ReportChapterType)
+	reportChapterTypeId := req.ReportChapterTypeId
+	prevReportChapterTypeId := req.PrevReportChapterTypeId
+	nextReportChapterTypeId := req.NextReportChapterTypeId
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		reportChapterType     *models.ReportChapterType
+		prevReportChapterType *models.ReportChapterType
+		nextReportChapterType *models.ReportChapterType
+
+		prevSort int
+		nextSort int
+	)
+
+	// 移动对象为分类, 判断权限
+	reportChapterType, err = ob.GetReportChapterTypeById(reportChapterTypeId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "当前报告章节不存在"
+			err = fmt.Errorf("获取报告章节信息失败,Err:" + err.Error())
+			return
+		}
+		errMsg = "移动失败"
+		err = fmt.Errorf("获取章节信息失败,Err:" + err.Error())
+		return
+	} else if reportChapterType.ReportChapterTypeId == 0 {
+		errMsg = "当前报告章节不存在"
+		err = fmt.Errorf("获取报告章节信息失败,Err:" + err.Error())
+		return
+	}
+
+	researchType := reportChapterType.ResearchType
+	if prevReportChapterTypeId > 0 {
+		prevReportChapterType, err = ob.GetReportChapterTypeById(prevReportChapterTypeId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevReportChapterType.Sort
+	}
+
+	if nextReportChapterTypeId > 0 {
+		//下一个兄弟节点
+		nextReportChapterType, err = ob.GetReportChapterTypeById(nextReportChapterTypeId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextReportChapterType.Sort
+	}
+
+	err, errMsg = moveReportChapterType(reportChapterType, prevReportChapterType, nextReportChapterType, researchType, prevSort, nextSort)
+	return
+}
+
+// moveReportChapterType 移动指标分类
+func moveReportChapterType(reportChapterType, prevReportChapterType, nextReportChapterType *models.ReportChapterType, researchType string, prevSort, nextSort int) (err error, errMsg string) {
+	ob := new(models.ReportChapterType)
+	updateCol := make([]string, 0)
+
+	if prevSort > 0 {
+		//如果是移动在两个兄弟节点之间
+		if nextSort > 0 {
+			//下一个兄弟节点
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevSort == nextSort || prevSort == reportChapterType.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+
+				//变更分类
+				if prevReportChapterType != nil {
+					_ = models.UpdateReportChapterTypeSortByResearchType(researchType, prevReportChapterType.ReportChapterTypeId, prevReportChapterType.Sort, updateSortStr)
+				} else {
+					_ = models.UpdateReportChapterTypeSortByResearchType(researchType, 0, prevSort, updateSortStr)
+				}
+
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextSort-prevSort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+
+					//变更分类
+					if prevReportChapterType != nil {
+						_ = models.UpdateReportChapterTypeSortByResearchType(researchType, prevReportChapterType.ReportChapterTypeId, prevSort, updateSortStr)
+					} else {
+						_ = models.UpdateReportChapterTypeSortByResearchType(researchType, 0, prevSort, updateSortStr)
+					}
+
+				}
+			}
+		}
+
+		reportChapterType.Sort = prevSort + 1
+		reportChapterType.LastUpdatedTime = time.Now()
+		updateCol = append(updateCol, "Sort", "LastUpdatedTime")
+	} else if prevReportChapterType == nil && nextReportChapterType == nil && researchType != "" {
+		//处理只拖动到目录里,默认放到目录底部的情况
+		var maxSort int
+		maxSort, err = ob.GetMaxSortByResearchType(researchType)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("查询组内排序信息失败,Err:" + err.Error())
+			return
+		}
+		reportChapterType.Sort = maxSort + 1 //那就是排在组内最后一位
+		reportChapterType.LastUpdatedTime = time.Now()
+		updateCol = append(updateCol, "Sort", "LastUpdatedTime")
+	} else {
+		// 拖动到父级分类的第一位
+		firstReportChapterType, tmpErr := ob.GetFirstReportChapterTypeByResearchType(researchType)
+		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstReportChapterType != nil && firstReportChapterType.ReportChapterTypeId != 0 && firstReportChapterType.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = models.UpdateReportChapterTypeSortByResearchType(researchType, firstReportChapterType.ReportChapterTypeId-1, 0, updateSortStr)
+		}
+
+		reportChapterType.Sort = 0 //那就是排在第一位
+		reportChapterType.LastUpdatedTime = time.Now()
+		updateCol = append(updateCol, "Sort", "LastUpdatedTime")
+	}
+
+	//更新
+	if len(updateCol) > 0 {
+		err = reportChapterType.Update(updateCol)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("修改失败,Err:" + err.Error())
+			return
+		}
+	}
+	return
+}

+ 51 - 0
services/report_chapter_type_sync.go

@@ -0,0 +1,51 @@
+package services
+
+import (
+	"encoding/json"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/utils"
+	"fmt"
+)
+
+type ChapterTypeSyncReq struct {
+	ReportChapterTypeId int    `description:"报告章节类型id"`
+	ResearchType        string `description:"研报类型"`
+}
+
+type ChapterTypeBaseResp struct {
+	Code   int    `json:"code" description:"状态码"`
+	Msg    string `json:"msg" description:"提示信息"`
+	ErrMsg string `json:"-" description:"错误信息,不用返回给前端,只是做日志记录"`
+}
+
+func ReportChapterTypeSync(pars *ChapterTypeSyncReq) (err error, errMsg string) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info("同步章节权限数据失败, Err: " + err.Error() + errMsg)
+			alarm_msg.SendAlarmMsg("同步章节权限数据失败,Err:"+err.Error(), 3)
+		}
+	}()
+	if utils.CrmEtaServerUrl == "" {
+		return
+	}
+	url := fmt.Sprint(utils.CrmEtaServerUrl, "/api/crm/chapter_type/sync")
+	b, err := crmEtaPost(url, pars)
+	if err != nil {
+		errMsg = "操作失败"
+		err = fmt.Errorf("url:%s err: %s", url, err.Error())
+		return
+	}
+	result := new(ChapterTypeBaseResp)
+	if e := json.Unmarshal(b, &result); e != nil {
+		errMsg = "操作失败"
+		err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+		return
+	}
+	utils.FileLog.Info("%s", string(b))
+	if result.Code != 200 {
+		err = fmt.Errorf("result: %s, err: %s", string(b), result.ErrMsg)
+		errMsg = result.Msg
+		return
+	}
+	return
+}

+ 3 - 0
services/report_classify.go

@@ -88,6 +88,7 @@ func AfterUpdateClassifyNameOrParent(classifyId, parentId, originParentId int, o
 	// 二级分类-修改名称
 	if originName != classifyName && parentId > 0 {
 		// 更新关键词
+		// todo 更新crm里的权限关键词
 		if e := models.UpdateChartPermissionNameFromMappingByKeyword(classifyName, originName, "rddp"); e != nil {
 			err = fmt.Errorf("更新二级分类关键词失败, Err: %s", e.Error())
 			return
@@ -97,6 +98,8 @@ func AfterUpdateClassifyNameOrParent(classifyId, parentId, originParentId int, o
 			err = fmt.Errorf("更新报告表二级分类名称失败, Err: %s", e.Error())
 			return
 		}
+
+		_ = EditKeywordPermissionSync(classifyName, originName)
 		return
 	}
 

+ 1404 - 0
services/speech_recognition.go

@@ -0,0 +1,1404 @@
+package services
+
+import (
+	"baliance.com/gooxml/document"
+	"baliance.com/gooxml/measurement"
+	"baliance.com/gooxml/schema/soo/wml"
+	"bufio"
+	"errors"
+	"eta/eta_api/models"
+	"eta/eta_api/models/speech_recognition"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/jung-kurt/gofpdf"
+	"os"
+	"strconv"
+	"sync"
+	"time"
+)
+
+const (
+	SpeechRecognitionExportTypeTxt  = 1
+	SpeechRecognitionExportTypeDocx = 2
+	SpeechRecognitionExportTypePdf  = 3
+
+	SpeechMenuCheckRemoveTypePass    = 0 // 目录删除校验-可删除
+	SpeechMenuCheckRemoveTypeRefused = 1 // 目录删除校验-不可删除
+	SpeechMenuCheckRemoveTypeWarning = 2 // 目录删除校验-警告
+)
+
+// GetSpeechRecognitionMenuTreeRecursive 递归获取标签目录树
+func GetSpeechRecognitionMenuTreeRecursive(list []*speech_recognition.SpeechRecognitionMenu, parentId int) []*speech_recognition.SpeechRecognitionMenuItem {
+	res := make([]*speech_recognition.SpeechRecognitionMenuItem, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			t := new(speech_recognition.SpeechRecognitionMenuItem)
+			t.UniqueCode = v.UniqueCode
+			t.MenuId = v.SpeechRecognitionMenuId
+			t.MenuName = v.MenuName
+			t.ParentId = v.ParentId
+			t.Level = v.Level
+			t.Sort = v.Sort
+			t.CreateTime = utils.TimeTransferString(utils.FormatDateTime, v.CreateTime)
+			t.Children = GetSpeechRecognitionMenuTreeRecursive(list, v.SpeechRecognitionMenuId)
+			res = append(res, t)
+		}
+	}
+	return res
+}
+
+// GetSpeechRecognitionTagMenuTreeRecursive 递归获取标签目录树
+func GetSpeechRecognitionTagMenuTreeRecursive(list []*speech_recognition.SpeechRecognitionTagMenu, parentId int) []*speech_recognition.SpeechRecognitionTagMenuItem {
+	res := make([]*speech_recognition.SpeechRecognitionTagMenuItem, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			t := new(speech_recognition.SpeechRecognitionTagMenuItem)
+			t.UniqueCode = v.UniqueCode
+			t.MenuId = v.SpeechRecognitionTagMenuId
+			t.MenuName = v.MenuName
+			t.ParentId = v.ParentId
+			t.Level = v.Level
+			t.Sort = v.Sort
+			t.CreateTime = utils.TimeTransferString(utils.FormatDateTime, v.CreateTime)
+			t.Children = GetSpeechRecognitionTagMenuTreeRecursive(list, v.SpeechRecognitionTagMenuId)
+			res = append(res, t)
+		}
+	}
+	return res
+}
+
+// BatchConvertSpeech 批量转写语音
+func BatchConvertSpeech(speeches []*speech_recognition.SpeechRecognition) {
+	var err error
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("批量转写语音失败, ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 1)
+		}
+	}()
+
+	conf, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("获取配置失败, Err: %s", e.Error())
+		return
+	}
+	if conf[models.BusinessConfTencentApiSecretId] == "" || conf[models.BusinessConfTencentApiSecretKey] == "" || conf[models.BusinessConfTencentApiRecTaskCallbackUrl] == "" {
+		err = fmt.Errorf("API配置有误, SecretId: %s, SecretKey: %s, Callback: %s", conf[models.BusinessConfTencentApiSecretId], conf[models.BusinessConfTencentApiSecretKey], conf[models.BusinessConfTencentApiRecTaskCallbackUrl])
+		return
+	}
+
+	// 限制接口请求频率
+	apiLimit := make(chan struct{}, 20)
+	var wg sync.WaitGroup
+
+	for _, v := range speeches {
+		wg.Add(1)
+
+		go func(speech *speech_recognition.SpeechRecognition) {
+			defer func() {
+				wg.Done()
+				<-apiLimit
+			}()
+			apiLimit <- struct{}{}
+
+			// 发起请求
+			var errMsg string
+			var r TencentRecTaskReq
+			r.FileUrl = speech.ResourceUrl
+			r.SecretId = conf[models.BusinessConfTencentApiSecretId]
+			r.SecretKey = conf[models.BusinessConfTencentApiSecretKey]
+			r.CallbackUrl = conf[models.BusinessConfTencentApiRecTaskCallbackUrl]
+			taskId, e := TencentCreateRecTask(r)
+			if e != nil {
+				errMsg = "创建语音识别任务失败"
+				utils.FileLog.Info("TencentCreateRecTask创建语音识别任务失败, ErrMsg: %s", e.Error())
+			}
+
+			if errMsg == "" {
+				apiLog := new(speech_recognition.SpeechRecognitionApiLog)
+				apiLog.SpeechRecognitionId = speech.SpeechRecognitionId
+				apiLog.RequestId = strconv.Itoa(taskId)
+				apiLog.RequestCode = -1
+				apiLog.CreateTime = time.Now().Local()
+				apiLog.ModifyTime = time.Now().Local()
+				if e = apiLog.Create(); e != nil {
+					errMsg = "生成API请求失败"
+					utils.FileLog.Info("CreateApiLog生成API请求记录失败, ErrMsg: %s", e.Error())
+					return
+				}
+			}
+
+			// 有报错则更新对应语音识别状态
+			if errMsg == "" {
+				return
+			}
+			speech.State = speech_recognition.SpeechRecognitionStateFail
+			speech.ConvertRemark = errMsg
+			speech.ModifyTime = time.Now().Local()
+			updateCols := []string{speech_recognition.SpeechRecognitionCols.State, speech_recognition.SpeechRecognitionCols.ConvertRemark, speech_recognition.SpeechRecognitionCols.ModifyTime}
+			if e = speech.Update(updateCols); e != nil {
+				utils.FileLog.Info("UpdateSpeech更新语音识别状态失败, ErrMsg: %s", e.Error())
+				return
+			}
+		}(v)
+	}
+
+	wg.Wait()
+
+	return
+}
+
+// SpeechRecognitionContentExport 导出语音识别内容
+func SpeechRecognitionContentExport(exportType int, exportTimestamp bool, fileName string, contents []*speech_recognition.SpeechRecognitionContent) (result string, err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println(err)
+		}
+	}()
+	if len(contents) == 0 {
+		return
+	}
+	// 整理内容
+	exportText := ""
+	exportArr := make([]string, 0)
+	exportPdfArr := make([]string, 0)
+	for _, v := range contents {
+		if v.Content == "" {
+			continue
+		}
+		sec := ""
+		secPdf := ""
+		if exportTimestamp {
+			// 毫秒转时间格式
+			sec = fmt.Sprintf("%s\n%s\n\n", utils.MillisecondsToHHMMSS(v.StartMs), v.Content)
+			secPdf = fmt.Sprintf("%s %s", utils.MillisecondsToHHMMSS(v.StartMs), v.Content)
+		} else {
+			sec = fmt.Sprintf("%s\n\n", v.Content)
+			secPdf = v.Content
+		}
+		exportText += sec
+		exportArr = append(exportArr, sec)
+		exportPdfArr = append(exportPdfArr, secPdf)
+	}
+
+	// 导出doc
+	if exportType == SpeechRecognitionExportTypeDocx {
+		doc := document.New()
+		for _, v := range exportArr {
+			p := doc.AddParagraph()
+			prop := p.Properties()
+			prop.Spacing().SetLineSpacing(measurement.Distance(1.5*15*measurement.Point), wml.ST_LineSpacingRuleAuto)
+			prop.SetAlignment(wml.ST_JcLeft)
+			run := p.AddRun()
+			runProp := run.Properties()
+			runProp.SetSize(measurement.Distance(15 * measurement.Point))
+			runProp.SetFontFamily("宋体")
+			run.AddText(v)
+			run.AddBreak()
+		}
+
+		filePath := fmt.Sprintf("%s.docx", fileName)
+		if e := doc.SaveToFile(filePath); e != nil {
+			err = fmt.Errorf("生成docx失败, Err: %s", e.Error())
+			return
+		}
+		result = filePath
+		return
+	}
+
+	// 导出pdf
+	if exportType == SpeechRecognitionExportTypePdf {
+		pdf := gofpdf.New("P", "mm", "A4", "")
+		pdf.AddPage()
+		pdf.AddUTF8Font("SimHei", "", "static/SimHei.ttf") // 此处字体文件只能用本地的
+		pdf.SetFont("SimHei", "", 14)
+
+		// 计算可用内容区域宽度
+		w, _ := pdf.GetPageSize()
+		marginLeft := 10.0
+		marginRight := 10.0
+		availableWidth := w - marginLeft - marginRight
+		for _, v := range exportPdfArr {
+			pdf.MultiCell(availableWidth, 10, v, "", "L", false)
+			pdf.MultiCell(availableWidth, 5, "", "", "L", false) // 单纯的换行
+		}
+		filePath := fmt.Sprintf("%s.pdf", fileName)
+		if e := pdf.OutputFileAndClose(filePath); e != nil {
+			err = fmt.Errorf("生成pdf失败, Err: %s", e.Error())
+			return
+		}
+		result = filePath
+		return
+	}
+
+	// 默认导出txt
+	filePath := fmt.Sprintf("%s.txt", fileName)
+	file, e := os.Create(filePath)
+	if e != nil {
+		err = fmt.Errorf("生成txt文件失败, err: %s", e.Error())
+		return
+	}
+	defer file.Close()
+
+	// 写入txt
+	writer := bufio.NewWriter(file)
+	_, e = writer.WriteString(exportText)
+	if e != nil {
+		err = fmt.Errorf("写入txt文件失败, err: %s", e.Error())
+		return
+	}
+	if e = writer.Flush(); e != nil {
+		err = fmt.Errorf("刷新txt缓存失败, err: %s", e.Error())
+		return
+	}
+	result = filePath
+	return
+}
+
+// ------------------ 移动目录/语音识别(有空的话后面可以优化一下,以后这种菜单混合移动的情况不会少,CV起来怪麻烦的) ------------------ //
+
+// MoveSpeechMenu 移动语音识别目录/识别文件
+func MoveSpeechMenu(req speech_recognition.SpeechRecognitionMenuMoveReq) (err error, errMsg string) {
+	menuId := req.MenuId
+	parentMenuId := req.ParentMenuId
+	prevMenuId := req.PrevMenuId
+	nextMenuId := req.NextMenuId
+
+	speechId := req.SpeechId
+	prevSpeechId := req.PrevSpeechId
+	nextSpeechId := req.NextSpeechId
+
+	//首先确定移动的对象是目录还是语音识别
+	//判断上一个节点是目录还是语音识别
+	//判断下一个节点是目录还是语音识别
+	//同时更新目录目录下的目录sort和语音识别sort
+	//更新当前移动的目录或者语音识别sort
+
+	menuOb := new(speech_recognition.SpeechRecognitionMenu)
+	speechOb := new(speech_recognition.SpeechRecognition)
+
+	var parentSpeechMenu *speech_recognition.SpeechRecognitionMenu
+	if parentMenuId > 0 {
+		t, e := menuOb.GetItemById(parentMenuId)
+		if e != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上级目录信息失败,Err:" + err.Error())
+			return
+		}
+		parentSpeechMenu = t
+	}
+
+	//如果有传入 上一个兄弟节点目录id
+	var (
+		speechMenu *speech_recognition.SpeechRecognitionMenu
+		prevMenu   *speech_recognition.SpeechRecognitionMenu
+		nextMenu   *speech_recognition.SpeechRecognitionMenu
+
+		speechItem *speech_recognition.SpeechRecognition
+		prevSpeech *speech_recognition.SpeechRecognition
+		nextSpeech *speech_recognition.SpeechRecognition
+		prevSort   int
+		nextSort   int
+	)
+
+	// 移动对象为目录
+	if speechId == 0 {
+		speechMenu, err = menuOb.GetItemById(menuId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前目录不存在"
+				err = errors.New("获取目录信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取目录信息失败,Err:" + err.Error())
+			return
+		}
+		if parentMenuId > 0 && parentSpeechMenu.Level == 3 {
+			errMsg = "最高只支持添加3级目录"
+			err = errors.New(errMsg)
+			return
+		}
+
+		// 如果是移动目录, 那么校验一下父级目录下是否有重名目录
+		{
+			cond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s <> ?`, speech_recognition.SpeechRecognitionMenuCols.MenuName, speech_recognition.SpeechRecognitionMenuCols.ParentId, speech_recognition.SpeechRecognitionMenuCols.SpeechRecognitionMenuId)
+			pars := make([]interface{}, 0)
+			pars = append(pars, speechMenu.MenuName, parentMenuId, menuId)
+			exists, e := menuOb.GetItemByCondition(cond, pars, "")
+			if e != nil && e.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = fmt.Errorf("获取父级目录下的同名目录失败, Err: %s", e.Error())
+				return
+			}
+			if exists != nil {
+				errMsg = "移动失败,目录名称已存在"
+				return
+			}
+		}
+	} else {
+		speechItem, err = speechOb.GetItemById(speechId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前语音识别不存在"
+				err = errors.New("获取目录信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取目录信息失败,Err:" + err.Error())
+			return
+		}
+		if parentMenuId == 0 {
+			errMsg = "移动失败,语音识别必须挂在目录下"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+
+	if prevMenuId > 0 {
+		prevMenu, err = menuOb.GetItemById(prevMenuId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点目录信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevMenu.Sort
+	} else if prevSpeechId > 0 {
+		prevSpeech, err = speechOb.GetItemById(prevSpeechId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevSpeech.Sort
+	}
+
+	// 下一个兄弟节点
+	if nextMenuId > 0 {
+		nextMenu, err = menuOb.GetItemById(nextMenuId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点目录信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextMenu.Sort
+	} else if nextSpeechId > 0 {
+		nextSpeech, err = speechOb.GetItemById(nextSpeechId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点目录信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextSpeech.Sort
+	}
+
+	err, errMsg = moveSpeechMenu(parentSpeechMenu, speechMenu, prevMenu, nextMenu, speechItem, prevSpeech, nextSpeech, parentMenuId, prevSort, nextSort)
+	return
+}
+
+func moveSpeechMenu(parentSpeechMenu, speechMenu, prevMenu, nextMenu *speech_recognition.SpeechRecognitionMenu, speechItem, prevSpeech, nextSpeech *speech_recognition.SpeechRecognition, parentMenuId int, prevSort, nextSort int) (err error, errMsg string) {
+	updateCol := make([]string, 0)
+
+	menuOb := new(speech_recognition.SpeechRecognitionMenu)
+	speechOb := new(speech_recognition.SpeechRecognition)
+
+	// 移动对象为目录, 判断目录是否存在
+	if speechMenu != nil {
+		oldParentId := speechMenu.ParentId
+		oldLevel := speechMenu.Level
+		var menuIds []int
+		if oldParentId != parentMenuId {
+			//更新子目录对应的level
+			childList, e, m := GetChildSpeechMenuByMenuId(speechMenu.SpeechRecognitionMenuId)
+			if e != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询子目录失败,Err:" + e.Error() + m)
+				return
+			}
+
+			if len(childList) > 0 {
+				for _, v := range childList {
+					if v.SpeechRecognitionMenuId == speechMenu.SpeechRecognitionMenuId {
+						continue
+					}
+					menuIds = append(menuIds, v.SpeechRecognitionMenuId)
+				}
+			}
+		}
+		//判断上级id是否一致,如果不一致的话,那么需要移动该目录层级
+		if speechMenu.ParentId != parentMenuId && parentMenuId != 0 {
+			if speechMenu.Level != parentSpeechMenu.Level+1 { //禁止层级调整
+				errMsg = "移动失败"
+				err = errors.New("不支持目录层级变更")
+				return
+			}
+			speechMenu.ParentId = parentSpeechMenu.SpeechRecognitionMenuId
+			speechMenu.RootId = parentSpeechMenu.RootId
+			speechMenu.Level = parentSpeechMenu.Level + 1
+			speechMenu.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ParentId", "RootId", "Level", "ModifyTime")
+		} else if speechMenu.ParentId != parentMenuId && parentMenuId == 0 {
+			errMsg = "移动失败"
+			err = errors.New("不支持目录层级变更")
+			return
+		}
+
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == speechMenu.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更目录
+					if prevMenu != nil {
+						_ = menuOb.UpdateSortByParentId(parentMenuId, prevMenu.SpeechRecognitionMenuId, prevMenu.Sort, updateSortStr)
+					} else {
+						_ = menuOb.UpdateSortByParentId(parentMenuId, 0, prevSort, updateSortStr)
+					}
+
+					//变更语音识别
+					if prevSpeech != nil {
+						//变更兄弟节点的排序
+						_ = speechOb.UpdateSortByMenuId(parentMenuId, prevSort, prevSpeech.SpeechRecognitionId, updateSortStr)
+					} else {
+						_ = speechOb.UpdateSortByMenuId(parentMenuId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+
+						//变更目录
+						if prevMenu != nil {
+							_ = menuOb.UpdateSortByParentId(parentMenuId, prevMenu.SpeechRecognitionMenuId, prevSort, updateSortStr)
+						} else {
+							_ = menuOb.UpdateSortByParentId(parentMenuId, 0, prevSort, updateSortStr)
+						}
+
+						//变更语音识别
+						if prevSpeech != nil {
+							//变更兄弟节点的排序
+							_ = speechOb.UpdateSortByMenuId(parentMenuId, prevSort, prevSpeech.SpeechRecognitionId, updateSortStr)
+						} else {
+							_ = speechOb.UpdateSortByMenuId(parentMenuId, prevSort, 0, updateSortStr)
+						}
+
+					}
+				}
+			}
+
+			speechMenu.Sort = prevSort + 1
+			speechMenu.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevMenu == nil && nextMenu == nil && prevSpeech == nil && nextSpeech == nil && parentMenuId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetSpeechMenuMaxSort(parentMenuId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			speechMenu.Sort = maxSort + 1 //那就是排在组内最后一位
+			speechMenu.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级目录的第一位
+			firstMenu, tmpErr := menuOb.GetFirstByParentId(parentMenuId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级目录下的排序第一条的目录信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该目录下存在其他目录,且第一个其他目录的排序等于0,那么需要调整排序
+			if firstMenu != nil && firstMenu.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = menuOb.UpdateSortByParentId(parentMenuId, firstMenu.SpeechRecognitionMenuId-1, 0, updateSortStr)
+				//该目录下的所有语音识别也需要+1
+				_ = speechOb.UpdateSortByMenuId(parentMenuId, 0, 0, updateSortStr)
+			} else {
+				//如果该目录下存在语音识别,且第一个语音识别的排序等于0,那么需要调整排序
+				firstSpeech, tErr := speechOb.GetFirstByMenuId(parentMenuId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级目录下的排序第一条的目录信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该目录下存在其他目录,且第一个其他目录的排序等于0,那么需要调整排序
+				if firstSpeech != nil && firstSpeech.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = speechOb.UpdateSortByMenuId(parentMenuId, 0, firstSpeech.SpeechRecognitionId-1, updateSortStr)
+					_ = menuOb.UpdateSortByParentId(parentMenuId, 0, 0, updateSortStr)
+				}
+			}
+
+			speechMenu.Sort = 0 //那就是排在第一位
+			speechMenu.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = speechMenu.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+			//更新对应目录的root_id和层级
+			if oldParentId != parentMenuId {
+				if len(menuIds) > 0 {
+					levelStep := speechMenu.Level - oldLevel
+					err = menuOb.UpdateChildByParentMenuId(menuIds, speechMenu.RootId, levelStep)
+					if err != nil {
+						errMsg = "移动失败"
+						err = errors.New("更新子目录失败,Err:" + err.Error())
+						return
+					}
+				}
+			}
+		}
+	} else {
+		if speechItem == nil {
+			errMsg = "当前语音识别不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		//如果改变了目录,那么移动该语音识别数据
+		if speechItem.MenuId != parentMenuId {
+			speechItem.MenuId = parentMenuId
+			speechItem.ModifyTime = time.Now()
+			updateCol = append(updateCol, speech_recognition.SpeechRecognitionCols.MenuId, speech_recognition.SpeechRecognitionCols.ModifyTime)
+		}
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == speechItem.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更目录
+					if prevMenu != nil {
+						_ = menuOb.UpdateSortByParentId(parentMenuId, prevMenu.SpeechRecognitionMenuId, prevMenu.Sort, updateSortStr)
+					} else {
+						_ = menuOb.UpdateSortByParentId(parentMenuId, 0, prevSort, updateSortStr)
+					}
+
+					//变更语音识别
+					if prevSpeech != nil {
+						//变更兄弟节点的排序
+						_ = speechOb.UpdateSortByMenuId(parentMenuId, prevSort, prevSpeech.SpeechRecognitionId, updateSortStr)
+					} else {
+						_ = speechOb.UpdateSortByMenuId(parentMenuId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+						//变更目录
+						if prevMenu != nil {
+							_ = menuOb.UpdateSortByParentId(parentMenuId, prevMenu.SpeechRecognitionMenuId, prevSort, updateSortStr)
+						} else {
+							_ = menuOb.UpdateSortByParentId(parentMenuId, 0, prevSort, updateSortStr)
+						}
+
+						//变更语音识别
+						if prevSpeech != nil {
+							//变更兄弟节点的排序
+							_ = speechOb.UpdateSortByMenuId(parentMenuId, prevSort, prevSpeech.SpeechRecognitionId, updateSortStr)
+						} else {
+							_ = speechOb.UpdateSortByMenuId(parentMenuId, prevSort, 0, updateSortStr)
+						}
+					}
+				}
+			}
+
+			speechItem.Sort = prevSort + 1
+			speechItem.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevMenu == nil && nextMenu == nil && prevSpeech == nil && nextSpeech == nil && parentMenuId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetSpeechMenuMaxSort(parentMenuId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			speechItem.Sort = maxSort + 1 //那就是排在组内最后一位
+			speechItem.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级目录的第一位
+			firstMenu, tmpErr := menuOb.GetFirstByParentId(parentMenuId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级目录下的排序第一条的目录信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该目录下存在其他目录,且第一个其他目录的排序等于0,那么需要调整排序
+			if firstMenu != nil && firstMenu.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = menuOb.UpdateSortByParentId(parentMenuId, firstMenu.SpeechRecognitionMenuId-1, 0, updateSortStr)
+				//该目录下的所有语音识别也需要+1
+				_ = speechOb.UpdateSortByMenuId(parentMenuId, 0, 0, updateSortStr)
+			} else {
+				//如果该目录下存在语音识别,且第一个语音识别的排序等于0,那么需要调整排序
+				firstSpeech, tErr := speechOb.GetFirstByMenuId(parentMenuId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级目录下的排序第一条的目录信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该目录下存在其他目录,且第一个其他目录的排序等于0,那么需要调整排序
+				if firstSpeech != nil && firstSpeech.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = speechOb.UpdateSortByMenuId(parentMenuId, 0, firstSpeech.SpeechRecognitionId-1, updateSortStr)
+					_ = menuOb.UpdateSortByParentId(parentMenuId, 0, 0, updateSortStr)
+				}
+			}
+
+			speechItem.Sort = 0 //那就是排在第一位
+			speechItem.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = speechItem.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+		}
+	}
+	return
+}
+
+func GetChildSpeechMenuByMenuId(menuId int) (targetList []*speech_recognition.SpeechRecognitionMenu, err error, errMsg string) {
+	menuOb := new(speech_recognition.SpeechRecognitionMenu)
+	//判断是否是挂在顶级目录下
+	speechMenu, err := menuOb.GetItemById(menuId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "当前目录不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		errMsg = "获取失败"
+		err = errors.New("获取目录信息失败,Err:" + err.Error())
+		return
+	}
+
+	cond := fmt.Sprintf(" AND %s = ?", speech_recognition.SpeechRecognitionMenuCols.RootId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, speechMenu.RootId)
+	order := fmt.Sprintf("%s ASC, %s ASC, %s ASC", speech_recognition.SpeechRecognitionMenuCols.Level, speech_recognition.SpeechRecognitionMenuCols.Sort, speech_recognition.SpeechRecognitionMenuCols.SpeechRecognitionMenuId)
+	tmpList, err := speechMenu.GetItemsByCondition(cond, pars, []string{}, order)
+	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.SpeechRecognitionMenuId == speechMenu.SpeechRecognitionMenuId {
+				idMap[v.SpeechRecognitionMenuId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ParentId]; ok {
+				idMap[v.SpeechRecognitionMenuId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.SpeechRecognitionMenuId]; ok {
+				targetList = append(targetList, v)
+			}
+		}
+	}
+	return
+}
+
+// GetSpeechMenuMaxSort 获取同级下最大的排序
+func GetSpeechMenuMaxSort(parentId int) (maxSort int, err error) {
+	menuOb := new(speech_recognition.SpeechRecognitionMenu)
+	speechOb := new(speech_recognition.SpeechRecognition)
+
+	//获取该层级下最大的排序数
+	menuMax, err := menuOb.GetMaxSortByParentId(parentId)
+	if err != nil {
+		return
+	}
+	maxSort = menuMax
+	speechMax, err := speechOb.GetMaxSortByMenuId(parentId)
+	if err != nil {
+		return
+	}
+	if maxSort < speechMax {
+		maxSort = speechMax
+	}
+	return
+}
+
+// ------------------ 移动目录/语音识别 ------------------ //
+
+// ------------------ 移动标签目录/标签 ------------------ //
+
+// MoveSpeechTagMenu 移动标签目录/标签
+func MoveSpeechTagMenu(req speech_recognition.SpeechRecognitionTagMenuMoveReq) (err error, errMsg string) {
+	menuId := req.MenuId
+	parentMenuId := req.ParentMenuId
+	prevMenuId := req.PrevMenuId
+	nextMenuId := req.NextMenuId
+
+	tagId := req.TagId
+	prevTagId := req.PrevTagId
+	nextTagId := req.NextTagId
+
+	//首先确定移动的对象是目录还是标签
+	//判断上一个节点是目录还是标签
+	//判断下一个节点是目录还是标签
+	//同时更新目录目录下的目录sort和标签sort
+	//更新当前移动的目录或者标签sort
+
+	menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+	tagOb := new(speech_recognition.SpeechRecognitionTag)
+
+	var parentTagMenu *speech_recognition.SpeechRecognitionTagMenu
+	if parentMenuId > 0 {
+		t, e := menuOb.GetItemById(parentMenuId)
+		if e != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上级目录信息失败,Err:" + err.Error())
+			return
+		}
+		parentTagMenu = t
+	}
+
+	//如果有传入 上一个兄弟节点目录id
+	var (
+		tagMenu  *speech_recognition.SpeechRecognitionTagMenu
+		prevMenu *speech_recognition.SpeechRecognitionTagMenu
+		nextMenu *speech_recognition.SpeechRecognitionTagMenu
+
+		tagItem  *speech_recognition.SpeechRecognitionTag
+		prevTag  *speech_recognition.SpeechRecognitionTag
+		nextTag  *speech_recognition.SpeechRecognitionTag
+		prevSort int
+		nextSort int
+	)
+
+	// 移动对象为目录
+	if tagId == 0 {
+		tagMenu, err = menuOb.GetItemById(menuId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前目录不存在"
+				err = errors.New("获取目录信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取目录信息失败,Err:" + err.Error())
+			return
+		}
+		if parentMenuId > 0 && parentTagMenu.Level == 3 {
+			errMsg = "最高只支持添加3级目录"
+			err = errors.New(errMsg)
+			return
+		}
+
+		// 如果是移动目录, 那么校验一下父级目录下是否有重名目录
+		{
+			cond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s <> ?`, speech_recognition.SpeechRecognitionTagMenuCols.MenuName, speech_recognition.SpeechRecognitionTagMenuCols.ParentId, speech_recognition.SpeechRecognitionTagMenuCols.SpeechRecognitionTagMenuId)
+			pars := make([]interface{}, 0)
+			pars = append(pars, tagMenu.MenuName, parentMenuId, menuId)
+			exists, e := menuOb.GetItemByCondition(cond, pars, "")
+			if e != nil && e.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = fmt.Errorf("获取父级目录下的同名目录失败, Err: %s", e.Error())
+				return
+			}
+			if exists != nil {
+				errMsg = "移动失败,目录名称已存在"
+				return
+			}
+		}
+	} else {
+		tagItem, err = tagOb.GetItemById(tagId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前标签不存在"
+				err = errors.New("获取目录信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取目录信息失败,Err:" + err.Error())
+			return
+		}
+		if parentMenuId == 0 {
+			errMsg = "移动失败,标签必须挂在目录下"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+
+	if prevMenuId > 0 {
+		prevMenu, err = menuOb.GetItemById(prevMenuId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点目录信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevMenu.Sort
+	} else if prevTagId > 0 {
+		prevTag, err = tagOb.GetItemById(prevTagId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevTag.Sort
+	}
+
+	// 下一个兄弟节点
+	if nextMenuId > 0 {
+		nextMenu, err = menuOb.GetItemById(nextMenuId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点目录信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextMenu.Sort
+	} else if nextTagId > 0 {
+		nextTag, err = tagOb.GetItemById(nextTagId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点目录信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextTag.Sort
+	}
+
+	err, errMsg = moveSpeechTagMenu(parentTagMenu, tagMenu, prevMenu, nextMenu, tagItem, prevTag, nextTag, parentMenuId, prevSort, nextSort)
+	return
+}
+
+func moveSpeechTagMenu(parentTagMenu, tagMenu, prevMenu, nextMenu *speech_recognition.SpeechRecognitionTagMenu, tagItem, prevTag, nextTag *speech_recognition.SpeechRecognitionTag, parentMenuId int, prevSort, nextSort int) (err error, errMsg string) {
+	updateCol := make([]string, 0)
+
+	menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+	tagOb := new(speech_recognition.SpeechRecognitionTag)
+
+	// 移动对象为目录, 判断目录是否存在
+	if tagMenu != nil {
+		oldParentId := tagMenu.ParentId
+		oldLevel := tagMenu.Level
+		var menuIds []int
+		if oldParentId != parentMenuId {
+			//更新子目录对应的level
+			childList, e, m := GetChildSpeechTagMenuByMenuId(tagMenu.SpeechRecognitionTagMenuId)
+			if e != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询子目录失败,Err:" + e.Error() + m)
+				return
+			}
+
+			if len(childList) > 0 {
+				for _, v := range childList {
+					if v.SpeechRecognitionTagMenuId == tagMenu.SpeechRecognitionTagMenuId {
+						continue
+					}
+					menuIds = append(menuIds, v.SpeechRecognitionTagMenuId)
+				}
+			}
+		}
+		//判断上级id是否一致,如果不一致的话,那么需要移动该目录层级
+		if tagMenu.ParentId != parentMenuId && parentMenuId != 0 {
+			if tagMenu.Level != parentTagMenu.Level+1 { //禁止层级调整
+				errMsg = "移动失败"
+				err = errors.New("不支持目录层级变更")
+				return
+			}
+			tagMenu.ParentId = parentTagMenu.SpeechRecognitionTagMenuId
+			tagMenu.RootId = parentTagMenu.RootId
+			tagMenu.Level = parentTagMenu.Level + 1
+			tagMenu.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ParentId", "RootId", "Level", "ModifyTime")
+		} else if tagMenu.ParentId != parentMenuId && parentMenuId == 0 {
+			errMsg = "移动失败"
+			err = errors.New("不支持目录层级变更")
+			return
+		}
+
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == tagMenu.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更目录
+					if prevMenu != nil {
+						_ = menuOb.UpdateSortByParentId(parentMenuId, prevMenu.SpeechRecognitionTagMenuId, prevMenu.Sort, updateSortStr)
+					} else {
+						_ = menuOb.UpdateSortByParentId(parentMenuId, 0, prevSort, updateSortStr)
+					}
+
+					//变更标签
+					if prevTag != nil {
+						//变更兄弟节点的排序
+						_ = tagOb.UpdateSortByMenuId(parentMenuId, prevSort, prevTag.SpeechRecognitionTagId, updateSortStr)
+					} else {
+						_ = tagOb.UpdateSortByMenuId(parentMenuId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+
+						//变更目录
+						if prevMenu != nil {
+							_ = menuOb.UpdateSortByParentId(parentMenuId, prevMenu.SpeechRecognitionTagMenuId, prevSort, updateSortStr)
+						} else {
+							_ = menuOb.UpdateSortByParentId(parentMenuId, 0, prevSort, updateSortStr)
+						}
+
+						//变更标签
+						if prevTag != nil {
+							//变更兄弟节点的排序
+							_ = tagOb.UpdateSortByMenuId(parentMenuId, prevSort, prevTag.SpeechRecognitionTagId, updateSortStr)
+						} else {
+							_ = tagOb.UpdateSortByMenuId(parentMenuId, prevSort, 0, updateSortStr)
+						}
+
+					}
+				}
+			}
+
+			tagMenu.Sort = prevSort + 1
+			tagMenu.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevMenu == nil && nextMenu == nil && prevTag == nil && nextTag == nil && parentMenuId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetSpeechTagMenuMaxSort(parentMenuId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			tagMenu.Sort = maxSort + 1 //那就是排在组内最后一位
+			tagMenu.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级目录的第一位
+			firstTagMenu, tmpErr := menuOb.GetFirstByParentId(parentMenuId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级目录下的排序第一条的目录信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该目录下存在其他目录,且第一个其他目录的排序等于0,那么需要调整排序
+			if firstTagMenu != nil && firstTagMenu.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = menuOb.UpdateSortByParentId(parentMenuId, firstTagMenu.SpeechRecognitionTagMenuId-1, 0, updateSortStr)
+				//该目录下的所有标签也需要+1
+				_ = tagOb.UpdateSortByMenuId(parentMenuId, 0, 0, updateSortStr)
+			} else {
+				//如果该目录下存在标签,且第一个标签的排序等于0,那么需要调整排序
+				firstTag, tErr := tagOb.GetFirstByMenuId(parentMenuId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级目录下的排序第一条的目录信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该目录下存在其他目录,且第一个其他目录的排序等于0,那么需要调整排序
+				if firstTag != nil && firstTag.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = tagOb.UpdateSortByMenuId(parentMenuId, 0, firstTag.SpeechRecognitionTagId-1, updateSortStr)
+					_ = menuOb.UpdateSortByParentId(parentMenuId, 0, 0, updateSortStr)
+				}
+			}
+
+			tagMenu.Sort = 0 //那就是排在第一位
+			tagMenu.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = tagMenu.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+			//更新对应目录的root_id和层级
+			if oldParentId != parentMenuId {
+				if len(menuIds) > 0 {
+					levelStep := tagMenu.Level - oldLevel
+					err = menuOb.UpdateChildByParentMenuId(menuIds, tagMenu.RootId, levelStep)
+					if err != nil {
+						errMsg = "移动失败"
+						err = errors.New("更新子目录失败,Err:" + err.Error())
+						return
+					}
+				}
+			}
+		}
+	} else {
+		if tagItem == nil {
+			errMsg = "当前标签不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		//如果改变了目录,那么移动该标签数据
+		if tagItem.MenuId != parentMenuId {
+			tagItem.MenuId = parentMenuId
+			tagItem.ModifyTime = time.Now()
+			updateCol = append(updateCol, speech_recognition.SpeechRecognitionTagCols.MenuId, speech_recognition.SpeechRecognitionTagCols.ModifyTime)
+		}
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == tagItem.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更目录
+					if prevMenu != nil {
+						_ = menuOb.UpdateSortByParentId(parentMenuId, prevMenu.SpeechRecognitionTagMenuId, prevMenu.Sort, updateSortStr)
+					} else {
+						_ = menuOb.UpdateSortByParentId(parentMenuId, 0, prevSort, updateSortStr)
+					}
+
+					//变更标签
+					if prevTag != nil {
+						//变更兄弟节点的排序
+						_ = tagOb.UpdateSortByMenuId(parentMenuId, prevSort, prevTag.SpeechRecognitionTagId, updateSortStr)
+					} else {
+						_ = tagOb.UpdateSortByMenuId(parentMenuId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+						//变更目录
+						if prevMenu != nil {
+							_ = menuOb.UpdateSortByParentId(parentMenuId, prevMenu.SpeechRecognitionTagMenuId, prevSort, updateSortStr)
+						} else {
+							_ = menuOb.UpdateSortByParentId(parentMenuId, 0, prevSort, updateSortStr)
+						}
+
+						//变更标签
+						if prevTag != nil {
+							//变更兄弟节点的排序
+							_ = tagOb.UpdateSortByMenuId(parentMenuId, prevSort, prevTag.SpeechRecognitionTagId, updateSortStr)
+						} else {
+							_ = tagOb.UpdateSortByMenuId(parentMenuId, prevSort, 0, updateSortStr)
+						}
+					}
+				}
+			}
+
+			tagItem.Sort = prevSort + 1
+			tagItem.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevMenu == nil && nextMenu == nil && prevTag == nil && nextTag == nil && parentMenuId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetSpeechTagMenuMaxSort(parentMenuId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			tagItem.Sort = maxSort + 1 //那就是排在组内最后一位
+			tagItem.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级目录的第一位
+			firstTagMenu, tmpErr := menuOb.GetFirstByParentId(parentMenuId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级目录下的排序第一条的目录信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该目录下存在其他目录,且第一个其他目录的排序等于0,那么需要调整排序
+			if firstTagMenu != nil && firstTagMenu.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = menuOb.UpdateSortByParentId(parentMenuId, firstTagMenu.SpeechRecognitionTagMenuId-1, 0, updateSortStr)
+				//该目录下的所有标签也需要+1
+				_ = tagOb.UpdateSortByMenuId(parentMenuId, 0, 0, updateSortStr)
+			} else {
+				//如果该目录下存在标签,且第一个标签的排序等于0,那么需要调整排序
+				firstTag, tErr := tagOb.GetFirstByMenuId(parentMenuId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级目录下的排序第一条的目录信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该目录下存在其他目录,且第一个其他目录的排序等于0,那么需要调整排序
+				if firstTag != nil && firstTag.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = tagOb.UpdateSortByMenuId(parentMenuId, 0, firstTag.SpeechRecognitionTagId-1, updateSortStr)
+					_ = menuOb.UpdateSortByParentId(parentMenuId, 0, 0, updateSortStr)
+				}
+			}
+
+			tagItem.Sort = 0 //那就是排在第一位
+			tagItem.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = tagItem.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+		}
+	}
+	return
+}
+
+func GetChildSpeechTagMenuByMenuId(menuId int) (targetList []*speech_recognition.SpeechRecognitionTagMenu, err error, errMsg string) {
+	menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+	//判断是否是挂在顶级目录下
+	tagMenu, err := menuOb.GetItemById(menuId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "当前目录不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		errMsg = "获取失败"
+		err = errors.New("获取目录信息失败,Err:" + err.Error())
+		return
+	}
+
+	cond := fmt.Sprintf(" AND %s = ?", speech_recognition.SpeechRecognitionTagMenuCols.RootId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, tagMenu.RootId)
+	order := fmt.Sprintf("%s ASC, %s ASC, %s ASC", speech_recognition.SpeechRecognitionTagMenuCols.Level, speech_recognition.SpeechRecognitionTagMenuCols.Sort, speech_recognition.SpeechRecognitionTagMenuCols.SpeechRecognitionTagMenuId)
+	tmpList, err := tagMenu.GetItemsByCondition(cond, pars, []string{}, order)
+	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.SpeechRecognitionTagMenuId == tagMenu.SpeechRecognitionTagMenuId {
+				idMap[v.SpeechRecognitionTagMenuId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ParentId]; ok {
+				idMap[v.SpeechRecognitionTagMenuId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.SpeechRecognitionTagMenuId]; ok {
+				targetList = append(targetList, v)
+			}
+		}
+	}
+	return
+}
+
+// GetSpeechTagMenuMaxSort 获取同级下最大的排序
+func GetSpeechTagMenuMaxSort(parentId int) (maxSort int, err error) {
+	menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+	tagOb := new(speech_recognition.SpeechRecognitionTag)
+
+	//获取该层级下最大的排序数
+	menuMax, err := menuOb.GetMaxSortByParentId(parentId)
+	if err != nil {
+		return
+	}
+	maxSort = menuMax
+	speechMax, err := tagOb.GetMaxSortByMenuId(parentId)
+	if err != nil {
+		return
+	}
+	if maxSort < speechMax {
+		maxSort = speechMax
+	}
+	return
+}
+
+// ------------------ 移动标签目录/标签 ------------------ //
+
+// GetSpeechRecognitionMenuChildrenRecursive 递归获取目录的子目录集
+func GetSpeechRecognitionMenuChildrenRecursive(list []*speech_recognition.SpeechRecognitionMenu, parentId int) []int {
+	childIds := make([]int, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			childIds = append(childIds, v.SpeechRecognitionMenuId)
+			ids := GetSpeechRecognitionMenuChildrenRecursive(list, v.SpeechRecognitionMenuId)
+			if len(ids) > 0 {
+				childIds = append(childIds, ids...)
+			}
+		}
+	}
+	return childIds
+}
+
+// GetSpeechRecognitionTagMenuChildrenRecursive 递归获取标签目录的子目录集
+func GetSpeechRecognitionTagMenuChildrenRecursive(list []*speech_recognition.SpeechRecognitionTagMenu, parentId int) []int {
+	childIds := make([]int, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			childIds = append(childIds, v.SpeechRecognitionTagMenuId)
+			ids := GetSpeechRecognitionTagMenuChildrenRecursive(list, v.SpeechRecognitionTagMenuId)
+			if len(ids) > 0 {
+				childIds = append(childIds, ids...)
+			}
+		}
+	}
+	return childIds
+}
+
+// CheckSpeechRecognitionMenuRemove 校验是否可删除目录
+func CheckSpeechRecognitionMenuRemove(menuId int) (result *speech_recognition.SpeechRecognitionMenuRemoveCheckResp, menuIds []int, err error) {
+	menuOb := new(speech_recognition.SpeechRecognitionMenu)
+	result = new(speech_recognition.SpeechRecognitionMenuRemoveCheckResp)
+
+	// 递归获取目录的子目录集合
+	menus, e := menuOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+	if e != nil {
+		err = fmt.Errorf("获取语音识别目录列表失败, err: %s", e.Error())
+		return
+	}
+	childIds := GetSpeechRecognitionMenuChildrenRecursive(menus, menuId)
+	menuIds = make([]int, 0)
+	menuIds = append(menuIds, menuId)
+	if len(childIds) > 0 {
+		menuIds = append(menuIds, childIds...)
+	}
+
+	// 校验目录下(包含子目录)是否有内容
+	speechOb := new(speech_recognition.SpeechRecognition)
+	cond := fmt.Sprintf(` AND %s IN (%s)`, speech_recognition.SpeechRecognitionCols.MenuId, utils.GetOrmInReplace(len(menuIds)))
+	pars := make([]interface{}, 0)
+	pars = append(pars, menuIds)
+	count, e := speechOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		err = fmt.Errorf("获取目录下的语音识别数失败, err: %s", e.Error())
+		return
+	}
+	if count > 0 {
+		result.CheckResult = SpeechMenuCheckRemoveTypeRefused
+		result.Tips = "该分类关联转写文件,删除失败!"
+		return
+	}
+
+	// 如果无语音识别, 但存在子目录, 则返回第二种提示
+	if len(childIds) > 0 {
+		result.CheckResult = SpeechMenuCheckRemoveTypeWarning
+		result.Tips = "该分类包含内容为空的子分类"
+		return
+	}
+
+	// 通过校验
+	result.Tips = "校验通过,可以删除"
+	return
+}
+
+// CheckSpeechRecognitionTagMenuRemove 校验是否可删除标签目录
+func CheckSpeechRecognitionTagMenuRemove(menuId int) (result *speech_recognition.SpeechRecognitionMenuRemoveCheckResp, menuIds []int, err error) {
+	menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+	result = new(speech_recognition.SpeechRecognitionMenuRemoveCheckResp)
+
+	// 递归获取目录的子目录集合
+	menus, e := menuOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+	if e != nil {
+		err = fmt.Errorf("获取标签目录列表失败, err: %s", e.Error())
+		return
+	}
+	childIds := GetSpeechRecognitionTagMenuChildrenRecursive(menus, menuId)
+	menuIds = make([]int, 0)
+	menuIds = append(menuIds, menuId)
+	if len(childIds) > 0 {
+		menuIds = append(menuIds, childIds...)
+	}
+
+	// 校验目录下(包含子目录)是否有内容
+	tagOb := new(speech_recognition.SpeechRecognitionTag)
+	cond := fmt.Sprintf(` AND %s IN (%s)`, speech_recognition.SpeechRecognitionTagCols.MenuId, utils.GetOrmInReplace(len(menuIds)))
+	pars := make([]interface{}, 0)
+	pars = append(pars, menuIds)
+	count, e := tagOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		err = fmt.Errorf("获取目录下的标签数失败, err: %s", e.Error())
+		return
+	}
+	if count > 0 {
+		result.CheckResult = SpeechMenuCheckRemoveTypeRefused
+		result.Tips = "该分类关联标签,删除失败!"
+		return
+	}
+
+	// 如果无内容, 但存在子目录, 则返回第二种提示
+	if len(childIds) > 0 {
+		result.CheckResult = SpeechMenuCheckRemoveTypeWarning
+		result.Tips = "该分类包含内容为空的子分类"
+		return
+	}
+
+	// 通过校验
+	result.Tips = "校验通过,可以删除"
+	return
+}
+
+// GetSpeechRecognitionMenuPathRecursive 根据子目录递归获取目录树
+func GetSpeechRecognitionMenuPathRecursive(list []*speech_recognition.SpeechRecognitionMenu, menuId int) []*speech_recognition.SpeechRecognitionMenuItem {
+	res := make([]*speech_recognition.SpeechRecognitionMenuItem, 0)
+	for _, v := range list {
+		if v.SpeechRecognitionMenuId == menuId {
+			t := new(speech_recognition.SpeechRecognitionMenuItem)
+			t.UniqueCode = v.UniqueCode
+			t.MenuId = v.SpeechRecognitionMenuId
+			t.MenuName = v.MenuName
+			t.ParentId = v.ParentId
+			t.Level = v.Level
+			t.Sort = v.Sort
+			t.CreateTime = utils.TimeTransferString(utils.FormatDateTime, v.CreateTime)
+			if v.ParentId > 0 {
+				res = GetSpeechRecognitionMenuPathRecursive(list, v.ParentId)
+			}
+			res = append(res, t)
+		}
+	}
+	return res
+}

+ 90 - 0
services/tencent_asr.go

@@ -0,0 +1,90 @@
+package services
+
+import (
+	"fmt"
+
+	asr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr/v20190614"
+	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
+	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
+	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
+)
+
+type TencentRecTaskReq struct {
+	SecretId    string
+	SecretKey   string
+	FileUrl     string
+	CallbackUrl string
+}
+
+// TencentCreateRecTask 创建录音文件识别
+func TencentCreateRecTask(req TencentRecTaskReq) (taskId int, err error) {
+	if req.SecretId == "" || req.SecretKey == "" || req.FileUrl == "" {
+		err = fmt.Errorf("参数有误, SecretId: %s, SecretKey: %s, FileUrl: %s", req.SecretId, req.SecretKey, req.FileUrl)
+		return
+	}
+	credential := common.NewCredential(
+		req.SecretId,
+		req.SecretKey,
+	)
+	cpf := profile.NewClientProfile()
+	cpf.HttpProfile.Endpoint = "asr.tencentcloudapi.com"
+	client, e := asr.NewClient(credential, "", cpf)
+	if e != nil {
+		err = fmt.Errorf("asr NewClient err: %s", e.Error())
+		return
+	}
+
+	// 实例化一个请求对象, 具体参数看文档https://cloud.tencent.com/document/product/1093/37823
+	request := asr.NewCreateRecTaskRequest()
+	request.EngineModelType = common.StringPtr("16k_zh")
+	request.ChannelNum = common.Uint64Ptr(1)
+	request.ResTextFormat = common.Uint64Ptr(2)
+	request.SourceType = common.Uint64Ptr(0)
+	request.Url = common.StringPtr(req.FileUrl)
+	if req.CallbackUrl != "" {
+		request.CallbackUrl = common.StringPtr(req.CallbackUrl)
+	}
+	request.SpeakerDiarization = common.Int64Ptr(1)
+	request.SpeakerNumber = common.Int64Ptr(0)
+	request.ConvertNumMode = common.Int64Ptr(0)
+	request.FilterDirty = common.Int64Ptr(2)
+	request.FilterPunc = common.Int64Ptr(0)
+	request.FilterModal = common.Int64Ptr(2)
+
+	// 返回的resp是一个CreateRecTaskResponse的实例
+	response, e := client.CreateRecTask(request)
+	if _, ok := e.(*errors.TencentCloudSDKError); ok {
+		err = fmt.Errorf("asr CreateRecTask err: %s", e.Error())
+		return
+	}
+	// 唯一标识, 回调会用到
+	taskId = int(*response.Response.Data.TaskId)
+	//fmt.Printf("%s", response.ToJsonString())
+	return
+}
+
+// TencentRecTaskCallback 录音识别回调参数
+type TencentRecTaskCallback struct {
+	Code         int     `description:"任务状态码,0为成功,其他:失败;详见https://cloud.tencent.com/document/product/1093/52632"`
+	Message      string  `description:"失败原因文字描述,成功时此值为空"`
+	RequestId    uint64  `json:"requestId" description:"任务唯一标识,与录音识别请求中返回的TaskId一致。数据格式必须设置为Uint64"`
+	Appid        uint64  `description:"腾讯云应用ID"`
+	ProjectId    int     `json:"projectid" description:"腾讯云项目ID"`
+	AudioUrl     string  `json:"audioUrl" description:"语音URL,如创建任务时为上传数据的方式,则不包含该字段"`
+	Text         string  `description:"识别出的结果文本"`
+	ResultDetail string  `json:"resultDetail" description:"包含详细识别结果,如创建任务时 ResTextFormat 为0,则不包含该字段"`
+	AudioTime    float64 `json:"audioTime" description:"语音总时长"`
+}
+
+// TencentRecTaskCallbackResp 录音识别回调响应体
+type TencentRecTaskCallbackResp struct {
+	Code    int    `description:"0为成功, 其他值代表失败"`
+	Message string `description:"失败原因说明"`
+}
+
+// TencentRecTaskSentenceDetail 录音识别相应结果-单句详情
+type TencentRecTaskSentenceDetail struct {
+	FinalSentence string `description:"单句最终结果"`
+	StartMs       int    `description:"单句开始时间(毫秒)"`
+	EndMs         int    `description:"单句结束时间(毫秒)"`
+}

BIN
static/SimHei.ttf


+ 45 - 0
utils/common.go

@@ -2301,6 +2301,7 @@ func GetColorMap() map[int]string {
 	return colorMap
 }
 
+<<<<<<< HEAD
 // 检查src属性是否以http或data:image开头
 func isValidSrc(src string) bool {
 	// 使用Parse函数解析URL
@@ -2397,4 +2398,48 @@ func customXssPolicy() (p *bluemonday.Policy) {
 	p.AllowAttrs("allow").Matching(regexp.MustCompile(`[a-z; -]*`)).OnElements("iframe")
 	p.AllowAttrs("allowfullscreen").OnElements("iframe")
 	return
+=======
+// SecondsToHHMMSS 秒转HH:MM:SS
+func SecondsToHHMMSS(seconds int) string {
+	duration := time.Duration(seconds) * time.Second
+	hours := int(duration.Hours())
+	minutes := int(duration.Minutes()) % 60
+	seconds = int(duration.Seconds()) % 60
+
+	if hours > 0 {
+		return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
+	} else {
+		return fmt.Sprintf("%02d:%02d", minutes, seconds)
+	}
+}
+
+// MillisecondsToHHMMSS 毫秒转HH:MM:SS
+func MillisecondsToHHMMSS(ms int) string {
+	duration := time.Duration(ms) * time.Millisecond
+	hours := int(duration / (time.Hour))
+	minutes := int((duration % (time.Hour)) / time.Minute)
+	seconds := int((duration % time.Hour % time.Minute) / time.Second)
+
+	// 将整数小时、分钟和秒数转换为字符串,并添加冒号分隔符
+	hourStr := strconv.Itoa(hours)
+	minuteStr := strconv.Itoa(minutes)
+	secondStr := strconv.Itoa(seconds)
+
+	// 根据不足两位数的情况补零
+	if len(hourStr) == 1 {
+		hourStr = "0" + hourStr
+	}
+	if len(minuteStr) == 1 {
+		minuteStr = "0" + minuteStr
+	}
+	if len(secondStr) == 1 {
+		secondStr = "0" + secondStr
+	}
+
+	return hourStr + ":" + minuteStr + ":" + secondStr
+}
+
+func ByteToMB(byteCount int) float64 {
+	return float64(byteCount) / (1024 * 1024)
+>>>>>>> custom
 }