ppt.go 13 KB

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