浏览代码

Merge branch 'feature/eta_1.6.9' into debug

hsun 1 年之前
父节点
当前提交
334d7d3c21

+ 639 - 55
controllers/speech_recognition/speech_recognition.go

@@ -6,9 +6,10 @@ import (
 	"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"
-	"strconv"
+	"strings"
 	"time"
 )
 
@@ -27,61 +28,235 @@ type SpeechRecognitionCommonController struct {
 // @Success 200 string "操作成功"
 // @router /rec_task/callback [post]
 func (this *SpeechRecognitionCommonController) RecTaskCallback() {
-	utils.FileLog.Info("RecTaskCallback, -1")
 	// 此接口返回指定响应体
 	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)
 	}()
-	var req services.TencentRecTaskCallback
-
-	utils.FileLog.Info("RecTaskCallback, 0")
 
-	code, _ := this.GetInt("code", 0)
-	req.Code = code
+	code, _ := this.GetInt("code", -1)
 	requestId, _ := this.GetInt("requestId", 0)
-	req.RequestId = uint64(requestId)
-	text := this.GetString("text")
-	req.Text = text
-	resultDetail := this.GetString("resultDetail")
-	req.ResultDetail = resultDetail
-
-	//utils.FileLog.Info("RecTaskCallback, body: " + string(this.Ctx.Input.RequestBody))
-	//if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
-	//	br.Code = 403
-	//	br.Message = "参数解析失败"
-	//	return
-	//}
-	utils.FileLog.Info("RecTaskCallback, 1")
-
-	// TODO:处理回调结果
-	apiLog := new(speech_recognition.SpeechRecognitionApiLog)
-	apiLog.RequestId = strconv.Itoa(int(req.RequestId))
-	j, e := json.Marshal(req)
+	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 {
-		utils.FileLog.Info("RecTaskCallback, 2 " + e.Error())
+		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
 	}
-	apiLog.RequestResult = string(j)
-	apiLog.CreateTime = time.Now().Local()
+	contents := make([]*speech_recognition.SpeechRecognitionContent, 0)
+	sorts := 0 // API返回的结果本身是已排过序的
+	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)
+	}
+
+	speechItem.State = speech_recognition.SpeechRecognitionStateSuccess
+	speechItem.ModifyTime = nowTime
+	speechCols := []string{speech_recognition.SpeechRecognitionCols.State, speech_recognition.SpeechRecognitionCols.ModifyTime}
+
+	apiLog.RequestCode = code
+	apiLog.RequestResult = detail
 	apiLog.ModifyTime = time.Now().Local()
-	if e := apiLog.Create(); e != nil {
-		br.Code = 403
-		br.Message = "获取回调结果失败"
+	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
+		}
+	}
+
+	speeches := make([]*speech_recognition.SpeechRecognition, 0)
+	nowTime := time.Now().Local()
+	for _, v := range req.Files {
+		t := new(speech_recognition.SpeechRecognition)
+		t.FileName = v.FileName
+		t.ResourceUrl = v.ResourceUrl
+		t.MenuId = req.MenuId
+		// TODO:所属目录位置
+		t.SysUserId = sysUser.AdminId
+		t.SysUserName = sysUser.RealName
+		t.State = speech_recognition.SpeechRecognitionStateWait
+		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(` %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
 	}
-	utils.FileLog.Info("RecTaskCallback, 3")
+	resp := make([]*speech_recognition.SpeechRecognitionItem, 0)
+	for _, v := range list {
+		resp = append(resp, speech_recognition.FormatSpeechRecognition2Item(v))
+	}
 
-	br.Code = 0
-	br.Message = "success"
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
 }
 
-// Save
-// @Title 保存
-// @Description 保存
+// TODO:Save
+// @Title 保存内容
+// @Description 保存内容
 // @Param	request	body speech_recognition.SpeechRecognitionSaveReq true "type json string"
 // @Success 200 string "操作成功"
 // @router /save [post]
-func (this *SpeechRecognitionCommonController) Save() {
+func (this *SpeechRecognitionController) Save() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
 		if br.ErrMsg == "" {
@@ -90,13 +265,13 @@ func (this *SpeechRecognitionCommonController) Save() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-	//sysUser := this.SysUser
-	//if sysUser == nil {
-	//	br.Msg = "请登录"
-	//	br.ErrMsg = "请登录,SysUser Is Empty"
-	//	br.Ret = 408
-	//	return
-	//}
+	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 = "参数有误"
@@ -104,27 +279,436 @@ func (this *SpeechRecognitionCommonController) Save() {
 		return
 	}
 
-	conf, e := models.GetBusinessConf()
+	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()
+		br.ErrMsg = "获取转写文件失败, Err: " + e.Error()
 		return
 	}
 
-	var taskReq services.TencentRecTaskReq
-	taskReq.FileUrl = req.FileName
-	taskReq.SecretId = conf[models.BusinessConfTencentApiSecretId]
-	taskReq.SecretKey = conf[models.BusinessConfTencentApiSecretKey]
-	taskReq.CallbackUrl = conf[models.BusinessConfTencentApiRecTaskCallbackUrl]
-	taskId, e := services.TencentCreateRecTask(taskReq)
+	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 = fmt.Sprintf("TencentCreateRecTask err: %s", e.Error())
+		br.ErrMsg = "获取转写文件失败, Err: " + e.Error()
+		return
+	}
+
+	// 重名校验
+	{
+		cond := fmt.Sprintf(` %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.Data = taskId
 	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() {
+		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 = "操作成功"
+}
+
+// TODO:List
+// @Title 语音识别列表
+// @Description 语音识别列表
+// @Param   ParentId  query  int  false  "父级ID"
+// @Success 200 {object} speech_recognition.SpeechRecognitionMenuNodeItem
+// @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
+	}
+
+	// TODO:标签列表
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// TODO:Detail
+// @Title 语音识别详情
+// @Description 语音识别详情
+// @Param   SpeechRecognitionId  query  int  true  "语音识别ID"
+// @Success 200 {object} speech_recognition.SpeechRecognitionMenuNodeItem
+// @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
+	}
+
+	// TODO:标签详情
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// TODO:Move
+// @Title 移动语音识别/目录
+// @Description 移动标签/目录
+// @Param	request	body speech_recognition.SpeechRecognitionTagRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /move [post]
+func (this *SpeechRecognitionController) 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
+	}
+
+	// TODO:移动语音识别/目录
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+//// TestConvert
+//// @Title 转写测试
+//// @Description 转写测试
+//// @Param	request	body speech_recognition.SpeechRecognitionSaveReq true "type json string"
+//// @Success 200 string "操作成功"
+//// @router /test_convert [post]
+//func (this *SpeechRecognitionCommonController) TestConvert() {
+//	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
+//	}
+//
+//	conf, e := models.GetBusinessConf()
+//	if e != nil {
+//		br.Msg = "操作失败"
+//		br.ErrMsg = "获取配置失败, Err: " + e.Error()
+//		return
+//	}
+//
+//	var taskReq services.TencentRecTaskReq
+//	taskReq.FileUrl = req.FileName
+//	taskReq.SecretId = conf[models.BusinessConfTencentApiSecretId]
+//	taskReq.SecretKey = conf[models.BusinessConfTencentApiSecretKey]
+//	taskReq.CallbackUrl = conf[models.BusinessConfTencentApiRecTaskCallbackUrl]
+//	taskId, e := services.TencentCreateRecTask(taskReq)
+//	if e != nil {
+//		br.Msg = "操作失败"
+//		br.ErrMsg = fmt.Sprintf("TencentCreateRecTask err: %s", e.Error())
+//		return
+//	}
+//
+//	br.Data = taskId
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "操作成功"
+//}

+ 410 - 0
controllers/speech_recognition/speech_recognition_menu.go

@@ -0,0 +1,410 @@
+package speech_recognition
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/speech_recognition"
+	"eta/eta_api/utils"
+	"fmt"
+	"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
+	{
+		if req.ParentId > 0 {
+			parentMenu, e := menuOb.GetItemById(req.ParentId)
+			if e != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取父级目录失败, Err: " + e.Error()
+				return
+			}
+			level += parentMenu.Level
+		}
+	}
+
+	menuOb.MenuName = req.MenuName
+	menuOb.ParentId = req.ParentId
+	menuOb.Level = level
+	menuOb.CreateTime = time.Now().Local()
+	menuOb.ModifyTime = time.Now().Local()
+	e := menuOb.Create()
+	if 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
+	}
+
+	// 校验目录下是否有内容
+	{
+		speechOb := new(speech_recognition.SpeechRecognition)
+		cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionCols.MenuId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.MenuId)
+		count, e := speechOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取目录下的语音识别数失败, Err: " + e.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "该分类关联转写文件,删除失败!"
+			return
+		}
+
+		cond = fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionMenuCols.ParentId)
+		pars = make([]interface{}, 0)
+		pars = append(pars, req.MenuId)
+		count, e = menuOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取下级目录数失败, Err: " + e.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "该分类含下级目录,删除失败!"
+			return
+		}
+	}
+
+	if e = menuItem.Del(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "删除目录失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Tree
+// @Title 目录树
+// @Description 目录树
+// @Param   ParentId  query  int  false  "父级ID"
+// @Success 200 {object} speech_recognition.SpeechRecognitionMenuNodeItem
+// @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
+	}
+	// 前端采用懒加载, 所以只查询目录及当前目录下的语音识别
+	parentId, _ := this.GetInt("ParentId")
+
+	menus := make([]*speech_recognition.SpeechRecognitionMenu, 0)
+	{
+		menuOb := new(speech_recognition.SpeechRecognitionMenu)
+		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
+	}
+
+	resp := make([]*speech_recognition.SpeechRecognitionMenuNodeItem, 0)
+	if len(menus) == 0 {
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+	menuIds := make([]int, 0)
+	for _, m := range menus {
+		menuIds = append(menuIds, m.SpeechRecognitionMenuId)
+	}
+
+	// 子目录
+	childMenus := make([]*speech_recognition.SpeechRecognitionMenu, 0)
+	{
+		menuOb := new(speech_recognition.SpeechRecognitionMenu)
+		cond := fmt.Sprintf(` AND %s IN (%s)`, speech_recognition.SpeechRecognitionMenuCols.ParentId, utils.GetOrmInReplace(len(menuIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, menuIds)
+		list, e := menuOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC, %s ASC", speech_recognition.SpeechRecognitionMenuCols.Sort, speech_recognition.SpeechRecognitionMenuCols.ParentId))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取子目录列表失败, Err: " + e.Error()
+			return
+		}
+		childMenus = list
+	}
+	menuChildren := make(map[int][]*speech_recognition.SpeechRecognitionMenuNodeItem)
+	for _, m := range childMenus {
+		if menuChildren[m.ParentId] == nil {
+			menuChildren[m.ParentId] = make([]*speech_recognition.SpeechRecognitionMenuNodeItem, 0)
+		}
+		menuChildren[m.ParentId] = append(menuChildren[m.ParentId], &speech_recognition.SpeechRecognitionMenuNodeItem{
+			NodeType:   speech_recognition.SpeechRecognitionMenuNodeTypeDefault,
+			MenuId:     m.SpeechRecognitionMenuId,
+			MenuName:   m.MenuName,
+			ParentId:   m.ParentId,
+			Sort:       m.Sort,
+			CreateTime: utils.TimeTransferString(utils.FormatDateTime, m.CreateTime),
+		})
+	}
+
+	// 目录下的语音识别
+	speeches := make([]*speech_recognition.SpeechRecognition, 0)
+	{
+		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)
+		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
+	}
+	menuSpeeches := make(map[int][]*speech_recognition.SpeechRecognitionMenuNodeItem)
+	for _, s := range speeches {
+		if menuSpeeches[s.MenuId] == nil {
+			menuSpeeches[s.MenuId] = make([]*speech_recognition.SpeechRecognitionMenuNodeItem, 0)
+		}
+		menuSpeeches[s.MenuId] = append(menuSpeeches[s.MenuId], &speech_recognition.SpeechRecognitionMenuNodeItem{
+			NodeType:              speech_recognition.SpeechRecognitionMenuNodeTypeSpeech,
+			SpeechRecognitionId:   s.SpeechRecognitionId,
+			SpeechRecognitionName: s.FileName,
+			ParentId:              s.MenuId,
+			Sort:                  s.Sort,
+			CreateTime:            utils.TimeTransferString(utils.FormatDateTime, s.CreateTime),
+		})
+	}
+
+	for _, m := range menus {
+		t := &speech_recognition.SpeechRecognitionMenuNodeItem{
+			NodeType:   speech_recognition.SpeechRecognitionMenuNodeTypeDefault,
+			MenuId:     m.SpeechRecognitionMenuId,
+			MenuName:   m.MenuName,
+			ParentId:   m.ParentId,
+			Sort:       m.Sort,
+			CreateTime: utils.TimeTransferString(utils.FormatDateTime, m.CreateTime),
+			Children:   make([]*speech_recognition.SpeechRecognitionMenuNodeItem, 0),
+		}
+		if menuSpeeches[m.SpeechRecognitionMenuId] != nil {
+			t.Children = append(t.Children, menuSpeeches[m.SpeechRecognitionMenuId]...)
+		}
+		if menuChildren[m.SpeechRecognitionMenuId] != nil {
+			t.Children = append(t.Children, menuChildren[m.SpeechRecognitionMenuId]...)
+		}
+		resp = append(resp, t)
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 329 - 0
controllers/speech_recognition/speech_recognition_tag.go

@@ -0,0 +1,329 @@
+package speech_recognition
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/speech_recognition"
+	"eta/eta_api/utils"
+	"fmt"
+	"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(` %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
+		}
+	}
+
+	tagOb.TagName = req.TagName
+	tagOb.MenuId = req.MenuId
+	// TODO:所属目录位置
+	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.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)
+	_, 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(` %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
+		}
+	}
+
+	tagOb.TagName = req.TagName
+	tagOb.ModifyTime = time.Now().Local()
+	updateCols := []string{speech_recognition.SpeechRecognitionTagCols.TagName, speech_recognition.SpeechRecognitionTagCols.ModifyTime}
+	if e = tagOb.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.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(` %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 = "操作成功"
+}
+
+// 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, "%s")
+		cond = fmt.Sprintf(` %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 = "获取成功"
+}
+
+// Move
+// @Title 移动标签/目录
+// @Description 移动标签/目录
+// @Param	request	body speech_recognition.SpeechRecognitionTagRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /tag/move [post]
+func (this *SpeechRecognitionTagController) 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
+	}
+
+	// TODO:移动标签/目录
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 455 - 0
controllers/speech_recognition/speech_recognition_tag_menu.go

@@ -0,0 +1,455 @@
+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"
+	"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
+	{
+		if req.ParentId > 0 {
+			parentMenu, e := menuOb.GetItemById(req.ParentId)
+			if e != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取父级目录失败, Err: " + e.Error()
+				return
+			}
+			level += parentMenu.Level
+		}
+	}
+
+	menuOb.MenuName = req.MenuName
+	menuOb.ParentId = req.ParentId
+	menuOb.Level = level
+	menuOb.CreateTime = time.Now().Local()
+	menuOb.ModifyTime = time.Now().Local()
+	e := menuOb.Create()
+	if 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
+	}
+
+	// 校验目录下是否有内容
+	{
+		tagOb := new(speech_recognition.SpeechRecognitionTag)
+		cond := fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionTagCols.MenuId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.MenuId)
+		count, e := tagOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取目录下的标签数失败, Err: " + e.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "该分类关联标签,删除失败!"
+			return
+		}
+
+		cond = fmt.Sprintf(` AND %s = ?`, speech_recognition.SpeechRecognitionTagMenuCols.ParentId)
+		pars = make([]interface{}, 0)
+		pars = append(pars, req.MenuId)
+		count, e = menuOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取下级目录数失败, Err: " + e.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "该分类含下级目录,删除失败!"
+			return
+		}
+	}
+
+	if e = menuItem.Del(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "删除目录失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Tree
+// @Title 目录树
+// @Description 目录树
+// @Param   ParentId  query  int  false  "父级ID"
+// @Success 200 {object} speech_recognition.SpeechRecognitionTagMenuNodeItem
+// @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
+	}
+	// 前端采用懒加载, 所以只查询目录及当前目录下的语音识别
+	parentId, _ := this.GetInt("ParentId")
+
+	menus := make([]*speech_recognition.SpeechRecognitionTagMenu, 0)
+	{
+		menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+		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
+	}
+
+	resp := make([]*speech_recognition.SpeechRecognitionTagMenuNodeItem, 0)
+	if len(menus) == 0 {
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+	menuIds := make([]int, 0)
+	for _, m := range menus {
+		menuIds = append(menuIds, m.SpeechRecognitionTagMenuId)
+	}
+
+	// 子目录
+	childMenus := make([]*speech_recognition.SpeechRecognitionTagMenu, 0)
+	{
+		menuOb := new(speech_recognition.SpeechRecognitionTagMenu)
+		cond := fmt.Sprintf(` AND %s IN (%s)`, speech_recognition.SpeechRecognitionTagMenuCols.ParentId, utils.GetOrmInReplace(len(menuIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, menuIds)
+		list, e := menuOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC, %s ASC", speech_recognition.SpeechRecognitionTagMenuCols.Sort, speech_recognition.SpeechRecognitionTagMenuCols.ParentId))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取子目录列表失败, Err: " + e.Error()
+			return
+		}
+		childMenus = list
+	}
+	menuChildren := make(map[int][]*speech_recognition.SpeechRecognitionTagMenuNodeItem)
+	for _, m := range childMenus {
+		if menuChildren[m.ParentId] == nil {
+			menuChildren[m.ParentId] = make([]*speech_recognition.SpeechRecognitionTagMenuNodeItem, 0)
+		}
+		menuChildren[m.ParentId] = append(menuChildren[m.ParentId], &speech_recognition.SpeechRecognitionTagMenuNodeItem{
+			NodeType:   speech_recognition.SpeechRecognitionTagMenuNodeTypeDefault,
+			MenuId:     m.SpeechRecognitionTagMenuId,
+			MenuName:   m.MenuName,
+			ParentId:   m.ParentId,
+			Sort:       m.Sort,
+			CreateTime: utils.TimeTransferString(utils.FormatDateTime, m.CreateTime),
+		})
+	}
+
+	// 目录下的标签
+	tags := make([]*speech_recognition.SpeechRecognitionTag, 0)
+	{
+		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)
+		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
+	}
+	menuTags := make(map[int][]*speech_recognition.SpeechRecognitionTagMenuNodeItem)
+	for _, s := range tags {
+		if menuTags[s.MenuId] == nil {
+			menuTags[s.MenuId] = make([]*speech_recognition.SpeechRecognitionTagMenuNodeItem, 0)
+		}
+		menuTags[s.MenuId] = append(menuTags[s.MenuId], &speech_recognition.SpeechRecognitionTagMenuNodeItem{
+			NodeType:   speech_recognition.SpeechRecognitionTagMenuNodeTypeTag,
+			TagId:      s.SpeechRecognitionTagId,
+			TagName:    s.TagName,
+			ParentId:   s.MenuId,
+			Sort:       s.Sort,
+			CreateTime: utils.TimeTransferString(utils.FormatDateTime, s.CreateTime),
+		})
+	}
+
+	for _, m := range menus {
+		t := &speech_recognition.SpeechRecognitionTagMenuNodeItem{
+			NodeType:   speech_recognition.SpeechRecognitionTagMenuNodeTypeDefault,
+			MenuId:     m.SpeechRecognitionTagMenuId,
+			MenuName:   m.MenuName,
+			ParentId:   m.ParentId,
+			Sort:       m.Sort,
+			CreateTime: utils.TimeTransferString(utils.FormatDateTime, m.CreateTime),
+			Children:   make([]*speech_recognition.SpeechRecognitionTagMenuNodeItem, 0),
+		}
+		if menuTags[m.SpeechRecognitionTagMenuId] != nil {
+			t.Children = append(t.Children, menuTags[m.SpeechRecognitionTagMenuId]...)
+		}
+		if menuChildren[m.SpeechRecognitionTagMenuId] != nil {
+			t.Children = append(t.Children, menuChildren[m.SpeechRecognitionTagMenuId]...)
+		}
+		resp = append(resp, t)
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// List
+// @Title 目录列表
+// @Description 目录列表
+// @Success 200 {object} speech_recognition.SpeechRecognitionTagMenuItem
+// @router /tag/menu/tree [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
+	}
+
+	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 = "获取成功"
+}

+ 165 - 2
models/speech_recognition/speech_recognition.go

@@ -8,6 +8,14 @@ import (
 	"time"
 )
 
+const (
+	SpeechRecognitionFileRemoveFlag = 1 // 文件删除标记
+
+	SpeechRecognitionStateWait    = 1
+	SpeechRecognitionStateSuccess = 2
+	SpeechRecognitionStateFail    = 3
+)
+
 // SpeechRecognition 语音识别主表
 type SpeechRecognition struct {
 	SpeechRecognitionId int       `orm:"column(speech_recognition_id);pk"`
@@ -17,8 +25,11 @@ type SpeechRecognition struct {
 	MenuPath            string    `description:"所属目录位置,例:/一级目录ID/二级目录ID"`
 	SysUserId           int       `description:"创建人ID"`
 	SysUserName         string    `description:"创建人姓名"`
-	State               int       `description:"状态:1-待转换;2-转换中;3-转换完成;4-转换失败"`
+	State               int       `description:"状态:1-待转换;2-转换完成;3-转换失败"`
 	Abstract            string    `description:"摘要,取前几段内容"`
+	Sort                int       `description:"目录下的排序"`
+	FileState           int       `description:"文件(非语音识别)删除状态:0-正常;1-删除(该字段作为软删标识)"`
+	ConvertRemark       string    `description:"转写备注-失败原因"`
 	CreateTime          time.Time `description:"创建时间"`
 	ModifyTime          time.Time `description:"修改时间"`
 }
@@ -33,6 +44,9 @@ var SpeechRecognitionCols = struct {
 	SysUserName         string
 	State               string
 	Abstract            string
+	Sort                string
+	FileState           string
+	ConvertRemark       string
 	CreateTime          string
 	ModifyTime          string
 }{
@@ -45,12 +59,15 @@ var SpeechRecognitionCols = struct {
 	SysUserName:         "sys_user_name",
 	State:               "state",
 	Abstract:            "abstract",
+	Sort:                "sort",
+	FileState:           "file_state",
+	ConvertRemark:       "convert_remark",
 	CreateTime:          "create_time",
 	ModifyTime:          "modify_time",
 }
 
 func (m *SpeechRecognition) TableName() string {
-	return "speech_recognition_id"
+	return "speech_recognition"
 }
 
 func (m *SpeechRecognition) PrimaryId() string {
@@ -154,7 +171,153 @@ func (m *SpeechRecognition) GetPageItemsByCondition(condition string, pars []int
 	return
 }
 
+// SpeechRecognitionItem 语音识别信息
+type SpeechRecognitionItem struct {
+	SpeechRecognitionId int
+	FileName            string `description:"文件名称"`
+	ResourceUrl         string `description:"文件路径"`
+	MenuId              int    `description:"目录ID"`
+	MenuPath            string `description:"所属目录位置,例:/一级目录ID/二级目录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.FileName = origin.FileName
+	item.ResourceUrl = origin.ResourceUrl
+	if origin.FileState == SpeechRecognitionFileRemoveFlag {
+		item.ResourceUrl = ""
+	}
+	item.MenuId = origin.MenuId
+	item.MenuPath = origin.MenuPath
+	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 {
 	FileName string
 }
+
+// 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:"文件地址"`
+}
+
+// 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
+}

+ 19 - 0
models/speech_recognition/speech_recognition_api_log.go

@@ -8,11 +8,28 @@ import (
 	"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:"修改时间"`
@@ -22,6 +39,7 @@ var SpeechRecognitionApiLogCols = struct {
 	Id                  string
 	SpeechRecognitionId string
 	RequestId           string
+	RequestCode         string
 	RequestResult       string
 	CreateTime          string
 	ModifyTime          string
@@ -29,6 +47,7 @@ var SpeechRecognitionApiLogCols = struct {
 	Id:                  "id",
 	SpeechRecognitionId: "speech_recognition_id",
 	RequestId:           "request_id",
+	RequestCode:         "request_code",
 	RequestResult:       "request_result",
 	CreateTime:          "create_time",
 	ModifyTime:          "modify_time",

+ 145 - 0
models/speech_recognition/speech_recognition_content.go

@@ -1,4 +1,149 @@
 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
 }

+ 174 - 0
models/speech_recognition/speech_recognition_menu.go

@@ -1,4 +1,178 @@
 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"`
+	MenuName                string    `description:"目录名称"`
+	ParentId                int       `description:"父级ID"`
+	Level                   int       `description:"目录层级"`
+	Sort                    int       `description:"排序"`
+	CreateTime              time.Time `description:"创建时间"`
+	ModifyTime              time.Time `description:"修改时间"`
+}
+
+var SpeechRecognitionMenuCols = struct {
+	SpeechRecognitionMenuId string
+	MenuName                string
+	ParentId                string
+	Level                   string
+	Sort                    string
+	CreateTime              string
+	ModifyTime              string
+}{
+	SpeechRecognitionMenuId: "speech_recognition_menu_id",
+	MenuName:                "menu_name",
+	ParentId:                "parent_id",
+	Level:                   "level",
+	Sort:                    "sort",
+	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
+}
+
+// SpeechRecognitionMenuNodeItem 语音识别目录节点
+type SpeechRecognitionMenuNodeItem struct {
+	NodeType              int                              `description:"节点类型:0-目录;1-语音识别"`
+	MenuId                int                              `description:"目录ID"`
+	MenuName              string                           `description:"目录名称"`
+	SpeechRecognitionId   int                              `description:"语音识别ID"`
+	SpeechRecognitionName string                           `description:"语音识别名称"`
+	ParentId              int                              `description:"父级ID"`
+	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"`
 }

+ 180 - 0
models/speech_recognition/speech_recognition_tag.go

@@ -1,4 +1,184 @@
 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"`
+	TagName                string    `description:"标签名称"`
+	MenuId                 int       `description:"目录ID"`
+	MenuPath               int       `description:"所属目录位置,例:/一级目录ID/二级目录ID"`
+	Sort                   int       `description:"排序"`
+	CreateTime             time.Time `description:"创建时间"`
+	ModifyTime             time.Time `description:"修改时间"`
+}
+
+var SpeechRecognitionTagCols = struct {
+	SpeechRecognitionTagId string
+	TagName                string
+	MenuId                 string
+	MenuPath               string
+	Sort                   string
+	CreateTime             string
+	ModifyTime             string
+}{
+	SpeechRecognitionTagId: "speech_recognition_tag_id",
+	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 {
+	TagId      int    `description:"标签ID"`
+	TagName    string `description:"标签名称"`
+	MenuId     int    `description:"目录ID"`
+	MenuPath   int    `description:"所属目录位置,例:/一级目录ID/二级目录ID"`
+	Sort       int    `description:"排序"`
+	CreateTime string `description:"创建时间"`
+}
+
+func FormatSpeechRecognitionTag2Item(origin *SpeechRecognitionTag) (item *SpeechRecognitionTagItem) {
+	if origin == nil {
+		return
+	}
+	item = new(SpeechRecognitionTagItem)
+	item.TagId = origin.SpeechRecognitionTagId
+	item.TagName = origin.TagName
+	item.MenuId = origin.MenuId
+	item.MenuPath = origin.MenuPath
+	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"`
 }

+ 134 - 0
models/speech_recognition/speech_recognition_tag_mapping.go

@@ -1,4 +1,138 @@
 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
 }

+ 185 - 0
models/speech_recognition/speech_recognition_tag_menu.go

@@ -1,4 +1,189 @@
 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"`
+	MenuName                   string    `description:"目录名称"`
+	ParentId                   int       `description:"父级ID"`
+	Level                      int       `description:"目录层级"`
+	Sort                       int       `description:"排序"`
+	CreateTime                 time.Time `description:"创建时间"`
+	ModifyTime                 time.Time `description:"修改时间"`
+}
+
+var SpeechRecognitionTagMenuCols = struct {
+	SpeechRecognitionTagMenuId string
+	MenuName                   string
+	ParentId                   string
+	Level                      string
+	Sort                       string
+	CreateTime                 string
+	ModifyTime                 string
+}{
+	SpeechRecognitionTagMenuId: "speech_recognition_tag_menu_id",
+	MenuName:                   "menu_name",
+	ParentId:                   "parent_id",
+	Level:                      "level",
+	Sort:                       "sort",
+	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 {
+	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 {
+	NodeType   int                                 `description:"节点类型:0-目录;1-标签"`
+	MenuId     int                                 `description:"目录ID"`
+	MenuName   string                              `description:"目录名称"`
+	TagId      int                                 `description:"标签ID"`
+	TagName    string                              `description:"标签名称"`
+	ParentId   int                                 `description:"父级ID"`
+	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"`
 }

+ 208 - 1
routers/commentsRouter.go

@@ -6064,7 +6064,79 @@ 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.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: "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: "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: "Move",
+            Router: `/move`,
+            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: "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`,
@@ -6073,6 +6145,141 @@ func init() {
             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: "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: "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: "Move",
+            Router: `/tag/move`,
+            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: "Remove",
+            Router: `/tag/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: "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: "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: "Tree",
+            Router: `/tag/menu/tree`,
+            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: "List",
+            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",

+ 3 - 0
routers/router.go

@@ -361,6 +361,9 @@ func init() {
 			web.NSInclude(
 				&speech_recognition.SpeechRecognitionCommonController{},
 				&speech_recognition.SpeechRecognitionController{},
+				&speech_recognition.SpeechRecognitionMenuController{},
+				&speech_recognition.SpeechRecognitionTagController{},
+				&speech_recognition.SpeechRecognitionTagMenuController{},
 			),
 		),
 	)

+ 112 - 0
services/speech_recognition.go

@@ -1 +1,113 @@
 package services
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/models/speech_recognition"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/utils"
+	"fmt"
+	"strconv"
+	"sync"
+	"time"
+)
+
+// 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.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
+}

+ 7 - 0
services/tecent_asr.go → services/tencent_asr.go

@@ -81,3 +81,10 @@ type TencentRecTaskCallbackResp struct {
 	Code    int    `description:"0为成功, 其他值代表失败"`
 	Message string `description:"失败原因说明"`
 }
+
+// TencentRecTaskSentenceDetail 录音识别相应结果-单句详情
+type TencentRecTaskSentenceDetail struct {
+	FinalSentence string `description:"单句最终结果"`
+	StartMs       int    `description:"单句开始时间(毫秒)"`
+	EndMs         int    `description:"单句结束时间(毫秒)"`
+}