Quellcode durchsuchen

英文研报、推送

hsun vor 1 Jahr
Ursprung
Commit
c903f732cd

+ 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 = "取消发布成功"
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 487 - 487
controllers/report.go


+ 25 - 0
go.mod

@@ -4,6 +4,10 @@ go 1.17
 
 require (
 	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/beego/v2 v2.0.7
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
@@ -15,17 +19,32 @@ require (
 	github.com/olivere/elastic/v7 v7.0.30
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.3.1
+	github.com/silenceper/wechat/v2 v2.1.4
 	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/yidane/formula v0.0.0-20210902154546-0782e1736717
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 )
 
 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/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/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/go-redis/redis/v8 v8.11.5 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
@@ -47,6 +66,12 @@ require (
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.3 // 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/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
 	golang.org/x/crypto v0.8.0 // indirect

+ 87 - 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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 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/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/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/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/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
 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/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-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.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
 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/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/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/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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 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/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/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/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/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.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 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-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=
@@ -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/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.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 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/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.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 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/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.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 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-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-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 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.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -303,6 +346,7 @@ 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.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-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 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.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -315,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.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 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.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ=
@@ -366,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.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-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 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-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@@ -490,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.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
 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/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/go.mod h1:LjhyrWzOLJ9l1azMoNr9iCvfNrHEREqvJHzSLQcD0/o=
 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/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=
@@ -508,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.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 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.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.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.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/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=
@@ -620,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/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/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.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 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.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/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/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=
@@ -635,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/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.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.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@@ -649,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/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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -665,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/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
 github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/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-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@@ -688,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/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.30/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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -749,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-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-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-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-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-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 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/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -916,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-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-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-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -942,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-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-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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1016,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-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-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-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -1147,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/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/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.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
 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-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 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/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

+ 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()
+
+	// 英文报告
+	initEnglishReport()
 }
 
 // initSystem 系统表 数据表
@@ -87,7 +90,19 @@ func initSystem() {
 // initReport 报告相关 数据表
 func initReport() {
 	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),   //指标与多图配置的关系表
 	)
 }
+
+// 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
+}

+ 102 - 120
routers/commentsRouter.go

@@ -304,6 +304,105 @@ func init() {
             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: "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.ControllerComments{
             Method: "Download",
@@ -934,15 +1033,6 @@ func init() {
             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: "Author",
-            Router: `/author`,
-            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: "ClassifyIdDetail",
@@ -1015,15 +1105,6 @@ func init() {
             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: "GetDayReportTickerList",
-            Router: `/getDayReportTickerList`,
-            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",
@@ -1033,42 +1114,6 @@ func init() {
             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: "GetDayWeekReportChapterTypeList",
-            Router: `/getDayWeekReportChapterTypeList`,
-            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: "GetDayWeekReportPauseTime",
-            Router: `/getDayWeekReportPauseTime`,
-            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: "GetDayWeekReportVideoList",
-            Router: `/getDayWeekReportVideoList`,
-            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: "GetLastDayWeekReportChapter",
-            Router: `/getLastDayWeekReportChapter`,
-            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",
@@ -1089,9 +1134,9 @@ func init() {
 
     beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "IsLastDayWeekReportChapter",
-            Router: `/isLastDayWeekReportChapter`,
-            AllowHTTPMethods: []string{"get"},
+            Method: "GetSunCode",
+            Router: `/getSunCode`,
+            AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
@@ -1141,24 +1186,6 @@ func init() {
             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: "PublishDayWeekReportChapter",
-            Router: `/publishDayWeekReportChapter`,
-            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: "SaveReportContent",
-            Router: `/saveReportContent`,
-            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",
@@ -1168,42 +1195,6 @@ func init() {
             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: "SendTemplateMsg",
-            Router: `/sendTemplateMsg`,
-            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: "SetDayWeekReportEnableRule",
-            Router: `/setDayWeekReportEnableUpdateRule`,
-            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: "SetDayWeekReportUpdateRule",
-            Router: `/setDayWeekReportUpdateRule`,
-            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: "ThsSendTemplateMsg",
-            Router: `/ths/sendTemplateMsg`,
-            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",
@@ -1213,15 +1204,6 @@ func init() {
             Filters: nil,
             Params: nil})
 
-    beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportUploadCommonController"] = append(beego.GlobalControllerRouter["hongze/hongze_ETA_mobile_api/controllers:ReportUploadCommonController"],
-        beego.ControllerComments{
-            Method: "UploadImg",
-            Router: `/uploadImg`,
-            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.ControllerComments{
             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/correlation"
 	"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/filter/cors"
@@ -91,6 +92,25 @@ func init() {
 				&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)
 }

+ 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
+}

+ 73 - 0
services/elastic.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"github.com/olivere/elastic/v7"
 	"hongze/hongze_ETA_mobile_api/models"
+	"hongze/hongze_ETA_mobile_api/services/alarm_msg"
 	"strings"
 )
 
@@ -75,3 +76,75 @@ func EsAddOrEditReport(indexName, docId string, item *models.ElasticReportDetail
 	}
 	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
+}

+ 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
+}

+ 64 - 63
services/report.go

@@ -11,6 +11,7 @@ import (
 	"hongze/hongze_ETA_mobile_api/services/alarm_msg"
 	"hongze/hongze_ETA_mobile_api/utils"
 	"html"
+	"os"
 	"regexp"
 	"strconv"
 	"strings"
@@ -465,69 +466,69 @@ func checkDayWeekChapterWrite(chapters []*models.ReportChapter, reportType strin
 }
 
 // PcCreateAndUploadSunCode 生成太阳码并上传OSS
-//func PcCreateAndUploadSunCode(scene, page string) (imgUrl string, err error) {
-//	if page == "" {
-//		err = errors.New("page不能为空")
-//		return
-//	}
-//
-//	// scene超过32位会生成失败,md5处理至32位
-//	sceneMD5 := "a=1"
-//	if scene != "" {
-//		sceneMD5 = utils.MD5(scene)
-//	}
-//	picByte, err := GetSunCode(page, sceneMD5)
-//	if err != nil {
-//		return
-//	}
-//	// 生成图片
-//	localPath := "./static/imgs"
-//	fileName := utils.GetRandStringNoSpecialChar(28) + ".png"
-//	fpath := fmt.Sprint(localPath, "/", fileName)
-//	f, err := os.Create(fpath)
-//	if err != nil {
-//		fmt.Println("11111")
-//		return
-//	}
-//	if _, err = f.Write(picByte); err != nil {
-//		return
-//	}
-//	defer func() {
-//		f.Close()
-//		os.Remove(fpath)
-//	}()
-//	// 上传OSS
-//	fileDir := "yb/suncode/"
-//	imgUrl, err = UploadAliyunToDir(fileName, fpath, "", fileDir)
-//	if err != nil {
-//		return
-//	}
-//
-//	if err != nil {
-//		return
-//	}
-//	// 记录参数
-//	if scene != "" {
-//		newSuncode := &models.YbPcSuncode{
-//			Scene:      scene,
-//			SceneMd5:   sceneMD5,
-//			CodePage:   page,
-//			SuncodeURL: imgUrl,
-//			CreateTime: time.Now(),
-//		}
-//		err = models.AddYbPcSunCode(newSuncode)
-//	}
-//	// 记录参数md5
-//	if scene != "" {
-//		newPars := &models.YbSuncodePars{
-//			Scene:      scene,
-//			SceneKey:   sceneMD5,
-//			CreateTime: time.Now(),
-//		}
-//		err = models.AddYbSuncodePars(newPars)
-//	}
-//	return
-//}
+func PcCreateAndUploadSunCode(scene, page string) (imgUrl string, err error) {
+	if page == "" {
+		err = errors.New("page不能为空")
+		return
+	}
+
+	// scene超过32位会生成失败,md5处理至32位
+	sceneMD5 := "a=1"
+	if scene != "" {
+		sceneMD5 = utils.MD5(scene)
+	}
+	picByte, err := GetSunCode(page, sceneMD5)
+	if err != nil {
+		return
+	}
+	// 生成图片
+	localPath := "./static/imgs"
+	fileName := utils.GetRandStringNoSpecialChar(28) + ".png"
+	fpath := fmt.Sprint(localPath, "/", fileName)
+	f, err := os.Create(fpath)
+	if err != nil {
+		fmt.Println("11111")
+		return
+	}
+	if _, err = f.Write(picByte); err != nil {
+		return
+	}
+	defer func() {
+		f.Close()
+		os.Remove(fpath)
+	}()
+	// 上传OSS
+	fileDir := "yb/suncode/"
+	imgUrl, err = UploadAliyunToDir(fileName, fpath, "", fileDir)
+	if err != nil {
+		return
+	}
+
+	if err != nil {
+		return
+	}
+	// 记录参数
+	if scene != "" {
+		newSuncode := &models.YbPcSuncode{
+			Scene:      scene,
+			SceneMd5:   sceneMD5,
+			CodePage:   page,
+			SuncodeURL: imgUrl,
+			CreateTime: time.Now(),
+		}
+		err = models.AddYbPcSunCode(newSuncode)
+	}
+	// 记录参数md5
+	if scene != "" {
+		newPars := &models.YbSuncodePars{
+			Scene:      scene,
+			SceneKey:   sceneMD5,
+			CreateTime: time.Now(),
+		}
+		err = models.AddYbSuncodePars(newPars)
+	}
+	return
+}
 
 // FilterReportContentBr 过滤报告正文前后换行符
 func FilterReportContentBr(content string) (res string, err error) {

+ 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")
+//}

+ 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)
+}

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