Browse Source

Merge branch 'feature/eta2.3.4_business_user' into debug

xyxie 3 weeks ago
parent
commit
d21c544623

+ 726 - 0
controllers/eta_training_video/eta_training_video.go

@@ -0,0 +1,726 @@
+package eta_training_video
+
+import (
+	"encoding/json"
+	"eta/eta_forum_admin/controllers"
+	"eta/eta_forum_admin/models"
+	"eta/eta_forum_admin/models/eta_training_video"
+	etaTrainingVideoService "eta/eta_forum_admin/services/eta_training_video"
+	"eta/eta_forum_admin/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"html/template"
+	"strings"
+	"time"
+)
+
+// EtaTrainingVideoController ETA培训视频
+type EtaTrainingVideoController struct {
+	controllers.BaseAuthController
+}
+
+// PageList
+// @Title 视频列表-分页
+// @Description 视频列表-分页
+// @Param   PageSize		query   int		true	"每页数据量"
+// @Param   CurrentIndex	query   int		true	"当前页码"
+// @Param   Keyword			query	string	false	"关键词: 视频名称/社会信用码/视频编码"
+// @Param   StartTime		query	string	false	"创建时间区间: 开始时间"
+// @Param   EndTime			query	string	false	"创建时间区间: 结束时间"
+// @Param   ClassifyId		query	int		false	"分类ID"
+// @Param   TagIds			query	string	false	"标签IDs, 英文逗号拼接"
+// @Param   PublishState	query	int		false	"发布状态: 1-未发布; 2-已发布"
+// @Success 200 Ret=200 获取成功
+// @router /page_list [get]
+func (this *EtaTrainingVideoController) PageList() {
+	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
+	}
+
+	// 分页
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	// 筛选项
+	cond := ``
+	pars := make([]interface{}, 0)
+	{
+		// 关键词
+		keyword := this.GetString("Keyword", "")
+		keyword = strings.TrimSpace(keyword)
+		keyword = template.HTMLEscapeString(keyword)
+		if keyword != "" {
+			kw := fmt.Sprint("%", keyword, "%")
+			cond += fmt.Sprintf(` AND (%s LIKE ?)`, eta_training_video.VideoColumns.Title)
+			pars = append(pars, kw)
+		}
+
+		// 创建时间
+		startTime := this.GetString("StartTime", "")
+		endTime := this.GetString("EndTime", "")
+		if startTime != "" && endTime != "" {
+			st := fmt.Sprint(startTime, " 00:00:00")
+			ed := fmt.Sprint(endTime, " 23:59:59")
+			cond += fmt.Sprintf(` AND (%s BETWEEN ? AND ?)`, eta_training_video.VideoColumns.CreateTime)
+			pars = append(pars, st, ed)
+		}
+
+		// 分类ID
+		classifyId, _ := this.GetInt("ClassifyId", 0)
+		if classifyId > 0 {
+			cond += fmt.Sprintf(` AND FIND_IN_SET(?, %s)`, eta_training_video.VideoColumns.ClassifyIds)
+			pars = append(pars, classifyId)
+		}
+
+		// 标签IDs
+		strTagIds := this.GetString("TagIds", "")
+		if strTagIds != "" {
+			strTags := strings.Split(strTagIds, ",")
+			if len(strTags) > 0 {
+				joinArr := make([]string, 0)
+				cond += ` AND (`
+				for _, s := range strTags {
+					joinArr = append(joinArr, fmt.Sprintf(`FIND_IN_SET(?, %s)`, eta_training_video.VideoColumns.TagIds))
+					pars = append(pars, s)
+				}
+				cond += strings.Join(joinArr, ` OR `)
+				cond += `)`
+			} else {
+				cond += ` AND 1=2`
+			}
+		}
+
+		// 发布状态
+		publishState, _ := this.GetInt("PublishState", 0)
+		if publishState > 0 {
+			stateMap := map[int]int{1: 0, 2: 1}
+			cond += fmt.Sprintf(` AND %s = ?`, eta_training_video.VideoColumns.PublishState)
+			pars = append(pars, stateMap[publishState])
+		}
+	}
+
+	// 获取列表
+	videoOb := new(eta_training_video.EtaTrainingVideo)
+	total, e := videoOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取视频总数失败, Err: " + e.Error()
+		return
+	}
+	list, e := videoOb.GetPageItemsByCondition(cond, pars, []string{}, "", startSize, pageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取视频列表失败, Err: " + e.Error()
+		return
+	}
+	items, e := etaTrainingVideoService.FormatVideosToVideoItems(list)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = e.Error()
+		return
+	}
+
+	resp := new(eta_training_video.EtaTrainingVideoListResp)
+	resp.List = items
+	resp.Paging = paging.GetPaging(currentIndex, pageSize, total)
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增视频
+// @Description 新增视频
+// @Param	request	body eta_training_video.EtaTrainingVideoAddReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /add [post]
+func (this *EtaTrainingVideoController) 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 eta_training_video.EtaTrainingVideoAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择视频分类"
+		return
+	}
+	req.Title = strings.TrimSpace(req.Title)
+	if req.Title == "" {
+		br.Msg = "请输入视频名称"
+		return
+	}
+	if req.Introduce != "" {
+		req.Introduce = strings.TrimSpace(req.Introduce)
+		req.Introduce = template.HTMLEscapeString(req.Introduce)
+	}
+	req.CoverImg = strings.TrimSpace(req.CoverImg)
+	if req.CoverImg == "" {
+		br.Msg = "请上传封面图"
+		return
+	}
+	req.VideoUrl = strings.TrimSpace(req.VideoUrl)
+	if req.VideoUrl == "" {
+		br.Msg = "请上传视频"
+		return
+	}
+
+	// 分类
+	strClassifyIds := ""
+	classifyRelates := make([]*eta_training_video.EtaTrainingVideoClassifyRelate, 0)
+	{
+		classifyOB := new(eta_training_video.EtaTrainingVideoClassify)
+		classifies, e := classifyOB.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取分类失败, Err: " + e.Error()
+			return
+		}
+		for _, c := range classifies {
+			if c.EtaTrainingVideoClassifyId != req.ClassifyId {
+				continue
+			}
+			strClassifyIds += fmt.Sprintf("%d,", req.ClassifyId)
+			classifyRelates = append(classifyRelates, &eta_training_video.EtaTrainingVideoClassifyRelate{
+				EtaTrainingVideoClassifyId: c.EtaTrainingVideoClassifyId,
+				ClassifyParentId:           c.ParentId,
+				ClassifyName:               c.ClassifyName,
+			})
+			// 分类所有父级
+			if c.ParentId > 0 {
+				parents := etaTrainingVideoService.GetClassifyParentsRecursive(classifies, c.ParentId)
+				for _, p := range parents {
+					strClassifyIds += fmt.Sprintf("%d,", p.EtaTrainingVideoClassifyId)
+					classifyRelates = append(classifyRelates, &eta_training_video.EtaTrainingVideoClassifyRelate{
+						EtaTrainingVideoClassifyId: p.EtaTrainingVideoClassifyId,
+						ClassifyParentId:           p.ParentId,
+						ClassifyName:               p.ClassifyName,
+					})
+				}
+			}
+		}
+		strClassifyIds = strings.TrimRight(strClassifyIds, ",")
+	}
+
+	// 标签
+	strTagIds := ""
+	tagRelates := make([]*eta_training_video.EtaTrainingVideoTagRelate, 0)
+	if len(req.TagIds) > 0 {
+		tagOB := new(eta_training_video.EtaTrainingVideoTag)
+		tags, e := tagOB.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取标签失败, Err: " + e.Error()
+			return
+		}
+		tagMap := make(map[int]*eta_training_video.EtaTrainingVideoTag)
+		for _, t := range tags {
+			tagMap[t.EtaTrainingVideoTagId] = t
+		}
+		for _, r := range req.TagIds {
+			t := tagMap[r]
+			if t == nil {
+				continue
+			}
+			tagRelates = append(tagRelates, &eta_training_video.EtaTrainingVideoTagRelate{
+				EtaTrainingVideoTagId: r,
+				TagName:               t.TagName,
+			})
+			strTagIds += fmt.Sprintf("%d,", r)
+		}
+		strTagIds = strings.TrimRight(strTagIds, ",")
+	}
+
+	// 视频编码
+	now := time.Now().Local()
+	videoCode := utils.MD5(fmt.Sprint(now.UnixMicro()))
+
+	item := new(eta_training_video.EtaTrainingVideo)
+	item.VideoCode = videoCode
+	item.Title = req.Title
+	item.Introduce = req.Introduce
+	item.ClassifyIds = strClassifyIds
+	item.TagIds = strTagIds
+	item.CoverImg = req.CoverImg
+	item.VideoUrl = req.VideoUrl
+	item.CreateTime = now
+	item.ModifyTime = now
+	if e := item.CreateVideoAndRelates(item, classifyRelates, tagRelates); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "新增视频及关联失败, Err: " + e.Error()
+		return
+	}
+
+	// 操作日志
+	go func() {
+		logItem := new(eta_training_video.EtaTrainingVideoOpLog)
+		logItem.EtaTrainingVideoId = item.EtaTrainingVideoId
+		logItem.SysUserId = sysUser.AdminId
+		logItem.SysRealName = sysUser.RealName
+		logItem.OpType = eta_training_video.VideoOpTypeAdd
+		logItem.OpData = string(this.Ctx.Input.RequestBody)
+		logItem.CreateTime = now
+		_ = logItem.Create()
+	}()
+
+	// 更新标签引用数
+	go func() {
+		tagOB := new(eta_training_video.EtaTrainingVideoTag)
+		for _, t := range tagRelates {
+			_ = tagOB.UpdateVideoTotal(t.EtaTrainingVideoTagId)
+		}
+	}()
+
+	br.Data = item.EtaTrainingVideoId
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Edit
+// @Title 编辑视频
+// @Description 编辑视频
+// @Param	request	body eta_training_video.EtaTrainingVideoEditReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /edit [post]
+func (this *EtaTrainingVideoController) 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 eta_training_video.EtaTrainingVideoEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.VideoId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, 视频ID: %d", req.VideoId)
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择视频分类"
+		return
+	}
+	req.Title = strings.TrimSpace(req.Title)
+	if req.Title == "" {
+		br.Msg = "请输入视频名称"
+		return
+	}
+	if req.Introduce == "" {
+		req.Introduce = strings.TrimSpace(req.Introduce)
+		req.Introduce = template.HTMLEscapeString(req.Introduce)
+	}
+	req.CoverImg = strings.TrimSpace(req.CoverImg)
+	if req.CoverImg == "" {
+		br.Msg = "请上传封面图"
+		return
+	}
+	req.VideoUrl = strings.TrimSpace(req.VideoUrl)
+	if req.VideoUrl == "" {
+		br.Msg = "请上传视频"
+		return
+	}
+
+	ob := new(eta_training_video.EtaTrainingVideo)
+	item, e := ob.GetItemById(req.VideoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "视频不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取视频失败, Err: " + e.Error()
+		return
+	}
+
+	// 分类
+	strClassifyIds := ""
+	classifyRelates := make([]*eta_training_video.EtaTrainingVideoClassifyRelate, 0)
+	{
+		classifyOB := new(eta_training_video.EtaTrainingVideoClassify)
+		classifies, e := classifyOB.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取分类失败, Err: " + e.Error()
+			return
+		}
+		for _, c := range classifies {
+			if c.EtaTrainingVideoClassifyId != req.ClassifyId {
+				continue
+			}
+			strClassifyIds += fmt.Sprintf("%d,", req.ClassifyId)
+			classifyRelates = append(classifyRelates, &eta_training_video.EtaTrainingVideoClassifyRelate{
+				EtaTrainingVideoClassifyId: c.EtaTrainingVideoClassifyId,
+				ClassifyParentId:           c.ParentId,
+				ClassifyName:               c.ClassifyName,
+			})
+			// 分类所有父级
+			if c.ParentId > 0 {
+				parents := etaTrainingVideoService.GetClassifyParentsRecursive(classifies, c.ParentId)
+				for _, p := range parents {
+					strClassifyIds += fmt.Sprintf("%d,", p.EtaTrainingVideoClassifyId)
+					classifyRelates = append(classifyRelates, &eta_training_video.EtaTrainingVideoClassifyRelate{
+						EtaTrainingVideoClassifyId: p.EtaTrainingVideoClassifyId,
+						ClassifyParentId:           p.ParentId,
+						ClassifyName:               p.ClassifyName,
+					})
+				}
+			}
+		}
+		strClassifyIds = strings.TrimRight(strClassifyIds, ",")
+	}
+
+	// 标签
+	strTagIds := ""
+	tagRelates := make([]*eta_training_video.EtaTrainingVideoTagRelate, 0)
+	if len(req.TagIds) > 0 {
+		tagOB := new(eta_training_video.EtaTrainingVideoTag)
+		tags, e := tagOB.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取标签失败, Err: " + e.Error()
+			return
+		}
+		tagMap := make(map[int]*eta_training_video.EtaTrainingVideoTag)
+		for _, t := range tags {
+			tagMap[t.EtaTrainingVideoTagId] = t
+		}
+		for _, r := range req.TagIds {
+			t := tagMap[r]
+			if t == nil {
+				continue
+			}
+			tagRelates = append(tagRelates, &eta_training_video.EtaTrainingVideoTagRelate{
+				EtaTrainingVideoTagId: r,
+				TagName:               t.TagName,
+			})
+			strTagIds += fmt.Sprintf("%d,", r)
+		}
+		strTagIds = strings.TrimRight(strTagIds, ",")
+	}
+
+	now := time.Now().Local()
+	item.Title = req.Title
+	item.Introduce = req.Introduce
+	item.ClassifyIds = strClassifyIds
+	item.TagIds = strTagIds
+	item.CoverImg = req.CoverImg
+	item.VideoUrl = req.VideoUrl
+	item.ModifyTime = now
+	cols := []string{"Title", "Introduce", "ClassifyIds", "TagIds", "CoverImg", "VideoUrl", "ModifyTime"}
+	if e := item.UpdateVideoAndRelates(item, cols, classifyRelates, tagRelates); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新视频及关联失败, Err: " + e.Error()
+		return
+	}
+
+	// 操作日志
+	go func() {
+		logItem := new(eta_training_video.EtaTrainingVideoOpLog)
+		logItem.EtaTrainingVideoId = item.EtaTrainingVideoId
+		logItem.SysUserId = sysUser.AdminId
+		logItem.SysRealName = sysUser.RealName
+		logItem.OpType = eta_training_video.VideoOpTypeEdit
+		logItem.OpData = string(this.Ctx.Input.RequestBody)
+		logItem.CreateTime = now
+		_ = logItem.Create()
+	}()
+
+	// 更新标签引用数
+	go func() {
+		tagOB := new(eta_training_video.EtaTrainingVideoTag)
+		for _, t := range tagRelates {
+			_ = tagOB.UpdateVideoTotal(t.EtaTrainingVideoTagId)
+		}
+	}()
+
+	br.Data = item.EtaTrainingVideoId
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Publish
+// @Title 发布/取消视频
+// @Description 发布/取消视频
+// @Param	request	body eta_training_video.EtaTrainingVideoPublishReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /publish [post]
+func (this *EtaTrainingVideoController) Publish() {
+	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 eta_training_video.EtaTrainingVideoPublishReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.VideoId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprint("参数有误, 视频ID: ", req.VideoId)
+		return
+	}
+	if req.PublishState != 0 && req.PublishState != 1 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprint("参数有误, 发布状态: ", req.PublishState)
+		return
+	}
+
+	ob := new(eta_training_video.EtaTrainingVideo)
+	item, e := ob.GetItemById(req.VideoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "视频不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取ETA培训视频失败, Err: " + e.Error()
+		return
+	}
+	now := time.Now().Local()
+	item.PublishState = req.PublishState
+	item.ModifyTime = now
+	cols := []string{"PublishState", "ModifyTime"}
+	if item.PublishState == 1 {
+		item.PublishTime = now
+		cols = append(cols, "PublishTime")
+	}
+	if e := item.Update(cols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "发布ETA培训视频失败, Err: " + e.Error()
+		return
+	}
+
+	// 操作日志
+	go func() {
+		logItem := new(eta_training_video.EtaTrainingVideoOpLog)
+		logItem.EtaTrainingVideoId = item.EtaTrainingVideoId
+		logItem.SysUserId = sysUser.AdminId
+		logItem.SysRealName = sysUser.RealName
+		opTypeMap := map[int]int{0: eta_training_video.VideoOpTypeCancelPublish, 1: eta_training_video.VideoOpTypePublish}
+		logItem.OpType = opTypeMap[req.PublishState]
+		logItem.OpData = string(this.Ctx.Input.RequestBody)
+		logItem.CreateTime = now
+		_ = logItem.Create()
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Remove
+// @Title 删除视频
+// @Description 删除视频
+// @Param	request	body eta_training_video.EtaTrainingVideoRemoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /remove [post]
+func (this *EtaTrainingVideoController) 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 eta_training_video.EtaTrainingVideoRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.VideoId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprint("参数有误, 视频ID: ", req.VideoId)
+		return
+	}
+
+	ob := new(eta_training_video.EtaTrainingVideo)
+	item, e := ob.GetItemById(req.VideoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取ETA培训视频失败, Err: " + e.Error()
+		return
+	}
+	if e := item.Del(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "删除ETA培训视频失败, Err: " + e.Error()
+		return
+	}
+
+	// 移除分类、标签关联, 更新标签引用数
+	go func() {
+		cr := new(eta_training_video.EtaTrainingVideoClassifyRelate)
+		_ = cr.RemoveRelateByVideoId(item.EtaTrainingVideoId)
+
+		tr := new(eta_training_video.EtaTrainingVideoTagRelate)
+		_ = tr.RemoveRelateByVideoId(item.EtaTrainingVideoId)
+
+		t := new(eta_training_video.EtaTrainingVideoTag)
+		_ = t.RemoveVideoTotalByVideoId(item.EtaTrainingVideoId)
+	}()
+
+	// 操作日志
+	go func() {
+		logItem := new(eta_training_video.EtaTrainingVideoOpLog)
+		logItem.EtaTrainingVideoId = item.EtaTrainingVideoId
+		logItem.SysUserId = sysUser.AdminId
+		logItem.SysRealName = sysUser.RealName
+		logItem.OpType = eta_training_video.VideoOpTypeRemove
+		b, e := json.Marshal(item)
+		if e != nil {
+			return
+		}
+		logItem.OpData = string(b)
+		logItem.CreateTime = time.Now().Local()
+		_ = logItem.Create()
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Detail
+// @Title 视频详情
+// @Description 视频详情
+// @Param   VideoId		query	int		false	"视频ID"
+// @Success 200 Ret=200 操作成功
+// @router /detail [get]
+func (this *EtaTrainingVideoController) 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
+	}
+	videoId, _ := this.GetInt("VideoId", 0)
+	if videoId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprint("参数有误, 视频ID: ", videoId)
+		return
+	}
+
+	ob := new(eta_training_video.EtaTrainingVideo)
+	item, e := ob.GetItemById(videoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "视频不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取ETA培训视频失败, Err: " + e.Error()
+		return
+	}
+
+	list := make([]*eta_training_video.EtaTrainingVideo, 0)
+	list = append(list, item)
+	formats, e := etaTrainingVideoService.FormatVideosToVideoItems(list)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "格式化视频信息失败, Err: " + e.Error()
+		return
+	}
+	result := new(eta_training_video.EtaTrainingVideoItem)
+	if len(formats) > 0 {
+		result = formats[0]
+	}
+
+	br.Data = result
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 404 - 0
controllers/eta_training_video/eta_training_video_classify.go

@@ -0,0 +1,404 @@
+package eta_training_video
+
+import (
+	"encoding/json"
+	"eta/eta_forum_admin/controllers"
+	"eta/eta_forum_admin/models"
+	"eta/eta_forum_admin/models/eta_training_video"
+	etaTrainingVideoService "eta/eta_forum_admin/services/eta_training_video"
+	"eta/eta_forum_admin/utils"
+	"fmt"
+	"html/template"
+	"sort"
+	"strings"
+	"time"
+)
+
+// EtaTrainingVideoClassifyController ETA培训视频分类
+type EtaTrainingVideoClassifyController struct {
+	controllers.BaseAuthController
+}
+
+// Tree
+// @Title 分类树
+// @Description 分类树
+// @Param   Keyword			query	string	false	"关键词: 分类名称"
+// @Success 200 Ret=200 获取成功
+// @router /classify/tree [get]
+func (this *EtaTrainingVideoClassifyController) 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
+	}
+
+	resp := new(eta_training_video.EtaTrainingVideoClassifyResp)
+	resp.List = make([]*eta_training_video.EtaTrainingVideoClassifyItem, 0)
+
+	cond := ``
+	pars := make([]interface{}, 0)
+	// 关键词
+	keyword := this.GetString("Keyword", "")
+	keyword = strings.TrimSpace(keyword)
+	keyword = template.HTMLEscapeString(keyword)
+
+	// 获取所有分类
+	ob := new(eta_training_video.EtaTrainingVideoClassify)
+	classifies, e := ob.GetItemsByCondition(cond, pars, []string{}, ``)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取分类列表失败, Err: " + e.Error()
+		return
+	}
+
+	list := make([]*eta_training_video.EtaTrainingVideoClassify, 0)
+	if keyword != "" {
+		classifyMap := make(map[int]*eta_training_video.EtaTrainingVideoClassify)
+		parentMap := make(map[int]*eta_training_video.EtaTrainingVideoClassify)
+		existMap := make(map[int]bool)
+
+		for _, m := range classifies {
+			classifyMap[m.EtaTrainingVideoClassifyId] = m
+		}
+		for _, m := range classifies {
+			if m.ParentId > 0 {
+				parentMap[m.EtaTrainingVideoClassifyId] = classifyMap[m.ParentId]
+			}
+		}
+
+		// 遍历菜单, 取出跟关键词匹配的菜单(以后可能会更改成无限级分类, 所以相似度匹配在这里处理)
+		for _, m := range classifies {
+			if !strings.Contains(m.ClassifyName, keyword) {
+				continue
+			}
+			if existMap[m.EtaTrainingVideoClassifyId] {
+				continue
+			}
+			existMap[m.EtaTrainingVideoClassifyId] = true
+			list = append(list, m)
+
+			// 取出关键词所匹配的所有父级菜单
+			if m.ParentId > 0 {
+				parents := etaTrainingVideoService.GetClassifyParentsRecursive(classifies, m.ParentId)
+				for _, p := range parents {
+					if !existMap[p.EtaTrainingVideoClassifyId] {
+						existMap[p.EtaTrainingVideoClassifyId] = true
+						list = append(list, p)
+					}
+				}
+			}
+		}
+
+		sort.Slice(list, func(i, j int) bool {
+			return list[j].Sort > list[i].Sort
+		})
+	} else {
+		list = classifies
+	}
+
+	items := make([]*eta_training_video.EtaTrainingVideoClassifyItem, 0)
+	for _, v := range list {
+		t := &eta_training_video.EtaTrainingVideoClassifyItem{
+			ClassifyId:   v.EtaTrainingVideoClassifyId,
+			ClassifyName: v.ClassifyName,
+			ParentId:     v.ParentId,
+			Sort:         v.Sort,
+			Children:     make([]*eta_training_video.EtaTrainingVideoClassifyItem, 0),
+		}
+		items = append(items, t)
+	}
+
+	// 递归返回树形结构
+	items = etaTrainingVideoService.GetClassifyTreeRecursive(items, 0)
+
+	resp.List = items
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增分类
+// @Description 新增分类
+// @Param	request	body eta_training_video.EtaTrainingVideoClassifyAddReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /classify/add [post]
+func (this *EtaTrainingVideoClassifyController) 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 eta_training_video.EtaTrainingVideoClassifyAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+	nameRune := []rune(req.ClassifyName)
+	if len(nameRune) > 50 {
+		br.Msg = "分类名称不可超过50个字符"
+		return
+	}
+
+	// 重名校验
+	{
+		ob := new(eta_training_video.EtaTrainingVideoClassify)
+		cond := fmt.Sprintf(` AND %s = ? AND %s = ?`, eta_training_video.VideoClassifyColumns.ParentId, eta_training_video.VideoClassifyColumns.ClassifyName)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ParentId, req.ClassifyName)
+		exist, e := ob.GetItemByCondition(cond, pars)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取重名分类失败, Err: " + e.Error()
+			return
+		}
+		if exist != nil {
+			br.Msg = "分类名称已存在"
+			return
+		}
+	}
+
+	item := new(eta_training_video.EtaTrainingVideoClassify)
+	item.ClassifyName = req.ClassifyName
+	item.ParentId = req.ParentId
+	item.SysUserId = sysUser.AdminId
+	item.SysRealName = sysUser.RealName
+	item.CreateTime = time.Now().Local()
+	item.ModifyTime = time.Now().Local()
+	if e := item.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 eta_training_video.EtaTrainingVideoTagEditReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /classify/edit [post]
+func (this *EtaTrainingVideoClassifyController) 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 eta_training_video.EtaTrainingVideoClassifyEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, 分类ID: %d", req.ClassifyId)
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+	nameRune := []rune(req.ClassifyName)
+	if len(nameRune) > 50 {
+		br.Msg = "分类名称不可超过50个字符"
+		return
+	}
+
+	ob := new(eta_training_video.EtaTrainingVideoClassify)
+	item, e := ob.GetItemById(req.ClassifyId)
+	if e != nil {
+		if e.Error() != utils.ErrNoRow() {
+			br.Msg = "分类不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取分类失败, Err: " + e.Error()
+		return
+	}
+	if item.ParentId == req.ParentId && item.ClassifyName == req.ClassifyName {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+
+	// 重名校验
+	{
+		cond := fmt.Sprintf(` AND %s = ? AND %s = ?`, eta_training_video.VideoClassifyColumns.ParentId, eta_training_video.VideoClassifyColumns.ClassifyName)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ParentId, req.ClassifyName)
+		exist, e := ob.GetItemByCondition(cond, pars)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取重名分类失败, Err: " + e.Error()
+			return
+		}
+		if exist != nil && exist.EtaTrainingVideoClassifyId != item.EtaTrainingVideoClassifyId {
+			br.Msg = "分类已存在"
+			return
+		}
+	}
+
+	now := time.Now().Local()
+	item.ClassifyName = req.ClassifyName
+	item.ParentId = req.ParentId
+	item.ModifyTime = now
+	cols := []string{"ClassifyName", "ParentId", "ModifyTime"}
+	if e := item.Update(cols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新分类失败, Err: " + e.Error()
+		return
+	}
+
+	// 更新关联表冗余
+	go func() {
+		relateOB := new(eta_training_video.EtaTrainingVideoClassifyRelate)
+		_ = relateOB.UpdateClassifyInfoByClassifyId(item.EtaTrainingVideoClassifyId, req.ParentId, req.ClassifyName)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Remove
+// @Title 删除分类
+// @Description 删除分类
+// @Param	request	body eta_training_video.EtaTrainingVideoClassifyRemoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /classify/remove [post]
+func (this *EtaTrainingVideoClassifyController) 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 eta_training_video.EtaTrainingVideoClassifyRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, 分类ID: %d", req.ClassifyId)
+		return
+	}
+
+	ob := new(eta_training_video.EtaTrainingVideoClassify)
+	item, e := ob.GetItemById(req.ClassifyId)
+	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
+	}
+
+	// 删除校验
+	{
+		// 子分类
+		childCond := fmt.Sprintf(` AND %s = ?`, eta_training_video.VideoClassifyColumns.ParentId)
+		childPars := make([]interface{}, 0)
+		childPars = append(childPars, item.EtaTrainingVideoClassifyId)
+		childTotal, e := ob.GetCountByCondition(childCond, childPars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取子分类数失败, Err: " + e.Error()
+			return
+		}
+		if childTotal > 0 {
+			br.Msg = "该分类下已关联内容, 不可删除"
+			return
+		}
+
+		// 引用
+		useOB := new(eta_training_video.EtaTrainingVideoClassifyRelate)
+		useCond := fmt.Sprintf(` AND %s = ?`, eta_training_video.VideoClassifyRelateColumns.EtaTrainingVideoClassifyId)
+		usePars := make([]interface{}, 0)
+		usePars = append(usePars, item.EtaTrainingVideoClassifyId)
+		useTotal, e := useOB.GetCountByCondition(useCond, usePars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取分类引用数失败, Err: " + e.Error()
+			return
+		}
+		if useTotal > 0 {
+			br.Msg = "该分类下已关联内容, 不可删除"
+			return
+		}
+	}
+
+	if e := item.Del(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "删除分类失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 346 - 0
controllers/eta_training_video/eta_training_video_tag.go

@@ -0,0 +1,346 @@
+package eta_training_video
+
+import (
+	"encoding/json"
+	"eta/eta_forum_admin/controllers"
+	"eta/eta_forum_admin/models"
+	"eta/eta_forum_admin/models/eta_training_video"
+	"eta/eta_forum_admin/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"html/template"
+	"strings"
+	"time"
+)
+
+// EtaTrainingVideoTagController ETA培训视频标签
+type EtaTrainingVideoTagController struct {
+	controllers.BaseAuthController
+}
+
+// PageList
+// @Title 标签列表-分页
+// @Description 标签列表-分页
+// @Param   PageSize		query   int		true	"每页数据量"
+// @Param   CurrentIndex	query   int		true	"当前页码"
+// @Param   Keyword			query	string	false	"关键词: 标签名称"
+// @Success 200 Ret=200 获取成功
+// @router /tag/page_list [get]
+func (this *EtaTrainingVideoTagController) PageList() {
+	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
+	}
+
+	// 分页
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	cond := ``
+	pars := make([]interface{}, 0)
+	// 关键词
+	keyword := this.GetString("Keyword", "")
+	keyword = strings.TrimSpace(keyword)
+	keyword = template.HTMLEscapeString(keyword)
+	if keyword != "" {
+		kw := fmt.Sprint("%", keyword, "%")
+		cond += fmt.Sprintf(` AND %s LIKE ?`, eta_training_video.VideoTagColumns.TagName)
+		pars = append(pars, kw)
+	}
+
+	// 获取列表
+	tagOB := new(eta_training_video.EtaTrainingVideoTag)
+	total, e := tagOB.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取标签总数失败, Err: " + e.Error()
+		return
+	}
+	list, e := tagOB.GetPageItemsByCondition(cond, pars, []string{}, "", startSize, pageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取标签列表失败, Err: " + e.Error()
+		return
+	}
+	items := make([]*eta_training_video.EtaTrainingVideoTagItem, 0)
+	for _, v := range list {
+		t := new(eta_training_video.EtaTrainingVideoTagItem)
+		t.TagId = v.EtaTrainingVideoTagId
+		t.TagName = v.TagName
+		t.VideoTotal = v.VideoTotal
+		t.CreateTime = utils.TimeTransferString(utils.FormatDateTime, v.CreateTime)
+		t.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime)
+		items = append(items, t)
+	}
+
+	resp := new(eta_training_video.EtaTrainingVideoTagPageListResp)
+	resp.List = items
+	resp.Paging = paging.GetPaging(currentIndex, pageSize, total)
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增标签
+// @Description 新增标签
+// @Param	request	body eta_training_video.EtaTrainingVideoTagAddReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /tag/add [post]
+func (this *EtaTrainingVideoTagController) 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 eta_training_video.EtaTrainingVideoTagAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	req.TagName = strings.TrimSpace(req.TagName)
+	if req.TagName == "" {
+		br.Msg = "请输入标签名称"
+		return
+	}
+	nameRune := []rune(req.TagName)
+	if len(nameRune) > 50 {
+		br.Msg = "标签名称不可超过50个字符"
+		return
+	}
+
+	// 重名校验
+	{
+		item := new(eta_training_video.EtaTrainingVideoTag)
+		cond := fmt.Sprintf(` AND %s = ?`, eta_training_video.VideoTagColumns.TagName)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.TagName)
+		exist, e := item.GetItemByCondition(cond, pars)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取重名标签失败, Err: " + e.Error()
+			return
+		}
+		if exist != nil {
+			br.Msg = "标签名称已存在"
+			return
+		}
+	}
+
+	item := new(eta_training_video.EtaTrainingVideoTag)
+	item.TagName = req.TagName
+	item.SysUserId = sysUser.AdminId
+	item.SysRealName = sysUser.RealName
+	item.CreateTime = time.Now().Local()
+	item.ModifyTime = time.Now().Local()
+	if e := item.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 eta_training_video.EtaTrainingVideoTagEditReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /tag/edit [post]
+func (this *EtaTrainingVideoTagController) 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 eta_training_video.EtaTrainingVideoTagEditReq
+	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("参数有误, 标签ID: %d", req.TagId)
+		return
+	}
+	req.TagName = strings.TrimSpace(req.TagName)
+	if req.TagName == "" {
+		br.Msg = "请输入标签名称"
+		return
+	}
+	nameRune := []rune(req.TagName)
+	if len(nameRune) > 50 {
+		br.Msg = "标签名称不可超过50个字符"
+		return
+	}
+
+	ob := new(eta_training_video.EtaTrainingVideoTag)
+	item, e := ob.GetItemById(req.TagId)
+	if e != nil {
+		if e.Error() != utils.ErrNoRow() {
+			br.Msg = "标签不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取标签失败, Err: " + e.Error()
+		return
+	}
+	if item.TagName == req.TagName {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+
+	// 重名校验
+	{
+		cond := fmt.Sprintf(` AND %s = ?`, eta_training_video.VideoTagColumns.TagName)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.TagName)
+		exist, e := ob.GetItemByCondition(cond, pars)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取重名标签失败, Err: " + e.Error()
+			return
+		}
+		if exist != nil && exist.EtaTrainingVideoTagId != item.EtaTrainingVideoTagId {
+			br.Msg = "标签已存在"
+			return
+		}
+	}
+
+	now := time.Now().Local()
+	item.TagName = req.TagName
+	item.ModifyTime = now
+	cols := []string{"TagName", "ModifyTime"}
+	if e := item.Update(cols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新标签失败, Err: " + e.Error()
+		return
+	}
+
+	// 更新关联表冗余
+	go func() {
+		relateOB := new(eta_training_video.EtaTrainingVideoTagRelate)
+		_ = relateOB.UpdateTagNameByTagId(item.EtaTrainingVideoTagId, req.TagName)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Remove
+// @Title 删除标签
+// @Description 删除标签
+// @Param	request	body eta_training_video.EtaTrainingVideoTagRemoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /tag/remove [post]
+func (this *EtaTrainingVideoTagController) 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 eta_training_video.EtaTrainingVideoTagRemoveReq
+	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.Sprint("参数有误, 标签ID: ", req.TagId)
+		return
+	}
+
+	ob := new(eta_training_video.EtaTrainingVideoTag)
+	item, e := ob.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
+	}
+	if e := item.Del(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "删除标签失败, Err: " + e.Error()
+		return
+	}
+
+	// 移除关联
+	go func() {
+		relateOB := new(eta_training_video.EtaTrainingVideoTagRelate)
+		_ = relateOB.RemoveRelateByTagId(item.EtaTrainingVideoTagId)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 56 - 0
controllers/resource.go

@@ -109,3 +109,59 @@ func (this *ResourceController) ImageUpload() {
 	br.Data = resp
 	return
 }
+
+// @Title 获取STSToken
+// @Description 获取STSToken
+// @Success 200 获取成功
+// @router /oss/get_sts_token [get]
+func (this *ResourceController) OssSTSToken() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	ossClient := services.NewOssClient()
+	if ossClient == nil {
+		br.Msg = "上传失败"
+		br.ErrMsg = "初始化OSS服务失败"
+		return
+	}
+	resp, e := ossClient.GetUploadToken()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取OSS上传Token失败, Err: " + e.Error()
+		return
+	}
+
+	br.Data = resp
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+
+	//source, _ := this.GetInt("StorageSource")
+	//
+	//if source == utils.STORAGESOURCE_OSS {
+	//	resp, err := services.GetOssSTSToken()
+	//	if err != nil {
+	//		br.Msg = "获取失败"
+	//		br.ErrMsg = "获取STSToken失败, Err: " + err.Error()
+	//		return
+	//	}
+	//	br.Data = resp
+	//	br.Msg = "获取成功"
+	//	br.Ret = 200
+	//	br.Success = true
+	//} else if source == utils.STORAGESOURCE_MINIO {
+	//	resp, err := services.GetMinIOSTSToken()
+	//	if err != nil {
+	//		br.Msg = "获取失败"
+	//		br.ErrMsg = "获取STSToken失败, Err: " + err.Error()
+	//		return
+	//	}
+	//	br.Data = resp
+	//	br.Msg = "获取成功"
+	//	br.Ret = 200
+	//	br.Success = true
+	//}
+}

+ 16 - 0
models/db.go

@@ -3,6 +3,7 @@ package models
 import (
 	"eta/eta_forum_admin/models/company"
 	"eta/eta_forum_admin/models/eta_business"
+	"eta/eta_forum_admin/models/eta_training_video"
 	"eta/eta_forum_admin/models/eta_trial"
 	"eta/eta_forum_admin/models/help_doc"
 	"eta/eta_forum_admin/models/system"
@@ -38,6 +39,8 @@ func init() {
 	initEtaVersionUpdateLog()
 	// 帮助文档
 	initHelpDoc()
+	// ETA培训视频相关表
+	initEtaTrainingVideo()
 }
 
 // initChart 图表 数据表
@@ -119,3 +122,16 @@ func initHelpDoc() {
 		new(help_doc.HelpDoc),         //文章
 	)
 }
+
+// initEtaTrainingVideo ETA培训视频相关表
+func initEtaTrainingVideo() {
+	orm.RegisterModel(
+		new(eta_training_video.EtaTrainingVideo),      // 视频表
+		new(eta_training_video.EtaTrainingVideoOpLog), // 视频操作记录表
+		//new(eta_training_video.EtaTrainingVideoViewLog),        // 视频访问记录表
+		new(eta_training_video.EtaTrainingVideoTag),            // 标签表
+		new(eta_training_video.EtaTrainingVideoTagRelate),      // 标签关联表
+		new(eta_training_video.EtaTrainingVideoClassify),       // 分类表
+		new(eta_training_video.EtaTrainingVideoClassifyRelate), // 分类关联表
+	)
+}

+ 303 - 0
models/eta_training_video/eta_training_video.go

@@ -0,0 +1,303 @@
+package eta_training_video
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+const (
+	VideoUnPublish = 0
+	VideoPublished = 1
+)
+
+// EtaTrainingVideo ETA培训视频
+type EtaTrainingVideo struct {
+	EtaTrainingVideoId int       `orm:"column(eta_training_video_id);pk"`
+	VideoCode          string    `description:"视频唯一编码"`
+	Title              string    `description:"视频标题"`
+	Introduce          string    `description:"视频简介"`
+	ClassifyIds        string    `description:"视频分类IDs, 英文逗号拼接"`
+	TagIds             string    `description:"标签IDs, 英文逗号拼接"`
+	CoverImg           string    `description:"封面图"`
+	VideoUrl           string    `description:"视频地址"`
+	PublishState       int       `description:"发布状态:0-未发布; 1-已发布"`
+	PublishTime        time.Time `description:"发布时间"`
+	ViewTotal          int       `description:"访问量"`
+	CreateTime         time.Time `description:"创建时间"`
+	ModifyTime         time.Time `description:"更新时间"`
+}
+
+func (m *EtaTrainingVideo) TableName() string {
+	return "eta_training_video"
+}
+
+func (m *EtaTrainingVideo) PrimaryId() string {
+	return VideoColumns.EtaTrainingVideoId
+}
+
+var VideoColumns = struct {
+	EtaTrainingVideoId string
+	VideoCode          string
+	Title              string
+	Introduce          string
+	ClassifyIds        string
+	TagIds             string
+	CoverImg           string
+	VideoUrl           string
+	PublishState       string
+	PublishTime        string
+	ViewTotal          string
+	CreateTime         string
+	ModifyTime         string
+}{
+	EtaTrainingVideoId: "eta_training_video_id",
+	VideoCode:          "video_code",
+	Title:              "title",
+	Introduce:          "introduce",
+	ClassifyIds:        "classify_ids",
+	TagIds:             "tag_ids",
+	CoverImg:           "cover_img",
+	VideoUrl:           "video_url",
+	PublishState:       "publish_state",
+	PublishTime:        "publish_time",
+	ViewTotal:          "view_total",
+	CreateTime:         "create_time",
+	ModifyTime:         "modify_time",
+}
+
+func (m *EtaTrainingVideo) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.EtaTrainingVideoId = int(id)
+	return
+}
+
+func (m *EtaTrainingVideo) CreateMulti(items []*EtaTrainingVideo) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *EtaTrainingVideo) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *EtaTrainingVideo) 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.EtaTrainingVideoId).Exec()
+	return
+}
+
+func (m *EtaTrainingVideo) GetItemById(id int) (item *EtaTrainingVideo, 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 *EtaTrainingVideo) GetItemByCondition(condition string, pars []interface{}) (item *EtaTrainingVideo, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *EtaTrainingVideo) 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 *EtaTrainingVideo) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*EtaTrainingVideo, 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 *EtaTrainingVideo) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*EtaTrainingVideo, 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
+}
+
+func (m *EtaTrainingVideo) CreateVideoAndRelates(videoItem *EtaTrainingVideo, classifyRelates []*EtaTrainingVideoClassifyRelate, tagRelates []*EtaTrainingVideoTagRelate) (err error) {
+	o := orm.NewOrm()
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	id, e := tx.Insert(videoItem)
+	if e != nil {
+		err = fmt.Errorf("insert video err: %s", e.Error())
+		return
+	}
+	videoItem.EtaTrainingVideoId = int(id)
+
+	if len(classifyRelates) > 0 {
+		for _, cr := range classifyRelates {
+			cr.EtaTrainingVideoId = videoItem.EtaTrainingVideoId
+		}
+		if _, e = tx.InsertMulti(len(classifyRelates), classifyRelates); e != nil {
+			err = fmt.Errorf("insert multi classify relates err: %s", e.Error())
+			return
+		}
+	}
+	if len(tagRelates) > 0 {
+		for _, tr := range tagRelates {
+			tr.EtaTrainingVideoId = videoItem.EtaTrainingVideoId
+		}
+		if _, e = tx.InsertMulti(len(tagRelates), tagRelates); e != nil {
+			err = fmt.Errorf("insert multi tag relates err: %s", e.Error())
+			return
+		}
+	}
+	return
+}
+
+func (m *EtaTrainingVideo) UpdateVideoAndRelates(videoItem *EtaTrainingVideo, updateCols []string, classifyRelates []*EtaTrainingVideoClassifyRelate, tagRelates []*EtaTrainingVideoTagRelate) (err error) {
+	if videoItem.EtaTrainingVideoId <= 0 {
+		return
+	}
+	o := orm.NewOrm()
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	if len(updateCols) > 0 {
+		_, e := tx.Update(videoItem, updateCols...)
+		if e != nil {
+			err = fmt.Errorf("update video err: %s", e.Error())
+			return
+		}
+	}
+
+	// 分类
+	sql := `DELETE FROM eta_training_video_classify_relate WHERE eta_training_video_id = ?`
+	if _, e := tx.Raw(sql, videoItem.EtaTrainingVideoId).Exec(); e != nil {
+		err = fmt.Errorf("clear classify relates err: %s", e.Error())
+		return
+	}
+	if len(classifyRelates) > 0 {
+		for _, cr := range classifyRelates {
+			cr.EtaTrainingVideoId = videoItem.EtaTrainingVideoId
+		}
+		if _, e := tx.InsertMulti(len(classifyRelates), classifyRelates); e != nil {
+			err = fmt.Errorf("insert multi classify relates err: %s", e.Error())
+			return
+		}
+	}
+
+	// 标签
+	sql = `DELETE FROM eta_training_video_tag_relate WHERE eta_training_video_id = ?`
+	if _, e := tx.Raw(sql, videoItem.EtaTrainingVideoId).Exec(); e != nil {
+		err = fmt.Errorf("clear tag relates err: %s", e.Error())
+		return
+	}
+	if len(tagRelates) > 0 {
+		for _, tr := range tagRelates {
+			tr.EtaTrainingVideoId = videoItem.EtaTrainingVideoId
+		}
+		if _, e := tx.InsertMulti(len(tagRelates), tagRelates); e != nil {
+			err = fmt.Errorf("insert multi tag relates err: %s", e.Error())
+			return
+		}
+	}
+	return
+}
+
+// EtaTrainingVideoAddReq 新增视频请求体
+type EtaTrainingVideoAddReq struct {
+	Title      string `description:"视频标题"`
+	Introduce  string `description:"视频简介"`
+	CoverImg   string `description:"封面图"`
+	VideoUrl   string `description:"视频地址"`
+	ClassifyId int    `description:"分类ID"`
+	TagIds     []int  `description:"标签IDs"`
+}
+
+// EtaTrainingVideoEditReq 编辑视频请求体
+type EtaTrainingVideoEditReq struct {
+	VideoId int `description:"视频ID"`
+	EtaTrainingVideoAddReq
+}
+
+// EtaTrainingVideoPublishReq 发布/取消发布视频请求体
+type EtaTrainingVideoPublishReq struct {
+	VideoId      int `description:"视频ID"`
+	PublishState int `description:"发布状态:0-取消发布;1-发布"`
+}
+
+// EtaTrainingVideoRemoveReq 删除视频请求体
+type EtaTrainingVideoRemoveReq struct {
+	VideoId int `description:"视频ID"`
+}
+
+// EtaTrainingVideoListResp 视频分页列表响应体
+type EtaTrainingVideoListResp struct {
+	List   []*EtaTrainingVideoItem `description:"视频列表数据"`
+	Paging *paging.PagingItem      `description:"分页数据"`
+}
+
+// EtaTrainingVideoItem ETA视频信息
+type EtaTrainingVideoItem struct {
+	VideoId      int                           `description:"视频ID"`
+	VideoCode    string                        `description:"视频唯一编码"`
+	Title        string                        `description:"视频标题"`
+	Introduce    string                        `description:"视频简介"`
+	Classify     *EtaTrainingVideoClassifyItem `description:"视频分类"`
+	Tags         []*EtaTrainingVideoTagItem    `description:"视频标签"`
+	CoverImg     string                        `description:"封面图"`
+	VideoUrl     string                        `description:"视频地址"`
+	PublishState int                           `description:"发布状态:0-未发布;1-已发布"`
+	PublishTime  string                        `description:"发布时间"`
+	ViewTotal    int                           `description:"访问量"`
+	CreateTime   string                        `description:"创建时间"`
+	ModifyTime   string                        `description:"更新时间"`
+}

+ 162 - 0
models/eta_training_video/eta_training_video_classify.go

@@ -0,0 +1,162 @@
+package eta_training_video
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// EtaTrainingVideoClassify 视频分类
+type EtaTrainingVideoClassify struct {
+	EtaTrainingVideoClassifyId int       `orm:"column(eta_training_video_classify_id);pk"`
+	ClassifyName               string    `description:"视频分类名称"`
+	ParentId                   int       `description:"父级ID"`
+	Sort                       int       `description:"排序"`
+	SysUserId                  int       `description:"创建人ID"`
+	SysRealName                string    `description:"创建人姓名"`
+	CreateTime                 time.Time `description:"创建时间"`
+	ModifyTime                 time.Time `description:"更新时间"`
+}
+
+func (m *EtaTrainingVideoClassify) TableName() string {
+	return "eta_training_video_classify"
+}
+
+func (m *EtaTrainingVideoClassify) PrimaryId() string {
+	return VideoClassifyColumns.EtaTrainingVideoClassifyId
+}
+
+var VideoClassifyColumns = struct {
+	EtaTrainingVideoClassifyId string
+	ClassifyName               string
+	ParentId                   string
+	Sort                       string
+	SysUserId                  string
+	SysRealName                string
+	CreateTime                 string
+	ModifyTime                 string
+}{
+	EtaTrainingVideoClassifyId: "eta_training_video_classify_id",
+	ClassifyName:               "classify_name",
+	ParentId:                   "parent_id",
+	Sort:                       "sort",
+	SysUserId:                  "sys_user_id",
+	SysRealName:                "sys_real_name",
+	CreateTime:                 "create_time",
+	ModifyTime:                 "modify_time",
+}
+
+func (m *EtaTrainingVideoClassify) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.EtaTrainingVideoClassifyId = int(id)
+	return
+}
+
+func (m *EtaTrainingVideoClassify) CreateMulti(items []*EtaTrainingVideoClassify) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *EtaTrainingVideoClassify) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *EtaTrainingVideoClassify) 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.EtaTrainingVideoClassifyId).Exec()
+	return
+}
+
+func (m *EtaTrainingVideoClassify) GetItemById(id int) (item *EtaTrainingVideoClassify, 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 *EtaTrainingVideoClassify) GetItemByCondition(condition string, pars []interface{}) (item *EtaTrainingVideoClassify, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *EtaTrainingVideoClassify) 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 *EtaTrainingVideoClassify) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*EtaTrainingVideoClassify, 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 *EtaTrainingVideoClassify) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*EtaTrainingVideoClassify, 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
+}
+
+// EtaTrainingVideoClassifyAddReq 新增视频分类请求体
+type EtaTrainingVideoClassifyAddReq struct {
+	ParentId     int    `description:"父级ID"`
+	ClassifyName string `description:"视频分类名称"`
+}
+
+// EtaTrainingVideoClassifyEditReq 编辑视频分类请求体
+type EtaTrainingVideoClassifyEditReq struct {
+	ClassifyId int `description:"视频分类ID"`
+	EtaTrainingVideoClassifyAddReq
+}
+
+// EtaTrainingVideoClassifyRemoveReq 删除视频分类请求体
+type EtaTrainingVideoClassifyRemoveReq struct {
+	ClassifyId int `description:"视频分类ID"`
+}
+
+// EtaTrainingVideoClassifyResp 视频分类响应体
+type EtaTrainingVideoClassifyResp struct {
+	List []*EtaTrainingVideoClassifyItem
+}
+
+// EtaTrainingVideoClassifyItem 视频分类信息
+type EtaTrainingVideoClassifyItem struct {
+	ClassifyId   int    `description:"视频分类ID"`
+	ClassifyName string `description:"视频分类名称"`
+	ParentId     int    `description:"父级ID"`
+	Sort         int    `description:"排序"`
+	Children     []*EtaTrainingVideoClassifyItem
+}

+ 137 - 0
models/eta_training_video/eta_training_video_classify_relate.go

@@ -0,0 +1,137 @@
+package eta_training_video
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+)
+
+// EtaTrainingVideoClassifyRelate 分类关联表
+type EtaTrainingVideoClassifyRelate struct {
+	Id                         int    `orm:"column(id);pk"`
+	EtaTrainingVideoId         int    `description:"视频ID"`
+	EtaTrainingVideoClassifyId int    `description:"分类ID"`
+	ClassifyParentId           int    `description:"分类父级ID"`
+	ClassifyName               string `description:"分类名称"`
+}
+
+func (m *EtaTrainingVideoClassifyRelate) TableName() string {
+	return "eta_training_video_classify_relate"
+}
+
+func (m *EtaTrainingVideoClassifyRelate) PrimaryId() string {
+	return VideoClassifyRelateColumns.Id
+}
+
+var VideoClassifyRelateColumns = struct {
+	Id                         string
+	EtaTrainingVideoId         string
+	EtaTrainingVideoClassifyId string
+	ClassifyParentId           string
+	ClassifyName               string
+}{
+	Id:                         "id",
+	EtaTrainingVideoId:         "eta_training_video_id",
+	EtaTrainingVideoClassifyId: "eta_training_video_classify_id",
+	ClassifyParentId:           "classify_parent_id",
+	ClassifyName:               "classify_name",
+}
+
+func (m *EtaTrainingVideoClassifyRelate) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *EtaTrainingVideoClassifyRelate) CreateMulti(items []*EtaTrainingVideoClassifyRelate) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *EtaTrainingVideoClassifyRelate) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *EtaTrainingVideoClassifyRelate) 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 *EtaTrainingVideoClassifyRelate) GetItemById(id int) (item *EtaTrainingVideoClassifyRelate, 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 *EtaTrainingVideoClassifyRelate) GetItemByCondition(condition string, pars []interface{}) (item *EtaTrainingVideoClassifyRelate, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *EtaTrainingVideoClassifyRelate) 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 *EtaTrainingVideoClassifyRelate) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*EtaTrainingVideoClassifyRelate, 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 *EtaTrainingVideoClassifyRelate) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*EtaTrainingVideoClassifyRelate, 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
+}
+
+// UpdateClassifyInfoByClassifyId 更新分类冗余
+func (m *EtaTrainingVideoClassifyRelate) UpdateClassifyInfoByClassifyId(classifyId, parentId int, classifyName string) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ?, %s = ? WHERE %s = ?`, m.TableName(), VideoClassifyRelateColumns.ClassifyParentId, VideoClassifyRelateColumns.ClassifyName, VideoClassifyRelateColumns.EtaTrainingVideoClassifyId)
+	_, err = o.Raw(sql, parentId, classifyName, classifyId).Exec()
+	return
+}
+
+// RemoveRelateByVideoId 根据视频ID移除关联
+func (m *EtaTrainingVideoClassifyRelate) RemoveRelateByVideoId(video int) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ?`, m.TableName(), VideoClassifyRelateColumns.EtaTrainingVideoId)
+	_, err = o.Raw(sql, video).Exec()
+	return
+}

+ 136 - 0
models/eta_training_video/eta_training_video_op_logs.go

@@ -0,0 +1,136 @@
+package eta_training_video
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	VideoOpTypeAdd = iota + 1
+	VideoOpTypeEdit
+	VideoOpTypePublish
+	VideoOpTypeCancelPublish
+	VideoOpTypeRemove
+)
+
+// EtaTrainingVideoOpLog 视频操作记录表
+type EtaTrainingVideoOpLog struct {
+	Id                 int       `orm:"column(id);pk"`
+	EtaTrainingVideoId int       `description:"视频ID"`
+	SysUserId          int       `description:"操作人ID"`
+	SysRealName        string    `description:"操作人姓名"`
+	OpType             int       `description:"操作类型:1-新增;2-编辑;3-发布;4-取消发布;5-删除"`
+	OpData             string    `description:"操作数据"`
+	CreateTime         time.Time `description:"创建时间"`
+}
+
+func (m *EtaTrainingVideoOpLog) TableName() string {
+	return "eta_training_video_op_log"
+}
+
+func (m *EtaTrainingVideoOpLog) PrimaryId() string {
+	return VideoOpLogsColumns.Id
+}
+
+var VideoOpLogsColumns = struct {
+	Id                 string
+	EtaTrainingVideoId string
+	SysUserId          string
+	SysRealName        string
+	OpType             string
+	OpData             string
+	CreateTime         string
+}{
+	Id:                 "id",
+	EtaTrainingVideoId: "eta_training_video_id",
+	SysUserId:          "sys_user_id",
+	SysRealName:        "sys_real_name",
+	OpType:             "op_type",
+	OpData:             "op_data",
+	CreateTime:         "create_time",
+}
+
+func (m *EtaTrainingVideoOpLog) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *EtaTrainingVideoOpLog) CreateMulti(items []*EtaTrainingVideoOpLog) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *EtaTrainingVideoOpLog) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *EtaTrainingVideoOpLog) 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 *EtaTrainingVideoOpLog) GetItemById(id int) (item *EtaTrainingVideoOpLog, 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 *EtaTrainingVideoOpLog) GetItemByCondition(condition string, pars []interface{}) (item *EtaTrainingVideoOpLog, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *EtaTrainingVideoOpLog) 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 *EtaTrainingVideoOpLog) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*EtaTrainingVideoOpLog, 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 *EtaTrainingVideoOpLog) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*EtaTrainingVideoOpLog, 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
+}

+ 179 - 0
models/eta_training_video/eta_training_video_tag.go

@@ -0,0 +1,179 @@
+package eta_training_video
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+// EtaTrainingVideoTag 视频标签表
+type EtaTrainingVideoTag struct {
+	EtaTrainingVideoTagId int       `orm:"column(eta_training_video_tag_id);pk"`
+	TagName               string    `description:"标签名称"`
+	SysUserId             int       `description:"创建人ID"`
+	SysRealName           string    `description:"创建人姓名"`
+	VideoTotal            int       `description:"关联视频数"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"更新时间"`
+}
+
+func (m *EtaTrainingVideoTag) TableName() string {
+	return "eta_training_video_tag"
+}
+
+func (m *EtaTrainingVideoTag) PrimaryId() string {
+	return VideoTagColumns.EtaTrainingVideoTagId
+}
+
+var VideoTagColumns = struct {
+	EtaTrainingVideoTagId string
+	TagName               string
+	SysUserId             string
+	SysRealName           string
+	VideoTotal            string
+	CreateTime            string
+	ModifyTime            string
+}{
+	EtaTrainingVideoTagId: "eta_training_video_tag_id",
+	TagName:               "tag_name",
+	SysUserId:             "sys_user_id",
+	SysRealName:           "sys_real_name",
+	VideoTotal:            "video_total",
+	CreateTime:            "create_time",
+	ModifyTime:            "modify_time",
+}
+
+func (m *EtaTrainingVideoTag) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.EtaTrainingVideoTagId = int(id)
+	return
+}
+
+func (m *EtaTrainingVideoTag) CreateMulti(items []*EtaTrainingVideoTag) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *EtaTrainingVideoTag) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *EtaTrainingVideoTag) 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.EtaTrainingVideoTagId).Exec()
+	return
+}
+
+func (m *EtaTrainingVideoTag) GetItemById(id int) (item *EtaTrainingVideoTag, 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 *EtaTrainingVideoTag) GetItemByCondition(condition string, pars []interface{}) (item *EtaTrainingVideoTag, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *EtaTrainingVideoTag) 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 *EtaTrainingVideoTag) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*EtaTrainingVideoTag, 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 *EtaTrainingVideoTag) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*EtaTrainingVideoTag, 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
+}
+
+func (m *EtaTrainingVideoTag) UpdateVideoTotal(tagId int) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE eta_training_video_tag SET video_total = (
+				SELECT COUNT(1) FROM eta_training_video_tag_relate WHERE eta_training_video_tag_id = ?
+			) WHERE eta_training_video_tag_id = ?`
+	_, err = o.Raw(sql, tagId, tagId).Exec()
+	return
+}
+
+// RemoveVideoTotalByVideoId 更新标签的视频引用数
+func (m *EtaTrainingVideoTag) RemoveVideoTotalByVideoId(videoId int) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE eta_training_video_tag SET video_total = video_total-1 WHERE eta_training_video_tag_id IN (
+				SELECT DISTINCT eta_training_video_tag_id FROM eta_training_video_tag_relate WHERE eta_training_video_id = ?
+			) AND video_total > 0`
+	_, err = o.Raw(sql, videoId).Exec()
+	return
+}
+
+// EtaTrainingVideoTagAddReq 新增标签请求体
+type EtaTrainingVideoTagAddReq struct {
+	TagName string `description:"标签名称"`
+}
+
+// EtaTrainingVideoTagEditReq 编辑标签请求体
+type EtaTrainingVideoTagEditReq struct {
+	TagId int `description:"标签ID"`
+	EtaTrainingVideoTagAddReq
+}
+
+// EtaTrainingVideoTagRemoveReq 删除标签请求体
+type EtaTrainingVideoTagRemoveReq struct {
+	TagId int `description:"标签ID"`
+}
+
+// EtaTrainingVideoTagPageListResp 标签分页列表响应体
+type EtaTrainingVideoTagPageListResp struct {
+	List   []*EtaTrainingVideoTagItem `description:"标签列表数据"`
+	Paging *paging.PagingItem         `description:"分页数据"`
+}
+
+// EtaTrainingVideoTagItem 标签信息
+type EtaTrainingVideoTagItem struct {
+	TagId      int    `description:"标签ID"`
+	TagName    string `description:"标签名称"`
+	VideoTotal int    `description:"关联视频数"`
+	CreateTime string `description:"创建时间"`
+	ModifyTime string `description:"更新时间"`
+}

+ 142 - 0
models/eta_training_video/eta_training_video_tag_relate.go

@@ -0,0 +1,142 @@
+package eta_training_video
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+)
+
+// EtaTrainingVideoTagRelate 标签关联表
+type EtaTrainingVideoTagRelate struct {
+	Id                    int    `orm:"column(id);pk"`
+	EtaTrainingVideoId    int    `description:"视频ID"`
+	EtaTrainingVideoTagId int    `description:"标签ID"`
+	TagName               string `description:"标签名称"`
+}
+
+func (m *EtaTrainingVideoTagRelate) TableName() string {
+	return "eta_training_video_tag_relate"
+}
+
+func (m *EtaTrainingVideoTagRelate) PrimaryId() string {
+	return VideoTagRelateColumns.Id
+}
+
+var VideoTagRelateColumns = struct {
+	Id                    string
+	EtaTrainingVideoId    string
+	EtaTrainingVideoTagId string
+	TagName               string
+}{
+	Id:                    "id",
+	EtaTrainingVideoId:    "eta_training_video_id",
+	EtaTrainingVideoTagId: "eta_training_video_tag_id",
+	TagName:               "tag_name",
+}
+
+func (m *EtaTrainingVideoTagRelate) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *EtaTrainingVideoTagRelate) CreateMulti(items []*EtaTrainingVideoTagRelate) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *EtaTrainingVideoTagRelate) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *EtaTrainingVideoTagRelate) 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 *EtaTrainingVideoTagRelate) GetItemById(id int) (item *EtaTrainingVideoTagRelate, 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 *EtaTrainingVideoTagRelate) GetItemByCondition(condition string, pars []interface{}) (item *EtaTrainingVideoTagRelate, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *EtaTrainingVideoTagRelate) 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 *EtaTrainingVideoTagRelate) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*EtaTrainingVideoTagRelate, 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 *EtaTrainingVideoTagRelate) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*EtaTrainingVideoTagRelate, 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
+}
+
+// UpdateTagNameByTagId 更新标签名称
+func (m *EtaTrainingVideoTagRelate) UpdateTagNameByTagId(tagId int, tagName string) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ? WHERE %s = ?`, m.TableName(), VideoTagRelateColumns.TagName, VideoTagRelateColumns.EtaTrainingVideoTagId)
+	_, err = o.Raw(sql, tagName, tagId).Exec()
+	return
+}
+
+// RemoveRelateByTagId 根据标签ID移除关联
+func (m *EtaTrainingVideoTagRelate) RemoveRelateByTagId(tagId int) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ?`, m.TableName(), VideoTagRelateColumns.EtaTrainingVideoTagId)
+	_, err = o.Raw(sql, tagId).Exec()
+	return
+}
+
+// RemoveRelateByVideoId 根据视频ID移除关联
+func (m *EtaTrainingVideoTagRelate) RemoveRelateByVideoId(video int) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ?`, m.TableName(), VideoTagRelateColumns.EtaTrainingVideoId)
+	_, err = o.Raw(sql, video).Exec()
+	return
+}

+ 122 - 0
models/eta_training_video/eta_training_video_view_log.go

@@ -0,0 +1,122 @@
+package eta_training_video
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// EtaTrainingVideoViewLog 视频操作记录表
+type EtaTrainingVideoViewLog struct {
+	Id                     int       `orm:"column(id);pk"`
+	EtaTrainingVideoId     int       `description:"视频ID"`
+	EtaBusinessId          int       `description:"商家ID"`
+	EtaBusinessCodeEncrypt string    `description:"商家编码encrypt"`
+	CreateTime             time.Time `description:"创建时间"`
+}
+
+func (m *EtaTrainingVideoViewLog) TableName() string {
+	return "eta_training_video_view_log"
+}
+
+func (m *EtaTrainingVideoViewLog) PrimaryId() string {
+	return VideoViewLogColumns.Id
+}
+
+var VideoViewLogColumns = struct {
+	Id                     string
+	EtaTrainingVideoId     string
+	EtaBusinessId          string
+	EtaBusinessCodeEncrypt string
+	CreateTime             string
+}{
+	Id:                     "id",
+	EtaTrainingVideoId:     "eta_training_video_id",
+	EtaBusinessId:          "eta_business_id",
+	EtaBusinessCodeEncrypt: "eta_business_code_encrypt",
+	CreateTime:             "create_time",
+}
+
+func (m *EtaTrainingVideoViewLog) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *EtaTrainingVideoViewLog) CreateMulti(items []*EtaTrainingVideoViewLog) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *EtaTrainingVideoViewLog) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *EtaTrainingVideoViewLog) 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 *EtaTrainingVideoViewLog) GetItemById(id int) (item *EtaTrainingVideoViewLog, 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 *EtaTrainingVideoViewLog) GetItemByCondition(condition string, pars []interface{}) (item *EtaTrainingVideoViewLog, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *EtaTrainingVideoViewLog) 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 *EtaTrainingVideoViewLog) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*EtaTrainingVideoViewLog, 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 *EtaTrainingVideoViewLog) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*EtaTrainingVideoViewLog, 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
+}

+ 135 - 0
routers/commentsRouter.go

@@ -295,6 +295,132 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoClassifyController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoClassifyController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoClassifyController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/classify/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoClassifyController"],
+        beego.ControllerComments{
+            Method: "Tree",
+            Router: `/classify/tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"],
+        beego.ControllerComments{
+            Method: "PageList",
+            Router: `/page_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"],
+        beego.ControllerComments{
+            Method: "Publish",
+            Router: `/publish`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoTagController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoTagController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/tag/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoTagController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoTagController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/tag/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoTagController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoTagController"],
+        beego.ControllerComments{
+            Method: "PageList",
+            Router: `/tag/page_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoTagController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/eta_training_video:EtaTrainingVideoTagController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/tag/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/help_doc:HelpDocClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers/help_doc:HelpDocClassifyController"],
         beego.ControllerComments{
             Method: "AddClassify",
@@ -808,6 +934,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:ResourceController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:ResourceController"],
+        beego.ControllerComments{
+            Method: "OssSTSToken",
+            Router: `/oss/get_sts_token`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:SysRoleController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:SysRoleController"],
         beego.ControllerComments{
             Method: "SysMenuButtons",

+ 9 - 0
routers/router.go

@@ -10,6 +10,7 @@ package routers
 import (
 	"eta/eta_forum_admin/controllers"
 	"eta/eta_forum_admin/controllers/eta_business"
+	"eta/eta_forum_admin/controllers/eta_training_video"
 	"eta/eta_forum_admin/controllers/help_doc"
 	"github.com/beego/beego/v2/server/web"
 )
@@ -81,6 +82,14 @@ func init() {
 				&controllers.CrmConfigController{},
 			),
 		),
+
+		web.NSNamespace("/eta_training_video",
+			web.NSInclude(
+				&eta_training_video.EtaTrainingVideoController{},
+				&eta_training_video.EtaTrainingVideoClassifyController{},
+				&eta_training_video.EtaTrainingVideoTagController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 108 - 0
services/eta_training_video/eta_training_video.go

@@ -0,0 +1,108 @@
+package eta_training_video
+
+import (
+	"eta/eta_forum_admin/models/eta_training_video"
+	"eta/eta_forum_admin/utils"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+// FormatVideosToVideoItems 格式化视频列表
+func FormatVideosToVideoItems(list []*eta_training_video.EtaTrainingVideo) (items []*eta_training_video.EtaTrainingVideoItem, err error) {
+	items = make([]*eta_training_video.EtaTrainingVideoItem, 0)
+	if len(list) == 0 {
+		return
+	}
+
+	// 获取分类
+	classifyMap := make(map[int]*eta_training_video.EtaTrainingVideoClassify)
+	{
+		classifyOB := new(eta_training_video.EtaTrainingVideoClassify)
+		classifies, e := classifyOB.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取分类列表失败, Err: %s", e.Error())
+			return
+		}
+		for _, v := range classifies {
+			classifyMap[v.EtaTrainingVideoClassifyId] = v
+		}
+	}
+
+	// 获取标签
+	tagMap := make(map[int]*eta_training_video.EtaTrainingVideoTag)
+	{
+		tagOB := new(eta_training_video.EtaTrainingVideoTag)
+		tags, e := tagOB.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取标签列表失败, Err: %s", e.Error())
+			return
+		}
+		for _, v := range tags {
+			tagMap[v.EtaTrainingVideoTagId] = v
+		}
+	}
+
+	for _, v := range list {
+		b := new(eta_training_video.EtaTrainingVideoItem)
+		b.VideoId = v.EtaTrainingVideoId
+		b.VideoCode = v.VideoCode
+		b.Title = v.Title
+		b.Introduce = v.Introduce
+		b.CoverImg = v.CoverImg
+		b.VideoUrl = v.VideoUrl
+		b.PublishState = v.PublishState
+		b.PublishTime = utils.TimeTransferString(utils.FormatDateTime, v.PublishTime)
+		b.ViewTotal = v.ViewTotal
+		b.CreateTime = utils.TimeTransferString(utils.FormatDateTime, v.CreateTime)
+		b.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime)
+
+		// 分类
+		if v.ClassifyIds != "" {
+			strClassifyIdArr := strings.Split(v.ClassifyIds, ",")
+			if len(strClassifyIdArr) > 0 {
+				arr := make([]*eta_training_video.EtaTrainingVideoClassifyItem, 0)
+				for _, s := range strClassifyIdArr {
+					id, _ := strconv.Atoi(s)
+					c := classifyMap[id]
+					if c != nil {
+						arr = append(arr, &eta_training_video.EtaTrainingVideoClassifyItem{
+							ClassifyId:   c.EtaTrainingVideoClassifyId,
+							ClassifyName: c.ClassifyName,
+							ParentId:     c.ParentId,
+							Sort:         c.Sort,
+						})
+					}
+				}
+				tree := GetClassifyTreeRecursive(arr, 0)
+				if len(tree) > 0 {
+					b.Classify = tree[0]
+				}
+			}
+		}
+
+		// 标签
+		if v.TagIds != "" {
+			strTagIdArr := strings.Split(v.TagIds, ",")
+			if len(strTagIdArr) > 0 {
+				b.Tags = make([]*eta_training_video.EtaTrainingVideoTagItem, 0)
+				for _, s := range strTagIdArr {
+					id, _ := strconv.Atoi(s)
+					t := tagMap[id]
+					if t != nil {
+						b.Tags = append(b.Tags, &eta_training_video.EtaTrainingVideoTagItem{
+							TagId:      t.EtaTrainingVideoTagId,
+							TagName:    t.TagName,
+							VideoTotal: t.VideoTotal,
+							CreateTime: utils.TimeTransferString(utils.FormatDateTime, t.CreateTime),
+							ModifyTime: utils.TimeTransferString(utils.FormatDateTime, t.ModifyTime),
+						})
+					}
+				}
+			}
+		}
+
+		items = append(items, b)
+	}
+	return
+}

+ 46 - 0
services/eta_training_video/eta_training_video_classify.go

@@ -0,0 +1,46 @@
+package eta_training_video
+
+import (
+	"eta/eta_forum_admin/models/eta_training_video"
+)
+
+// GetClassifyTreeRecursive 递归菜单树
+func GetClassifyTreeRecursive(list []*eta_training_video.EtaTrainingVideoClassifyItem, parentId int) []*eta_training_video.EtaTrainingVideoClassifyItem {
+	res := make([]*eta_training_video.EtaTrainingVideoClassifyItem, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Children = GetClassifyTreeRecursive(list, v.ClassifyId)
+			res = append(res, v)
+		}
+	}
+	return res
+}
+
+// GetClassifyChildrenIdsRecursive 遍历子菜单IDs
+func GetClassifyChildrenIdsRecursive(list []*eta_training_video.EtaTrainingVideoClassify, parentId int) []int {
+	res := make([]int, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			ids := GetClassifyChildrenIdsRecursive(list, v.EtaTrainingVideoClassifyId)
+			res = append(res, v.EtaTrainingVideoClassifyId)
+			res = append(res, ids...)
+		}
+	}
+	return res
+}
+
+// GetClassifyParentsRecursive 遍历子分类的所有父级分类
+func GetClassifyParentsRecursive(list []*eta_training_video.EtaTrainingVideoClassify, parentId int) []*eta_training_video.EtaTrainingVideoClassify {
+	res := make([]*eta_training_video.EtaTrainingVideoClassify, 0)
+	for _, v := range list {
+		if v.EtaTrainingVideoClassifyId == parentId {
+			res = append(res, v)
+			parents := make([]*eta_training_video.EtaTrainingVideoClassify, 0)
+			if v.ParentId > 0 {
+				parents = GetClassifyParentsRecursive(list, v.ParentId)
+			}
+			res = append(res, parents...)
+		}
+	}
+	return res
+}