ppt.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. package services
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "eta_gn/eta_api/models"
  6. "eta_gn/eta_api/models/system"
  7. "eta_gn/eta_api/services/alarm_msg"
  8. "eta_gn/eta_api/services/ppt2img"
  9. "eta_gn/eta_api/utils"
  10. "fmt"
  11. "sort"
  12. "time"
  13. )
  14. const (
  15. ElementsTypeText = "text"
  16. ElementsTypeImage = "image"
  17. ElementsTypeChart = "chart"
  18. ElementsTypeSheet = "sheet"
  19. )
  20. type PPTContent struct {
  21. //Id int `json:"id" description:"此处因目录改版类型有int也有string且并没有使用到该字段所以注释掉"`
  22. Key int `json:"key"`
  23. ModelId int `json:"modelId"`
  24. Title string `json:"title"`
  25. Elements []PPTContentElements `json:"elements"`
  26. }
  27. type PPTContentElements struct {
  28. Type string `json:"type"`
  29. Position int `json:"position"`
  30. Content string `json:"content"`
  31. RichContent string `json:"richContent"`
  32. ChartId string `json:"chartId"`
  33. SheetId string `json:"sheetId"`
  34. SheetHeight string `json:"sheetHeight"`
  35. Src string `json:"src"`
  36. }
  37. // SavePPTReport 保存PPT报告
  38. func SavePPTReport(pptId, classifyId int, title string, adminInfo *system.Admin) (reportId int, reportCode, errMsg string, err error) {
  39. defer func() {
  40. if err != nil {
  41. utils.FileLog.Info("%s", err.Error())
  42. go alarm_msg.SendAlarmMsg("PPT转报告失败, SavePPTReport Msg: "+errMsg+", Err: "+err.Error(), 3)
  43. }
  44. }()
  45. if pptId == 0 {
  46. errMsg = "参数有误"
  47. err = errors.New("参数有误")
  48. return
  49. }
  50. item, e := models.GetPptV2ById(pptId)
  51. if e != nil {
  52. errMsg = "获取PPT失败"
  53. err = errors.New("获取PPT失败, Err: " + e.Error())
  54. return
  55. }
  56. // PPT内容转HTML
  57. htm, e := pptContent2Html(item.Content, false)
  58. if e != nil {
  59. errMsg = "转换失败"
  60. err = e
  61. return
  62. }
  63. // 2023-02-21 PPT可多次转为报告, 不做关联
  64. if classifyId == 0 {
  65. errMsg = "请选择报告类型"
  66. err = errors.New("请选择报告类型")
  67. return
  68. }
  69. if title == "" {
  70. errMsg = "标题不能为空"
  71. err = errors.New("标题不能为空")
  72. return
  73. }
  74. // 获取分类及父级分类
  75. classifyList, e := models.GetAllClassify()
  76. if e != nil {
  77. errMsg = "转换失败"
  78. err = errors.New("获取分类列表失败, Err: " + e.Error())
  79. return
  80. }
  81. classifyMap := make(map[int]*models.Classify, 0)
  82. for _, v := range classifyList {
  83. classifyMap[v.Id] = v
  84. }
  85. classifyIdFirst := 0
  86. classifyIdSecond := 0
  87. classifyIdThird := 0
  88. classifyNameFirst := ""
  89. classifyNameSecond := ""
  90. classifyNameThird := ""
  91. // 最小单元分类,第二级别的分类 ,最大的分类
  92. var baseClassify, twoClassify, threeClassify *models.Classify
  93. var hasTwo, hasThird bool
  94. baseClassify, ok := classifyMap[classifyId]
  95. if !ok {
  96. errMsg = "分类异常"
  97. err = errors.New("获取分类失败 ")
  98. return
  99. }
  100. twoClassify, hasTwo = classifyMap[baseClassify.ParentId]
  101. if hasTwo {
  102. threeClassify, hasThird = classifyMap[twoClassify.ParentId]
  103. }
  104. if hasThird { // 如果确实是有三级分类
  105. classifyIdFirst = threeClassify.Id
  106. classifyNameFirst = threeClassify.ClassifyName
  107. classifyIdSecond = twoClassify.Id
  108. classifyNameSecond = twoClassify.ClassifyName
  109. classifyIdThird = baseClassify.Id
  110. classifyNameThird = baseClassify.ClassifyName
  111. } else if hasTwo {
  112. classifyIdFirst = twoClassify.Id
  113. classifyNameFirst = twoClassify.ClassifyName
  114. classifyIdSecond = baseClassify.Id
  115. classifyNameSecond = baseClassify.ClassifyName
  116. } else {
  117. classifyIdFirst = baseClassify.Id
  118. classifyNameFirst = baseClassify.ClassifyName
  119. }
  120. // 新增报告
  121. nowTime := time.Now().Local()
  122. reportReq := &models.AddReq{
  123. AddType: 1,
  124. ClassifyIdFirst: classifyIdFirst,
  125. ClassifyNameFirst: classifyNameFirst,
  126. ClassifyIdSecond: classifyIdSecond,
  127. ClassifyNameSecond: classifyNameSecond,
  128. ClassifyIdThird: classifyIdThird,
  129. ClassifyNameThird: classifyNameThird,
  130. Title: title,
  131. Abstract: "",
  132. Author: "FICC团队",
  133. Frequency: utils.ReportFrequencyDefault,
  134. State: 1,
  135. Content: htm,
  136. CreateTime: nowTime.Format(utils.FormatDateTime),
  137. ReportVersion: 2,
  138. CollaborateType: 1, // 协作方式,1:个人,2:多人协作。默认:1
  139. ReportLayout: 1, // 报告布局,1:常规布局,2:智能布局。默认:1
  140. IsPublicPublish: 1, // 是否公开发布,1:是,2:否
  141. }
  142. // 如果PPT是公开的,则报告也公开发布
  143. if item.IsShare == 1 {
  144. reportReq.IsPublicPublish = 1
  145. }
  146. newReportId, newCode, _, e := CreateNewReport(*reportReq, adminInfo)
  147. if e != nil {
  148. errMsg = "转换失败"
  149. err = errors.New("新增报告失败, Err: " + e.Error())
  150. return
  151. }
  152. reportId = int(newReportId)
  153. reportCode = newCode
  154. // 更新报告中的ppt图片
  155. go saveReportPptImg(pptId, reportId, item.PptxUrl)
  156. return
  157. }
  158. // PPT2内容转HTML
  159. func pptContent2Html(content string, isEnglish bool) (htm string, err error) {
  160. contents := make([]PPTContent, 0)
  161. if e := json.Unmarshal([]byte(content), &contents); e != nil {
  162. err = errors.New("PPT内容转换失败")
  163. return
  164. }
  165. pageLen := len(contents)
  166. htmlContent := ``
  167. // iframe图表/表格域名
  168. // 获取基础配置, 若未配置则直接返回
  169. // 获取配置好的短信模版
  170. smsCond := ` AND conf_key = ? `
  171. smsPars := make([]interface{}, 0)
  172. smsPars = append(smsPars, "ChartViewUrl")
  173. conf := new(models.BusinessConf)
  174. conf, e := conf.GetItemByCondition(smsCond, smsPars)
  175. if e != nil {
  176. if utils.IsErrNoRow(e) {
  177. err = fmt.Errorf("请先配置公共图库的地址")
  178. return
  179. }
  180. err = fmt.Errorf("获取聚合短信配置信息失败, Err: %s", e.Error())
  181. return
  182. }
  183. if conf.ConfVal == "" {
  184. err = fmt.Errorf("请先配置公共图库的地址")
  185. return
  186. }
  187. chartRoot := conf.ConfVal
  188. if pageLen > 0 {
  189. htmlPrefix := `<p style="text-align: left; margin-top: 10px; font-size: 16px;">`
  190. htmlSuffix := `</p>`
  191. htmlBr := `<br>`
  192. for i := 0; i < pageLen; i++ {
  193. // 每页标题加粗居中
  194. title := contents[i].Title
  195. if title != "" {
  196. htmlContent += `<p style="font-size: 16px; text-align: left;"><strong>`
  197. htmlContent += title
  198. htmlContent += `</strong></p>`
  199. }
  200. ele := contents[i].Elements
  201. // 每页元素按照Position升序排序
  202. sort.Slice(ele, func(k, j int) bool {
  203. return ele[k].Position < ele[j].Position
  204. })
  205. for _, v := range ele {
  206. // 根据不同的Type拼接不同的内容
  207. htmlContent += htmlPrefix
  208. switch v.Type {
  209. case ElementsTypeText:
  210. htmlContent += v.RichContent
  211. case ElementsTypeImage:
  212. htmlContent += fmt.Sprint(`<img src="`, v.Src, `" class="fr-fic fr-dib fr-draggable">`)
  213. case ElementsTypeChart:
  214. if isEnglish {
  215. // 英文研报图表src多加一个fromPage=en, 表格暂时没有区分
  216. htmlContent += fmt.Sprintf(`<iframe src="%s/chartshow?code=%s&fromPage=en" width="100%%" height="350" style="border-width:0px; min-height:350px;"></iframe>`, chartRoot, v.ChartId)
  217. break
  218. }
  219. htmlContent += fmt.Sprintf(`<iframe src="%s/chartshow?code=%s" width="100%%" height="350" style="border-width:0px; min-height:350px;"></iframe>`, chartRoot, v.ChartId)
  220. case ElementsTypeSheet:
  221. htmlContent += fmt.Sprintf(`<iframe src="%s/sheetshow?code=%s" class="iframe%s" width="100%%" height="%s" style="border-width:0px;"></iframe>`, chartRoot, v.SheetId, v.SheetId, v.SheetHeight)
  222. }
  223. htmlContent += htmlSuffix
  224. }
  225. // 每页中间插入一个换行符, 最后一页不插入
  226. currentPage := i + 1
  227. if currentPage != pageLen {
  228. htmlContent += htmlPrefix + htmlBr + htmlSuffix
  229. }
  230. }
  231. }
  232. htm = htmlContent
  233. return
  234. }
  235. // ResetPPTReport 重置PPT关联的报告(如删除关联的报告后)
  236. func ResetPPTReport(reportId int, isEnglish bool) (err error) {
  237. defer func() {
  238. if err != nil {
  239. utils.FileLog.Info("%s", err.Error())
  240. go alarm_msg.SendAlarmMsg("重置PPT关联报告失败, ResetPPTReport Err: "+err.Error(), 3)
  241. }
  242. }()
  243. // 中文报告
  244. item, e := models.GetPptV2ByReportId(reportId)
  245. if e != nil && !utils.IsErrNoRow(e) {
  246. err = errors.New("获取PPT失败, Err: " + e.Error())
  247. return
  248. }
  249. if item != nil {
  250. updateCols := []string{"ReportId", "ReportCode"}
  251. item.ReportId = 0
  252. item.ReportCode = ""
  253. if e = item.Update(updateCols); e != nil {
  254. err = errors.New("更新PPT关联报告失败, Err: " + e.Error())
  255. return
  256. }
  257. }
  258. return
  259. }
  260. // saveReportPptImg ppt转报告后,需要再次将ppt转图片
  261. func saveReportPptImg(pptId, reportId int, pptUrl string) {
  262. var err error
  263. defer func() {
  264. if err != nil {
  265. utils.FileLog.Info(fmt.Sprintf("将ppt转图片失败, saveReportPptImg Err:%s", err.Error()))
  266. go alarm_msg.SendAlarmMsg("将ppt转图片失败, saveReportPptImg Err: "+err.Error(), 3)
  267. }
  268. }()
  269. // 更新报告内容
  270. report, e := models.GetReportByReportId(reportId)
  271. if e != nil && !utils.IsErrNoRow(e) {
  272. err = errors.New("获取报告失败, Err: " + e.Error())
  273. return
  274. }
  275. if report == nil {
  276. return
  277. }
  278. // 获取ppt转图片的结果
  279. list, err := ppt2img.Ppt2Img(pptUrl)
  280. if err != nil {
  281. return
  282. }
  283. reportPptImgList := make([]*models.ReportPptImg, 0)
  284. for _, v := range list {
  285. reportPptImg := &models.ReportPptImg{
  286. //ReportPptImgId: 0,
  287. PptId: pptId,
  288. ReportId: reportId,
  289. ReportChapterId: 0,
  290. ImgUrl: v,
  291. CreateTime: time.Now(),
  292. }
  293. reportPptImgList = append(reportPptImgList, reportPptImg)
  294. }
  295. //批量添加Ppt转报告的图片记录
  296. err = models.AddAndEditMultiReportPptImg(pptId, reportPptImgList)
  297. return
  298. }
  299. // UpdatePptEditing 更新PPT编辑状态
  300. func UpdatePptEditing(pptId, status, userId int, userName string, isEn bool) (ret models.PPTEditingCache, err error) {
  301. if pptId <= 0 {
  302. return
  303. }
  304. cacheKey := ""
  305. if isEn {
  306. cacheKey = fmt.Sprint(utils.CACHE_EN_PPT_EDITING, pptId)
  307. } else {
  308. cacheKey = fmt.Sprint(utils.CACHE_PPT_EDITING, pptId)
  309. }
  310. // 完成编辑
  311. if status == 2 {
  312. _ = utils.Rc.Delete(cacheKey)
  313. return
  314. }
  315. // 读取缓存中的结果
  316. var editor models.PPTEditingCache
  317. strCache, _ := utils.Rc.RedisString(cacheKey)
  318. fmt.Println(strCache)
  319. if strCache != "" {
  320. e := json.Unmarshal([]byte(strCache), &editor)
  321. if e != nil {
  322. err = fmt.Errorf("解析缓存内容失败: %s", e.Error())
  323. return
  324. }
  325. }
  326. // 标记编辑中
  327. if status == 1 {
  328. // 无人编辑, 写入缓存
  329. if !editor.IsEditing {
  330. ret.IsEditing = true
  331. ret.AdminId = userId
  332. ret.Editor = userName
  333. ret.Tips = fmt.Sprintf("当前%s正在编辑PPT", userName)
  334. b, _ := json.Marshal(ret)
  335. utils.Rc.SetNX(cacheKey, string(b), 3*time.Minute)
  336. return
  337. }
  338. // 有人编辑
  339. if editor.IsEditing {
  340. // 编辑用户与当前用户不一致, 返回编辑用户, 一致则更新缓存
  341. if userId == editor.AdminId {
  342. b, _ := json.Marshal(editor)
  343. utils.Rc.Do("SETEX", cacheKey, int64(180), string(b))
  344. }
  345. ret = editor
  346. return
  347. }
  348. } else {
  349. // 默认查询
  350. ret = editor
  351. }
  352. return
  353. }