report.go 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187
  1. package services
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "eta/eta_api/models"
  6. "eta/eta_api/models/company"
  7. "eta/eta_api/models/report"
  8. "eta/eta_api/models/report_approve"
  9. "eta/eta_api/models/system"
  10. "eta/eta_api/services/alarm_msg"
  11. "eta/eta_api/services/public_api"
  12. "eta/eta_api/utils"
  13. "fmt"
  14. "github.com/PuerkitoBio/goquery"
  15. "html"
  16. "os"
  17. "regexp"
  18. "strconv"
  19. "strings"
  20. "time"
  21. )
  22. func GetReportContentSub(content string) (contentSub string, err error) {
  23. content = html.UnescapeString(content)
  24. doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
  25. if err != nil {
  26. fmt.Println("create doc err:", err.Error())
  27. return
  28. }
  29. n := 0
  30. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  31. if n >= 5 {
  32. return
  33. }
  34. n++
  35. phtml, err := s.Html()
  36. if err != nil {
  37. fmt.Println("get html err", err.Error())
  38. return
  39. }
  40. if s.Text() != "" || strings.Contains(phtml, "src") {
  41. contentSub = contentSub + "<p>" + phtml + "</p>"
  42. }
  43. })
  44. return
  45. }
  46. // PublishDayWeekReport 发布晨周报
  47. func PublishDayWeekReport(reportInfo *models.Report) (tips string, err error, errMsg string) {
  48. reportId := reportInfo.Id
  49. if reportInfo.State == 2 {
  50. return
  51. }
  52. // 获取所有章节列表
  53. chapters, err := models.GetChapterListByReportId(reportId)
  54. if err != nil {
  55. return
  56. }
  57. chapterLen := len(chapters)
  58. if chapterLen <= 0 {
  59. tips = "报告章节为空,不可发布"
  60. errMsg = tips
  61. err = errors.New(tips)
  62. return
  63. }
  64. reportChapterIdList := make([]int, 0)
  65. // 获取报告中的所有章节列表
  66. chapterList, err := models.GetChapterListByReportId(reportId)
  67. if err != nil {
  68. return
  69. }
  70. for _, chapter := range chapterList {
  71. if chapter.PublishState == 1 {
  72. tips = "还存在未发布的章节"
  73. errMsg = tips
  74. err = errors.New(tips)
  75. return
  76. }
  77. reportChapterIdList = append(reportChapterIdList, chapter.ReportChapterId)
  78. }
  79. // 根据审批开关及审批流判断当前报告状态
  80. state, e := CheckReportCurrState(report_approve.FlowReportTypeChinese, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, reportInfo.ClassifyIdThird, models.ReportOperatePublish)
  81. if e != nil {
  82. //errMsg = "操作失败"
  83. err = errors.New("校验报告当前状态失败, Err: " + e.Error())
  84. return
  85. }
  86. // 如果状态不是已发布,那么就重置状态
  87. if state != models.ReportStatePublished {
  88. // 从无审批切换为有审批, 状态重置
  89. if e = models.ResetReportById(reportId, state); e != nil {
  90. //errMsg = "操作失败"
  91. err = fmt.Errorf("重置报告状态失败, Err: %s, ReportId: %d", e.Error(), reportId)
  92. return
  93. }
  94. return
  95. }
  96. // 需发布整期
  97. updateCols := make([]string, 0)
  98. updateCols = append(updateCols, "Title", "State", "ModifyTime")
  99. // 发布后标题调整
  100. //title := report.Title
  101. //title = strings.ReplaceAll(title, "【弘则FICC晨报】", "")
  102. //title = strings.ReplaceAll(title, "【弘则FICC周报】", "")
  103. //if title == "" {
  104. // // 取第一个需发布章节的标题
  105. // firstId := publishIdArr[0]
  106. // firstTitle := ""
  107. // for i := 0; i < chapterLen; i++ {
  108. // if chapters[i].ReportChapterId == firstId {
  109. // firstTitle = chapters[i].Title
  110. // break
  111. // }
  112. // }
  113. // title = firstTitle
  114. //}
  115. //report.Title = title
  116. reportInfo.State = models.ReportStatePublished
  117. // 研报后台4.4 只在没有发布过时更新发布时间,其余均按模版消息发送时间当作发布时间
  118. if reportInfo.MsgIsSend == 0 || reportInfo.PublishTime.IsZero() {
  119. reportInfo.PublishTime = time.Now().Local()
  120. updateCols = append(updateCols, "PublishTime")
  121. }
  122. reportInfo.ModifyTime = time.Now().Local()
  123. if e := models.PublishReportAndChapter(reportInfo, true, updateCols); e != nil {
  124. err = errors.New("发布报告及章节失败")
  125. return
  126. }
  127. // 生成章节音频
  128. if len(reportChapterIdList) > 0 {
  129. go func() {
  130. _ = UpdateChaptersVideo(reportChapterIdList)
  131. }()
  132. }
  133. // 更新报告ES
  134. go func() {
  135. _ = UpdateReportEs(reportInfo.Id, 2)
  136. }()
  137. // 发布时备份内容
  138. go SaveReportLogs(reportInfo, chapters, reportInfo.AdminId, reportInfo.AdminRealName)
  139. return
  140. }
  141. // UpdateChaptersVideo 更新章节音频
  142. func UpdateChaptersVideo(ids []int) (err error) {
  143. defer func() {
  144. if err != nil {
  145. utils.FileLog.Error("UpdateChaptersVideo, chapterIds:%v, Err:%s", ids, err.Error())
  146. go alarm_msg.SendAlarmMsg(fmt.Sprintf("更新章节音频失败, 章节ID: %v; Err: "+err.Error(), ids), 3)
  147. }
  148. }()
  149. if len(ids) <= 0 {
  150. return
  151. }
  152. chapterList, err := models.GetChapterListByChapterIds(ids)
  153. if err != nil {
  154. return
  155. }
  156. // 生成video
  157. nowTime := time.Now()
  158. updateCols := make([]string, 0)
  159. updateCols = append(updateCols, "VideoUrl", "VideoName", "VideoSize", "VideoPlaySeconds")
  160. for i := 0; i < len(chapterList); i++ {
  161. item := chapterList[i]
  162. // 忽略已有音频的章节
  163. if item.VideoUrl != "" && item.VideoName != "" && item.VideoSize != "" && item.VideoPlaySeconds != "" {
  164. continue
  165. }
  166. videoUrl, videoName, videoSize, videoPlaySeconds, e := CreateReportVideo(item.Title, html.UnescapeString(item.Content), nowTime.Format(utils.FormatDateTime))
  167. if e != nil {
  168. err = e
  169. return
  170. }
  171. item.VideoUrl = videoUrl
  172. item.VideoName = videoName
  173. item.VideoSize = videoSize
  174. item.VideoPlaySeconds = fmt.Sprintf("%.2f", videoPlaySeconds)
  175. if e = item.UpdateChapter(updateCols); e != nil {
  176. err = e
  177. }
  178. }
  179. return
  180. }
  181. // PublishTodayDayReport 发布今日晨报
  182. func PublishTodayDayReport() (err error) {
  183. nowTime := time.Now()
  184. startTime := time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.Local)
  185. endTime := time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 23, 59, 59, 0, time.Local)
  186. todayReport, err := models.GetUnPublishDayReport(startTime, endTime)
  187. if err != nil {
  188. if err.Error() == utils.ErrNoRow() { //如果是找不到待发送的晨报,那么需要将err置空
  189. err = nil
  190. }
  191. return
  192. }
  193. if todayReport != nil {
  194. if _, tmpErr, _ := PublishDayWeekReport(todayReport); tmpErr != nil {
  195. err = tmpErr
  196. return
  197. }
  198. // 定时发布的晨报自动推送客群
  199. reportDetail, tmpErr := models.GetReportById(todayReport.Id)
  200. if tmpErr != nil {
  201. err = tmpErr
  202. return
  203. }
  204. // 推送模板消息
  205. if tmpErr = SendMiniProgramReportWxMsg(todayReport.Id); tmpErr != nil {
  206. err = tmpErr
  207. return
  208. }
  209. if tmpErr = models.ModifyReportThsMsgIsSend(reportDetail); tmpErr != nil {
  210. err = tmpErr
  211. return
  212. }
  213. }
  214. return
  215. }
  216. func initp2_838 (){
  217. var condition string
  218. var pars []interface{}
  219. condition = " AND state = 2 "
  220. list ,err:= models.GetReportByCondition(condition,pars,[]string{},"",false,0,0)
  221. if err != nil{
  222. fmt.Println(err)
  223. }
  224. fmt.Println(len(list))
  225. for _,v:= range list{
  226. fmt.Println(v.Id)
  227. UpdateReportEs(v.Id,2)
  228. }
  229. }
  230. // UpdateReportEs 更新报告/章节Es
  231. func UpdateReportEs(reportId int, publishState int) (err error) {
  232. if reportId <= 0 {
  233. return
  234. }
  235. reportInfo, err := models.GetReportByReportId(reportId)
  236. if err != nil {
  237. return
  238. }
  239. categories := ""
  240. if reportInfo.HasChapter == 1 {
  241. // 晨周报
  242. chapterList, tmpErr := models.GetPublishedChapterListByReportId(reportInfo.Id)
  243. if tmpErr != nil {
  244. return
  245. }
  246. if len(chapterList) > 0 {
  247. // 更新章节的es数据
  248. for _, chapterInfo := range chapterList {
  249. err = updateReportChapterEsByChapter(chapterInfo)
  250. if err != nil {
  251. return
  252. }
  253. }
  254. }
  255. } else {
  256. //if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
  257. permissionList, tmpErr := models.GetChartPermissionNameFromMappingByKeyword("rddp", reportInfo.ClassifyIdSecond)
  258. if tmpErr != nil {
  259. return
  260. }
  261. categoryArr := make([]string, 0)
  262. for i := 0; i < len(permissionList); i++ {
  263. categoryArr = append(categoryArr, permissionList[i].PermissionName)
  264. }
  265. aliasArr, _ := addCategoryAliasToArr(categoryArr)
  266. categories = strings.Join(aliasArr, ",")
  267. //}
  268. }
  269. // 最小单位的分类id
  270. minClassifyId, minClassifyName, err := getMinClassify(reportInfo)
  271. if err != nil {
  272. return
  273. }
  274. // 新增报告ES
  275. esReport := &models.ElasticReportDetail{
  276. ReportId: reportInfo.Id,
  277. ReportChapterId: 0,
  278. Title: reportInfo.Title,
  279. Abstract: reportInfo.Abstract,
  280. BodyContent: utils.TrimHtml(html.UnescapeString(reportInfo.Content)),
  281. PublishTime: reportInfo.PublishTime.Format(utils.FormatDateTime),
  282. PublishState: publishState,
  283. Author: reportInfo.Author,
  284. ClassifyIdFirst: reportInfo.ClassifyIdFirst,
  285. ClassifyNameFirst: reportInfo.ClassifyNameFirst,
  286. ClassifyIdSecond: reportInfo.ClassifyIdSecond,
  287. ClassifyNameSecond: reportInfo.ClassifyNameSecond,
  288. ClassifyId: minClassifyId,
  289. ClassifyName: minClassifyName,
  290. Categories: categories,
  291. StageStr: strconv.Itoa(reportInfo.Stage),
  292. }
  293. docId := fmt.Sprintf("%d-%d", reportInfo.Id, 0)
  294. if err = EsAddOrEditReport(utils.EsReportIndexName, docId, esReport); err != nil {
  295. return
  296. }
  297. return
  298. }
  299. // addCategoryAliasToArr 品种别名
  300. func addCategoryAliasToArr(categoryArr []string) (aliasArr []string, err error) {
  301. aliasArr = categoryArr
  302. if len(categoryArr) > 0 {
  303. for i := 0; i < len(categoryArr); i++ {
  304. if strings.Contains(categoryArr[i], "沥青") {
  305. aliasArr = append(aliasArr, "BU")
  306. }
  307. if strings.Contains(categoryArr[i], "MEG") {
  308. aliasArr = append(aliasArr, "EG", "乙二醇")
  309. }
  310. if strings.Contains(categoryArr[i], "聚酯") {
  311. aliasArr = append(aliasArr, "长丝", "短纤", "瓶片")
  312. }
  313. if strings.Contains(categoryArr[i], "纯苯+苯乙烯") {
  314. aliasArr = append(aliasArr, "EB")
  315. }
  316. if strings.Contains(categoryArr[i], "聚乙烯") {
  317. aliasArr = append(aliasArr, "PP", "PE")
  318. }
  319. if strings.Contains(categoryArr[i], "玻璃纯碱") {
  320. aliasArr = append(aliasArr, "玻璃", "纯碱", "FG", "SA")
  321. }
  322. if strings.Contains(categoryArr[i], "甲醇") {
  323. aliasArr = append(aliasArr, "甲醇", "MA")
  324. }
  325. if strings.Contains(categoryArr[i], "橡胶") {
  326. aliasArr = append(aliasArr, "橡胶", "RU")
  327. }
  328. }
  329. }
  330. return
  331. }
  332. // UpdateReportChapterEs
  333. // @Description: 通过章节id更新报告章节ES
  334. // @author: Roc
  335. // @datetime 2024-06-20 13:16:22
  336. // @param reportChapterId int
  337. // @return err error
  338. func UpdateReportChapterEs(reportChapterId int) (err error) {
  339. if reportChapterId <= 0 {
  340. return
  341. }
  342. chapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
  343. if err != nil {
  344. return
  345. }
  346. err = updateReportChapterEsByChapter(chapterInfo)
  347. if err != nil {
  348. return
  349. }
  350. return
  351. }
  352. // updateReportChapterEsByChapter
  353. // @Description: 通过章节详情更新报告章节ES
  354. // @author: Roc
  355. // @datetime 2024-06-20 13:16:11
  356. // @param chapterInfo *models.ReportChapter
  357. // @return err error
  358. func updateReportChapterEsByChapter(chapterInfo *models.ReportChapter) (err error) {
  359. // TODO 弘则得单独处理晨报的权限
  360. //if utils.BusinessCode != utils.BusinessCodeRelease && utils.BusinessCode != utils.BusinessCodeSandbox && utils.BusinessCode != utils.BusinessCodeDebug {
  361. // br.Ret = 200
  362. // br.Success = true
  363. // br.Msg = "操作成功"
  364. // return
  365. //}
  366. // 章节对应的品种
  367. obj := report.ReportChapterPermissionMapping{}
  368. permissionList, tmpErr := obj.GetPermissionItemListById(chapterInfo.ReportChapterId)
  369. if tmpErr != nil {
  370. return
  371. }
  372. categoryArr := make([]string, 0)
  373. if len(permissionList) > 0 {
  374. for ii := 0; ii < len(permissionList); ii++ {
  375. categoryArr = append(categoryArr, permissionList[ii].ChartPermissionName)
  376. }
  377. }
  378. aliasArr, _ := addCategoryAliasToArr(categoryArr)
  379. categories := strings.Join(aliasArr, ",")
  380. // 新增/编辑ES
  381. esChapter := &models.ElasticReportDetail{
  382. ReportId: chapterInfo.ReportId,
  383. ReportChapterId: chapterInfo.ReportChapterId,
  384. Title: chapterInfo.Title,
  385. Abstract: chapterInfo.Abstract,
  386. BodyContent: utils.TrimHtml(html.UnescapeString(chapterInfo.Content)),
  387. PublishTime: chapterInfo.PublishTime.Format(utils.FormatDateTime),
  388. PublishState: chapterInfo.PublishState,
  389. Author: chapterInfo.Author,
  390. ClassifyIdFirst: chapterInfo.ClassifyIdFirst,
  391. ClassifyNameFirst: chapterInfo.ClassifyNameFirst,
  392. ClassifyIdSecond: 0,
  393. ClassifyNameSecond: "",
  394. ClassifyId: chapterInfo.ClassifyIdFirst,
  395. ClassifyName: chapterInfo.ClassifyNameFirst,
  396. Categories: categories,
  397. StageStr: strconv.Itoa(chapterInfo.Stage),
  398. }
  399. chapterDocId := fmt.Sprintf("%d-%d", chapterInfo.ReportId, chapterInfo.ReportChapterId)
  400. if err = EsAddOrEditReport(utils.EsReportIndexName, chapterDocId, esChapter); err != nil {
  401. return
  402. }
  403. return
  404. }
  405. // DeleteReportAndChapter 删除报告及章节
  406. func DeleteReportAndChapter(reportId int) (err error) {
  407. reportInfo, err := models.GetReportByReportId(reportId)
  408. if err != nil {
  409. err = errors.New("报告信息有误, Err: " + err.Error())
  410. return
  411. }
  412. if reportInfo.State == 2 {
  413. err = errors.New("报告已发布,不可删除")
  414. return
  415. }
  416. // 更新ES
  417. _ = UpdateReportEs(reportId, 1)
  418. // 删除
  419. if reportInfo.HasChapter == 1 && (reportInfo.ChapterType == utils.REPORT_TYPE_DAY || reportInfo.ChapterType == utils.REPORT_TYPE_WEEK) {
  420. err = models.DeleteDayWeekReportAndChapter(reportId)
  421. } else {
  422. err = models.DeleteReport(reportId)
  423. }
  424. if err != nil {
  425. err = errors.New("删除失败, Err: " + err.Error())
  426. return
  427. }
  428. // 重置PPT关联报告
  429. go func() {
  430. _ = ResetPPTReport(reportId, false)
  431. }()
  432. return
  433. }
  434. // 替换报告内容中的base64图片
  435. func replaceReportBase64ToImg(content string) (newContent string, err error) {
  436. if content == "" {
  437. return
  438. }
  439. pattern := "data:([a-z]+\\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?"
  440. re, _ := regexp.Compile(pattern)
  441. matcher := re.FindAllString(content, 999)
  442. if len(matcher) > 0 {
  443. for _, v := range matcher {
  444. imgUrl, tmpErr := reportBase64ToImg(v)
  445. if tmpErr != nil {
  446. err = tmpErr
  447. return
  448. }
  449. content = strings.ReplaceAll(content, v, imgUrl)
  450. }
  451. }
  452. newContent = content
  453. return
  454. }
  455. // 转换base64图片为img并上传
  456. func reportBase64ToImg(imageBase64 string) (resourceUrl string, err error) {
  457. if imageBase64 == "" {
  458. err = errors.New("图片为空")
  459. return
  460. }
  461. ext := ".png"
  462. uploadDir := "./static"
  463. randStr := utils.GetRandStringNoSpecialChar(28)
  464. fileName := randStr + ext
  465. fpath := uploadDir + "/" + fileName
  466. b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, imageBase64)
  467. if !b {
  468. err = errors.New("图片格式不正确")
  469. return
  470. }
  471. re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
  472. base64Str := re.ReplaceAllString(imageBase64, "")
  473. base64Str = strings.Replace(base64Str, " ", "", -1)
  474. err = utils.SaveBase64ToFile(base64Str, fpath)
  475. if err != nil {
  476. err = errors.New("图片保存失败" + err.Error())
  477. return
  478. }
  479. defer os.Remove(fpath)
  480. hzUploadDir := utils.RESOURCE_DIR + "images/"
  481. savePath := hzUploadDir + time.Now().Format("200601/20060102/")
  482. savePath += fileName
  483. //上传到阿里云 和 minio
  484. //if utils.ObjectStorageClient == "minio" {
  485. // err = UploadFileToMinIo(fileName, fpath, savePath)
  486. // if err != nil {
  487. // err = errors.New("文件上传失败" + err.Error())
  488. // return
  489. // }
  490. // resourceUrl = utils.MinIoImghost + savePath
  491. //} else {
  492. // err = UploadFileToAliyun(fileName, fpath, savePath)
  493. // if err != nil {
  494. // err = errors.New("文件上传失败" + err.Error())
  495. // return
  496. // }
  497. // resourceUrl = utils.Imghost + savePath
  498. //}
  499. ossClient := NewOssClient()
  500. if ossClient == nil {
  501. err = fmt.Errorf("初始化OSS服务失败")
  502. return
  503. }
  504. resourceUrl, err = ossClient.UploadFile(fileName, fpath, savePath)
  505. if err != nil {
  506. err = fmt.Errorf("文件上传失败, Err: %s", err.Error())
  507. return
  508. }
  509. item := new(models.Resource)
  510. item.ResourceUrl = resourceUrl
  511. item.ResourceType = 1
  512. item.CreateTime = time.Now()
  513. _, err = models.AddResource(item)
  514. if err != nil {
  515. err = errors.New("资源上传失败" + err.Error())
  516. return
  517. }
  518. return
  519. }
  520. // UpdateReportVideo 更新报告及其章节音频
  521. func UpdateReportVideo(reportId int) (err error) {
  522. defer func() {
  523. if err != nil {
  524. utils.FileLog.Error("UpdateReportVideo, reportId:%s, Err:%s", strconv.Itoa(reportId), err.Error())
  525. go alarm_msg.SendAlarmMsg("更新报告音频失败, 报告ID: "+strconv.Itoa(reportId)+", Err: "+err.Error(), 3)
  526. //go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "更新报告音频失败, 报告ID: " + reportIdStr + ", Err: "+err.Error(), utils.EmailSendToUsers)
  527. }
  528. }()
  529. if reportId == 0 {
  530. return
  531. }
  532. reportInfo, err := models.GetReportByReportId(reportId)
  533. if err != nil {
  534. return
  535. }
  536. if reportInfo.HasChapter == 1 {
  537. // 更新章节音频
  538. chapterList, tmpErr := models.GetPublishedChapterListByReportId(reportInfo.Id)
  539. if tmpErr != nil {
  540. err = tmpErr
  541. return
  542. }
  543. chapterIdArr := make([]int, 0)
  544. for i := 0; i < len(chapterList); i++ {
  545. chapterIdArr = append(chapterIdArr, chapterList[i].ReportChapterId)
  546. }
  547. //go UpdateChaptersVideo(chapterIds)
  548. err = UpdateChaptersVideo(chapterIdArr)
  549. } else {
  550. // 更新报告音频
  551. if reportInfo.VideoUrl != "" {
  552. return
  553. }
  554. nowTime := time.Now()
  555. updateCols := make([]string, 0)
  556. updateCols = append(updateCols, "VideoUrl", "VideoName", "VideoSize", "VideoPlaySeconds")
  557. videoUrl, videoName, videoSize, videoPlaySeconds, tmpErr := CreateReportVideo(reportInfo.Title, html.UnescapeString(reportInfo.Content), nowTime.Format(utils.FormatDateTime))
  558. reportInfo.VideoUrl = videoUrl
  559. reportInfo.VideoName = videoName
  560. reportInfo.VideoSize = videoSize
  561. reportInfo.VideoPlaySeconds = fmt.Sprintf("%.2f", videoPlaySeconds)
  562. tmpErr = reportInfo.UpdateReport(updateCols)
  563. if tmpErr != nil {
  564. err = tmpErr
  565. return
  566. }
  567. }
  568. return
  569. }
  570. func UpdateEmptyVideoReportVideo() (err error) {
  571. list, err := models.GetSyncEmptyVideoReport()
  572. if err != nil {
  573. return
  574. }
  575. listLen := len(list)
  576. if listLen <= 0 {
  577. fmt.Println("无报告需要更新音频")
  578. return
  579. }
  580. fmt.Println("Start 待更新报告音频数: ", listLen)
  581. for i := 0; i < listLen; i++ {
  582. if err = UpdateReportVideo(list[i].Id); err != nil {
  583. fmt.Printf("更新音频失败")
  584. fmt.Println(err.Error())
  585. return
  586. }
  587. }
  588. fmt.Println("End 报告音频更新完毕")
  589. return
  590. }
  591. // checkDayWeekChapterWrite 校验晨周报已写章节与本期应写章节
  592. func checkDayWeekChapterWrite(chapters []*models.ReportChapter, reportType string) (publishReport bool, tips string, publishIdArr, unPublishIdArr []int, err error) {
  593. nowTime := time.Now().Local()
  594. updateTypeArr := make([]int, 0) // 需更新的章节类型IDs
  595. publishIdArr = make([]int, 0) // 需发布的章节IDs
  596. unPublishIdArr = make([]int, 0) // 需取消发布/未发布的章节IDs
  597. // 校验章节内容
  598. if reportType == utils.REPORT_TYPE_DAY {
  599. // 晨报章节不能都为空
  600. isEmpty := true
  601. for i := 0; i < len(chapters); i++ {
  602. if chapters[i].Content != "" && chapters[i].Title != "" {
  603. isEmpty = false
  604. break
  605. }
  606. }
  607. if isEmpty {
  608. err = errors.New("报告章节内容均为空或标题为空,不可发布")
  609. return
  610. }
  611. } else {
  612. // 周报章节需至少有一篇已编辑且有标题
  613. editNum := 0
  614. for i := 0; i < len(chapters); i++ {
  615. if chapters[i].IsEdit == 1 && chapters[i].Title != "" {
  616. editNum += 1
  617. }
  618. }
  619. if editNum == 0 {
  620. err = errors.New("报告均未编辑或标题为空,不可发布")
  621. return
  622. }
  623. }
  624. // 章节类型列表
  625. types, e := models.GetReportChapterTypeListByResearchType(reportType)
  626. if e != nil {
  627. err = errors.New("获取章节类型列表失败")
  628. return
  629. }
  630. // 本期需更新的章节IDs
  631. typeLen := len(types)
  632. for i := 0; i < typeLen; i++ {
  633. if types[i].IsSet != 1 && types[i].Enabled != 0 {
  634. // 正常更新
  635. updateTypeArr = append(updateTypeArr, types[i].ReportChapterTypeId)
  636. } else {
  637. // 被设置为零值的也算作正常更新
  638. if types[i].PauseStartTime == utils.EmptyDateStr && types[i].PauseEndTime == utils.EmptyDateStr {
  639. updateTypeArr = append(updateTypeArr, types[i].ReportChapterTypeId)
  640. continue
  641. }
  642. // 暂停更新需校验时间
  643. startTime, _ := time.Parse(utils.FormatDate, types[i].PauseStartTime)
  644. endTime, _ := time.Parse(utils.FormatDate, types[i].PauseEndTime)
  645. if nowTime.Before(startTime) || nowTime.After(endTime.AddDate(0, 0, 1)) {
  646. updateTypeArr = append(updateTypeArr, types[i].ReportChapterTypeId)
  647. }
  648. }
  649. }
  650. // 校验本期需更新的章节是否都已编辑
  651. chapterLen := len(chapters)
  652. updateTypeLen := len(updateTypeArr)
  653. tipsArr := make([]string, 0)
  654. for i := 0; i < chapterLen; i++ {
  655. isWrite := false
  656. for ii := 0; ii < updateTypeLen; ii++ {
  657. // 本期应发布的章节
  658. if chapters[i].TypeId == updateTypeArr[ii] {
  659. // 标题或者内容为空的情况下, 记录tips提示信息且不发布该章节
  660. if chapters[i].Title == "" || chapters[i].Content == "" {
  661. tipsArr = append(tipsArr, chapters[i].TypeName)
  662. break
  663. }
  664. isWrite = true
  665. break
  666. }
  667. }
  668. if isWrite {
  669. publishIdArr = append(publishIdArr, chapters[i].ReportChapterId)
  670. } else {
  671. unPublishIdArr = append(unPublishIdArr, chapters[i].ReportChapterId)
  672. }
  673. }
  674. if len(tipsArr) > 0 {
  675. tips = "部分章节未发布:" + strings.Join(tipsArr, "、") + "未填写标题/内容"
  676. }
  677. // 周报需发布的章节与需更新的章节数相等则表示可发布整期, 晨报无限制
  678. if reportType == utils.REPORT_TYPE_DAY {
  679. publishReport = true
  680. } else {
  681. if len(publishIdArr) == updateTypeLen {
  682. publishReport = true
  683. }
  684. }
  685. return
  686. }
  687. // PcCreateAndUploadSunCode 生成太阳码并上传OSS
  688. func PcCreateAndUploadSunCode(scene, page string) (imgUrl string, err error) {
  689. if page == "" {
  690. err = errors.New("page不能为空")
  691. return
  692. }
  693. // scene超过32位会生成失败,md5处理至32位
  694. sceneMD5 := "a=1"
  695. if scene != "" {
  696. sceneMD5 = utils.MD5(scene)
  697. }
  698. picByte, err := GetSunCode(page, sceneMD5)
  699. if err != nil {
  700. return
  701. }
  702. // 生成图片
  703. localPath := "./static/imgs"
  704. fileName := utils.GetRandStringNoSpecialChar(28) + ".png"
  705. fpath := fmt.Sprint(localPath, "/", fileName)
  706. f, err := os.Create(fpath)
  707. if err != nil {
  708. fmt.Println("11111")
  709. return
  710. }
  711. if _, err = f.Write(picByte); err != nil {
  712. return
  713. }
  714. defer func() {
  715. f.Close()
  716. os.Remove(fpath)
  717. }()
  718. // 上传OSS
  719. fileDir := "yb/suncode/"
  720. //上传到阿里云 和 minio
  721. //if utils.ObjectStorageClient == "minio" {
  722. // imgUrl, err = UploadMinIoToDir(fileName, fpath, "", fileDir)
  723. // if err != nil {
  724. // return
  725. // }
  726. //} else {
  727. // imgUrl, err = UploadAliyunToDir(fileName, fpath, "", fileDir)
  728. // if err != nil {
  729. // return
  730. // }
  731. //}
  732. savePath := fileDir + time.Now().Format("200601/20060102/") + fileName
  733. ossClient := NewOssClient()
  734. if ossClient == nil {
  735. err = fmt.Errorf("初始化OSS服务失败")
  736. return
  737. }
  738. imgUrl, err = ossClient.UploadFile(fileName, fpath, savePath)
  739. if err != nil {
  740. err = fmt.Errorf("文件上传失败, Err: %s", err.Error())
  741. return
  742. }
  743. if err != nil {
  744. return
  745. }
  746. // 记录参数
  747. if scene != "" {
  748. newSuncode := &models.YbPcSuncode{
  749. Scene: scene,
  750. SceneMd5: sceneMD5,
  751. CodePage: page,
  752. SuncodeUrl: imgUrl,
  753. CreateTime: time.Now(),
  754. }
  755. err = models.AddYbPcSunCode(newSuncode)
  756. }
  757. // 记录参数md5
  758. if scene != "" {
  759. newPars := &models.YbSuncodePars{
  760. Scene: scene,
  761. SceneKey: sceneMD5,
  762. CreateTime: time.Now(),
  763. }
  764. err = models.AddYbSuncodePars(newPars)
  765. }
  766. return
  767. }
  768. // CreateNewReport 创建新报告
  769. func CreateNewReport(req models.AddReq, adminInfo *system.Admin) (newReportId int64, reportCode, errMsg string, err error) {
  770. contentSub := ""
  771. if req.Content != "" {
  772. e := utils.ContentXssCheck(req.Content)
  773. if e != nil {
  774. errMsg = "存在非法标签"
  775. err = errors.New("存在非法标签, Err: " + e.Error())
  776. return
  777. }
  778. contentClean, e := FilterReportContentBr(req.Content)
  779. if e != nil {
  780. errMsg = "内容去除前后空格失败"
  781. err = errors.New("内容去除前后空格失败, Err: " + e.Error())
  782. return
  783. }
  784. req.Content = contentClean
  785. sub, e := GetReportContentSub(req.Content)
  786. if e != nil {
  787. go alarm_msg.SendAlarmMsg("ContentSub 失败,Err:"+e.Error(), 3)
  788. }
  789. contentSub = sub
  790. }
  791. maxStage, e := models.GetReportStage(req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird)
  792. if e != nil {
  793. errMsg = "期数获取失败!"
  794. err = errors.New("期数获取失败,Err:" + e.Error())
  795. return
  796. }
  797. item := new(models.Report)
  798. item.AddType = req.AddType
  799. item.ClassifyIdFirst = req.ClassifyIdFirst
  800. item.ClassifyNameFirst = req.ClassifyNameFirst
  801. item.ClassifyIdSecond = req.ClassifyIdSecond
  802. item.ClassifyNameSecond = req.ClassifyNameSecond
  803. item.Title = req.Title
  804. item.Abstract = req.Abstract
  805. item.Author = req.Author
  806. item.Frequency = req.Frequency
  807. item.State = req.State
  808. item.Content = html.EscapeString(req.Content)
  809. item.Stage = maxStage + 1
  810. item.ContentSub = html.EscapeString(contentSub)
  811. item.CreateTime = req.CreateTime
  812. item.ModifyTime = time.Now()
  813. item.ReportVersion = req.ReportVersion
  814. item.AdminId = adminInfo.AdminId
  815. item.AdminRealName = adminInfo.RealName
  816. item.ClassifyIdThird = req.ClassifyIdThird
  817. item.ClassifyNameThird = req.ClassifyNameThird
  818. // 产品要求,如果是多人协作,那么就是章节类型的报告
  819. if req.CollaborateType == 2 {
  820. item.HasChapter = 1
  821. item.ChapterType = ""
  822. }
  823. item.LastModifyAdminId = adminInfo.AdminId
  824. item.LastModifyAdminName = adminInfo.RealName
  825. item.ContentModifyTime = time.Now()
  826. item.NeedSplice = 1
  827. item.ContentStruct = html.EscapeString(req.ContentStruct)
  828. item.HeadImg = req.HeadImg
  829. item.EndImg = req.EndImg
  830. item.CanvasColor = req.CanvasColor
  831. item.HeadResourceId = req.HeadResourceId
  832. item.EndResourceId = req.EndResourceId
  833. item.CollaborateType = req.CollaborateType
  834. item.ReportLayout = req.ReportLayout
  835. item.IsPublicPublish = req.IsPublicPublish
  836. item.ReportCreateTime = time.Now()
  837. err, errMsg = AddReportAndChapter(item, 0, req.GrantAdminIdList)
  838. return
  839. }
  840. // FilterReportContentBr 过滤报告正文前后换行符
  841. func FilterReportContentBr(content string) (res string, err error) {
  842. newContent := content
  843. //content = `<p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><strong>四季度验证投产预期</strong></p><p style=\"font-size: 16px;\"><strong>&nbsp;</strong></p><p style=\"font-size: 16px;\"><strong>PTA:<strong><strong>近端短缺的格局出现缓解的迹象,表现在:PX进口回升;国内重整及常减压提负;PTA、PX投产在即</strong></strong><strong>。正套部分或全部止盈。</strong></strong></p><p style=\"font-size: 16px;\"><strong>&nbsp;</strong></p><p style=\"font-size: 16px;\"><strong>乙二醇:</strong><strong style=\"font-weight: 700; color: rgb(0, 0, 0); font-family: ;\">到港预报集中的情况下仍在去库,4200-4600区间震荡操作,节前没有明显方向,不建议在节前备货的时间段布局空单</strong><strong>。</strong></p><p style=\"font-size: 16px;\"><strong>&nbsp;</strong></p><p style=\"font-size: 16px;\"><strong>1、PX国内供应本周回落,PTA工厂负荷变动滞后于PX装置。</strong></p><p style=\"font-size: 16px;\">PX装置变动:</p><p style=\"font-size: 16px;\">海南炼化一期66万吨PX装置因故障停车检修,重启时间待跟踪,其二期100万吨PX装置预计在此装置重启后停车检修。</p><p style=\"font-size: 16px;\">天津石化一套100万吨重整已于20日重启中,其39万吨PX预计下周初出产品。</p><p style=\"font-size: 16px;\">韩国SK 位于仁川的130万吨PX装置按计划在23日停车检修,计划检修时长45天左右。</p><p style=\"font-size: 16px;\">截至周五,中国国内PX负荷小幅回落至73.4%(前值<span style=\"color: rgb(65, 65, 65); font-family: sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;\">76.8</span>%),亚洲PX负荷小幅回落至68.7%(<span style=\"color: rgb(65, 65, 65); font-family: sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;\">72</span>%)。</p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/4VcA2eUE1Sgkx0KQ0NjchM0ajIis.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/vi3laEb3qejWISetD0SjGTwuAxUg.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=760c86a78c35ab9286903e9ce52a6682\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=1e36d11158bf146313f3f65a65dbc5f3\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=7a69494b1a84520b2cdf1dccb3c78bda\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=0a2e374c0689d5fba4a9dc40474a33b0\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"font-size: 16px;\">PXN本周延续回落至380美金附近。</p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=ad1b869f60ab31060730e8bd58260d8c\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=d1c87cb3adff22aea0fc37dfb1870626\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=bac98256b0b86e92ed4084fbbd223f5b\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><span style=\"font-size: 16px; font-family: Arial, Helvetica, sans-serif;\">PX平衡表</span></p><p style=\"margin: 0px; padding: 0px; font-size: 16px; color: rgb(0, 0, 0); font-family: ;\">海关统计,国内8月PX进口总量在78.8万吨,环比增加17.3万吨,增幅28%;同比减少30.8万吨,降幅28.1%;8月PX出口量0.47万吨,环比下降5万吨。</p><p style=\"margin: 0px; padding: 0px; font-size: 16px; color: rgb(0, 0, 0); font-family: ;\"><span style=\"color: rgb(0, 0, 0); font-family: ;\">PX 8月进口量大幅回升至79万吨附近,其中8月从韩国进口的PX量达到33.65万吨。9月目前公布的1-20号从韩国进口PX的量已经接近8月全月的水平,即环比8月仍是大幅增加的情况:</span><span style=\"font-size: 16px;\">据悉,9月1-20日韩国PX出口总量在30.9万吨,其中出口至中国27.9万吨。</span></p><p style=\"margin: 0px; padding: 0px; font-size: 16px; color: rgb(0, 0, 0); font-family: ;\"><span style=\"font-size: 16px;\">预计9月进口量环比8月进一步增加。</span></p><p style=\"margin: 0px; padding: 0px; font-size: 16px; color: rgb(0, 0, 0); font-family: ;\"><span style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/TD59rptBDNwJ7J2qpxd8HXi4X5cQ.png\" class=\"fr-fic fr-dib fr-draggable\"></span></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><strong><span style=\"color: rgb(0, 0, 0); font-family: ;\">原油本周偏弱,欧美汽油利润本周均出现了明显回升。</span></strong></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><strong><span style=\"color: rgb(0, 0, 0); font-family: ;\"><span style=\"color: rgb(34, 34, 34); font-family: system-ui, -apple-system, BlinkMacSystemFont, ;\">美国飓风将在下周登陆墨西哥湾,届时或将影响海上钻机的运行以及炼厂开工。</span>&nbsp;</span></strong></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=ef172d65e20b986351ed50545f3d36d1\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=4930c0981fc33ab125e6ef346c9cbd5b\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=f0cae34883508273d482d4cf8c577410\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><strong>2、</strong><strong>乙烯连续跌价至900美金,PX投产压力临近PXN延续压缩。石脑油亏损本周延续修复。</strong></p><p style=\"font-size: 16px;\">PTA:按照PX11-12月上1054美金(石脑油672美金,Brent86.15美金)计算,醋酸3075元,目前含醋酸的原料成本在5670元附近。可以发现虽然Brent和PX环比上周都出现了明显的下跌,但石脑油相对上周环比上涨,石脑油亏损本周延续修复,目前石脑油-Brent价差回升至0以上。</p><p style=\"font-size: 16px;\">给到200-300的最低加工成本,PTA的估值在5870-5970元。周五日盘收盘后TA11合约5742,11月及之后的PTA合约均亏损。</p><p style=\"font-size: 16px;\">PTA基差再度回升至1000附近,周五小幅走弱至970。</p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=3e743a3907292d9ad2dce6033ae7d8a4\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/Csak38HlAbt8XsbBsPeSWlaUKJiO.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/LOPKDaDnRnbJauWqlvbhQ5VNxa03.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/Ocdq2m3bMDqZnM8PTXVyj8XILt7Z.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=268b3f7204aa21accb8d292154cf4e3c\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"font-size: 16px;\">乙二醇:静态来看,按照盘面(煤价按照900元),锚定石脑油672美金,乙烯900美金,甲醇2640元,北美乙烷价格38美分/加仑计算,乙二醇的综合成本仍在5050元附近。周五日盘收盘01合约按照综合成本亏损700元附近。</p><p style=\"font-size: 16px;\">国内外采乙烷制乙二醇目前扭亏为盈。</p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=f1e3ba1df230353571d00af2dfb7e92a\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=0d17c186dda211dab039f2ccd39a8d76\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=3a93cb16f02731bc5c74b560f9590456\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=e1a38d9058b719d8dd5bf746bc9739be\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/drjYJ468Tpnr3Bno52CQZXRuayow.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/bv4JvZN7OxhXFnx0xL0EW6yGFlX8.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220918/54TYCFo1ulvOhbJpk9jQPJcnDyan.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/fZayI9g8kS2ILeVu799dBnAdugNB.png\" class=\"fr-fic fr-dib fr-draggable\"></p><ol style=\"margin: 0px; padding: 0px; list-style: none; color: rgb(0, 0, 0); font-family: ;\"><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><strong>3、</strong><span style=\"font-size: 16px; color: rgb(0, 0, 0); font-family: ;\"><strong>需求端本周仍偏弱:继上周加弹负荷下滑后,本周织造负荷下滑。延续坯布库存回升同时原料库存下降,聚酯连续累库(瓶片低库存优势也明显减弱),两家大厂减产执行过程和节前备货中和,因此减产去库成效甚微</strong><strong>。</strong></span><span style=\"font-size: 16px;\">&nbsp;</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"font-size: 18px !important; color: rgb(0, 0, 0); font-family: Tahoma, ;\">终端:江浙加弹综合开工微幅回升至77%(前值</span><span style=\"font-size: 16px; color: rgb(0, 0, 0); font-family: Tahoma, ;\">76</span><span style=\"font-size: 18px !important; color: rgb(0, 0, 0); font-family: Tahoma, ;\">%);江浙织机综合开工回落至69%(前值</span><span style=\"font-size: 16px; color: rgb(0, 0, 0); font-family: Tahoma, ;\">71</span><span style=\"font-size: 18px !important; color: rgb(0, 0, 0); font-family: Tahoma, ;\">%);</span><span style=\"font-size: 18px !important; color: rgb(0, 0, 0); font-family: Tahoma, ;\">江浙印染综合开工维持在77%</span><span style=\"font-size: 18px !important; color: rgb(0, 0, 0); font-family: Tahoma, ;\">&nbsp;。</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"font-size: 18px !important; color: rgb(0, 0, 0); font-family: Tahoma, ;\">本周,涤丝仅周三稍有放量,整体涤丝销售氛围偏弱为主。终端在新订单氛围走弱和成本端偏弱氛围下,原料不再进一步跟进,消化前期备货为主,综合原料备货有所下降。截至目前,原料备货集中在10-15天,偏高备货至10月底。</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"font-size: 18px !important; color: rgb(0, 0, 0); font-family: Tahoma, ;\">订单情况:近期的织造端新单氛围整体走弱明显,前期较好的圆机和经编工厂尤为明显,出货量也有所放缓,部分工厂生产前期订单为主。</span><br><span style=\"font-size: 18px !important; color: rgb(0, 0, 0); font-family: Tahoma, ;\">直接需求:</span>近期装置轮动检修与重启,长丝大厂陆续执行减产动作,但也有几套切片装置恢复,整体而言聚酯负荷仍以区间波动为主。截至本周五,初步核算聚酯负荷在83.9%(前值84.3%)。</li></ol><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=9c163c32bf7fca689c0067e285eb1a30\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"font-size: 16px;\">轻纺城成交量节前震荡回升但仍在偏低水平。</p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/sHBC03ZwLXP1jUK1mDdd0hUjEtQo.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=5e2a381802d34c5cf33502d8a9649bd6\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><p style=\"text-align: left; margin-top: 10px; font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=e47bcc409b9b7e9178805ac6c4a00bc1\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe><br></p><p style=\"font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=12be062cbc0c61819f99648cef6e2740\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe><br></p><p style=\"font-size: 16px;\"><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=fea70f6d5bf7f1899394be0619cc2da1\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe><br><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=8a50b210b7ebb4fbb890d8ad58a817f6\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe><br><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=800bf3710951ca96c2e5a3693c94a20d\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe><br><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=fd1bff8836af41d0c32d0ed168c8b748\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe><br><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=baf493ba32b3c39e070e24ac5546fd6b\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></p><ul style=\"margin: 0px; padding: 0px; list-style: none; color: rgb(0, 0, 0); font-family: ;\"><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><strong>4、PTA行情沙盘推演<strong>2022.9.23:</strong><strong>PTA近端短缺的格局出现缓解的迹象,表现在:PX进口回升;国内重整及常减压提负;PTA、PX投产在即</strong></strong><strong>。正套部分或全部止盈。</strong></li></ul><p style=\"font-size: 16px;\"><img style=\"width: 100%;\" src=\"https://hzstatic.hzinsights.com/static/images/202209/20220922/d1HYjIFoBG9L2bGuWrXq7sBPpnT7.png\" class=\"fr-fic fr-dii fr-draggable\"><br></p><p style=\"font-size: 16px;\">PTA库存结构变化跟踪:本期PTA库存大幅去化17.9万吨附近(本周仓单集中注销,仓单库存大幅下降至0附近),聚酯成品折算PTA库存大幅累库9.3万吨,叠加PTA库存在聚酯成品库存累库的情况下去库8.6万吨。</p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/qYivQYoLkxJrmWGI5kNOy8i05q8P.png\" class=\"fr-fic fr-dib fr-draggable\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/cvLkXlLwczkvzktkN4R2SKCoHFby.png\" class=\"fr-fic fr-dib fr-draggable\" style=\"width: 572px;\"></p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/EzRc4yuCqVcbdw2lb5FS2MCWvHd1.png\" class=\"fr-fic fr-dib fr-draggable\" style=\"width: 595px;\"></p><p style=\"font-size: 16px;\">PTA平衡表——按照Q4有500万吨新装置投产(东营威联化学250万吨及嘉通能源250万吨分别在11、12月计入产能基数)计算,按照9-10月聚酯月均负荷84%(下调1%)、87%(下调1%)预估,8-12月出口预计25万吨附近。8-10月目前预估均为去库格局。</p><p style=\"font-size: 16px;\">PX折算PTA与PTA合计9月去库幅度修正后大幅收窄。</p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/6VzQkr5nkbeqlCakRzTvZgl1PeFE.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"margin: 0px; padding: 0px; font-size: 16px;\"><br></p><p style=\"margin: 0px; padding: 0px; font-size: 16px;\"><strong style=\"color: rgb(0, 0, 0); font-family: ;\">5、</strong><strong style=\"color: rgb(0, 0, 0); font-family: ;\">乙二醇行情沙盘推演2022.9.23:到港预报集中的情况下仍在去库,4200-4600区间震荡操作,节前没有明显方向,不建议在节前备货的时间段布局空单。</strong></p><p style=\"margin: 0px; padding: 0px; font-size: 16px;\"><strong style=\"color: rgb(0, 0, 0); font-family: ;\"><img style=\"width: 100%;\" src=\"https://hzstatic.hzinsights.com/static/images/202209/20220916/e5XTMMmx04X6wpKITZ2NgjUyz8sI.png\" class=\"fr-fic fr-dii fr-draggable\"></strong><br></p><p style=\"margin: 0px; padding: 0px; font-size: 16px;\">乙二醇国内供需:</p><ul style=\"margin: 0px; padding: 0px; list-style: none; color: rgb(0, 0, 0); font-family: ;\"><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"color: rgb(0, 0, 0); font-family: Arial, Helvetica, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 28px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;\">乙二醇负荷低位仍在下降:截至9月22日,中国大陆地区乙二醇整体开工负荷在43.98%(较上期下降2.44%),其中煤制乙二醇开工负荷在28.67%(较上期下降2.08%)。</span><span style=\"font-family: Arial, Helvetica, sans-serif;\">&nbsp;</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"font-family: Arial, Helvetica, sans-serif;\">后续供应存增加预期:</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"font-family: Arial, Helvetica, sans-serif;\">上周意外停车的大连大型装置预计本周末或下周初开车。</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"font-family: Arial, Helvetica, sans-serif;\">内蒙古40万吨装置装置周内正常出料,负荷回升中。</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"font-family: Arial, Helvetica, sans-serif;\">陕西30万吨装置将于近日重启,预计9月底前后出料;内蒙古26万吨装置计划本月底前后投料重启。</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"font-family: Arial, Helvetica, sans-serif;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/Yy4lTK54zBA3ifN4DFdpAhE1TQ4w.png\" class=\"fr-fic fr-dib fr-draggable\"></span><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/eXC4GZApxmJQLQSZZ15HV6J1nGdK.png\" class=\"fr-fic fr-dib fr-draggable\"></li></ul><p style=\"font-size: 16px;\">乙二醇到港预报与实际到港:</p><p style=\"font-size: 16px;\"><span style=\"font-size: 16px; color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; font-family: Arial, Helvetica, sans-serif; text-indent: 32px; float: none; display: inline !important;\"><span style=\"color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 32px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;\">隆众口径:截至9月29日,国内乙二醇华东总到港量预计在19.21万吨,较上一期增加3.45万吨,提升21.93个百分点。</span>&nbsp;</span></p><p style=\"font-size: 16px;\"><span style=\"font-size: 16px; color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; font-family: Arial, Helvetica, sans-serif; text-indent: 32px; float: none; display: inline !important;\">港口发货节前回升。</span></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/WXVwl3NV0U68hnEjeNX5RFPGowq3.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/2FuGYgp5dpx6XbCNXLdSzdh4uzvp.png\" class=\"fr-fic fr-dib fr-draggable\"></p><p style=\"font-size: 16px;\">海外装置:</p><p style=\"font-size: 16px;\">印度IOC 32.5万吨装置将于近期停车技改,预计停车将持续至12月份。</p><p style=\"font-size: 16px;\">伊朗 Marun 44.5万吨装置目前处于停车状态,该装置此前货源供应印度市场为主。</p><p style=\"font-size: 16px;\">美国Sasol 28万吨装置计划于10月上旬停车检修,预计检修时长在一个月附近。</p><p style=\"font-size: 16px;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/CRzzaHscEcvhW8KXdajvLqABw11g.png\" class=\"fr-fic fr-dib fr-draggable\"></p><ul style=\"margin: 0px; padding: 0px; list-style: none; color: rgb(0, 0, 0); font-family: ;\"><li style=\"margin: 0px; padding: 0px; font-size: 16px;\">库存:</li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\">隆众口径:<span style=\"font-size: 16px; color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; font-family: Arial, Helvetica, sans-serif; text-indent: 32px; float: none; display: inline !important;\">截至9月22日,华东主港地区MEG港口库存总量81.6万吨,较上一统计周期减少3.64万吨,降低4.27%。受周末台风天气影响,主港本周到货延迟且整体出货尚可,本周港口库存延续去库走势。</span></li></ul><ul style=\"margin: 0px; padding: 0px; list-style: none;\"><li style=\"margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: ;\">节前备货,聚酯工厂的乙二醇备货量本周小幅回升至84万吨附近。</li><li><br></li><li><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=c13c3e669ae5940893d1fd0e669754a5\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></li><li><iframe src=\"https://chartlib.hzinsights.com/chartshow?code=0fac897a91cbfcdb67958ae9e5d039a7\" width=\"100%\" height=\"350\" style=\"border-width:0px; min-height:350px;\"></iframe></li><li style=\"margin: 0px; padding: 0px;\"><span style=\"font-size: 16px; font-family: Arial, Helvetica, sans-serif;\">乙二醇平衡表:</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;\">2022年8月我国乙二醇当月进口量为598134.40吨,累计进口量为5189987.09吨,进口量环比升4.83%,进口量同比下跌23.97%,累计进口量比去年同期降9.28%。&nbsp;</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px;\"><span style=\"font-family: Arial, Helvetica, sans-serif;\">按照9-10月聚酯月均负荷84%、87%,8月进口量比预估略多,9月因受到台风影响到港持续延迟,压力预计在9月下旬至9月底显现,因此预计也只有55万吨附近,Q4进口预计仍有差别,10-12月目前预计进口60-65万吨(其中10月或偏多)。</span></li><li style=\"margin: 0px; padding: 0px; font-size: 16px; font-family: Arial, Helvetica, sans-serif;\">进口量调整后,8月去库收窄至16万吨偏下,9月目前预计去库14万吨附近,10月累库压力来自于到港集中及新装置开始贡献产量,11月累库压力来自于需求回落+新装置产量提升。</li><li style=\"margin: 0px; padding: 0px; font-size: 16px; font-family: Arial, Helvetica, sans-serif;\">11-12月警惕供应端的超预期缩量。</li><li style=\"margin: 0px; padding: 0px; font-size: 16px; font-family: Arial, Helvetica, sans-serif;\"><img src=\"https://hongze.oss-accelerate.aliyuncs.com/static/images/202209/20220925/VEW93DsD4vrzXuNUfTMCFiJSU8Y3.png\" class=\"fr-fic fr-dib fr-draggable\"></li></ul><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p><p style=\"font-size: 16px;\"><br></p>`
  844. content = html.UnescapeString(content)
  845. if content == "" {
  846. return
  847. }
  848. // 过滤编辑器版权html
  849. content = strings.Replace(content, "<p data-f-id=\"pbf\" style=\"text-align: center; font-size: 14px; margin-top: 30px; opacity: 0.65; font-family: sans-serif;\">Powered by <a href=\"https://www.froala.com/wysiwyg-editor?pb=1\" title=\"Froala Editor\">Froala Editor</a></p>", "", -1)
  850. defer func() {
  851. if err != nil {
  852. go alarm_msg.SendAlarmMsg("过滤报告正文前后换行符及空格失败, ErrMsg: "+err.Error(), 3)
  853. }
  854. }()
  855. // 做一个配置,有问题的时候随时关闭
  856. configKey := "report_filter_br"
  857. conf, e := company.GetConfigDetailByCode(configKey)
  858. if e != nil {
  859. err = errors.New("获取报告过滤配置失败, Err: " + e.Error())
  860. return
  861. }
  862. if conf.ConfigValue != "1" {
  863. return content, nil
  864. }
  865. // 找出所有<p>标签, <p>标签的索引
  866. re := regexp.MustCompile(`(?is:<p(.*?)</p>)`)
  867. arr := re.FindAllString(content, -1)
  868. indexArr := re.FindAllIndex([]byte(content), -1)
  869. // 空<p>正则
  870. emptyRe := `<p[^>]*>(<br>|<br/>)+</p>`
  871. startIsBr := false
  872. countEmptyBr := 0 // 需要连续替换的空<p>总数
  873. lastBrRange := 0 // 最后一个空<p>右侧index, 用来判断是否为连续的空<p>
  874. // 注:以下逻辑只适用于去除前面的空行, 由于编辑器始终会在文章最后面跟上自己的html标签, 此处不再进行后面空行的去除=_=!
  875. for i := range arr {
  876. byteRange := indexArr[i]
  877. if len(byteRange) == 2 {
  878. // 内容开头不为<p>直接跳出遍历, 否则才进行空<p>的判断
  879. if i == 0 && byteRange[0] == 0 {
  880. startIsBr = true
  881. }
  882. if !startIsBr {
  883. break
  884. }
  885. if lastBrRange != 0 {
  886. // 说明不是连续的空<p>, 中间出现了其他标签, 那么结束遍历, 进行最终的文本替换
  887. if lastBrRange != byteRange[0] {
  888. break
  889. }
  890. }
  891. // 正则匹配为空<p>则计数, 记录该空<p>右侧index
  892. m, e := regexp.Match(emptyRe, []byte(arr[i]))
  893. if e != nil {
  894. err = e
  895. return
  896. }
  897. if m {
  898. countEmptyBr += 1
  899. lastBrRange = byteRange[1]
  900. continue
  901. }
  902. // 遍历到该<p>标签不为空了, 结束遍历
  903. break
  904. }
  905. }
  906. if countEmptyBr > 0 {
  907. reg, e := regexp.Compile(emptyRe)
  908. if e != nil {
  909. err = errors.New("正则解析失败, Err: " + e.Error())
  910. return
  911. }
  912. counted := 0 // 已替换数
  913. res = reg.ReplaceAllStringFunc(content, func(s string) string {
  914. counted += 1
  915. if counted <= countEmptyBr {
  916. return ""
  917. } else {
  918. return s
  919. }
  920. })
  921. if res == "" {
  922. res = newContent
  923. }
  924. } else {
  925. res = content
  926. if res == "" {
  927. res = newContent
  928. }
  929. }
  930. return
  931. }
  932. // GetEnglishReportOverview 获取英文研报overview部分
  933. func GetEnglishReportOverview(content string) (res string, err error) {
  934. content = html.UnescapeString(content)
  935. doc, e := goquery.NewDocumentFromReader(strings.NewReader(content))
  936. if e != nil {
  937. err = errors.New("Create Doc Err: " + e.Error())
  938. return
  939. }
  940. target := "overview"
  941. label := "</strong>"
  942. start := -1
  943. end := -1
  944. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  945. h, e := s.Html()
  946. if e != nil {
  947. err = errors.New("Get Html1 Err: " + e.Error())
  948. return
  949. }
  950. h = strings.ToLower(h)
  951. t := s.Text()
  952. t = strings.ToLower(t)
  953. if strings.Contains(h, label) && t != "" && strings.Contains(t, target) {
  954. start = i
  955. }
  956. if start != -1 && end == -1 && i > start && strings.Contains(h, label) {
  957. end = i
  958. }
  959. })
  960. if start != -1 && end != -1 {
  961. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  962. if i > start && i < end {
  963. h, e := s.Html()
  964. if e != nil {
  965. err = errors.New("Get Html2 Err: " + e.Error())
  966. return
  967. }
  968. // 包含iframe则过滤掉
  969. if strings.Contains(h, "iframe") {
  970. return
  971. }
  972. res += `<p>` + h + `</p>`
  973. }
  974. })
  975. }
  976. return
  977. }
  978. // GetReportContentSubWithoutIframe 获取报告正文前几段,过滤iframe
  979. func GetReportContentSubWithoutIframe(content string) (contentSub string, err error) {
  980. content = html.UnescapeString(content)
  981. doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
  982. if err != nil {
  983. fmt.Println("create doc err:", err.Error())
  984. return
  985. }
  986. label := "iframe"
  987. n := 0
  988. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  989. if n >= 5 {
  990. return
  991. }
  992. n++
  993. h, err := s.Html()
  994. if err != nil {
  995. fmt.Println("get html err", err.Error())
  996. return
  997. }
  998. // 包含iframe则过滤掉
  999. if strings.Contains(h, label) {
  1000. return
  1001. }
  1002. if s.Text() != "" || strings.Contains(h, "src") {
  1003. contentSub = contentSub + "<p>" + h + "</p>"
  1004. }
  1005. })
  1006. return
  1007. }
  1008. // UpdateReportEditMark 更新研报当前更新状态
  1009. // status 枚举值 1:编辑中, 2:只做查询,3:完成编辑
  1010. func UpdateReportEditMark(reportId, reportChapterId, nowUserId, status int, nowUserName, lang string) (ret models.MarkReportResp, err error) {
  1011. //更新标记key
  1012. key := fmt.Sprint(`crm:report:edit:`, reportId)
  1013. // 章节id不为0则加上章节id
  1014. if reportChapterId > 0 {
  1015. key = fmt.Sprint(key, ":", reportChapterId)
  1016. }
  1017. ret.Status = 0
  1018. ret.Msg = "无人编辑"
  1019. opUserId, e := utils.Rc.RedisInt(key)
  1020. var opUser models.MarkReportItem
  1021. var classifyNameFirst string
  1022. if e != nil {
  1023. opUserInfoStr, tErr := utils.Rc.RedisString(key)
  1024. if tErr == nil {
  1025. tErr = json.Unmarshal([]byte(opUserInfoStr), &opUser)
  1026. if tErr == nil {
  1027. opUserId = opUser.AdminId
  1028. }
  1029. }
  1030. }
  1031. //判断是否是晨报或者周报,如果是则跳过
  1032. var reportInfo *models.ReportDetail
  1033. classifyNameFirst = opUser.ReportClassifyNameFirst
  1034. if reportId > 0 && status != 2 && classifyNameFirst == "" {
  1035. //查询报告ID信息
  1036. reportInfo, err = models.GetReportById(reportId)
  1037. if err != nil {
  1038. err = fmt.Errorf("报告不存在")
  1039. return
  1040. }
  1041. classifyNameFirst = reportInfo.ClassifyNameFirst
  1042. }
  1043. if opUserId > 0 && opUserId != nowUserId {
  1044. editor := opUser.Editor
  1045. if editor == "" {
  1046. //查询账号的用户姓名
  1047. otherInfo, e := system.GetSysAdminById(opUserId)
  1048. if e != nil {
  1049. err = fmt.Errorf("查询其他编辑者信息失败")
  1050. return
  1051. }
  1052. editor = otherInfo.RealName
  1053. }
  1054. ret.Status = 1
  1055. if lang == utils.EnLangVersion {
  1056. ret.Msg = fmt.Sprintf("%s is currently editing the report", editor)
  1057. } else {
  1058. ret.Msg = fmt.Sprintf("当前%s正在编辑报告", editor)
  1059. }
  1060. ret.Editor = editor
  1061. return
  1062. }
  1063. if status == 1 {
  1064. nowUser := &models.MarkReportItem{AdminId: nowUserId, Editor: nowUserName, ReportClassifyNameFirst: classifyNameFirst}
  1065. bt, e := json.Marshal(nowUser)
  1066. if e != nil {
  1067. err = fmt.Errorf("格式化编辑者信息失败")
  1068. return
  1069. }
  1070. if opUserId > 0 {
  1071. utils.Rc.Do("SETEX", key, int64(60), string(bt)) //3分钟缓存
  1072. } else {
  1073. utils.Rc.SetNX(key, string(bt), time.Second*60*1) //3分钟缓存
  1074. }
  1075. } else if status == 3 {
  1076. //完成编辑,开始清除编辑缓存
  1077. _ = utils.Rc.Delete(key)
  1078. }
  1079. return
  1080. }
  1081. // HandleVideoDecibel 处理报告中的音频文件
  1082. func HandleVideoDecibel(chapterInfo *models.ReportChapter) {
  1083. public_api.HandleVideoDecibel(chapterInfo.ReportChapterId)
  1084. return
  1085. }
  1086. // SaveReportLogs 记录报告日志
  1087. func SaveReportLogs(item *models.Report, chapters []*models.ReportChapter, adminId int, adminRealName string) {
  1088. if item == nil && len(chapters) == 0 {
  1089. return
  1090. }
  1091. var err error
  1092. defer func() {
  1093. if err != nil {
  1094. tips := fmt.Sprintf("报告日志记录, SaveReportLogs error: %s", err.Error())
  1095. go alarm_msg.SendAlarmMsg(tips, 2)
  1096. }
  1097. }()
  1098. if item != nil {
  1099. e := models.AddReportSaveLog(item.Id, item.AdminId, item.Content, item.ContentSub, item.ContentStruct, item.CanvasColor, item.AdminRealName, item.HeadResourceId, item.EndResourceId)
  1100. if e != nil {
  1101. err = fmt.Errorf("AddReportSaveLog: %s", e.Error())
  1102. return
  1103. }
  1104. }
  1105. if len(chapters) > 0 {
  1106. e := models.MultiAddReportChaptersSaveLog(chapters, adminId, adminRealName)
  1107. if e != nil {
  1108. err = fmt.Errorf("MultiAddReportChaptersSaveLog: %s", e.Error())
  1109. return
  1110. }
  1111. }
  1112. return
  1113. }