report.go 79 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685
  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/system"
  9. "eta/eta_api/services/alarm_msg"
  10. "eta/eta_api/services/public_api"
  11. "eta/eta_api/utils"
  12. "fmt"
  13. "github.com/PuerkitoBio/goquery"
  14. "html"
  15. "os"
  16. "regexp"
  17. "strconv"
  18. "strings"
  19. "time"
  20. )
  21. func GetReportContentSub(content string) (contentSub string, err error) {
  22. content = html.UnescapeString(content)
  23. doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
  24. if err != nil {
  25. fmt.Println("create doc err:", err.Error())
  26. return
  27. }
  28. n := 0
  29. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  30. if n >= 5 {
  31. return
  32. }
  33. n++
  34. phtml, err := s.Html()
  35. if err != nil {
  36. fmt.Println("get html err", err.Error())
  37. return
  38. }
  39. if s.Text() != "" || strings.Contains(phtml, "src") {
  40. contentSub = contentSub + "<p>" + phtml + "</p>"
  41. }
  42. })
  43. return
  44. }
  45. // PublishDayWeekReport 发布晨周报
  46. func PublishDayWeekReport(reportId int) (tips string, err error) {
  47. report, err := models.GetReportByReportId(reportId)
  48. if err != nil {
  49. return
  50. }
  51. if report.State == 2 {
  52. return
  53. }
  54. chapters, err := models.GetChapterListByReportId(reportId)
  55. if err != nil {
  56. return
  57. }
  58. chapterLen := len(chapters)
  59. if chapterLen <= 0 {
  60. err = errors.New("报告章节为空,不可发布")
  61. return
  62. }
  63. reportType := chapters[0].ReportType
  64. // 校验章节
  65. publishReport, tips, publishIdArr, unPublishIdArr, err := checkDayWeekChapterWrite(chapters, reportType)
  66. if err != nil {
  67. return
  68. }
  69. publishLen := len(publishIdArr)
  70. if publishLen <= 0 {
  71. err = errors.New("报告章节均不可发布")
  72. return
  73. }
  74. // 需发布整期
  75. updateCols := make([]string, 0)
  76. if publishReport {
  77. updateCols = append(updateCols, "Title", "State", "ModifyTime")
  78. // 发布后标题调整
  79. title := report.Title
  80. title = strings.ReplaceAll(title, "【弘则FICC晨报】", "")
  81. title = strings.ReplaceAll(title, "【弘则FICC周报】", "")
  82. if title == "" {
  83. // 取第一个需发布章节的标题
  84. firstId := publishIdArr[0]
  85. firstTitle := ""
  86. for i := 0; i < chapterLen; i++ {
  87. if chapters[i].ReportChapterId == firstId {
  88. firstTitle = chapters[i].Title
  89. break
  90. }
  91. }
  92. title = firstTitle
  93. }
  94. report.Title = title
  95. report.State = 2
  96. // 研报后台4.4 只在没有发布过时更新发布时间,其余均按模版消息发送时间当作发布时间
  97. if report.MsgIsSend == 0 || report.PublishTime.IsZero() {
  98. report.PublishTime = time.Now().Local()
  99. updateCols = append(updateCols, "PublishTime")
  100. }
  101. report.ModifyTime = time.Now().Local()
  102. }
  103. publishIdStr := utils.IntArr2joinString(publishIdArr, ",")
  104. //unPublishIdStr := utils.IntArr2joinString(unPublishIdArr, ",")
  105. if e := models.PublishReportAndChapter(report, publishIdArr, unPublishIdArr, publishReport, updateCols); e != nil {
  106. err = errors.New("发布报告及章节失败")
  107. return
  108. }
  109. // 生成章节音频
  110. go func() {
  111. _ = UpdateChaptersVideo(publishIdStr)
  112. }()
  113. // 更新报告ES
  114. go func() {
  115. _ = UpdateReportEs(report.Id, 2)
  116. }()
  117. // 发布时备份内容
  118. go SaveReportLogs(report, chapters, report.AdminId, report.AdminRealName)
  119. return
  120. }
  121. // UpdateChaptersVideo 更新章节音频
  122. func UpdateChaptersVideo(chapterIds string) (err error) {
  123. defer func() {
  124. if err != nil {
  125. utils.FileLog.Error("UpdateChaptersVideo, chapterIds:%s, Err:%s", chapterIds, err.Error())
  126. go alarm_msg.SendAlarmMsg("更新章节音频失败, 章节ID: "+chapterIds+", Err: "+err.Error(), 3)
  127. }
  128. }()
  129. if chapterIds == "" {
  130. return
  131. }
  132. ids := make([]int, 0)
  133. chapterIdArr := strings.Split(chapterIds, ",")
  134. for _, v := range chapterIdArr {
  135. id, e := strconv.Atoi(v)
  136. if e != nil {
  137. return
  138. }
  139. ids = append(ids, id)
  140. }
  141. chapterList, err := models.GetChapterListByChapterIds(ids)
  142. if err != nil {
  143. return
  144. }
  145. // 生成video
  146. nowTime := time.Now()
  147. updateCols := make([]string, 0)
  148. updateCols = append(updateCols, "VideoUrl", "VideoName", "VideoSize", "VideoPlaySeconds")
  149. for i := 0; i < len(chapterList); i++ {
  150. item := chapterList[i]
  151. // 忽略已有音频的章节
  152. if item.VideoUrl != "" && item.VideoName != "" && item.VideoSize != "" && item.VideoPlaySeconds != "" {
  153. continue
  154. }
  155. videoUrl, videoName, videoSize, videoPlaySeconds, e := CreateReportVideo(item.Title, html.UnescapeString(item.Content), nowTime.Format(utils.FormatDateTime))
  156. if e != nil {
  157. err = e
  158. return
  159. }
  160. item.VideoUrl = videoUrl
  161. item.VideoName = videoName
  162. item.VideoSize = videoSize
  163. item.VideoPlaySeconds = fmt.Sprintf("%.2f", videoPlaySeconds)
  164. if e = item.UpdateChapter(updateCols); e != nil {
  165. err = e
  166. }
  167. }
  168. return
  169. }
  170. // PublishTodayDayReport 发布今日晨报
  171. func PublishTodayDayReport() (err error) {
  172. nowTime := time.Now()
  173. startTime := time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.Local)
  174. endTime := time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 23, 59, 59, 0, time.Local)
  175. todayReport, err := models.GetUnPublishDayReport(startTime, endTime)
  176. if err != nil {
  177. if err.Error() == utils.ErrNoRow() { //如果是找不到待发送的晨报,那么需要将err置空
  178. err = nil
  179. }
  180. return
  181. }
  182. if todayReport != nil {
  183. if _, tmpErr := PublishDayWeekReport(todayReport.Id); tmpErr != nil {
  184. err = tmpErr
  185. return
  186. }
  187. // 定时发布的晨报自动推送客群
  188. reportDetail, tmpErr := models.GetReportById(todayReport.Id)
  189. if tmpErr != nil {
  190. err = tmpErr
  191. return
  192. }
  193. // 推送模板消息
  194. if tmpErr = SendMiniProgramReportWxMsg(todayReport.Id); tmpErr != nil {
  195. err = tmpErr
  196. return
  197. }
  198. if tmpErr = models.ModifyReportThsMsgIsSend(reportDetail); tmpErr != nil {
  199. err = tmpErr
  200. return
  201. }
  202. }
  203. return
  204. }
  205. // UpdateReportEs 更新报告/章节Es
  206. func UpdateReportEs(reportId int, publishState int) (err error) {
  207. if reportId <= 0 {
  208. return
  209. }
  210. reportInfo, err := models.GetReportByReportId(reportId)
  211. if err != nil {
  212. return
  213. }
  214. categories := ""
  215. if reportInfo.HasChapter == 1 {
  216. // 晨周报
  217. chapterList, tmpErr := models.GetPublishedChapterListByReportId(reportInfo.Id)
  218. if tmpErr != nil {
  219. return
  220. }
  221. if len(chapterList) > 0 {
  222. for i := 0; i < len(chapterList); i++ {
  223. // 章节对应的品种
  224. permissionList, tmpErr := models.GetChapterTypePermissionByReportChapterTypeId(chapterList[i].TypeId)
  225. if tmpErr != nil {
  226. return
  227. }
  228. categoryArr := make([]string, 0)
  229. if len(permissionList) > 0 {
  230. for ii := 0; ii < len(permissionList); ii++ {
  231. categoryArr = append(categoryArr, permissionList[ii].PermissionName)
  232. }
  233. }
  234. aliasArr, _ := addCategoryAliasToArr(categoryArr)
  235. chapterCategories := strings.Join(aliasArr, ",")
  236. esChapter := &models.ElasticReportDetail{
  237. ReportId: chapterList[i].ReportId,
  238. ReportChapterId: chapterList[i].ReportChapterId,
  239. Title: chapterList[i].Title,
  240. Abstract: chapterList[i].Abstract,
  241. BodyContent: utils.TrimHtml(html.UnescapeString(chapterList[i].Content)),
  242. PublishTime: chapterList[i].PublishTime.Format(utils.FormatDateTime),
  243. PublishState: chapterList[i].PublishState,
  244. Author: chapterList[i].Author,
  245. ClassifyIdFirst: chapterList[i].ClassifyIdFirst,
  246. ClassifyNameFirst: chapterList[i].ClassifyNameFirst,
  247. ClassifyIdSecond: 0,
  248. ClassifyNameSecond: "",
  249. Categories: chapterCategories,
  250. StageStr: strconv.Itoa(chapterList[i].Stage),
  251. }
  252. chapterDocId := fmt.Sprintf("%d-%d", reportInfo.Id, chapterList[i].ReportChapterId)
  253. if err = EsAddOrEditReport(utils.EsReportIndexName, chapterDocId, esChapter); err != nil {
  254. return
  255. }
  256. }
  257. }
  258. } else {
  259. //if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
  260. permissionList, tmpErr := models.GetChartPermissionNameFromMappingByKeyword(reportInfo.ClassifyNameSecond, "rddp")
  261. if tmpErr != nil {
  262. return
  263. }
  264. categoryArr := make([]string, 0)
  265. for i := 0; i < len(permissionList); i++ {
  266. categoryArr = append(categoryArr, permissionList[i].PermissionName)
  267. }
  268. aliasArr, _ := addCategoryAliasToArr(categoryArr)
  269. categories = strings.Join(aliasArr, ",")
  270. //}
  271. }
  272. // 新增报告ES
  273. esReport := &models.ElasticReportDetail{
  274. ReportId: reportInfo.Id,
  275. ReportChapterId: 0,
  276. Title: reportInfo.Title,
  277. Abstract: reportInfo.Abstract,
  278. BodyContent: utils.TrimHtml(html.UnescapeString(reportInfo.Content)),
  279. PublishTime: reportInfo.PublishTime.Format(utils.FormatDateTime),
  280. PublishState: publishState,
  281. Author: reportInfo.Author,
  282. ClassifyIdFirst: reportInfo.ClassifyIdFirst,
  283. ClassifyNameFirst: reportInfo.ClassifyNameFirst,
  284. ClassifyIdSecond: reportInfo.ClassifyIdSecond,
  285. ClassifyNameSecond: reportInfo.ClassifyNameSecond,
  286. Categories: categories,
  287. StageStr: strconv.Itoa(reportInfo.Stage),
  288. }
  289. docId := fmt.Sprintf("%d-%d", reportInfo.Id, 0)
  290. if err = EsAddOrEditReport(utils.EsReportIndexName, docId, esReport); err != nil {
  291. return
  292. }
  293. return
  294. }
  295. // addCategoryAliasToArr 品种别名
  296. func addCategoryAliasToArr(categoryArr []string) (aliasArr []string, err error) {
  297. aliasArr = categoryArr
  298. if len(categoryArr) > 0 {
  299. for i := 0; i < len(categoryArr); i++ {
  300. if strings.Contains(categoryArr[i], "沥青") {
  301. aliasArr = append(aliasArr, "BU")
  302. }
  303. if strings.Contains(categoryArr[i], "MEG") {
  304. aliasArr = append(aliasArr, "EG", "乙二醇")
  305. }
  306. if strings.Contains(categoryArr[i], "聚酯") {
  307. aliasArr = append(aliasArr, "长丝", "短纤", "瓶片")
  308. }
  309. if strings.Contains(categoryArr[i], "纯苯+苯乙烯") {
  310. aliasArr = append(aliasArr, "EB")
  311. }
  312. if strings.Contains(categoryArr[i], "聚乙烯") {
  313. aliasArr = append(aliasArr, "PP", "PE")
  314. }
  315. if strings.Contains(categoryArr[i], "玻璃纯碱") {
  316. aliasArr = append(aliasArr, "玻璃", "纯碱", "FG", "SA")
  317. }
  318. if strings.Contains(categoryArr[i], "甲醇") {
  319. aliasArr = append(aliasArr, "甲醇", "MA")
  320. }
  321. if strings.Contains(categoryArr[i], "橡胶") {
  322. aliasArr = append(aliasArr, "橡胶", "RU")
  323. }
  324. }
  325. }
  326. return
  327. }
  328. // UpdateReportChapterEs 更新报告章节ES
  329. func UpdateReportChapterEs(reportChapterId int) (err error) {
  330. if reportChapterId <= 0 {
  331. return
  332. }
  333. chapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
  334. if err != nil {
  335. return
  336. }
  337. // 章节对应的品种
  338. permissionList, tmpErr := models.GetChapterTypePermissionByReportChapterTypeId(chapterInfo.TypeId)
  339. if tmpErr != nil {
  340. return
  341. }
  342. categoryArr := make([]string, 0)
  343. if len(permissionList) > 0 {
  344. for ii := 0; ii < len(permissionList); ii++ {
  345. categoryArr = append(categoryArr, permissionList[ii].PermissionName)
  346. }
  347. }
  348. aliasArr, _ := addCategoryAliasToArr(categoryArr)
  349. categories := strings.Join(aliasArr, ",")
  350. // 新增/编辑ES
  351. esChapter := &models.ElasticReportDetail{
  352. ReportId: chapterInfo.ReportId,
  353. ReportChapterId: chapterInfo.ReportChapterId,
  354. Title: chapterInfo.Title,
  355. Abstract: chapterInfo.Abstract,
  356. BodyContent: utils.TrimHtml(html.EscapeString(chapterInfo.Content)),
  357. PublishTime: chapterInfo.PublishTime.Format(utils.FormatDateTime),
  358. PublishState: chapterInfo.PublishState,
  359. Author: chapterInfo.Author,
  360. ClassifyIdFirst: chapterInfo.ClassifyIdFirst,
  361. ClassifyNameFirst: chapterInfo.ClassifyNameFirst,
  362. ClassifyIdSecond: 0,
  363. ClassifyNameSecond: "",
  364. Categories: categories,
  365. StageStr: strconv.Itoa(chapterInfo.Stage),
  366. }
  367. chapterDocId := fmt.Sprintf("%d-%d", chapterInfo.ReportId, chapterInfo.ReportChapterId)
  368. if err = EsAddOrEditReport(utils.EsReportIndexName, chapterDocId, esChapter); err != nil {
  369. return
  370. }
  371. return
  372. }
  373. // DeleteReportAndChapter 删除报告及章节
  374. func DeleteReportAndChapter(reportId int) (err error) {
  375. reportInfo, err := models.GetReportByReportId(reportId)
  376. if err != nil {
  377. err = errors.New("报告信息有误, Err: " + err.Error())
  378. return
  379. }
  380. if reportInfo.State == 2 {
  381. err = errors.New("报告已发布,不可删除")
  382. return
  383. }
  384. // 更新ES
  385. _ = UpdateReportEs(reportId, 1)
  386. // 删除
  387. if reportInfo.HasChapter == 1 && (reportInfo.ChapterType == utils.REPORT_TYPE_DAY || reportInfo.ChapterType == utils.REPORT_TYPE_WEEK) {
  388. err = models.DeleteDayWeekReportAndChapter(reportId)
  389. } else {
  390. err = models.DeleteReport(reportId)
  391. }
  392. if err != nil {
  393. err = errors.New("删除失败, Err: " + err.Error())
  394. return
  395. }
  396. // 重置PPT关联报告
  397. go func() {
  398. _ = ResetPPTReport(reportId, false)
  399. }()
  400. return
  401. }
  402. // 替换报告内容中的base64图片
  403. func replaceReportBase64ToImg(content string) (newContent string, err error) {
  404. if content == "" {
  405. return
  406. }
  407. 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}==)?"
  408. re, _ := regexp.Compile(pattern)
  409. matcher := re.FindAllString(content, 999)
  410. if len(matcher) > 0 {
  411. for _, v := range matcher {
  412. imgUrl, tmpErr := reportBase64ToImg(v)
  413. if tmpErr != nil {
  414. err = tmpErr
  415. return
  416. }
  417. content = strings.ReplaceAll(content, v, imgUrl)
  418. }
  419. }
  420. newContent = content
  421. return
  422. }
  423. // 转换base64图片为img并上传
  424. func reportBase64ToImg(imageBase64 string) (resourceUrl string, err error) {
  425. if imageBase64 == "" {
  426. err = errors.New("图片为空")
  427. return
  428. }
  429. ext := ".png"
  430. uploadDir := "./static"
  431. randStr := utils.GetRandStringNoSpecialChar(28)
  432. fileName := randStr + ext
  433. fpath := uploadDir + "/" + fileName
  434. b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, imageBase64)
  435. if !b {
  436. err = errors.New("图片格式不正确")
  437. return
  438. }
  439. re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
  440. base64Str := re.ReplaceAllString(imageBase64, "")
  441. base64Str = strings.Replace(base64Str, " ", "", -1)
  442. err = utils.SaveBase64ToFile(base64Str, fpath)
  443. if err != nil {
  444. err = errors.New("图片保存失败" + err.Error())
  445. return
  446. }
  447. defer os.Remove(fpath)
  448. hzUploadDir := utils.RESOURCE_DIR + "images/"
  449. savePath := hzUploadDir + time.Now().Format("200601/20060102/")
  450. savePath += fileName
  451. //上传到阿里云 和 minio
  452. //if utils.ObjectStorageClient == "minio" {
  453. // err = UploadFileToMinIo(fileName, fpath, savePath)
  454. // if err != nil {
  455. // err = errors.New("文件上传失败" + err.Error())
  456. // return
  457. // }
  458. // resourceUrl = utils.MinIoImghost + savePath
  459. //} else {
  460. // err = UploadFileToAliyun(fileName, fpath, savePath)
  461. // if err != nil {
  462. // err = errors.New("文件上传失败" + err.Error())
  463. // return
  464. // }
  465. // resourceUrl = utils.Imghost + savePath
  466. //}
  467. ossClient := NewOssClient()
  468. if ossClient == nil {
  469. err = fmt.Errorf("初始化OSS服务失败")
  470. return
  471. }
  472. resourceUrl, err = ossClient.UploadFile(fileName, fpath, savePath)
  473. if err != nil {
  474. err = fmt.Errorf("文件上传失败, Err: %s", err.Error())
  475. return
  476. }
  477. item := new(models.Resource)
  478. item.ResourceUrl = resourceUrl
  479. item.ResourceType = 1
  480. item.CreateTime = time.Now()
  481. _, err = models.AddResource(item)
  482. if err != nil {
  483. err = errors.New("资源上传失败" + err.Error())
  484. return
  485. }
  486. return
  487. }
  488. // UpdateReportVideo 更新报告及其章节音频
  489. func UpdateReportVideo(reportId int) (err error) {
  490. defer func() {
  491. if err != nil {
  492. utils.FileLog.Error("UpdateReportVideo, reportId:%s, Err:%s", strconv.Itoa(reportId), err.Error())
  493. go alarm_msg.SendAlarmMsg("更新报告音频失败, 报告ID: "+strconv.Itoa(reportId)+", Err: "+err.Error(), 3)
  494. //go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "更新报告音频失败, 报告ID: " + reportIdStr + ", Err: "+err.Error(), utils.EmailSendToUsers)
  495. }
  496. }()
  497. if reportId == 0 {
  498. return
  499. }
  500. reportInfo, err := models.GetReportByReportId(reportId)
  501. if err != nil {
  502. return
  503. }
  504. if reportInfo.HasChapter == 1 {
  505. // 更新章节音频
  506. chapterList, tmpErr := models.GetPublishedChapterListByReportId(reportInfo.Id)
  507. if tmpErr != nil {
  508. err = tmpErr
  509. return
  510. }
  511. chapterIdArr := make([]string, 0)
  512. for i := 0; i < len(chapterList); i++ {
  513. chapterIdArr = append(chapterIdArr, strconv.Itoa(chapterList[i].ReportChapterId))
  514. }
  515. chapterIds := strings.Join(chapterIdArr, ",")
  516. //go UpdateChaptersVideo(chapterIds)
  517. err = UpdateChaptersVideo(chapterIds)
  518. } else {
  519. // 更新报告音频
  520. if reportInfo.VideoUrl != "" {
  521. return
  522. }
  523. nowTime := time.Now()
  524. updateCols := make([]string, 0)
  525. updateCols = append(updateCols, "VideoUrl", "VideoName", "VideoSize", "VideoPlaySeconds")
  526. videoUrl, videoName, videoSize, videoPlaySeconds, tmpErr := CreateReportVideo(reportInfo.Title, html.UnescapeString(reportInfo.Content), nowTime.Format(utils.FormatDateTime))
  527. reportInfo.VideoUrl = videoUrl
  528. reportInfo.VideoName = videoName
  529. reportInfo.VideoSize = videoSize
  530. reportInfo.VideoPlaySeconds = fmt.Sprintf("%.2f", videoPlaySeconds)
  531. tmpErr = reportInfo.UpdateReport(updateCols)
  532. if tmpErr != nil {
  533. err = tmpErr
  534. return
  535. }
  536. }
  537. return
  538. }
  539. func UpdateEmptyVideoReportVideo() (err error) {
  540. list, err := models.GetSyncEmptyVideoReport()
  541. if err != nil {
  542. return
  543. }
  544. listLen := len(list)
  545. if listLen <= 0 {
  546. fmt.Println("无报告需要更新音频")
  547. return
  548. }
  549. fmt.Println("Start 待更新报告音频数: ", listLen)
  550. for i := 0; i < listLen; i++ {
  551. if err = UpdateReportVideo(list[i].Id); err != nil {
  552. fmt.Printf("更新音频失败")
  553. fmt.Println(err.Error())
  554. return
  555. }
  556. }
  557. fmt.Println("End 报告音频更新完毕")
  558. return
  559. }
  560. // checkDayWeekChapterWrite 校验晨周报已写章节与本期应写章节
  561. func checkDayWeekChapterWrite(chapters []*models.ReportChapter, reportType string) (publishReport bool, tips string, publishIdArr, unPublishIdArr []int, err error) {
  562. nowTime := time.Now().Local()
  563. updateTypeArr := make([]int, 0) // 需更新的章节类型IDs
  564. publishIdArr = make([]int, 0) // 需发布的章节IDs
  565. unPublishIdArr = make([]int, 0) // 需取消发布/未发布的章节IDs
  566. // 校验章节内容
  567. if reportType == utils.REPORT_TYPE_DAY {
  568. // 晨报章节不能都为空
  569. isEmpty := true
  570. for i := 0; i < len(chapters); i++ {
  571. if chapters[i].Content != "" && chapters[i].Title != "" {
  572. isEmpty = false
  573. break
  574. }
  575. }
  576. if isEmpty {
  577. err = errors.New("报告章节内容均为空或标题为空,不可发布")
  578. return
  579. }
  580. } else {
  581. // 周报章节需至少有一篇已编辑且有标题
  582. editNum := 0
  583. for i := 0; i < len(chapters); i++ {
  584. if chapters[i].IsEdit == 1 && chapters[i].Title != "" {
  585. editNum += 1
  586. }
  587. }
  588. if editNum == 0 {
  589. err = errors.New("报告均未编辑或标题为空,不可发布")
  590. return
  591. }
  592. }
  593. // 章节类型列表
  594. types, e := models.GetReportChapterTypeListByResearchType(reportType)
  595. if e != nil {
  596. err = errors.New("获取章节类型列表失败")
  597. return
  598. }
  599. // 本期需更新的章节IDs
  600. typeLen := len(types)
  601. for i := 0; i < typeLen; i++ {
  602. if types[i].IsSet != 1 && types[i].Enabled != 0 {
  603. // 正常更新
  604. updateTypeArr = append(updateTypeArr, types[i].ReportChapterTypeId)
  605. } else {
  606. // 被设置为零值的也算作正常更新
  607. if types[i].PauseStartTime == utils.EmptyDateStr && types[i].PauseEndTime == utils.EmptyDateStr {
  608. updateTypeArr = append(updateTypeArr, types[i].ReportChapterTypeId)
  609. continue
  610. }
  611. // 暂停更新需校验时间
  612. startTime, _ := time.Parse(utils.FormatDate, types[i].PauseStartTime)
  613. endTime, _ := time.Parse(utils.FormatDate, types[i].PauseEndTime)
  614. if nowTime.Before(startTime) || nowTime.After(endTime.AddDate(0, 0, 1)) {
  615. updateTypeArr = append(updateTypeArr, types[i].ReportChapterTypeId)
  616. }
  617. }
  618. }
  619. // 校验本期需更新的章节是否都已编辑
  620. chapterLen := len(chapters)
  621. updateTypeLen := len(updateTypeArr)
  622. tipsArr := make([]string, 0)
  623. for i := 0; i < chapterLen; i++ {
  624. isWrite := false
  625. for ii := 0; ii < updateTypeLen; ii++ {
  626. // 本期应发布的章节
  627. if chapters[i].TypeId == updateTypeArr[ii] {
  628. // 标题或者内容为空的情况下, 记录tips提示信息且不发布该章节
  629. if chapters[i].Title == "" || chapters[i].Content == "" {
  630. tipsArr = append(tipsArr, chapters[i].TypeName)
  631. break
  632. }
  633. isWrite = true
  634. break
  635. }
  636. }
  637. if isWrite {
  638. publishIdArr = append(publishIdArr, chapters[i].ReportChapterId)
  639. } else {
  640. unPublishIdArr = append(unPublishIdArr, chapters[i].ReportChapterId)
  641. }
  642. }
  643. if len(tipsArr) > 0 {
  644. tips = "部分章节未发布:" + strings.Join(tipsArr, "、") + "未填写标题/内容"
  645. }
  646. // 周报需发布的章节与需更新的章节数相等则表示可发布整期, 晨报无限制
  647. if reportType == utils.REPORT_TYPE_DAY {
  648. publishReport = true
  649. } else {
  650. if len(publishIdArr) == updateTypeLen {
  651. publishReport = true
  652. }
  653. }
  654. return
  655. }
  656. // PcCreateAndUploadSunCode 生成太阳码并上传OSS
  657. func PcCreateAndUploadSunCode(scene, page string) (imgUrl string, err error) {
  658. if page == "" {
  659. err = errors.New("page不能为空")
  660. return
  661. }
  662. // scene超过32位会生成失败,md5处理至32位
  663. sceneMD5 := "a=1"
  664. if scene != "" {
  665. sceneMD5 = utils.MD5(scene)
  666. }
  667. picByte, err := GetSunCode(page, sceneMD5)
  668. if err != nil {
  669. return
  670. }
  671. // 生成图片
  672. localPath := "./static/imgs"
  673. fileName := utils.GetRandStringNoSpecialChar(28) + ".png"
  674. fpath := fmt.Sprint(localPath, "/", fileName)
  675. f, err := os.Create(fpath)
  676. if err != nil {
  677. fmt.Println("11111")
  678. return
  679. }
  680. if _, err = f.Write(picByte); err != nil {
  681. return
  682. }
  683. defer func() {
  684. f.Close()
  685. os.Remove(fpath)
  686. }()
  687. // 上传OSS
  688. fileDir := "yb/suncode/"
  689. //上传到阿里云 和 minio
  690. //if utils.ObjectStorageClient == "minio" {
  691. // imgUrl, err = UploadMinIoToDir(fileName, fpath, "", fileDir)
  692. // if err != nil {
  693. // return
  694. // }
  695. //} else {
  696. // imgUrl, err = UploadAliyunToDir(fileName, fpath, "", fileDir)
  697. // if err != nil {
  698. // return
  699. // }
  700. //}
  701. savePath := fileDir + time.Now().Format("200601/20060102/") + fileName
  702. ossClient := NewOssClient()
  703. if ossClient == nil {
  704. err = fmt.Errorf("初始化OSS服务失败")
  705. return
  706. }
  707. imgUrl, err = ossClient.UploadFile(fileName, fpath, savePath)
  708. if err != nil {
  709. err = fmt.Errorf("文件上传失败, Err: %s", err.Error())
  710. return
  711. }
  712. if err != nil {
  713. return
  714. }
  715. // 记录参数
  716. if scene != "" {
  717. newSuncode := &models.YbPcSuncode{
  718. Scene: scene,
  719. SceneMd5: sceneMD5,
  720. CodePage: page,
  721. SuncodeUrl: imgUrl,
  722. CreateTime: time.Now(),
  723. }
  724. err = models.AddYbPcSunCode(newSuncode)
  725. }
  726. // 记录参数md5
  727. if scene != "" {
  728. newPars := &models.YbSuncodePars{
  729. Scene: scene,
  730. SceneKey: sceneMD5,
  731. CreateTime: time.Now(),
  732. }
  733. err = models.AddYbSuncodePars(newPars)
  734. }
  735. return
  736. }
  737. // CreateNewReport 创建新报告
  738. func CreateNewReport(req models.AddReq, adminInfo *system.Admin) (newReportId int64, reportCode, errMsg string, err error) {
  739. contentSub := ""
  740. if req.Content != "" {
  741. e := utils.ContentXssCheck(req.Content)
  742. if e != nil {
  743. errMsg = "存在非法标签"
  744. err = errors.New("存在非法标签, Err: " + e.Error())
  745. return
  746. }
  747. contentClean, e := FilterReportContentBr(req.Content)
  748. if e != nil {
  749. errMsg = "内容去除前后空格失败"
  750. err = errors.New("内容去除前后空格失败, Err: " + e.Error())
  751. return
  752. }
  753. req.Content = contentClean
  754. sub, e := GetReportContentSub(req.Content)
  755. if e != nil {
  756. go alarm_msg.SendAlarmMsg("ContentSub 失败,Err:"+e.Error(), 3)
  757. }
  758. contentSub = sub
  759. }
  760. maxStage, e := models.GetReportStage(req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird)
  761. if e != nil {
  762. errMsg = "期数获取失败!"
  763. err = errors.New("期数获取失败,Err:" + e.Error())
  764. return
  765. }
  766. item := new(models.Report)
  767. item.AddType = req.AddType
  768. item.ClassifyIdFirst = req.ClassifyIdFirst
  769. item.ClassifyNameFirst = req.ClassifyNameFirst
  770. item.ClassifyIdSecond = req.ClassifyIdSecond
  771. item.ClassifyNameSecond = req.ClassifyNameSecond
  772. item.Title = req.Title
  773. item.Abstract = req.Abstract
  774. item.Author = req.Author
  775. item.Frequency = req.Frequency
  776. item.State = req.State
  777. item.Content = html.EscapeString(req.Content)
  778. item.Stage = maxStage + 1
  779. item.ContentSub = html.EscapeString(contentSub)
  780. item.CreateTime = req.CreateTime
  781. item.ModifyTime = time.Now()
  782. item.ReportVersion = req.ReportVersion
  783. item.AdminId = adminInfo.AdminId
  784. item.AdminRealName = adminInfo.RealName
  785. newReportId, e = models.AddReport(item)
  786. if e != nil {
  787. errMsg = "保存失败"
  788. err = errors.New("保存失败,Err:" + e.Error())
  789. return
  790. }
  791. // 处理权限
  792. //if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
  793. go func() {
  794. permissionItems, e := models.GetPermission(req.ClassifyNameSecond)
  795. if e != nil {
  796. alarm_msg.SendAlarmMsg("获取权限失败,Err:"+err.Error(), 3)
  797. }
  798. for _, v := range permissionItems {
  799. e = models.AddChartPermissionChapterMapping(v.ChartPermissionId, newReportId)
  800. if e != nil {
  801. alarm_msg.SendAlarmMsg("新增权限失败,Err:"+err.Error(), 3)
  802. }
  803. }
  804. // 同步crm权限
  805. _ = EditReportPermissionSync(newReportId, req.ClassifyNameSecond)
  806. }()
  807. //}
  808. reportCode = utils.MD5(strconv.Itoa(int(newReportId)))
  809. //修改唯一编码
  810. {
  811. go models.ModifyReportCode(newReportId, reportCode)
  812. }
  813. return
  814. }
  815. // FilterReportContentBr 过滤报告正文前后换行符
  816. func FilterReportContentBr(content string) (res string, err error) {
  817. newContent := content
  818. //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>`
  819. content = html.UnescapeString(content)
  820. if content == "" {
  821. return
  822. }
  823. // 过滤编辑器版权html
  824. 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)
  825. defer func() {
  826. if err != nil {
  827. go alarm_msg.SendAlarmMsg("过滤报告正文前后换行符及空格失败, ErrMsg: "+err.Error(), 3)
  828. }
  829. }()
  830. // 做一个配置,有问题的时候随时关闭
  831. configKey := "report_filter_br"
  832. conf, e := company.GetConfigDetailByCode(configKey)
  833. if e != nil {
  834. err = errors.New("获取报告过滤配置失败, Err: " + e.Error())
  835. return
  836. }
  837. if conf.ConfigValue != "1" {
  838. return content, nil
  839. }
  840. // 找出所有<p>标签, <p>标签的索引
  841. re := regexp.MustCompile(`(?is:<p(.*?)</p>)`)
  842. arr := re.FindAllString(content, -1)
  843. indexArr := re.FindAllIndex([]byte(content), -1)
  844. // 空<p>正则
  845. emptyRe := `<p[^>]*>(<br>|<br/>)+</p>`
  846. startIsBr := false
  847. countEmptyBr := 0 // 需要连续替换的空<p>总数
  848. lastBrRange := 0 // 最后一个空<p>右侧index, 用来判断是否为连续的空<p>
  849. // 注:以下逻辑只适用于去除前面的空行, 由于编辑器始终会在文章最后面跟上自己的html标签, 此处不再进行后面空行的去除=_=!
  850. for i := range arr {
  851. byteRange := indexArr[i]
  852. if len(byteRange) == 2 {
  853. // 内容开头不为<p>直接跳出遍历, 否则才进行空<p>的判断
  854. if i == 0 && byteRange[0] == 0 {
  855. startIsBr = true
  856. }
  857. if !startIsBr {
  858. break
  859. }
  860. if lastBrRange != 0 {
  861. // 说明不是连续的空<p>, 中间出现了其他标签, 那么结束遍历, 进行最终的文本替换
  862. if lastBrRange != byteRange[0] {
  863. break
  864. }
  865. }
  866. // 正则匹配为空<p>则计数, 记录该空<p>右侧index
  867. m, e := regexp.Match(emptyRe, []byte(arr[i]))
  868. if e != nil {
  869. err = e
  870. return
  871. }
  872. if m {
  873. countEmptyBr += 1
  874. lastBrRange = byteRange[1]
  875. continue
  876. }
  877. // 遍历到该<p>标签不为空了, 结束遍历
  878. break
  879. }
  880. }
  881. if countEmptyBr > 0 {
  882. reg, e := regexp.Compile(emptyRe)
  883. if e != nil {
  884. err = errors.New("正则解析失败, Err: " + e.Error())
  885. return
  886. }
  887. counted := 0 // 已替换数
  888. res = reg.ReplaceAllStringFunc(content, func(s string) string {
  889. counted += 1
  890. if counted <= countEmptyBr {
  891. return ""
  892. } else {
  893. return s
  894. }
  895. })
  896. if res == "" {
  897. res = newContent
  898. }
  899. } else {
  900. res = content
  901. if res == "" {
  902. res = newContent
  903. }
  904. }
  905. return
  906. }
  907. // GetEnglishReportOverview 获取英文研报overview部分
  908. func GetEnglishReportOverview(content string) (res string, err error) {
  909. content = html.UnescapeString(content)
  910. doc, e := goquery.NewDocumentFromReader(strings.NewReader(content))
  911. if e != nil {
  912. err = errors.New("Create Doc Err: " + e.Error())
  913. return
  914. }
  915. target := "overview"
  916. label := "</strong>"
  917. start := -1
  918. end := -1
  919. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  920. h, e := s.Html()
  921. if e != nil {
  922. err = errors.New("Get Html1 Err: " + e.Error())
  923. return
  924. }
  925. h = strings.ToLower(h)
  926. t := s.Text()
  927. t = strings.ToLower(t)
  928. if strings.Contains(h, label) && t != "" && strings.Contains(t, target) {
  929. start = i
  930. }
  931. if start != -1 && end == -1 && i > start && strings.Contains(h, label) {
  932. end = i
  933. }
  934. })
  935. if start != -1 && end != -1 {
  936. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  937. if i > start && i < end {
  938. h, e := s.Html()
  939. if e != nil {
  940. err = errors.New("Get Html2 Err: " + e.Error())
  941. return
  942. }
  943. // 包含iframe则过滤掉
  944. if strings.Contains(h, "iframe") {
  945. return
  946. }
  947. res += `<p>` + h + `</p>`
  948. }
  949. })
  950. }
  951. return
  952. }
  953. // GetReportContentSubWithoutIframe 获取报告正文前几段,过滤iframe
  954. func GetReportContentSubWithoutIframe(content string) (contentSub string, err error) {
  955. content = html.UnescapeString(content)
  956. doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
  957. if err != nil {
  958. fmt.Println("create doc err:", err.Error())
  959. return
  960. }
  961. label := "iframe"
  962. n := 0
  963. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  964. if n >= 5 {
  965. return
  966. }
  967. n++
  968. h, err := s.Html()
  969. if err != nil {
  970. fmt.Println("get html err", err.Error())
  971. return
  972. }
  973. // 包含iframe则过滤掉
  974. if strings.Contains(h, label) {
  975. return
  976. }
  977. if s.Text() != "" || strings.Contains(h, "src") {
  978. contentSub = contentSub + "<p>" + h + "</p>"
  979. }
  980. })
  981. return
  982. }
  983. // UpdateReportEditMark 更新研报当前更新状态
  984. // status 枚举值 1:编辑中,0:完成编辑, 2:只做查询
  985. func UpdateReportEditMark(reportId, reportChapterId, nowUserId, status int, nowUserName, lang string) (ret models.MarkReportResp, err error) {
  986. //更新标记key
  987. key := fmt.Sprint(`crm:report:edit:`, reportId)
  988. // 章节id不为0则加上章节id
  989. if reportChapterId > 0 {
  990. key = fmt.Sprint(key, ":", reportChapterId)
  991. }
  992. ret.Status = 0
  993. ret.Msg = "无人编辑"
  994. opUserId, e := utils.Rc.RedisInt(key)
  995. var opUser models.MarkReportItem
  996. var classifyNameFirst string
  997. if e != nil {
  998. opUserInfoStr, tErr := utils.Rc.RedisString(key)
  999. if tErr == nil {
  1000. tErr = json.Unmarshal([]byte(opUserInfoStr), &opUser)
  1001. if tErr == nil {
  1002. opUserId = opUser.AdminId
  1003. }
  1004. }
  1005. }
  1006. //判断是否是晨报或者周报,如果是则跳过
  1007. var reportInfo *models.ReportDetail
  1008. classifyNameFirst = opUser.ReportClassifyNameFirst
  1009. if reportId > 0 && status != 2 && classifyNameFirst == "" {
  1010. //查询报告ID信息
  1011. reportInfo, err = models.GetReportById(reportId)
  1012. if err != nil {
  1013. err = fmt.Errorf("报告不存在")
  1014. return
  1015. }
  1016. classifyNameFirst = reportInfo.ClassifyNameFirst
  1017. }
  1018. if opUserId > 0 && opUserId != nowUserId {
  1019. editor := opUser.Editor
  1020. if editor == "" {
  1021. //查询账号的用户姓名
  1022. otherInfo, e := system.GetSysAdminById(opUserId)
  1023. if e != nil {
  1024. err = fmt.Errorf("查询其他编辑者信息失败")
  1025. return
  1026. }
  1027. editor = otherInfo.RealName
  1028. }
  1029. ret.Status = 1
  1030. if lang == utils.EnLangVersion {
  1031. ret.Msg = fmt.Sprintf("%s is currently editing the report", editor)
  1032. } else {
  1033. ret.Msg = fmt.Sprintf("当前%s正在编辑报告", editor)
  1034. }
  1035. ret.Editor = editor
  1036. return
  1037. }
  1038. if status == 1 {
  1039. nowUser := &models.MarkReportItem{AdminId: nowUserId, Editor: nowUserName, ReportClassifyNameFirst: classifyNameFirst}
  1040. bt, e := json.Marshal(nowUser)
  1041. if e != nil {
  1042. err = fmt.Errorf("格式化编辑者信息失败")
  1043. return
  1044. }
  1045. if opUserId > 0 {
  1046. utils.Rc.Do("SETEX", key, int64(180), string(bt)) //3分钟缓存
  1047. } else {
  1048. utils.Rc.SetNX(key, string(bt), time.Second*60*3) //3分钟缓存
  1049. }
  1050. } else if status == 0 {
  1051. //清除编辑缓存
  1052. _ = utils.Rc.Delete(key)
  1053. }
  1054. return
  1055. }
  1056. // HandleVideoDecibel 处理报告中的音频文件
  1057. func HandleVideoDecibel(chapterInfo *models.ReportChapter) {
  1058. public_api.HandleVideoDecibel(chapterInfo.ReportChapterId)
  1059. return
  1060. }
  1061. // SaveReportLogs 记录报告日志
  1062. func SaveReportLogs(item *models.Report, chapters []*models.ReportChapter, adminId int, adminRealName string) {
  1063. if item == nil && len(chapters) == 0 {
  1064. return
  1065. }
  1066. var err error
  1067. defer func() {
  1068. if err != nil {
  1069. tips := fmt.Sprintf("报告日志记录, SaveReportLogs error: %s", err.Error())
  1070. go alarm_msg.SendAlarmMsg(tips, 2)
  1071. }
  1072. }()
  1073. if item != nil {
  1074. e := models.AddReportSaveLog(item.Id, item.AdminId, item.Content, item.ContentSub, item.ContentStruct, item.CanvasColor, item.AdminRealName, item.HeadResourceId, item.EndResourceId)
  1075. if e != nil {
  1076. err = fmt.Errorf("AddReportSaveLog: %s", e.Error())
  1077. return
  1078. }
  1079. }
  1080. if len(chapters) > 0 {
  1081. e := models.MultiAddReportChaptersSaveLog(chapters, adminId, adminRealName)
  1082. if e != nil {
  1083. err = fmt.Errorf("MultiAddReportChaptersSaveLog: %s", e.Error())
  1084. return
  1085. }
  1086. }
  1087. return
  1088. }
  1089. // AddReportAndChapter
  1090. // @Description: 新增报告(包含章节)
  1091. // @author: Roc
  1092. // @datetime 2024-06-04 11:23:20
  1093. // @param reportInfo *models.Report
  1094. // @param inheritReportId int
  1095. // @return err error
  1096. // @return errMsg string
  1097. func AddReportAndChapter(reportInfo *models.Report, inheritReportId int) (err error, errMsg string) {
  1098. // 获取最小分类id
  1099. minClassifyId := reportInfo.ClassifyIdThird
  1100. if minClassifyId <= 0 {
  1101. minClassifyId = reportInfo.ClassifyIdSecond
  1102. }
  1103. if minClassifyId <= 0 {
  1104. minClassifyId = reportInfo.ClassifyIdFirst
  1105. }
  1106. if minClassifyId <= 0 {
  1107. errMsg = "分类异常"
  1108. err = errors.New(errMsg)
  1109. return
  1110. }
  1111. errMsg = "生成报告失败"
  1112. // 报告继承
  1113. if inheritReportId > 0 {
  1114. inheritReport, tmpErr := models.GetReportByReportId(inheritReportId)
  1115. if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
  1116. errMsg = "获取待继承的报告失败"
  1117. err = tmpErr
  1118. return
  1119. }
  1120. if inheritReport != nil {
  1121. // 判断当前的报告分类与继承的报告分类是否一致
  1122. if inheritReport.ClassifyIdFirst != reportInfo.ClassifyIdFirst || inheritReport.ClassifyIdSecond != reportInfo.ClassifyIdSecond || inheritReport.ClassifyIdThird != reportInfo.ClassifyIdThird {
  1123. errMsg = "分类异常,与继承的报告分类不一致"
  1124. err = tmpErr
  1125. return
  1126. }
  1127. reportInfo.ChapterType = inheritReport.ChapterType
  1128. reportInfo.Content = inheritReport.Content
  1129. reportInfo.ContentSub = inheritReport.ContentSub
  1130. reportInfo.ContentStruct = inheritReport.ContentStruct
  1131. reportInfo.HeadImg = inheritReport.HeadImg
  1132. reportInfo.EndImg = inheritReport.EndImg
  1133. reportInfo.CanvasColor = inheritReport.CanvasColor
  1134. reportInfo.NeedSplice = inheritReport.NeedSplice
  1135. reportInfo.HeadResourceId = inheritReport.HeadResourceId
  1136. reportInfo.EndResourceId = inheritReport.EndResourceId
  1137. }
  1138. }
  1139. // 获取待生成的报告章节
  1140. addChapterList, allGrantUserList, err, errMsg := getAddChapter(reportInfo, minClassifyId, inheritReportId)
  1141. // 新增报告及章节
  1142. var reportId int64
  1143. reportId, err = models.AddReportAndChapter(reportInfo, allGrantUserList, addChapterList)
  1144. if err != nil {
  1145. err = errors.New("新增报告及章节失败, Err: " + err.Error())
  1146. return
  1147. }
  1148. reportCode := utils.MD5(strconv.Itoa(reportInfo.Id))
  1149. reportInfo.ReportCode = reportCode
  1150. // 修改唯一编码
  1151. {
  1152. go models.ModifyReportCode(reportId, reportCode)
  1153. }
  1154. // TODO 报告权限处理
  1155. //处理权限
  1156. //go func() {
  1157. // permissionItems, e := models.GetPermission(req.ClassifyNameSecond)
  1158. // if e != nil {
  1159. // alarm_msg.SendAlarmMsg("获取权限失败,Err:"+e.Error(), 3)
  1160. // return
  1161. // }
  1162. // for _, v := range permissionItems {
  1163. // e = models.AddChartPermissionChapterMapping(v.ChartPermissionId, int64(item.Id))
  1164. // if e != nil {
  1165. // alarm_msg.SendAlarmMsg("新增权限失败,Err:"+e.Error(), 3)
  1166. // return
  1167. // }
  1168. // }
  1169. // // 同步crm权限
  1170. // _ = EditReportPermissionSync(int64(item.Id), req.ClassifyNameSecond)
  1171. //}()
  1172. return
  1173. }
  1174. // getAddChapter
  1175. // @Description: 获取待新增的报告章节列表
  1176. // @author: Roc
  1177. // @datetime 2024-06-04 13:10:58
  1178. // @param reportInfo *models.Report
  1179. // @param minClassifyId int
  1180. // @param inheritReportId int
  1181. // @return chapterList []*models.ReportChapter
  1182. // @return err error
  1183. // @return errMsg string
  1184. func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int) (chapterList []models.AddReportChapter, allGrantUserList []*report.ReportGrant, err error, errMsg string) {
  1185. // 待生成的报告章节内容
  1186. chapterList = make([]models.AddReportChapter, 0)
  1187. // 所有的授权用户
  1188. allGrantUserList = make([]*report.ReportGrant, 0)
  1189. if reportInfo.HasChapter != 1 {
  1190. return
  1191. }
  1192. // TODO 弘则得单独处理启用禁用的情况
  1193. //if utils.BusinessCode != utils.BusinessCodeRelease && utils.BusinessCode != utils.BusinessCodeSandbox && utils.BusinessCode != utils.BusinessCodeDebug {
  1194. // br.Ret = 200
  1195. // br.Success = true
  1196. // br.Msg = "操作成功"
  1197. // return
  1198. //}
  1199. errMsg = "生成报告章节失败"
  1200. // 章节类型列表
  1201. typeList, err := models.GetReportChapterTypeListByClassifyId(minClassifyId)
  1202. if err != nil {
  1203. err = errors.New(errMsg)
  1204. return
  1205. }
  1206. // 分类章节的授权用户
  1207. typeGrantListMap := make(map[int][]*report.ReportChapterGrant)
  1208. // 自定义章节列表
  1209. customAddChapterList := make([]models.AddReportChapter, 0)
  1210. // 报告继承
  1211. inheritChapterMap := make(map[int]*models.ReportChapter)
  1212. if inheritReportId > 0 {
  1213. // 继承待继承的报告章节内容
  1214. inheritReportChapters, tmpErr := models.GetChapterListByReportId(inheritReportId)
  1215. if tmpErr != nil {
  1216. errMsg = "获取待继承的报告章节失败"
  1217. err = tmpErr
  1218. return
  1219. }
  1220. reportChaptersIdList := make([]int, 0)
  1221. for _, v := range inheritReportChapters {
  1222. reportChaptersIdList = append(reportChaptersIdList, v.ReportChapterId)
  1223. }
  1224. // 继承的报告章节用户map
  1225. grantListMap := make(map[int][]*report.ReportChapterGrant)
  1226. // 授权数据列表
  1227. if len(reportChaptersIdList) > 0 {
  1228. obj := report.ReportChapterGrant{}
  1229. grantList, tmpErr := obj.GetGrantListByIdList(reportChaptersIdList)
  1230. if tmpErr != nil {
  1231. errMsg = "获取待继承的报告章节的授权用户列表失败"
  1232. err = tmpErr
  1233. return
  1234. }
  1235. for _, v := range grantList {
  1236. currReportChapterId := v.ReportChapterId
  1237. tmpGrantList, ok := grantListMap[currReportChapterId]
  1238. if !ok {
  1239. tmpGrantList = make([]*report.ReportChapterGrant, 0)
  1240. }
  1241. v.ReportChapterId = 0
  1242. v.GrantId = 0
  1243. tmpGrantList = append(tmpGrantList, v)
  1244. grantListMap[currReportChapterId] = tmpGrantList
  1245. }
  1246. }
  1247. // 继承的报告章节内容
  1248. for i := 0; i < len(inheritReportChapters); i++ {
  1249. customChapter := inheritReportChapters[i]
  1250. // 授权用户列表
  1251. tmpGrantList, ok := grantListMap[customChapter.ReportChapterId]
  1252. if !ok {
  1253. tmpGrantList = make([]*report.ReportChapterGrant, 0)
  1254. }
  1255. typeGrantListMap[customChapter.TypeId] = tmpGrantList
  1256. // 判断该章节是否是系统章节,如果是的话,那就是需要额外创建的
  1257. if customChapter.TypeId > 0 {
  1258. inheritChapterMap[customChapter.TypeId] = customChapter
  1259. continue
  1260. }
  1261. // 自定义的报告内容
  1262. customChapter.ReportId = reportInfo.Id
  1263. customChapter.ReportChapterId = 0
  1264. customChapter.ClassifyIdFirst = reportInfo.ClassifyIdFirst
  1265. customChapter.ClassifyNameFirst = reportInfo.ClassifyNameFirst
  1266. customChapter.ClassifyIdSecond = reportInfo.ClassifyIdSecond
  1267. customChapter.ClassifyNameSecond = reportInfo.ClassifyNameSecond
  1268. customChapter.ClassifyIdThird = reportInfo.ClassifyIdThird
  1269. customChapter.ClassifyNameThird = reportInfo.ClassifyNameThird
  1270. customChapter.Stage = reportInfo.Stage
  1271. customChapter.PublishState = 1
  1272. customChapter.CreateTime = reportInfo.CreateTime
  1273. customChapter.ModifyTime = time.Now()
  1274. customChapter.LastModifyAdminId = reportInfo.LastModifyAdminId
  1275. customChapter.LastModifyAdminName = reportInfo.LastModifyAdminName
  1276. customChapter.ContentModifyTime = time.Now()
  1277. customAddChapter := models.AddReportChapter{
  1278. ReportChapter: customChapter,
  1279. GrantList: tmpGrantList,
  1280. }
  1281. customAddChapterList = append(customAddChapterList, customAddChapter)
  1282. }
  1283. }
  1284. // 最大排序
  1285. var maxSort int
  1286. for _, typeItem := range typeList {
  1287. v := inheritChapterMap[typeItem.ReportChapterTypeId]
  1288. chapterItem := new(models.ReportChapter)
  1289. if v != nil {
  1290. chapterItem.AddType = 2
  1291. chapterItem.Title = v.Title
  1292. chapterItem.ReportType = v.ReportType
  1293. chapterItem.ClassifyIdFirst = reportInfo.ClassifyIdFirst
  1294. chapterItem.ClassifyNameFirst = reportInfo.ClassifyNameFirst
  1295. chapterItem.ClassifyIdSecond = reportInfo.ClassifyIdSecond
  1296. chapterItem.ClassifyNameSecond = reportInfo.ClassifyNameSecond
  1297. chapterItem.ClassifyIdThird = reportInfo.ClassifyIdThird
  1298. chapterItem.ClassifyNameThird = reportInfo.ClassifyNameThird
  1299. chapterItem.TypeId = typeItem.ReportChapterTypeId
  1300. chapterItem.TypeName = typeItem.ReportChapterTypeName
  1301. chapterItem.Content = v.Content
  1302. chapterItem.ContentSub = v.ContentSub
  1303. chapterItem.Stage = reportInfo.Stage
  1304. chapterItem.PublishState = 1
  1305. chapterItem.Sort = typeItem.Sort
  1306. chapterItem.CreateTime = reportInfo.CreateTime
  1307. chapterItem.ModifyTime = time.Now()
  1308. chapterItem.LastModifyAdminId = reportInfo.LastModifyAdminId
  1309. chapterItem.LastModifyAdminName = reportInfo.LastModifyAdminName
  1310. chapterItem.ContentModifyTime = time.Now()
  1311. chapterItem.ContentStruct = v.ContentStruct
  1312. chapterItem.CanvasColor = v.CanvasColor
  1313. chapterItem.HeadResourceId = v.HeadResourceId
  1314. chapterItem.EndResourceId = v.EndResourceId
  1315. chapterItem.CollaborateType = v.CollaborateType
  1316. chapterItem.ReportLayout = v.ReportLayout
  1317. chapterItem.ReportCreateTime = time.Now()
  1318. } else {
  1319. chapterItem.AddType = 1
  1320. //chapterItem.ReportType = reportType
  1321. chapterItem.ClassifyIdFirst = reportInfo.ClassifyIdFirst
  1322. chapterItem.ClassifyNameFirst = reportInfo.ClassifyNameFirst
  1323. chapterItem.ClassifyIdSecond = reportInfo.ClassifyIdSecond
  1324. chapterItem.ClassifyNameSecond = reportInfo.ClassifyNameSecond
  1325. chapterItem.ClassifyIdThird = reportInfo.ClassifyIdThird
  1326. chapterItem.ClassifyNameThird = reportInfo.ClassifyNameThird
  1327. chapterItem.TypeId = typeItem.ReportChapterTypeId
  1328. chapterItem.TypeName = typeItem.ReportChapterTypeName
  1329. chapterItem.Stage = reportInfo.Stage
  1330. chapterItem.PublishState = 1
  1331. chapterItem.Sort = typeItem.Sort
  1332. chapterItem.CreateTime = reportInfo.CreateTime
  1333. chapterItem.ModifyTime = time.Now()
  1334. chapterItem.LastModifyAdminId = reportInfo.LastModifyAdminId
  1335. chapterItem.LastModifyAdminName = reportInfo.LastModifyAdminName
  1336. chapterItem.ContentModifyTime = time.Now()
  1337. //chapterItem.ContentStruct = v.ContentStruct
  1338. //chapterItem.HeadImg = v.HeadImg
  1339. //chapterItem.EndImg = v.EndImg
  1340. //chapterItem.CanvasColor = v.CanvasColor
  1341. //chapterItem.HeadResourceId = v.HeadResourceId
  1342. //chapterItem.EndResourceId = v.EndResourceId
  1343. chapterItem.CollaborateType = reportInfo.CollaborateType
  1344. chapterItem.ReportLayout = reportInfo.ReportLayout
  1345. chapterItem.ReportCreateTime = time.Now()
  1346. }
  1347. if typeItem.Sort > maxSort {
  1348. maxSort = typeItem.Sort
  1349. }
  1350. // 授权用户列表
  1351. tmpGrantList, ok := typeGrantListMap[chapterItem.TypeId]
  1352. if !ok {
  1353. tmpGrantList = make([]*report.ReportChapterGrant, 0)
  1354. }
  1355. addChapter := models.AddReportChapter{
  1356. ReportChapter: chapterItem,
  1357. GrantList: tmpGrantList,
  1358. }
  1359. chapterList = append(chapterList, addChapter)
  1360. }
  1361. // 将自定义的章节内容添加到待生成的章节内容中
  1362. for _, addChapterItem := range customAddChapterList {
  1363. maxSort++
  1364. addChapterItem.ReportChapter.Sort = maxSort
  1365. chapterList = append(chapterList, addChapterItem)
  1366. }
  1367. hasGrantUserMap := make(map[int]bool)
  1368. for _, grantList := range typeGrantListMap {
  1369. for _, grant := range grantList {
  1370. if _, ok := hasGrantUserMap[grant.AdminId]; !ok {
  1371. allGrantUserList = append(allGrantUserList, &report.ReportGrant{
  1372. //GrantId: 0,
  1373. //ReportId: 0,
  1374. AdminId: grant.AdminId,
  1375. CreateTime: time.Now(),
  1376. })
  1377. }
  1378. }
  1379. }
  1380. return
  1381. }
  1382. // EditChapterBaseInfoAndPermission
  1383. // @Description: 修改报告章节的基础信息、授权用户权限、品种权限
  1384. // @author: Roc
  1385. // @datetime 2024-06-05 11:49:11
  1386. // @param reportInfo *models.Report
  1387. // @param reportChapterInfo *models.ReportChapter
  1388. // @param title string
  1389. // @param permissionIdList []int
  1390. // @param adminIdList []int
  1391. // @return err error
  1392. // @return errMsg string
  1393. func EditChapterBaseInfoAndPermission(reportInfo *models.Report, reportChapterInfo *models.ReportChapter, title string, permissionIdList []int, adminIdList []int) (err error, errMsg string) {
  1394. errMsg = "修改失败"
  1395. if reportInfo.State == 2 {
  1396. errMsg = "该报告已发布,不允许编辑"
  1397. err = errors.New(errMsg)
  1398. return
  1399. }
  1400. updateCols := make([]string, 0)
  1401. // 如果标题内容,那么就修改
  1402. if title != `` {
  1403. reportChapterInfo.Title = title
  1404. reportChapterInfo.ModifyTime = time.Now()
  1405. updateCols = append(updateCols, "Title", "ModifyTime")
  1406. reportChapterInfo.UpdateChapter(updateCols)
  1407. }
  1408. reportGrantObj := report.ReportGrant{}
  1409. chapterGrantObj := report.ReportChapterGrant{}
  1410. chapterPermissionObj := report.ReportChapterPermissionMapping{}
  1411. // 需要添加的报告章节授权数据
  1412. addChapterAdminList := make([]*report.ReportChapterGrant, 0)
  1413. // 待移除的报告章节授权数据id
  1414. delReportChapterGrantIdList := make([]int, 0)
  1415. // 处理当前报告章节需要新增/移除的授权信息
  1416. {
  1417. // 获取当前章节已经授权的用户信息
  1418. chapterGrantList, tmpErr := chapterGrantObj.GetGrantListById(reportChapterInfo.ReportChapterId)
  1419. if tmpErr != nil {
  1420. err = tmpErr
  1421. return
  1422. }
  1423. // 当前章节已经授权的用户信息
  1424. currChapterAdminMap := make(map[int]*report.ReportChapterGrant)
  1425. // 需要删除的报告章节授权数据
  1426. delChapterAdminMap := make(map[int]*report.ReportChapterGrant)
  1427. for _, v := range chapterGrantList {
  1428. currChapterAdminMap[v.AdminId] = v
  1429. delChapterAdminMap[v.AdminId] = v
  1430. }
  1431. for _, adminId := range adminIdList {
  1432. _, ok := currChapterAdminMap[adminId]
  1433. // 如果存在,那么从 “需要删除的报告章节授权数据” 的map中移除
  1434. if ok {
  1435. delete(delChapterAdminMap, adminId)
  1436. continue
  1437. }
  1438. // 如果不存在,那么就新增授权
  1439. addChapterAdminList = append(addChapterAdminList, &report.ReportChapterGrant{
  1440. //GrantId: 0,
  1441. ReportChapterId: reportChapterInfo.ReportChapterId,
  1442. AdminId: adminId,
  1443. CreateTime: time.Now(),
  1444. })
  1445. }
  1446. // 查出需要移除的授权id
  1447. for _, v := range delChapterAdminMap {
  1448. delReportChapterGrantIdList = append(delReportChapterGrantIdList, v.GrantId)
  1449. }
  1450. }
  1451. // 其他章节授权的用户
  1452. otherChapterAdminMap := make(map[int]bool)
  1453. {
  1454. // 获取报告所有的章节id
  1455. reportChapterIdList, tmpErr := models.GetReportChapterIdList(reportInfo.Id)
  1456. if tmpErr != nil {
  1457. err = tmpErr
  1458. return
  1459. }
  1460. if len(reportChapterIdList) > 0 {
  1461. list, tmpErr := chapterGrantObj.GetGrantListByIdList(reportChapterIdList)
  1462. if tmpErr != nil {
  1463. err = tmpErr
  1464. return
  1465. }
  1466. for _, v := range list {
  1467. // 如果是当前章节,因为涉及到重新授权,所以得过滤
  1468. if v.ReportChapterId == reportChapterInfo.ReportChapterId {
  1469. continue
  1470. }
  1471. otherChapterAdminMap[v.AdminId] = true
  1472. }
  1473. }
  1474. }
  1475. // 需要添加的报告授权数据
  1476. addReportAdminList := make([]*report.ReportGrant, 0)
  1477. // 待移除的报告授权数据id
  1478. delReportGrantIdList := make([]int, 0)
  1479. // 处理当前报告需要新增/移除的授权信息
  1480. {
  1481. // 获取当前报告已经授权的用户信息
  1482. reportGrantList, tmpErr := reportGrantObj.GetGrantListById(reportInfo.Id)
  1483. if tmpErr != nil {
  1484. err = tmpErr
  1485. return
  1486. }
  1487. // 当前报告已经授权的用户信息
  1488. currReportAdminMap := make(map[int]*report.ReportGrant)
  1489. // 需要删除的报告授权数据
  1490. delReportAdminMap := make(map[int]*report.ReportGrant)
  1491. for _, v := range reportGrantList {
  1492. currReportAdminMap[v.AdminId] = v
  1493. delReportAdminMap[v.AdminId] = v
  1494. }
  1495. // 先看需要新增哪些用户
  1496. for _, tmpAdminId := range adminIdList {
  1497. _, ok := currReportAdminMap[tmpAdminId]
  1498. // 如果章节中需要新增的用户 已经在 报告授权用户里面,那么就忽略,可以不用新增了
  1499. if ok {
  1500. delete(delReportAdminMap, tmpAdminId)
  1501. continue
  1502. }
  1503. // 如果不存在,那么就新增授权
  1504. addReportAdminList = append(addReportAdminList, &report.ReportGrant{
  1505. //GrantId: 0,
  1506. ReportId: reportInfo.Id,
  1507. AdminId: tmpAdminId,
  1508. CreateTime: time.Now(),
  1509. })
  1510. }
  1511. // 再看看章节中,需要移除的用户
  1512. for _, tmpAdminId := range delReportChapterGrantIdList {
  1513. _, ok := otherChapterAdminMap[tmpAdminId]
  1514. // 如果章节中需要移除的用户 在 报告中其他章节的授权用户里面,那么就忽略,可以不用删除了
  1515. if ok {
  1516. delete(delReportAdminMap, tmpAdminId)
  1517. continue
  1518. }
  1519. }
  1520. // 查出需要移除的授权id
  1521. for _, v := range delReportAdminMap {
  1522. delReportGrantIdList = append(delReportGrantIdList, v.GrantId)
  1523. }
  1524. }
  1525. // 需要添加的报告章节品种权限数据
  1526. addChapterPermissionList := make([]*report.ReportChapterPermissionMapping, 0)
  1527. // 待移除的报告章节品种权限数据id
  1528. delChapterPermissionMappingIdList := make([]int, 0)
  1529. // 处理当前报告章节需要新增/移除的品种权限信息
  1530. {
  1531. // 获取当前章节已经配置的品种权限信息
  1532. chapterPermissionList, tmpErr := chapterPermissionObj.GetPermissionListById(reportChapterInfo.ReportChapterId)
  1533. if tmpErr != nil {
  1534. err = tmpErr
  1535. return
  1536. }
  1537. // 当前章节已经配置的品种权限信息
  1538. currChapterPermissionMap := make(map[int]*report.ReportChapterPermissionMapping)
  1539. // 需要删除的报告章节品种权限配置
  1540. delChapterPermissionMap := make(map[int]*report.ReportChapterPermissionMapping)
  1541. for _, v := range chapterPermissionList {
  1542. currChapterPermissionMap[v.ChartPermissionId] = v
  1543. delChapterPermissionMap[v.ChartPermissionId] = v
  1544. }
  1545. for _, permissionId := range permissionIdList {
  1546. _, ok := currChapterPermissionMap[permissionId]
  1547. // 如果存在,那么从 “需要删除的报告章节品种权限配置” 的map中移除
  1548. if ok {
  1549. delete(delChapterPermissionMap, permissionId)
  1550. continue
  1551. }
  1552. // 如果不存在,那么就新增品种权限配置
  1553. addChapterPermissionList = append(addChapterPermissionList, &report.ReportChapterPermissionMapping{
  1554. //ReportChapterPermissionMappingId: 0,
  1555. ReportChapterId: reportChapterInfo.ReportChapterId,
  1556. ChartPermissionId: permissionId,
  1557. CreateTime: time.Now(),
  1558. })
  1559. }
  1560. // 查出需要移除的品种权限配置
  1561. for _, v := range delChapterPermissionMap {
  1562. delChapterPermissionMappingIdList = append(delChapterPermissionMappingIdList, v.ReportChapterPermissionMappingId)
  1563. }
  1564. }
  1565. err = models.EditChapterBaseInfoAndPermission(reportChapterInfo, updateCols, addReportAdminList, addChapterAdminList, addChapterPermissionList, delReportGrantIdList, delReportChapterGrantIdList, delChapterPermissionMappingIdList)
  1566. return
  1567. }