Quellcode durchsuchen

Merge branch 'dev/1.3' into debug

hsun vor 2 Jahren
Ursprung
Commit
f86210eb01

+ 150 - 0
controllers/classify.go

@@ -0,0 +1,150 @@
+package controllers
+
+import (
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"hongze/hongze_ETA_mobile_api/models"
+	"hongze/hongze_ETA_mobile_api/utils"
+)
+
+// 分类
+type ClassifyController struct {
+	BaseAuthController
+}
+
+// @Title 获取分类列表
+// @Description 获取分类列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "检索关键词"
+// @Param   CompanyType   query   string  false       "产品类型,枚举值:'ficc','权益';不传默认返回全部"
+// @Param   HideDayWeek   query   int  false       "是否隐藏晨周报"
+// @Success 200 {object} models.Classify
+// @router /list [get]
+func (this *ClassifyController) ListClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyWord := this.GetString("KeyWord")
+	companyType := this.GetString("CompanyType")
+	hideDayWeek, _ := this.GetInt("HideDayWeek")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+
+	startSize = utils.StartIndex(currentIndex, pageSize)
+	list, err := models.GetClassifyList(startSize, pageSize, keyWord, companyType, hideDayWeek)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	total, err := models.GetClassifyListCount(keyWord, companyType, hideDayWeek)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	parentIds := make([]int, 0)
+	for i := range list {
+		parentIds = append(parentIds, list[i].Id)
+	}
+	parentIdLen := len(parentIds)
+	if parentIdLen == 0 {
+		resp := &models.ClassifyListResp{
+			List:   list,
+			Paging: paging.GetPaging(currentIndex, pageSize, 0),
+		}
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	// 获取一级分类-子目录列表
+	menuListMap := make(map[int][]*models.ClassifyMenu, 0)
+	var menuCond string
+	var menuPars []interface{}
+	menuCond += ` AND classify_id IN (` + utils.GetOrmInReplace(parentIdLen) + `)`
+	menuPars = append(menuPars, parentIds)
+	parentMenus, e := models.GetClassifyMenuList(menuCond, menuPars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取一级分类子目录列表失败"
+		return
+	}
+	for i := range parentMenus {
+		if menuListMap[parentMenus[i].ClassifyId] == nil {
+			menuListMap[parentMenus[i].ClassifyId] = make([]*models.ClassifyMenu, 0)
+		}
+		menuListMap[parentMenus[i].ClassifyId] = append(menuListMap[parentMenus[i].ClassifyId], parentMenus[i])
+	}
+
+	// 获取子分类
+	children, e := models.GetClassifyChildByParentIds(parentIds, keyWord)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取子分类失败"
+		return
+	}
+	childrenIds := make([]int, 0)
+	for i := range children {
+		childrenIds = append(childrenIds, children[i].Id)
+	}
+	childrenIdsLen := len(childrenIds)
+
+	// 获取二级分类-子目录关联
+	relateMap := make(map[int]int, 0)
+	if childrenIdsLen > 0 {
+		var relateCond string
+		var relatePars []interface{}
+		relateCond += ` AND classify_id IN (` + utils.GetOrmInReplace(childrenIdsLen) + `)`
+		relatePars = append(relatePars, childrenIds)
+		relates, e := models.GetClassifyMenuRelationList(relateCond, relatePars)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取二级分类子目录关联失败, Err: " + e.Error()
+			return
+		}
+		for i := range relates {
+			relateMap[relates[i].ClassifyId] = relates[i].MenuId
+		}
+	}
+
+	// 二级分类
+	childrenMap := make(map[int][]*models.ClassifyItem, 0)
+	for i := range children {
+		if childrenMap[children[i].ParentId] == nil {
+			childrenMap[children[i].ParentId] = make([]*models.ClassifyItem, 0)
+		}
+		childrenMap[children[i].ParentId] = append(childrenMap[children[i].ParentId], &models.ClassifyItem{
+			Classify:       *children[i],
+			ClassifyMenuId: relateMap[children[i].Id],
+		})
+	}
+
+	// 一级分类
+	for i := range list {
+		list[i].ClassifyMenuList = menuListMap[list[i].Id]
+		list[i].Child = childrenMap[list[i].Id]
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(models.ClassifyListResp)
+	resp.List = list
+	resp.Paging = page
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 707 - 0
controllers/english_report/email.go

@@ -0,0 +1,707 @@
+package english_report
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"hongze/hongze_ETA_mobile_api/controllers"
+	"hongze/hongze_ETA_mobile_api/models"
+	"hongze/hongze_ETA_mobile_api/models/company"
+	"hongze/hongze_ETA_mobile_api/services"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// EnglishReportEmailController 英文研报邮箱/英文客户联系人
+type EnglishReportEmailController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 英文研报邮箱列表
+// @Description 英文研报邮箱列表
+// @Param   CompanyId	query	int		false	"客户ID"
+// @Param   Keywords	query	string	false	"关键词:联系人名称/邮箱地址"
+// @Param   SortType	query	int		false	"点击量排序方式:1-倒序;2-正序"
+// @Param   SortParam	query	int		false	"排序字段:1-点击量;2-点击时间"
+// @Param   ListParam   query   int  	false   "筛选字段参数,用来筛选的字段, 枚举值:1:正式 、 2:临时 、 3:终止 、4:正式+临时 "
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /email/list [get]
+func (this *EnglishReportEmailController) List() {
+	br := new(models.BaseResponse).Init()
+	br.IsSendEmail = false
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	companyId, _ := this.GetInt("CompanyId", 0)
+	keywords := this.GetString("Keywords", "")
+	sortType, _ := this.GetInt("SortType", 1)
+	sortParam, _ := this.GetInt("SortParam", 1)
+	listParam, _ := this.GetInt("ListParam", 1)
+
+	var cond, order string
+	var pars []interface{}
+	if companyId > 0 {
+		cond += ` AND a.company_id = ?`
+		pars = append(pars, companyId)
+	}
+	if keywords != "" {
+		k := "%" + keywords + "%"
+		cond += ` AND (a.name LIKE ? OR a.email LIKE ?)`
+		pars = append(pars, k, k)
+	}
+
+	if sortParam == 1 {
+		if sortType == 1 {
+			order = ` ORDER BY a.view_total DESC`
+		} else {
+			order = ` ORDER BY a.view_total ASC`
+		}
+	} else {
+		if sortType == 1 {
+			order = ` ORDER BY a.create_time DESC`
+		} else {
+			order = ` ORDER BY a.create_time ASC`
+		}
+	}
+
+	if listParam == 1 {
+		cond += ` AND a.status = 1`
+	}
+	if listParam == 2 {
+		cond += ` AND a.status = 2`
+	}
+	if listParam == 3 {
+		cond += ` AND a.status = 3`
+	}
+	if listParam == 4 {
+		cond += ` AND (a.status = 1 OR a.status = 2)`
+	}
+	var startSize int
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	total, list, e := models.GetEnglishReportEmailPageList(cond, pars, order, startSize, pageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取邮箱列表失败, Err: " + e.Error()
+		return
+	}
+
+	respList := make([]*models.EnglishReportEmailResp, 0)
+	for i := range list {
+		item := &models.EnglishReportEmailResp{
+			Id:                  list[i].Id,
+			Name:                list[i].Name,
+			Email:               list[i].Email,
+			Mobile:              list[i].Mobile,
+			CountryCode:         list[i].CountryCode,
+			BusinessCardUrl:     list[i].BusinessCardUrl,
+			AdminName:           list[i].AdminName,
+			CreateTime:          list[i].CreateTime.Format(utils.FormatDateTime),
+			ViewTotal:           list[i].ViewTotal,
+			Enabled:             list[i].Enabled,
+			CompanyName:         list[i].CompanyName,
+			RegisterCompanyName: list[i].RegisterCompanyName,
+			RegisterTime:        list[i].RegisterTime.Format(utils.FormatDateTime),
+		}
+		if item.RegisterTime == "0001-01-01 00:00:00" {
+			item.RegisterTime = ""
+		}
+		respList = append(respList, item)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := &models.EnglishReportEmailPageListResp{
+		Paging: page,
+		List:   respList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Send
+// @Title 群发推送
+// @Description 群发推送
+// @Param	request  body  models.EnglishReportEmailSaveReq  true  "type json string"
+// @Success 200 string "操作成功"
+// @router /email/send [post]
+func (this *EnglishReportEmailController) Send() {
+	br := new(models.BaseResponse).Init()
+	br.IsSendEmail = false
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EnglishReportEmailSendReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportId <= 0 {
+		br.Msg = "请选择报告"
+		return
+	}
+	if req.Theme == "" {
+		br.Msg = "请输入邮件主题"
+		return
+	}
+	//prefixTheme := "【HI China Macro and Commodity】"
+	//if !strings.Contains(req.Theme, prefixTheme) {
+	//	req.Theme = prefixTheme + req.Theme
+	//}
+	runeThe := []rune(req.Theme)
+	if len(runeThe) > 100 {
+		br.Msg = "邮件主题不可超过100字符"
+		return
+	}
+
+	// 获取报告信息
+	reportInfo, e := models.GetEnglishReportItemById(req.ReportId)
+	if e != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "获取报告信息失败, Err: " + e.Error()
+		return
+	}
+	if reportInfo.EmailState != 0 {
+		br.Msg = "请勿重复发送"
+		br.ErrMsg = fmt.Sprintf("报告群发邮件状态有误, 当前状态: %d", reportInfo.EmailState)
+		return
+	}
+	if reportInfo.State != 2 {
+		br.Msg = "报告未发布"
+		br.ErrMsg = fmt.Sprintf("报告状态有误, 当前状态: %d", reportInfo.State)
+		return
+	}
+	if reportInfo.Title == "" {
+		br.Msg = "报告标题有误"
+		br.ErrMsg = "报告标题为空, 不可推送"
+		return
+	}
+	if reportInfo.Content == `` {
+		br.Msg = "报告内容有误"
+		br.ErrMsg = "报告内容为空, 不可推送"
+		return
+	}
+
+	// 获取邮件配置
+	conf := new(models.EnglishReportEmailConf)
+	authKey := "english_report_email_conf"
+	confAuth, e := company.GetConfigDetailByCode(authKey)
+	if e != nil {
+		br.Msg = "无权操作"
+		br.ErrMsg = "获取群发邮件权限失败, Err: " + e.Error()
+		return
+	}
+	if confAuth.ConfigValue == "" {
+		br.Msg = "无权操作"
+		br.ErrMsg = "群发邮件配置为空"
+		return
+	}
+	if e := json.Unmarshal([]byte(confAuth.ConfigValue), &conf); e != nil {
+		br.Msg = "无权操作"
+		br.ErrMsg = "群发邮件配置有误"
+		return
+	}
+	authArr := strings.Split(conf.SendAuthGroup, ",")
+	if !utils.InArrayByStr(authArr, sysUser.RoleTypeCode) {
+		br.Msg = "无权操作"
+		return
+	}
+
+	// 获取收件人列表
+	emailCond := " and enabled = 1 "
+	var emailPars []interface{}
+	if req.EmailIds != "" {
+		emailIdArr := strings.Split(req.EmailIds, ",")
+		if len(emailIdArr) == 0 {
+			br.Msg = "收件人列表有误"
+			br.ErrMsg = fmt.Sprintf("收件人列表为空, 收件人IDs: %s", req.EmailIds)
+			return
+		}
+		emailCond += ` AND id IN (` + req.EmailIds + `)`
+	}
+	emails, e := models.GetEnglishReportEmailList(emailCond, emailPars, "")
+	if e != nil {
+		br.Msg = "获取收件人列表失败"
+		br.ErrMsg = "获取收件人列表失败, Err: " + e.Error()
+		return
+	}
+	if len(emails) == 0 {
+		br.Msg = "收件人列表为空(或收件人账号被禁用)"
+		br.ErrMsg = "收件人列表为空, 不可推送"
+		return
+	}
+
+	// 获取模板
+	confKey := "english_report_email_tmp"
+	confTmp, e := company.GetConfigDetailByCode(confKey)
+	if e != nil {
+		br.Msg = "获取邮件模板失败"
+		br.ErrMsg = "获取邮件模板失败, Err: " + e.Error()
+		return
+	}
+	if confTmp.ConfigValue == `` {
+		br.Msg = "邮件模板有误"
+		br.ErrMsg = "邮件模板为空, 不可推送"
+		return
+	}
+
+	// 同一篇报告避免重复推送给同一个邮件
+	var cond string
+	var pars []interface{}
+	cond += ` AND report_id = ? AND send_status = 1 AND report_type = 0`
+	pars = append(pars, req.ReportId)
+	logs, e := models.GetEnglishReportEmailLogList(cond, pars)
+	if e != nil {
+		br.Msg = "推送失败"
+		br.ErrMsg = "获取邮件发送日志列表失败, Err: " + e.Error()
+		return
+	}
+	repeatMap := make(map[int]bool, 0)
+	for i := range logs {
+		if logs[i].SendStatus == 1 {
+			repeatMap[logs[i].EmailId] = true
+		}
+	}
+
+	// 推送信息
+	nowTime := time.Now().Local()
+	sendData := make([]*services.EnglishReportSendEmailRequest, 0)
+	logData := make([]*models.EnglishReportEmailLog, 0)
+	for i := range emails {
+		if repeatMap[emails[i].Id] {
+			continue
+		}
+
+		r := new(services.EnglishReportSendEmailRequest)
+		r.ReportId = reportInfo.Id
+		r.EmailId = emails[i].Id
+		r.Email = strings.Replace(emails[i].Email, " ", "", -1)
+		r.Subject = req.Theme
+		r.FromAlias = conf.FromAlias
+
+		// 获取报告Overview部分
+		if reportInfo.Overview == "" {
+			br.Msg = "报告Overview部分不可为空"
+			return
+		}
+		overview := reportInfo.Overview
+
+		// 填充模板
+		t := reportInfo.PublishTime.Format("02/01/2006")
+		l := fmt.Sprintf(utils.EnglishShareUrl+"/report/detail?code=%s", reportInfo.ReportCode)
+		ct := confTmp.ConfigValue
+		ct = strings.Replace(ct, "{{REPORT_TITLE}}", reportInfo.Title, 1)
+		ct = strings.Replace(ct, "{{REPORT_ABSTRACT}}", reportInfo.Abstract, 1)
+		ct = strings.Replace(ct, "{{REPORT_CONTENT}}", overview, 1)
+		ct = strings.Replace(ct, "{{REPORT_SHARE_LINK}}", l, 1)
+		ct = strings.Replace(ct, "{{REPORT_TIME}}", t, 1)
+		r.HtmlBody = ct
+		r.ReportTitle = reportInfo.Title
+		r.ReportAbstract = reportInfo.Abstract
+		r.ReportContent = overview
+		r.ReportShareLink = l
+		r.ReportTime = t
+		sendData = append(sendData, r)
+
+		// 日志
+		logData = append(logData, &models.EnglishReportEmailLog{
+			ReportId:   reportInfo.Id,
+			EmailId:    emails[i].Id,
+			Email:      emails[i].Email,
+			Source:     models.EnglishReportEmailLogSourceAli,
+			SendStatus: models.EnglishReportEmailLogStatusIng,
+			CreateTime: nowTime,
+		})
+	}
+	if len(sendData) == 0 {
+		br.Msg = "无邮件可推送"
+		return
+	}
+
+	// 写入日志
+	emailLog := new(models.EnglishReportEmailLog)
+	if e = emailLog.InsertMulti(logData); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "批量写入群发邮件日志失败, Err: " + e.Error()
+		return
+	}
+
+	// 修改推送状态
+	updateCols := []string{"EmailState", "ModifyTime"}
+	reportInfo.EmailState = 1
+	reportInfo.ModifyTime = time.Now().Local()
+	if e = reportInfo.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新推送状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 群发邮件
+	go func() {
+		services.BatchSendAliEnglishReportEmail(sendData)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// LogList
+// @Title 邮件群发日志列表
+// @Description 邮件群发日志列表
+// @Param   ReportId  query  int  true  "报告ID"
+// @Param   ReportType  query  int  true  "类型:0英文研报,1英文线上路演"
+// @Param   SendStatus  query  int  false  "发送状态:-1-已发送;0-发送失败;1-发送成功"
+// @Param   Keywords	query	string	false	"关键词:邮箱"
+// @Success 200 {object} models.EnglishReportEmailLogPageListResp
+// @router /email/log_list [get]
+func (this *EnglishReportEmailController) LogList() {
+	br := new(models.BaseResponse).Init()
+	br.IsSendEmail = false
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	reportId, _ := this.GetInt("ReportId", 0)
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	reportType, _ := this.GetInt("ReportType", 0)
+	keywords := this.GetString("Keywords", "")
+
+	var startSize int
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	var cond string
+	var pars []interface{}
+	cond += ` AND report_id = ? AND report_type = ?`
+	pars = append(pars, reportId, reportType)
+	// 推送状态
+	status := this.GetString("SendStatus", "")
+	if status != "" {
+		statusInt, e := strconv.Atoi(status)
+		if e != nil {
+			br.Msg = "状态类型有误"
+			return
+		}
+		cond += ` AND send_status = ?`
+		pars = append(pars, statusInt)
+	}
+	// 关键词
+	if keywords != "" {
+		k := fmt.Sprint("%", keywords, "%")
+		cond += ` AND email LIKE ? `
+		pars = append(pars, k)
+	}
+
+	total, list, e := models.GetEnglishReportEmailLogPageList(cond, pars, "", startSize, pageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取邮箱列表失败, Err: " + e.Error()
+		return
+	}
+
+	respList := make([]*models.EnglishReportEmailLogPageList, 0)
+	for i := range list {
+		v := new(models.EnglishReportEmailLogPageList)
+		// 推送结果
+		v.ResultMsg = "发送成功"
+		if list[i].ErrMsg != "" {
+			v.ResultMsg = list[i].ErrMsg
+		}
+		v.SendId = list[i].Id
+		v.ReportId = list[i].ReportId
+		v.EmailId = list[i].EmailId
+		v.Email = list[i].Email
+		v.SendStatus = list[i].SendStatus
+		v.Source = list[i].Source
+		v.CreateTime = list[i].CreateTime.Format(utils.FormatDateTime)
+		respList = append(respList, v)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := &models.EnglishReportEmailLogPageListResp{
+		Paging: page,
+		List:   respList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Resend
+// @Title 重新推送
+// @Description 群发推送
+// @Param	request  body  models.EnglishReportEmailResendReq  true  "type json string"
+// @Success 200 string "操作成功"
+// @router /email/resend [post]
+func (this *EnglishReportEmailController) Resend() {
+	br := new(models.BaseResponse).Init()
+	br.IsSendEmail = false
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EnglishReportEmailResendReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportId <= 0 {
+		br.Msg = "请选择报告"
+		return
+	}
+
+	// 获取报告信息
+	reportInfo, e := models.GetEnglishReportItemById(req.ReportId)
+	if e != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "获取报告信息失败, Err: " + e.Error()
+		return
+	}
+	if reportInfo.State != 2 {
+		br.Msg = "报告未发布"
+		br.ErrMsg = fmt.Sprintf("报告状态有误, 当前状态: %d", reportInfo.State)
+		return
+	}
+	if reportInfo.Title == "" {
+		br.Msg = "报告标题有误"
+		br.ErrMsg = "报告标题为空, 不可推送"
+		return
+	}
+	if reportInfo.Content == `` {
+		br.Msg = "报告内容有误"
+		br.ErrMsg = "报告内容为空, 不可推送"
+		return
+	}
+	if reportInfo.Abstract == `` {
+		br.Msg = "报告摘要为空"
+		br.ErrMsg = "报告摘要为空, 不可推送"
+		return
+	}
+	if reportInfo.Overview == "" {
+		br.Msg = "报告Overview部分不可为空"
+		br.ErrMsg = "报告Overview为空, 不可推送"
+		return
+	}
+	// 重新发送超过100字符直接截取
+	theme := reportInfo.Abstract
+	runeAbs := []rune(reportInfo.Abstract)
+	runeThe := make([]rune, 0)
+	if len(runeAbs) > 100 {
+		runeThe = runeAbs[:100]
+		theme = string(runeThe)
+	}
+
+	// 获取邮件配置
+	conf := new(models.EnglishReportEmailConf)
+	authKey := "english_report_email_conf"
+	confAuth, e := company.GetConfigDetailByCode(authKey)
+	if e != nil {
+		br.Msg = "无权操作"
+		br.ErrMsg = "获取群发邮件权限失败, Err: " + e.Error()
+		return
+	}
+	if confAuth.ConfigValue == "" {
+		br.Msg = "无权操作"
+		br.ErrMsg = "群发邮件配置为空"
+		return
+	}
+	if e := json.Unmarshal([]byte(confAuth.ConfigValue), &conf); e != nil {
+		br.Msg = "无权操作"
+		br.ErrMsg = "群发邮件配置有误"
+		return
+	}
+	authArr := strings.Split(conf.SendAuthGroup, ",")
+	if !utils.InArrayByStr(authArr, sysUser.RoleTypeCode) {
+		br.Msg = "无权操作"
+		return
+	}
+
+	// 获取模板
+	confKey := "english_report_email_tmp"
+	confTmp, e := company.GetConfigDetailByCode(confKey)
+	if e != nil {
+		br.Msg = "获取邮件模板失败"
+		br.ErrMsg = "获取邮件模板失败, Err: " + e.Error()
+		return
+	}
+	if confTmp.ConfigValue == `` {
+		br.Msg = "邮件模板有误"
+		br.ErrMsg = "邮件模板为空, 不可推送"
+		return
+	}
+
+	// 获取发送失败的日志
+	emailIds := make([]int, 0)
+	emails := make([]*models.EnglishReportEmail, 0)
+	logIds := make([]int, 0)
+	if req.SendId > 0 {
+		logItem, e := models.GetEnglishReportEmailLogById(req.SendId)
+		if e != nil {
+			br.Msg = "发送失败"
+			br.ErrMsg = "获取原邮件推送日志失败, Err: " + e.Error()
+			return
+		}
+		emailIds = append(emailIds, logItem.EmailId)
+		logIds = append(logIds, logItem.Id)
+	} else {
+		var logCond string
+		var logPars []interface{}
+		logCond += ` AND report_id = ? AND send_status = 0 AND report_type=0`
+		logPars = append(logPars, req.ReportId)
+		logList, e := models.GetEnglishReportEmailLogList(logCond, logPars)
+		if e != nil {
+			br.Msg = "发送失败"
+			br.ErrMsg = "获取原邮件推送日志列表失败, Err: " + e.Error()
+			return
+		}
+		for i := range logList {
+			emailIds = append(emailIds, logList[i].EmailId)
+			logIds = append(logIds, logList[i].Id)
+		}
+	}
+
+	// 获取收件人列表
+	// 注: 此处不从失败日志中取email, 因为有可能是地址有误导致的推送失败
+	if len(emailIds) > 0 {
+		var emailCond string
+		var emailPars []interface{}
+		emailCond += ` AND id IN (` + utils.GetOrmInReplace(len(emailIds)) + `) and enabled = 1 `
+		emailPars = append(emailPars, emailIds)
+		em, e := models.GetEnglishReportEmailList(emailCond, emailPars, "")
+		if e != nil {
+			br.Msg = "获取收件人列表失败"
+			br.ErrMsg = "获取收件人列表失败, Err: " + e.Error()
+			return
+		}
+		emails = em
+	}
+	if len(emails) == 0 {
+		br.Msg = "无邮件可推送"
+		return
+	}
+
+	// 推送信息
+	nowTime := time.Now().Local()
+	sendData := make([]*services.EnglishReportSendEmailRequest, 0)
+	logData := make([]*models.EnglishReportEmailLog, 0)
+	for i := range emails {
+		r := new(services.EnglishReportSendEmailRequest)
+		r.ReportId = reportInfo.Id
+		r.EmailId = emails[i].Id
+		r.Email = strings.Replace(emails[i].Email, " ", "", -1)
+		r.Subject = theme
+		r.FromAlias = conf.FromAlias
+		// 填充模板
+		t := reportInfo.PublishTime.Format("02/01/2006")
+		l := fmt.Sprintf(utils.EnglishShareUrl+"/report/detail?code=%s", reportInfo.ReportCode)
+		ct := confTmp.ConfigValue
+		ct = strings.Replace(ct, "{{REPORT_TITLE}}", reportInfo.Title, 1)
+		ct = strings.Replace(ct, "{{REPORT_ABSTRACT}}", reportInfo.Abstract, 1)
+		ct = strings.Replace(ct, "{{REPORT_CONTENT}}", reportInfo.Overview, 1)
+		ct = strings.Replace(ct, "{{REPORT_SHARE_LINK}}", l, 1)
+		ct = strings.Replace(ct, "{{REPORT_TIME}}", t, 1)
+		r.HtmlBody = ct
+		r.ReportTitle = reportInfo.Title
+		r.ReportAbstract = reportInfo.Abstract
+		r.ReportContent = reportInfo.Overview
+		r.ReportShareLink = l
+		r.ReportTime = t
+		sendData = append(sendData, r)
+
+		// 日志
+		logData = append(logData, &models.EnglishReportEmailLog{
+			ReportId:   reportInfo.Id,
+			EmailId:    emails[i].Id,
+			Email:      emails[i].Email,
+			Source:     models.EnglishReportEmailLogSourceAli,
+			SendStatus: models.EnglishReportEmailLogStatusIng,
+			CreateTime: nowTime,
+		})
+	}
+
+	// 标记原有日志为已删除
+	if len(logIds) > 0 {
+		if e = models.DeleteEnglishReportEmailLogByIds(logIds); e != nil {
+			br.Msg = "发送失败"
+			br.ErrMsg = "删除原邮件日志失败, Err: " + e.Error()
+			return
+		}
+	}
+
+	// 写入新的日志
+	emailLog := new(models.EnglishReportEmailLog)
+	if e = emailLog.InsertMulti(logData); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "批量写入群发邮件日志失败, Err: " + e.Error()
+		return
+	}
+
+	// 群发邮件
+	go func() {
+		services.BatchSendAliEnglishReportEmail(sendData)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 589 - 0
controllers/english_report/report.go

@@ -0,0 +1,589 @@
+package english_report
+
+import (
+	"encoding/json"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"hongze/hongze_ETA_mobile_api/controllers"
+	"hongze/hongze_ETA_mobile_api/models"
+	"hongze/hongze_ETA_mobile_api/models/company"
+	"hongze/hongze_ETA_mobile_api/services"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"html"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+// EnglishReportController 研报活动模块
+type EnglishReportController struct {
+	controllers.BaseAuthController
+}
+
+// @Title 获取报告详情接口
+// @Description 获取报告详情
+// @Param	request	body models.ReportDetailReq true "type json string"
+// @Success 200 {object} models.EnglishReportDetailView
+// @router /detail [get]
+func (this *EnglishReportController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	reportId, err := this.GetInt("ReportId")
+	if err != nil {
+		br.Msg = "获取参数失败!"
+		br.ErrMsg = "获取参数失败,Err:" + err.Error()
+		return
+	}
+	if reportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	item, err := models.GetEnglishReportById(reportId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	item.Content = html.UnescapeString(item.Content)
+	item.ContentSub = html.UnescapeString(item.ContentSub)
+
+	// 获取邮件配置-是否有权限群发
+	conf := new(models.EnglishReportEmailConf)
+	authKey := "english_report_email_conf"
+	confAuth, e := company.GetConfigDetailByCode(authKey)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取群发邮件权限失败, Err: " + e.Error()
+		return
+	}
+	if confAuth.ConfigValue == "" {
+		br.Msg = "获取失败"
+		br.ErrMsg = "群发邮件配置为空"
+		return
+	}
+	if e := json.Unmarshal([]byte(confAuth.ConfigValue), &conf); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "群发邮件配置有误"
+		return
+	}
+	authOk := false
+	authArr := strings.Split(conf.SendAuthGroup, ",")
+	if utils.InArrayByStr(authArr, sysUser.RoleTypeCode) {
+		authOk = true
+	}
+	item.EmailAuth = authOk
+
+	// 是否有群发邮件失败的记录,标记红点
+	failList, e := models.GetEnglishReportEmailLogFailList(0)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取群发邮件记录失败, Err: " + e.Error()
+		return
+	}
+	failMap := make(map[int]bool, 0)
+	for i := range failList {
+		failMap[failList[i].ReportId] = true
+	}
+	item.EmailHasFail = failMap[item.Id]
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = item
+}
+
+// @Title 获取报告列表接口
+// @Description 获取报告列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   StartDate   query   string  true       "开始时间"
+// @Param   EndDate   query   string  true       "结束时间"
+// @Param   Frequency   query   string  true       "频度"
+// @Param   ClassifyNameFirst   query   string  true       "一级分类名称"
+// @Param   ClassifyNameSecond   query   string  true       "二级分类名称"
+// @Param   State   query   int  true       "状态"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Param   PublishSort   query   string  true       "desc:降序,asc 升序(预留)"
+// @Param   CompanyType   query   string  false       "产品类型,枚举值:'ficc','权益';不传默认返回全部"
+// @Success 200 {object} models.ReportListResp
+// @router /list [get]
+func (this *EnglishReportController) ListReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		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")
+
+	startDate := this.GetString("StartDate")
+	endDate := this.GetString("EndDate")
+	frequency := this.GetString("Frequency")
+	classifyNameFirst := this.GetString("ClassifyNameFirst")
+	classifyNameSecond := this.GetString("ClassifyNameSecond")
+	state, _ := this.GetInt("State")
+	keyWord := this.GetString("KeyWord")
+	companyType := this.GetString("CompanyType")
+	// 群发邮件状态筛选
+	emailState, _ := this.GetInt("EmailState")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	var condition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += ` AND (title LIKE '%` + keyWord + `%' OR author LIKE '%` + keyWord + `%' ) `
+	}
+	if startDate != "" {
+		condition += ` AND create_time >= ? `
+		pars = append(pars, startDate)
+	}
+	if endDate != "" {
+		condition += ` AND create_time <= ? `
+		pars = append(pars, endDate)
+	}
+	if frequency != "" {
+		condition += ` AND frequency = ? `
+		pars = append(pars, frequency)
+	}
+	if classifyNameFirst != "" {
+		condition += ` AND classify_name_first = ? `
+		pars = append(pars, classifyNameFirst)
+	}
+
+	if classifyNameSecond != "" {
+		condition += ` AND classify_name_second = ? `
+		pars = append(pars, classifyNameSecond)
+	}
+	if state > 0 {
+		condition += ` AND state = ? `
+		pars = append(pars, state)
+	}
+	// 未群发邮件(包含推送邮件失败的)
+	if emailState == 1 {
+		failIds, e := models.GetHasFailEmailLogReportIds()
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取存在邮件推送失败记录的英文报告IDs失败, Err:" + e.Error()
+			return
+		}
+		condition += ` AND email_state = 0`
+		if len(failIds) > 0 {
+			condition += ` OR id IN (` + utils.GetOrmInReplace(len(failIds)) + `)`
+			pars = append(pars, failIds)
+		}
+	}
+	// 已群发邮件
+	if emailState == 2 {
+		successIds, e := models.GetSuccessEmailLogReportIds()
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取邮件推送记录均为成功的英文报告IDs失败, Err:" + e.Error()
+			return
+		}
+		condition += ` AND email_state = 1`
+		if len(successIds) > 0 {
+			condition += ` AND id IN (` + utils.GetOrmInReplace(len(successIds)) + `)`
+			pars = append(pars, successIds)
+		}
+	}
+
+	total, err := models.GetEnglishReportListCount(condition, pars, companyType)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	list, err := models.GetEnglishReportList(condition, pars, companyType, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	// 获取邮件配置-是否有权限群发
+	conf := new(models.EnglishReportEmailConf)
+	authKey := "english_report_email_conf"
+	confAuth, e := company.GetConfigDetailByCode(authKey)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取群发邮件权限失败, Err: " + e.Error()
+		return
+	}
+	if confAuth.ConfigValue == "" {
+		br.Msg = "获取失败"
+		br.ErrMsg = "群发邮件配置为空"
+		return
+	}
+	if e := json.Unmarshal([]byte(confAuth.ConfigValue), &conf); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "群发邮件配置有误"
+		return
+	}
+	authOk := false
+	authArr := strings.Split(conf.SendAuthGroup, ",")
+	if utils.InArrayByStr(authArr, sysUser.RoleTypeCode) {
+		authOk = true
+	}
+
+	// 是否有群发邮件失败的记录,标记红点
+	failList, e := models.GetEnglishReportEmailLogFailList(0)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取群发邮件记录失败, Err: " + e.Error()
+		return
+	}
+	failMap := make(map[int]bool, 0)
+	for i := range failList {
+		failMap[failList[i].ReportId] = true
+	}
+
+	for _, item := range list {
+		if item.State == 2 {
+			item.ShareUrl = "https://share.hzinsights.com/reportEn?code=" + item.ReportCode
+		}
+		item.EmailAuth = authOk
+		item.EmailHasFail = failMap[item.Id]
+		// 邮箱PV大于0的时候, 不展示最初版本的PV
+		if item.PvEmail > 0 {
+			item.Pv = 0
+		}
+
+		/*key := fmt.Sprint(`crm:enReport:edit:`, item.Id)
+		opUserId, _ := utils.Rc.RedisInt(key)
+		//如果当前没有人操作,获取当前操作人是本人,那么编辑按钮可用
+		if opUserId <= 0 || (opUserId == this.SysUser.AdminId) {
+			item.CanEdit = true
+		} else {
+			adminInfo, errAdmin := system.GetSysUserById(opUserId)
+			if errAdmin != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取失败,Err:" + errAdmin.Error()
+				return
+			}
+			item.Editor = adminInfo.RealName
+		}*/
+		markStatus, err := services.UpdateEnReportEditMark(item.Id, this.SysUser.AdminId, 2, this.SysUser.RealName)
+		if err != nil {
+			br.Msg = "查询标记状态失败"
+			br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+			return
+		}
+		if markStatus.Status == 0 {
+			item.CanEdit = true
+		} else {
+			item.Editor = markStatus.Editor
+		}
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(models.EnglishReportListResp)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 获取分类列表
+// @Description 获取分类列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "检索关键词"
+// @Param   CompanyType   query   string  false       "产品类型,枚举值:'ficc','权益';不传默认返回全部"
+// @Success 200 {object} models.EnglishClassifyListResp
+// @router /classify/list [get]
+func (this *EnglishReportController) ListClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyWord := this.GetString("KeyWord")
+	classifyType, _ := this.GetInt("ClassifyType", 0)
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	page := paging.GetPaging(currentIndex, pageSize, 0)
+	resp := new(models.EnglishClassifyListResp)
+
+	list, err := models.GetEnglishClassifyList(startSize, pageSize, keyWord, classifyType)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	total, err := models.GetEnglishClassifyListCount(keyWord, classifyType)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	if total == 0 {
+		resp.List = make([]*models.EnglishClassifyList, 0)
+		resp.Paging = page
+
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+	var parentIds []int
+	for _, v := range list {
+		parentIds = append(parentIds, v.Id)
+	}
+
+	childMap := make(map[int][]*models.EnglishClassify)
+	tmpList, err := models.GetEnglishClassifyChildByParentIds(parentIds, keyWord, classifyType)
+	if err != nil {
+		br.Msg = "获取二级分类失败"
+		br.ErrMsg = "获取二级分类失败,Err:" + err.Error()
+		return
+	}
+
+	for _, v := range tmpList {
+		childMap[v.ParentId] = append(childMap[v.ParentId], v)
+	}
+	for _, v := range list {
+		if child, ok := childMap[v.Id]; ok {
+			v.Child = child
+		}
+	}
+	var sortList models.RSClassifyList
+	sortList = list
+	sort.Sort(sortList)
+
+	for _, item := range sortList {
+		var sortChildList models.RSChildClassifyList
+		sortChildList = item.Child
+		sort.Sort(sortChildList)
+		item.Child = sortChildList
+	}
+	resp.List = sortList
+	resp.Paging = page
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// @Title 删除分类列表
+// @Description 删除分类列表
+// @Param   ClassifyId   int  true       "分类名称"
+// @Param   ParentId   query   int  true   "父级Id"
+// @Success 200 保存成功
+// @router /classify/delete [get]
+func (this *EnglishReportController) DelClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	classifyId, _ := this.GetInt("ClassifyId")
+	//parentId, _ := this.GetInt("ParentId")
+
+	classifyInfo, err := models.GetEnglishReportClassifyById(classifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "当前分类不存在"
+			br.ErrMsg = "当前分类不存在"
+			return
+		}
+		br.Msg = "获取分类信息失败"
+		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+
+	count, err := models.GetEnglishClassifyChildCounts(classifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取信息失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	if count > 0 {
+		br.Msg = "请先删除该分类下关联分类"
+		br.Ret = 403
+		return
+	}
+
+	if classifyInfo.ClassifyType == 0 {
+		reportCount, e := models.GetEnglishReportCounts(classifyId, classifyInfo.ParentId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "获取信息失败"
+			br.ErrMsg = "获取失败,Err:" + e.Error()
+			return
+		}
+
+		if reportCount > 0 {
+			br.Msg = "该分类有关联报告,不允许删除"
+			br.Ret = 403
+			return
+		}
+	} else {
+		videoCount, e := models.GetEnglishVideoCounts(classifyId, classifyInfo.ParentId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "获取信息失败"
+			br.ErrMsg = "获取失败,Err:" + e.Error()
+			return
+		}
+
+		if videoCount > 0 {
+			br.Msg = "该分类有关联的路演视频,不允许删除"
+			br.Ret = 403
+			return
+		}
+	}
+
+	if err = models.DeleteEnglishClassify(classifyId); err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除报告失败, Err: " + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除成功"
+}
+
+// @Title 发布报告接口
+// @Description 发布报告
+// @Param	request	body models.PublishReq true "type json string"
+// @Success 200 Ret=200 发布成功
+// @router /publish [post]
+func (this *EnglishReportController) PublishReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.PublishReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportIds := req.ReportIds
+	if reportIds == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+
+	reportArr := strings.Split(reportIds, ",")
+	for _, v := range reportArr {
+		vint, err := strconv.Atoi(v)
+		if err != nil {
+			br.Msg = "参数错误"
+			br.ErrMsg = "参数错误,Err:" + err.Error()
+			return
+		}
+		report, err := models.GetEnglishReportById(vint)
+		if err != nil {
+			br.Msg = "获取报告信息失败"
+			br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
+			return
+		}
+		if report == nil {
+			br.Msg = "报告不存在"
+			return
+		}
+
+		var tmpErr error
+
+		if report.Content == "" {
+			br.Msg = "报告内容为空,不可发布"
+			br.ErrMsg = "报告内容为空,不需要生成,report_id:" + strconv.Itoa(report.Id)
+			return
+		}
+		if tmpErr = models.PublishEnglishReportById(report.Id); tmpErr != nil {
+			br.Msg = "报告发布失败"
+			br.ErrMsg = "报告发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(report.Id)
+			return
+		}
+		go func() {
+			_ = services.UpdateEnglishReportEs(report.Id, 2)
+		}()
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "发布成功"
+}
+
+// @Title 取消发布报告接口
+// @Description 取消发布报告
+// @Param	request	body models.PublishCancelReq true "type json string"
+// @Success 200 Ret=200 取消发布成功
+// @router /publish/cancel [post]
+func (this *EnglishReportController) PublishCancleReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.PublishCancelReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportIds <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+	err = models.PublishCancelEnglishReport(req.ReportIds)
+	if err != nil {
+		br.Msg = "取消发布失败"
+		br.ErrMsg = "取消发布失败,Err:" + err.Error()
+		return
+	}
+	// 更新es里的报告状态
+	go func() {
+		_ = services.UpdateEnglishReportEs(req.ReportIds, 1)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "取消发布成功"
+}

+ 2984 - 0
controllers/report.go

@@ -0,0 +1,2984 @@
+package controllers
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/beego/beego/v2/server/web"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"hongze/hongze_ETA_mobile_api/models"
+	"hongze/hongze_ETA_mobile_api/services"
+	"hongze/hongze_ETA_mobile_api/services/alarm_msg"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"html"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// ReportController 报告
+type ReportController struct {
+	BaseAuthController
+}
+
+// 报告
+//type ReportCommonController struct {
+//	BaseCommonController
+//}
+
+// ReportUploadCommonController 报告上传
+type ReportUploadCommonController struct {
+	web.Controller
+}
+
+// @Title 获取报告列表接口
+// @Description 获取报告列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   StartDate   query   string  true       "开始时间"
+// @Param   EndDate   query   string  true       "结束时间"
+// @Param   Frequency   query   string  true       "频度"
+// @Param   ClassifyNameFirst   query   string  true       "一级分类名称"
+// @Param   ClassifyNameSecond   query   string  true       "二级分类名称"
+// @Param   State   query   int  true       "状态"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Param   PublishSort   query   string  true       "desc:降序,asc 升序(预留)"
+// @Param   CompanyType   query   string  false       "产品类型,枚举值:'ficc','权益';不传默认返回全部"
+// @Success 200 {object} models.ReportListResp
+// @router /list [get]
+func (this *ReportController) ListReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	startDate := this.GetString("StartDate")
+	endDate := this.GetString("EndDate")
+	frequency := this.GetString("Frequency")
+	classifyNameFirst := this.GetString("ClassifyNameFirst")
+	classifyNameSecond := this.GetString("ClassifyNameSecond")
+	state, _ := this.GetInt("State")
+	keyWord := this.GetString("KeyWord")
+	companyType := this.GetString("CompanyType")
+	msgIsSend, _ := this.GetInt("MsgIsSend")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	var condition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += ` AND (title LIKE '%` + keyWord + `%' OR author LIKE '%` + keyWord + `%' ) `
+	}
+	if startDate != "" {
+		condition += ` AND create_time >= ? `
+		pars = append(pars, startDate)
+	}
+	if endDate != "" {
+		condition += ` AND create_time <= ? `
+		pars = append(pars, endDate)
+	}
+	if frequency != "" {
+		condition += ` AND frequency = ? `
+		pars = append(pars, frequency)
+	}
+	if classifyNameFirst != "" {
+		condition += ` AND classify_name_first = ? `
+		pars = append(pars, classifyNameFirst)
+	}
+
+	if classifyNameSecond != "" {
+		condition += ` AND classify_name_second = ? `
+		pars = append(pars, classifyNameSecond)
+	}
+	if state > 0 {
+		condition += ` AND state = ? `
+		pars = append(pars, state)
+	}
+	// 消息是否已推送 1-未推送; 2-已推送
+	if msgIsSend == 1 {
+		condition += ` AND (msg_is_send = 0 OR ths_msg_is_send = 0) `
+	}
+	if msgIsSend == 2 {
+		condition += ` AND msg_is_send = 1 AND ths_msg_is_send = 1 `
+	}
+	total, err := models.GetReportListCount(condition, pars, companyType)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	list, err := models.GetReportList(condition, pars, companyType, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	listLen := len(list)
+	if listLen > 0 {
+		reportIdArr := make([]string, 0)
+		syncReportIdArr := make([]string, 0) // 同步过来的报告IDs
+		for i := 0; i < listLen; i++ {
+			reportIdArr = append(reportIdArr, strconv.Itoa(list[i].Id))
+			if list[i].OldReportId > 0 {
+				syncReportIdArr = append(syncReportIdArr, strconv.Itoa(list[i].OldReportId))
+			}
+		}
+		reportIds := strings.Join(reportIdArr, ",")
+		syncReportIds := strings.Join(syncReportIdArr, ",")
+
+		// 查询同步过来的报告对应的老报告PV+UV
+		pvMap := make(map[int]int, 0)
+		uvMap := make(map[int]int, 0)
+		if syncReportIds != "" {
+			puvList, e := models.GetPUVByResearchReportIds(syncReportIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取同步报告对应的PV、UV失败, Err: " + e.Error()
+				return
+			}
+			puvLen := len(puvList)
+			for i := 0; i < puvLen; i++ {
+				pvMap[puvList[i].ResearchReportId] = puvList[i].Pv
+				uvMap[puvList[i].ResearchReportId] = puvList[i].Uv
+			}
+		}
+		// 晨周报音频列表
+		videoList, err := models.GetReportChapterVideoListByReportIds(reportIds)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取报告音频文件失败,Err:" + err.Error()
+			return
+		}
+		for i := 0; i < listLen; i++ {
+			list[i].Content = html.UnescapeString(list[i].Content)
+			list[i].ContentSub = html.UnescapeString(list[i].ContentSub)
+			// 除周报外其余报告均可推送客群
+			list[i].NeedThsMsg = 1
+			//if list[i].HasChapter == 1 && list[i].ChapterType == utils.REPORT_TYPE_WEEK {
+			//	list[i].NeedThsMsg = 0
+			//}
+			chapterList := make([]*models.ReportChapterVideoList, 0)
+			for ii := 0; ii < len(videoList); ii++ {
+				if list[i].Id == videoList[ii].ReportId {
+					chapterList = append(chapterList, videoList[ii])
+				}
+			}
+			list[i].ChapterVideoList = chapterList
+			list[i].Pv += pvMap[list[i].OldReportId]
+			list[i].Uv += uvMap[list[i].OldReportId]
+		}
+	}
+
+	for _, item := range list {
+		/*key := fmt.Sprint(`crm:report:edit:`, item.Id)
+		opUserId, _ := utils.Rc.RedisInt(key)
+		//如果当前没有人操作,获取当前操作人是本人,那么编辑按钮可用
+		if opUserId <= 0 || (opUserId == this.SysUser.AdminId) || item.ClassifyNameFirst == "周报" || item.ClassifyNameFirst == "晨报" {
+			item.CanEdit = true
+		} else {
+			adminInfo, errAdmin := system.GetSysUserById(opUserId)
+			if errAdmin != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取失败,Err:" + errAdmin.Error()
+				return
+			}
+			item.Editor = adminInfo.RealName
+		}*/
+		if item.ClassifyNameFirst == "周报" || item.ClassifyNameFirst == "晨报" {
+			item.CanEdit = true
+		} else {
+			markStatus, err := services.UpdateReportEditMark(item.Id, this.SysUser.AdminId, 2, this.SysUser.RealName)
+			if err != nil {
+				br.Msg = "查询标记状态失败"
+				br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+				return
+			}
+			if markStatus.Status == 0 {
+				item.CanEdit = true
+			} else {
+				item.Editor = markStatus.Editor
+			}
+		}
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(models.ReportListResp)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 发布报告接口
+// @Description 发布报告
+// @Param	request	body models.PublishReq true "type json string"
+// @Success 200 Ret=200 发布成功
+// @router /publish [post]
+func (this *ReportController) PublishReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.PublishReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportIds := req.ReportIds
+	if reportIds == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+
+	reportArr := strings.Split(reportIds, ",")
+	tips := ""
+	for _, v := range reportArr {
+		vint, err := strconv.Atoi(v)
+		if err != nil {
+			br.Msg = "参数错误"
+			br.ErrMsg = "参数错误,Err:" + err.Error()
+			return
+		}
+		report, err := models.GetReportById(vint)
+		if err != nil {
+			br.Msg = "获取报告信息失败"
+			br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
+			return
+		}
+		if report == nil {
+			br.Msg = "报告不存在"
+			return
+		}
+		var publishTime time.Time
+		if report.MsgIsSend == 1 && report.PublishTime != "" { //如果报告曾经发布过,并且已经发送过模版消息,则章节的发布时间为报告的发布时间
+			publishTime, _ = time.ParseInLocation(utils.FormatDateTime, report.PublishTime, time.Local)
+		} else {
+			publishTime = time.Now()
+		}
+
+		var tmpErr error
+		if report.HasChapter == 1 && (report.ChapterType == utils.REPORT_TYPE_DAY || report.ChapterType == utils.REPORT_TYPE_WEEK) {
+			// 发布晨周报
+			if tips, tmpErr = services.PublishDayWeekReport(vint); tmpErr != nil {
+				br.Msg = "报告发布失败"
+				br.ErrMsg = "晨周报发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(report.Id)
+				return
+			}
+		} else {
+			if report.Content == "" {
+				br.Msg = "报告内容为空,不可发布"
+				br.ErrMsg = "报告内容为空,不需要生成,report_id:" + strconv.Itoa(report.Id)
+				return
+			}
+			if tmpErr = models.PublishReportById(report.Id, publishTime); tmpErr != nil {
+				br.Msg = "报告发布失败"
+				br.ErrMsg = "报告发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(report.Id)
+				return
+			}
+			go func() {
+				// 生成音频
+				if report.VideoUrl == "" {
+					_ = services.CreateVideo(report)
+				}
+				//// 推送找钢网
+				//if utils.RunMode == "release" && (report.ClassifyNameSecond == "知白守黑日评" || report.ClassifyNameSecond == "股债日评") {
+				//	_ = services.ZhaoGangSend(report)
+				//}
+				// 更新报告Es
+				_ = services.UpdateReportEs(report.Id, 2)
+			}()
+		}
+	}
+	// 发布晨周报部分章节未发布的提示
+	if tips != "" {
+		br.Data = tips
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "发布成功"
+}
+
+// @Title 取消发布报告接口
+// @Description 取消发布报告
+// @Param	request	body models.PublishCancelReq true "type json string"
+// @Success 200 Ret=200 取消发布成功
+// @router /publish/cancle [post]
+func (this *ReportController) PublishCancleReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.PublishCancelReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportIds <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+	publishTimeNullFlag := true
+	reportInfo, err := models.GetReportById(req.ReportIds)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
+		return
+	}
+	if reportInfo.MsgIsSend == 1 {
+		publishTimeNullFlag = false
+	}
+	err = models.PublishCancleReport(req.ReportIds, publishTimeNullFlag)
+	if err != nil {
+		br.Msg = "取消发布失败"
+		br.ErrMsg = "取消发布失败,Err:" + err.Error()
+		return
+	}
+	// 更新ES禁用
+	{
+		go services.UpdateReportEs(req.ReportIds, 1)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "取消发布成功"
+}
+
+// @Title 删除报告接口
+// @Description 删除报告
+// @Param	request	body models.DeleteReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /delete [post]
+func (this *ReportController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.DeleteReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportIds <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+
+	if err := services.DeleteReportAndChapter(req.ReportIds); err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除报告失败, Err: " + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除成功"
+}
+
+// @Title 新增报告接口
+// @Description 新增报告
+// @Param	request	body models.AddReq true "type json string"
+// @Success 200 {object} models.AddResp
+// @router /add [post]
+func (this *ReportController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.AddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	var contentSub string
+	if req.Content != "" {
+		content, e := services.FilterReportContentBr(req.Content)
+		if e != nil {
+			br.Msg = "内容去除前后空格失败"
+			br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+			return
+		}
+		req.Content = content
+
+		contentSub, err = services.GetReportContentSub(req.Content)
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("ContentSub 失败,Err:"+err.Error(), 3)
+			//utils.SendEmail(utils.APPNAME+"失败提醒", "解析 ContentSub 失败,Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+	}
+	maxStage, err := models.GetReportStage(req.ClassifyIdFirst, req.ClassifyIdSecond)
+	if err != nil {
+		br.Msg = "期数获取失败!"
+		br.ErrMsg = "期数获取失败,Err:" + err.Error()
+		return
+	}
+
+	item := new(models.Report)
+	item.AddType = req.AddType
+	item.ClassifyIdFirst = req.ClassifyIdFirst
+	item.ClassifyNameFirst = req.ClassifyNameFirst
+	item.ClassifyIdSecond = req.ClassifyIdSecond
+	item.ClassifyNameSecond = req.ClassifyNameSecond
+	item.Title = req.Title
+	item.Abstract = req.Abstract
+	item.Author = req.Author
+	item.Frequency = req.Frequency
+	item.State = req.State
+	item.Content = html.EscapeString(req.Content)
+	item.Stage = maxStage + 1
+	item.ContentSub = html.EscapeString(contentSub)
+	item.CreateTime = req.CreateTime
+	item.ModifyTime = time.Now()
+	item.ReportVersion = req.ReportVersion
+	item.AdminId = sysUser.AdminId
+	item.AdminRealName = sysUser.RealName
+	newReportId, err := models.AddReport(item)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+	//处理权限
+	{
+		permissionItems, err := models.GetPermission(req.ClassifyNameSecond)
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("获取权限失败,Err:"+err.Error(), 3)
+			//utils.SendEmail(utils.APPNAME+"失败提醒", "获取权限失败,Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+		for _, v := range permissionItems {
+			err = models.AddChartPermissionChapterMapping(v.ChartPermissionId, newReportId)
+			if err != nil {
+				go alarm_msg.SendAlarmMsg("新增权限失败,Err:"+err.Error(), 3)
+				//utils.SendEmail(utils.APPNAME+"失败提醒", "新增权限失败,Err:"+err.Error()+strconv.FormatInt(newReportId, 10), utils.EmailSendToUsers)
+			}
+		}
+	}
+	reportCode := utils.MD5(strconv.Itoa(int(newReportId)))
+	//修改唯一编码
+	{
+		go models.ModifyReportCode(newReportId, reportCode)
+	}
+	resp := new(models.AddResp)
+	resp.ReportId = newReportId
+	resp.ReportCode = reportCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// @Title 编辑报告接口
+// @Description 编辑报告
+// @Param	request	body models.EditReq true "type json string"
+// @Success 200 {object} models.EditResp
+// @router /edit [post]
+func (this *ReportController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.EditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.Content == "" {
+		br.Msg = "报告内容不能为空"
+		return
+	}
+	var contentSub string
+	if req.Content != "" {
+		content, e := services.FilterReportContentBr(req.Content)
+		if e != nil {
+			br.Msg = "内容去除前后空格失败"
+			br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+			return
+		}
+		req.Content = content
+
+		contentSub, err = services.GetReportContentSub(req.Content)
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("解析 ContentSub 失败,Err:"+err.Error(), 3)
+			//utils.SendEmail(utils.APPNAME+"失败提醒", "解析 ContentSub 失败,Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+	}
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	//更新标记key
+	markStatus, err := services.UpdateReportEditMark(int(req.ReportId), sysUser.AdminId, 1, sysUser.RealName)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+	if markStatus.Status == 1 {
+		br.Msg = markStatus.Msg
+		//br.Ret = 202 //202 服务器已接受请求,但尚未处理。
+		return
+	}
+
+	var stage int
+	report, _ := models.GetReportById(int(req.ReportId))
+	if report != nil {
+		if report.ClassifyNameFirst != req.ClassifyNameFirst || report.ClassifyNameSecond != req.ClassifyNameSecond {
+			maxStage, _ := models.GetReportStageEdit(req.ClassifyIdFirst, req.ClassifyIdSecond, int(req.ReportId))
+			maxStage = maxStage + 1
+			stage = maxStage
+		} else {
+			stage = report.Stage
+		}
+	}
+
+	item := new(models.Report)
+	item.ClassifyIdFirst = req.ClassifyIdFirst
+	item.ClassifyNameFirst = req.ClassifyNameFirst
+	item.ClassifyIdSecond = req.ClassifyIdSecond
+	item.ClassifyNameSecond = req.ClassifyNameSecond
+	item.Title = req.Title
+	item.Abstract = req.Abstract
+	item.Author = req.Author
+	item.Frequency = req.Frequency
+	item.State = req.State
+	item.Stage = stage
+	item.Content = html.EscapeString(req.Content)
+	item.ContentSub = html.EscapeString(contentSub)
+	item.CreateTime = req.CreateTime
+	err = models.EditReport(item, req.ReportId)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+	//处理权限
+	{
+		err = models.RemoveChartPermissionChapterMapping(req.ReportId)
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("修改删除报告权限失败,Err:"+err.Error(), 3)
+			//utils.SendEmail(utils.APPNAME+"失败提醒", "修改删除报告权限失败,Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+		permissionItems, err := models.GetPermission(req.ClassifyNameSecond)
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("获取权限失败,Err:"+err.Error(), 3)
+			//utils.SendEmail(utils.APPNAME+"失败提醒", "获取权限失败,Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+		for _, v := range permissionItems {
+			err = models.AddChartPermissionChapterMapping(v.ChartPermissionId, req.ReportId)
+			if err != nil {
+				go alarm_msg.SendAlarmMsg("新增权限失败,Err:"+err.Error(), 3)
+				//utils.SendEmail(utils.APPNAME+"失败提醒", "新增权限失败,Err:"+err.Error()+strconv.FormatInt(req.ReportId, 10), utils.EmailSendToUsers)
+			}
+		}
+	}
+	reportCode := utils.MD5(strconv.Itoa(int(req.ReportId)))
+	resp := new(models.EditResp)
+	resp.ReportId = req.ReportId
+	resp.ReportCode = reportCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// @Title 获取报告详情接口
+// @Description 获取报告详情
+// @Param	request	body models.ReportDetailReq true "type json string"
+// @Success 200 {object} models.Report
+// @router /detail [get]
+func (this *ReportController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	/*var req models.ReportDetailReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}*/
+	reportId, err := this.GetInt("ReportId")
+	if err != nil {
+		br.Msg = "获取参数失败!"
+		br.ErrMsg = "获取参数失败,Err:" + err.Error()
+		return
+	}
+	if reportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+	item, err := models.GetReportById(reportId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	chapterList := make([]*models.ReportChapter, 0)
+	if item.HasChapter == 1 && (item.ChapterType == utils.REPORT_TYPE_DAY || item.ChapterType == utils.REPORT_TYPE_WEEK) {
+		// 获取章节内容
+		tmpChapterList, err := models.GetPublishedChapterListByReportId(item.Id)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取晨/周报章节列表失败, Err: " + err.Error()
+			return
+		}
+
+		if len(tmpChapterList) > 0 {
+			// 获取更新规则
+			researchType := tmpChapterList[0].ReportType
+			chapterTypeList, tmpErr := models.GetAllReportChapterTypeListByResearchType(researchType)
+			if tmpErr != nil {
+				br.Msg = "获取更新规则失败"
+				br.ErrMsg = "获取更新规则失败, Err: " + tmpErr.Error()
+				return
+			}
+			// 调整章节更新
+			nowTime := time.Now().Local()
+			for _, item := range tmpChapterList {
+				stop := false
+				for _, rule := range chapterTypeList {
+					if rule.ReportChapterTypeId == item.TypeId {
+						//fmt.Println("rule.Enabled :", rule.Enabled, ";name=", rule.ReportChapterTypeName, "item.IsEdit:", item.IsEdit, "rule.IsSet:", rule.IsSet)
+						// 如果被永久暂停更新了
+						if rule.Enabled == 0 && item.IsEdit == 0 { //该章节已被永久禁用,同时未被操作过
+							stop = true
+						} else if rule.PauseStartTime != "" && rule.PauseEndTime != "" {
+							startTime, timeErr := time.ParseInLocation(utils.FormatDate, rule.PauseStartTime, time.Local)
+							if timeErr != nil {
+								br.Msg = "获取更新规则失败"
+								br.ErrMsg = "更新规则时间转换失败4001, Err: " + timeErr.Error()
+								return
+							}
+							endTime, timeErr := time.ParseInLocation(utils.FormatDate, rule.PauseEndTime, time.Local)
+							if timeErr != nil {
+								br.Msg = "获取更新规则失败"
+								br.ErrMsg = "更新规则时间转换失败4002, Err: " + timeErr.Error()
+								return
+							}
+							// 暂停更新
+							if nowTime.After(startTime) && nowTime.Before(endTime.AddDate(0, 0, 1)) {
+								stop = true
+							}
+							break
+						}
+					}
+				}
+				if !stop {
+					item.Content = html.UnescapeString(item.Content)
+					item.ContentSub = html.UnescapeString(item.ContentSub)
+					chapterList = append(chapterList, item)
+				}
+			}
+		}
+
+		item.Abstract = item.Title
+	}
+	item.Content = html.UnescapeString(item.Content)
+	item.ContentSub = html.UnescapeString(item.ContentSub)
+
+	resp := &models.ReportDetailView{
+		ReportDetail: item,
+		ChapterList:  chapterList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 图片上传
+// @Description 图片上传接口
+// @Param   file   query   file  true       "文件"
+// @Success 200 新增成功
+// @router /upload [post]
+func (this *ReportController) Upload() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	f, h, err := this.GetFile("file")
+	if err != nil {
+		br.Msg = "获取资源信息失败"
+		br.ErrMsg = "获取资源信息失败,Err:" + err.Error()
+		return
+	}
+	ext := path.Ext(h.Filename)
+	dateDir := time.Now().Format("20060102")
+	uploadDir := utils.STATIC_DIR + "hongze/" + dateDir
+	err = os.MkdirAll(uploadDir, 777)
+	if err != nil {
+		br.Msg = "存储目录创建失败"
+		br.ErrMsg = "存储目录创建失败,Err:" + err.Error()
+		return
+	}
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	fileName := randStr + ext
+	fpath := uploadDir + "/" + fileName
+	defer f.Close() //关闭上传文件
+	err = this.SaveToFile("file", fpath)
+	if err != nil {
+		br.Msg = "文件上传失败"
+		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+		return
+	}
+	//上传到阿里云
+	resourceUrl, err := services.UploadAliyun(fileName, fpath)
+	if err != nil {
+		br.Msg = "文件上传失败"
+		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+		return
+	}
+
+	defer func() {
+		os.Remove(fpath)
+	}()
+
+	item := new(models.Resource)
+	item.ResourceUrl = resourceUrl
+	item.ResourceType = 1
+	item.CreateTime = time.Now()
+	newId, err := models.AddResource(item)
+	if err != nil {
+		br.Msg = "资源上传失败"
+		br.ErrMsg = "资源上传失败,Err:" + err.Error()
+		return
+	}
+	resp := new(models.ResourceResp)
+	resp.Id = newId
+	resp.ResourceUrl = resourceUrl
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}
+
+// @Title 根据分类获取最近一次报告详情接口
+// @Description 根据分类获取最近一次报告详情
+// @Param	request	body models.ClassifyIdDetailReq true "type json string"
+// @Success 200 {object} models.Report
+// @router /classifyIdDetail [get]
+func (this *ReportController) ClassifyIdDetail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	//classify_id_first=34&classify_id_second=36
+
+	/*var req models.ClassifyIdDetailReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}*/
+	classifyIdFirst, _ := this.GetInt("classify_id_first")
+	if classifyIdFirst <= 0 {
+		classifyIdFirst, _ = this.GetInt("ClassifyIdFirst")
+	}
+	classifyIdSecond, _ := this.GetInt("classify_id_second")
+	if classifyIdSecond <= 0 {
+		classifyIdSecond, _ = this.GetInt("ClassifyIdSecond")
+	}
+	item, err := models.GetReportDetailByClassifyId(classifyIdFirst, classifyIdSecond)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败!"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	if item != nil {
+		item.Content = html.UnescapeString(item.Content)
+		item.ContentSub = html.UnescapeString(item.ContentSub)
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = item
+}
+
+// @Title 模板消息推送接口
+// @Description 模板消息推送
+// @Param	request	body models.SendTemplateMsgReq true "type json string"
+// @Success 200 Ret=200 推送成功
+// @router /sendTemplateMsg [post]
+//func (this *ReportController) SendTemplateMsg() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//	var req models.SendTemplateMsgReq
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//	if req.ReportId <= 0 {
+//		br.Msg = "参数错误"
+//		br.ErrMsg = "参数错误"
+//		return
+//	}
+//	report, _ := models.GetReportById(req.ReportId)
+//	if report.MsgIsSend == 1 {
+//		br.Msg = "消息已推送,请勿重复操作"
+//		br.ErrMsg = "模板消息已推送,请勿重复操作"
+//		return
+//	}
+//
+//	videoNameDate := `(` + time.Now().Format("0102") + `)`
+//	err = models.UpdateReportPublishTime(req.ReportId, videoNameDate)
+//	if err != nil {
+//		br.Msg = "修改报告发布时间失败"
+//		br.ErrMsg = "修改发布时间失败,Err:" + err.Error()
+//		return
+//	}
+//	err = models.UpdateReportChapterPublishTime(req.ReportId, videoNameDate)
+//	if err != nil {
+//		br.Msg = "修改章节发布时间失败"
+//		br.ErrMsg = "修改发布时间失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	services.SendMiniProgramReportWxMsg(req.ReportId)
+//	err = models.ModifyReportMsgIsSend(req.ReportId)
+//	if err != nil {
+//		br.Msg = "发送失败"
+//		br.ErrMsg = "发送失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "发送成功"
+//	br.IsAddLog = true
+//}
+
+// Author
+// @Title 获取报告作者接口
+// @Description 获取报告作者
+// @Param   AuthorType   query   int  true       "来源类型,1:中文,2:英文"
+// @Param   StartDate   query   string  true       "开始时间"
+// @Success 200 {object} models.ReportAuthorResp
+// @router /author [get]
+//func (this *ReportController) Author() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//
+//	keyword := this.GetString("Keyword")
+//
+//	var condition string
+//	var pars []interface{}
+//	condition += ` AND author_type = 1 AND enable = 1`
+//
+//	if keyword != `` {
+//		condition += ` AND report_author like ? `
+//		pars = append(pars, "%"+keyword+"%")
+//	}
+//
+//	_, items, err := models.GetReportAuthorList(condition, pars, 0, 10000)
+//	if err != nil {
+//		br.Msg = "获取失败!"
+//		br.ErrMsg = "获取失败,Err:" + err.Error()
+//		return
+//	}
+//	resp := models.ReportAuthorResp{
+//		List: items,
+//	}
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "获取成功"
+//	br.Data = resp
+//}
+
+// @Title 保存草稿
+// @Description 保存草稿
+// @Param	request	body models.SaveReportContent true "type json string"
+// @Success 200 {object} models.ReportAuthorResp
+// @router /saveReportContent [post]
+//func (this *ReportController) SaveReportContent() {
+//	br := new(models.BaseResponse).Init()
+//	br.IsSendEmail = false
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//	var req models.SaveReportContent
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//	reportId := req.ReportId
+//	noChangeFlag := req.NoChange
+//	fmt.Println("reportId:", reportId)
+//	if reportId > 0 {
+//		markStatus, err := services.UpdateReportEditMark(int(req.ReportId), sysUser.AdminId, 1, sysUser.RealName)
+//		if err != nil {
+//			br.Msg = err.Error()
+//			return
+//		}
+//		if markStatus.Status == 1 {
+//			br.Msg = markStatus.Msg
+//			return
+//		}
+//	}
+//	if reportId > 0 && noChangeFlag != 1 {
+//		fmt.Println("line 617")
+//		content := req.Content
+//		if content == "" {
+//			content = this.GetString("Content")
+//		}
+//		if content != "" {
+//			contentClean, e := services.FilterReportContentBr(req.Content)
+//			if e != nil {
+//				br.Msg = "内容去除前后空格失败"
+//				br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+//				return
+//			}
+//			content = contentClean
+//
+//			contentSub, err := services.GetReportContentSub(content)
+//			if err != nil {
+//				go alarm_msg.SendAlarmMsg("解析 ContentSub 失败,Err:"+err.Error(), 3)
+//				//utils.SendEmail(utils.APPNAME+"失败提醒", "解析 ContentSub 失败,Err:"+err.Error(), utils.EmailSendToUsers)
+//			}
+//			content = html.EscapeString(content)
+//			contentSub = html.EscapeString(contentSub)
+//			err = models.EditReportContent(reportId, content, contentSub)
+//			if err != nil {
+//				br.Msg = "保存失败"
+//				br.ErrMsg = "保存失败,Err:" + err.Error()
+//				return
+//			}
+//			go models.AddReportSaveLog(reportId, this.SysUser.AdminId, content, contentSub, this.SysUser.AdminName)
+//		}
+//	}
+//	resp := new(models.SaveReportContentResp)
+//	resp.ReportId = reportId
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "保存成功成功"
+//	br.Data = resp
+//}
+
+// @Title 图片上传
+// @Description 图片上传接口
+// @Param   File   query   file  true       "文件"
+// @Success 200 上传成功
+// @router /uploadImg [post]
+//func (this *ReportUploadCommonController) UploadImg() {
+//	var err error
+//	defer func() {
+//		if err != nil {
+//			fmt.Println("文件上传失败:", err.Error())
+//			go alarm_msg.SendAlarmMsg("URI:"+this.Ctx.Input.URI()+" 文件上传失败:"+err.Error(), 3)
+//			//go utils.SendEmail(utils.APPNAME+"失败提醒", "URI:"+this.Ctx.Input.URI()+" 文件上传失败:"+err.Error(), utils.EmailSendToUsers)
+//		}
+//	}()
+//	f, h, err := this.GetFile("file")
+//	if err != nil {
+//		return
+//	}
+//	ext := path.Ext(h.Filename)
+//	dateDir := time.Now().Format("20060102")
+//	uploadDir := utils.STATIC_DIR + "hongze/" + dateDir
+//	err = os.MkdirAll(uploadDir, 777)
+//	if err != nil {
+//		return
+//	}
+//	randStr := utils.GetRandStringNoSpecialChar(28)
+//	fileName := randStr + ext
+//	fpath := uploadDir + "/" + fileName
+//	defer f.Close() //关闭上传文件
+//	err = this.SaveToFile("file", fpath)
+//	if err != nil {
+//		return
+//	}
+//	//上传到阿里云
+//	resourceUrl, err := services.UploadAliyun(fileName, fpath)
+//	if err != nil {
+//		return
+//	}
+//
+//	defer func() {
+//		os.Remove(fpath)
+//	}()
+//
+//	item := new(models.Resource)
+//	item.ResourceUrl = resourceUrl
+//	item.ResourceType = 1
+//	item.CreateTime = time.Now()
+//	newId, err := models.AddResource(item)
+//	if err != nil {
+//		return
+//	}
+//	resp := new(models.ResourceResp)
+//	resp.Id = newId
+//	resp.ResourceUrl = resourceUrl
+//	this.Data["json"] = map[string]string{"link": resourceUrl}
+//	this.ServeJSON()
+//}
+
+// @Title 研报浏览数据导出
+// @Description 研报浏览数据导出接口
+// @Param   ReportIds   query   string  true       "报告id,多个报告用英文,隔开"
+// @Success 200 {object} company.CompanyListResp
+// @router /report_view_record/export [get]
+//func (this *ReportController) Export() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//
+//	reportIds := this.GetString("ReportIds")
+//	if reportIds == "" {
+//		br.Msg = "请选择需要下载的报告"
+//		br.ErrMsg = "请选择需要下载的报告"
+//		return
+//	}
+//
+//	//生成excel文件
+//	dir, err := os.Executable()
+//	exPath := filepath.Dir(dir)
+//	downLoadnFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+//	xlsxFile := xlsx.NewFile()
+//	if err != nil {
+//		br.Msg = "生成文件失败"
+//		br.ErrMsg = "生成文件失败"
+//		return
+//	}
+//
+//	//只有权益研究员,以及超管可以下载
+//	if sysUser.RoleTypeCode != utils.ROLE_TYPE_CODE_RAI_RESEARCHR && sysUser.RoleTypeCode != utils.ROLE_TYPE_CODE_ADMIN {
+//		br.Msg = "没有下载权限"
+//		br.ErrMsg = "没有下载权限"
+//		return
+//	}
+//
+//	//普通样式
+//	style := xlsx.NewStyle()
+//	alignment := xlsx.Alignment{
+//		Horizontal: "center",
+//		Vertical:   "center",
+//		WrapText:   true,
+//	}
+//	//设置默认字体和文字大小
+//	xlsx.SetDefaultFont(12, "宋体")
+//
+//	style.Alignment = alignment
+//	style.ApplyAlignment = true
+//
+//	//标题样式
+//	titleStyle := xlsx.NewStyle()
+//	titleFont := xlsx.NewFont(20, "宋体")
+//	titleFont.Bold = true
+//	titleStyle.Font = *titleFont
+//	titleStyle.Alignment = alignment
+//	//titleStyle.ApplyAlignment = true
+//
+//	//表头
+//	headerStyle := xlsx.NewStyle()
+//	headerFont := xlsx.NewFont(12, "宋体")
+//	headerFont.Bold = true
+//	headerStyle.Font = *headerFont
+//	headerStyle.Alignment = alignment
+//	headerStyle.ApplyAlignment = true
+//
+//	defer func() {
+//		os.Remove(downLoadnFilePath)
+//	}()
+//
+//	//记录已经命名了的表单名称
+//	sheetNameMap := make(map[string]int)
+//
+//	reportSlice := strings.Split(reportIds, ",")
+//	for _, reportIdStr := range reportSlice {
+//		reportId, convErr := strconv.Atoi(reportIdStr)
+//		if convErr != nil {
+//			br.Msg = "报告传参异常"
+//			br.ErrMsg = "报告传参异常"
+//			return
+//		}
+//
+//		report, reportErr := models.GetReportById(reportId)
+//		if reportErr != nil {
+//			br.Msg = "查询报告异常"
+//			br.ErrMsg = "查询报告异常:" + reportErr.Error()
+//			return
+//		}
+//
+//		//查询浏览记录
+//		_, viewList, err := models.GetViewListByReportId(reportId)
+//		//fmt.Println(total)
+//		//fmt.Println(viewList)
+//		//fmt.Println(err)
+//		if err != nil {
+//			br.Msg = "查询报告浏览记录异常"
+//			br.ErrMsg = "查询报告浏览记录异常:" + err.Error()
+//			return
+//		}
+//
+//		//导入报表
+//		//报表名称
+//		createTime, _ := time.Parse(utils.FormatDateTime, report.CreateTime)
+//		timeStr := createTime.Format("0102")
+//		reportName := report.Title + "(" + timeStr + ")"
+//
+//		//表单名称
+//		sheetName := reportName
+//
+//		//判断当前sheet表单map中是否已经存在该表单名称,如果存在,那么需要添加后缀,并给sheet表单map中赋值次数加1处理;不存在,那么还是使用当前名称,并给sheet表单map中赋值次数为1
+//		if n, ok := sheetNameMap[sheetName]; ok == true {
+//			n++
+//			sheetName = sheetName + "_" + strconv.Itoa(n)
+//			sheetNameMap[sheetName] = n
+//		} else {
+//			sheetNameMap[sheetName] = 1
+//		}
+//
+//		sheel, err := xlsxFile.AddSheet(sheetName)
+//		if err != nil {
+//			br.Msg = "新增Sheet失败"
+//			br.ErrMsg = "新增Sheet失败,Err:" + err.Error()
+//			return
+//		}
+//		//设置列宽
+//		sheel.SetColWidth(0, 0, 28)
+//		sheel.SetColWidth(1, 1, 18)
+//		sheel.SetColWidth(2, 2, 18)
+//		sheel.SetColWidth(3, 3, 18)
+//		sheel.SetColWidth(4, 4, 40)
+//
+//		//标题行
+//		titleRow := sheel.AddRow()
+//		titleRow.SetHeight(40)
+//
+//		//标题列
+//		titleCell := titleRow.AddCell()
+//		titleCell.HMerge = 4 //向右合并列数,不包括自身列
+//
+//		//报表标题名称
+//		titleCell.SetValue(reportName)
+//		titleCell.SetStyle(titleStyle)
+//
+//		//表头
+//		headerRow := sheel.AddRow()
+//		headerRow.SetHeight(18)
+//
+//		timeHeaderCell := headerRow.AddCell()
+//		timeHeaderCell.SetValue("浏览时间")
+//		timeHeaderCell.SetStyle(headerStyle)
+//
+//		nameHeaderCell := headerRow.AddCell()
+//		nameHeaderCell.SetValue("用户名称")
+//		nameHeaderCell.SetStyle(headerStyle)
+//
+//		mobileHeaderCell := headerRow.AddCell()
+//		mobileHeaderCell.SetValue("手机号")
+//		mobileHeaderCell.SetStyle(headerStyle)
+//
+//		emailHeaderCell := headerRow.AddCell()
+//		emailHeaderCell.SetValue("邮箱")
+//		emailHeaderCell.SetStyle(headerStyle)
+//
+//		companyHeaderCell := headerRow.AddCell()
+//		companyHeaderCell.SetValue("所属企业客户名称")
+//		companyHeaderCell.SetStyle(headerStyle)
+//
+//		for _, v := range viewList {
+//			dataRow := sheel.AddRow()
+//			dataRow.SetHeight(18)
+//
+//			timeCell := dataRow.AddCell()
+//			timeCell.SetString(v.CreateTime.Format(utils.FormatDateTime))
+//			timeCell.SetStyle(style)
+//
+//			nameCell := dataRow.AddCell()
+//			nameCell.SetString(v.RealName)
+//			nameCell.SetStyle(style)
+//
+//			mobileCell := dataRow.AddCell()
+//			mobileCell.SetString(v.Mobile)
+//			mobileCell.SetStyle(style)
+//
+//			emailCell := dataRow.AddCell()
+//			emailCell.SetString(v.Email)
+//			emailCell.SetStyle(style)
+//
+//			companyCell := dataRow.AddCell()
+//			companyCell.SetString(v.CompanyName)
+//			companyCell.SetStyle(style)
+//		}
+//	}
+//
+//	err = xlsxFile.Save(downLoadnFilePath)
+//	if err != nil {
+//		br.Msg = "保存文件失败"
+//		br.ErrMsg = "保存文件失败"
+//		return
+//	}
+//	randStr := time.Now().Format(utils.FormatDateTimeUnSpace)
+//	downloadFileName := "研报浏览记录" + randStr + ".xlsx"
+//	this.Ctx.Output.Download(downLoadnFilePath, downloadFileName)
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "导出成功"
+//}
+
+// @Title 客群消息推送接口
+// @Description 客群消息推送接口
+// @Param	request	body models.SendTemplateMsgReq true "type json string"
+// @Success 200 Ret=200 推送成功
+// @router /ths/sendTemplateMsg [post]
+//func (this *ReportController) ThsSendTemplateMsg() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//	var req models.ThsSendTemplateMsgReq
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//	for _, v := range req.ReportId {
+//		if v <= 0 {
+//			br.Msg = "参数错误"
+//			br.ErrMsg = "参数错误"
+//			return
+//		}
+//
+//		//加锁,避免重复点击造成的多次推送
+//		{
+//			redisKey := fmt.Sprint("report:send:ths:", v)
+//			ok := utils.Rc.SetNX(redisKey, 1, time.Second*300)
+//			if !ok {
+//				br.Msg = "报告已推送客群,请勿重复推送"
+//				return
+//			}
+//			defer func() {
+//				utils.Rc.Delete(redisKey)
+//			}()
+//		}
+//
+//		report, err := models.GetReportById(v)
+//		if err != nil {
+//			if err.Error() == utils.ErrNoRow() {
+//				br.Msg = "报告已经被删除,请刷新页面"
+//				br.ErrMsg = "报告已经被删除,请刷新页面,req:" + string(this.Ctx.Input.RequestBody)
+//				return
+//			}
+//			br.Msg = "获取报告信息失败"
+//			br.ErrMsg = "获取报告信息失败,req:" + string(this.Ctx.Input.RequestBody)
+//			return
+//		}
+//		if report.ThsMsgIsSend == 1 {
+//			br.Msg = "消息已推送,请勿重复操作"
+//			br.ErrMsg = "模板消息已推送,请勿重复操作1"
+//			return
+//		}
+//		// 周报无客群
+//		if report.HasChapter == 1 && report.ChapterType == utils.REPORT_TYPE_WEEK {
+//			br.Msg = "周报无需推送客群消息"
+//			return
+//		}
+//		//err = services.SendReportMiniToThs(report)
+//		//if err != nil {
+//		//	br.Msg = "消息已推送,请勿重复操作"
+//		//	br.ErrMsg = "模板消息已推送,请勿重复操作2, Err: " + err.Error()
+//		//	return
+//		//}
+//		err = models.ModifyReportThsMsgIsSend(report)
+//		if err != nil {
+//			br.Msg = "发送失败"
+//			br.ErrMsg = "发送失败,Err:" + err.Error()
+//			return
+//		}
+//	}
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "发送成功"
+//}
+
+// GetDayWeekReportChapterTypeList
+// @Title 获取晨报周报章节类型列表
+// @Description 获取晨报周报章节类型列表
+// @Param	StopType	query	string	true	"停更类型 stop; disable;"
+// @Success 200 {object} models.ReportListResp
+// @router /getDayWeekReportChapterTypeList [get]
+//func (this *ReportController) GetDayWeekReportChapterTypeList() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//
+//	var condition string
+//	var pars []interface{}
+//	// 停更类型
+//	stopType := this.GetString("StopType")
+//	switch stopType {
+//	case "", "stop":
+//		condition += ` AND enabled = ? `
+//		pars = append(pars, 1)
+//	case "disable":
+//	default:
+//		br.Msg = "停更类型异常"
+//		br.IsSendEmail = false
+//		return
+//	}
+//
+//	dayList := make([]*models.ReportChapterType, 0)
+//	weekList := make([]*models.ReportChapterType, 0)
+//
+//	// 晨报
+//	{
+//		tmpCondition := condition
+//		tmpPars := pars
+//		// 报告类型
+//		tmpCondition += ` AND research_type = ? `
+//		tmpPars = append(tmpPars, "day")
+//
+//		list, err := models.GetAllReportChapterTypeList(tmpCondition, tmpPars)
+//		if err != nil {
+//			br.Msg = "获取报告章节类型列表失败"
+//			br.ErrMsg = "获取报告章节类型列表失败, Err: " + err.Error()
+//			return
+//		}
+//		nowTime := time.Now()
+//		for _, v := range list {
+//			if v.IsSet == 1 {
+//				endTime, _ := time.Parse(utils.FormatDate, v.PauseEndTime)
+//				if nowTime.After(endTime.AddDate(0, 0, 1)) { //设置过期了已经
+//					v.IsSet = 0
+//					v.PauseStartTime = ``
+//					v.PauseEndTime = ``
+//				}
+//			}
+//			dayList = append(dayList, v)
+//		}
+//	}
+//
+//	// 周报
+//	{
+//		tmpCondition := condition
+//		tmpPars := pars
+//		// 报告类型
+//		tmpCondition += ` AND research_type = ? `
+//		tmpPars = append(tmpPars, "week")
+//
+//		list, err := models.GetAllReportChapterTypeList(tmpCondition, tmpPars)
+//		if err != nil {
+//			br.Msg = "获取报告章节类型列表失败"
+//			br.ErrMsg = "获取报告章节类型列表失败, Err: " + err.Error()
+//			return
+//		}
+//		nowTime := time.Now()
+//		for _, v := range list {
+//			if v.IsSet == 1 {
+//				endTime, _ := time.Parse(utils.FormatDate, v.PauseEndTime)
+//				if nowTime.After(endTime.AddDate(0, 0, 1)) { //设置过期了已经
+//					v.IsSet = 0
+//					v.PauseStartTime = ``
+//					v.PauseEndTime = ``
+//				}
+//			}
+//			weekList = append(weekList, v)
+//		}
+//	}
+//
+//	resp := models.UpdateReportChapterTypeResp{
+//		Day:  dayList,
+//		Week: weekList,
+//	}
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "获取成功"
+//	br.Data = resp
+//}
+
+// @Title 获取晨报周报的更新暂停时间
+// @Description 获取晨报周报的更新暂停时间
+// @Success 200 {object} models.ReportListResp
+// @router /getDayWeekReportPauseTime [get]
+//func (this *ReportController) GetDayWeekReportPauseTime() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//
+//	list, err := models.GetDayWeekReportPauseTimeList()
+//	if err != nil {
+//		br.Msg = "获取晨报周报的更新暂停时间失败"
+//		br.ErrMsg = "获取晨报周报的更新暂停时间失败, Err: " + err.Error()
+//		return
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "获取成功"
+//	br.Data = list
+//}
+
+// SetDayWeekReportUpdateRule
+// @Title 设置晨报周报的更新规则
+// @Description 设置晨报周报的更新规则
+// @Param	request	body models.SetDayWeekReportUpdateRuleReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /setDayWeekReportUpdateRule [post]
+//func (this *ReportController) SetDayWeekReportUpdateRule() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//
+//	var req models.SetDayWeekReportUpdateRuleReq
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	researchType := req.ResearchType
+//	if researchType == "" {
+//		br.Msg = "报告类型不能为空"
+//		return
+//	}
+//
+//	// 设置章节类型的暂停时间
+//	if err := models.SetDayWeekReportUpdateRule(researchType, req.List); err != nil {
+//		br.Msg = "设置暂停时间失败"
+//		br.ErrMsg = "设置暂停时间失败, Err: " + err.Error()
+//		return
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "操作成功"
+//}
+
+// SetDayWeekReportEnableRule
+// @Title 设置晨报周报的永久暂停更新规则
+// @Description 设置晨报周报的永久暂停更新规则
+// @Param	request	body models.SetDayWeekReportEnableUpdateRuleReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /setDayWeekReportEnableUpdateRule [post]
+//func (this *ReportController) SetDayWeekReportEnableRule() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//
+//	var req models.SetDayWeekReportEnableUpdateRuleReq
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	dayReportChapterTypeIdList := strings.Split(req.DayReportChapterTypeId, ",")
+//	weekReportChapterTypeIdList := strings.Split(req.WeekReportChapterTypeId, ",")
+//	//if len(reportChapterTypeIdList) <= 0 {
+//	//	br.Msg = "报告类型选择异常"
+//	//	br.IsSendEmail = false
+//	//	return
+//	//}
+//
+//	// 设置章节类型的禁用状态
+//	if err := models.SetDayWeekReportEnableUpdateRule(dayReportChapterTypeIdList, weekReportChapterTypeIdList); err != nil {
+//		br.Msg = "设置永久停更失败"
+//		br.ErrMsg = "设置永久停更失败, Err: " + err.Error()
+//		return
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "操作成功"
+//}
+
+// @Title 新增晨报周报
+// @Description 新增晨报周报
+// @Param	request	body models.SaveDayWeekReportReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /addDayWeekReport [post]
+func (this *ReportController) AddDayWeekReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.SaveDayWeekReportReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	reportType := req.ReportType
+	if reportType == "" {
+		br.Msg = "请选择报告类型"
+		return
+	}
+	if reportType != utils.REPORT_TYPE_DAY && reportType != utils.REPORT_TYPE_WEEK {
+		br.Msg = "报告类型有误"
+		return
+	}
+	if req.Title == "" {
+		br.Msg = "请输入报告标题"
+		return
+	}
+	if req.CreateTime == "" {
+		br.Msg = "请选择发布时间"
+		return
+	}
+
+	// 报告
+	var frequency, classifyName string
+	var classifyId int
+	if reportType == utils.REPORT_TYPE_DAY {
+		frequency = "日度"
+		classify, err := models.GetClassifyByKeyword("晨报")
+		if err == nil {
+			classifyId = classify.Id
+			classifyName = "晨报"
+		}
+	} else if reportType == utils.REPORT_TYPE_WEEK {
+		frequency = "周度"
+		classify, err := models.GetClassifyByKeyword("周报")
+		if err == nil {
+			classifyId = classify.Id
+			classifyName = "周报"
+		}
+	} else {
+		br.Msg = "报告类型有误"
+		return
+	}
+
+	// 获取晨周报期数
+	yearStart := time.Date(time.Now().Local().Year(), 1, 1, 0, 0, 0, 0, time.Local)
+	maxStage, err := models.GetDayWeekReportStage(classifyId, yearStart)
+	if err != nil {
+		br.Msg = "获取报告期数失败"
+		br.ErrMsg = "获取报告期数失败,Err:" + err.Error()
+		return
+	}
+	stage := maxStage + 1
+
+	item := new(models.Report)
+	item.AddType = 1
+	item.ClassifyIdFirst = classifyId
+	item.ClassifyNameFirst = classifyName
+	item.Title = req.Title
+	item.Author = req.Author
+	item.Frequency = frequency
+	item.State = 1
+	item.Stage = stage
+	item.CreateTime = req.CreateTime
+	item.ModifyTime = time.Now()
+	item.HasChapter = 1
+	item.ChapterType = reportType
+
+	// 章节类型列表
+	typeList, err := models.GetReportChapterTypeListByResearchType(reportType)
+	if err != nil {
+		br.Msg = "获取报告章节类型列表失败"
+		br.ErrMsg = "获取报告章节类型列表失败,Err:" + err.Error()
+		return
+	}
+	var chapterList []*models.ReportChapter
+	// 晨报自动继承上一期内容
+	skip := false
+	if reportType == utils.REPORT_TYPE_DAY {
+		lastDayReport, err := models.GetLastPublishDayWeekReport(utils.REPORT_TYPE_DAY)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取上一期晨报失败"
+			br.ErrMsg = "获取上一期晨报失败,Err:" + err.Error()
+			return
+		}
+		if lastDayReport != nil {
+			// 继承上一篇章节内容
+			lastReportChapters, err := models.GetPublishedChapterListByReportId(lastDayReport.Id)
+			if err != nil {
+				br.Msg = "获取上一期晨报章节失败"
+				br.ErrMsg = "获取上一期晨报章节失败,Err:" + err.Error()
+				return
+			}
+			chapterMap := make(map[int]*models.ReportChapter)
+			for i := 0; i < len(lastReportChapters); i++ {
+				chapterMap[lastReportChapters[i].TypeId] = lastReportChapters[i]
+			}
+			for _, typeItem := range typeList {
+				v := chapterMap[typeItem.ReportChapterTypeId]
+				chapterItem := new(models.ReportChapter)
+				if v != nil {
+					chapterItem.AddType = 2
+					chapterItem.Title = v.Title
+					chapterItem.ReportType = reportType
+					chapterItem.ClassifyIdFirst = classifyId
+					chapterItem.ClassifyNameFirst = classifyName
+					chapterItem.TypeId = typeItem.ReportChapterTypeId
+					chapterItem.TypeName = typeItem.ReportChapterTypeName
+					chapterItem.Content = v.Content
+					chapterItem.ContentSub = v.ContentSub
+					chapterItem.Stage = stage
+					chapterItem.PublishState = 1
+					chapterItem.Sort = typeItem.Sort
+					chapterItem.CreateTime = req.CreateTime
+					chapterItem.ModifyTime = time.Now()
+				} else {
+					chapterItem.AddType = 1
+					chapterItem.ReportType = reportType
+					chapterItem.ClassifyIdFirst = classifyId
+					chapterItem.ClassifyNameFirst = classifyName
+					chapterItem.TypeId = typeItem.ReportChapterTypeId
+					chapterItem.TypeName = typeItem.ReportChapterTypeName
+					chapterItem.Stage = stage
+					chapterItem.PublishState = 1
+					chapterItem.Sort = typeItem.Sort
+					chapterItem.CreateTime = req.CreateTime
+					chapterItem.ModifyTime = time.Now()
+				}
+				chapterList = append(chapterList, chapterItem)
+			}
+			skip = true
+		}
+	}
+	if !skip {
+		// 空白章节
+		for _, typeItem := range typeList {
+			chapterItem := new(models.ReportChapter)
+			chapterItem.AddType = 1
+			chapterItem.ReportType = reportType
+			chapterItem.ClassifyIdFirst = classifyId
+			chapterItem.ClassifyNameFirst = classifyName
+			chapterItem.TypeId = typeItem.ReportChapterTypeId
+			chapterItem.TypeName = typeItem.ReportChapterTypeName
+			chapterItem.Stage = stage
+			chapterItem.PublishState = 1
+			chapterItem.Sort = typeItem.Sort
+			chapterItem.CreateTime = req.CreateTime
+			chapterItem.ModifyTime = time.Now()
+			chapterList = append(chapterList, chapterItem)
+		}
+	}
+
+	// 新增报告及章节
+	var reportId int64
+	if reportId, err = models.AddReportAndChapter(item, chapterList); err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "新增报告及章节失败, Err: " + err.Error()
+		return
+	}
+	reportCode := utils.MD5(strconv.Itoa(int(reportId)))
+	// 修改唯一编码
+	{
+		go models.ModifyReportCode(reportId, reportCode)
+	}
+
+	resp := new(models.AddResp)
+	resp.ReportId = reportId
+	resp.ReportCode = reportCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// @Title 编辑晨周报
+// @Description 编辑晨周报
+// @Param	request	body models.SaveDayWeekReportReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /editDayWeekReport [post]
+func (this *ReportController) EditDayWeekReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.SaveDayWeekReportReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ReportId == 0 {
+		br.Msg = "报告ID有误"
+		return
+	}
+	if req.ReportType == "" {
+		br.Msg = "请选择报告类型"
+		return
+	}
+	if req.ReportType != utils.REPORT_TYPE_DAY && req.ReportType != utils.REPORT_TYPE_WEEK {
+		br.Msg = "报告类型有误"
+		return
+	}
+	if req.Title == "" {
+		br.Msg = "请输入报告标题"
+		return
+	}
+	if req.CreateTime == "" {
+		br.Msg = "请选择发布时间"
+		return
+	}
+
+	reportInfo, err := models.GetReportByReportId(req.ReportId)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败, Err: " + err.Error()
+		return
+	}
+	if req.ReportType != reportInfo.ChapterType {
+		br.Msg = "暂不允许修改晨周报类型"
+		return
+	}
+
+	reportInfo.Title = req.Title
+	reportInfo.CreateTime = req.CreateTime
+	reportInfo.Author = req.Author
+	err = models.EditReport(reportInfo, int64(req.ReportId))
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// @Title 获取报告章节列表
+// @Description 获取报告章节列表
+// @Param   ReportId	query	string	true	"报告ID"
+// @Success 200 {object} company.CompanyListResp
+// @router /getReportChapterList [get]
+func (this *ReportController) GetReportChapterList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	reqReportId := this.GetString("ReportId")
+	reportId, _ := strconv.Atoi(reqReportId)
+	if reportId <= 0 {
+		br.Msg = "报告ID有误"
+		return
+	}
+
+	// 获取章节列表
+	chapterList, err := models.GetChapterListByReportId(reportId)
+	if err != nil {
+		br.Msg = "获取章节列表失败"
+		br.ErrMsg = "获取章节列表失败, Err: " + err.Error()
+		return
+	}
+	typeList, err := models.GetReportChapterTypeList()
+	if err != nil {
+		br.Msg = "获取章节类型列表失败"
+		br.ErrMsg = "获取章节类型列表失败, Err: " + err.Error()
+		return
+	}
+	typeIdImg := make(map[int]string, 0)
+	for i := 0; i < len(typeList); i++ {
+		typeIdImg[typeList[i].ReportChapterTypeId] = typeList[i].EditImgUrl
+	}
+
+	resp := make([]*models.ReportChapterResp, 0)
+	if len(chapterList) > 0 {
+		// 获取更新规则
+		researchType := chapterList[0].ReportType
+		chapterTypeList, tmpErr := models.GetAllReportChapterTypeListByResearchType(researchType)
+		if tmpErr != nil {
+			br.Msg = "获取更新规则失败"
+			br.ErrMsg = "获取更新规则失败, Err: " + tmpErr.Error()
+			return
+		}
+		// 调整章节更新
+		nowTime := time.Now().Local()
+		for _, item := range chapterList {
+			stop := false
+			for _, rule := range chapterTypeList {
+				if rule.ReportChapterTypeId == item.TypeId {
+					//fmt.Println("rule.Enabled :", rule.Enabled, ";name=", rule.ReportChapterTypeName, "item.IsEdit:", item.IsEdit, "rule.IsSet:", rule.IsSet)
+					// 如果被永久暂停更新了
+					if rule.Enabled == 0 && item.IsEdit == 0 { //该章节已被永久禁用,同时未被操作过
+						stop = true
+					} else if rule.PauseStartTime != "" && rule.PauseEndTime != "" && rule.PauseStartTime != utils.EmptyDateStr && rule.PauseEndTime != utils.EmptyDateStr {
+						startTime, timeErr := time.ParseInLocation(utils.FormatDate, rule.PauseStartTime, time.Local)
+						if timeErr != nil {
+							br.Msg = "获取更新规则失败"
+							br.ErrMsg = "更新规则时间转换失败4001, Err: " + timeErr.Error()
+							return
+						}
+						endTime, timeErr := time.ParseInLocation(utils.FormatDate, rule.PauseEndTime, time.Local)
+						if timeErr != nil {
+							br.Msg = "获取更新规则失败"
+							br.ErrMsg = "更新规则时间转换失败4002, Err: " + timeErr.Error()
+							return
+						}
+						// 暂停更新
+						if nowTime.After(startTime) && nowTime.Before(endTime.AddDate(0, 0, 1)) {
+							stop = true
+						}
+						break
+					}
+				}
+			}
+			if !stop {
+				resp = append(resp, &models.ReportChapterResp{
+					ReportChapterId:  item.ReportChapterId,
+					ReportId:         item.ReportId,
+					ReportType:       item.ReportType,
+					TypeId:           item.TypeId,
+					TypeName:         item.TypeName,
+					TypeEditImg:      typeIdImg[item.TypeId],
+					Title:            item.Title,
+					IsEdit:           item.IsEdit,
+					Trend:            item.Trend,
+					Sort:             item.Sort,
+					PublishState:     item.PublishState,
+					VideoUrl:         item.VideoUrl,
+					VideoName:        item.VideoName,
+					VideoPlaySeconds: item.VideoPlaySeconds,
+					VideoSize:        item.VideoSize,
+					VideoKind:        item.VideoKind,
+					ModifyTime:       item.ModifyTime.Format(utils.FormatDate),
+				})
+			}
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// @Title 获取上一篇已发表的晨周报章节内容
+// @Description 获取上一篇已发表的晨周报章节内容
+// @Param   TypeId		query	int		true	"品种ID"
+// @Param   ReportType	query   string  true	"报告类型 day-晨报; week-周报; "
+// @Success 200 {object} company.CompanyListResp
+// @router /getLastDayWeekReportChapter [get]
+//func (this *ReportController) GetLastDayWeekReportChapter() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//
+//	typeId, _ := this.GetInt("TypeId")
+//	reportType := this.GetString("ReportType")
+//	item, err := models.GetLastPublishedReportChapter(typeId, reportType)
+//	if err != nil && err.Error() != utils.ErrNoRow() {
+//		br.Msg = "获取失败"
+//		br.ErrMsg = "获取上一篇晨周报失败, Err: " + err.Error()
+//		return
+//	}
+//	if item != nil {
+//		item.Content = html.UnescapeString(item.Content)
+//		item.ContentSub = html.UnescapeString(item.ContentSub)
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "获取成功"
+//	br.Data = item
+//}
+
+// @Title 获取晨周报章节信息
+// @Description 获取晨周报章节信息
+// @Param	ReportChapterId  query  int  true  "报告章节ID"
+// @Success 200 Ret=200 保存成功
+// @router /getDayWeekChapter [get]
+func (this *ReportController) GetDayWeekChapter() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	reportChapterId, _ := this.GetInt("ReportChapterId")
+	if reportChapterId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+	chapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "获取章节信息失败"
+		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+		return
+	}
+	if chapterInfo != nil {
+		chapterInfo.Content = html.UnescapeString(chapterInfo.Content)
+		chapterInfo.ContentSub = html.UnescapeString(chapterInfo.ContentSub)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = chapterInfo
+}
+
+// @Title 编辑晨周报章节内容
+// @Description 编辑晨周报章节内容
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /editDayWeekChapter [post]
+func (this *ReportController) EditDayWeekChapter() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EditReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportChapterId := req.ReportChapterId
+	if reportChapterId <= 0 {
+		br.Msg = "报告章节ID有误"
+		return
+	}
+	//if req.Title == "" {
+	//	br.Msg = "请输入标题"
+	//	return
+	//}
+	if req.Content == "" {
+		br.Msg = "请输入内容"
+		return
+	}
+	if req.AddType == 0 {
+		br.Msg = "请选择新增方式"
+		return
+	}
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "报告章节信息有误"
+		br.ErrMsg = "报告章节信息有误, Err: " + err.Error()
+		return
+	}
+	reqTickerList := req.TickerList
+	newContent := req.Content
+	// 更新章节及指标
+	contentSub := ""
+	if req.Content != "" {
+		contentClean, e := services.FilterReportContentBr(req.Content)
+		if e != nil {
+			br.Msg = "内容去除前后空格失败"
+			br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+			return
+		}
+		req.Content = contentClean
+
+		contentSub, err = services.GetReportContentSub(req.Content)
+		if err != nil {
+			br.Msg = "内容分段解析失败"
+			br.ErrMsg = "编辑报告章节-解析 ContentSub 失败, Err: " + err.Error()
+			return
+		}
+	}
+	if req.Content == "" {
+		req.Content = newContent
+	}
+	reportChapterInfo.Title = req.Title
+	reportChapterInfo.AddType = req.AddType
+	reportChapterInfo.Author = req.Author
+	reportChapterInfo.Content = html.EscapeString(req.Content)
+	reportChapterInfo.ContentSub = html.EscapeString(contentSub)
+	reportChapterInfo.IsEdit = 1
+	reportChapterInfo.CreateTime = req.CreateTime
+	reportChapterInfo.VideoUrl = req.VideoUrl
+	reportChapterInfo.VideoName = req.VideoName
+	reportChapterInfo.VideoPlaySeconds = req.VideoPlaySeconds
+	reportChapterInfo.VideoSize = req.VideoSize
+	reportChapterInfo.VideoKind = 1
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "Title", "AddType", "Author", "Content", "ContentSub", "IsEdit", "CreateTime")
+	if req.VideoUrl != "" {
+		updateCols = append(updateCols, "VideoUrl", "VideoName", "VideoSize", "VideoPlaySeconds", "VideoKind")
+	}
+	// 晨报更新指标
+	tickerList := make([]*models.ReportChapterTicker, 0)
+	if reportChapterInfo.ReportType == "day" && len(reqTickerList) > 0 {
+		nowTime := time.Now()
+		for i := 0; i < len(reqTickerList); i++ {
+			tickerList = append(tickerList, &models.ReportChapterTicker{
+				ReportChapterId: reportChapterInfo.ReportChapterId,
+				Sort:            reqTickerList[i].Sort,
+				Ticker:          reqTickerList[i].Ticker,
+				CreateTime:      nowTime,
+				UpdateTime:      nowTime,
+			})
+		}
+	}
+	err = models.UpdateChapterAndTicker(reportChapterInfo, updateCols, tickerList)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// @Title 获取章节趋势标签列表
+// @Description 获取章节趋势标签列表
+// @Success 200 Ret=200 获取成功
+// @router /getChapterTrendTag [get]
+func (this *ReportController) GetChapterTrendTag() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	list, err := models.GetKeyWordListByFrom("trend")
+	if err != nil {
+		br.Msg = "获取章节趋势标签列表失败"
+		br.ErrMsg = "获取章节趋势标签列表失败, Err: " + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// @Title 编辑章节趋势标签
+// @Description 编辑章节趋势标签
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /editChapterTrendTag [post]
+func (this *ReportController) EditChapterTrendTag() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EditChapterTrendTagReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportChapterId <= 0 {
+		br.Msg = "章节ID有误"
+		return
+	}
+	if req.Trend == "" {
+		br.Msg = "请输入标签"
+		return
+	}
+
+	chapterInfo, err := models.GetReportChapterInfoById(req.ReportChapterId)
+	if err != nil {
+		br.Msg = "获取章节信息失败"
+		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+		return
+	}
+
+	// 更新章节标签
+	chapterInfo.Trend = req.Trend
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "Trend")
+	if err = chapterInfo.UpdateChapter(updateCols); err != nil {
+		br.Msg = "更新标签失败"
+		br.ErrMsg = "更新标签失败, Err: " + err.Error()
+		return
+	}
+
+	// 添加关键词
+	if err = models.AddTrendTagKeyWord(req.Trend); err != nil {
+		br.Msg = "添加标签关键词失败"
+		br.ErrMsg = "添加标签关键词失败, Err: " + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// @Title 获取晨报数据指标列表
+// @Description 获取晨报数据指标列表
+// @Param   ReportChapterId  query  int  true  "章节ID"
+// @Success 200 Ret=200 保存成功
+// @router /getDayReportTickerList [get]
+//func (this *ReportController) GetDayReportTickerList() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//
+//	reportChapterId, _ := this.GetInt("ReportChapterId")
+//	if reportChapterId <= 0 {
+//		br.Msg = "章节ID有误"
+//		return
+//	}
+//	chapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+//	if err != nil {
+//		br.Msg = "获取章节信息失败"
+//		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+//		return
+//	}
+//
+//	// 获取ticker列表
+//	tickerList, err := models.GetDailyBaseColumnList("", chapterInfo.TypeId)
+//	if err != nil {
+//		br.Msg = "获取指标列表失败"
+//		br.ErrMsg = "获取指标列表失败, Err: " + err.Error()
+//		return
+//	}
+//	// 获取章节ticker
+//	reportTicker, err := models.GetTickerListByReportChapterId(reportChapterId)
+//	if err != nil {
+//		br.Msg = "获取章节Ticker失败"
+//		br.ErrMsg = "获取章节Ticker失败, Err: " + err.Error()
+//		return
+//	}
+//	selectMap := make(map[string]int, 0)
+//	for i := 0; i < len(reportTicker); i++ {
+//		selectMap[reportTicker[i].Ticker] = 1
+//	}
+//	// 选中状态
+//	for _, item := range tickerList {
+//		if _, in := selectMap[item.BaseColumnTicker]; in {
+//			item.Selected = 1
+//		}
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "获取成功"
+//	br.Data = tickerList
+//}
+
+// @Title 获取晨周报音视频列表
+// @Description 获取晨周报音视频列表
+// @Param   ReportId  query  int  true  "报告ID"
+// @Success 200 Ret=200 保存成功
+// @router /getDayWeekReportVideoList [get]
+//func (this *ReportController) GetDayWeekReportVideoList() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//
+//	reportId, _ := this.GetInt("ReportId")
+//	if reportId <= 0 {
+//		br.Msg = "参数有误"
+//		return
+//	}
+//	list, err := models.GetReportChapterVideoList(reportId)
+//	if err != nil {
+//		br.Msg = "获取失败"
+//		br.ErrMsg = "获取报告音频列表失败, Err: " + err.Error()
+//		return
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "获取成功"
+//	br.Data = list
+//}
+
+// @Title 是否为晨周报本期最后一个发布的报告
+// @Description 是否为晨周报本期最后一个发布的报告
+// @Param	ReportChapterId  query  int  true  "报告章节ID"
+// @Success 200 Ret=200 获取成功
+// @router /isLastDayWeekReportChapter [get]
+//func (this *ReportController) IsLastDayWeekReportChapter() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//
+//	reportChapterId, _ := this.GetInt("ReportChapterId")
+//	if reportChapterId <= 0 {
+//		br.Msg = "参数有误"
+//		return
+//	}
+//	chapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+//	if err != nil {
+//		br.Msg = "章节信息有误"
+//		br.ErrMsg = "章节信息有误, Err: " + err.Error()
+//		return
+//	}
+//	// 获取已发布章节数及本期报告应发布数
+//	publishedNum, err := models.CountPublishedChapterNum(chapterInfo.ReportId)
+//	if err != nil {
+//		br.Msg = "获取已发布章节数失败"
+//		br.ErrMsg = "获取已发布章节数失败, Err: " + err.Error()
+//		return
+//	}
+//	enableTypeList, err := models.GetEnableReportChapterTypeList(chapterInfo.ReportType)
+//	if err != nil {
+//		br.Msg = "获取章节类型列表失败"
+//		br.ErrMsg = "获取章节类型列表失败, Err: " + err.Error()
+//		return
+//	}
+//	var isLast bool
+//	enableNum := len(enableTypeList)
+//	publishedNum += 1
+//	if publishedNum == enableNum {
+//		isLast = true
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "获取成功"
+//	br.Data = isLast
+//}
+
+// @Title 发布晨周报章节
+// @Description 发布晨周报章节
+// @Param	request	body models.PublishReportChapterReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /publishDayWeekReportChapter [post]
+//func (this *ReportController) PublishDayWeekReportChapter() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//	sysUser := this.SysUser
+//	if sysUser == nil {
+//		br.Msg = "请登录"
+//		br.ErrMsg = "请登录,SysUser Is Empty"
+//		br.Ret = 408
+//		return
+//	}
+//
+//	var req models.PublishReportChapterReq
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//	if req.ReportChapterId <= 0 {
+//		br.Msg = "参数有误"
+//		return
+//	}
+//	if req.Title == "" {
+//		br.Msg = "请输入标题"
+//		return
+//	}
+//	if req.Content == "" {
+//		br.Msg = "请输入内容"
+//		return
+//	}
+//	if req.AddType == 0 {
+//		br.Msg = "请选择新增方式"
+//		return
+//	}
+//
+//	reqTickerList := req.TickerList
+//
+//	chapterInfo, err := models.GetReportChapterInfoById(req.ReportChapterId)
+//	if err != nil {
+//		br.Msg = "章节信息有误"
+//		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+//		return
+//	}
+//
+//	reportInfo, err := models.GetReportById(chapterInfo.ReportId)
+//	if err != nil {
+//		br.Msg = "查询报告有误"
+//		br.ErrMsg = "查询报告信息失败, Err: " + err.Error()
+//		return
+//	}
+//
+//	// 获取规则配置
+//	reportChapterTypeRule, err := models.GetReportChapterTypeById(chapterInfo.TypeId)
+//	if err != nil {
+//		br.Msg = "获取配置信息异常"
+//		br.ErrMsg = "获取配置信息异常, Err: " + err.Error()
+//		return
+//	}
+//	if reportChapterTypeRule.Enabled == 0 {
+//		br.Msg = "该章节已永久停更"
+//		br.ErrMsg = "该章节已永久停更 "
+//		br.IsSendEmail = false
+//		return
+//	}
+//
+//	updateCols := make([]string, 0)
+//	updateCols = append(updateCols, "Title", "AddType", "Author", "Content", "ContentSub", "IsEdit", "CreateTime", "PublishState", "PublishTime")
+//
+//	nowTime := time.Now()
+//	var publishTime time.Time
+//	if reportInfo.MsgIsSend == 1 && reportInfo.PublishTime != "" { //如果报告曾经发布过,并且已经发送过模版消息,则章节的发布时间为报告的发布时间
+//		publishTime, _ = time.ParseInLocation(utils.FormatDateTime, reportInfo.PublishTime, time.Local)
+//	} else {
+//		publishTime = time.Now()
+//	}
+//	if req.VideoUrl != "" {
+//		chapterInfo.VideoUrl = req.VideoUrl
+//		chapterInfo.VideoName = req.VideoName
+//		chapterInfo.VideoSize = req.VideoSize
+//		chapterInfo.VideoPlaySeconds = req.VideoPlaySeconds
+//		chapterInfo.VideoKind = 1
+//
+//		updateCols = append(updateCols, "VideoUrl", "VideoName", "VideoSize", "VideoPlaySeconds", "VideoKind")
+//	} else {
+//		if chapterInfo.VideoUrl == "" {
+//			// 生成video
+//			videoUrl, videoName, videoSize, videoPlaySeconds, err := services.CreateReportVideo(req.Title, req.Content, publishTime.Format(utils.FormatDateTime))
+//			if err != nil {
+//				br.Msg = "生成video失败"
+//				br.ErrMsg = "生成video失败, Err: " + err.Error()
+//				return
+//			}
+//			chapterInfo.VideoUrl = videoUrl
+//			chapterInfo.VideoName = videoName
+//			chapterInfo.VideoSize = videoSize
+//			chapterInfo.VideoPlaySeconds = fmt.Sprintf("%.2f", videoPlaySeconds)
+//			chapterInfo.VideoKind = 2
+//			updateCols = append(updateCols, "VideoUrl", "VideoName", "VideoSize", "VideoPlaySeconds", "VideoKind")
+//		}
+//	}
+//
+//	// 更新章节信息
+//	contentSub := ""
+//	if req.Content != "" {
+//		contentClean, e := services.FilterReportContentBr(req.Content)
+//		if e != nil {
+//			br.Msg = "内容去除前后空格失败"
+//			br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+//			return
+//		}
+//		req.Content = contentClean
+//
+//		contentSub, err = services.GetReportContentSub(req.Content)
+//		if err != nil {
+//			br.Msg = "内容分段解析失败"
+//			br.ErrMsg = "编辑报告章节-解析 ContentSub 失败, Err: " + err.Error()
+//			return
+//		}
+//	}
+//	chapterInfo.Title = req.Title
+//	chapterInfo.AddType = req.AddType
+//	chapterInfo.Author = req.Author
+//	chapterInfo.Content = html.EscapeString(req.Content)
+//	chapterInfo.ContentSub = html.EscapeString(contentSub)
+//	chapterInfo.IsEdit = 1
+//	chapterInfo.CreateTime = req.CreateTime
+//	chapterInfo.PublishState = 2
+//	chapterInfo.PublishTime = publishTime
+//
+//	// 晨报更新指标
+//	tickerList := make([]*models.ReportChapterTicker, 0)
+//	if chapterInfo.ReportType == "day" && len(reqTickerList) > 0 {
+//		for i := 0; i < len(reqTickerList); i++ {
+//			tickerList = append(tickerList, &models.ReportChapterTicker{
+//				ReportChapterId: chapterInfo.ReportChapterId,
+//				Sort:            reqTickerList[i].Sort,
+//				Ticker:          reqTickerList[i].Ticker,
+//				CreateTime:      nowTime,
+//				UpdateTime:      nowTime,
+//			})
+//		}
+//	}
+//	if err = models.UpdateChapterAndTicker(chapterInfo, updateCols, tickerList); err != nil {
+//		br.Msg = "发布失败"
+//		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+//		return
+//	}
+//	// 更新章节ES
+//	{
+//		go services.UpdateReportChapterEs(chapterInfo.ReportChapterId)
+//	}
+//
+//	// 同时发布报告
+//	if req.PublishReport == 1 {
+//		if _, e := services.PublishDayWeekReport(chapterInfo.ReportId); e != nil {
+//			br.Msg = "章节发布成功,报告发布失败,请手动发布报告"
+//			br.ErrMsg = "发布晨/周报失败, Err: " + e.Error()
+//			return
+//		}
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "操作成功"
+//}
+
+// @Title 发布晨周报
+// @Description 发布晨周报
+// @Param	request	body models.PublishDayWeekReportReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /publishDayWeekReport [post]
+func (this *ReportController) PublishDayWeekReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.PublishDayWeekReportReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+	reportInfo, err := models.GetReportByReportId(reportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布"
+		return
+	}
+	tips, err := services.PublishDayWeekReport(reportId)
+	if err != nil {
+		br.Msg = "发布失败"
+		br.ErrMsg = "发布晨/周报失败, Err: " + err.Error()
+		return
+	}
+	// 部分章节未发布的提示信息
+	if tips != "" {
+		br.Data = tips
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// GetSunCode 获取太阳码
+// @Title 公共模块
+// @Description 获取分享海报
+// @Param	request	body models.SunCodeReq true "type json string"
+// @Success 200 {object} string "获取成功"
+// @failure 400 {string} string "获取失败"
+// @router /getSunCode [post]
+func (this *ReportController) GetSunCode() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.SunCodeReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	var sunCodeUrl string
+	//先查,查不到再去生成上传
+	item, err := models.GetYbPcSunCode(req.CodeScene, req.CodePage)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "查询太阳码失败!"
+		br.ErrMsg = "查询太阳码失败,Err:" + err.Error()
+		return
+	}
+	if item != nil {
+		sunCodeUrl = item.SuncodeURL
+	}
+
+	if sunCodeUrl == "" {
+		sunCodeUrl, err = services.PcCreateAndUploadSunCode(req.CodeScene, req.CodePage)
+		if err != nil {
+			br.Msg = "生成太阳码失败!"
+			br.ErrMsg = "生成太阳码失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	br.Data = sunCodeUrl
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// GetStopDayWeekReportChapterTypeList
+// @Title 获取暂停更新的晨报周报章节类型列表
+// @Description 获取暂停更新的晨报周报章节类型列表
+// @Success 200 {object} models.ReportListResp
+// @router /getStopDayWeekReportChapterTypeList [get]
+func (this *ReportController) GetStopDayWeekReportChapterTypeList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	stopDay := make([]*models.ReportChapterType, 0)
+	stopWeek := make([]*models.ReportChapterType, 0)
+	disableDay := make([]*models.ReportChapterType, 0)
+	disableWeek := make([]*models.ReportChapterType, 0)
+
+	stopList, err := models.GetStopUpdateReportChapterTypeListByResearchType()
+	if err != nil {
+		br.Msg = "获取暂停更新报告章节类型列表失败"
+		br.ErrMsg = "获取暂停更新报告章节类型列表失败, Err: " + err.Error()
+		return
+	}
+
+	for _, v := range stopList {
+		if v.ResearchType == "day" {
+			stopDay = append(stopDay, v)
+		} else {
+			stopWeek = append(stopWeek, v)
+		}
+	}
+
+	disableList, err := models.GetDisableUpdateReportChapterTypeListByResearchType()
+	if err != nil {
+		br.Msg = "获取停止更新报告章节类型列表失败"
+		br.ErrMsg = "获取停止更新报告章节类型列表失败, Err: " + err.Error()
+		return
+	}
+	for _, v := range disableList {
+		if v.ResearchType == "day" {
+			disableDay = append(disableDay, v)
+		} else {
+			disableWeek = append(disableWeek, v)
+		}
+	}
+
+	resp := models.StopUpdateReportChapterTypeResp{
+		StopDay:     stopDay,
+		StopWeek:    stopWeek,
+		DisableDay:  disableDay,
+		DisableWeek: disableWeek,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// MarkEditStatus
+// @Title 标记报告编辑状态
+// @Description 标记报告编辑状态接口
+// @Param	request	body request.MarkEditEnReport true "type json string"
+// @Success 200 标记成功 ;202 标记成功
+// @router /mark [post]
+func (this *ReportController) MarkEditStatus() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.MarkEditReport
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ReportId <= 0 {
+		br.Msg = "缺少报告Id"
+		return
+	}
+	if req.Status <= 0 {
+		br.Msg = "标记状态异常"
+		return
+	}
+	//更新标记key
+	data, err := services.UpdateReportEditMark(req.ReportId, sysUser.AdminId, req.Status, sysUser.RealName)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+
+	msg := "标记成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+	br.Data = data
+}
+
+// @Title 模板消息/客群消息推送接口
+// @Description 模板消息/客群消息推送接口
+// @Param	request	body models.SendTemplateMsgReq true "type json string"
+// @Success 200 Ret=200 推送成功
+// @router /sendMsg [post]
+func (this *ReportController) SendMsg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.SendTemplateMsgReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误"
+		return
+	}
+
+	// 加锁,避免重复点击造成的多次推送
+	{
+		redisKey := fmt.Sprint("report:send:ths:", req.ReportId)
+		ok := utils.Rc.SetNX(redisKey, 1, time.Second*300)
+		if !ok {
+			br.Msg = "报告已推送, 请勿重复推送"
+			return
+		}
+		defer func() {
+			_ = utils.Rc.Delete(redisKey)
+		}()
+	}
+
+	report, e := models.GetReportById(req.ReportId)
+	if e != nil {
+		br.Msg = "报告不存在或已被删除, 请刷新页面"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+	if report.MsgIsSend == 1 && report.ThsMsgIsSend == 1 {
+		br.Msg = "消息已推送,请勿重复操作"
+		br.ErrMsg = "消息已推送,请勿重复操作"
+		return
+	}
+
+	// 更新报告发布时间
+	videoNameDate := `(` + time.Now().Format("0102") + `)`
+	if err = models.UpdateReportPublishTime(req.ReportId, videoNameDate); err != nil {
+		br.Msg = "修改报告发布时间失败"
+		br.ErrMsg = "修改发布时间失败,Err:" + err.Error()
+		return
+	}
+	if err = models.UpdateReportChapterPublishTime(req.ReportId, videoNameDate); err != nil {
+		br.Msg = "修改章节发布时间失败"
+		br.ErrMsg = "修改发布时间失败,Err:" + err.Error()
+		return
+	}
+
+	// 可能存在历史数据两个只推了一个所以此处加个判断
+	// 推送模板消息
+	if report.MsgIsSend == 0 {
+		go func() {
+			fmt.Println("推送模板消息:", req.ReportId)
+			_ = services.SendMiniProgramReportWxMsg(req.ReportId)
+		}()
+	}
+	// 推送客群消息
+	if report.ThsMsgIsSend == 0 {
+		//go func() {
+		// 周报无客群消息
+		// 2023-3-19 15:19:24 现在开始周报又要推送了
+		//if report.HasChapter == 1 && report.ChapterType == utils.REPORT_TYPE_WEEK {
+		//	return
+		//}
+		//_ = services.SendReportMiniToThs(report)
+		//}()
+	}
+
+	// 更新推送消息状态
+	if err = models.ModifyReportMsgIsSendV2(req.ReportId); err != nil {
+		br.Msg = "推送失败"
+		br.ErrMsg = "更新报告消息状态失败, Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "推送成功"
+	br.IsAddLog = true
+}
+
+// GetStopDayWeekReportChapterTypeList
+// @Title 获取暂停更新的晨报周报章节类型列表
+// @Description 获取暂停更新的晨报周报章节类型列表
+// @Param   ReportId	query	string	true	"报告ID"
+// @Success 200 {object} models.ReportListResp
+// @router /CheckDayWeekReportChapterVideo [get]
+func (this *ReportController) CheckDayWeekReportChapterVideo() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	reqReportId := this.GetString("ReportId")
+	reportId, _ := strconv.Atoi(reqReportId)
+	if reportId <= 0 {
+		br.Msg = "报告ID有误"
+		return
+	}
+
+	// 获取章节列表
+	chapterList, err := models.GetChapterListByReportId(reportId)
+	if err != nil {
+		br.Msg = "获取章节列表失败"
+		br.ErrMsg = "获取章节列表失败, Err: " + err.Error()
+		return
+	}
+	var typeNameArr []string
+	for _, v := range chapterList {
+		if v.VideoUrl == "" || (v.VideoUrl != "" && v.VideoKind == 2) {
+			typeNameArr = append(typeNameArr, v.TypeName)
+		}
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = typeNameArr
+}

+ 26 - 0
go.mod

@@ -4,27 +4,47 @@ go 1.17
 
 
 require (
 require (
 	github.com/PuerkitoBio/goquery v1.8.1
 	github.com/PuerkitoBio/goquery v1.8.1
+	github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4
+	github.com/alibabacloud-go/dm-20151123/v2 v2.0.1
+	github.com/alibabacloud-go/tea v1.2.0
+	github.com/alibabacloud-go/tea-utils/v2 v2.0.1
 	github.com/beego/bee/v2 v2.0.4
 	github.com/beego/bee/v2 v2.0.4
 	github.com/beego/beego/v2 v2.0.7
 	github.com/beego/beego/v2 v2.0.7
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/go-sql-driver/mysql v1.7.0
 	github.com/go-sql-driver/mysql v1.7.0
 	github.com/go-xorm/xorm v0.7.9
 	github.com/go-xorm/xorm v0.7.9
+	github.com/gorilla/websocket v1.4.2
 	github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53
 	github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53
 	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
 	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
 	github.com/olivere/elastic/v7 v7.0.30
 	github.com/olivere/elastic/v7 v7.0.30
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.3.1
 	github.com/shopspring/decimal v1.3.1
+	github.com/silenceper/wechat/v2 v2.1.4
 	github.com/tealeg/xlsx v1.0.5
 	github.com/tealeg/xlsx v1.0.5
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.655
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.655
 	github.com/xuri/excelize/v2 v2.7.1
 	github.com/xuri/excelize/v2 v2.7.1
 	github.com/yidane/formula v0.0.0-20210902154546-0782e1736717
 	github.com/yidane/formula v0.0.0-20210902154546-0782e1736717
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 )
 )
 
 
 require (
 require (
+	github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
+	github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
+	github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
+	github.com/alibabacloud-go/openapi-util v0.0.11 // indirect
+	github.com/alibabacloud-go/tea-utils v1.3.1 // indirect
+	github.com/alibabacloud-go/tea-xml v1.1.2 // indirect
+	github.com/aliyun/credentials-go v1.1.2 // indirect
 	github.com/andybalholm/cascadia v1.3.1 // indirect
 	github.com/andybalholm/cascadia v1.3.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
+	github.com/clbanning/mxj/v2 v2.5.5 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/fatih/structs v1.1.0 // indirect
 	github.com/garyburd/redigo v1.6.3 // indirect
 	github.com/garyburd/redigo v1.6.3 // indirect
+	github.com/go-redis/redis/v8 v8.11.5 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
@@ -46,6 +66,12 @@ require (
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.3 // indirect
 	github.com/richardlehane/msoleps v1.0.3 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
+	github.com/sirupsen/logrus v1.9.0 // indirect
+	github.com/spf13/cast v1.4.1 // indirect
+	github.com/tidwall/gjson v1.14.1 // indirect
+	github.com/tidwall/match v1.1.1 // indirect
+	github.com/tidwall/pretty v1.2.0 // indirect
+	github.com/tjfoc/gmsm v1.3.2 // indirect
 	github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
 	github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
 	github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
 	github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
 	golang.org/x/crypto v0.8.0 // indirect
 	golang.org/x/crypto v0.8.0 // indirect

+ 88 - 0
go.sum

@@ -54,12 +54,41 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4 h1:7Q2FEyqxeZeIkwYMwRC3uphxV4i7O2eV4ETe21d6lS4=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
+github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50=
+github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
+github.com/alibabacloud-go/dm-20151123/v2 v2.0.1 h1:wEIEI4vvk7vtiJmXEUnom+ylEBfy107WQjaPNyQc7E4=
+github.com/alibabacloud-go/dm-20151123/v2 v2.0.1/go.mod h1:q9cd++SgWfT9U5kdBbRRlvzrr0HOKayLxfe9s0NVZhQ=
+github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
+github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
+github.com/alibabacloud-go/openapi-util v0.0.11 h1:iYnqOPR5hyEEnNZmebGyRMkkEJRWUEjDiiaOHZ5aNhA=
+github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
+github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
+github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
+github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
+github.com/alibabacloud-go/tea v1.2.0 h1:UTe0AVhJTdSv0c2eIZN3dM2BgtXx4dLxd8yLG8Jw3lk=
+github.com/alibabacloud-go/tea v1.2.0/go.mod h1:ojRksXLcgqz1usnR6nIkbVf3PtWzjb9ze3dkwqj5zcg=
+github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I=
+github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.1 h1:K6kwgo+UiYx+/kr6CO0PN5ACZDzE3nnn9d77215AkTs=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.1/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
+github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M=
+github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
 github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
 github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
 github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
 github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
 github.com/aliyun/alibaba-cloud-sdk-go v1.62.274 h1:UKoNWCTvUohk0besAPgnSzilZMWrb4Mul37DE8/KCdA=
 github.com/aliyun/alibaba-cloud-sdk-go v1.62.274 h1:UKoNWCTvUohk0besAPgnSzilZMWrb4Mul37DE8/KCdA=
 github.com/aliyun/alibaba-cloud-sdk-go v1.62.274/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
 github.com/aliyun/alibaba-cloud-sdk-go v1.62.274/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
 github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis=
 github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis=
 github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
 github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
+github.com/aliyun/credentials-go v1.1.2 h1:qU1vwGIBb3UJ8BwunHDRFtAhS6jnQLnde/yk0+Ih2GY=
+github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
 github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
 github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@@ -99,6 +128,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
+github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
+github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
 github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
 github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
 github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
 github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
@@ -116,7 +147,10 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
 github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
 github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
 github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
 github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
 github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
+github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
+github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
 github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
 github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
 github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
@@ -154,6 +188,8 @@ github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADG
 github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
 github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -178,6 +214,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
+github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
 github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
 github.com/flosch/pongo2 v0.0.0-20200529170236-5abacdfa4915/go.mod h1:fB4mx6dzqFinCxIf3a7Mf5yLk+18Bia9mPAnuejcvDA=
 github.com/flosch/pongo2 v0.0.0-20200529170236-5abacdfa4915/go.mod h1:fB4mx6dzqFinCxIf3a7Mf5yLk+18Bia9mPAnuejcvDA=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
@@ -187,6 +225,7 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
 github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo=
 github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/garyburd/redigo v1.6.3 h1:HCeeRluvAgMusMomi1+6Y5dmFOdYV/JzoRrrbFlkGIc=
 github.com/garyburd/redigo v1.6.3 h1:HCeeRluvAgMusMomi1+6Y5dmFOdYV/JzoRrrbFlkGIc=
 github.com/garyburd/redigo v1.6.3/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=
 github.com/garyburd/redigo v1.6.3/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=
@@ -210,8 +249,11 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
 github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
 github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
+github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
+github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -296,6 +338,7 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -303,10 +346,12 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -314,6 +359,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
+github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ=
 github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ=
@@ -365,6 +412,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
 github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@@ -489,11 +537,13 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
 github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19 h1:LhWT2dBuNkYexwRSsPpYh67e0ikmH1ebBDaVkGHoMts=
 github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19 h1:LhWT2dBuNkYexwRSsPpYh67e0ikmH1ebBDaVkGHoMts=
 github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19/go.mod h1:LjhyrWzOLJ9l1azMoNr9iCvfNrHEREqvJHzSLQcD0/o=
 github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19/go.mod h1:LjhyrWzOLJ9l1azMoNr9iCvfNrHEREqvJHzSLQcD0/o=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
@@ -507,11 +557,18 @@ github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
 github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
 github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
 github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
 github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
 github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
 github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
 github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
@@ -619,12 +676,17 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
 github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
 github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
 github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
 github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
 github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
 github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
+github.com/silenceper/wechat/v2 v2.1.4 h1:X+G9C/EiBET5AK0zhrflX3ESCP/yxhJUvoRoSXHm0js=
+github.com/silenceper/wechat/v2 v2.1.4/go.mod h1:F0PKqImb15THnwoqRNrZO1z3vpwyWuiHr5zzfnjdECY=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartwalle/pongo2render v1.0.1/go.mod h1:MGnTzND7nEMz7g194kjlnw8lx/V5JJlb1hr5kDXEO0I=
 github.com/smartwalle/pongo2render v1.0.1/go.mod h1:MGnTzND7nEMz7g194kjlnw8lx/V5JJlb1hr5kDXEO0I=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
 github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@@ -634,6 +696,8 @@ github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJ
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
+github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@@ -648,6 +712,7 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
 github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -664,6 +729,18 @@ github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2K
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
 github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
 github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
 github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.655 h1:wXBlXLfBbqTBpsiKBBULW63KvMy3wsu3/CD25cR9NEA=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.655/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.655 h1:0pGCMlxAGBFNRoxl+uQ6P3raXwnnaLg6pxWSG8uWMW0=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.655/go.mod h1:Ml0/OhRAv5wOjHrrJ4EhNK+2a6XBciHT+bBIjUiO/+g=
+github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
+github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
+github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@@ -687,6 +764,7 @@ github.com/yidane/formula v0.0.0-20210902154546-0782e1736717/go.mod h1:9/dQiKiN0
 github.com/ylywyn/jpush-api-go-client v0.0.0-20190906031852-8c4466c6e369/go.mod h1:Nv7wKD2/bCdKUFNKcJRa99a+1+aSLlCRJFriFYdjz/I=
 github.com/ylywyn/jpush-api-go-client v0.0.0-20190906031852-8c4466c6e369/go.mod h1:Nv7wKD2/bCdKUFNKcJRa99a+1+aSLlCRJFriFYdjz/I=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -748,11 +826,14 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
 golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
 golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
 golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -915,6 +996,7 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -941,6 +1023,7 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1015,6 +1098,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs
 golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
 golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
 golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
 golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
 golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -1146,12 +1230,16 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
+gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
+gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
 gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
 gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

+ 1 - 0
models/classify.go

@@ -39,6 +39,7 @@ type Classify struct {
 	YbRightBanner     string    `description:"Pc端详情页,右侧,报告合集背景图"`
 	YbRightBanner     string    `description:"Pc端详情页,右侧,报告合集背景图"`
 	RelateTel         int       `description:"是否在电话会中可选: 0-否; 1-是"`
 	RelateTel         int       `description:"是否在电话会中可选: 0-否; 1-是"`
 	RelateVideo       int       `description:"是否在路演视频中可选: 0-否; 1-是"`
 	RelateVideo       int       `description:"是否在路演视频中可选: 0-否; 1-是"`
+	IsMassSend        int       `description:"1:群发,0:非群发"`
 }
 }
 
 
 type ClassifyAddReq struct {
 type ClassifyAddReq struct {

+ 87 - 0
models/classify_menu_relation.go

@@ -0,0 +1,87 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ClassifyMenuRelation 报告分类-子目录关联表
+type ClassifyMenuRelation struct {
+	Id         int       `orm:"column(id);pk"`
+	MenuId     int       `description:"子目录ID"`
+	ClassifyId int       `description:"二级分类ID"`
+	CreateTime time.Time `description:"创建时间"`
+}
+
+func (item *ClassifyMenuRelation) TableName() string {
+	return "classify_menu_relation"
+}
+
+func (item *ClassifyMenuRelation) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(item)
+	if err != nil {
+		return
+	}
+	item.Id = int(id)
+	return
+}
+
+func (item *ClassifyMenuRelation) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(item, cols...)
+	return
+}
+
+func (item *ClassifyMenuRelation) InsertMulti(items []*ClassifyMenuRelation) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+// GetClassifyMenuRelationList 获取子目录关联列表
+func GetClassifyMenuRelationList(condition string, pars []interface{}) (list []*ClassifyMenuRelation, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM classify_menu_relation WHERE 1 = 1 `
+	sql += condition
+	sql += ` ORDER BY create_time DESC`
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}
+
+// DeleteAndInsertClassifyMenuRelation 新增子目录关联
+func DeleteAndInsertClassifyMenuRelation(classifyId, menuId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+		} else {
+			_ = tx.Commit()
+		}
+	}()
+
+	// 删除
+	sql := `DELETE FROM classify_menu_relation WHERE classify_id = ?`
+	if _, e := tx.Raw(sql, classifyId).Exec(); e != nil {
+		err = e
+		return
+	}
+
+	// 新增
+	if menuId > 0 {
+		relate := &ClassifyMenuRelation{
+			ClassifyId: classifyId,
+			MenuId:     menuId,
+			CreateTime: time.Now().Local(),
+		}
+		_, e := tx.Insert(relate)
+		if e != nil {
+			err = e
+		}
+	}
+	return
+}

+ 27 - 1
models/db.go

@@ -72,6 +72,9 @@ func init() {
 
 
 	//多图配置
 	//多图配置
 	initMultipleGraphConfig()
 	initMultipleGraphConfig()
+
+	// 英文报告
+	initEnglishReport()
 }
 }
 
 
 // initSystem 系统表 数据表
 // initSystem 系统表 数据表
@@ -87,7 +90,19 @@ func initSystem() {
 // initReport 报告相关 数据表
 // initReport 报告相关 数据表
 func initReport() {
 func initReport() {
 	orm.RegisterModel(
 	orm.RegisterModel(
-		new(ClassifyMenu), // 报告分类-子目录表
+		new(Report),
+		new(ResearchReport),                      //日报、周报信息
+		new(ChartPermissionSearchKeyWordMapping), //报告分类权限表
+		new(ReportChapter),                       // 报告章节表
+		new(ReportChapterTicker),                 // 晨报章节ticker
+		new(ReportChapterTypePermission),         // 晨周报章节类型权限表
+		new(YbPcSuncode),
+		new(YbSuncodePars),
+		new(ReportAuthor),                  //报告作者
+		new(ClassifyMenu),                  // 报告分类-子目录表
+		new(ClassifyMenuRelation),          // 报告分类-子目录关联表
+		new(ChartPermissionChapterMapping), // 权限mapping表
+		new(ReportChapterType),             // 报告章节类型表
 	)
 	)
 }
 }
 
 
@@ -135,3 +150,14 @@ func initMultipleGraphConfig() {
 		//new(data_manage.MultipleGraphConfigEdbMapping),   //指标与多图配置的关系表
 		//new(data_manage.MultipleGraphConfigEdbMapping),   //指标与多图配置的关系表
 	)
 	)
 }
 }
+
+// initEnglishReport 英文报告
+func initEnglishReport() {
+	orm.RegisterModel(
+		new(EnglishReport),
+		new(EnglishReportEmail),
+		new(EnglishReportEmailLog),
+		new(EnglishClassify),
+		new(EnglishVideo), // 英文研报线上路演
+	)
+}

+ 722 - 0
models/english_report.go

@@ -0,0 +1,722 @@
+package models
+
+import (
+	"errors"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"strings"
+	"time"
+)
+
+type EnglishReport struct {
+	Id                 int       `orm:"column(id)" description:"报告Id"`
+	AddType            int       `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int       `description:"一级分类id"`
+	ClassifyNameFirst  string    `description:"一级分类名称"`
+	ClassifyIdSecond   int       `description:"二级分类id"`
+	ClassifyNameSecond string    `description:"二级分类名称"`
+	Title              string    `description:"标题"`
+	Abstract           string    `description:"摘要"`
+	Author             string    `description:"作者"`
+	Frequency          string    `description:"频度"`
+	CreateTime         string    `description:"创建时间"`
+	ModifyTime         time.Time `description:"修改时间"`
+	State              int       `description:"1:未发布,2:已发布"`
+	PublishTime        time.Time `description:"发布时间"`
+	Stage              int       `description:"期数"`
+	Content            string    `description:"内容"`
+	VideoUrl           string    `description:"音频文件URL"`
+	VideoName          string    `description:"音频文件名称"`
+	VideoPlaySeconds   string    `description:"音频播放时长"`
+	VideoSize          string    `description:"音频文件大小,单位M"`
+	ContentSub         string    `description:"内容前两个章节"`
+	ReportCode         string    `description:"报告唯一编码"`
+	Pv                 int       `description:"Pv"`
+	PvEmail            int       `description:"邮箱PV"`
+	EmailState         int       `description:"群发邮件状态: 0-未发送; 1-已发送"`
+	Overview           string    `description:"英文概述部分"`
+	KeyTakeaways       string    `description:"关键点"`
+	FromReportId       int       `description:"继承的报告ID(英文策略报告ID)"`
+	AdminId            int       `description:"创建者账号"`
+	AdminRealName      string    `description:"创建者姓名"`
+}
+
+func GetEnglishReportStage(classifyIdFirst, classifyIdSecond int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ``
+	if classifyIdSecond > 0 {
+		sql = "SELECT MAX(stage) AS max_stage FROM english_report WHERE classify_id_second=? "
+		o.Raw(sql, classifyIdSecond).QueryRow(&count)
+	} else {
+		sql = "SELECT MAX(stage) AS max_stage FROM english_report WHERE classify_id_first=? "
+		o.Raw(sql, classifyIdFirst).QueryRow(&count)
+	}
+	return
+}
+
+func GetEnglishReportStageEdit(classifyIdFirst, classifyIdSecond, reportId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ``
+	if classifyIdSecond > 0 {
+		sql = "SELECT MAX(stage) AS max_stage FROM english_report WHERE classify_id_second=? AND id<>? "
+		o.Raw(sql, classifyIdSecond, reportId).QueryRow(&count)
+	} else {
+		sql = "SELECT MAX(stage) AS max_stage FROM english_report WHERE classify_id_first=? AND id<>? "
+		o.Raw(sql, classifyIdFirst, reportId).QueryRow(&count)
+	}
+	return
+}
+
+func AddEnglishReport(item *EnglishReport) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	lastId, err = o.Insert(item)
+	return
+}
+
+func ModifyEnglishReportCode(reportId int64, reportCode string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report SET report_code=? WHERE id=? `
+	_, err = o.Raw(sql, reportCode, reportId).Exec()
+	return
+}
+
+type AddEnglishReportReq struct {
+	AddType            int    `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	State              int    `description:"状态:1:未发布,2:已发布"`
+	Content            string `description:"内容"`
+	CreateTime         string `description:"创建时间"`
+	Overview           string `description:"英文概述部分"`
+}
+
+type AddEnglishReportResp struct {
+	ReportId   int64  `description:"报告id"`
+	ReportCode string `description:"报告code"`
+}
+
+type EditEnglishReportReq struct {
+	ReportId           int64  `description:"报告id"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	State              int    `description:"状态:1:未发布,2:已发布"`
+	Content            string `description:"内容"`
+	CreateTime         string `description:"创建时间"`
+	Overview           string `description:"英文概述部分"`
+}
+
+type EditEnglishReportFromPolicyReq struct {
+	ReportId   int64  `description:"报告id"`
+	Title      string `description:"标题"`
+	Abstract   string `description:"摘要"`
+	Author     string `description:"作者"`
+	Frequency  string `description:"频度"`
+	CreateTime string `description:"创建时间"`
+	//Overview           string `description:"英文概述部分"`
+}
+type EditEnglishReportResp struct {
+	ReportId   int64  `description:"报告id"`
+	ReportCode string `description:"报告code"`
+}
+
+type ElasticEnglishReportDetail struct {
+	Id                 string `description:"报告id或者线上路演Id"`
+	ReportId           int    `description:"报告id"`
+	VideoId            int    `description:"线上路演Id"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	StageStr           string `description:"报告期数"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	PublishState       int    `description:"状态:1:未发布,2:已发布"`
+	BodyContent        string `description:"内容"`
+	ContentSub         string `description:"前两段内容"`
+	CreateTime         string `description:"创建时间"`
+	PublishTime        string `description:"发布时间"`
+	ReportCode         string `description:"报告唯一编码"`
+	Overview           string `description:"英文概述部分"`
+}
+
+func EditEnglishReport(item *EnglishReport, reportId int64) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report
+			SET
+			  classify_id_first =?,
+			  classify_name_first = ?,
+			  classify_id_second = ?,
+			  classify_name_second = ?,
+			  title = ?,
+			  abstract = ?,
+			  author = ?,
+			  frequency = ?,
+			  state = ?,
+			  content = ?,
+			  content_sub = ?,
+			  stage =?,
+			  create_time = ?,
+			  modify_time = ?,
+			  overview = ?
+			WHERE id = ? `
+	_, err = o.Raw(sql, item.ClassifyIdFirst, item.ClassifyNameFirst, item.ClassifyIdSecond, item.ClassifyNameSecond, item.Title,
+		item.Abstract, item.Author, item.Frequency, item.State, item.Content, item.ContentSub, item.Stage, item.CreateTime, time.Now(), item.Overview, reportId).Exec()
+	return
+}
+
+type EnglishReportDetail struct {
+	Id                 int    `orm:"column(id)" description:"报告Id"`
+	AddType            int    `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	CreateTime         string `description:"创建时间"`
+	ModifyTime         string `description:"修改时间"`
+	State              int    `description:"1:未发布,2:已发布"`
+	PublishTime        string `description:"发布时间"`
+	Stage              int    `description:"期数"`
+	MsgIsSend          int    `description:"消息是否已发送,0:否,1:是"`
+	ReportCode         string `description:"报告唯一编码"`
+	Content            string `description:"内容"`
+	VideoUrl           string `description:"音频文件URL"`
+	VideoName          string `description:"音频文件名称"`
+	VideoPlaySeconds   string `description:"音频播放时长"`
+	ContentSub         string `description:"内容前两个章节"`
+	Pv                 int    `description:"Pv"`
+	Overview           string `description:"英文概述部分"`
+	FromReportId       int    `description:"继承的报告ID(英文策略报告ID)"`
+	KeyTakeaways       string `description:"关键点"`
+	EmailState         int    `description:"群发邮件状态: 0-未发送; 1-已发送"`
+	EmailAuth          bool   `description:"是否有权限群发邮件"`
+	EmailHasFail       bool   `description:"是否存在邮件发送失败的记录"`
+}
+
+func GetEnglishReportById(reportId int) (item *EnglishReportDetail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report WHERE id=?`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+func GetEnglishReportItemById(reportId int) (item *EnglishReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report WHERE id = ? LIMIT 1`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+type EnglishReportList struct {
+	Id                 int       `description:"报告Id"`
+	AddType            int       `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int       `description:"一级分类id"`
+	ClassifyNameFirst  string    `description:"一级分类名称"`
+	ClassifyIdSecond   int       `description:"二级分类id"`
+	ClassifyNameSecond string    `description:"二级分类名称"`
+	Title              string    `description:"标题"`
+	Abstract           string    `description:"摘要"`
+	Author             string    `description:"作者"`
+	Frequency          string    `description:"频度"`
+	CreateTime         string    `description:"创建时间"`
+	ModifyTime         time.Time `description:"修改时间"`
+	State              int       `description:"1:未发布,2:已发布"`
+	PublishTime        string    `description:"发布时间"`
+	Stage              int       `description:"期数"`
+	Content            string    `description:"内容"`
+	VideoUrl           string    `description:"音频文件URL"`
+	VideoName          string    `description:"音频文件名称"`
+	VideoPlaySeconds   string    `description:"音频播放时长"`
+	ContentSub         string    `description:"内容前两个章节"`
+	ReportCode         string    `description:"报告唯一编码"`
+	Pv                 int       `description:"Pv"`
+	ShareUrl           string    `description:"分享url"`
+	PvEmail            int       `description:"邮箱PV"`
+	EmailState         int       `description:"群发邮件状态: 0-未发送; 1-已发送"`
+	EmailAuth          bool      `description:"是否有权限群发邮件"`
+	EmailHasFail       bool      `description:"是否存在邮件发送失败的记录"`
+	CanEdit            bool      `description:"是否可编辑"`
+	Editor             string    `description:"编辑人"`
+	FromReportId       int       `description:"继承的报告ID(英文策略报告ID)"`
+	AdminId            int       `description:"创建者账号"`
+	AdminRealName      string    `description:"创建者姓名"`
+}
+
+type EnglishReportListResp struct {
+	List   []*EnglishReportList
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+func GetEnglishReportListCount(condition string, pars []interface{}, companyType string) (count int, err error) {
+	//产品权限
+	companyTypeSqlStr := ``
+	if companyType == "ficc" {
+		companyTypeSqlStr = " AND classify_id_first != 40 "
+	} else if companyType == "权益" {
+		companyTypeSqlStr = " AND classify_id_first = 40 "
+	}
+
+	oRddp := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count  FROM english_report WHERE 1=1 ` + companyTypeSqlStr
+	if condition != "" {
+		sql += condition
+	}
+	err = oRddp.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetEnglishReportList(condition string, pars []interface{}, companyType string, startSize, pageSize int) (items []*EnglishReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	//产品权限
+	companyTypeSqlStr := ``
+	if companyType == "ficc" {
+		companyTypeSqlStr = " AND classify_id_first != 40 "
+	} else if companyType == "权益" {
+		companyTypeSqlStr = " AND classify_id_first = 40 "
+	}
+
+	sql := `SELECT * 
+        FROM english_report WHERE 1=1  ` + companyTypeSqlStr
+	if condition != "" {
+		sql += condition
+	}
+	sql += `ORDER BY state ASC, modify_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetEnglishReportByCondition(condition string, pars []interface{}) (items []*EnglishReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * 
+        FROM english_report WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// 发布报告
+func PublishEnglishReportById(reportId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report SET state=2,publish_time=now(),modify_time=NOW() WHERE id = ? `
+	_, err = o.Raw(sql, reportId).Exec()
+	return
+}
+
+// 取消发布报告
+func PublishCancelEnglishReport(reportIds int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` UPDATE english_report SET state=1,publish_time=null WHERE id =?  `
+	_, err = o.Raw(sql, reportIds).Exec()
+	return
+}
+
+// DeleteEnglishReportAndChapter 删除报告及章节
+func DeleteEnglishReportAndChapter(reportInfo *EnglishReportDetail) (err error) {
+	reportId := reportInfo.Id
+	if reportInfo.State == 2 {
+		err = errors.New("报告已发布,不可删除")
+		return
+	}
+	err = DeleteEnglishReport(reportId)
+	if err != nil {
+		err = errors.New("删除失败, Err: " + err.Error())
+		return
+	}
+
+	return
+}
+
+// 删除报告
+func DeleteEnglishReport(reportIds int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` DELETE FROM english_report WHERE id =? `
+	_, err = o.Raw(sql, reportIds).Exec()
+	return
+}
+
+func EditEnglishReportContent(reportId int, content, contentSub string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` UPDATE english_report SET content=?,content_sub=?,modify_time=NOW() WHERE id=? `
+	_, err = o.Raw(sql, content, contentSub, reportId).Exec()
+	return
+}
+
+func AddEnglishReportSaveLog(reportId, adminId int, content, contentSub, adminName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` INSERT INTO english_report_save_log(report_id, content,content_sub,admin_id,admin_name) VALUES (?,?,?,?,?) `
+	_, err = o.Raw(sql, reportId, content, contentSub, adminId, adminName).Exec()
+	return
+}
+
+type EnglishClassifyList struct {
+	Id            int       `orm:"column(id);pk"`
+	ClassifyName  string    `description:"分类名称"`
+	Sort          int       `description:"排序"`
+	ParentId      int       `description:"父级分类id"`
+	CreateTime    time.Time `description:"创建时间"`
+	ModifyTime    time.Time `description:"修改时间"`
+	ClassifyLabel string    `description:"分类标签"`
+	ShowType      int       `description:"展示类型:1-列表 2-专栏"`
+	IsShow        int       `description:"是否在小程序显示:1-显示 0-隐藏"`
+	ClassifyType  int       `description:"分类类型:0英文报告,1英文线上路演"`
+	Child         []*EnglishClassify
+}
+
+type EnglishClassifyListResp struct {
+	List   []*EnglishClassifyList
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+// 获取分类列表
+func GetEnglishClassifyList(startSize, pageSize int, keyWord string, classifyType int) (items []*EnglishClassifyList, err error) {
+	sql := ``
+	o := orm.NewOrmUsingDB("rddp")
+	if keyWord != "" {
+		sql = `SELECT * FROM (
+                   SELECT * FROM english_classify
+                   WHERE parent_id=0 AND classify_name LIKE '%` + keyWord + `%' AND classify_type = ?
+                   UNION
+                   SELECT * FROM english_classify
+                   WHERE id IN(SELECT parent_id FROM english_classify
+                   WHERE parent_id>0 AND classify_name LIKE '%` + keyWord + `%') AND classify_type = ?
+                   )AS t
+                   ORDER BY sort ASC,create_time ASC
+                   LIMIT ?,? `
+		_, err = o.Raw(sql, classifyType, classifyType, startSize, pageSize).QueryRows(&items)
+	} else {
+		sql = `SELECT * FROM english_classify WHERE parent_id=0 AND classify_type = ? ORDER BY sort ASC,create_time ASC LIMIT ?,? `
+		_, err = o.Raw(sql, classifyType, startSize, pageSize).QueryRows(&items)
+	}
+	return
+}
+
+func GetEnglishClassifyListCount(keyWord string, classifyType int) (count int, err error) {
+	sqlCount := ``
+	o := orm.NewOrmUsingDB("rddp")
+	if keyWord != "" {
+		sqlCount = `SELECT  COUNT(1) AS count FROM (
+               SELECT * FROM english_classify
+               WHERE parent_id=0 AND classify_name LIKE '%` + keyWord + `%' AND classify_type = ?
+               UNION
+               SELECT * FROM english_classify
+               WHERE id IN(SELECT parent_id FROM english_classify
+               WHERE parent_id>0 AND classify_name LIKE '%` + keyWord + `%' AND classify_type = ?)
+               )AS t `
+		err = o.Raw(sqlCount, classifyType, classifyType).QueryRow(&count)
+	} else {
+		sqlCount = `SELECT COUNT(1) AS count FROM english_classify WHERE parent_id=0 AND classify_type = ?`
+		err = o.Raw(sqlCount, classifyType).QueryRow(&count)
+	}
+
+	return
+}
+
+func GetEnglishClassifyChild(parentId int, keyWord string, classifyType int) (items []*EnglishClassify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ``
+	if keyWord != "" {
+		sql = `SELECT * FROM english_classify WHERE parent_id=? AND classify_type = ? AND classify_name LIKE '%` + keyWord + `%' ORDER BY create_time ASC `
+	} else {
+		sql = `SELECT * FROM english_classify WHERE parent_id=? AND classify_type = ? ORDER BY create_time ASC `
+	}
+	_, err = o.Raw(sql, parentId, classifyType).QueryRows(&items)
+	return
+}
+
+func GetEnglishClassifyChildByParentIds(parentIds []int, keyWord string, classifyType int) (items []*EnglishClassify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ``
+	if keyWord != "" {
+		sql = `SELECT * FROM english_classify WHERE parent_id IN (` + utils.GetOrmInReplace(len(parentIds)) + `) AND classify_type = ? AND classify_name LIKE '%` + keyWord + `%' ORDER BY create_time ASC `
+	} else {
+		sql = `SELECT * FROM english_classify WHERE parent_id IN (` + utils.GetOrmInReplace(len(parentIds)) + `) AND classify_type = ? ORDER BY create_time ASC `
+	}
+	_, err = o.Raw(sql, parentIds, classifyType).QueryRows(&items)
+	return
+}
+
+func GetEnglishReportDetailByClassifyId(classifyIdFirst, classifyIdSecond int) (item *EnglishReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM english_report WHERE 1=1 `
+	if classifyIdSecond > 0 {
+		sql = sql + ` AND classify_id_second=?   ORDER BY stage DESC LIMIT 1`
+		err = o.Raw(sql, classifyIdSecond).QueryRow(&item)
+	} else {
+		sql = sql + ` AND classify_id_first=?   ORDER BY stage DESC LIMIT 1`
+		err = o.Raw(sql, classifyIdFirst).QueryRow(&item)
+	}
+	return
+}
+
+// Update 更新
+func (item *EnglishReport) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(item, cols...)
+	return
+}
+
+// ModifyEnglishReportAuthor 更改英文报告作者
+func ModifyEnglishReportAuthor(condition string, pars []interface{}, authorName string) (count int, err error) {
+	//产品权限
+	oRddp := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report set author = ? WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	err = oRddp.Raw(sql, authorName, pars).QueryRow(&count)
+	return
+}
+
+type EnglishClassify struct {
+	Id            int       `orm:"column(id);pk"`
+	ClassifyName  string    `description:"分类名称"`
+	Sort          int       `description:"排序"`
+	ParentId      int       `description:"父级分类id"`
+	CreateTime    time.Time `description:"创建时间"`
+	ModifyTime    time.Time `description:"修改时间"`
+	ClassifyLabel string    `description:"分类标签"`
+	ShowType      int       `description:"展示类型:1-列表 2-专栏"`
+	IsShow        int       `description:"是否在小程序显示:1-显示 0-隐藏"`
+	ClassifyType  int       `description:"分类类型:0英文报告,1英文线上路演"`
+}
+
+func AddEnglishClassify(item *EnglishClassify) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	lastId, err = o.Insert(item)
+	return
+}
+
+func ModifyEnglishClassify(item *EnglishClassify) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_classify
+	SET
+	classify_name = ?,
+	sort = ?,
+	parent_id = ?,
+	modify_time = ? 
+	WHERE id = ? `
+	_, err = o.Raw(sql, item.ClassifyName, item.Sort, item.ParentId, item.ModifyTime, item.Id).Exec()
+	return
+}
+
+// UpdateClassify 更新分类
+func (classifyInfo *EnglishClassify) UpdateEnglishClassify(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(classifyInfo, cols...)
+
+	return
+}
+
+// DeleteEnglishClassify 删除英文分类
+func DeleteEnglishClassify(classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` DELETE FROM english_classify WHERE id =? `
+	_, err = o.Raw(sql, classifyId).Exec()
+	return
+}
+
+func GetEnglishClassifyChildCounts(parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count FROM english_classify WHERE parent_id=? `
+	err = o.Raw(sql, parentId).QueryRow(&count)
+	return
+}
+
+func GetEnglishReportCounts(classifyId, parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ``
+	if parentId == 0 {
+		sql = `SELECT COUNT(1) AS count FROM english_report WHERE classify_id_first=? `
+	} else {
+		sql = `SELECT COUNT(1) AS count FROM english_report WHERE classify_id_second=? `
+	}
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+func GetEnglishClassifyCountsByName(name string, parentId int, classifyType int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count FROM english_classify WHERE classify_name=? AND parent_id = ? AND classify_type = ? `
+	err = o.Raw(sql, name, parentId, classifyType).QueryRow(&count)
+	return
+}
+
+// 获取分类列表
+func GetEnglishFirstClassifyList(classifyType, startSize, pageSize int) (items []*EnglishClassifyList, err error) {
+	sql := ``
+
+	sql = `SELECT * FROM english_classify WHERE parent_id=0 AND classify_type = ?  ORDER BY sort ASC,create_time ASC LIMIT ?,? `
+
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, classifyType, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetEnglishFirstClassifyListCount(classifyType int) (count int, err error) {
+	sqlCount := ``
+	sqlCount = `SELECT COUNT(1) AS count FROM english_classify WHERE parent_id=0 AND classify_type = ? `
+
+	o := orm.NewOrmUsingDB("rddp")
+	err = o.Raw(sqlCount, classifyType).QueryRow(&count)
+	return
+}
+
+func GetEnglishReportClassifyById(classifyId int) (item *EnglishClassify, err error) {
+	sql := `SELECT * FROM english_classify WHERE id=?`
+	o := orm.NewOrmUsingDB("rddp")
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+func GetEnglishReportClassifyByIds(classifyIds []int) (list []*EnglishClassify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_classify WHERE id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `)`
+	_, err = o.Raw(sql, classifyIds).QueryRows(&list)
+	return
+}
+
+// UpdateEnglishReportSecondClassifyNameByClassifyId 更新报告分类名称字段
+func UpdateEnglishReportSecondClassifyNameByClassifyId(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE english_report SET classify_name_second = ? WHERE classify_id_second = ? "
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
+// UpdateEnglishReportFirstClassifyNameByClassifyId 更新报告分类名称字段
+func UpdateEnglishReportFirstClassifyNameByClassifyId(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE english_report SET classify_name_first = ? WHERE classify_id_first = ? "
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
+// UpdateEnglishReportFirstClassifyNameByClassifyId 更新报告分类名称字段
+func UpdateEnglishReportByClassifyId(classifyFirstName, classifySecondName string, firstClassifyId, secondClassifyId int, ids string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE english_report SET classify_name_first = ?,classify_name_second = ?,classify_id_first=?, classify_id_second =?  WHERE id IN (" + ids + ") "
+	_, err = o.Raw(sql, classifyFirstName, classifySecondName, firstClassifyId, secondClassifyId).Exec()
+	return
+}
+
+// FetchEnglishReportById 主键获取英文报告
+func FetchEnglishReportById(reportId int) (item *EnglishReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report WHERE id=?`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+// UpdateReport 更新英文报告
+func (reportInfo *EnglishReport) UpdateReport(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(reportInfo, cols...)
+
+	return
+}
+
+// GetAllEnglishClassify 获取所有英文分类
+func GetAllEnglishClassify(classifyType int) (list []*Classify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM english_classify where classify_type = ? `
+	_, err = o.Raw(sql, classifyType).QueryRows(&list)
+	return
+}
+
+// GetEnglishReportByIds 根据IDs获取英文报告列表
+func GetEnglishReportByIds(reportIds []int, fieldArr []string) (list []*EnglishReport, err error) {
+	listLen := len(reportIds)
+	if listLen == 0 {
+		return
+	}
+	fields := ` * `
+	if len(fieldArr) > 0 {
+		fields = strings.Join(fieldArr, ",")
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT ` + fields + ` FROM english_report WHERE id IN (` + utils.GetOrmInReplace(listLen) + `)`
+	_, err = o.Raw(sql, reportIds).QueryRows(&list)
+	return
+}
+
+// MarkEditEnReport 标记编辑英文研报的请求数据
+type MarkEditEnReport struct {
+	ReportId int `description:"研报id"`
+	Status   int `description:"标记状态,1:编辑中,2:编辑完成"`
+}
+
+type EnglishClassifyNameParentName struct {
+	Id                 int       `description:"分类ID"`
+	ClassifyName       string    `description:"分类名称"`
+	Sort               int       `description:"排序"`
+	ParentId           int       `description:"父级分类id"`
+	CreateTime         time.Time `description:"创建时间"`
+	ModifyTime         time.Time `description:"修改时间"`
+	ClassifyLabel      string    `description:"分类标签"`
+	ShowType           int       `description:"展示类型:1-列表 2-专栏"`
+	IsShow             int       `description:"是否在小程序显示:1-显示 0-隐藏"`
+	ParentClassifyName string    `description:"父级分类名称"`
+}
+
+func GetEnglishClassifyByClassifyNameAndParentName(parentClassiyName, classifyName string) (item EnglishClassifyNameParentName, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := "SELECT c1.*, c2.classify_name as parent_classify_name FROM english_classify c1 LEFT JOIN english_classify c2 on c1.parent_id = c2.id where c1.parent_id > 0 and c2.classify_name = ? and c1.classify_name= ?"
+	err = o.Raw(sql, parentClassiyName, classifyName).QueryRow(&item)
+	return
+}
+
+type RSClassifyList []*EnglishClassifyList
+
+func (m RSClassifyList) Len() int {
+	return len(m)
+}
+
+func (m RSClassifyList) Less(i, j int) bool {
+	return m[i].Sort < m[j].Sort
+}
+
+func (m RSClassifyList) Swap(i, j int) {
+	m[i], m[j] = m[j], m[i]
+}
+
+type RSChildClassifyList []*EnglishClassify
+
+func (m RSChildClassifyList) Len() int {
+	return len(m)
+}
+
+func (m RSChildClassifyList) Less(i, j int) bool {
+	return m[i].Sort < m[j].Sort
+}
+
+func (m RSChildClassifyList) Swap(i, j int) {
+	m[i], m[j] = m[j], m[i]
+}
+
+// GetEnglishClassifyByClassifyNameParentId 获取英文分类
+func GetEnglishClassifyByClassifyNameParentId(classifyName string, parentId, classifyType int) (item *Classify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM english_classify where classify_name = ? and parent_id = ? and classify_type = ? `
+	err = o.Raw(sql, classifyName, parentId, classifyType).QueryRow(&item)
+	return
+}

+ 292 - 0
models/english_report_email.go

@@ -0,0 +1,292 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+// EnglishReportEmail 英文研报-邮箱/客户联系人
+type EnglishReportEmail struct {
+	Id              int       `orm:"column(id);pk" description:"邮箱ID"`
+	CompanyId       int       `description:"客户ID"`
+	Name            string    `description:"联系人名称"`
+	Email           string    `description:"邮箱地址"`
+	Mobile          string    `description:"手机号"`
+	CountryCode     string    `description:"区号,86、852、886等"`
+	BusinessCardUrl string    `description:"名片"`
+	ViewTotal       int       `description:"累计点击量/阅读量"`
+	LastViewTime    time.Time `description:"最后阅读时间"`
+	IsDeleted       int       `description:"删除状态:0-正常;1-已删除"`
+	Enabled         int       `description:"邮箱状态:1:有效,0:禁用"`
+	AdminId         int       `description:"创建人ID"`
+	AdminName       string    `description:"创建人姓名"`
+	Status          int       `description:"1:正式,2:临时,3:终止"`
+	CompanyName     string    `description:"公司名称"`
+	CreateTime      time.Time `description:"创建时间"`
+	ModifyTime      time.Time `description:"更新时间"`
+	RegisterTime    time.Time `description:"注册时间"`
+}
+
+func (item *EnglishReportEmail) TableName() string {
+	return "english_report_email"
+}
+
+// EnglishReportEmailSaveReq 保存邮箱请求体
+type EnglishReportEmailSaveReq struct {
+	Id              int    `description:"邮箱ID, 大于0为编辑"`
+	CompanyId       int    `description:"客户ID"`
+	Name            string `description:"客户名称"`
+	Email           string `description:"邮箱地址"`
+	Mobile          string `description:"手机号"`
+	CountryCode     string `description:"区号,86、852、886等"`
+	BusinessCardUrl string `description:"名片"`
+	Enabled         int    `description:"邮箱状态:1:有效,0:禁用"`
+}
+
+func (item *EnglishReportEmail) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(item)
+	if err != nil {
+		return
+	}
+	item.Id = int(id)
+	return
+}
+
+func (item *EnglishReportEmail) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(item, cols...)
+	return
+}
+
+// GetEnglishReportEmailById 主键获取邮箱
+func GetEnglishReportEmailById(id int) (item *EnglishReportEmail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report_email WHERE is_deleted = 0 AND id = ? LIMIT 1`
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+// EnglishReportEmailPageListResp 分页列表响应体
+type EnglishReportEmailPageListResp struct {
+	List   []*EnglishReportEmailResp
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+// EnglishReportEmailResp 邮箱响应体
+type EnglishReportEmailResp struct {
+	Id                  int       `description:"邮箱ID"`
+	CompanyId           int       `description:"客户ID"`
+	Name                string    `description:"客户名称"`
+	Email               string    `description:"邮箱地址"`
+	Mobile              string    `description:"手机号"`
+	CountryCode         string    `description:"区号,86、852、886等"`
+	BusinessCardUrl     string    `description:"名片"`
+	AdminName           string    `description:"创建人姓名"`
+	CreateTime          string    `description:"创建时间"`
+	ViewTotal           int       `description:"累计点击量"`
+	Enabled             int       `description:"邮箱状态:1:有效,0:禁用"`
+	CompanyName         string    `description:"公司名称"`
+	RegisterCompanyName string    `description:"注册公司名称"`
+	Status              int       `description:"1:正式,2:临时,3:终止"`
+	LastViewTime        time.Time `description:"最后阅读时间"`
+	IsDeleted           int       `description:"删除状态:0-正常;1-已删除"`
+	AdminId             int       `description:"创建人ID"`
+	ModifyTime          string    `description:"更新时间"`
+	RegisterTime        string    `description:"注册时间"`
+}
+
+// EnglishReportEmailRespItem 邮箱响应体
+type EnglishReportEmailRespItem struct {
+	Id                  int       `description:"邮箱ID"`
+	Name                string    `description:"客户名称"`
+	Email               string    `description:"邮箱地址"`
+	Mobile              string    `description:"手机号"`
+	CountryCode         string    `description:"区号,86、852、886等"`
+	BusinessCardUrl     string    `description:"名片"`
+	AdminName           string    `description:"创建人姓名"`
+	CreateTime          time.Time `description:"创建时间"`
+	ViewTotal           int       `description:"累计点击量"`
+	Enabled             int       `description:"邮箱状态:1:有效,0:禁用"`
+	CompanyName         string    `description:"公司名称"`
+	RegisterCompanyName string    `description:"注册公司名称"`
+	RegisterTime        time.Time `description:"注册时间"`
+}
+
+// GetEnglishReportEmailPageList 获取邮箱列表-分页
+func GetEnglishReportEmailPageList(condition string, pars []interface{}, order string, startSize, pageSize int) (total int, list []*EnglishReportEmailRespItem, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.id,a.name,a.email,a.mobile,a.country_code,a.business_card_url,a.view_total,a.company_id,
+a.last_view_time,a.admin_id,a.admin_name,a.create_time,a.modify_time,a.enabled,a.status,a.is_deleted,a.register_time,
+b.company_name AS company_name,a.company_name AS register_company_name FROM english_report_email AS a LEFT JOIN 
+	english_company AS b ON a.company_id = b.company_id
+WHERE a.is_deleted = 0 `
+	sql += condition
+	if order != "" {
+		sql += order
+	} else {
+		sql += ` ORDER BY a.create_time DESC`
+	}
+	totalSQl := `SELECT COUNT(1) total FROM (` + sql + `) z`
+	if err = o.Raw(totalSQl, pars).QueryRow(&total); err != nil {
+		return
+	}
+	sql += ` LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&list)
+	return
+}
+
+// GetEnglishReportEmailList 获取邮箱列表
+func GetEnglishReportEmailList(condition string, pars []interface{}, order string) (list []*EnglishReportEmail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report_email WHERE is_deleted = 0 `
+	sql += condition
+	if order != "" {
+		sql += order
+	} else {
+		sql += ` ORDER BY create_time DESC`
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}
+
+// GetEnglishReportEmailByEmail 地址获取邮箱
+func GetEnglishReportEmailByEmail(email string) (item *EnglishReportEmailResp, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.id,a.name,a.email,a.mobile,a.country_code,a.business_card_url,a.view_total,
+a.last_view_time,a.admin_id,a.admin_name,a.create_time,a.modify_time,a.enabled,a.status,
+b.company_name AS company_name,a.company_name AS register_company_name FROM english_report_email AS a LEFT JOIN 
+	english_company AS b ON a.company_id = b.company_id WHERE a.is_deleted = 0 AND a.email = ? LIMIT 1`
+	err = o.Raw(sql, email).QueryRow(&item)
+	return
+}
+
+// EnglishReportEmailDelReq 删除邮箱请求体
+type EnglishReportEmailDelReq struct {
+	EmailId int `description:"邮箱ID"`
+}
+
+// EnglishReportEmailDelReq 删除邮箱请求体
+type EnglishReportEditEnabledReq struct {
+	EmailId int `description:"邮箱ID"`
+	Enabled int `description:"1:有效,0:禁用"`
+}
+
+// DelEnglishReportEmail 删除邮箱
+func DelEnglishReportEmail(id int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `DELETE FROM english_report_email WHERE id = ? LIMIT 1`
+	_, err = o.Raw(sql, id).Exec()
+	return
+}
+
+// EnglishReportEmailSendReq 群发邮件请求体
+type EnglishReportEmailSendReq struct {
+	ReportId int    `description:"报告ID"`
+	EmailIds string `description:"邮箱IDs, 为空则表示全部"`
+	Theme    string `description:"邮件主题"`
+}
+
+// EnglishReportEmailConf 英文研报邮件配置
+type EnglishReportEmailConf struct {
+	FromAlias     string `description:"发信人昵称" json:"from_alias"`
+	SendAuthGroup string `description:"群发邮件权限组, 英文逗号分隔" json:"send_auth_group"`
+}
+
+// GetEnglishCompanyViewPageList 获取联系人点击量列表-分页
+func GetEnglishCompanyViewPageList(condition string, pars []interface{}, order string, startSize, pageSize int) (total int, list []*EnglishReportEmail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report_email WHERE view_total > 0 `
+	sql += condition
+	if order != "" {
+		sql += order
+	} else {
+		sql += ` ORDER BY create_time DESC`
+	}
+
+	totalSQl := `SELECT COUNT(1) total FROM (` + sql + `) z`
+	if err = o.Raw(totalSQl, pars).QueryRow(&total); err != nil {
+		return
+	}
+	sql += ` LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&list)
+	return
+}
+
+// MultiCreateEnglishEmail 批量新增英文邮箱/联系人
+//func MultiCreateEnglishEmail(items []*EnglishReportEmail, logs []*EnglishReportEmailOpLog) (err error) {
+//	o := orm.NewOrmUsingDB("rddp")
+//	tx, err := o.Begin()
+//	if err != nil {
+//		return
+//	}
+//	defer func() {
+//		if err != nil {
+//			_ = tx.Rollback()
+//		} else {
+//			_ = tx.Commit()
+//		}
+//	}()
+//
+//	// 新增联系人
+//	_, err = tx.InsertMulti(len(items), items)
+//	if err != nil {
+//		return
+//	}
+//
+//	// 新增日志
+//	_, err = tx.InsertMulti(len(logs), logs)
+//	return
+//}
+
+// EnglishReportEmailResendReq 邮件重发请求体
+type EnglishReportEmailResendReq struct {
+	ReportId int `description:"报告ID"`
+	SendId   int `description:"发送ID, 0表示批量发送全部失败邮件"`
+}
+
+// UpdateEnglishEmailEnabledByCompanyId 更新客户下所有联系人状态
+func UpdateEnglishEmailEnabledByCompanyId(companyId, enabled int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report_email SET enabled = ? WHERE company_id = ?`
+	_, err = o.Raw(sql, enabled, companyId).Exec()
+	return
+}
+
+// EnglishReportMoveReq 移动至当前客户请求体
+type EnglishReportMoveReq struct {
+	EmailId   int `description:"邮箱ID"`
+	CompanyId int `description:"公司id"`
+}
+
+// GetEnglishReportEmailListWithCompany 获取邮箱列表
+func GetEnglishReportEmailListWithCompany(condition string, pars []interface{}, order string) (list []*EnglishReportEmailRespItem, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.id,a.name,a.email,a.mobile,a.country_code,a.business_card_url,a.view_total,
+a.last_view_time,a.admin_id,a.admin_name,a.create_time,a.modify_time,a.enabled,a.status,
+b.company_name AS company_name,a.company_name AS register_company_name FROM english_report_email AS a LEFT JOIN 
+	english_company AS b ON a.company_id = b.company_id WHERE is_deleted = 0 `
+	sql += condition
+	if order != "" {
+		sql += order
+	} else {
+		sql += ` ORDER BY create_time DESC`
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}
+
+// GetEnCompanyIdsByKeyword 关键词获取英文客户IDs
+func GetEnCompanyIdsByKeyword(keyword string) (companyIds []int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT DISTINCT
+				a.company_id
+			FROM
+				english_report_email AS a
+			JOIN english_company AS b ON a.company_id = b.company_id AND b.is_deleted = 0 AND b.enabled = 1
+			WHERE
+				a.is_deleted = 0 AND a.status = 1 AND (a.email LIKE ? OR a.mobile LIKE ? OR b.company_name LIKE ?)`
+	_, err = o.Raw(sql, keyword, keyword, keyword).QueryRows(&companyIds)
+	return
+}

+ 160 - 0
models/english_report_email_log.go

@@ -0,0 +1,160 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"time"
+)
+
+const (
+	EnglishReportEmailLogSourceAli     = 1 // 来源-阿里云
+	EnglishReportEmailLogSourceTencent = 2 // 来源-腾讯云
+
+	EnglishReportEmailLogStatusIng     = -1 // 推送状态-已发送
+	EnglishReportEmailLogStatusFail    = 0  // 推送状态-发送失败
+	EnglishReportEmailLogStatusSuccess = 1  // 推送状态-发送成功
+)
+
+// EnglishReportEmailLog 英文研报-邮件推送记录
+type EnglishReportEmailLog struct {
+	Id           int       `orm:"column(id);pk;auto" description:"邮箱ID"`
+	ReportId     int       `description:"报告ID或者线上路演ID"`
+	ReportType   int       `description:"类型:0英文研报,1英文线上路演"`
+	EmailId      int       `description:"邮箱ID"`
+	Email        string    `description:"邮箱地址"`
+	SendData     string    `description:"请求信息"`
+	Result       string    `description:"响应信息"`
+	SendStatus   int       `description:"发送状态:-1-已发送(一个中间状态,重新推送时可能会产生这种状态);0-发送失败;1-发送成功"`
+	CreateTime   time.Time `description:"请求时间"`
+	Source       int       `description:"服务商:1-阿里云;2-腾讯云"`
+	IsDeleted    int       `description:"是否已删除: 0-正常; 1-已删除"`
+	CallbackData string    `description:"回调信息"`
+	ErrMsg       string    `description:"错误信息(Result/CallbackData里面的取起来麻烦=_=!)"`
+}
+
+func (item *EnglishReportEmailLog) TableName() string {
+	return "english_report_email_log"
+}
+
+func (item *EnglishReportEmailLog) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Insert(item)
+	return
+}
+
+func (item *EnglishReportEmailLog) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(item, cols...)
+	return
+}
+
+func (item *EnglishReportEmailLog) InsertMulti(items []*EnglishReportEmailLog) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+// GetEnglishReportEmailLogList 获取日志列表
+func GetEnglishReportEmailLogList(condition string, pars []interface{}) (list []*EnglishReportEmailLog, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report_email_log WHERE is_deleted = 0 `
+	if condition != `` {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}
+
+// EnglishReportEmailLogPageListResp 英文研报-邮件推送记录分页列表响应体
+type EnglishReportEmailLogPageListResp struct {
+	List   []*EnglishReportEmailLogPageList `description:"列表数据"`
+	Paging *paging.PagingItem               `description:"分页数据"`
+}
+
+// EnglishReportEmailLogPageList 英文研报-邮件推送记录分页列表
+type EnglishReportEmailLogPageList struct {
+	SendId     int    `description:"推送ID"`
+	ReportId   int    `description:"报告ID"`
+	EmailId    int    `description:"邮箱ID"`
+	Email      string `description:"邮箱地址"`
+	ResultMsg  string `description:"结果详情"`
+	SendStatus int    `description:"发送状态:-1-已发送;0-发送失败;1-发送成功"`
+	Source     int    `description:"服务商:1-阿里云;2-腾讯云"`
+	CreateTime string `description:"创建时间"`
+}
+
+// GetEnglishReportEmailLogPageList 获取日志列表-分页
+func GetEnglishReportEmailLogPageList(condition string, pars []interface{}, order string, startSize, pageSize int) (total int, list []*EnglishReportEmailLog, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report_email_log WHERE is_deleted = 0 `
+	sql += condition
+	if order != "" {
+		sql += order
+	} else {
+		sql += ` ORDER BY send_status ASC, create_time DESC`
+	}
+	totalSQl := `SELECT COUNT(1) total FROM (` + sql + `) z`
+	if err = o.Raw(totalSQl, pars).QueryRow(&total); err != nil {
+		return
+	}
+	sql += ` LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&list)
+	return
+}
+
+// GetEnglishReportEmailLogById 主键获取日志
+func GetEnglishReportEmailLogById(id int) (item *EnglishReportEmailLog, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report_email_log WHERE is_deleted = 0 AND id = ? LIMIT 1`
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+// DeleteEnglishReportEmailLogByIds IDs删除日志
+func DeleteEnglishReportEmailLogByIds(logIds []int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report_email_log SET is_deleted = 1 WHERE id IN (` + utils.GetOrmInReplace(len(logIds)) + `)`
+	_, err = o.Raw(sql, logIds).Exec()
+	return
+}
+
+// EnglishReportEmailLogFail 群发消息日志失败列表
+type EnglishReportEmailLogFail struct {
+	HasFail  int `description:"是否有失败记录: 0-无; 1-有"`
+	ReportId int `description:"报告ID"`
+}
+
+// GetEnglishReportEmailLogFailList 获取群发消息日志失败列表
+func GetEnglishReportEmailLogFailList(reportType int) (list []*EnglishReportEmailLogFail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT 1 AS has_fail, report_id FROM english_report_email_log WHERE report_type=? and is_deleted = 0 AND send_status = 0 GROUP BY report_id`
+	_, err = o.Raw(sql, reportType).QueryRows(&list)
+	return
+}
+
+// GetEnglishReportEmailLog 获取邮件推送日志
+func GetEnglishReportEmailLog(condition string, pars []interface{}) (item *EnglishReportEmailLog, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report_email_log WHERE 1 = 1 `
+	sql += condition
+	sql += ` ORDER BY id DESC LIMIT 1`
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+// GetHasFailEmailLogReportIds 获取存在邮件推送失败记录的英文报告IDs
+func GetHasFailEmailLogReportIds() (reportIds []int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT DISTINCT report_id FROM english_report_email_log WHERE is_deleted = 0 AND send_status = 0`
+	_, err = o.Raw(sql).QueryRows(&reportIds)
+	return
+}
+
+// GetSuccessEmailLogReportIds 获取邮件推送记录均为成功的英文报告IDs
+func GetSuccessEmailLogReportIds() (reportIds []int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT DISTINCT report_id FROM english_report_email_log WHERE is_deleted = 0 AND (send_status != 0 AND send_status != -1)`
+	_, err = o.Raw(sql).QueryRows(&reportIds)
+	return
+}

+ 316 - 0
models/english_video.go

@@ -0,0 +1,316 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"strings"
+	"time"
+)
+
+type EnglishVideo struct {
+	Id                 int       `orm:"column(id);pk;auto" description:"路演视频Id"`
+	ClassifyIdFirst    int       `description:"一级分类id"`
+	ClassifyNameFirst  string    `description:"一级分类名称"`
+	ClassifyIdSecond   int       `description:"二级分类id"`
+	ClassifyNameSecond string    `description:"二级分类名称"`
+	Title              string    `description:"标题"`
+	Abstract           string    `description:"摘要"`
+	Author             string    `description:"作者"`
+	CreateTime         time.Time `description:"创建时间"`
+	ModifyTime         time.Time `description:"修改时间"`
+	State              int       `description:"1:未发布,2:已发布"`
+	PublishTime        time.Time `description:"发布时间"`
+	VideoUrl           string    `description:"视频文件URL"`
+	VideoCoverUrl      string    `description:"视频文件封面地址"`
+	VideoSeconds       string    `description:"视频时长"`
+	VideoCode          string    `description:"报告唯一编码"`
+	Pv                 int       `description:"Pv"`
+	PvEmail            int       `description:"邮箱PV"`
+	EmailState         int       `description:"群发邮件状态: 0-未发送; 1-已发送"`
+	Overview           string    `description:"英文概述部分"`
+	AdminId            int       `description:"上传视频的管理员账号"`
+	AdminRealName      string    `description:"上传视频的管理员姓名"`
+}
+
+func AddEnglishVideo(item *EnglishVideo) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Insert(item)
+	return
+}
+
+func ModifyEnglishVideoCode(id int, VideoCode string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_video SET video_code=? WHERE id=? `
+	_, err = o.Raw(sql, VideoCode, id).Exec()
+	return
+}
+
+type SaveEnglishVideoReq struct {
+	Id                 int    `description:"路演视频ID"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	//ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	//ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	State              int    `description:"状态:1:未发布,2:已发布"`
+	VideoUrl           string `description:"视频文件URL"`
+	VideoCoverUrl      string `description:"视频文件封面地址"`
+	VideoSeconds       string `description:"视频时长"`
+	Overview           string `description:"英文概述部分"`
+}
+
+type SaveEnglishVideoResp struct {
+	Id   int  `description:"路演视频ID"`
+	VideoCode string `description:"报告code"`
+}
+
+
+
+type ElasticEnglishVideoDetail struct {
+	Id            int    `description:"路演视频ID"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	StageStr           string `description:"报告期数"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	PublishState       int    `description:"状态:1:未发布,2:已发布"`
+	BodyContent        string `description:"内容"`
+	ContentSub         string `description:"前两段内容"`
+	CreateTime         string `description:"创建时间"`
+	PublishTime        string `description:"发布时间"`
+	VideoCode         string `description:"报告唯一编码"`
+	Overview           string `description:"英文概述部分"`
+}
+
+func EditEnglishVideo(item *EnglishVideo, Id int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_video
+			SET
+			  classify_id_first =?,
+			  classify_name_first = ?,
+			  classify_id_second = ?,
+			  classify_name_second = ?,
+			  title = ?,
+			  abstract = ?,
+			  state = ?,
+			  modify_time = ?,
+			  overview = ?,
+              video_url = ?,
+              video_cover_url = ?,
+              video_seconds = ?
+			WHERE id = ? `
+	_, err = o.Raw(sql, item.ClassifyIdFirst, item.ClassifyNameFirst, item.ClassifyIdSecond, item.ClassifyNameSecond, item.Title,
+		item.Abstract, item.State, time.Now(), item.Overview, item.VideoUrl, item.VideoCoverUrl, item.VideoSeconds, Id).Exec()
+	return
+}
+
+type EnglishVideoDetail struct {
+	Id                 int    `description:"路演视频ID"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	CreateTime         string `description:"创建时间"`
+	ModifyTime         string `description:"修改时间"`
+	State              int    `description:"1:未发布,2:已发布"`
+	PublishTime        string `description:"发布时间"`
+	MsgIsSend          int    `description:"消息是否已发送,0:否,1:是"`
+	VideoCode          string `description:"报告唯一编码"`
+	VideoUrl           string `description:"视频文件URL"`
+	VideoCoverUrl      string `description:"视频文件封面地址"`
+	VideoSeconds       string `description:"视频时长"`
+	Pv                 int    `description:"Pv"`
+	Overview           string `description:"英文概述部分"`
+}
+
+func GetEnglishVideoById(Id int) (item *EnglishVideoDetail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_video WHERE id=?`
+	err = o.Raw(sql, Id).QueryRow(&item)
+	return
+}
+
+func GetEnglishVideoItemById(Id int) (item *EnglishVideo, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_video WHERE id = ? LIMIT 1`
+	err = o.Raw(sql, Id).QueryRow(&item)
+	return
+}
+
+type EnglishVideoList struct {
+	Id                 int    `description:"id"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	CreateTime         string `description:"创建时间"`
+	ModifyTime         string `description:"修改时间"`
+	State              int    `description:"1:未发布,2:已发布"`
+	PublishTime        string `description:"发布时间"`
+	VideoUrl           string `description:"视频文件URL"`
+	VideoCoverUrl      string `description:"视频文件封面地址"`
+	VideoSeconds       string `description:"视频时长"`
+	VideoCode          string `description:"报告唯一编码"`
+	Pv                 int    `description:"Pv"`
+	ShareUrl           string `description:"分享url"`
+	PvEmail            int    `description:"邮箱PV"`
+	EmailState         int    `description:"群发邮件状态: 0-未发送; 1-已发送"`
+	EmailAuth          bool   `description:"是否有权限群发邮件"`
+	EmailHasFail       bool   `description:"是否存在邮件发送失败的记录"`
+	Overview           string `description:"英文概述部分"`
+	AdminId            int    `description:"上传视频的管理员账号"`
+	AdminRealName      string `description:"上传视频的管理员姓名"`
+}
+
+type EnglishVideoListResp struct {
+	List   []*EnglishVideoList
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type EnglishVideoReq struct {
+	Id  int `description:"路演视频id"`
+}
+
+func GetEnglishVideoListCount(condition string, pars []interface{}) (count int, err error) {
+	//产品权限
+	oRddp := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count  FROM english_video WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	err = oRddp.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetEnglishVideoList(condition string, pars []interface{}, startSize, pageSize int) (items []*EnglishVideoList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+
+	sql := `SELECT * 
+        FROM english_video WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += `ORDER BY  modify_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetEnglishVideoByCondition(condition string, pars []interface{}) (items []*EnglishVideo, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * 
+        FROM english_video WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// 发布报告
+func PublishEnglishVideoById(Id int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_video SET state=2,publish_time=now(),modify_time=NOW() WHERE id = ? `
+	_, err = o.Raw(sql, Id).Exec()
+	return
+}
+
+//取消发布报告
+func PublishCancelEnglishVideo(Ids int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` UPDATE english_video SET state=1,publish_time=null WHERE id =?  `
+	_, err = o.Raw(sql, Ids).Exec()
+	return
+}
+
+// DeleteEnglishVideo 删除路演视频
+func DeleteEnglishVideo(Id int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` DELETE FROM english_video WHERE id=? `
+	_, err = o.Raw(sql, Id).Exec()
+	return
+}
+
+
+func GetEnglishVideoDetailByClassifyId(classifyIdFirst, classifyIdSecond int) (item *EnglishVideo, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM english_video WHERE 1=1 `
+	if classifyIdSecond > 0 {
+		sql = sql + ` AND classify_id_second=?   ORDER BY stage DESC LIMIT 1`
+		err = o.Raw(sql, classifyIdSecond).QueryRow(&item)
+	} else {
+		sql = sql + ` AND classify_id_first=?   ORDER BY stage DESC LIMIT 1`
+		err = o.Raw(sql, classifyIdFirst).QueryRow(&item)
+	}
+	return
+}
+
+// Update 更新
+func (item *EnglishVideo) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(item, cols...)
+	return
+}
+
+func GetEnglishVideoCounts(classifyId, parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ``
+	if parentId == 0 {
+		sql = `SELECT COUNT(1) AS count FROM english_video WHERE classify_id_first=? `
+	} else {
+		sql = `SELECT COUNT(1) AS count FROM english_video WHERE classify_id_second=? `
+	}
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+// UpdateEnglishVideoSecondClassifyNameByClassifyId 更新报告分类名称字段
+func UpdateEnglishVideoSecondClassifyNameByClassifyId(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE english_video SET classify_name_second = ? WHERE classify_id_second = ? "
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
+// UpdateEnglishVideoFirstClassifyNameByClassifyId 更新报告分类名称字段
+func UpdateEnglishVideoFirstClassifyNameByClassifyId(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE english_video SET classify_name_first = ? WHERE classify_id_first = ? "
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
+// UpdateEnglishVideoFirstClassifyNameByClassifyId 更新报告分类名称字段
+func UpdateEnglishVideoByClassifyId(classifyFirstName, classifySecondName  string, firstClassifyId, secondClassifyId int, ids string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE english_video SET classify_name_first = ?,classify_name_second = ?,classify_id_first=?, classify_id_second =?  WHERE id IN ("+ids+") "
+	_, err = o.Raw(sql, classifyFirstName, classifySecondName, firstClassifyId, secondClassifyId).Exec()
+	return
+}
+
+// GetEnglishVideoByIds 根据IDs获取英文报告列表
+func GetEnglishVideoByIds(Ids []int, fieldArr []string) (list []*EnglishVideo, err error) {
+	listLen := len(Ids)
+	if listLen == 0 {
+		return
+	}
+	fields := ` * `
+	if len(fieldArr) > 0 {
+		fields = strings.Join(fieldArr, ",")
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT ` + fields + ` FROM english_video WHERE id IN (` + utils.GetOrmInReplace(listLen) + `)`
+	_, err = o.Raw(sql, Ids).QueryRows(&list)
+	return
+}

+ 48 - 0
models/permission.go

@@ -0,0 +1,48 @@
+package models
+
+import "github.com/beego/beego/v2/client/orm"
+
+// ChartPermissionSearchKeyWordMapping 权限相关
+type ChartPermissionSearchKeyWordMapping struct {
+	Id                 int    `description:"id" json:"-"`
+	ChartPermissionId  int    `description:"权限id"`
+	KeyWord            string `description:"二级分类名称"`
+	From               string `description:"类型标识" json:"-"`
+	TacticType         string `description:"策略表type字段值" json:"-"`
+	TeleconferenceSort int    `description:"电话会类型排序" json:"-"`
+}
+
+func GetPermission(classifyNameSecond string) (items []*ChartPermissionSearchKeyWordMapping, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM chart_permission_search_key_word_mapping AS a WHERE a.from='rddp' AND a.key_word=? `
+	_, err = o.Raw(sql, classifyNameSecond).QueryRows(&items)
+	return
+}
+
+func AddChartPermissionChapterMapping(chartPermissionId int, reportId int64) (err error) {
+	sql := `INSERT INTO chart_permission_chapter_mapping (chart_permission_id, report_chapter_type_id,research_type)
+           VALUES(?,?,?)`
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, chartPermissionId, reportId, "rddp").Exec()
+	return
+}
+
+func RemoveChartPermissionChapterMapping(reportId int64) (err error) {
+	sql := ` DELETE FROM chart_permission_chapter_mapping WHERE research_type=? AND report_chapter_type_id=? `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, "rddp", reportId).Exec()
+	return
+}
+
+type ChartPermissionMappingIdName struct {
+	PermissionId   int
+	PermissionName string
+}
+
+func GetChartPermissionNameFromMappingByKeyword(keyword string, source string) (list []*ChartPermissionMappingIdName, err error) {
+	o := orm.NewOrm()
+	sql := " SELECT b.chart_permission_id AS permission_id,b.permission_name FROM chart_permission_search_key_word_mapping AS a INNER JOIN chart_permission AS b ON a.chart_permission_id = b.chart_permission_id WHERE a.`from` = ? AND a.key_word = ? "
+	_, err = o.Raw(sql, source, keyword).QueryRows(&list)
+
+	return
+}

+ 1008 - 0
models/report.go

@@ -0,0 +1,1008 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+type Report struct {
+	Id                 int       `orm:"column(id)" description:"报告Id"`
+	AddType            int       `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int       `description:"一级分类id"`
+	ClassifyNameFirst  string    `description:"一级分类名称"`
+	ClassifyIdSecond   int       `description:"二级分类id"`
+	ClassifyNameSecond string    `description:"二级分类名称"`
+	Title              string    `description:"标题"`
+	Abstract           string    `description:"摘要"`
+	Author             string    `description:"作者"`
+	Frequency          string    `description:"频度"`
+	CreateTime         string    `description:"创建时间"`
+	ModifyTime         time.Time `description:"修改时间"`
+	State              int       `description:"1:未发布,2:已发布"`
+	PublishTime        time.Time `description:"发布时间"`
+	Stage              int       `description:"期数"`
+	MsgIsSend          int       `description:"消息是否已发送,0:否,1:是"`
+	ThsMsgIsSend       int       `description:"客户群消息是否已发送,0:否,1:是"`
+	Content            string    `description:"内容"`
+	VideoUrl           string    `description:"音频文件URL"`
+	VideoName          string    `description:"音频文件名称"`
+	VideoPlaySeconds   string    `description:"音频播放时长"`
+	VideoSize          string    `description:"音频文件大小,单位M"`
+	ContentSub         string    `description:"内容前两个章节"`
+	ReportCode         string    `description:"报告唯一编码"`
+	ReportVersion      int       `description:"1:旧版,2:新版"`
+	HasChapter         int       `description:"是否有章节 0-否 1-是"`
+	ChapterType        string    `description:"章节类型 day-晨报 week-周报"`
+	OldReportId        int       `description:"research_report表ID, 大于0则表示该报告为老后台同步过来的"`
+	MsgSendTime        time.Time `description:"模版消息发送时间"`
+	AdminId            int       `description:"创建者账号"`
+	AdminRealName      string    `description:"创建者姓名"`
+}
+
+type ReportList struct {
+	Id                 int                       `description:"报告Id"`
+	AddType            int                       `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int                       `description:"一级分类id"`
+	ClassifyNameFirst  string                    `description:"一级分类名称"`
+	ClassifyIdSecond   int                       `description:"二级分类id"`
+	ClassifyNameSecond string                    `description:"二级分类名称"`
+	Title              string                    `description:"标题"`
+	Abstract           string                    `description:"摘要"`
+	Author             string                    `description:"作者"`
+	Frequency          string                    `description:"频度"`
+	CreateTime         string                    `description:"创建时间"`
+	ModifyTime         time.Time                 `description:"修改时间"`
+	State              int                       `description:"1:未发布,2:已发布"`
+	PublishTime        string                    `description:"发布时间"`
+	Stage              int                       `description:"期数"`
+	MsgIsSend          int                       `description:"模板消息是否已发送,0:否,1:是"`
+	Content            string                    `description:"内容"`
+	VideoUrl           string                    `description:"音频文件URL"`
+	VideoName          string                    `description:"音频文件名称"`
+	VideoPlaySeconds   string                    `description:"音频播放时长"`
+	ContentSub         string                    `description:"内容前两个章节"`
+	Pv                 int                       `description:"Pv"`
+	Uv                 int                       `description:"Uv"`
+	ReportCode         string                    `description:"报告唯一编码"`
+	ReportVersion      int                       `description:"1:旧版,2:新版"`
+	ThsMsgIsSend       int                       `description:"客户群消息是否已发送,0:否,1:是"`
+	NeedThsMsg         int                       `description:"是否需要推送客群消息 0-否 1-是"`
+	HasChapter         int                       `description:"是否有章节 0-否 1-是"`
+	ChapterType        string                    `description:"章节类型 day-晨报 week-周报"`
+	ChapterVideoList   []*ReportChapterVideoList `description:"章节音频列表"`
+	OldReportId        int                       `description:"research_report表ID, 大于0则表示该报告为老后台同步过来的"`
+	MsgSendTime        string                    `description:"模版消息发送时间"`
+	CanEdit            bool                      `description:"是否可编辑"`
+	Editor             string                    `description:"编辑人"`
+	AdminId            int                       `description:"创建者账号"`
+	AdminRealName      string                    `description:"创建者姓名"`
+}
+
+type ReportListResp struct {
+	List   []*ReportList
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+func GetReportListCount(condition string, pars []interface{}, companyType string) (count int, err error) {
+	//产品权限
+	companyTypeSqlStr := ``
+	if companyType == "ficc" {
+		companyTypeSqlStr = " AND classify_id_first != 40 "
+	} else if companyType == "权益" {
+		companyTypeSqlStr = " AND classify_id_first = 40 "
+	}
+
+	oRddp := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count  FROM report WHERE 1=1 ` + companyTypeSqlStr
+	if condition != "" {
+		sql += condition
+	}
+	err = oRddp.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetReportList(condition string, pars []interface{}, companyType string, startSize, pageSize int) (items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	//产品权限
+	companyTypeSqlStr := ``
+	if companyType == "ficc" {
+		companyTypeSqlStr = " AND classify_id_first != 40 "
+	} else if companyType == "权益" {
+		companyTypeSqlStr = " AND classify_id_first = 40 "
+	}
+
+	sql := `SELECT *,
+        (SELECT COUNT(1) FROM report_view_record AS rvr WHERE rvr.report_id=report.id) AS pv,
+        (SELECT COUNT(DISTINCT user_id) FROM report_view_record AS rvr WHERE rvr.report_id=report.id) AS uv
+        FROM report WHERE 1=1  ` + companyTypeSqlStr
+	if condition != "" {
+		sql += condition
+	}
+	sql += `ORDER BY state ASC, modify_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// 发布报告
+func PublishReport(reportIds string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET state=2,publish_time=now(),modify_time=NOW() WHERE id IN (` + reportIds + `)`
+	_, err = o.Raw(sql).Exec()
+	return
+}
+
+// 取消发布报告
+func PublishCancleReport(reportIds int, publishTimeNullFlag bool) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var sql string
+	if publishTimeNullFlag {
+		sql = ` UPDATE report SET state=1, publish_time=null WHERE id =?`
+	} else {
+		sql = ` UPDATE report SET state=1 WHERE id =?`
+	}
+	_, err = o.Raw(sql, reportIds).Exec()
+	return
+}
+
+// 删除报告
+func DeleteReport(reportIds int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` DELETE FROM report WHERE id =? `
+	_, err = o.Raw(sql, reportIds).Exec()
+	return
+}
+
+type ReportDetail struct {
+	Id                 int    `orm:"column(id)" description:"报告Id"`
+	AddType            int    `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	CreateTime         string `description:"创建时间"`
+	ModifyTime         string `description:"修改时间"`
+	State              int    `description:"1:未发布,2:已发布"`
+	PublishTime        string `description:"发布时间"`
+	Stage              int    `description:"期数"`
+	MsgIsSend          int    `description:"消息是否已发送,0:否,1:是"`
+	Content            string `description:"内容"`
+	VideoUrl           string `description:"音频文件URL"`
+	VideoName          string `description:"音频文件名称"`
+	VideoPlaySeconds   string `description:"音频播放时长"`
+	ContentSub         string `description:"内容前两个章节"`
+	ThsMsgIsSend       int    `description:"客户群消息是否已发送,0:否,1:是"`
+	HasChapter         int    `description:"是否有章节 0-否 1-是"`
+	ChapterType        string `description:"章节类型 day-晨报 week-周报"`
+}
+
+func GetReportById(reportId int) (item *ReportDetail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE id=?`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+func GetReportByIds(reportIds string) (list []*ReportDetail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE id in ` + reportIds
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// GetSimpleReportByIds 根据报告ID查询报告基本信息
+func GetSimpleReportByIds(reportIds string) (list []*ReportDetail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT id, title FROM report WHERE id IN (` + reportIds + `)`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+func GetReportStage(classifyIdFirst, classifyIdSecond int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ``
+	if classifyIdSecond > 0 {
+		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_second=? "
+		o.Raw(sql, classifyIdSecond).QueryRow(&count)
+	} else {
+		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_first=? "
+		o.Raw(sql, classifyIdFirst).QueryRow(&count)
+	}
+	return
+}
+
+func GetReportStageEdit(classifyIdFirst, classifyIdSecond, reportId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ``
+	if classifyIdSecond > 0 {
+		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_second=? AND id<>? "
+		o.Raw(sql, classifyIdSecond, reportId).QueryRow(&count)
+	} else {
+		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_first=? AND id<>? "
+		o.Raw(sql, classifyIdFirst, reportId).QueryRow(&count)
+	}
+	return
+}
+
+type PublishReq struct {
+	ReportIds string `description:"报告id,多个用英文逗号隔开"`
+}
+
+type PublishCancelReq struct {
+	ReportIds int `description:"报告id"`
+}
+
+type DeleteReq struct {
+	ReportIds int `description:"报告id"`
+}
+
+type AddReq struct {
+	AddType            int    `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	State              int    `description:"状态:1:未发布,2:已发布"`
+	Content            string `description:"内容"`
+	CreateTime         string `description:"创建时间"`
+	ReportVersion      int    `description:"1:旧版,2:新版"`
+}
+
+type AddResp struct {
+	ReportId   int64  `description:"报告id"`
+	ReportCode string `description:"报告code"`
+}
+
+func AddReport(item *Report) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	lastId, err = o.Insert(item)
+	return
+}
+
+type EditReq struct {
+	ReportId           int64  `description:"报告id"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	State              int    `description:"状态:1:未发布,2:已发布"`
+	Content            string `description:"内容"`
+	CreateTime         string `description:"创建时间"`
+}
+
+type EditResp struct {
+	ReportId   int64  `description:"报告id"`
+	ReportCode string `description:"报告code"`
+}
+
+func EditReport(item *Report, reportId int64) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report
+			SET
+			  classify_id_first =?,
+			  classify_name_first = ?,
+			  classify_id_second = ?,
+			  classify_name_second = ?,
+			  title = ?,
+			  abstract = ?,
+			  author = ?,
+			  frequency = ?,
+			  state = ?,
+			  content = ?,
+			  content_sub = ?,
+			  stage =?,
+			  create_time = ?,
+			  modify_time = ?
+			WHERE id = ? `
+	_, err = o.Raw(sql, item.ClassifyIdFirst, item.ClassifyNameFirst, item.ClassifyIdSecond, item.ClassifyNameSecond, item.Title,
+		item.Abstract, item.Author, item.Frequency, item.State, item.Content, item.ContentSub, item.Stage, item.CreateTime, time.Now(), reportId).Exec()
+	return
+}
+
+type ReportDetailReq struct {
+	ReportId int `description:"报告id"`
+}
+
+type ClassifyIdDetailReq struct {
+	ClassifyIdFirst  int `description:"报告一级分类id"`
+	ClassifyIdSecond int `description:"报告二级分类id"`
+}
+
+func GetReportDetailByClassifyId(classifyIdFirst, classifyIdSecond int) (item *Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report WHERE 1=1 `
+	if classifyIdSecond > 0 {
+		sql = sql + ` AND classify_id_second=?   ORDER BY stage DESC LIMIT 1`
+		err = o.Raw(sql, classifyIdSecond).QueryRow(&item)
+	} else {
+		sql = sql + ` AND classify_id_first=?   ORDER BY stage DESC LIMIT 1`
+		err = o.Raw(sql, classifyIdFirst).QueryRow(&item)
+	}
+	return
+}
+
+type SendTemplateMsgReq struct {
+	ReportId int `description:"报告id"`
+}
+
+func ModifyReportMsgIsSend(reportId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	report, err := GetReportById(reportId)
+	if err != nil {
+		return
+	}
+	if report.MsgIsSend == 0 {
+		sql := `UPDATE report SET msg_is_send = 1, msg_send_time=NOW()  WHERE id = ? `
+		_, err = o.Raw(sql, reportId).Exec()
+	}
+	return
+}
+
+func ModifyReportVideo(reportId int, videoUrl, videoName, videoSize string, playSeconds float64) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET video_url=?,video_name=?,video_play_seconds=?,video_size=? WHERE id=? `
+	_, err = o.Raw(sql, videoUrl, videoName, playSeconds, videoSize, reportId).Exec()
+	return
+}
+
+type ReportItem struct {
+	Id                 int       `orm:"column(id)" description:"报告Id"`
+	AddType            int       `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int       `description:"一级分类id"`
+	ClassifyNameFirst  string    `description:"一级分类名称"`
+	ClassifyIdSecond   int       `description:"二级分类id"`
+	ClassifyNameSecond string    `description:"二级分类名称"`
+	Title              string    `description:"标题"`
+	Abstract           string    `description:"摘要"`
+	Author             string    `description:"作者"`
+	Frequency          string    `description:"频度"`
+	CreateTime         time.Time `description:"创建时间"`
+	ModifyTime         time.Time `description:"修改时间"`
+	State              int       `description:"1:未发布,2:已发布"`
+	PublishTime        time.Time `description:"发布时间"`
+	Stage              int       `description:"期数"`
+	MsgIsSend          int       `description:"消息是否已发送,0:否,1:是"`
+	Content            string    `description:"内容"`
+	VideoUrl           string    `description:"音频文件URL"`
+	VideoName          string    `description:"音频文件名称"`
+	VideoPlaySeconds   string    `description:"音频播放时长"`
+	ContentSub         string    `description:"内容前两个章节"`
+}
+
+func GetReportItemById(reportId int) (item *ReportItem, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE id=?`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+type SaveReportContent struct {
+	Content  string `description:"内容"`
+	ReportId int    `description:"报告id"`
+	NoChange int    `description:"内容是否未改变:1:内容未改变"`
+}
+
+func EditReportContent(reportId int, content, contentSub string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` UPDATE report SET content=?,content_sub=?,modify_time=NOW() WHERE id=? `
+	_, err = o.Raw(sql, content, contentSub, reportId).Exec()
+	return
+}
+
+func AddReportSaveLog(reportId, adminId int, content, contentSub, adminName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` INSERT INTO report_save_log(report_id, content,content_sub,admin_id,admin_name) VALUES (?,?,?,?,?) `
+	_, err = o.Raw(sql, reportId, content, contentSub, adminId, adminName).Exec()
+	return
+}
+
+type SaveReportContentResp struct {
+	ReportId int `description:"报告id"`
+}
+
+func ModifyReportCode(reportId int64, reportCode string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET report_code=? WHERE id=? `
+	_, err = o.Raw(sql, reportCode, reportId).Exec()
+	return
+}
+
+func ModifyReportThsMsgIsSend(item *ReportDetail) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	if item.ThsMsgIsSend == 0 {
+		sql := `UPDATE report SET ths_msg_is_send = 1 WHERE id = ? `
+		_, err = o.Raw(sql, item.Id).Exec()
+	}
+	return
+}
+
+type ThsSendTemplateMsgReq struct {
+	ReportId []int `description:"报告id"`
+}
+
+type PublishDayWeekReportReq struct {
+	ReportId int `description:"报告ID"`
+}
+
+// SaveDayWeekReportReq 新增晨报周报请求体
+type SaveDayWeekReportReq struct {
+	ReportId   int    `description:"报告ID"`
+	Title      string `description:"标题"`
+	ReportType string `description:"一级分类ID"`
+	Author     string `description:"作者"`
+	CreateTime string `description:"创建时间"`
+}
+
+// GetDayWeekReportStage 获取晨报周报期数
+func GetDayWeekReportStage(classifyIdFirst int, yearStart time.Time) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_first = ? AND create_time > ? "
+	o.Raw(sql, classifyIdFirst, yearStart).QueryRow(&count)
+
+	return
+}
+
+// AddReportAndChapter 新增报告及章节
+func AddReportAndChapter(reportItem *Report, chapterItemList []*ReportChapter) (reportId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	if reportId, err = to.Insert(reportItem); err != nil {
+		return
+	}
+	if len(chapterItemList) > 0 {
+		for _, chapterItem := range chapterItemList {
+			chapterItem.ReportId = int(reportId)
+			if _, tmpErr := to.Insert(chapterItem); tmpErr != nil {
+				return
+			}
+		}
+	}
+
+	return
+}
+
+// GetReportByReportId 主键获取报告
+func GetReportByReportId(reportId int) (item *Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE id = ?`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+// GetReportByOldReportId 根据老报告id主键获取报告
+func GetReportByOldReportId(reportId int) (item *Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE old_report_id = ?`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+// DeleteDayWeekReportAndChapter 删除晨周报及章节
+func DeleteDayWeekReportAndChapter(reportId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	sql := ` DELETE FROM report WHERE id = ? LIMIT 1 `
+	if _, err = to.Raw(sql, reportId).Exec(); err != nil {
+		return
+	}
+	sql = ` DELETE FROM report_chapter WHERE report_id = ? `
+	if _, err = to.Raw(sql, reportId).Exec(); err != nil {
+		return
+	}
+
+	return
+}
+
+// UpdateReport 更新报告
+func (reportInfo *Report) UpdateReport(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(reportInfo, cols...)
+
+	return
+}
+
+// 晨周报详情
+type ReportDetailView struct {
+	*ReportDetail
+	ChapterList []*ReportChapter
+}
+
+func GetUnPublishDayReport(startTime time.Time, endTime time.Time) (item *Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT
+				*
+			FROM
+				report AS a
+			WHERE
+				a.has_chapter = 1
+			AND a.chapter_type = "day"
+			AND a.state = 1
+			AND (
+				a.create_time BETWEEN ? AND ?
+			)
+			ORDER BY
+				a.create_time DESC
+			LIMIT 1 `
+	err = o.Raw(sql, startTime, endTime).QueryRow(&item)
+	return
+}
+
+type ElasticReportDetail struct {
+	ReportId           int    `description:"报告ID"`
+	ReportChapterId    int    `description:"报告章节ID"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	BodyContent        string `description:"内容"`
+	PublishTime        string `description:"发布时间"`
+	PublishState       int    `description:"发布状态 1-未发布 2-已发布"`
+	Author             string `description:"作者"`
+	ClassifyIdFirst    int    `description:"一级分类ID"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类ID"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Categories         string `description:"关联的品种名称(包括品种别名)"`
+	StageStr           string `description:"报告期数"`
+}
+
+// GetLastPublishedDayWeekReport 获取上一篇已发布的晨周报
+func GetLastPublishDayWeekReport(chapterType string) (item *Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report WHERE has_chapter = 1 AND chapter_type = ? AND state = 2 ORDER BY publish_time DESC LIMIT 1 `
+	err = o.Raw(sql, chapterType).QueryRow(&item)
+
+	return
+}
+
+// GetNewReportExist
+func GetNewReportExist(oldReportId int) (item *Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT id FROM report WHERE old_report_id = ? LIMIT 1 `
+	err = o.Raw(sql, oldReportId).QueryRow(&item)
+
+	return
+}
+
+// PublishReportAndChapter 发布报告及章节
+func PublishReportAndChapter(reportInfo *Report, publishIds string, unPublishIds string, isPublishReport bool, cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	// 更新报告
+	if isPublishReport {
+		if _, err = to.Update(reportInfo, cols...); err != nil {
+			return
+		}
+	}
+	// 发布章节
+	if publishIds != "" {
+		sql := ` UPDATE report_chapter SET publish_state = 2, publish_time = ? WHERE report_id = ? AND report_chapter_id IN (` + publishIds + `) `
+		_, err = to.Raw(sql, reportInfo.PublishTime, reportInfo.Id).Exec()
+	}
+	if unPublishIds != "" {
+		sql := ` UPDATE report_chapter SET publish_state = 1, publish_time = NULL, is_edit = 0 WHERE report_id = ? AND report_chapter_id IN (` + unPublishIds + `) `
+		_, err = to.Raw(sql, reportInfo.Id).Exec()
+	}
+	return
+}
+
+func GetSyncEmptyVideoReport() (list []*Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT id FROM report WHERE old_report_id > 0 AND state = 2 AND chapter_type = "" AND (video_size = "" OR video_play_seconds = "")
+UNION ALL
+SELECT DISTINCT report_id FROM report_chapter WHERE publish_state = 2 AND (video_size = "" OR video_play_seconds = "") `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// 发布报告
+func PublishReportById(reportId int, publishTime time.Time) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET state = 2, publish_time = ?, modify_time = NOW() WHERE id = ? `
+	_, err = o.Raw(sql, publishTime, reportId).Exec()
+	return
+}
+
+// GetCommentReportByReportId 查询有留言的报告列表
+func GetCommentReportByReportId(condition string, pars []interface{}, startSize, pageSize int) (list []*Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT
+	id,
+	create_time,
+	title,
+	classify_name_first,
+	classify_id_first,
+	classify_name_second,
+	classify_id_second,
+	state,
+IF
+	( publish_time, publish_time, create_time ) AS publish_time 
+FROM
+	report 
+WHERE
+	1=1
+  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY  publish_time DESC , title ASC LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&list)
+	return
+}
+
+// GetCommentReportByReportIdOrderBy 查询有留言的报告列表(指定排序)
+func GetCommentReportByReportIdOrderBy(condition string, pars []interface{}, startSize, pageSize int, orderBy string) (list []*Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT
+	id,
+	create_time,
+	title,
+	classify_name_first,
+	classify_id_first,
+	classify_name_second,
+	classify_id_second,
+	state,
+IF
+	( publish_time, publish_time, create_time ) AS publish_time 
+FROM
+	report 
+WHERE
+	1=1
+  `
+	if condition != "" {
+		sql += condition
+	}
+	if orderBy == `` {
+		sql += ` ORDER BY  publish_time DESC , title ASC `
+	} else {
+		sql += orderBy
+	}
+	sql += ` LIMIT ?,? `
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&list)
+	return
+}
+
+// GetCommentReportTotalByReportId 查询有留言的报告列表总数
+func GetCommentReportTotalByReportId(condition string, pars []interface{}) (total int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT count(*)
+        FROM report WHERE 1=1`
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&total)
+	return
+}
+
+// 点赞相关的报告列表
+type LikeReportItem struct {
+	ReportId              int       `description:"报告Id"`
+	ReportChapterId       int       `description:"报告章节Id"`
+	ClassifyIdFirst       int       `description:"一级分类id"`
+	ClassifyNameFirst     string    `description:"一级分类名称"`
+	ClassifyIdSecond      int       `description:"二级分类id"`
+	ClassifyNameSecond    string    `description:"二级分类名称"`
+	ReportChapterTypeId   int       `description:"章节类型"`
+	ReportChapterTypeName string    `description:"品种名称"`
+	PublishTime           time.Time `description:"发布时间" `
+	Title                 string    `description:"标题"`
+}
+
+// GetLikeReportByReportIdReportChapterId 获取有被点赞的报告列表
+func GetLikeReportByReportIdReportChapterId(reportIds string, chapterIds string, orderStr string, startSize, pageSize int) (list []*LikeReportItem, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `( SELECT
+id AS report_id,
+0 AS report_chapter_id,
+classify_id_first,
+classify_id_second,
+classify_name_first,
+classify_name_second,
+0 as report_chapter_type_id,
+"" as report_chapter_type_name,
+publish_time,
+title
+FROM
+	report
+WHERE
+	classify_name_first != "晨报" 
+	AND classify_name_first != "周报"
+	AND id in (` + reportIds + `)
+	)
+UNION
+	
+( SELECT
+report_id,
+report_chapter_id,
+classify_id_first,
+0 as classify_id_second,
+classify_name_first,
+null as classify_name_second,
+type_id as report_chapter_type_id,
+type_name as report_chapter_type_name,
+publish_time,
+title
+FROM
+	report_chapter
+WHERE
+	 report_chapter_id in (` + chapterIds + `)
+	)`
+	if orderStr != "" {
+		sql += ` ORDER BY FIELD(CONCAT(report_id, "-",report_chapter_id),` + orderStr + `)`
+	} else {
+		sql += ` ORDER BY  publish_time DESC, report_id Desc`
+	}
+	sql += ` LIMIT ?,?`
+	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&list)
+	return
+}
+
+// GetLikeReportTotalByReportIdReportChapterId 获取有被点赞的报告列表总数
+func GetLikeReportTotalByReportIdReportChapterId(reportIds string, chapterIds string) (total int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select count(*) from (( SELECT
+id AS report_id,
+0 AS report_chapter_id,
+classify_id_first,
+classify_id_second,
+classify_name_first,
+classify_name_second,
+0 as report_chapter_type_id,
+publish_time,
+title
+FROM
+	report
+WHERE
+	classify_name_first != "晨报" 
+	AND classify_name_first != "周报"
+	AND id in (` + reportIds + `)
+	)
+UNION
+	
+( SELECT
+report_id,
+report_chapter_id,
+classify_id_first,
+0 as classify_id_second,
+classify_name_first,
+null as classify_name_second,
+type_id as report_chapter_type_id,
+publish_time,
+title
+FROM
+	report_chapter
+WHERE
+report_chapter_id in (` + chapterIds + `)
+	)) r`
+	err = o.Raw(sql).QueryRow(&total)
+	return
+}
+
+// GetPageReportList 分页获取报告列表
+func GetPageReportList(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE 1=1 `
+	sql += condition
+	sql += ` ORDER BY modify_time DESC`
+	totalSql := `SELECT COUNT(1) total FROM (` + sql + `) z `
+	err = o.Raw(totalSql, pars).QueryRow(&total)
+	if err != nil {
+		return
+	}
+	sql += ` LIMIT ?,? `
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// SunCodeReq 获取太阳码请求体
+type SunCodeReq struct {
+	CodePage  string `json:"CodePage" description:"太阳码page"`
+	CodeScene string `json:"CodeScene" description:"太阳码scene"`
+}
+
+// YbPcSuncode 活动海报表
+type YbPcSuncode struct {
+	SuncodeID  uint32    `orm:"column(suncode_id);pk" `
+	Scene      string    `gorm:"column:scene;type:varchar(255);not null;default:0" json:"scene"` // 微信scene
+	SceneMd5   string    `gorm:"column:scene_md5;type:varchar(255);not null" json:"sceneMd5"`
+	CodePage   string    `gorm:"column:code_page;type:varchar(255);not null;default:''" json:"codePage"`     // 路径
+	SuncodeURL string    `gorm:"column:suncode_url;type:varchar(255);not null;default:''" json:"suncodeUrl"` // 太阳码储存地址
+	CreateTime time.Time `gorm:"column:create_time;type:timestamp;default:CURRENT_TIMESTAMP" json:"createTime"`
+}
+
+// GetYbPcSunCode 获取太阳码
+func GetYbPcSunCode(scene, page string) (item *YbPcSuncode, err error) {
+	o := orm.NewOrmUsingDB("default")
+	sql := `SELECT * FROM yb_pc_suncode WHERE scene = ? AND code_page = ? `
+	err = o.Raw(sql, scene, page).QueryRow(&item)
+	return
+}
+
+func AddYbPcSunCode(item *YbPcSuncode) (err error) {
+	o := orm.NewOrmUsingDB("default")
+	_, err = o.Insert(item)
+	return
+}
+
+// YbSuncodePars 小程序太阳码scene参数
+type YbSuncodePars struct {
+	ID         uint32    `orm:"column(id);pk" `
+	Scene      string    `gorm:"column:scene;type:varchar(255);not null;default:''" json:"scene"`        // scene参数
+	SceneKey   string    `gorm:"column:scene_key;type:varchar(32);not null;default:''" json:"scene_key"` // MD5值
+	CreateTime time.Time `gorm:"column:create_time;type:datetime;default:CURRENT_TIMESTAMP" json:"createTime"`
+}
+
+func AddYbSuncodePars(item *YbSuncodePars) (err error) {
+	o := orm.NewOrmUsingDB("default")
+	_, err = o.Insert(item)
+	return
+}
+
+// UpdateReportSecondClassifyNameByClassifyId 更新报告分类名称字段
+func UpdateReportSecondClassifyNameByClassifyId(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE report SET classify_name_second = ? WHERE classify_id_second = ? "
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
+// UpdateReportFirstClassifyNameByClassifyId 更新报告分类一级名称字段
+func UpdateReportFirstClassifyNameByClassifyId(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE report SET classify_name_first = ? WHERE classify_id_first = ? "
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
+// UpdateReportSecondClassifyFirstNameByClassifyId 更新报告二级分类的一级分类名称和id
+func UpdateReportSecondClassifyFirstNameByClassifyId(classifyId, newClassifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE report SET classify_name_first = ?, classify_id_first = ? WHERE classify_id_second = ? "
+	_, err = o.Raw(sql, classifyName, newClassifyId, classifyId).Exec()
+	return
+}
+
+// GetEmptyContentSubPPTReport 获取前两章为空的PPT报告
+func GetEmptyContentSubPPTReport() (list []*Report, err error) {
+	sql := `SELECT
+				r.id,
+				r.content,
+				r.content_sub
+			FROM
+				report AS r
+			JOIN ppt_v2 AS p ON r.id = p.report_id
+			WHERE
+				p.report_id > 0 AND r.content_sub = ""`
+	_, err = orm.NewOrmUsingDB("rddp").Raw(sql).QueryRows(&list)
+	return
+}
+
+// ModifyReportAuthor 更改报告作者
+func ModifyReportAuthor(condition string, pars []interface{}, authorName string) (count int, err error) {
+	//产品权限
+	oRddp := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report set author = ? WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	err = oRddp.Raw(sql, authorName, pars).QueryRow(&count)
+	return
+}
+
+func UpdateReportPublishTime(reportId int, videoNameDate string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql1 := ` UPDATE report SET publish_time = NOW() WHERE id = ?  `
+	_, err = o.Raw(sql1, reportId).Exec()
+	if err != nil {
+		return
+	}
+	//修改音频标题
+	sql2 := ` UPDATE report SET video_name=CONCAT(SUBSTRING_INDEX(video_name,"(",1),"` + videoNameDate + `") WHERE id = ? and (video_name !="" and video_name is not null)`
+	_, err = o.Raw(sql2, reportId).Exec()
+	return
+}
+
+func UpdateReportChapterPublishTime(reportId int, videoNameDate string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql1 := ` UPDATE report_chapter SET publish_time = NOW() WHERE report_id = ? `
+	_, err = o.Raw(sql1, reportId).Exec()
+	if err != nil {
+		return
+	}
+	//修改音频标题
+	sql2 := ` UPDATE report_chapter SET video_name=CONCAT(SUBSTRING_INDEX(video_name,"(",1),"` + videoNameDate + `") WHERE report_id = ? and (video_name !="" and video_name is not null)`
+	_, err = o.Raw(sql2, reportId).Exec()
+	return
+}
+
+// MarkEditReport 标记编辑英文研报的请求数据
+type MarkEditReport struct {
+	ReportId int `description:"研报id"`
+	Status   int `description:"标记状态,1:编辑中,2:编辑完成"`
+}
+
+type MarkReportResp struct {
+	Status int    `description:"状态:0:无人编辑, 1:当前有人在编辑"`
+	Msg    string `description:"提示信息"`
+	Editor string `description:"编辑者姓名"`
+}
+
+type MarkReportItem struct {
+	AdminId                 int    `description:"编辑者ID"`
+	Editor                  string `description:"编辑者姓名"`
+	ReportClassifyNameFirst string
+}
+
+// GetReportByCondition 获取报告
+func GetReportByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, isPage bool, startSize, pageSize int) (items []*Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := `*`
+	if len(fieldArr) > 0 {
+		fields = strings.Join(fieldArr, ",")
+	}
+	sql := `SELECT ` + fields + ` FROM report WHERE 1=1 `
+	sql += condition
+	order := ` ORDER BY modify_time DESC`
+	if orderRule != `` {
+		order = orderRule
+	}
+	sql += order
+	if isPage {
+		sql += ` LIMIT ?,?`
+		_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	} else {
+		_, err = o.Raw(sql, pars).QueryRows(&items)
+	}
+	return
+}
+
+// ModifyReportMsgIsSendV2 更新报告消息状态
+func ModifyReportMsgIsSendV2(reportId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET msg_is_send = 1, ths_msg_is_send = 1, msg_send_time = NOW() WHERE id = ? LIMIT 1`
+	_, err = o.Raw(sql, reportId).Exec()
+	return
+}

+ 109 - 0
models/report_author.go

@@ -0,0 +1,109 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+type ReportAuthor struct {
+	Id           int       `orm:"column(id)" description:"报告作者ID"`
+	ReportAuthor string    `description:"报告作者名称"`
+	AuthorType   int       `description:"类型,1:中文;2:英文"`
+	Enable       int       `description:"是否启用,0:禁用,1:启用"`
+	IsDelete     int       `description:"是否删除,0:未删除,1:已删除"`
+	CreateTime   time.Time `description:"创建时间"`
+	ModifyTime   time.Time `description:"更新时间"`
+}
+
+// GetReportAuthorList 获取报告作者列表
+func GetReportAuthorList(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*ReportAuthor, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	baseSql := `  report_author WHERE is_delete=0 `
+	if condition != "" {
+		baseSql += condition
+	}
+
+	totalSql := ` SELECT count(1) total FROM ` + baseSql
+	err = o.Raw(totalSql, pars).QueryRow(&total)
+	if err != nil {
+		return
+	}
+
+	sql := ` SELECT * FROM ` + baseSql + ` ORDER BY id desc LIMIT ?,? `
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetReportAuthorCount 获取报告作者列表数
+func GetReportAuthorCount(condition string, pars []interface{}) (total int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+
+	totalSql := ` SELECT count(1) total FROM report_author WHERE 1=1 `
+	if condition != "" {
+		totalSql += condition
+	}
+	err = o.Raw(totalSql, pars).QueryRow(&total)
+	return
+}
+
+type ReportAuthorResp struct {
+	List   []*ReportAuthor
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+// AddReportAuthorReq 新增报告作者请求体
+type AddReportAuthorReq struct {
+	Id         int    `description:"作者id"`
+	AuthorType int    `description:"类型,1:中文;2:英文"`
+	Author     string `description:"报告作者名称"`
+}
+
+// GetReportAuthorById 根据作者id获取数据
+func GetReportAuthorById(authorId int) (item *ReportAuthor, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_author WHERE is_delete=0 AND id = ? `
+	err = o.Raw(sql, authorId).QueryRow(&item)
+	return
+}
+
+// GetReportAuthorByAuthor 根据作者名称获取数据
+func GetReportAuthorByAuthor(title string, authorType int) (item *ReportAuthor, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_author WHERE is_delete=0 AND report_author=? AND author_type = ? `
+	err = o.Raw(sql, title, authorType).QueryRow(&item)
+	return
+}
+
+// GetReportAuthorByAuthorAndId 根据作者名称和作者id获取数据
+func GetReportAuthorByAuthorAndId(title string, authorType, authorId int) (item *ReportAuthor, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_author WHERE is_delete=0 AND report_author=? AND author_type = ? AND id != ? `
+	err = o.Raw(sql, title, authorType, authorId).QueryRow(&item)
+	return
+}
+
+// AddReportAuthor 新增作者
+func AddReportAuthor(item *ReportAuthor) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	lastId, err = o.Insert(item)
+	return
+}
+
+// Update 更新作者基础信息
+func (item *ReportAuthor) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(item, cols...)
+	return
+}
+
+// EnableReportAuthorReq 启用/禁用报告作者请求体
+type EnableReportAuthorReq struct {
+	Id         int `description:"作者id"`
+	EnableType int `description:"是否启用,0:禁用,1:启用"`
+}
+
+// DeleteReportAuthorReq 删除报告作者请求体
+type DeleteReportAuthorReq struct {
+	Id int `description:"作者id"`
+}

+ 304 - 0
models/report_chapter.go

@@ -0,0 +1,304 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ReportChapter 报告章节
+type ReportChapter struct {
+	ReportChapterId   int       `orm:"column(report_chapter_id);pk" description:"报告章节ID"`
+	ReportId          int       `description:"报告ID"`
+	ReportType        string    `description:"报告类型 day-晨报 week-周报"`
+	ClassifyIdFirst   int       `description:"一级分类id"`
+	ClassifyNameFirst string    `description:"一级分类名称"`
+	TypeId            int       `description:"品种ID"`
+	TypeName          string    `description:"品种名称"`
+	Title             string    `description:"标题"`
+	Abstract          string    `description:"摘要"`
+	AddType           int       `description:"新增方式:1:新增报告,2:继承报告"`
+	Author            string    `description:"作者"`
+	Content           string    `description:"内容"`
+	ContentSub        string    `description:"内容前两个章节"`
+	Stage             int       `description:"期数"`
+	Trend             string    `description:"趋势观点"`
+	Sort              int       `description:"排序: 数值越小越靠前"`
+	IsEdit            int       `description:"是否已编辑 0-待编辑 1-已编辑"`
+	PublishState      int       `description:"发布状态 1-待发布,2-已发布"`
+	PublishTime       time.Time `description:"发布时间"`
+	VideoUrl          string    `description:"音频文件URL"`
+	VideoName         string    `description:"音频文件名称"`
+	VideoPlaySeconds  string    `description:"音频播放时长"`
+	VideoSize         string    `description:"音频文件大小,单位M"`
+	VideoKind         int       `description:"音频生成方式:1,手动上传,2:自动生成"`
+	CreateTime        string    `description:"创建时间"`
+	ModifyTime        time.Time `description:"修改时间"`
+}
+
+type ReportChapterResp struct {
+	ReportChapterId  int    `description:"报告章节ID"`
+	ReportId         int    `description:"报告ID"`
+	ReportType       string `description:"报告类型 day-晨报 week-周报"`
+	TypeId           int    `description:"品种ID"`
+	TypeName         string `description:"品种名称"`
+	TypeEditImg      string `description:"后台编辑时的图片"`
+	Title            string `description:"标题"`
+	Abstract         string `description:"摘要"`
+	Author           string `description:"作者"`
+	Content          string `description:"内容"`
+	ContentSub       string `description:"内容前两个章节"`
+	Stage            int    `description:"期数"`
+	Trend            string `description:"趋势观点"`
+	Sort             int    `description:"排序: 数值越小越靠前"`
+	IsEdit           int    `description:"是否已编辑 0-待编辑 1-已编辑"`
+	PublishState     int    `description:"发布状态 1-待发布,2-已发布"`
+	VideoUrl         string `description:"音频文件URL"`
+	VideoName        string `description:"音频文件名称"`
+	VideoPlaySeconds string `description:"音频播放时长"`
+	VideoSize        string `description:"音频文件大小,单位M"`
+	VideoKind        int    `description:"音频生成方式:1,手动上传,2:自动生成"`
+	PublishTime      string `description:"发布时间"`
+	CreateTime       string `description:"创建时间"`
+	ModifyTime       string `description:"修改时间"`
+}
+
+// GetChapterListByReportId 根据ReportId获取章节列表
+func GetChapterListByReportId(reportId int) (list []*ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE report_id = ? ORDER BY sort ASC`
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}
+
+// GetPublishedChapterListByReportId 根据ReportId获取已发布章节列表
+func GetPublishedChapterListByReportId(reportId int) (list []*ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE report_id = ? AND publish_state = 2 ORDER BY sort ASC`
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}
+
+// EditReportChapterReq 编辑报告章节请求体
+type EditReportChapterReq struct {
+	ReportChapterId  int            `description:"报告章节ID"`
+	Title            string         `description:"标题"`
+	AddType          int            `description:"新增方式:1:新增报告,2:继承报告"`
+	Author           string         `description:"作者"`
+	Content          string         `description:"内容"`
+	TickerList       []EditTickList `description:"指标信息"`
+	CreateTime       string         `description:"发布时间"`
+	VideoUrl         string         `description:"音频文件URL"`
+	VideoName        string         `description:"音频文件名称"`
+	VideoPlaySeconds string         `description:"音频播放时长"`
+	VideoSize        string         `description:"音频文件大小,单位M"`
+}
+
+type EditTickList struct {
+	Label  string
+	Ticker string
+	Sort   int
+}
+
+// GetReportChapterInfoById 根据主键获取报告章节
+func GetReportChapterInfoById(reportChapterId int) (item *ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE report_chapter_id = ? `
+	err = o.Raw(sql, reportChapterId).QueryRow(&item)
+
+	return
+}
+
+// GetLastPublishedReportChapter 获取上一篇已发表的晨周报章节
+func GetLastPublishedReportChapter(typeId int, reportType string) (item *ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE publish_state = 2 AND type_id = ? AND report_type = ? ORDER BY report_chapter_id DESC limit 1 `
+	err = o.Raw(sql, typeId, reportType).QueryRow(&item)
+
+	return
+}
+
+// UpdateChapter 更新报表章节
+func (chapterInfo *ReportChapter) UpdateChapter(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(chapterInfo, cols...)
+
+	return
+}
+
+// EditChapterTrendTagReq 编辑章节趋势标签请求体
+type EditChapterTrendTagReq struct {
+	ReportChapterId int    `description:"章节ID"`
+	Trend           string `description:"趋势"`
+}
+
+// UpdateChapterAndTicker 更新章节及ticker
+func UpdateChapterAndTicker(chapterInfo *ReportChapter, updateCols []string, tickerList []*ReportChapterTicker) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 更新章节
+	if err = chapterInfo.UpdateChapter(updateCols); err != nil {
+		return
+	}
+	// 清空并新增章节ticker
+	if err = ClearReportChapterTicker(chapterInfo.ReportChapterId); err != nil {
+		return
+	}
+	tickerLen := len(tickerList)
+	if tickerLen > 0 {
+		for i := 0; i < tickerLen; i++ {
+			_, tmpErr := InsertChapterTicker(tickerList[i])
+			if tmpErr != nil {
+				return
+			}
+		}
+	}
+
+	return
+}
+
+// ReportChapterVideoList 报告章节音频列表
+type ReportChapterVideoList struct {
+	ReportId         int    `description:"报告ID"`
+	ReportChapterId  int    `description:"报告章节ID"`
+	VideoUrl         string `description:"音频文件URL"`
+	VideoName        string `description:"音频文件名称"`
+	VideoPlaySeconds string `description:"音频播放时长"`
+}
+
+// GetReportChapterVideoList 获取报告章节音频列表
+func GetReportChapterVideoList(reportId int) (list []*ReportChapterVideoList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT
+				report_id,
+				report_chapter_id,
+				video_url,
+				video_name,
+				video_play_seconds
+			FROM
+				report_chapter
+			WHERE
+				report_id = ? AND publish_state = 2 AND video_url != ""
+			ORDER BY
+				report_chapter_id ASC `
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}
+
+// GetReportChapterVideoListByReportIds 根据报告ID集合获取报告章节音频列表
+func GetReportChapterVideoListByReportIds(reportIds string) (list []*ReportChapterVideoList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT
+				report_id,
+				report_chapter_id,
+				video_url,
+				video_name,
+				video_play_seconds
+			FROM
+				report_chapter
+			WHERE
+				report_id IN (` + reportIds + `) AND publish_state = 2 AND video_url != ""
+			ORDER BY
+				report_chapter_id ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+
+	return
+}
+
+// GetReportChapterVideoListByChapterIds 根据章节ID集合获取报告章节音频列表
+func GetReportChapterVideoListByChapterIds(chapterIds string) (list []*ReportChapterVideoList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT
+				report_id,
+				report_chapter_id,
+				video_url,
+				video_name,
+				video_play_seconds
+			FROM
+				report_chapter
+			WHERE
+				report_chapter_id IN (` + chapterIds + `) AND publish_state = 2
+			ORDER BY
+				report_chapter_id ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+
+	return
+}
+
+// PublishReportChapterReq 发布报告章节请求体
+type PublishReportChapterReq struct {
+	ReportChapterId  int            `description:"报告章节ID"`
+	Title            string         `description:"标题"`
+	AddType          int            `description:"新增方式:1:新增报告,2:继承报告"`
+	Author           string         `description:"作者"`
+	Content          string         `description:"内容"`
+	TickerList       []EditTickList `description:"指标信息"`
+	CreateTime       string         `description:"发布时间"`
+	PublishReport    int            `description:"是否同时发布报告"`
+	VideoUrl         string         `description:"音频文件URL"`
+	VideoName        string         `description:"音频文件名称"`
+	VideoPlaySeconds string         `description:"音频播放时长"`
+	VideoSize        string         `description:"音频文件大小,单位M"`
+}
+
+// CountPublishedChapterNum 获取报告已发布的章节数
+func CountPublishedChapterNum(reportId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT COUNT(1) AS ct FROM report_chapter WHERE report_id = ? AND publish_state = 2 `
+	err = o.Raw(sql, reportId).QueryRow(&count)
+
+	return
+}
+
+// GetChapterListByReportId 根据ReportId获取章节列表
+func GetChapterListByChapterIds(chapterIds string) (list []*ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE report_chapter_id IN (` + chapterIds + `) ORDER BY sort ASC`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// GetChapterSimpleListByChapterIds 根据章节ID获取章节列表
+func GetChapterSimpleListByChapterIds(chapterIds string) (list []*ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT report_id, report_chapter_id, title, type_name, create_time, IF(publish_time,publish_time,create_time) as publish_time FROM report_chapter WHERE report_chapter_id IN (` + chapterIds + `) ORDER BY publish_time desc, sort ASC`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// GetChapterSimpleListByReportIds 根据ReportId获取章节列表
+func GetChapterSimpleListByReportIds(reportIds string) (list []*ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT report_id, report_chapter_id, title, type_name, create_time, IF(publish_time,publish_time,create_time) as publish_time FROM report_chapter WHERE publish_state = 2 and report_id IN (` + reportIds + `) ORDER BY publish_time desc, sort ASC`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// UpdateReportChapterTypeNameByTypeId 更新章节类型名称
+func UpdateReportChapterTypeNameByTypeId(typeId int, typeName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report_chapter SET type_name = ? WHERE type_id = ?`
+	_, err = o.Raw(sql, typeName, typeId).Exec()
+	return
+}
+
+// CountReportChapterByTypeId 通过章节类型ID获取章节数
+func CountReportChapterByTypeId(typeId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT COUNT(1) AS ct FROM report_chapter WHERE type_id = ?`
+	err = o.Raw(sql, typeId).QueryRow(&count)
+	return
+}

+ 73 - 0
models/report_chapter_ticker.go

@@ -0,0 +1,73 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type ReportChapterTicker struct {
+	Id              int       `orm:"column(id);pk" description:"主键ID"`
+	ReportChapterId int       `description:"报告章节ID"`
+	Sort            int       `description:"排序"`
+	Ticker          string    `description:"ticker"`
+	CreateTime      time.Time `description:"创建时间"`
+	UpdateTime      time.Time `description:"更新时间"`
+}
+
+// 新增章节ticker
+func InsertChapterTicker(tickerInfo *ReportChapterTicker) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	lastId, err = o.Insert(tickerInfo)
+	return
+}
+
+// GetTickerListByReportChapterId 根据章节ID获取ticker列表
+func GetTickerListByReportChapterId(reportChapterId int) (list []*ReportChapterTicker, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_ticker WHERE report_chapter_id = ? ORDER BY sort ASC `
+	_, err = o.Raw(sql, reportChapterId).QueryRows(&list)
+
+	return
+}
+
+// ClearReportChapterTicker 清空章节ticker
+func ClearReportChapterTicker(reportChapterId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` DELETE FROM report_chapter_ticker WHERE report_chapter_id = ? `
+	_, err = o.Raw(sql, reportChapterId).Exec()
+
+	return
+}
+
+// DailyBaseColumn 基础列
+type DailyBaseColumn struct {
+	BaseColumnId          int       `orm:"column(base_column_id)" description:"主键ID"`
+	TableName             string    `description:"表名"`
+	BaseColumnName        string    `description:"列名称"`
+	BaseColumnChapterType string    `description:"列类型"`
+	BaseColumnTicker      string    `description:"指标"`
+	Enabled               int       `description:"状态"`
+	CreatedTime           time.Time `description:"创建时间"`
+	LastUpdatedTime       time.Time `description:"更新时间"`
+	Freq                  string    `description:"频率 D-日度 W-周度 M-月度"`
+	Catalog               string    `description:"分类名称"`
+	ReportChapterTypeId   int       `description:"分类ID"`
+	Selected              int       `description:"选中状态 0-未选中 1-已选中"`
+}
+
+// GetDailyBaseColumnList 获取基础列列表
+func GetDailyBaseColumnList(keyword string, typeId int) (list []*DailyBaseColumn, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM daily_base_column WHERE 1 = 1 `
+	pars := make([]interface{}, 0)
+	if keyword != "" {
+		keyword = "%" + keyword + "%"
+		pars = append(pars, keyword)
+		sql += ` AND base_column_name like ? `
+	}
+	pars = append(pars, typeId)
+	sql += ` AND report_chapter_type_id = ? `
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+
+	return
+}

+ 385 - 0
models/report_chapter_type.go

@@ -0,0 +1,385 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"time"
+)
+
+type ReportChapterType struct {
+	ReportChapterTypeId    int       `orm:"column(report_chapter_type_id);pk" description:"报告章节类型id"`
+	ReportChapterTypeKey   string    `description:"章节key"`
+	ReportChapterTypeThumb string    `description:"H5展示的图片"`
+	BannerUrl              string    `description:"banner显示图片"`
+	ReportChapterTypeName  string    `description:"报告章节类型名称"`
+	Sort                   int       `description:"排序字段"`
+	Enabled                int       `description:"启禁用状态"`
+	CreatedTime            time.Time `description:"创建时间"`
+	LastUpdatedTime        time.Time `description:"更新时间"`
+	ResearchType           string    `description:"研报类型"`
+	SelectedImage          string    `description:"选中时的图片"`
+	UnselectedImage        string    `description:"没选中时的图片"`
+	PcSelectedImage        string    `description:"PC-选中的图片"`
+	PcUnselectedImage      string    `description:"PC-未选中的图片"`
+	EditImgUrl             string    `description:"管理后台编辑时选用的图"`
+	TickerTitle            string    `description:"指标列的标题"`
+	IsShow                 int       `description:"是否显示(研报小程序端根据此字段判断)"`
+	PauseStartTime         string    `description:"暂停开始日期"`
+	PauseEndTime           string    `description:"暂停结束日期"`
+	IsSet                  int       `description:"是否设置:0为设置,1已设置"`
+	YbIconUrl              string    `description:"研报小程序icon"`
+	YbBottomIcon           string    `description:"研报小程序详情底部icon"`
+}
+
+func (item *ReportChapterType) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(item)
+	if err != nil {
+		return
+	}
+	item.ReportChapterTypeId = int(id)
+	return
+}
+
+func (item *ReportChapterType) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(item, cols...)
+	return
+}
+
+// DeleteReportChapterType 删除章节类型及相关权限
+func DeleteReportChapterType(typeId int, reportType string) (err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 删除章节类型
+	sql := `DELETE FROM report_chapter_type WHERE report_chapter_type_id = ? LIMIT 1`
+	_, err = to.Raw(sql, typeId).Exec()
+
+	// 删除章节类型权限
+	sql = `DELETE FROM report_chapter_type_permission WHERE report_chapter_type_id = ?`
+	_, err = to.Raw(sql, typeId).Exec()
+
+	// 周报-删除实际权限
+	if reportType == utils.REPORT_TYPE_WEEK {
+		sql = `DELETE FROM chart_permission_chapter_mapping WHERE report_chapter_type_id = ? AND research_type = ?`
+		_, err = to.Raw(sql, typeId, reportType).Exec()
+	}
+	return
+}
+
+// GetReportChapterTypeById 获取章节类型
+func GetReportChapterTypeById(reportChapterTypeId int) (item *ReportChapterType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type WHERE report_chapter_type_id = ? `
+	err = o.Raw(sql, reportChapterTypeId).QueryRow(&item)
+	return
+}
+
+// GetReportChapterTypeList 获取章节类型列表
+func GetReportChapterTypeList() (list []*ReportChapterType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type WHERE enabled = 1 `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// GetReportChapterTypeListByResearchType 通过报告类型获取章节类型列表
+func GetReportChapterTypeListByResearchType(researchType string) (list []*ReportChapterType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type WHERE research_type = ? AND enabled = 1`
+	_, err = o.Raw(sql, researchType).QueryRows(&list)
+	return
+}
+
+// GetAllReportChapterTypeListByResearchType 通过报告类型获取章节类型列表
+func GetAllReportChapterTypeListByResearchType(researchType string) (list []*ReportChapterType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type WHERE research_type = ?`
+	_, err = o.Raw(sql, researchType).QueryRows(&list)
+	return
+}
+
+// GetAllReportChapterTypeList 通过传入的条件获取所有的章节类型列表
+func GetAllReportChapterTypeList(condition string, pars []interface{}) (list []*ReportChapterType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type WHERE 1=1 `
+	sql += condition
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}
+
+// GetEnableReportChapterTypeList 获取未暂停的章节类型列表
+func GetEnableReportChapterTypeList(researchType string) (list []*ReportChapterType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT
+				*
+			FROM
+				report_chapter_type
+			WHERE
+				enabled = 1
+			AND research_type = ?
+			AND (
+				(
+					ISNULL(pause_start_time) AND ISNULL(pause_end_time)
+				)
+				OR (
+					NOW() NOT BETWEEN pause_start_time AND pause_end_time
+				)
+			) `
+	_, err = o.Raw(sql, researchType).QueryRows(&list)
+	return
+}
+
+// 晨报周报暂停时间
+type DayWeekReportPauseTime struct {
+	ResearchType   string `description:"报告类型 day; week;"`
+	PauseStartTime string `description:"暂停开始时间"`
+	PauseEndTime   string `description:"暂停结束时间"`
+}
+
+// GetDayWeekReportPauseTimeList 获取晨报周报暂停时间
+func GetDayWeekReportPauseTimeList() (list []*DayWeekReportPauseTime, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT research_type, MAX(a.pause_start_time) AS pause_start_time, MAX(a.pause_end_time) AS pause_end_time FROM report_chapter_type AS a WHERE a.is_set = 1 GROUP BY a.research_type`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// SetDayWeekReportUpdateRuleReq 设置章节类型的暂停时间请求体
+type SetDayWeekReportUpdateRuleReq struct {
+	ResearchType string                    `description:"报告类型 day; week;"`
+	List         []DayWeekReportUpdateRule `description:"暂停规则"`
+}
+
+type DayWeekReportUpdateRule struct {
+	ReportChapterTypeId int    `description:"章节类型ID"`
+	PauseStartTime      string `description:"暂停开始时间"`
+	PauseEndTime        string `description:"暂停结束时间"`
+}
+
+// SetDayWeekReportEnableUpdateRuleReq 设置章节类型的永久暂停请求体
+type SetDayWeekReportEnableUpdateRuleReq struct {
+	DayReportChapterTypeId  string `description:"章节类型ID 英文逗号拼接"`
+	WeekReportChapterTypeId string `description:"章节类型ID 英文逗号拼接"`
+}
+
+// ResetDayWeekReportUpdateRule 重置章节类型的暂停时间
+func ResetDayWeekReportUpdateRule(researchType string) (err error) {
+	o := orm.NewOrm()
+	sql := ` UPDATE report_chapter_type SET pause_start_time = null, pause_end_time = null, is_set = 0 WHERE research_type = ?`
+	_, err = o.Raw(sql, researchType).Exec()
+	return
+}
+
+// SetDayWeekReportUpdateRule 设置章节类型的暂停时间
+func SetDayWeekReportUpdateRule(researchType string, list []DayWeekReportUpdateRule) (err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 先将所有品种的状态变更为启用
+	sql := ` UPDATE report_chapter_type SET pause_start_time = null, pause_end_time = null, is_set = 0 WHERE research_type = ?`
+	_, err = to.Raw(sql, researchType).Exec()
+	if err != nil {
+		return
+	}
+	for _, v := range list {
+		// 时间异常的话,给过滤
+		if v.PauseStartTime == `` || v.PauseEndTime == `` {
+			continue
+		}
+		tmpSql := ` UPDATE report_chapter_type SET pause_start_time = ?, pause_end_time = ?, is_set = 1 WHERE research_type = ? AND report_chapter_type_id = ? `
+		_, err = to.Raw(tmpSql, v.PauseStartTime, v.PauseEndTime, researchType, v.ReportChapterTypeId).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// SetDayWeekReportEnableUpdateRule 设置章节类型的禁用状态
+func SetDayWeekReportEnableUpdateRule(dayReportChapterTypeIdList, weekReportChapterTypeIdList []string) (err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	{
+		researchType := `day`
+		// 先将所有品种的状态变更为启用
+		sql := ` UPDATE report_chapter_type SET enabled = 1 WHERE research_type = ?`
+		_, err = to.Raw(sql, researchType).Exec()
+		if err != nil {
+			return
+		}
+
+		// 然后将需要的品种的状态变更为禁用
+		num := len(dayReportChapterTypeIdList)
+		if num > 0 {
+			sql = ` UPDATE report_chapter_type SET pause_start_time = null, pause_end_time = null, is_set = 0 , enabled = 0 WHERE research_type = ? AND report_chapter_type_id IN (` + utils.GetOrmInReplace(num) + `) `
+			_, err = to.Raw(sql, researchType, dayReportChapterTypeIdList).Exec()
+		}
+	}
+
+	{
+		researchType := `week`
+		// 先将所有品种的状态变更为启用
+		sql := ` UPDATE report_chapter_type SET enabled = 1 WHERE research_type = ?`
+		_, err = to.Raw(sql, researchType).Exec()
+		if err != nil {
+			return
+		}
+
+		// 然后将需要的品种的状态变更为禁用
+		num := len(weekReportChapterTypeIdList)
+		if num > 0 {
+			sql = ` UPDATE report_chapter_type SET pause_start_time = null, pause_end_time = null, is_set = 0 , enabled = 0 WHERE research_type = ? AND report_chapter_type_id IN (` + utils.GetOrmInReplace(num) + `) `
+			_, err = to.Raw(sql, researchType, weekReportChapterTypeIdList).Exec()
+		}
+	}
+	return
+}
+
+// StopUpdateReportChapterTypeResp 停止更新的报告分类列表
+type StopUpdateReportChapterTypeResp struct {
+	StopDay     []*ReportChapterType `description:"暂时停更晨报"`
+	StopWeek    []*ReportChapterType `description:"暂时停更周报"`
+	DisableDay  []*ReportChapterType `description:"永久停更晨报"`
+	DisableWeek []*ReportChapterType `description:"永久停更周报"`
+}
+
+// GetStopUpdateReportChapterTypeListByResearchType 获取暂停更新章节类型列表
+func GetStopUpdateReportChapterTypeListByResearchType() (list []*ReportChapterType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type WHERE is_set = 1 AND pause_end_time >= ? AND enabled = 1 `
+	_, err = o.Raw(sql, time.Now().Format(utils.FormatDate)).QueryRows(&list)
+	return
+}
+
+// GetDisableUpdateReportChapterTypeListByResearchType 获取停止更新的章节类型列表
+func GetDisableUpdateReportChapterTypeListByResearchType() (list []*ReportChapterType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type WHERE enabled = 0`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// UpdateReportChapterTypeResp 停止更新的报告分类列表
+type UpdateReportChapterTypeResp struct {
+	Day  []*ReportChapterType `description:"所有晨报品种"`
+	Week []*ReportChapterType `description:"所有周报品种"`
+}
+
+type ReportChapterTypePageListResp struct {
+	List   []*ReportChapterTypeListItem
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+// GetReportChapterTypeCount 获取章节类型总数
+func GetReportChapterTypeCount(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT COUNT(1) FROM report_chapter_type WHERE 1 = 1 `
+	sql += condition
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetReportChapterTypeList 获取章节类型列表
+func GetReportChapterTypePageList(condition string, pars []interface{}, startSize, pageSize int) (list []*ReportChapterType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type WHERE 1 = 1 `
+	sql += condition
+	sql += ` ORDER BY sort ASC, created_time DESC`
+	sql += ` LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&list)
+	return
+}
+
+// ReportChapterTypeListItem 章节类型列表信息
+type ReportChapterTypeListItem struct {
+	ReportChapterTypeId   int    `description:"报告章节类型id"`
+	ReportChapterTypeName string `description:"报告章节类型名称"`
+	Sort                  int    `description:"排序字段"`
+	CreatedTime           string `description:"创建时间"`
+	ResearchType          string `description:"研报类型"`
+	SelectedImage         string `description:"选中时的图片"`
+	UnselectedImage       string `description:"没选中时的图片"`
+	WordsImage            string `description:"带字的icon"`
+	EditImgUrl            string `description:"管理后台编辑时选用的图"`
+	IsShow                int    `description:"显示隐藏: 1-显示; 0-隐藏"`
+}
+
+// ReportChapterTypeAddReq 新增章节类型请求体
+type ReportChapterTypeAddReq struct {
+	ReportChapterTypeName string `description:"报告章节类型名称"`
+	Sort                  int    `description:"排序字段"`
+	ResearchType          string `description:"研报类型"`
+	SelectedImage         string `description:"选中时的icon"`
+	UnselectedImage       string `description:"未选中时的icon"`
+	WordsImage            string `description:"带字的icon"`
+	EditImgUrl            string `description:"管理后台编辑时选用的图"`
+	IsShow                int    `description:"显示隐藏: 1-显示; 0-隐藏"`
+}
+
+// ReportChapterTypeEditReq 编辑章节类型请求体
+type ReportChapterTypeEditReq struct {
+	ReportChapterTypeId   int    `description:"报告章节类型id"`
+	ReportChapterTypeName string `description:"报告章节类型名称"`
+	Sort                  int    `description:"排序字段"`
+	ResearchType          string `description:"研报类型"`
+	SelectedImage         string `description:"选中时的icon"`
+	UnselectedImage       string `description:"未选中时的icon"`
+	WordsImage            string `description:"带字的icon"`
+	EditImgUrl            string `description:"管理后台编辑时选用的图"`
+	IsShow                int    `description:"显示隐藏: 1-显示; 0-隐藏"`
+}
+
+// ReportChapterTypeDelReq 删除章节类型请求体
+type ReportChapterTypeDelReq struct {
+	ReportChapterTypeId int `description:"报告章节类型id"`
+}
+
+// ReportChapterTypeAuthSettingReq 章节类型权限配置请求体
+type ReportChapterTypeAuthSettingReq struct {
+	ReportChapterTypeId   int   `description:"章节类型ID"`
+	ChartPermissionIdList []int `description:"权限id数组"`
+}
+
+// GetReportChapterTypeByCondition 获取章节类型
+func GetReportChapterTypeByCondition(condition string, pars []interface{}) (item *ReportChapterType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type WHERE 1 = 1 `
+	sql += condition
+	sql += ` LIMIT 1`
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}

+ 82 - 0
models/report_chapter_type_permission.go

@@ -0,0 +1,82 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"time"
+)
+
+type ReportChapterTypePermission struct {
+	Id                    int       `orm:"column(id);pk" description:"主键ID"`
+	ReportChapterTypeId   int       `description:"报告章节类型ID"`
+	ReportChapterTypeName string    `description:"章节名称"`
+	ChartPermissionId     int       `description:"大分类ID"`
+	PermissionName        string    `description:"权限名称"`
+	ResearchType          string    `description:"研报类型"`
+	CreatedTime           time.Time `description:"创建时间"`
+}
+
+// AddChapterTypePermission 新增章节类型权限
+func AddChapterTypePermission(item *ReportChapterTypePermission) (lastId int64, err error) {
+	o := orm.NewOrm()
+	lastId, err = o.Insert(item)
+	return
+}
+
+// GetChapterTypePermissionByTypeIdAndResearchType 根据章节类型ID及研报类型获取章节类型权限列表
+func GetChapterTypePermissionByTypeIdAndResearchType(typeId int, researchType string) (list []*ReportChapterTypePermission, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type_permission WHERE report_chapter_type_id = ? AND research_type = ? ORDER BY chart_permission_id ASC `
+	_, err = o.Raw(sql, typeId, researchType).QueryRows(&list)
+	return
+}
+
+// SetReportChapterTypePermission 设置报告章节类型权限
+func SetReportChapterTypePermission(chapterTypeId int, researchType string, newPermissions []*ReportChapterTypePermission, newWeekPermissions []*ChartPermissionChapterMapping) (err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 删除原章节类型权限
+	sql := `DELETE FROM report_chapter_type_permission WHERE report_chapter_type_id = ? AND research_type = ?`
+	_, err = to.Raw(sql, chapterTypeId, researchType).Exec()
+	if err != nil {
+		return
+	}
+
+	// 新增章节类型权限
+	if len(newPermissions) > 0 {
+		_, err = to.InsertMulti(len(newPermissions), newPermissions)
+		if err != nil {
+			return
+		}
+	}
+
+	// 周报章节调整chart_permission_chapter_mapping表
+	if researchType == utils.REPORT_TYPE_WEEK {
+		// 删除原权限
+		sql = `DELETE FROM chart_permission_chapter_mapping WHERE report_chapter_type_id = ? AND research_type = ?`
+		_, err = to.Raw(sql, chapterTypeId, researchType).Exec()
+		if err != nil {
+			return
+		}
+
+		// 新增权限
+		if len(newWeekPermissions) > 0 {
+			_, err = to.InsertMulti(len(newWeekPermissions), newWeekPermissions)
+			if err != nil {
+				return
+			}
+		}
+	}
+	return
+}

+ 302 - 0
models/research_report.go

@@ -0,0 +1,302 @@
+package models
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"strconv"
+	"time"
+)
+
+// ResearchReport 研究报告表(晨报、周报等)结构体
+type ResearchReport struct {
+	ResearchReportId    int       `orm:"column(research_report_id);pk" description:"研究报告id"`
+	ResearchReportName  string    `description:"研究报告名称"`
+	ResearchReportTitle string    `description:"研究报告标题"`
+	ResearchReportImg   string    `description:"报告缩略图URL"`
+	ResearchReportDate  time.Time `description:"报告日期"`
+	Type                string    `description:"报告类型,枚举值:day 晨报  week 周报 twoweek双周报 month 月报;默认:day"`
+	Author              string    `description:"作者"`
+	ReportVariety       string    `description:"研究报告的品种,双周报和月报有标识"`
+	IsHasMenu           int8      `description:"报告是否含有目录"`
+	IsSendedMsg         int8      `description:"是否发送过模板消息"`
+	Periods             int       `description:"期数"`
+	Status              string    `description:"状态,draft:草稿,"`
+	Enabled             int8      `description:"报告状态"`
+	CreatedTime         string    `description:"创建时间"`
+	LastUpdatedTime     time.Time `description:"最近一次更新时间"`
+	Viewers             int       `description:"H5观看用户数"`
+}
+
+// GetResearchReportListByIds 根据报告id集合获取报告数据列表
+func GetResearchReportListByIds(researchReportIds string) (list []*ResearchReport, err error) {
+	if researchReportIds == "" {
+		return
+	}
+	o := orm.NewOrm()
+	sql := `select * from research_report where research_report_id in (` + researchReportIds + `)`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// Update 更新数据
+func (researchReport *ResearchReport) Update(updateCols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(researchReport, updateCols...)
+	return
+}
+
+type ResearchReportList struct {
+	ResearchReportId    int       `orm:"column(research_report_id);pk" description:"研究报告id"`
+	ResearchReportName  string    `description:"研究报告名称"`
+	ResearchReportTitle string    `description:"研究报告标题"`
+	ResearchReportImg   string    `description:"报告缩略图URL"`
+	ResearchReportDate  time.Time `description:"报告日期"`
+	Type                string    `description:"报告类型,枚举值:day 晨报  week 周报 twoweek双周报 month 月报;默认:day"`
+	Author              string    `description:"作者"`
+	ReportVariety       string    `description:"研究报告的品种,双周报和月报有标识"`
+	IsHasMenu           int8      `description:"报告是否含有目录"`
+	IsSendedMsg         int8      `description:"是否发送过模板消息"`
+	Periods             int       `description:"期数"`
+	Status              string    `description:"状态,draft:草稿,"`
+	Enabled             int8      `description:"报告状态"`
+	CreatedTime         string    `description:"创建时间"`
+	LastUpdatedTime     time.Time `description:"最近一次更新时间"`
+	Viewers             int       `description:"H5观看用户数"`
+	LinkUrl             string    `description:"报告阅读地址"`
+}
+
+// GetResearchReportList 获取报告列表
+func GetResearchReportList(condition string, pars []interface{}, startSize, pageSize int) (total int, list []*ResearchReportList, err error) {
+	o := orm.NewOrm()
+
+	sql := `select * from research_report where enabled = 1 `
+	sql += condition
+	sql += ` order by research_report_date desc,created_time desc `
+
+	totalSql := `select count(1) total from (` + sql + `) z `
+	err = o.Raw(totalSql, pars).QueryRow(&total)
+	if err != nil {
+		return
+	}
+	sql += ` LIMIT ?,? `
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&list)
+
+	return
+}
+
+// 获取今年报告
+func GetMigrateReportList() (list []*ResearchReport, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT
+				*
+			FROM
+				research_report AS a
+			WHERE
+				a.enabled = 1
+			AND a.status = "report"
+			AND a.research_report_date >= "2022-01-01"
+			ORDER BY a.research_report_date ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+
+	return
+}
+
+// ResearchReport 研究报告表(晨报、周报等)结构体
+type ResearchReportType struct {
+	ResearchReportTypeId    int       `orm:"column(research_report_type_id);pk" description:"报告章节ID"`
+	ResearchReportId        int       `description:"报告ID"`
+	TypeId                  int       `description:"分类ID"`
+	Edit                    int       `description:"是否编辑过"`
+	Trend                   string    `description:"趋势观点"`
+	ResearchReportTypeTitle string    `description:"报告标题"`
+	CreatedTime             string    `description:"创建时间"`
+	LastUpdatedTime         time.Time `description:"最近一次更新时间"`
+}
+
+type ResearchReportTypeContent struct {
+	ResearchReportTypeContentId int       `orm:"column(research_report_type_content_id);pk" description:"章节内容ID"`
+	ResearchReportTypeId        int       `description:"报告章节ID"`
+	Sort                        int       `description:"排序"`
+	ContentType                 string    `description:"内容分类类型"`
+	Content                     string    `description:"内容"`
+	ImgUrl                      string    `description:"图片路径"`
+	CreatedTime                 time.Time `description:"创建时间"`
+	LastUpdatedTime             time.Time `description:"最近一次更新时间"`
+}
+
+type ResearchReportTypeTicker struct {
+	ResearchReportTypeTickerId int       `orm:"column(research_report_type_ticker_id);pk" description:"章节tickerID"`
+	ResearchReportTypeId       int       `description:"报告章节ID"`
+	Sort                       int       `description:"排序"`
+	Ticker                     string    `description:"指标的ticker"`
+	CreatedTime                time.Time `description:"创建时间"`
+	LastUpdatedTime            time.Time `description:"最近一次更新时间"`
+}
+
+func GetResearchReportTypeList(researchReportId int) (list []*ResearchReportType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM research_report_type WHERE research_report_id = ? ORDER BY created_time ASC `
+	_, err = o.Raw(sql, researchReportId).QueryRows(&list)
+	return
+}
+
+func GetResearchReportTypeListByReportIds(reportIds string) (list []*ResearchReportType, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM research_report_type WHERE research_report_id IN (` + reportIds + `) ORDER BY created_time ASC,research_report_type_id ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+func GetResearchReportTypeContentList(researchReportTypeId int) (list []*ResearchReportTypeContent, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM research_report_type_content WHERE research_report_type_id = ? ORDER BY sort ASC `
+	_, err = o.Raw(sql, researchReportTypeId).QueryRows(&list)
+	return
+}
+
+func GetResearchReportTypeContentListByReportTypeIds(reportTypeIds string) (list []*ResearchReportTypeContent, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM research_report_type_content WHERE research_report_type_id IN (` + reportTypeIds + `) ORDER BY research_report_type_id ASC,sort ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+func GetResearchReportTypeTickerList(researchReportTypeId int) (list []*ResearchReportTypeTicker, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM research_report_type_ticker WHERE research_report_type_id = ? ORDER BY sort ASC `
+	_, err = o.Raw(sql, researchReportTypeId).QueryRows(&list)
+	return
+}
+
+func GetResearchReportTypeTickerListByReportTypeIds(reportTypeIds string) (list []*ResearchReportTypeTicker, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM research_report_type_ticker WHERE research_report_type_id IN (` + reportTypeIds + `) ORDER BY research_report_type_id ASC,sort ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+type CreateDayWeekReport struct {
+	Report      *Report
+	ChapterList []*CreateDayWeekReportChapter
+}
+
+type CreateDayWeekReportChapter struct {
+	Chapter    *ReportChapter
+	TickerList []*ReportChapterTicker
+}
+
+// 新增迁移晨周报
+func CreateMigrateNewDayWeekReport(newDayWeekReport *CreateDayWeekReport) (newReportId int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 新增报告
+	reportId, err := to.Insert(newDayWeekReport.Report)
+	if err != nil {
+		fmt.Println("InsertReportErr:" + err.Error())
+		return
+	}
+	// 新增章节
+	for _, chapter := range newDayWeekReport.ChapterList {
+		chapter.Chapter.ReportId = int(reportId)
+		lastChapterId, tmpErr := to.Insert(chapter.Chapter)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		// 新增晨报ticker
+		for _, ticker := range chapter.TickerList {
+			ticker.ReportChapterId = int(lastChapterId)
+			if _, tmpErr = to.Insert(ticker); tmpErr != nil {
+				err = tmpErr
+				return
+			}
+		}
+	}
+	// 修改报告code
+	reportCode := utils.MD5(strconv.Itoa(int(reportId)))
+	sql := `UPDATE report SET report_code = ? WHERE id = ? `
+	if _, err = to.Raw(sql, reportCode, reportId).Exec(); err != nil {
+		fmt.Println("UpdateReportCodeErr:" + err.Error())
+	}
+	newReportId = int(reportId)
+
+	return
+}
+
+// 新增迁移其他报告
+func CreateMigrateNewOtherReport(reportInfo *Report, mappingList []*ChartPermissionChapterMapping) (newReportId int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 新增报告
+	reportId, err := to.Insert(reportInfo)
+	if err != nil {
+		fmt.Println("InsertReportErr:" + err.Error())
+		return
+	}
+	// 修改报告code
+	reportCode := utils.MD5(strconv.Itoa(int(reportId)))
+	sql := `UPDATE report SET report_code = ? WHERE id = ? `
+	if _, err = to.Raw(sql, reportCode, reportId).Exec(); err != nil {
+		fmt.Println("UpdateReportCodeErr:" + err.Error())
+		return
+	}
+
+	// 新增权限
+	newReportId = int(reportId)
+	if len(mappingList) > 0 {
+		r := orm.NewOrm()
+		for _, mapping := range mappingList {
+			sql := ` INSERT INTO chart_permission_chapter_mapping (chart_permission_id, report_chapter_type_id,research_type) VALUES(?,?,?) `
+			if _, err = r.Raw(sql, mapping.ChartPermissionId, newReportId, "rddp").Exec(); err != nil {
+				fmt.Println("InsertChartPermissionErr:" + err.Error())
+				return
+			}
+		}
+	}
+
+	return
+}
+
+type ChartPermissionChapterMapping struct {
+	Id                  int    `orm:"column(id);pk"`
+	ChartPermissionId   int    `description:"权限ID"`
+	ReportChapterTypeId int    `description:"report_chapter_type表主键id或research_report表主键id或tactic表主键id"`
+	ResearchType        string `description:"报告类型 week;two_week;tactic;month;other;rddp; "`
+}
+
+func GetChapterPermissionMappingByCondition(reportChapterTypeId int, researchType string) (list []*ChartPermissionChapterMapping, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM chart_permission_chapter_mapping WHERE report_chapter_type_id = ? AND research_type = ? `
+	_, err = o.Raw(sql, reportChapterTypeId, researchType).QueryRows(&list)
+	return
+}
+
+func GetChapterPermissionMappingByResearchReportIds(researchReportIds string) (list []*ChartPermissionChapterMapping, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM chart_permission_chapter_mapping WHERE report_chapter_type_id IN (` + researchReportIds + `) AND research_type != "rddp" ORDER BY report_chapter_type_id ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}

+ 35 - 0
models/search_key_word.go

@@ -0,0 +1,35 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// SearchKeyWord 搜索关键词
+type SearchKeyWord struct {
+	SearchKeyWordId     int       `orm:"column(search_key_word_id)" description:"主键ID"`
+	KeyWord             string    `description:"关键词"`
+	From                string    `description:"来源,在什么地方筛选"`
+	CreatedTime         time.Time `description:"创建时间"`
+	LastUpdatedTime     time.Time `description:"更新时间"`
+	TeleconferenceImage string    `description:"电话会对应的类型图片"`
+	BannerImage         string    `description:"Banner图"`
+}
+
+// AddTrendTagKeyWord 新增趋势标签关键词
+func AddTrendTagKeyWord(trend string) (err error) {
+	o := orm.NewOrm()
+	sql := " REPLACE INTO search_key_word (`key_word`,`from`) values (?,'trend') "
+	_, err = o.Raw(sql, trend).Exec()
+
+	return
+}
+
+// GetKeyWordListByFrom 根据来源获取搜索关键词列表
+func GetKeyWordListByFrom(from string) (list []*SearchKeyWord, err error) {
+	o := orm.NewOrm()
+	sql := " SELECT * FROM search_key_word WHERE `from` = ? ORDER BY created_time ASC "
+	_, err = o.Raw(sql, from).QueryRows(&list)
+
+	return
+}

+ 454 - 0
models/user_view_history.go

@@ -0,0 +1,454 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"time"
+)
+
+type UserViewHistory struct {
+	ViewHistoryId        int       `orm:"column(id);pk"`
+	UserId               int       `description:"用户id"`
+	Mobile               string    `description:"手机号"`
+	Email                string    `description:"邮箱"`
+	RealName             string    `description:"用户实际姓名"`
+	CompanyName          string    `description:"公司名称"`
+	ViewTitle            string    `description:"访问标题"`
+	ViewPage             string    `description:"访问页面"`
+	ReportChapterModule  string    `description:"访问核心观点或者图文逻辑"`
+	CreatedTime          string    `description:"创建时间"`
+	LastUpdatedTime      time.Time `description:"访问历史类型,weekly_report 周报,pdf;默认值:weekly_report"`
+	ResearchReportId     int       `description:"研报id"`
+	ResearchReportTypeId int       `description:"报告章节id,为0时表示查看目录或者首页"`
+}
+
+//根据用户id字符串获取用户的浏览数
+type UserViewTotalSlice struct {
+	UserId      int       `description:"用户id"`
+	Total       int       `description:"总阅读数"`
+	CreatedTime time.Time `description:"用户浏览时间"`
+}
+
+func GetCountUserViewHistoryByUserIds(userIds string) (items []*UserViewTotalSlice, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT count(1) total,user_id,max(created_time) as created_time FROM user_view_history WHERE user_id in (` + userIds + `) group by user_id`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+	//return items2,err
+}
+
+//根据用户手机号字符串获取用户的浏览数
+type UserViewMobileTotalSlice struct {
+	Mobile      string    `description:"用户手机号"`
+	Total       int       `description:"总阅读数"`
+	CreatedTime time.Time `description:"用户浏览时间"`
+}
+
+func GetCountUserViewHistoryByMobiles(mobiles string) (items []*UserViewMobileTotalSlice, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT count(1) total,mobile,max(created_time) as created_time FROM user_view_history WHERE mobile in (` + mobiles + `) group by mobile`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+//根据用户id字符串获取用户的浏览数
+type UserViewEmailTotalSlice struct {
+	Email       string    `description:"用户邮箱"`
+	Total       int       `description:"总阅读数"`
+	CreatedTime time.Time `description:"用户浏览时间"`
+}
+
+func GetCountUserViewHistoryByEmails(emails string) (items []*UserViewEmailTotalSlice, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT count(1) total,email,max(created_time) as created_time FROM user_view_history WHERE email in (` + emails + `) group by email`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+	//return items2,err
+}
+
+func GetCountCygxArticleHistoryRecordByMobiles(mobiles string) (items []*UserViewMobileTotalSlice, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT count(1) total,h.mobile,max(h.create_time) as created_time FROM cygx_article_history_record_all  AS h  INNER JOIN cygx_article  AS art  ON  art.article_id = h.article_id  WHERE h.mobile in (` + mobiles + `) AND h.is_del = 0  AND h.company_id != 16 group by h.mobile`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetCountCygxArticleHistoryRecordByEmails(emails string) (items []*UserViewEmailTotalSlice, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT count(1) total,h.email,max(h.create_time) as created_time FROM cygx_article_history_record_all AS h  INNER JOIN cygx_article  AS art  ON  art.article_id = h.article_id  WHERE h.email in (` + emails + `) AND h.is_del = 0 AND h.company_id != 16 group by email`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// CompanyLastViewSlice 根据手机号获取客户的最新浏览时间
+type CompanyLastViewSlice struct {
+	CompanyId int       `description:"客户id"`
+	ViewTime  time.Time `description:"用户浏览时间"`
+}
+
+// GetLastUserViewHistoryByCompanyIdsMobile 根据手机号获取客户的最新浏览时间
+func GetLastUserViewHistoryByCompanyIdsMobile(companyIds string) (items []*CompanyLastViewSlice, err error) {
+	today := time.Now().Format(utils.FormatDate)
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,max(b.created_time) view_time
+FROM
+	wx_user a
+	JOIN user_view_history b ON a.mobile = b.mobile 
+WHERE
+	a.company_id IN ( ` + companyIds + ` )  and b.mobile !="" and b.created_time>=? GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetLastUserViewHistoryByCompanyIdsEmail 根据邮箱获取客户的最新浏览时间
+func GetLastUserViewHistoryByCompanyIdsEmail(companyIds string) (items []*CompanyLastViewSlice, err error) {
+	today := time.Now().Format(utils.FormatDate)
+	o := orm.NewOrm()
+	//	sql := `SELECT
+	//	a.company_id ,max(b.created_time) view_time
+	//FROM
+	//	wx_user a
+	//	JOIN user_view_history b ON a.email = b.email
+	//WHERE a.company_id IN ( ` + companyIds + ` ) and b.email !="" and b.mobile=""  and b.created_time>=?  GROUP BY company_id`
+
+	sql := `SELECT
+	a.company_id ,max(b.created_time) view_time FROM wx_user a
+	JOIN user_view_history b ON a.email = b.email 
+WHERE b.email !="" and b.mobile=""  and b.created_time>=?  GROUP BY company_id`
+
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetLastAdvisoryArticleViewRecordByCompanyIdsMobile 根据手机号获取客户的最新浏览时间
+func GetLastAdvisoryArticleViewRecordByCompanyIdsMobile(companyIds string) (items []*CompanyLastViewSlice, err error) {
+	today := time.Now().Format(utils.FormatDate)
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,max(b.create_time) view_time
+FROM
+	wx_user a
+	JOIN advisory_user_chart_article_record b ON a.mobile = b.mobile 
+WHERE
+	a.company_id IN ( ` + companyIds + ` )  and b.mobile !="" and b.create_time>=? GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetLastAdvisoryArticleViewRecordByCompanyIdsEmail 根据邮箱获取客户的最新浏览时间
+func GetLastAdvisoryArticleViewRecordByCompanyIdsEmail(companyIds string) (items []*CompanyLastViewSlice, err error) {
+	today := time.Now().Format(utils.FormatDate)
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,max(b.create_time) view_time
+FROM
+	wx_user a
+	JOIN advisory_user_chart_article_record b ON a.email = b.email 
+WHERE a.company_id IN ( ` + companyIds + ` ) and b.email !="" and b.mobile=""  and b.create_time>=?  GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetLastCygxArticleViewRecordByCompanyIdsMobile 根据手机号获取客户的最新浏览时间
+func GetLastCygxArticleViewRecordByCompanyIdsMobile(companyIds string) (items []*CompanyLastViewSlice, err error) {
+	o := orm.NewOrm()
+	//dataName := ""
+	//if utils.RunMode == "debug" {
+	//	dataName = "test_v2_hongze_rddp"
+	//} else {
+	//	dataName = "hongze_rddp"
+	//}
+	sql := `SELECT
+	a.company_id ,max(b.create_time) view_time
+FROM
+	wx_user a
+	JOIN cygx_article_history_record_newpv b ON a.mobile = b.mobile 
+WHERE
+	a.company_id IN ( ` + companyIds + ` )  and b.mobile !="" GROUP BY company_id`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetLastCygxArticleViewRecordByCompanyIdsEmail 根据邮箱获取客户的最新浏览时间
+func GetLastCygxArticleViewRecordByCompanyIdsEmail(companyIds string) (items []*CompanyLastViewSlice, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,max(b.create_time) view_time
+FROM
+	wx_user a
+	JOIN cygx_article_history_record_newpv b ON a.email = b.email 
+WHERE a.company_id IN ( ` + companyIds + ` ) and b.email !="" and b.mobile=""   GROUP BY company_id`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetLastReportViewRecordByCompanyIdsMobile 根据手机号获取客户的最新浏览时间
+func GetLastReportViewRecordByCompanyIdsMobile(companyIds string) (items []*CompanyLastViewSlice, err error) {
+	today := time.Now().Format(utils.FormatDate)
+	o := orm.NewOrm()
+	dataName := ""
+	if utils.RunMode == "debug" {
+		dataName = "test_v2_hongze_rddp"
+	} else {
+		dataName = "hongze_rddp"
+	}
+	sql := `SELECT
+	a.company_id ,max(b.create_time) view_time
+FROM
+	wx_user a
+	JOIN ` + dataName + `.report_view_record b ON a.mobile = b.mobile 
+WHERE
+	a.company_id IN ( ` + companyIds + ` )  and b.mobile !="" and b.create_time>=? GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetLastReportViewRecordByCompanyIdsEmail 根据邮箱获取客户的最新浏览时间
+func GetLastReportViewRecordByCompanyIdsEmail(companyIds string) (items []*CompanyLastViewSlice, err error) {
+	today := time.Now().Format(utils.FormatDate)
+	o := orm.NewOrm()
+	dataName := ""
+	if utils.RunMode == "debug" {
+		dataName = "test_v2_hongze_rddp"
+	} else {
+		dataName = "hongze_rddp"
+	}
+	sql := `SELECT	a.company_id ,max(b.create_time) view_time FROM wx_user a
+	JOIN ` + dataName + `.report_view_record b ON a.email = b.email 
+WHERE a.company_id IN ( ` + companyIds + ` ) and b.email !="" and b.mobile=""   and b.create_time>=? GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetLastUserViewStatisticsByCompanyIdsMobile 根据手机号获取客户的最新浏览时间
+func GetLastUserViewStatisticsByCompanyIdsMobile(companyIds string) (items []*CompanyLastViewSlice, err error) {
+	today := time.Now().Format(utils.FormatDate)
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,max(b.last_view_time) view_time
+FROM
+	wx_user a
+	JOIN user_view_statistics b ON a.mobile = b.mobile 
+WHERE
+	a.company_id IN ( ` + companyIds + ` )  and a.mobile !="" and b.mobile !="" and b.date<=? GROUP BY a.company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetLastUserViewStatisticsByCompanyIdsEmail 根据邮箱获取客户的最新浏览时间
+func GetLastUserViewStatisticsByCompanyIdsEmail(companyIds string) (items []*CompanyLastViewSlice, err error) {
+	today := time.Now().Format(utils.FormatDate)
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,max(b.last_view_time) view_time
+FROM
+	wx_user a
+	JOIN user_view_statistics b ON a.email = b.email 
+WHERE a.company_id IN ( ` + companyIds + ` ) and a.email !="" and b.email !="" and a.mobile="" and b.mobile="" and b.date<=? GROUP BY a.company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// CompanyViewTotalSlice 获取客户的浏览次数
+type CompanyViewTotalSlice struct {
+	CompanyId int `description:"客户id"`
+	ViewTotal int `description:"用户浏览次数"`
+}
+
+// GetCountUserViewHistoryByCompanyIdsMobile 根据手机号获取客户的浏览次数
+func GetCountUserViewHistoryByCompanyIdsMobile(companyIds string) (items []*CompanyViewTotalSlice, err error) {
+	today := time.Now().Format(utils.FormatDate) + " 00:00:00"
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,count(1) view_total FROM 	wx_user a
+	JOIN user_view_history b ON a.mobile = b.mobile 
+WHERE 	a.company_id IN ( ` + companyIds + ` )  and b.mobile !="" and b.created_time>=? GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetCountUserViewHistoryByCompanyIdsEmail 根据邮箱获取客户的浏览次数
+func GetCountUserViewHistoryByCompanyIdsEmail(companyIds string) (items []*CompanyViewTotalSlice, err error) {
+	today := time.Now().Format(utils.FormatDate) + " 00:00:00"
+	o := orm.NewOrm()
+	//	sql := `SELECT
+	//	a.company_id ,count(1) view_total
+	//FROM
+	//	wx_user a
+	//	JOIN user_view_history b ON a.email = b.email
+	//WHERE a.company_id IN ( ` + companyIds + ` ) and b.email !="" and b.mobile=""  and b.created_time>=? GROUP BY company_id`
+
+	sql := `SELECT a.company_id ,count(1) view_total FROM wx_user a 
+JOIN user_view_history b ON a.email = b.email 
+WHERE  b.email !="" and b.mobile=""  and b.created_time>=? GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetCountAdvisoryArticleViewRecordByCompanyIdsMobile 根据手机号获取客户的浏览次数
+func GetCountAdvisoryArticleViewRecordByCompanyIdsMobile(companyIds string) (items []*CompanyViewTotalSlice, err error) {
+	today := time.Now().Format(utils.FormatDate) + " 00:00:00"
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,count(1) view_total
+FROM
+	wx_user a
+	JOIN advisory_user_chart_article_record b ON a.mobile = b.mobile 
+WHERE
+	a.company_id IN ( ` + companyIds + ` )  and b.mobile !="" and create_time>=? GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetCountAdvisoryArticleViewRecordByCompanyIdsEmail 根据邮箱获取客户的浏览次数
+func GetCountAdvisoryArticleViewRecordByCompanyIdsEmail(companyIds string) (items []*CompanyViewTotalSlice, err error) {
+	today := time.Now().Format(utils.FormatDate) + " 00:00:00"
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,count(1) view_total
+FROM
+	wx_user a
+	JOIN advisory_user_chart_article_record b ON a.email = b.email 
+WHERE a.company_id IN ( ` + companyIds + ` ) and b.email !="" and b.mobile="" and create_time>=? GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetCountCygxArticleViewRecordByCompanyIdsMobile 根据手机号获取客户的浏览次数
+func GetCountCygxArticleViewRecordByCompanyIdsMobile(companyIds string) (items []*CompanyViewTotalSlice, err error) {
+	o := orm.NewOrm()
+	//dataName := ""
+	//if utils.RunMode == "debug" {
+	//	dataName = "test_v2_hongze_rddp"
+	//} else {
+	//	dataName = "hongze_rddp"
+	//}
+	sql := `SELECT
+	a.company_id ,count(1) view_total
+FROM
+	wx_user a
+	JOIN cygx_article_history_record_newpv b ON a.mobile = b.mobile 
+WHERE
+	a.company_id IN ( ` + companyIds + ` )  and b.mobile !="" GROUP BY company_id`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetCountCygxArticleViewRecordByCompanyIdsEmail 根据邮箱获取客户的浏览次数
+func GetCountCygxArticleViewRecordByCompanyIdsEmail(companyIds string) (items []*CompanyViewTotalSlice, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,count(1) view_total
+FROM
+	wx_user a
+	JOIN cygx_article_history_record_newpv b ON a.email = b.email 
+WHERE a.company_id IN ( ` + companyIds + ` ) and b.email !="" and b.mobile="" GROUP BY company_id`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetCountReportViewRecordByCompanyIdsMobile 根据手机号获取客户的浏览次数
+func GetCountReportViewRecordByCompanyIdsMobile(companyIds string) (items []*CompanyViewTotalSlice, err error) {
+	today := time.Now().Format(utils.FormatDate) + " 00:00:00"
+	o := orm.NewOrm()
+	dataName := ""
+	if utils.RunMode == "debug" {
+		dataName = "test_v2_hongze_rddp"
+	} else {
+		dataName = "hongze_rddp"
+	}
+	sql := `SELECT
+	a.company_id ,count(1) view_total
+FROM
+	wx_user a
+	JOIN ` + dataName + `.report_view_record b ON a.mobile = b.mobile 
+WHERE
+	a.company_id IN ( ` + companyIds + ` )  and b.mobile !="" and create_time>=? GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetCountReportViewRecordByCompanyIdsEmail 根据邮箱获取客户的浏览次数
+func GetCountReportViewRecordByCompanyIdsEmail(companyIds string) (items []*CompanyViewTotalSlice, err error) {
+	today := time.Now().Format(utils.FormatDate) + " 00:00:00"
+	o := orm.NewOrm()
+	dataName := ""
+	if utils.RunMode == "debug" {
+		dataName = "test_v2_hongze_rddp"
+	} else {
+		dataName = "hongze_rddp"
+	}
+	sql := `SELECT	a.company_id ,count(1) view_total FROM wx_user a
+	JOIN ` + dataName + `.report_view_record b ON a.email = b.email 
+WHERE a.company_id IN ( ` + companyIds + ` ) and b.email !="" and b.mobile="" and create_time>=? GROUP BY company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetUserViewStatisticsByCompanyIdsMobile 根据手机号获取客户的浏览次数
+func GetUserViewStatisticsByCompanyIdsMobile(companyIds string) (items []*CompanyViewTotalSlice, err error) {
+	today := time.Now().Format(utils.FormatDate)
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,sum(b.view_num) view_total
+FROM
+	wx_user a
+	JOIN user_view_statistics b ON a.mobile = b.mobile 
+WHERE
+	a.company_id IN ( ` + companyIds + ` )  and a.mobile !="" and b.mobile !="" and date<=? GROUP BY a.company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// GetUserViewStatisticsByCompanyIdsEmail 根据邮箱获取客户的浏览次数
+func GetUserViewStatisticsByCompanyIdsEmail(companyIds string) (items []*CompanyViewTotalSlice, err error) {
+	today := time.Now().Format(utils.FormatDate)
+	o := orm.NewOrm()
+	sql := `SELECT
+	a.company_id ,sum(b.view_num) view_total
+FROM
+	wx_user a
+	JOIN user_view_statistics b ON a.email = b.email 
+WHERE a.company_id IN ( ` + companyIds + ` ) and a.email !="" and b.email !="" and a.mobile="" and b.mobile="" and date<=? GROUP BY a.company_id`
+	_, err = o.Raw(sql, today).QueryRows(&items)
+	return
+}
+
+// UserViewStatisticsInfo 根据用户手机号字符串获取用户的浏览数和最晚阅读次数
+type UserViewStatisticsInfo struct {
+	Mobile       string    `description:"用户手机号"`
+	Total        int       `description:"总阅读数"`
+	LastViewTime time.Time `description:"用户浏览时间"`
+}
+
+// GetUserViewStatisticsByMobile 根据手机号获取联系人的浏览次数
+func GetUserViewStatisticsByMobile(mobile string) (item *UserViewStatisticsInfo, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT mobile,sum(view_num) total,max(last_view_time) last_view_time FROM  user_view_statistics  WHERE mobile = ? `
+	err = o.Raw(sql, mobile).QueryRow(&item)
+	return
+}
+
+type ResearchReportViewPUV struct {
+	ResearchReportId int
+	Pv               int
+	Uv               int
+}
+
+// GetPUVByResearchReportIds 通过报告IDs获取老报告PV、UV
+func GetPUVByResearchReportIds(reportIds string) (list []*ResearchReportViewPUV, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+				research_report_id,
+				COUNT(1) AS pv,
+				COUNT(DISTINCT user_id) AS uv
+			FROM
+				user_view_history
+			WHERE
+				research_report_id IN (` + reportIds +`)
+			GROUP BY
+				research_report_id`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}

+ 476 - 0
models/wechat_send_msg.go

@@ -0,0 +1,476 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"strings"
+)
+
+type OpenIdList struct {
+	OpenId string
+	UserId int
+}
+
+func GetOpenIdList() (items []*OpenIdList, err error) {
+	//openIdstr := WxUsersGet()
+	//sql:=` SELECT DISTINCT open_id FROM wx_user AS wu
+	//      INNER JOIN company AS c ON c.company_id = wu.company_id
+	//      INNER JOIN company_product AS d ON c.company_id=d.company_id
+	//      WHERE wu.open_id IS NOT NULL AND  d.status IN('正式','试用','永续') `
+	sql := `SELECT DISTINCT ur.open_id,wu.user_id FROM wx_user AS wu 
+          INNER JOIN company AS c ON c.company_id = wu.company_id 
+          INNER JOIN company_product AS d ON c.company_id=d.company_id
+		INNER join user_record  as ur on wu.user_id=ur.user_id
+          WHERE ur.open_id != "" AND ur.subscribe=1 and ur.create_platform=1 AND  d.status IN('正式','试用','永续') `
+	//if openIdstr != "" {
+	//	sql += ` AND ur.open_id in (` + openIdstr + `) `
+	//}
+	_, err = orm.NewOrm().Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetOpenIdListByMobile(mobile string) (items []*OpenIdList, err error) {
+	//openIdstr := WxUsersGet()
+	sql := `SELECT DISTINCT ur.open_id,wu.user_id FROM wx_user AS wu 
+          INNER JOIN company AS c ON c.company_id = wu.company_id 
+          INNER join user_record  as ur on wu.user_id=ur.user_id
+          WHERE ur.open_id != "" AND ur.subscribe=1 and ur.create_platform=1 AND wu.mobile=? `
+	//if openIdstr != "" {
+	//	sql += ` AND ur.open_id in (` + openIdstr + `) `
+	//}
+	_, err = orm.NewOrm().Raw(sql, mobile).QueryRows(&items)
+	return
+}
+
+// 获取预约活动的用户的openID
+func GetActivityOpenIdList(activityId int) (items []*OpenIdList, err error) {
+	sql := `SELECT DISTINCT cr.open_id,u.user_id
+			FROM
+			cygx_my_schedule AS m
+			INNER JOIN user_record AS u ON u.bind_account = m.mobile 
+			INNER JOIN cygx_user_record AS cr ON cr.union_id = u.union_id 
+			WHERE m.activity_id = ? AND u.create_platform = 4 `
+	_, err = orm.NewOrm().Raw(sql, activityId).QueryRows(&items)
+	return
+}
+
+// 获取预约活动的用户的openID测试环境
+func GetActivityOpenIdListByDeBug(activityId int) (items []*OpenIdList, err error) {
+	sql := `SELECT
+			u.open_id,
+			u.user_id 
+		FROM
+			cygx_my_schedule AS s
+			INNER JOIN wx_user AS wx ON wx.user_id = s.user_id
+			INNER JOIN user_record AS u ON u.bind_account = wx.mobile
+			INNER JOIN company_product AS p ON p.company_id = wx.company_id 
+		WHERE
+			s.activity_id = ? 
+			AND u.create_platform = 1 
+			AND p.STATUS IN ( '正式', '试用', '永续' ) 
+		GROUP BY
+			u.open_id`
+	_, err = orm.NewOrm().Raw(sql, activityId).QueryRows(&items)
+	return
+}
+
+// 获取预约活动的用户的openID
+func GetActivitySpecialOpenIdList() (items []*OpenIdList, err error) {
+	sql := `SELECT DISTINCT cr.open_id,u.user_id
+			FROM
+			cygx_user_follow_special AS m
+			INNER JOIN user_record AS u ON u.bind_account = m.mobile 
+			INNER JOIN cygx_user_record AS cr ON cr.union_id = u.union_id 
+			WHERE  u.create_platform = 4 `
+	_, err = orm.NewOrm().Raw(sql).QueryRows(&items)
+	return
+}
+
+// 获取预约活动的用户的openID测试环境
+func GetActivitySpecialOpenIdListByDeBug() (items []*OpenIdList, err error) {
+	sql := `SELECT
+			u.open_id,
+			u.user_id 
+		FROM
+			cygx_user_follow_special AS s
+			INNER JOIN wx_user AS wx ON wx.user_id = s.user_id
+			INNER JOIN user_record AS u ON u.bind_account = wx.mobile
+			INNER JOIN company_product AS p ON p.company_id = wx.company_id 
+		WHERE
+			u.create_platform = 1 
+			AND p.STATUS IN ( '正式', '试用', '永续' ) 
+		GROUP BY
+			u.open_id`
+	_, err = orm.NewOrm().Raw(sql).QueryRows(&items)
+	return
+}
+
+// 获取关注作者的用户的openID
+func GetFollowDepartmentOpenIdList(departmentId int) (items []*OpenIdList, err error) {
+	sql := `SELECT cr.open_id,u.user_id
+			FROM
+				cygx_article_department_follow AS f
+				INNER JOIN user_record AS u ON u.bind_account = f.mobile
+				INNER JOIN wx_user AS wx ON wx.user_id = f.user_id
+				INNER JOIN company_product AS p ON p.company_id = wx.company_id 
+				INNER JOIN cygx_user_record AS cr ON cr.union_id = u.union_id 
+			WHERE
+				f.department_id = ?
+				AND u.create_platform = 4 
+				AND f.type = 1 
+				AND p.status IN ('正式','试用','永续')
+			GROUP BY
+				cr.open_id `
+	_, err = orm.NewOrm().Raw(sql, departmentId).QueryRows(&items)
+	return
+}
+
+// 获取关注作者的用户的openID测试环境
+func GetFollowDepartmentOpenIdListByDeBug(departmentId int) (items []*OpenIdList, err error) {
+	sql := `SELECT
+			u.open_id,
+			u.user_id 
+		FROM
+			cygx_article_department_follow AS f
+			INNER JOIN wx_user AS wx ON wx.user_id = f.user_id
+			INNER JOIN user_record AS u ON u.bind_account = wx.mobile
+			INNER JOIN company_product AS p ON p.company_id = wx.company_id
+		WHERE
+			f.department_id = ? 
+			AND u.create_platform = 1
+			AND f.type = 1 
+			AND p.STATUS IN ( '正式', '试用', '永续' ) 
+		GROUP BY
+			u.open_id`
+	_, err = orm.NewOrm().Raw(sql, departmentId).QueryRows(&items)
+	return
+}
+
+// 获取关注产业的用户的openID
+func GetFollowindustrialOpenIdList(industrialManagementId int) (items []*OpenIdList, err error) {
+	sql := `SELECT cr.open_id,u.user_id
+			FROM
+			cygx_industry_fllow AS f
+			INNER JOIN wx_user AS wx ON wx.user_id = f.user_id
+			INNER JOIN user_record AS u ON u.bind_account = wx.mobile 
+			INNER JOIN cygx_user_record AS cr ON cr.union_id = u.union_id 
+			WHERE
+			f.industrial_management_id = ? 
+			AND u.create_platform = 4 
+			AND f.type = 1 GROUP BY cr.open_id `
+	_, err = orm.NewOrm().Raw(sql, industrialManagementId).QueryRows(&items)
+	return
+}
+
+// 获取关注产业的用户的openID 测试环境
+func GetFollowindustrialOpenIdListByDeBug(industrialManagementId int) (items []*OpenIdList, err error) {
+	sql := `SELECT
+			u.open_id,
+			u.user_id 
+		FROM
+			cygx_industry_fllow AS f
+			INNER JOIN wx_user AS wx ON wx.user_id = f.user_id
+			INNER JOIN user_record AS u ON u.bind_account = wx.mobile
+		WHERE
+			f.industrial_management_id = ? 
+			AND u.create_platform = 1 
+			AND u.bind_account != ""
+			AND f.type = 1 
+		GROUP BY
+			u.open_id`
+	_, err = orm.NewOrm().Raw(sql, industrialManagementId).QueryRows(&items)
+	return
+}
+
+// GetUserOpenidListByUserIds 根据用户id字符串集合来获取他的openid列表集合
+func GetUserOpenidListByUserIds(userIdStr []string) (list []*OpenIdList, err error) {
+	if len(userIdStr) <= 0 {
+		return
+	}
+	sql := `SELECT open_id,u.user_id FROM user_record WHERE user_id in (` + strings.Join(userIdStr, ",") + `) and create_platform = 1`
+	_, err = orm.NewOrm().Raw(sql).QueryRows(&list)
+	return
+}
+
+func GetAdminOpenIdByMobile(mobile string) (items []*OpenIdList, err error) {
+	sql := `SELECT DISTINCT ur.open_id,wu.user_id FROM wx_user AS wu 
+          INNER JOIN company AS c ON c.company_id = wu.company_id 
+          INNER join user_record  as ur on wu.user_id=ur.user_id
+          WHERE ur.open_id != "" and ur.create_platform=1 AND wu.mobile=? `
+	_, err = orm.NewOrm().Raw(sql, mobile).QueryRows(&items)
+	return
+}
+
+// 根据手机号获取用户的openid查研观向小助手专用
+func GetUserRecordListByMobile(platform int, bindAccount string) (items []*OpenIdList, err error) {
+	var sql string
+	if utils.RunMode == "release" {
+		sql = `SELECT cr.open_id FROM user_record  as u 
+			INNER JOIN cygx_user_record AS cr ON cr.union_id = u.union_id 
+			WHERE create_platform=? AND bind_account IN (` + bindAccount + `)`
+	} else {
+		platform = 1
+		sql = `SELECT open_id FROM	user_record  WHERE create_platform =? AND bind_account IN (` + bindAccount + `)`
+	}
+	_, err = orm.NewOrm().Raw(sql, platform).QueryRows(&items)
+	return
+}
+
+// 获取单个用户openid
+func GetCompanyDetailByIdGroup(platform int, bindAccount string) (item *OpenIdList, err error) {
+	o := orm.NewOrm()
+	var sql string
+	sql = `SELECT cr.open_id,wu.user_id FROM user_record  as u 
+			INNER JOIN cygx_user_record AS cr ON cr.union_id = u.union_id 
+			INNER JOIN wx_user AS wu ON wu.mobile = u.bind_account 
+			WHERE create_platform=? AND u.bind_account = ?`
+	err = o.Raw(sql, platform, bindAccount).QueryRow(&item)
+	return
+}
+
+// 获取小助手所有的用户的openid
+func GetCygxUserAllOpneid() (items []*OpenIdList, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+			cr.open_id,
+			r.user_id 
+		FROM
+			user_record AS r
+			INNER JOIN cygx_user_record AS cr ON cr.union_id = r.union_id 
+		WHERE
+			r.create_platform = 4`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetOpenIdArr() (items []string, err error) {
+	sql := ` SELECT DISTINCT ur.open_id FROM wx_user AS wu 
+          INNER JOIN company AS c ON c.company_id = wu.company_id 
+          INNER JOIN company_product AS d ON c.company_id=d.company_id
+		INNER JOIN user_record  AS ur ON wu.user_id=ur.user_id
+          WHERE ur.open_id != "" AND ur.subscribe=1 AND ur.create_platform=1 AND  d.status IN('正式','试用','永续')
+         ORDER BY FIELD(c.company_id, 16) desc, ur.user_record_id asc`
+	_, err = orm.NewOrm().Raw(sql).QueryRows(&items)
+	return
+}
+
+// 获取预约活动的用户的openID测试环境
+func GetActivitySpecialOpenIdListMobile(condition string, pars []interface{}) (items []*OpenIdList, err error) {
+	sql := `SELECT
+			cr.open_id,
+			u.user_id
+		FROM
+			company_report_permission AS p
+			INNER JOIN wx_user AS u ON u.company_id = p.company_id
+			INNER JOIN user_record AS r ON r.user_id = u.user_id
+			INNER JOIN cygx_user_record AS cr ON cr.union_id = r.union_id 
+		WHERE
+			 r.create_platform = 4  AND p.STATUS IN ('正式','试用','永续')  ` + condition + ` GROUP BY cr.open_id`
+	_, err = orm.NewOrm().Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// 获取所有关注了该产业用户的openid
+func GetCygxUserIndustryFllowOpneid(IndustrialManagementId int) (items []*OpenIdList, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+			cr.open_id,
+			r.user_id 
+		FROM
+			user_record AS r
+			INNER JOIN cygx_user_record AS cr ON cr.union_id = r.union_id 
+			INNER join cygx_industry_fllow  as cf on cf.user_id = r.user_id 
+		WHERE
+			r.create_platform = 4
+			AND cf.industrial_management_id = ?`
+	_, err = o.Raw(sql, IndustrialManagementId).QueryRows(&items)
+	return
+}
+
+// 获取所有关注了该产业用户的openid
+func GetCygxUserIndustryFllowOpneidByActivityIds(activityId int) (items []*OpenIdList, err error) {
+	o := orm.NewOrm()
+	sql := `			
+SELECT
+	cr.open_id,
+	r.user_id 
+FROM
+	cygx_industrial_activity_group_management AS agm
+	INNER JOIN cygx_industry_fllow AS f ON f.industrial_management_id = agm.industrial_management_id
+	INNER JOIN user_record AS r
+	INNER JOIN cygx_user_record AS cr 
+WHERE
+	agm.activity_id = ? 
+	AND cr.union_id = r.union_id 
+	AND r.create_platform = 4 
+	AND r.user_id = f.user_id;`
+	_, err = o.Raw(sql, activityId).QueryRows(&items)
+	return
+}
+
+// 获取所有关注了该产业永续客户的openid
+func GetCygxForeverUserIndustryFllowOpneidByActivityIds(activityId int) (items []*OpenIdList, err error) {
+	o := orm.NewOrm()
+	sql := `			
+SELECT
+	cr.open_id,
+	r.user_id 
+FROM
+	cygx_industrial_activity_group_management AS agm
+	INNER JOIN cygx_industry_fllow AS f ON f.industrial_management_id = agm.industrial_management_id
+	INNER JOIN user_record AS r
+	INNER JOIN cygx_user_record AS cr 
+	INNER JOIN wx_user AS wx ON wx.user_id = r.user_id 
+	INNER JOIN company_product AS p ON p.company_id = wx.company_id 
+WHERE
+	agm.activity_id = ? 
+	AND cr.union_id = r.union_id 
+	AND r.create_platform = 4 
+	AND r.user_id = f.user_id
+	AND p.status = "永续";`
+	_, err = o.Raw(sql, activityId).QueryRows(&items)
+	return
+}
+
+// 获取所有关注了该产业试用客户的openid
+func GetCygxTryOutUserIndustryFllowOpneidByActivityIds(activityId int) (items []*OpenIdList, err error) {
+	o := orm.NewOrm()
+	sql := `			
+SELECT
+	cr.open_id,
+	r.user_id 
+FROM
+	cygx_industrial_activity_group_management AS agm
+	INNER JOIN cygx_industry_fllow AS f ON f.industrial_management_id = agm.industrial_management_id
+	INNER JOIN user_record AS r
+	INNER JOIN cygx_user_record AS cr 
+	INNER JOIN wx_user AS wx ON wx.user_id = r.user_id 
+	INNER JOIN company_product AS p ON p.company_id = wx.company_id 
+WHERE
+	agm.activity_id = ? 
+	AND cr.union_id = r.union_id 
+	AND r.create_platform = 4 
+	AND r.user_id = f.user_id
+	AND p.status = "试用";`
+	_, err = o.Raw(sql, activityId).QueryRows(&items)
+	return
+}
+
+// 获取所有关注了该产业用户的companyIds
+func GetCygxIndustryFollowCompanyIdsByActivityId(activityId int) (item *string, err error) {
+	o := orm.NewOrm()
+	sql := `			
+SELECT
+	GROUP_CONCAT( DISTINCT f.company_id SEPARATOR ',' ) AS company_ids
+FROM
+	cygx_industrial_activity_group_management AS agm
+	INNER JOIN cygx_industry_fllow AS f ON f.industrial_management_id = agm.industrial_management_id
+	INNER JOIN user_record AS r
+	INNER JOIN cygx_user_record AS cr 
+WHERE
+	agm.activity_id = ? 
+	AND r.create_platform = 4 
+	AND r.user_id = f.user_id;`
+	err = o.Raw(sql, activityId).QueryRow(&item)
+	return
+}
+
+// 获取所有关注了该产业用户的openidBy公司id
+func GetCygxUserIndustryFllowOpneidByActivityIdAndCompanyIds(activityId int, companyIds string) (items []*OpenIdList, err error) {
+	o := orm.NewOrm()
+	sql := `			
+SELECT
+	cr.open_id,
+	r.user_id 
+FROM
+	cygx_industrial_activity_group_management AS agm
+	INNER JOIN cygx_industry_fllow AS f ON f.industrial_management_id = agm.industrial_management_id
+	INNER JOIN user_record AS r
+	INNER JOIN cygx_user_record AS cr 
+WHERE
+	agm.activity_id = ? 
+	AND cr.union_id = r.union_id 
+	AND r.create_platform = 4 
+	AND r.user_id = f.user_id
+	AND f.company_id IN (` + companyIds + `);`
+	_, err = o.Raw(sql, activityId).QueryRows(&items)
+	return
+}
+
+// 获取所有互动过的用户id
+func GetCygxInteractiveUserByActivityId(activityId int) (items []*int, err error) {
+	o := orm.NewOrm()
+	sql := `			
+SELECT user_id FROM cygx_activity_signup WHERE activity_id =? AND fail_type = 0
+UNION ALL
+SELECT user_id FROM cygx_activity_appointment WHERE activity_id = ?
+UNION ALL
+SELECT user_id FROM cygx_activity_meeting_reminder WHERE activity_id = ?
+UNION ALL
+SELECT user_id FROM cygx_activity_help_ask  WHERE activity_id = ? `
+	_, err = o.Raw(sql, activityId, activityId, activityId, activityId).QueryRows(&items)
+	return
+}
+
+// 获取所有用户的openid
+func GetCygxUserOpneidByUserIds(userIds string) (items []*OpenIdList, err error) {
+	o := orm.NewOrm()
+	sql := `			
+SELECT
+	cr.open_id,
+	r.user_id 
+FROM
+	user_record AS r
+	INNER JOIN cygx_user_record AS cr 
+WHERE
+	cr.union_id = r.union_id 
+	AND r.create_platform = 4 
+	AND r.user_id IN (` + userIds + `);`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetOpenIdArrByClassifyNameSecond(classifyNameSecond string) (items []string, err error) {
+	sql := ` SELECT DISTINCT ur.open_id FROM wx_user AS wu 
+			INNER JOIN company AS c ON c.company_id = wu.company_id 
+			INNER JOIN company_product AS d ON c.company_id=d.company_id
+			INNER JOIN user_record  AS ur ON wu.user_id=ur.user_id
+			INNER JOIN company_report_permission AS e ON d.company_id=e.company_id
+			INNER JOIN chart_permission AS f ON e.chart_permission_id=f.chart_permission_id
+			INNER JOIN chart_permission_search_key_word_mapping AS g ON f.chart_permission_id=g.chart_permission_id
+			WHERE ur.open_id != "" AND ur.subscribe=1 AND ur.create_platform=1 AND  d.status IN('正式','试用','永续')
+			AND g.from='rddp'
+			AND g.key_word=?
+			ORDER BY FIELD(c.company_id, 16) DESC, ur.user_record_id ASC  `
+	_, err = orm.NewOrm().Raw(sql, classifyNameSecond).QueryRows(&items)
+	return
+}
+
+func GetOpenIdArrByChartPermissionIds(chartPermissionIds string) (items []string, err error) {
+	sql := ` SELECT DISTINCT ur.open_id FROM wx_user AS wu 
+INNER JOIN company AS c ON c.company_id = wu.company_id 
+INNER JOIN company_product AS d ON c.company_id=d.company_id
+INNER JOIN user_record  AS ur ON wu.user_id=ur.user_id
+INNER JOIN company_report_permission AS e ON d.company_id=e.company_id
+INNER JOIN chart_permission AS f ON e.chart_permission_id=f.chart_permission_id
+WHERE ur.open_id != "" AND ur.subscribe=1 AND ur.create_platform=1 AND  d.status IN('正式','试用','永续')
+AND f.chart_permission_id IN(` + chartPermissionIds + `)
+ORDER BY FIELD(c.company_id, 16) DESC, ur.user_record_id ASC  `
+	_, err = orm.NewOrm().Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetOpenIdArrByVarietyTag(varietyTagId int) (items []string, err error) {
+	sql := ` SELECT DISTINCT ur.open_id FROM wx_user AS wu 
+INNER JOIN company AS c ON c.company_id = wu.company_id 
+INNER JOIN company_product AS d ON c.company_id=d.company_id
+INNER JOIN user_record  AS ur ON wu.user_id=ur.user_id
+INNER JOIN company_report_permission AS e ON d.company_id=e.company_id
+INNER JOIN chart_permission AS f ON e.chart_permission_id=f.chart_permission_id
+INNER JOIN variety_tag AS g ON f.chart_permission_id=g.chart_permission_id
+WHERE ur.open_id != "" AND ur.subscribe=1 AND ur.create_platform=1 AND  d.status IN('正式','试用','永续')
+AND g.variety_tag_id=?
+ORDER BY FIELD(c.company_id, 16) DESC, ur.user_record_id ASC  `
+	_, err = orm.NewOrm().Raw(sql, varietyTagId).QueryRows(&items)
+	return
+}

+ 60 - 0
models/xfyun.go

@@ -0,0 +1,60 @@
+package models
+
+type XfSendParam struct {
+	Common struct {
+		AppId string `json:"app_id"`
+	} `json:"common"`
+	Business struct {
+		Aue    string `json:"aue"`
+		Sfl    int    `json:"sfl"`
+		Auf    string `json:"auf"`
+		Vcn    string `json:"vcn"`
+		Speed  int    `json:"speed"`
+		Volume int    `json:"volume"`
+		Pitch  int    `json:"pitch"`
+		Bgs    int    `json:"bgs"`
+		Tte    string `json:"tte"`
+		Reg    string `json:"reg"`
+		Rdn    string `json:"rdn"`
+	} `json:"business"`
+	Data struct {
+		Text   string `json:"text"`
+		Status int    `json:"status"`
+	} `json:"data"`
+}
+
+type XfReciveResult struct {
+	Code    int
+	Message string
+	Sid     string
+	Data    *struct {
+		Audio  string `json:"audio"`
+		Ced    string `json:"ced"`
+		Status int    `json:"status"`
+	} `json:"data"`
+}
+
+type AudioReq struct {
+	ReportId string
+}
+
+type EdbdataImportReq struct {
+	FullPath  string
+	SysUserId string
+}
+
+type EdbdataExportExcelReq struct {
+	StartDate  string
+	EndDate    string
+	Frequency  string
+	ClassifyId int
+	KeyWord    string
+	Mobile     string
+}
+
+// 内容转音频后的结构体
+type VideoInfo struct {
+	VideoUrl         string  `description:"链接"`
+	VideoPlaySeconds float64 `description:"时长"`
+	VideoSize        string  `description:"大小"`
+}

+ 297 - 0
routers/commentsRouter.go

@@ -304,6 +304,105 @@ func init() {
             Filters: nil,
             Filters: nil,
             Params: nil})
             Params: nil})
 
 
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "DelClassify",
+            Router: `/classify/delete`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "ListClassify",
+            Router: `/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "ListReport",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "PublishReport",
+            Router: `/publish`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "PublishCancleReport",
+            Router: `/publish/cancel`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportEmailController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportEmailController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/email/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportEmailController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportEmailController"],
+        beego.ControllerComments{
+            Method: "LogList",
+            Router: `/email/log_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportEmailController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportEmailController"],
+        beego.ControllerComments{
+            Method: "Resend",
+            Router: `/email/resend`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportEmailController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers/english_report:EnglishReportEmailController"],
+        beego.ControllerComments{
+            Method: "Send",
+            Router: `/email/send`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ClassifyController"],
+        beego.ControllerComments{
+            Method: "ListClassify",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:PptCommonController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:PptCommonController"],
     beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:PptCommonController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:PptCommonController"],
         beego.ControllerComments{
         beego.ControllerComments{
             Method: "Download",
             Method: "Download",
@@ -907,6 +1006,204 @@ func init() {
             Filters: nil,
             Filters: nil,
             Params: nil})
             Params: nil})
 
 
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "CheckDayWeekReportChapterVideo",
+            Router: `/CheckDayWeekReportChapterVideo`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "AddDayWeekReport",
+            Router: `/addDayWeekReport`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "ClassifyIdDetail",
+            Router: `/classifyIdDetail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "EditChapterTrendTag",
+            Router: `/editChapterTrendTag`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "EditDayWeekChapter",
+            Router: `/editDayWeekChapter`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "EditDayWeekReport",
+            Router: `/editDayWeekReport`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "GetChapterTrendTag",
+            Router: `/getChapterTrendTag`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "GetDayWeekChapter",
+            Router: `/getDayWeekChapter`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "GetReportChapterList",
+            Router: `/getReportChapterList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "GetStopDayWeekReportChapterTypeList",
+            Router: `/getStopDayWeekReportChapterTypeList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "GetSunCode",
+            Router: `/getSunCode`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "ListReport",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "MarkEditStatus",
+            Router: `/mark`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "PublishReport",
+            Router: `/publish`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "PublishCancleReport",
+            Router: `/publish/cancle`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "PublishDayWeekReport",
+            Router: `/publishDayWeekReport`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "SendMsg",
+            Router: `/sendMsg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "Upload",
+            Router: `/upload`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ResourceController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ResourceController"],
     beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ResourceController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ResourceController"],
         beego.ControllerComments{
         beego.ControllerComments{
             Method: "Upload",
             Method: "Upload",

+ 20 - 0
routers/router.go

@@ -12,6 +12,7 @@ import (
 	"hongze/hongze_ETA_mobile_api/controllers/data_manage"
 	"hongze/hongze_ETA_mobile_api/controllers/data_manage"
 	"hongze/hongze_ETA_mobile_api/controllers/data_manage/correlation"
 	"hongze/hongze_ETA_mobile_api/controllers/data_manage/correlation"
 	"hongze/hongze_ETA_mobile_api/controllers/data_manage/future_good"
 	"hongze/hongze_ETA_mobile_api/controllers/data_manage/future_good"
+	"hongze/hongze_ETA_mobile_api/controllers/english_report"
 
 
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web/filter/cors"
 	"github.com/beego/beego/v2/server/web/filter/cors"
@@ -91,6 +92,25 @@ func init() {
 				&correlation.CorrelationChartInfoController{},
 				&correlation.CorrelationChartInfoController{},
 			),
 			),
 		),
 		),
+		web.NSNamespace("/classify",
+			web.NSInclude(
+				&controllers.ClassifyController{},
+			),
+		),
+		web.NSNamespace("/report",
+			web.NSInclude(
+				&controllers.ReportController{},
+			),
+			web.NSInclude(
+				&controllers.ReportUploadCommonController{},
+			),
+		),
+		web.NSNamespace("/english_report",
+			web.NSInclude(
+				&english_report.EnglishReportController{},
+				&english_report.EnglishReportEmailController{},
+			),
+		),
 	)
 	)
 	web.AddNamespace(ns)
 	web.AddNamespace(ns)
 }
 }

+ 143 - 0
services/aliyun_email.go

@@ -0,0 +1,143 @@
+package services
+
+import (
+	"encoding/json"
+	openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
+	dm "github.com/alibabacloud-go/dm-20151123/v2/client"
+	util "github.com/alibabacloud-go/tea-utils/v2/service"
+	"github.com/alibabacloud-go/tea/tea"
+	"time"
+)
+
+var (
+	AliyunEmailAccountName       = "ficcemail@hzmail.hzinsights.com"
+	AliyunEmailAccessKeyId       = "LTAIFMZYQhS2BTvW"
+	AliyunEmailAccessKeySecret   = "12kk1ptCHoGWedhBnKRVW5hRJzq9Fq"
+	AliyunEmailReplyAddress      = "ficcemail@hzinsights.com"
+	AliyunEmailReplyAddressAlias = "弘则研究"
+)
+
+// AliyunEmail 阿里云邮件
+type AliyunEmail struct {
+	Client *dm.Client
+}
+
+// NewClient
+func (em *AliyunEmail) NewClient() (err error) {
+	config := &openapi.Config{
+		AccessKeyId:     tea.String(AliyunEmailAccessKeyId),
+		AccessKeySecret: tea.String(AliyunEmailAccessKeySecret),
+	}
+	// 访问的域名
+	config.Endpoint = tea.String("dm.aliyuncs.com")
+	_result, _err := dm.NewClient(config)
+	em.Client = _result
+	err = _err
+	return
+}
+
+// AliyunEmailResult 邮件推送响应体
+type AliyunEmailResult struct {
+	Code        string `description:"错误码"`
+	StatusCode  int    `description:"状态码"`
+	Message     string `description:"响应信息"`
+	Data        string `description:"请求体"`
+	Description string `description:"描述"`
+	EnvId       string `description:"请求成功-事件ID"`
+	RequestId   string `description:"请求成功-请求ID"`
+}
+
+// AliyunEmailResultData 邮件推送响应内容
+type AliyunEmailResultData struct {
+	Code       string `description:"错误码"`
+	HostId     string `description:""`
+	Message    string `description:"响应信息"`
+	Recommend  string `description:""`
+	RequestId  string `description:"请求ID"`
+	StatusCode int    `description:"状态码"`
+}
+
+// SendEmail 邮件推送
+func (em *AliyunEmail) SendEmail(item *EnglishReportSendEmailRequest) (ok bool, result string, err error) {
+	if em.Client == nil {
+		if e := em.NewClient(); e != nil {
+			err = e
+			return
+		}
+	}
+	singleSendMailRequest := &dm.SingleSendMailRequest{}
+	singleSendMailRequest.SetAccountName(AliyunEmailAccountName)
+	singleSendMailRequest.SetAddressType(1)
+	singleSendMailRequest.SetReplyToAddress(false)
+	singleSendMailRequest.SetSubject(item.Subject)
+	singleSendMailRequest.SetToAddress(item.Email)
+	singleSendMailRequest.SetHtmlBody(item.HtmlBody)
+	singleSendMailRequest.SetFromAlias(item.FromAlias)
+
+	runtime := &util.RuntimeOptions{}
+	tryErr := func() error {
+		res, e := em.Client.SingleSendMailWithOptions(singleSendMailRequest, runtime)
+		if e != nil {
+			return e
+		}
+		// 请求成功
+		if tea.Int32Value(res.StatusCode) == 200 {
+			ok = true
+		}
+		resByte, e := json.Marshal(res.Body)
+		if e != nil {
+			return e
+		}
+		result = string(resByte)
+		return nil
+	}()
+
+	if tryErr != nil {
+		var e = &tea.SDKError{}
+		if t, ok := tryErr.(*tea.SDKError); ok {
+			e = t
+		} else {
+			e.Message = tea.String(tryErr.Error())
+		}
+		err = e
+		errByte, _ := json.Marshal(err)
+		result = string(errByte)
+	}
+	return
+}
+
+// BatchSendEmail 批量推送邮件
+func (em *AliyunEmail) BatchSendEmail(list []*EnglishReportSendEmailRequest) (results []*EnglishReportSendEmailResult, err error) {
+	results = make([]*EnglishReportSendEmailResult, 0)
+	if len(list) == 0 {
+		return
+	}
+	if e := em.NewClient(); e != nil {
+		err = e
+		return
+	}
+
+	// sendSingleMail接口有QPS100的限制, 保险起见每秒只请求50次接口
+	max := 50
+	c := 0
+	for i := range list {
+		if c >= max {
+			time.Sleep(time.Second)
+			c = 0
+		}
+		c += 1
+
+		dataByte, _ := json.Marshal(list[i])
+		ok, result, _ := em.SendEmail(list[i])
+		results = append(results, &EnglishReportSendEmailResult{
+			ReportId:   list[i].ReportId,
+			EmailId:    list[i].EmailId,
+			Email:      list[i].Email,
+			Ok:         ok,
+			SendData:   string(dataByte),
+			ResultData: result,
+			Source:     1,
+		})
+	}
+	return
+}

+ 150 - 0
services/elastic.go

@@ -0,0 +1,150 @@
+package services
+
+import (
+	"context"
+	"fmt"
+	"github.com/olivere/elastic/v7"
+	"hongze/hongze_ETA_mobile_api/models"
+	"hongze/hongze_ETA_mobile_api/services/alarm_msg"
+	"strings"
+)
+
+func NewClient() (client *elastic.Client, err error) {
+	client, err = elastic.NewClient(
+		elastic.SetURL(ES_URL),
+		elastic.SetBasicAuth(ES_USERNAME, ES_PASSWORD),
+		elastic.SetSniff(false))
+	return
+}
+
+// EsAddOrEditReport 新增编辑es报告
+func EsAddOrEditReport(indexName, docId string, item *models.ElasticReportDetail) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("EsAddOrEditReport Err:", err.Error())
+		}
+	}()
+	client, err := NewClient()
+	if err != nil {
+		return
+	}
+	// docId为报告ID+章节ID
+	searchById, err := client.Get().Index(indexName).Id(docId).Do(context.Background())
+	if err != nil && !strings.Contains(err.Error(), "404") {
+		fmt.Println("Get Err" + err.Error())
+		return
+	}
+	if searchById != nil && searchById.Found {
+		resp, err := client.Update().Index(indexName).Id(docId).Doc(map[string]interface{}{
+			"ReportId":           item.ReportId,
+			"ReportChapterId":    item.ReportChapterId,
+			"Title":              item.Title,
+			"Abstract":           item.Abstract,
+			"BodyContent":        item.BodyContent,
+			"PublishTime":        item.PublishTime,
+			"PublishState":       item.PublishState,
+			"Author":             item.Author,
+			"ClassifyIdFirst":    item.ClassifyIdFirst,
+			"ClassifyNameFirst":  item.ClassifyNameFirst,
+			"ClassifyIdSecond":   item.ClassifyIdSecond,
+			"ClassifyNameSecond": item.ClassifyNameSecond,
+			"Categories":         item.Categories,
+			"StageStr":           item.StageStr,
+		}).Do(context.Background())
+		if err != nil {
+			return err
+		}
+		//fmt.Println(resp.Status, resp.Result)
+		if resp.Status == 0 {
+			fmt.Println("修改成功" + docId)
+			err = nil
+		} else {
+			fmt.Println("EditData", resp.Status, resp.Result)
+		}
+	} else {
+		resp, err := client.Index().Index(indexName).Id(docId).BodyJson(item).Do(context.Background())
+		if err != nil {
+			fmt.Println("新增失败:", err.Error())
+			return err
+		}
+		if resp.Status == 0 && resp.Result == "created" {
+			fmt.Println("新增成功" + docId)
+			return nil
+		} else {
+			fmt.Println("AddData", resp.Status, resp.Result)
+		}
+	}
+	return
+}
+
+// EsAddOrEditEnglishReport 新增编辑es英文报告
+func EsAddOrEditEnglishReport(indexName, docId string, item *models.ElasticEnglishReportDetail) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("EsAddOrEditEnglishReport Err:", err.Error())
+			go alarm_msg.SendAlarmMsg("新增编辑es英文报告 EsAddOrEditEnglishReport,Err:"+err.Error(), 3)
+		}
+	}()
+	client, err := NewClient()
+	if err != nil {
+		return
+	}
+	// docId为报告ID
+	searchById, err := client.Get().Index(indexName).Id(docId).Do(context.Background())
+	if err != nil {
+		if strings.Contains(err.Error(), "404") {
+			err = nil
+		} else {
+			fmt.Println("Get Err" + err.Error())
+			return
+		}
+	}
+	if searchById != nil && searchById.Found {
+		resp, e := client.Update().Index(indexName).Id(docId).Doc(map[string]interface{}{
+			"Id":                 item.Id,
+			"ReportId":           item.ReportId,
+			"VideoId":            item.VideoId,
+			"Title":              item.Title,
+			"Abstract":           item.Abstract,
+			"BodyContent":        item.BodyContent,
+			"PublishTime":        item.PublishTime,
+			"PublishState":       item.PublishState,
+			"Author":             item.Author,
+			"ClassifyIdFirst":    item.ClassifyIdFirst,
+			"ClassifyNameFirst":  item.ClassifyNameFirst,
+			"ClassifyIdSecond":   item.ClassifyIdSecond,
+			"ClassifyNameSecond": item.ClassifyNameSecond,
+			"CreateTime":         item.CreateTime,
+			"Overview":           item.Overview,
+			"ReportCode":         item.ReportCode,
+			"Frequency":          item.Frequency,
+			"StageStr":           item.StageStr,
+			"ContentSub":         item.ContentSub,
+		}).Do(context.Background())
+		if e != nil {
+			err = e
+			return
+		}
+		//fmt.Println(resp.Status, resp.Result)
+		if resp.Status == 0 {
+			fmt.Println("修改成功" + docId)
+			err = nil
+		} else {
+			fmt.Println("EditData", resp.Status, resp.Result)
+		}
+	} else {
+		resp, e := client.Index().Index(indexName).Id(docId).BodyJson(item).Do(context.Background())
+		if e != nil {
+			err = e
+			fmt.Println("新增失败:", err.Error())
+			return
+		}
+		if resp.Status == 0 && resp.Result == "created" {
+			fmt.Println("新增成功" + docId)
+			return
+		} else {
+			fmt.Println("AddData", resp.Status, resp.Result)
+		}
+	}
+	return
+}

+ 69 - 0
services/elasticsearch.go

@@ -0,0 +1,69 @@
+package services
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/olivere/elastic/v7"
+)
+
+const (
+	ES_URL      = "http://es-cn-nif227b580019rgw6.public.elasticsearch.aliyuncs.com:9200" //<1>
+	ES_USERNAME = "elastic"                                                               //<2>
+	ES_PASSWORD = "hongze@2021"                                                           //<3>
+	//Grafana pwd-> 20521bb9
+	//Grafana username-> emon
+)
+
+type tracelog struct{}
+
+// 实现输出
+func (tracelog) Printf(format string, v ...interface{}) {
+	fmt.Printf(format, v...)
+}
+
+func RemoveDuplicatesAndEmpty(a []string) (ret []string) {
+	a_len := len(a)
+	for i := 0; i < a_len; i++ {
+		if (i > 0 && a[i-1] == a[i]) || len(a[i]) == 0 {
+			continue
+		}
+		ret = append(ret, a[i])
+	}
+	return
+}
+
+func init21123() {
+	fmt.Println("start")
+	client, err := elastic.NewClient(elastic.SetURL(ES_URL), elastic.SetBasicAuth(ES_USERNAME, ES_PASSWORD), elastic.SetSniff(false))
+	if err != nil {
+		fmt.Println("err:", err)
+	}
+	fmt.Println(client)
+	keyWordStr := "医疗器械"
+	queryString := elastic.NewQueryStringQuery(`Title:(` + keyWordStr + `)`)
+
+	boolqueryJson, err := json.Marshal(queryString)
+	fmt.Println("err:", err)
+	fmt.Println("queryString ", string(boolqueryJson))
+
+	var esIndex = "cygx_article"
+	//boolquery.Must(elastic.NewMatchQuery("Title", keyWord), elastic.NewMatchQuery("BodyText", keyWord))
+	highlight := elastic.NewHighlight()
+	highlight = highlight.Fields(elastic.NewHighlighterField("Title"), elastic.NewHighlighterField("BodyText"))
+	highlight = highlight.PreTags("<font color='red'>").PostTags("</font>")
+	request := client.Search(esIndex).Highlight(highlight).Query(queryString)
+
+	requestJson, err := json.Marshal(request)
+	fmt.Println("err:", err)
+	fmt.Println("requestJson ", string(requestJson))
+
+	searchByMatch, err := request.Do(context.Background())
+
+	if searchByMatch.Hits != nil {
+
+	}
+
+	fmt.Println(searchByMatch)
+	fmt.Println("end")
+}

+ 537 - 0
services/english_report.go

@@ -0,0 +1,537 @@
+package services
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"hongze/hongze_ETA_mobile_api/models"
+	"hongze/hongze_ETA_mobile_api/models/system"
+	"hongze/hongze_ETA_mobile_api/services/alarm_msg"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"html"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// CreateNewEnglishReport 生成英文研报
+func CreateNewEnglishReport(req models.AddEnglishReportReq, adminInfo *system.Admin) (newReportId int64, reportCode string, err error) {
+	var contentSub string
+	if req.Content != "" {
+		content, e := FilterReportContentBr(req.Content)
+		if e != nil {
+			err = errors.New("内容去除前后空格失败, Err: " + e.Error())
+			return
+		}
+		req.Content = content
+
+		contentSub, e = GetReportContentSub(req.Content)
+		if e != nil {
+			go alarm_msg.SendAlarmMsg("ContentSub 失败,Err:"+e.Error(), 3)
+			err = errors.New("获取报告内容前几段失败, Err: " + e.Error())
+			return
+		}
+	}
+	maxStage, e := models.GetEnglishReportStage(req.ClassifyIdFirst, req.ClassifyIdSecond)
+	if e != nil {
+		err = errors.New("期数获取失败, Err: " + e.Error())
+		return
+	}
+
+	item := new(models.EnglishReport)
+	item.AddType = req.AddType
+	item.ClassifyIdFirst = req.ClassifyIdFirst
+	item.ClassifyNameFirst = req.ClassifyNameFirst
+	item.ClassifyIdSecond = req.ClassifyIdSecond
+	item.ClassifyNameSecond = req.ClassifyNameSecond
+	item.Title = req.Title
+	item.Abstract = req.Abstract
+	item.Author = req.Author
+	item.Frequency = req.Frequency
+	item.State = req.State
+	item.Content = html.EscapeString(req.Content)
+	item.Stage = maxStage + 1
+	item.ContentSub = html.EscapeString(contentSub)
+	item.CreateTime = req.CreateTime
+	item.ModifyTime = time.Now()
+	item.AdminId = adminInfo.AdminId
+	item.AdminRealName = adminInfo.RealName
+	newReportId, e = models.AddEnglishReport(item)
+	if e != nil {
+		err = errors.New("新增报告失败, Err: " + e.Error())
+		return
+	}
+	reportCode = utils.MD5(strconv.Itoa(int(newReportId)))
+	//修改唯一编码
+	{
+		go models.ModifyEnglishReportCode(newReportId, reportCode)
+	}
+	return
+}
+
+// IEnglishEmailSend 英文研报-邮件推送接口
+type IEnglishEmailSend interface {
+	NewClient() (err error)
+	SendEmail(item *EnglishReportSendEmailRequest) (ok bool, result string, err error)
+	BatchSendEmail(list []*EnglishReportSendEmailRequest) (results []*EnglishReportSendEmailResult, err error)
+}
+
+// EnglishReportSendEmailRequest 英文研报-推送邮件请求体
+type EnglishReportSendEmailRequest struct {
+	ReportId        int    `description:"英文报告ID"`
+	EmailId         int    `description:"邮箱ID"`
+	Email           string `description:"邮箱地址"`
+	Subject         string `description:"邮件主题"`
+	FromAlias       string `description:"发信人昵称"`
+	ReportTitle     string `description:"报告标题"`
+	ReportAbstract  string `description:"报告摘要"`
+	ReportContent   string `description:"报告内容"`
+	ReportShareLink string `description:"报告分享链接"`
+	ReportTime      string `description:"报告时间"`
+	HtmlBody        string `description:"模板内容主体"`
+}
+
+// EnglishReportSendEmailResult 英文研报-推送邮件响应体
+type EnglishReportSendEmailResult struct {
+	ReportId   int    `description:"英文报告ID"`
+	EmailId    int    `description:"邮箱ID"`
+	Email      string `description:"邮箱地址"`
+	Ok         bool   `description:"是否推送成功"`
+	SendData   string `description:"请求数据-JSON"`
+	ResultData string `description:"推送结果-JSON"`
+	Source     int    `description:"服务来源:1-阿里云;2-腾讯云"`
+}
+
+// BatchSendAliEnglishReportEmail 批量推送英文研报邮件
+func BatchSendAliEnglishReportEmail(list []*EnglishReportSendEmailRequest) (err error) {
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("阿里云群发英文研报邮件失败, Err: "+err.Error(), 3)
+		}
+	}()
+	if len(list) == 0 {
+		return
+	}
+	requestMap := make(map[int]*EnglishReportSendEmailRequest, 0)
+	for i := range list {
+		requestMap[list[i].EmailId] = list[i]
+	}
+
+	// 请求阿里云接口批量推送
+	aliEmail := new(AliyunEmail)
+	resultList, e := aliEmail.BatchSendEmail(list)
+	if e != nil {
+		err = e
+		return
+	}
+
+	// 返回的结果更新日志
+	resendList := make([]*EnglishReportSendEmailRequest, 0)
+	failLogIds := make([]int, 0)
+	updateCols := []string{"SendData", "Result", "SendStatus", "ErrMsg"}
+	for i := range resultList {
+		var cond string
+		var pars []interface{}
+		cond = ` AND is_deleted = 0 AND report_id = ? AND email_id = ? AND source = ? AND send_status = ?`
+		pars = append(pars, resultList[i].ReportId, resultList[i].EmailId, models.EnglishReportEmailLogSourceAli, models.EnglishReportEmailLogStatusIng)
+		l, e := models.GetEnglishReportEmailLog(cond, pars)
+		if e != nil {
+			continue
+		}
+		l.SendData = resultList[i].SendData
+		l.Result = resultList[i].ResultData
+		if resultList[i].Ok {
+			l.SendStatus = models.EnglishReportEmailLogStatusSuccess
+		} else {
+			l.SendStatus = models.EnglishReportEmailLogStatusFail
+			failLogIds = append(failLogIds, l.Id)
+			if requestMap[resultList[i].EmailId] != nil {
+				resendList = append(resendList, requestMap[resultList[i].EmailId])
+			}
+			// 取出错误信息
+			r := new(AliyunEmailResult)
+			if e = json.Unmarshal([]byte(resultList[i].ResultData), &r); e != nil {
+				continue
+			}
+			rd := new(AliyunEmailResultData)
+			res := strings.Replace(r.Data, `\`, ``, -1)
+			if e = json.Unmarshal([]byte(res), &rd); e != nil {
+				continue
+			}
+			l.ErrMsg = rd.Message
+		}
+		if e = l.Update(updateCols); e != nil {
+			continue
+		}
+	}
+
+	// 推送失败的重新腾讯云, 若腾讯云也失败将不再自动重推, 用户手动去重推
+	if len(resendList) > 0 && len(failLogIds) > 0 {
+		_ = ResendTencentEnglishReportEmail(resendList, failLogIds)
+	}
+	return
+}
+
+// ResendTencentEnglishReportEmail 腾讯云邮件重新推送
+func ResendTencentEnglishReportEmail(resendList []*EnglishReportSendEmailRequest, failLogIds []int) (err error) {
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("腾讯云重发英文研报邮件失败, Err: "+err.Error(), 3)
+		}
+	}()
+	if len(resendList) == 0 || len(failLogIds) == 0 {
+		return
+	}
+
+	// 标记原有日志为已删除
+	if len(failLogIds) > 0 {
+		if e := models.DeleteEnglishReportEmailLogByIds(failLogIds); e != nil {
+			err = errors.New("删除原邮件日志失败, Err: " + e.Error())
+			return
+		}
+	}
+
+	// 写入新的日志
+	nowTime := time.Now().Local()
+	logData := make([]*models.EnglishReportEmailLog, 0)
+	for i := range resendList {
+		sendByte, e := json.Marshal(resendList[i])
+		if e != nil {
+			err = errors.New("sendByte json.Marshal Err, Err: " + e.Error())
+			return
+		}
+
+		logData = append(logData, &models.EnglishReportEmailLog{
+			ReportId:   resendList[i].ReportId,
+			EmailId:    resendList[i].EmailId,
+			Email:      resendList[i].Email,
+			SendData:   string(sendByte),
+			Source:     models.EnglishReportEmailLogSourceTencent,
+			SendStatus: models.EnglishReportEmailLogStatusIng,
+			CreateTime: nowTime,
+		})
+	}
+	emailLog := new(models.EnglishReportEmailLog)
+	if e := emailLog.InsertMulti(logData); e != nil {
+		err = errors.New("批量写入群发邮件日志失败, Err: " + e.Error())
+		return
+	}
+
+	// 请求腾讯云
+	tecentEmail := new(TencentEmail)
+	resultList, e := tecentEmail.BatchSendEmail(resendList)
+	if e != nil {
+		err = e
+		return
+	}
+	updateCols := []string{"SendData", "Result", "SendStatus", "ErrMsg"}
+	for i := range resultList {
+		var cond string
+		var pars []interface{}
+		cond = ` AND is_deleted = 0 AND report_id = ? AND email_id = ? AND source = ? AND send_status = ?`
+		pars = append(pars, resultList[i].ReportId, resultList[i].EmailId, models.EnglishReportEmailLogSourceTencent, models.EnglishReportEmailLogStatusIng)
+		l, e := models.GetEnglishReportEmailLog(cond, pars)
+		if e != nil {
+			continue
+		}
+		l.SendData = resultList[i].SendData
+		l.Result = resultList[i].ResultData
+		if resultList[i].Ok {
+			l.SendStatus = models.EnglishReportEmailLogStatusSuccess
+		} else {
+			l.SendStatus = models.EnglishReportEmailLogStatusFail
+			r := new(TencentEmailResult)
+			if e = json.Unmarshal([]byte(resultList[i].ResultData), &r); e != nil {
+				continue
+			}
+			l.ErrMsg = r.Message
+		}
+		if e = l.Update(updateCols); e != nil {
+			continue
+		}
+	}
+	return
+}
+
+// UpdateEnglishReportEs 更新英文报告/章节Es
+func UpdateEnglishReportEs(reportId int, publishState int) (err error) {
+	if reportId <= 0 {
+		return
+	}
+	reportInfo, err := models.GetEnglishReportById(reportId)
+	if err != nil {
+		return
+	}
+	// 新增报告ES
+	esReport := &models.ElasticEnglishReportDetail{
+		Id:                 strconv.Itoa(reportInfo.Id),
+		ReportId:           reportInfo.Id,
+		Title:              reportInfo.Title,
+		Abstract:           reportInfo.Abstract,
+		BodyContent:        utils.TrimHtml(html.UnescapeString(reportInfo.Content)),
+		PublishTime:        reportInfo.PublishTime,
+		CreateTime:         reportInfo.CreateTime,
+		ReportCode:         reportInfo.ReportCode,
+		PublishState:       publishState,
+		Author:             reportInfo.Author,
+		Frequency:          reportInfo.Frequency,
+		ClassifyIdFirst:    reportInfo.ClassifyIdFirst,
+		ClassifyNameFirst:  reportInfo.ClassifyNameFirst,
+		ClassifyIdSecond:   reportInfo.ClassifyIdSecond,
+		ClassifyNameSecond: reportInfo.ClassifyNameSecond,
+		StageStr:           strconv.Itoa(reportInfo.Stage),
+		Overview:           utils.TrimHtml(html.UnescapeString(reportInfo.Overview)),
+		ContentSub:         utils.TrimHtml(html.UnescapeString(reportInfo.ContentSub)),
+	}
+	docId := fmt.Sprintf("%d", reportInfo.Id)
+	if err = EsAddOrEditEnglishReport(utils.EsEnglishReportIndexName, docId, esReport); err != nil {
+		return
+	}
+	return
+}
+
+// UpdateEnglishVideoEs 更新英文线上路演Es
+func UpdateEnglishVideoEs(videoId int, publishState int) (err error) {
+	if videoId <= 0 {
+		return
+	}
+	videoInfo, err := models.GetEnglishVideoById(videoId)
+	if err != nil {
+		return
+	}
+	// 新增报告ES
+	esReport := &models.ElasticEnglishReportDetail{
+		Id:                 "v" + strconv.Itoa(videoInfo.Id),
+		VideoId:            videoInfo.Id,
+		Title:              videoInfo.Title,
+		Abstract:           videoInfo.Abstract,
+		BodyContent:        "",
+		PublishTime:        videoInfo.PublishTime,
+		CreateTime:         videoInfo.CreateTime,
+		ReportCode:         videoInfo.VideoCode,
+		PublishState:       publishState,
+		Author:             videoInfo.Author,
+		Frequency:          "",
+		ClassifyIdFirst:    videoInfo.ClassifyIdFirst,
+		ClassifyNameFirst:  videoInfo.ClassifyNameFirst,
+		ClassifyIdSecond:   videoInfo.ClassifyIdSecond,
+		ClassifyNameSecond: videoInfo.ClassifyNameSecond,
+		StageStr:           "",
+		Overview:           utils.TrimHtml(html.UnescapeString(videoInfo.Overview)),
+		ContentSub:         "",
+	}
+	docId := fmt.Sprintf("v%d", videoInfo.Id)
+	if err = EsAddOrEditEnglishReport(utils.EsEnglishReportIndexName, docId, esReport); err != nil {
+		return
+	}
+	return
+}
+
+func UpdateEnglishReportClassifyId(oldItem, newItem, newParent *models.EnglishClassify, classifyId int) (err error) {
+	//一级分类改为二级分类
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("英文报告分类改名-同步更新报告表字段及权限表关键词失败3, Err:"+err.Error(), 3)
+		}
+	}()
+	//如果二级分类变更为一级分类
+	//如果二级分类更换了父级分类,则更新报告中的父级分类ID
+	//如果一级分类更换了名称,则更新所有报告中的一级分类名称
+	//如果二级分类更换了名称,则更新所有报告中的二级分类名称
+	if oldItem.ParentId != newItem.ParentId {
+		parentClassifyName := ""
+		parentId := 0
+
+		//二级分类改为一级分类, 或者二级分类的一级分类有变更
+		if newItem.ParentId > 0 {
+			parentClassifyName = newParent.ClassifyName
+			parentId = newParent.Id
+		}
+
+		// 更新报告表分类字段
+		var condition string
+		var pars []interface{}
+
+		condition += ` AND classify_id_second = ? `
+		pars = append(pars, classifyId)
+		list, e := models.GetEnglishReportByCondition(condition, pars)
+		if e != nil {
+			err = e
+			return
+		}
+		if len(list) > 0 {
+			//二级分类改为一级分类
+			if oldItem.ParentId > 0 && newItem.ParentId == 0 {
+				//查询该二级分类下是否存在关联报告,如果存在则不允许变更
+				err = fmt.Errorf("该分类有关联的报告,不允许变更为一级分类")
+				return
+			}
+		}
+		var idSlice []string
+		for _, report := range list {
+			idSlice = append(idSlice, strconv.Itoa(report.Id))
+		}
+		ids := strings.Join(idSlice, ",")
+		if ids != "" {
+			if err = models.UpdateEnglishReportByClassifyId(parentClassifyName, newItem.ClassifyName, parentId, oldItem.Id, ids); err != nil {
+				return
+			}
+		}
+	} else if oldItem.ClassifyName != newItem.ClassifyName {
+		if oldItem.ParentId > 0 {
+			//只对二级分类名称作了修改
+			// 更新报告表分类字段
+			if err = models.UpdateEnglishReportSecondClassifyNameByClassifyId(classifyId, newItem.ClassifyName); err != nil {
+				return
+			}
+		} else if oldItem.ParentId == 0 {
+			//只对一级分类名称作了修改
+			// 更新报告表分类字段
+			if err = models.UpdateEnglishReportFirstClassifyNameByClassifyId(classifyId, newItem.ClassifyName); err != nil {
+				return
+			}
+		}
+	}
+	return
+}
+
+func UpdateEnglishVideoClassifyId(oldItem, newItem, newParent *models.EnglishClassify, classifyId int) (err error) {
+	//一级分类改为二级分类
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("英文报告分类改名-同步更新报告表字段及权限表关键词失败3, Err:"+err.Error(), 3)
+		}
+	}()
+	//如果二级分类变更为一级分类
+	//如果二级分类更换了父级分类,则更新报告中的父级分类ID
+	//如果一级分类更换了名称,则更新所有报告中的一级分类名称
+	//如果二级分类更换了名称,则更新所有报告中的二级分类名称
+	if oldItem.ParentId != newItem.ParentId {
+		parentClassifyName := ""
+		parentId := 0
+		//二级分类改为一级分类, 或者二级分类的一级分类有变更
+		if newItem.ParentId > 0 {
+			parentClassifyName = newParent.ClassifyName
+			parentId = newParent.Id
+		}
+
+		// 更新报告表分类字段
+		var condition string
+		var pars []interface{}
+
+		condition += ` AND classify_id_second = ? `
+		pars = append(pars, classifyId)
+		list, e := models.GetEnglishVideoByCondition(condition, pars)
+		if e != nil {
+			err = e
+			return
+		}
+		if len(list) > 0 {
+			//二级分类改为一级分类
+			if oldItem.ParentId > 0 && newItem.ParentId == 0 {
+				//查询该二级分类下是否存在关联报告,如果存在则不允许变更
+				err = fmt.Errorf("该分类有关联的视频,不允许变更为一级分类")
+				return
+			}
+		}
+		var idSlice []string
+		for _, report := range list {
+			idSlice = append(idSlice, strconv.Itoa(report.Id))
+		}
+		ids := strings.Join(idSlice, ",")
+		if ids != "" {
+			if err = models.UpdateEnglishVideoByClassifyId(parentClassifyName, newItem.ClassifyName, parentId, oldItem.Id, ids); err != nil {
+				return
+			}
+		}
+	} else if oldItem.ClassifyName != newItem.ClassifyName {
+		if oldItem.ParentId > 0 {
+			//只对二级分类名称作了修改
+			// 更新报告表分类字段
+			if err = models.UpdateEnglishVideoSecondClassifyNameByClassifyId(classifyId, newItem.ClassifyName); err != nil {
+				return
+			}
+		} else if oldItem.ParentId == 0 {
+			//只对一级分类名称作了修改
+			// 更新报告表分类字段
+			if err = models.UpdateEnglishVideoFirstClassifyNameByClassifyId(classifyId, newItem.ClassifyName); err != nil {
+				return
+			}
+		}
+	}
+	return
+}
+
+// UpdateAllPublishedEnglishReportToEs 更新所有已发布的报告ES
+func UpdateAllPublishedEnglishReportToEs() (err error) {
+	// 获取所有已发布的报告
+	var condition string
+	var pars []interface{}
+	condition = ` AND state = 2 `
+	reportList, err := models.GetEnglishReportByCondition(condition, pars)
+	count := 0
+	failCount := 0
+	for i := 0; i < len(reportList); i++ {
+		if err = UpdateEnglishReportEs(reportList[i].Id, 2); err != nil {
+			fmt.Printf("更新失败, report_id: %d, Err: %s\n", reportList[i].Id, err.Error())
+			failCount += 1
+		} else {
+			count += 1
+		}
+	}
+	fmt.Printf("报告总数:%d, 更新成功数: %d, 更新失败数: %d", len(reportList), count, failCount)
+
+	return
+}
+
+// UpdateEnReportEditMark 更新英文研报当前更新状态
+// status 枚举值 1:编辑中,0:完成编辑, 2:只做查询
+func UpdateEnReportEditMark(reportId, nowUserId, status int, nowUserName string) (ret models.MarkReportResp, err error) {
+	//更新标记key
+	key := fmt.Sprint(`crm:enReport:edit:`, reportId)
+	opUserId, e := utils.Rc.RedisInt(key)
+	var opUser models.MarkReportItem
+	if e != nil {
+		opUserInfoStr, tErr := utils.Rc.RedisString(key)
+		if tErr == nil {
+			tErr = json.Unmarshal([]byte(opUserInfoStr), &opUser)
+			if tErr == nil {
+				opUserId = opUser.AdminId
+			}
+		}
+	}
+	if opUserId > 0 && opUserId != nowUserId {
+		editor := opUser.Editor
+		if editor == "" {
+			//查询账号的用户姓名
+			otherInfo, e := system.GetSysAdminById(opUserId)
+			if e != nil {
+				err = fmt.Errorf("查询其他编辑者信息失败")
+				return
+			}
+			editor = otherInfo.RealName
+		}
+
+		ret.Status = 1
+		ret.Msg = fmt.Sprintf("当前%s正在编辑报告", editor)
+		ret.Editor = editor
+		return
+	}
+	if status == 1 {
+		nowUser := &models.MarkReportItem{AdminId: nowUserId, Editor: nowUserName}
+		bt, e := json.Marshal(nowUser)
+		if e != nil {
+			err = fmt.Errorf("格式化编辑者信息失败")
+			return
+		}
+		if opUserId > 0 {
+			utils.Rc.Do("SETEX", key, int64(180), string(bt)) //3分钟缓存
+		} else {
+			utils.Rc.SetNX(key, string(bt), time.Second*60*3) //3分钟缓存
+		}
+	} else if status == 0 {
+		//清除编辑缓存
+		_ = utils.Rc.Delete(key)
+	}
+	return
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 535 - 0
services/report.go


+ 182 - 0
services/tencent_yun.go

@@ -0,0 +1,182 @@
+package services
+
+import (
+	"encoding/json"
+	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
+	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
+	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
+	ses "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses/v20201002"
+	"time"
+)
+
+const (
+	TencentSDKSecretId  = "AKIDa4WvJar361NZXkogKoHgtyxvkVDqKclq" // 腾讯云主账号SecretId
+	TencentSDKSecretKey = "61sdMzVvMejjZFj2KTQ9tgR4adYMzR1a"     // 腾讯云主账号SecretKey
+
+	TencentEmailFromEmailAddress = "email@txhzmail.hzinsights.com" // 腾讯云邮件发信地址
+	TencentEmailTemplateID       = 54181                           // 腾讯云邮件模板ID
+)
+
+// TencentEmail 腾讯云邮件
+type TencentEmail struct {
+	Client *ses.Client
+}
+
+// NewClient
+func (te *TencentEmail) NewClient() (err error) {
+	// 实例化一个认证对象,入参需要传入腾讯云账户secretId,secretKey,此处还需注意密钥对的保密
+	// 密钥可前往https://console.cloud.tencent.com/cam/capi网站进行获取
+	credential := common.NewCredential(
+		TencentSDKSecretId,
+		TencentSDKSecretKey,
+	)
+	// 实例化一个client选项,可选的,没有特殊需求可以跳过
+	cpf := profile.NewClientProfile()
+	cpf.HttpProfile.Endpoint = "ses.tencentcloudapi.com"
+	// 实例化要请求产品的client对象,clientProfile是可选的
+	client, _err := ses.NewClient(credential, "ap-hongkong", cpf)
+	te.Client = client
+	err = _err
+	return
+}
+
+// TencentEmailTemplateData 邮箱模板替换数据
+type TencentEmailTemplateData struct {
+	REPORT_TITLE      string `description:"报告标题"`
+	REPORT_ABSTRACT   string `description:"报告摘要"`
+	REPORT_CONTENT    string `description:"报告内容"`
+	REPORT_SHARE_LINK string `description:"报告分享链接"`
+	REPORT_TIME       string `description:"报告时间"`
+}
+
+// TencentEmailResult 邮件推送响应体
+type TencentEmailResult struct {
+	Code      string `description:"错误码"`
+	Message   string `description:"响应信息"`
+	RequestId string `description:"请求成功-请求ID"`
+}
+
+// SendEmail 推送模板邮件
+func (te *TencentEmail) SendEmail(req *EnglishReportSendEmailRequest) (ok bool, result string, err error) {
+	if te.Client == nil {
+		if e := te.NewClient(); e != nil {
+			err = e
+			return
+		}
+	}
+	// 实例化一个请求对象,每个接口都会对应一个request对象
+	request := ses.NewSendEmailRequest()
+
+	// 模板数据替换
+	tmpData := &TencentEmailTemplateData{
+		REPORT_TITLE:      req.ReportTitle,
+		REPORT_ABSTRACT:   req.ReportAbstract,
+		REPORT_CONTENT:    req.ReportContent,
+		REPORT_SHARE_LINK: req.ReportShareLink,
+		REPORT_TIME:       req.ReportTime,
+	}
+	tmpDataByte, e := json.Marshal(tmpData)
+	if e != nil {
+		err = e
+		return
+	}
+	tmpDataStr := string(tmpDataByte)
+	request.Template = &ses.Template{
+		TemplateID:   common.Uint64Ptr(TencentEmailTemplateID),
+		TemplateData: common.StringPtr(tmpDataStr),
+	}
+	request.FromEmailAddress = common.StringPtr(TencentEmailFromEmailAddress)
+	request.Destination = common.StringPtrs([]string{req.Email})
+	request.Subject = common.StringPtr(req.Subject)
+
+	// 返回的resp是一个SendEmailResponse的实例,与请求对象对应
+	response, e := te.Client.SendEmail(request)
+	if _, o := e.(*errors.TencentCloudSDKError); o {
+		errByte, _ := json.Marshal(e)
+		result = string(errByte)
+		return
+	}
+	if e != nil {
+		err = e
+		return
+	}
+	ok = true
+	result = response.ToJsonString()
+	return
+}
+
+// BatchSendEmail 批量推送邮件
+func (te *TencentEmail) BatchSendEmail(list []*EnglishReportSendEmailRequest) (results []*EnglishReportSendEmailResult, err error) {
+	results = make([]*EnglishReportSendEmailResult, 0)
+	if len(list) == 0 {
+		return
+	}
+	if e := te.NewClient(); e != nil {
+		err = e
+		return
+	}
+
+	// SendEmail接口有QPS20的限制, 保险起见每秒只请求10次接口
+	max := 10
+	c := 0
+	for i := range list {
+		if c >= max {
+			time.Sleep(time.Second)
+			c = 0
+		}
+		c += 1
+
+		dataByte, _ := json.Marshal(list[i])
+		ok, result, _ := te.SendEmail(list[i])
+		results = append(results, &EnglishReportSendEmailResult{
+			ReportId:   list[i].ReportId,
+			EmailId:    list[i].EmailId,
+			Email:      list[i].Email,
+			Ok:         ok,
+			SendData:   string(dataByte),
+			ResultData: result,
+			Source:     1,
+		})
+	}
+	return
+}
+
+// TencentEmailCallBack 回调请求体
+type TencentEmailCallBack struct {
+	Event      string `description:"事件类型"`
+	Email      string `description:"收件人地址"`
+	Link       string `description:"用户点击的邮件中的链接 URL,仅在event=click时生效"`
+	BulkId     string `description:"SendEmail 接口返回的 MessageId"`
+	Timestamp  int    `description:"事件产生的时间戳"`
+	Reason     string `description:"邮件递送失败的原因"`
+	BounceType string `description:"如果收件人邮件服务商拒信,拒信类型,取值:soft_bounce | hard_bounce,仅在event=bounce的时候生效"`
+	Username   string `description:"腾讯云账号对应的 appId"`
+	From       string `description:"发信地址(不带发件人别名)"`
+	FromDomain string `description:"发信域名"`
+	TemplateId int    `description:"模板 Id"`
+}
+
+//func init() {
+//	fmt.Println("start email init")
+//
+//	te := new(TencentEmail)
+//	te.NewClient()
+//	req := new(EnglishReportSendEmailRequest)
+//	req.Subject = "这是一封邮件的主题"
+//	req.Email = ""
+//	req.ReportTitle = "Report Hello World"
+//	req.ReportAbstract = "This is abstract"
+//	req.ReportContent = "This is content"
+//	req.ReportShareLink = "https://share.hzinsights.com/reportEn?code=a226e450e214f350856e2980b6e55ac9"
+//	req.ReportTime = "2022/11/23"
+//
+//	ok, result, e := te.SendEmail(req)
+//	fmt.Println(ok)
+//	if e != nil {
+//		fmt.Println("出错了: ", e)
+//	} else {
+//		fmt.Println("没出错: ", result)
+//	}
+//
+//	fmt.Println("end email init")
+//}

+ 192 - 0
services/video.go

@@ -2,11 +2,110 @@ package services
 
 
 import (
 import (
 	"bytes"
 	"bytes"
+	"encoding/base64"
 	"encoding/binary"
 	"encoding/binary"
+	"encoding/json"
+	"errors"
+	"github.com/PuerkitoBio/goquery"
+	"github.com/kgiannakakis/mp3duration/src/mp3duration"
+	"hongze/hongze_ETA_mobile_api/models"
+	"hongze/hongze_ETA_mobile_api/services/alarm_msg"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"html"
 	"io"
 	"io"
+	"io/ioutil"
+	"os"
+	"strings"
+	"time"
 	"unicode"
 	"unicode"
 )
 )
 
 
+func CreateVideo(report *models.ReportDetail) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("CreateVideo Err:%s", err.Error())
+			go alarm_msg.SendAlarmMsg("CreateVideo, Err:"+err.Error(), 3)
+			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+	}()
+	ct, err := time.Parse(utils.FormatDateTime, report.CreateTime)
+	createTime := ct.Format("0102")
+	videoName := report.Title + "(" + createTime + ")"
+	content := html.UnescapeString(report.Content)
+	content = strings.Replace(content, "Powered", "", -1)
+	content = strings.Replace(content, "by", "", -1)
+	content = strings.Replace(content, "Froala", "", -1)
+	content = strings.Replace(content, "Editor", "", -1)
+	doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
+	if err != nil {
+		return
+	}
+
+	param := new(models.XfSendParam)
+	param.Common.AppId = utils.XfAPPID
+	param.Business.Aue = "lame"
+	param.Business.Sfl = 1
+	param.Business.Auf = "audio/L16;rate=16000"
+	param.Business.Vcn = utils.XfVcn
+	param.Business.Speed = 50
+	param.Business.Volume = 100
+	param.Business.Pitch = 50
+	param.Business.Bgs = 0
+	param.Business.Tte = "UTF8"
+	param.Business.Reg = "2"
+	param.Business.Rdn = "0"
+	param.Data.Status = 2
+	videoContent := doc.Text()
+
+	saveName := utils.GetRandStringNoSpecialChar(16) + ".mp3"
+	savePath := "./" + saveName
+	//if utils.FileIsExist(savePath) {
+	//	os.Remove(savePath)
+	//}
+	contentArr := GetChineseCount(videoContent)
+	for _, v := range contentArr {
+		newText := v
+		param.Data.Text = base64.StdEncoding.EncodeToString([]byte(newText))
+		result, err := json.Marshal(param)
+		if err != nil {
+			return err
+		}
+		err = GetXfVideo(result, savePath)
+		if err != nil {
+			err = errors.New("GetXfVideo Err:" + err.Error())
+			utils.FileLog.Error("GetXfVideo err", err.Error())
+			return err
+		}
+		time.Sleep(5 * time.Second)
+	}
+	uploadUrl, err := UploadAudioAliyun(saveName, savePath)
+	if err != nil {
+		err = errors.New("UploadAudioAliyun Err:" + err.Error())
+		return
+	}
+
+	fileBody, err := ioutil.ReadFile(savePath)
+	videoSize := len(fileBody)
+	sizeFloat := (float64(videoSize) / float64(1024)) / float64(1024)
+	sizeStr := utils.SubFloatToFloatStr(sizeFloat, 2)
+
+	playSeconds, err := mp3duration.Calculate(savePath)
+	if playSeconds <= 0 {
+		playSeconds, err = utils.GetVideoPlaySeconds(savePath)
+		if err != nil {
+			err = errors.New("GetVideoPlaySeconds Err:" + err.Error())
+			return
+		}
+	}
+
+	if playSeconds > 0 {
+		if utils.FileIsExist(savePath) {
+			os.Remove(savePath)
+		}
+	}
+	err = models.ModifyReportVideo(report.Id, uploadUrl, videoName, sizeStr, playSeconds)
+	return
+}
 
 
 func GetChineseCount(str1 string) []string {
 func GetChineseCount(str1 string) []string {
 	fontArr := make([]string, 0)
 	fontArr := make([]string, 0)
@@ -87,3 +186,96 @@ func getFourccType(boxHeader BoxHeader) (fourccType string) {
 	return
 	return
 }
 }
 
 
+// CreateReportVideo 生成报告video
+func CreateReportVideo(reportTitle, reportContent, reportTime string) (uploadUrl, videoName, sizeStr string, playSeconds float64, err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("CreateReportVideo Err:%s", err.Error())
+			go alarm_msg.SendAlarmMsg("CreateReportVideo, reportTitle:"+reportTitle+", Err:"+err.Error(), 3)
+			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "CreateReportVideo, reportTitle:" + reportTitle +", Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+	}()
+	if reportContent == "" {
+		return
+	}
+	ct, err := time.Parse(utils.FormatDateTime, reportTime)
+	if err != nil {
+		return
+	}
+	createTime := ct.Format("0102")
+	videoName = reportTitle + "(" + createTime + ")"
+	content := html.UnescapeString(reportContent)
+	content = strings.Replace(content, "Powered", "", -1)
+	content = strings.Replace(content, "by", "", -1)
+	content = strings.Replace(content, "Froala", "", -1)
+	content = strings.Replace(content, "Editor", "", -1)
+	doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
+	if err != nil {
+		return
+	}
+
+	param := new(models.XfSendParam)
+	param.Common.AppId = utils.XfAPPID
+	param.Business.Aue = "lame"
+	param.Business.Sfl = 1
+	param.Business.Auf = "audio/L16;rate=16000"
+	param.Business.Vcn = utils.XfVcn
+	param.Business.Speed = 50
+	param.Business.Volume = 100
+	param.Business.Pitch = 50
+	param.Business.Bgs = 0
+	param.Business.Tte = "UTF8"
+	param.Business.Reg = "2"
+	param.Business.Rdn = "0"
+	param.Data.Status = 2
+	videoContent := doc.Text()
+
+	saveName := utils.GetRandStringNoSpecialChar(16) + ".mp3"
+	savePath := "./" + saveName
+	//if utils.FileIsExist(savePath) {
+	//	os.Remove(savePath)
+	//}
+	contentArr := GetChineseCount(videoContent)
+	for _, v := range contentArr {
+		newText := v
+		param.Data.Text = base64.StdEncoding.EncodeToString([]byte(newText))
+		result, tmpErr := json.Marshal(param)
+		if tmpErr != nil {
+			return
+		}
+		err = GetXfVideo(result, savePath)
+		if err != nil {
+			err = errors.New("GetXfVideo Err:" + err.Error())
+			utils.FileLog.Error("GetXfVideo err", err.Error())
+			return
+		}
+		time.Sleep(5 * time.Second)
+	}
+	uploadUrl, err = UploadAudioAliyun(saveName, savePath)
+	if err != nil {
+		err = errors.New("UploadAudioAliyun Err:" + err.Error())
+		return
+	}
+
+	fileBody, err := ioutil.ReadFile(savePath)
+	videoSize := len(fileBody)
+	sizeFloat := (float64(videoSize) / float64(1024)) / float64(1024)
+	sizeStr = utils.SubFloatToFloatStr(sizeFloat, 2)
+
+	playSeconds, err = mp3duration.Calculate(savePath)
+	if playSeconds <= 0 {
+		playSeconds, err = utils.GetVideoPlaySeconds(savePath)
+		if err != nil {
+			err = errors.New("GetVideoPlaySeconds Err:" + err.Error())
+			return
+		}
+	}
+
+	if playSeconds > 0 {
+		if utils.FileIsExist(savePath) {
+			os.Remove(savePath)
+		}
+	}
+
+	return
+}

+ 222 - 0
services/wechat_send_msg.go

@@ -0,0 +1,222 @@
+package services
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"hongze/hongze_ETA_mobile_api/models"
+	"hongze/hongze_ETA_mobile_api/services/alarm_msg"
+	"hongze/hongze_ETA_mobile_api/utils"
+	"io/ioutil"
+	"net/http"
+	"strconv"
+	"strings"
+)
+
+type SendTemplateResponse struct {
+	Errcode int    `json:"errcode"`
+	Errmsg  string `json:"errmsg"`
+	MsgID   int    `json:"msgid"`
+}
+
+type ClearQuotaResponse struct {
+	Errcode int    `json:"errcode"`
+	Errmsg  string `json:"errmsg"`
+}
+
+// SendMiniProgramReportWxMsg 推送报告微信模板消息-小程序链接
+func SendMiniProgramReportWxMsg(reportId int) (err error) {
+	var msg string
+	reportIdStr := strconv.Itoa(reportId)
+	defer func() {
+		if err != nil {
+			fmt.Println("msg:", msg)
+			utils.FileLog.Error(fmt.Sprintf("SendMiniProgramReportWxMsg, 发送报告模版消息失败, ReportId:%s, Err:%s", reportIdStr, err.Error()))
+			go alarm_msg.SendAlarmMsg("SendMiniProgramReportWxMsg发送报告模版消息失败;"+"ReportId:"+reportIdStr+",Err:"+err.Error()+";msg:"+msg, 3)
+			//go utils.SendEmail("SendMiniProgramReportWxMsg发送报告模版消息失败"+"【"+utils.APPNAME+"】"+"【"+utils.RunMode+"】"+time.Now().Format("2006-01-02 15:04:05"), "ReportId:"+reportIdStr+";"+msg+";Err:"+err.Error(), toUser)
+		}
+	}()
+	utils.FileLog.Info("%s", "services SendMsg")
+
+	report, err := models.GetReportById(reportId)
+	if err != nil {
+		msg = "GetReportInfo Err:" + err.Error()
+		return
+	}
+	if report == nil {
+		utils.FileLog.Info("报告信息不存在")
+		return
+	}
+	//if report.MsgIsSend == 1 {
+	//	return
+	//}
+	//accessToken, err := models.GetWxAccessToken()
+	//if err != nil {
+	//	msg = "GetWxAccessToken Err:" + err.Error()
+	//	return
+	//}
+	//if accessToken == "" {
+	//	msg = "accessToken is empty"
+	//	return
+	//}
+
+	var openIdArr []string
+	if report.ClassifyIdSecond <= 0 {
+		openIdArr, err = models.GetOpenIdArr()
+		if err != nil {
+			msg = "get GetOpenIdArr err:" + err.Error()
+			return
+		}
+	} else {
+		classify, err := models.GetClassifyById(report.ClassifyIdSecond)
+		if err != nil {
+			msg = "获取报告分类失败 err:" + err.Error()
+			return err
+		}
+		if classify.IsMassSend == 1 {
+			openIdArr, err = models.GetOpenIdArr()
+			if err != nil {
+				msg = "get GetOpenIdArr err:" + err.Error()
+				return err
+			}
+		} else {
+			openIdArr, err = models.GetOpenIdArrByClassifyNameSecond(report.ClassifyNameSecond)
+			if err != nil {
+				msg = "GetOpenIdArrByClassifyNameSecond err:" + err.Error()
+				return err
+			}
+		}
+	}
+
+	//sendUrl := "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken
+	//fmt.Println("send start")
+	//utils.FileLog.Info("send start")
+	//sendMap := make(map[string]interface{})
+	//sendData := make(map[string]interface{})
+
+	title := fmt.Sprintf("弘则%s", report.ClassifyNameFirst)
+	if CheckTwoWeekOrMonthReport(report.ClassifyIdFirst, report.ClassifyNameFirst) {
+		title = fmt.Sprintf("弘则%s", report.ClassifyNameSecond)
+	}
+	//redirectUrl := utils.TemplateRedirectUrl + strconv.Itoa(reportId)
+	first := fmt.Sprintf("Hi,最新一期%s已上线,欢迎查看", report.ClassifyNameFirst)
+	keyword1 := title
+	keyword2 := report.Title
+	keyword3 := report.PublishTime
+	keyword4 := report.Abstract
+
+	//sendData["first"] = map[string]interface{}{"value": first, "color": "#173177"}
+	//sendData["keyword1"] = map[string]interface{}{"value": keyword1, "color": "#173177"}
+	//sendData["keyword2"] = map[string]interface{}{"value": keyword2, "color": "#173177"}
+	//sendData["keyword3"] = map[string]interface{}{"value": keyword3, "color": "#173177"}
+	//sendData["keyword4"] = map[string]interface{}{"value": keyword4, "color": "#173177"}
+	//
+	//sendMap["template_id"] = utils.TemplateIdByProduct
+	////sendMap["url"] = redirectUrl
+	//sendMap["data"] = sendData
+
+	var wxAppPath string
+	if report.ChapterType == utils.REPORT_TYPE_WEEK {
+		wxAppPath = fmt.Sprintf("pages-report/chapterList?reportId=%s", reportIdStr)
+	} else {
+		wxAppPath = fmt.Sprintf("pages-report/reportDetail?reportId=%s", reportIdStr)
+	}
+
+	//if wxAppPath != "" {
+	//	sendMap["miniprogram"] = map[string]interface{}{"appid": utils.WxYbAppId, "pagepath": wxAppPath}
+	//}
+	//err = sendTemplateMsg(sendUrl, sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_REPORT)
+
+	sendInfo := new(SendWxTemplate)
+	sendInfo.First = first
+	sendInfo.Keyword1 = keyword1
+	sendInfo.Keyword2 = keyword2
+	sendInfo.Keyword3 = keyword3
+	sendInfo.Keyword4 = keyword4
+	sendInfo.TemplateId = utils.TemplateIdByProduct
+	sendInfo.RedirectUrl = wxAppPath
+	sendInfo.Resource = wxAppPath
+	sendInfo.SendType = utils.TEMPLATE_MSG_REPORT
+	sendInfo.OpenIdArr = openIdArr
+	sendInfo.RedirectTarget = 1
+	err = SendTemplateMsg(sendInfo)
+
+	return
+}
+
+// CheckTwoWeekOrMonthReport 校验推送报告是否为双周报或者月报
+func CheckTwoWeekOrMonthReport(classifyId int, classifyName string) (ok bool) {
+	if utils.RunMode == "debug" {
+		miniStrArr := []string{
+			"双周报", "月报",
+		}
+		if utils.InArrayByStr(miniStrArr, classifyName) {
+			ok = true
+		}
+	} else {
+		// 此处生产环境用ID主要是担心分类改了名字...
+		IdArr := []int{
+			96, 112,
+		}
+		if utils.InArrayByInt(IdArr, classifyId) {
+			ok = true
+		}
+	}
+	return
+}
+
+type SendWxTemplate struct {
+	WxAppId        string   `description:"公众号appId"`
+	First          string   `description:"模板消息first字段"`
+	Keyword1       string   `description:"模板消息keyword1字段"`
+	Keyword2       string   `description:"模板消息keyword2字段"`
+	Keyword3       string   `description:"模板消息keyword3字段"`
+	Keyword4       string   `description:"模板消息keyword4字段"`
+	Keyword5       string   `description:"模板消息keyword5字段"`
+	Remark         string   `description:"模板消息remark字段"`
+	TemplateId     string   `description:"模板id"`
+	RedirectUrl    string   `description:"跳转地址"`
+	RedirectTarget int      `description:"小程序跳转目标:1:弘则研报小程序,2:随手办公小程序"`
+	Resource       string   `description:"资源唯一标识"`
+	SendType       int      `description:"发送的消息类型:1:报告,2:指标更新提醒,3:审批通知,4:销售领取客户通知,5:活动取消通知,6活动更改时间通知,7:关注的作者发布报告通知,8:发送日报(周报、双周报、月报)模板消息,9:活动预约/报名时间通知"`
+	OpenIdArr      []string `description:"消息接收者openid"`
+}
+
+// 推送模板消息
+func SendTemplateMsg(sendInfo *SendWxTemplate) (err error) {
+	postData, err := json.Marshal(sendInfo)
+	if err != nil {
+		alarm_msg.SendAlarmMsg("SendTemplateMsg json.Marshal Err:"+err.Error(), 1)
+		return err
+	}
+	body := ioutil.NopCloser(strings.NewReader(string(postData)))
+	client := &http.Client{}
+	req, err := http.NewRequest("POST", utils.SendWxTemplateMsgUrl, body)
+	if err != nil {
+		alarm_msg.SendAlarmMsg("SendTemplateMsg http.NewRequest Err:"+err.Error(), 1)
+		return err
+	}
+	contentType := "application/json;charset=utf-8"
+	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("Authorization", utils.SendTemplateMsgAuthorization)
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println("http client.Do Err:" + err.Error())
+		return err
+	}
+	defer resp.Body.Close()
+	b, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return err
+	}
+	result := new(models.BaseResponse)
+	err = json.Unmarshal(b, &result)
+	if err != nil {
+		return err
+	}
+	if result.Ret != 200 {
+		err = errors.New(string(b))
+		return err
+	}
+	return
+}

+ 85 - 0
services/wx_app.go

@@ -0,0 +1,85 @@
+package services
+
+import (
+	"fmt"
+	wechat "github.com/silenceper/wechat/v2"
+	"github.com/silenceper/wechat/v2/cache"
+	"github.com/silenceper/wechat/v2/miniprogram"
+	"github.com/silenceper/wechat/v2/miniprogram/auth"
+	"github.com/silenceper/wechat/v2/miniprogram/config"
+	"github.com/silenceper/wechat/v2/miniprogram/encryptor"
+	"github.com/silenceper/wechat/v2/miniprogram/qrcode"
+	"hongze/hongze_ETA_mobile_api/utils"
+)
+
+// 微信小程序配置信息
+var (
+	WxId        string //微信原始ID
+	WxAppId     string
+	WxAppSecret string
+	WxPlatform  int    //用户来源,需要入库,用来保存该用户来自哪个平台,默认是:1
+	EnvVersion  string // 小程序版本, release-正式版; trial-体验版; develop-开发版
+)
+
+func init() {
+	WxAppId = `wxb059c872d79b9967`
+	WxId = `gh_75abb562a946`
+	WxAppSecret = `1737c73e9f69a21de1a345b8f0800258`
+	WxPlatform = 6 //弘则研报来源
+}
+
+func GetWxApp() (miniprogram *miniprogram.MiniProgram) {
+	wc := wechat.NewWechat()
+	memory := cache.NewMemory()
+	//memory := cache.NewRedis(global.Redis)
+	cfg := &config.Config{
+		AppID:     WxAppId,
+		AppSecret: WxAppSecret,
+		Cache:     memory,
+	}
+
+	miniprogram = wc.GetMiniProgram(cfg)
+	return
+}
+
+// GetSession 获取用户详情
+func GetSession(code string) (userInfo auth.ResCode2Session, err error) {
+	wechatClient := GetWxApp()
+	authClient := wechatClient.GetAuth()
+	userInfo, err = authClient.Code2Session(code)
+	return
+}
+
+// GetSession 获取用户详情
+func GetUserInfo(code string) (userInfo auth.ResCode2Session, err error) {
+	wechatClient := GetWxApp()
+	authClient := wechatClient.GetAuth()
+	fmt.Println("code:", code)
+	userInfo, err = authClient.Code2Session(code)
+	return
+}
+
+// 获取解密信息 GetDecryptInfo
+func GetDecryptInfo(sessionKey, encryptedData, iv string) (decryptData *encryptor.PlainData, err error) {
+	wechatClient := GetWxApp()
+	encryptorClient := wechatClient.GetEncryptor()
+	decryptData, err = encryptorClient.Decrypt(sessionKey, encryptedData, iv)
+	return
+}
+
+// GetSunCode 获取太阳码
+func GetSunCode(page, scene string) (resp []byte, err error) {
+	// 此处因初始化顺序问题不放在init中
+	env := "trial"
+	if utils.RunMode == "release" {
+		env = "release"
+	}
+	codePars := qrcode.QRCoder{
+		Page:       page,
+		Scene:      scene,
+		EnvVersion: env,
+	}
+	wechatClient := GetWxApp()
+	qr := wechatClient.GetQRCode()
+	return qr.GetWXACodeUnlimit(codePars)
+}

+ 108 - 0
services/xfyun.go

@@ -0,0 +1,108 @@
+package services
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/crypt"
+	"hongze/hongze_ETA_mobile_api/models"
+	"net/url"
+	"strings"
+	"time"
+
+	"github.com/gorilla/websocket"
+	"hongze/hongze_ETA_mobile_api/utils"
+)
+
+// 科大讯飞,语音合成
+func GetXfVideo(body []byte, savePath string) (err error) {
+	path, err := assembleAuthUrl()
+	if err != nil {
+		return
+	}
+	conn, _, err := websocket.DefaultDialer.Dial(path, nil)
+	if err != nil {
+		return
+	}
+	defer conn.Close()
+
+	err = conn.WriteMessage(websocket.TextMessage, body)
+	if err != nil {
+		return
+	}
+
+	for {
+		_, message, err := conn.ReadMessage()
+		if err != nil {
+			fmt.Println("ReadMessage Err:" + err.Error())
+			return err
+		}
+		item := new(models.XfReciveResult)
+		err = json.Unmarshal(message, &item)
+		if err != nil {
+			fmt.Println("json.Unmarshal Err:" + err.Error())
+			return err
+		}
+		if item.Code != 0 {
+			goto readWebSocketFail
+		}
+		if item.Code == 0 && item.Data != nil {
+			if item.Data.Status == 1 {
+				audio := item.Data.Audio
+				err = utils.SaveBase64ToFileBySeek(audio, savePath)
+				if err != nil {
+					fmt.Println("文件保存失败", err.Error())
+					goto readWebSocketFail
+				}
+			} else {
+				audio := item.Data.Audio
+				err = utils.SaveBase64ToFileBySeek(audio, savePath)
+				if err != nil {
+					fmt.Println("文件保存失败", err.Error())
+					goto webSocketClose
+					//return
+				}
+				fmt.Println("goto close")
+				goto webSocketClose
+			}
+		}
+	}
+readWebSocketFail:
+	conn.Close()
+	fmt.Println("goto readWebSocketFail")
+webSocketClose:
+	conn.Close()
+	fmt.Println("goto webSocketClose")
+	return nil
+}
+
+// @hosturl :  like  wss://iat-api.xfyun.cn/v2/iat
+// @apikey : apiKey
+// @apiSecret : apiSecret
+func assembleAuthUrl() (callUrl string, err error) {
+	ul, err := url.Parse(utils.XfHostUrl)
+	if err != nil {
+		return
+	}
+	//签名时间
+	date := time.Now().UTC().Format(time.RFC1123)
+	//参与签名的字段 host ,date, request-line
+	signString := []string{"host: " + ul.Host, "date: " + date, "GET " + ul.Path + " HTTP/1.1"}
+	//拼接签名字符串
+	sign := strings.Join(signString, "\n")
+	fmt.Println("sign:", sign)
+	//签名结果
+	sha := crypt.HmacSha256EncryptToBase64([]byte(sign), []byte(utils.XfAPISecret))
+	//构建请求参数 此时不需要urlencoding
+	authUrl := fmt.Sprintf("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", utils.XfAPIKey,
+		"hmac-sha256", "host date request-line", sha)
+	//将请求参数使用base64编码
+	authorization := base64.StdEncoding.EncodeToString([]byte(authUrl))
+	v := url.Values{}
+	v.Add("host", ul.Host)
+	v.Add("date", date)
+	v.Add("authorization", authorization)
+	//将编码后的字符串url encode后添加到url后面
+	callUrl = utils.XfHostUrl + "?" + v.Encode()
+	return
+}

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.