share_poster.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. package services
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "github.com/PuerkitoBio/goquery"
  7. "hongze/hongze_yb/global"
  8. "hongze/hongze_yb/models/tables/yb_poster_config"
  9. "hongze/hongze_yb/models/tables/yb_poster_resource"
  10. "hongze/hongze_yb/models/tables/yb_suncode_pars"
  11. "hongze/hongze_yb/services/alarm_msg"
  12. "hongze/hongze_yb/services/wx_app"
  13. "hongze/hongze_yb/utils"
  14. "io/ioutil"
  15. "net/http"
  16. "os"
  17. "strings"
  18. "time"
  19. )
  20. var ServerUrl = "http://127.0.0.1:5008/"
  21. // SharePosterReq 分享海报请求体
  22. type SharePosterReq struct {
  23. CodePage string `json:"code_page" description:"太阳码page"`
  24. CodeScene string `json:"code_scene" description:"太阳码scene"`
  25. Source string `json:"source" description:"来源"`
  26. Version string `json:"version" description:"海报版本号" `
  27. Pars string `json:"pars" description:"海报动态信息"`
  28. }
  29. // CreatePosterFromSource 生成分享海报
  30. func CreatePosterFromSource(codePage, codeScene, source, version, pars string) (imgUrl string, err error) {
  31. var errMsg string
  32. defer func() {
  33. if err != nil {
  34. global.LOG.Critical(fmt.Sprintf("CreatePosterFromSource: source=%s, pars:%s, errMsg:%s", source, pars, errMsg))
  35. reqSlice := make([]string, 0)
  36. reqSlice = append(reqSlice, fmt.Sprint("CodePage:", codePage, "\n"))
  37. reqSlice = append(reqSlice, fmt.Sprint("CodeScene:", codeScene, "\n"))
  38. reqSlice = append(reqSlice, fmt.Sprint("Source:", source, "\n"))
  39. reqSlice = append(reqSlice, fmt.Sprint("Version:", version, "\n"))
  40. reqSlice = append(reqSlice, fmt.Sprint("Pars:", pars, "\n"))
  41. go alarm_msg.SendAlarmMsg("CreatePosterFromSource生成分享海报失败, Msg:"+errMsg+";Err:"+err.Error()+"\n;Req:\n"+strings.Join(reqSlice, ";"), 3)
  42. }
  43. }()
  44. if codePage == "" || source == "" || pars == "" {
  45. errMsg = "参数有误"
  46. err = errors.New(errMsg)
  47. return
  48. }
  49. path := fmt.Sprint(codePage, "?", codeScene)
  50. // 非列表来源获取历史图片,无则生成
  51. if !strings.Contains(source, "list") && source != "price_driven" {
  52. poster, tmpErr := yb_poster_resource.GetPosterByCondition(path, "poster", version)
  53. if tmpErr != nil && tmpErr != utils.ErrNoRow {
  54. err = tmpErr
  55. return
  56. }
  57. if poster != nil && poster.ImgURL != "" {
  58. imgUrl = poster.ImgURL
  59. return
  60. }
  61. }
  62. // 图片长宽
  63. heightMap := map[string]int{
  64. "activity_detail": 1210,
  65. "activity_list": 1948,
  66. "special_column_detail": 1480,
  67. "special_column_list": 1548,
  68. "chart_detail": 1536,
  69. "chart_list": 1352,
  70. "report_detail": 1420,
  71. "report_list": 1344,
  72. "price_driven": 1344,
  73. }
  74. widthMap := map[string]int{
  75. "activity_detail": 1280,
  76. "activity_list": 1280,
  77. "special_column_detail": 1176,
  78. "special_column_list": 1176,
  79. "chart_detail": 1176,
  80. "chart_list": 1176,
  81. "report_detail": 1176,
  82. "report_list": 1176,
  83. "price_driven": 1176,
  84. }
  85. width := widthMap[source]
  86. height := heightMap[source]
  87. if width == 0 || height == 0 {
  88. errMsg = "图片长度有误"
  89. err = errors.New(errMsg)
  90. return "", err
  91. }
  92. // 生成太阳码
  93. sunCodeUrl, err := CreateAndUploadSunCode(codePage, codeScene, version)
  94. if err != nil {
  95. return
  96. }
  97. // 填充html内容
  98. contentStr, newHeight, err := fillContent2Html(source, pars, sunCodeUrl, height)
  99. if err != nil {
  100. errMsg = "html内容有误"
  101. return
  102. }
  103. // 请求python服务htm2img
  104. htm2ImgReq := make(map[string]interface{})
  105. htm2ImgReq["html_content"] = contentStr
  106. htm2ImgReq["width"] = width
  107. htm2ImgReq["height"] = newHeight
  108. res, err := postHtml2Img(htm2ImgReq)
  109. if err != nil || res == nil {
  110. errMsg = "html转图片请求失败"
  111. return
  112. }
  113. if res.Code != 200 {
  114. errMsg = "html转图片请求失败"
  115. err = errors.New("html转图片失败: " + res.Msg)
  116. return
  117. }
  118. imgUrl = res.Data
  119. // 记录海报信息
  120. newPoster := &yb_poster_resource.YbPosterResource{
  121. Path: path,
  122. ImgURL: imgUrl,
  123. Type: "poster",
  124. Version: version,
  125. CreateTime: time.Now(),
  126. }
  127. err = newPoster.Create()
  128. return
  129. }
  130. type Html2ImgResp struct {
  131. Code int `json:"code"`
  132. Msg string `json:"msg"`
  133. Data string `json:"data"`
  134. }
  135. // postHtml2Img 请求htm2img接口
  136. func postHtml2Img(param map[string]interface{}) (resp *Html2ImgResp, err error) {
  137. // 目前仅此处调用该接口,暂不加授权、校验等
  138. postUrl := ServerUrl + "htm2img"
  139. postData, err := json.Marshal(param)
  140. if err != nil {
  141. return
  142. }
  143. result, err := Html2ImgHttpPost(postUrl, string(postData), "application/json")
  144. if err != nil {
  145. return
  146. }
  147. if err = json.Unmarshal(result, &resp); err != nil {
  148. return
  149. }
  150. return resp, nil
  151. }
  152. // Html2ImgHttpPost post请求
  153. func Html2ImgHttpPost(url, postData string, params ...string) ([]byte, error) {
  154. body := ioutil.NopCloser(strings.NewReader(postData))
  155. client := &http.Client{}
  156. req, err := http.NewRequest("POST", url, body)
  157. if err != nil {
  158. return nil, err
  159. }
  160. contentType := "application/x-www-form-urlencoded;charset=utf-8"
  161. if len(params) > 0 && params[0] != "" {
  162. contentType = params[0]
  163. }
  164. req.Header.Set("Content-Type", contentType)
  165. resp, err := client.Do(req)
  166. if err != nil {
  167. return nil, err
  168. }
  169. defer resp.Body.Close()
  170. b, err := ioutil.ReadAll(resp.Body)
  171. fmt.Println("HttpPost:" + string(b))
  172. return b, err
  173. }
  174. // CreateAndUploadSunCode 生成太阳码并上传OSS
  175. func CreateAndUploadSunCode(page, scene, version string) (imgUrl string, err error) {
  176. if page == "" {
  177. err = errors.New("page不能为空")
  178. return
  179. }
  180. path := fmt.Sprint(page, "?", scene)
  181. exist, err := yb_poster_resource.GetPosterByCondition(path, "qrcode", version)
  182. if err != nil && err != utils.ErrNoRow {
  183. return
  184. }
  185. if exist != nil && exist.ImgURL != "" {
  186. return exist.ImgURL, nil
  187. }
  188. // scene超过32位会生成失败,md5处理至32位
  189. sceneMD5 := "a=1"
  190. if scene != "" {
  191. sceneMD5 = utils.MD5(scene)
  192. }
  193. picByte, err := wx_app.GetSunCode(page, sceneMD5)
  194. if err != nil {
  195. return
  196. }
  197. // 生成图片
  198. localPath := "./static/img"
  199. fileName := utils.GetRandStringNoSpecialChar(28) + ".png"
  200. fpath := fmt.Sprint(localPath, "/", fileName)
  201. f, err := os.Create(fpath)
  202. if err != nil {
  203. return
  204. }
  205. if _, err = f.Write(picByte); err != nil {
  206. return
  207. }
  208. defer func() {
  209. f.Close()
  210. os.Remove(fpath)
  211. }()
  212. // 上传OSS
  213. fileDir := "images/yb/suncode/"
  214. imgUrl, err = UploadAliyunToDir(fileName, fpath, fileDir)
  215. if err != nil {
  216. return
  217. }
  218. // 记录二维码信息
  219. newPoster := &yb_poster_resource.YbPosterResource{
  220. Path: path,
  221. ImgURL: imgUrl,
  222. Type: "qrcode",
  223. Version: version,
  224. CreateTime: time.Now(),
  225. }
  226. err = newPoster.Create()
  227. if err != nil {
  228. return
  229. }
  230. // 记录参数md5
  231. if scene != "" {
  232. newPars := &yb_suncode_pars.YbSuncodePars{
  233. Scene: scene,
  234. SceneKey: sceneMD5,
  235. CreateTime: time.Now(),
  236. }
  237. err = newPars.Create()
  238. }
  239. return
  240. }
  241. type PosterParsReq struct {
  242. ActivityTitle string `json:"activity_title"`
  243. ActivityAvatar string `json:"activity_avatar"`
  244. ActivitySpeaker string `json:"activity_speaker"`
  245. ActivityTime string `json:"activity_time"`
  246. ChartName string `json:"chart_name"`
  247. ChartImage string `json:"chart_image"`
  248. ReportType string `json:"report_type"`
  249. ReportAvatar string `json:"report_avatar"`
  250. ReportTitle string `json:"report_title"`
  251. ReportAbstract string `json:"report_abstract"`
  252. Stage1 string `json:"stage_1"`
  253. Avatar1 string `json:"avatar_1"`
  254. Title1 string `json:"title_1"`
  255. Author1 string `json:"author_1"`
  256. Tag1 string `json:"tag_1"`
  257. Img1 string `json:"img_1"`
  258. Time1 string `json:"time_1"`
  259. Abstract1 string `json:"abstract_1"`
  260. Status1 string `json:"status_1"`
  261. Speaker1 string `json:"speaker_1"`
  262. Stage2 string `json:"stage_2"`
  263. Avatar2 string `json:"avatar_2"`
  264. Title2 string `json:"title_2"`
  265. Author2 string `json:"author_2"`
  266. Tag2 string `json:"tag_2"`
  267. Img2 string `json:"img_2"`
  268. Abstract2 string `json:"abstract_2"`
  269. Time2 string `json:"time_2"`
  270. Status2 string `json:"status_2"`
  271. Speaker2 string `json:"speaker_2"`
  272. ListTitle string `json:"list_title"`
  273. CoreDrivenType string `json:"core_driven_type"`
  274. CoreDrivenContent string `json:"core_driven_content"`
  275. MainVariable string `json:"main_variable"`
  276. UpdateTime string `json:"update_time"`
  277. }
  278. // fillContent2Html 填充HTML动态内容
  279. func fillContent2Html(source, pars, sunCodeUrl string, height int) (contentStr string, newHeight int, err error) {
  280. params := new(PosterParsReq)
  281. if err = json.Unmarshal([]byte(pars), &params); err != nil {
  282. return
  283. }
  284. template := fmt.Sprint("static/htm2img/", source, ".html")
  285. contentByte, err := ioutil.ReadFile(template)
  286. if err != nil {
  287. return
  288. }
  289. newHeight = height
  290. contentStr = string(contentByte)
  291. // 列表的动态内容不完整的用默认内容的填充
  292. var emptyTime1, emptyTime2 bool
  293. if strings.Contains(source, "list") {
  294. if params.Title1 == "" || params.Title2 == "" {
  295. defaultAvatar := "https://hzstatic.hzinsights.com/static/images/202112/20211210/wn6c3oYKTfT4NbTZgRGflRuIBZaa.png"
  296. switch source {
  297. case "activity_list":
  298. if params.Title1 == "" {
  299. params.ListTitle = "线下沙龙"
  300. params.Status1 = "未开始"
  301. params.Avatar1 = defaultAvatar
  302. params.Title1 = "周度报告"
  303. params.Speaker1 = "FICC研究员"
  304. params.Time1 = "2022-5-10"
  305. emptyTime1 = true
  306. }
  307. params.Status2 = params.Status1
  308. params.Avatar2 = defaultAvatar
  309. params.Title2 = "周度报告"
  310. params.Speaker2 = "FICC研究员"
  311. params.Time2 = "2022-5-10"
  312. emptyTime2 = true
  313. case "special_column_list":
  314. if params.Title1 == "" {
  315. params.Stage1 = "第1期"
  316. params.Avatar1 = defaultAvatar
  317. params.Title1 = "弘则FICC专栏"
  318. params.Author1 = "弘则研究"
  319. params.Tag1 = "FICC研究员"
  320. }
  321. params.Stage2 = "第2期"
  322. params.Avatar2 = defaultAvatar
  323. params.Title2 = "弘则FICC专栏"
  324. params.Author2 = "弘则研究"
  325. params.Tag2 = "FICC研究员"
  326. case "chart_list":
  327. if params.Title1 == "" {
  328. params.Title1 = "螺纹仓单-热卷仓单季节性"
  329. params.Img1 = "https://hzstatic.hzinsights.com/static/images/202112/20211227/8VBIH1l6VraYpLCoBS6qOIXA5Zoq.png"
  330. }
  331. params.Title2 = "卷螺期货现货价差"
  332. params.Img2 = "https://hzstatic.hzinsights.com/static/images/202204/20220427/d8GRfdR3Xfrvk397SnYudcwVs9pV.png"
  333. case "report_list":
  334. if params.Title1 == "" {
  335. params.Img1 = defaultAvatar
  336. params.Title1 = "弘则FICC研报"
  337. params.Time1 = "1期"
  338. }
  339. params.Img2 = defaultAvatar
  340. params.Title2 = "弘则FICC研报"
  341. params.Time2 = "2期"
  342. }
  343. }
  344. }
  345. // 填充指定内容
  346. switch source {
  347. case "activity_detail":
  348. contentStr = strings.Replace(contentStr, "{{ACTIVITY_TITLE}}", params.ActivityTitle, 1)
  349. contentStr = strings.Replace(contentStr, "{{ACTIVITY_AVATAR}}", params.ActivityAvatar, 1)
  350. contentStr = strings.Replace(contentStr, "{{ACTIVITY_SPEAKER}}", params.ActivitySpeaker, 1)
  351. contentStr = strings.Replace(contentStr, "{{ACTIVITY_TIME}}", params.ActivityTime, 1)
  352. case "special_column_detail":
  353. contentStr = strings.Replace(contentStr, "{{REPORT_AVATAR}}", params.ReportAvatar, 1)
  354. contentStr = strings.Replace(contentStr, "{{REPORT_TITLE}}", params.ReportTitle, 1)
  355. contentStr = strings.Replace(contentStr, "{{REPORT_ABSTRACT}}", params.ReportAbstract, 1)
  356. case "chart_detail":
  357. contentStr = strings.Replace(contentStr, "{{CHART_NAME}}", params.ChartName, 1)
  358. contentStr = strings.Replace(contentStr, "{{CHART_IMAGE}}", params.ChartImage, 1)
  359. case "report_detail":
  360. doc, tmpErr := goquery.NewDocumentFromReader(strings.NewReader(params.ReportAbstract))
  361. if tmpErr != nil {
  362. err = tmpErr
  363. return
  364. }
  365. abstract := ""
  366. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  367. phtml, tmpErr := s.Html()
  368. if tmpErr != nil {
  369. err = tmpErr
  370. return
  371. }
  372. st := s.Text()
  373. if st != "" && st != "<br>" && st != "<br style=\"max-width: 100%;\">" && !strings.Contains(phtml, "iframe") {
  374. abstract = abstract + "<p>" + phtml + "</p>"
  375. }
  376. })
  377. contentStr = strings.Replace(contentStr, "{{REPORT_TYPE}}", params.ReportType, 1)
  378. contentStr = strings.Replace(contentStr, "{{REPORT_TITLE}}", params.ReportTitle, 1)
  379. contentStr = strings.Replace(contentStr, "{{REPORT_ABSTRACT}}", abstract, 1)
  380. case "special_column_list":
  381. contentStr = strings.Replace(contentStr, "{{LIST_TITLE}}", params.ListTitle, 1)
  382. contentStr = strings.Replace(contentStr, "{{STAGE_1}}", "第"+params.Stage1+"期", 1)
  383. contentStr = strings.Replace(contentStr, "{{AVATAR_1}}", params.Avatar1, 1)
  384. contentStr = strings.Replace(contentStr, "{{TITLE_1}}", params.Title1, 1)
  385. contentStr = strings.Replace(contentStr, "{{AUTHOR_1}}", params.Author1, 1)
  386. contentStr = strings.Replace(contentStr, "{{TAG_1}}", params.Tag1, 1)
  387. contentStr = strings.Replace(contentStr, "{{STAGE_2}}", "第"+params.Stage2+"期", 1)
  388. contentStr = strings.Replace(contentStr, "{{AVATAR_2}}", params.Avatar2, 1)
  389. contentStr = strings.Replace(contentStr, "{{TITLE_2}}", params.Title2, 1)
  390. contentStr = strings.Replace(contentStr, "{{AUTHOR_2}}", params.Author2, 1)
  391. contentStr = strings.Replace(contentStr, "{{TAG_2}}", params.Tag2, 1)
  392. case "chart_list":
  393. contentStr = strings.Replace(contentStr, "{{LIST_TITLE}}", params.ListTitle, 1)
  394. contentStr = strings.Replace(contentStr, "{{TITLE_1}}", params.Title1, 1)
  395. contentStr = strings.Replace(contentStr, "{{IMG_1}}", params.Img1, 1)
  396. contentStr = strings.Replace(contentStr, "{{TITLE_2}}", params.Title2, 1)
  397. contentStr = strings.Replace(contentStr, "{{IMG_2}}", params.Img2, 1)
  398. case "report_list":
  399. contentStr = strings.Replace(contentStr, "{{LIST_TITLE}}", params.ListTitle, 1)
  400. contentStr = strings.Replace(contentStr, "{{TITLE_1}}", params.Title1, 1)
  401. contentStr = strings.Replace(contentStr, "{{ABSTRACT_1}}", params.Abstract1, 1)
  402. contentStr = strings.Replace(contentStr, "{{IMG_1}}", params.Img1, 1)
  403. contentStr = strings.Replace(contentStr, "{{TIME_1}}", params.Time1, 1)
  404. contentStr = strings.Replace(contentStr, "{{TITLE_2}}", params.Title2, 1)
  405. contentStr = strings.Replace(contentStr, "{{ABSTRACT_2}}", params.Abstract2, 1)
  406. contentStr = strings.Replace(contentStr, "{{IMG_2}}", params.Img2, 1)
  407. contentStr = strings.Replace(contentStr, "{{TIME_2}}", params.Time2, 1)
  408. case "price_driven":
  409. contentStr = strings.Replace(contentStr, "{{LIST_TITLE}}", params.ListTitle, 1)
  410. contentStr = strings.Replace(contentStr, "{{CORE_DRIVEN_TYPE}}", params.CoreDrivenType, 1)
  411. contentStr = strings.Replace(contentStr, "{{CORE_DRIVEN_CONTENT}}", params.CoreDrivenContent, 1)
  412. contentStr = strings.Replace(contentStr, "{{MAIN_VARIABLE}}", params.MainVariable, 1)
  413. contentStr = strings.Replace(contentStr, "{{UPDATE_TIME}}", params.UpdateTime, 1)
  414. case "activity_list":
  415. bgColorMap := map[string]string{
  416. "未开始": "#E3B377",
  417. "进行中": "#3385FF",
  418. "已结束": "#A2A2A2",
  419. }
  420. statusItemMap := map[string]string{
  421. "未开始": "block",
  422. "进行中": "none",
  423. "已结束": "none",
  424. }
  425. offlineMap := map[string]string{
  426. "线上会议": "none",
  427. "线下沙龙": "block",
  428. }
  429. onlineMap := map[string]string{
  430. "线上会议": "block",
  431. "线下沙龙": "none",
  432. }
  433. if params.Status1 != "未开始" {
  434. newHeight = 1715
  435. }
  436. contentStr = strings.Replace(contentStr, "{{LIST_TITLE}}", "弘则FICC周度电话会安排", 1)
  437. contentStr = strings.Replace(contentStr, "{{BG_COLORE_1}}", bgColorMap[params.Status1], 1)
  438. contentStr = strings.Replace(contentStr, "{{STATUS_1}}", params.Status1, 1)
  439. contentStr = strings.Replace(contentStr, "{{AVATAR_1}}", params.Avatar1, 1)
  440. contentStr = strings.Replace(contentStr, "{{TITLE_1}}", params.Title1, 1)
  441. contentStr = strings.Replace(contentStr, "{{SPEAKER_1}}", params.Speaker1, 1)
  442. contentStr = strings.Replace(contentStr, "{{TIME_1}}", params.Time1, 1)
  443. contentStr = strings.Replace(contentStr, "{{SHOW_ITEM_1}}", statusItemMap[params.Status1], 1)
  444. contentStr = strings.Replace(contentStr, "{{SHOW_OFFLINE_1}}", offlineMap[params.ListTitle], 1)
  445. contentStr = strings.Replace(contentStr, "{{SHOW_ONLINE_1}}", onlineMap[params.ListTitle], 1)
  446. contentStr = strings.Replace(contentStr, "{{BG_COLORE_2}}", bgColorMap[params.Status2], 1)
  447. contentStr = strings.Replace(contentStr, "{{STATUS_2}}", params.Status2, 1)
  448. contentStr = strings.Replace(contentStr, "{{AVATAR_2}}", params.Avatar2, 1)
  449. contentStr = strings.Replace(contentStr, "{{TITLE_2}}", params.Title2, 1)
  450. contentStr = strings.Replace(contentStr, "{{SPEAKER_2}}", params.Speaker2, 1)
  451. contentStr = strings.Replace(contentStr, "{{TIME_2}}", params.Time2, 1)
  452. contentStr = strings.Replace(contentStr, "{{SHOW_ITEM_2}}", statusItemMap[params.Status2], 1)
  453. contentStr = strings.Replace(contentStr, "{{SHOW_OFFLINE_2}}", offlineMap[params.ListTitle], 1)
  454. contentStr = strings.Replace(contentStr, "{{SHOW_ONLINE_2}}", onlineMap[params.ListTitle], 1)
  455. // 用默认内容填充的活动时间字体颜色调至看不见
  456. color1 := "#999"
  457. color2 := "#999"
  458. if emptyTime1 {
  459. color1 = "#fff"
  460. }
  461. if emptyTime2 {
  462. color2 = "#fff"
  463. }
  464. contentStr = strings.Replace(contentStr, "{{TIME_COLOR_1}}", color1, 1)
  465. contentStr = strings.Replace(contentStr, "{{TIME_COLOR_2}}", color2, 1)
  466. }
  467. contentStr = strings.Replace(contentStr, "{{SUN_CODE}}", sunCodeUrl, 1)
  468. return
  469. }
  470. // GetRddpShareImg 获取日度点评分享图
  471. func GetRddpShareImg(title string) (imgUrl string, err error) {
  472. if title == "" {
  473. return
  474. }
  475. template := fmt.Sprint("static/htm2img/rddp-share.html")
  476. contentByte, e := ioutil.ReadFile(template)
  477. if e != nil {
  478. err = errors.New("读取模板失败, Err: " + e.Error())
  479. return
  480. }
  481. contentStr := string(contentByte)
  482. contentStr = strings.Replace(contentStr, "{{TITLE}}", title, 1)
  483. htm2ImgReq := make(map[string]interface{})
  484. htm2ImgReq["html_content"] = contentStr
  485. htm2ImgReq["width"] = 1000
  486. htm2ImgReq["height"] = 800
  487. res, e := postHtml2Img(htm2ImgReq)
  488. if e != nil || res == nil {
  489. err = errors.New("html转图片请求失败")
  490. return
  491. }
  492. if res.Code != 200 {
  493. err = errors.New("html转图片请求失败: " + res.Msg)
  494. return
  495. }
  496. imgUrl = res.Data
  497. return
  498. }
  499. // CreatePosterFromSourceV2 生成分享海报(通过配置获取相关信息)
  500. func CreatePosterFromSourceV2(codePage, codeScene, source, version, pars string) (imgUrl string, err error) {
  501. var errMsg string
  502. defer func() {
  503. if err != nil {
  504. global.LOG.Critical(fmt.Sprintf("CreatePosterFromSource: source=%s, pars:%s, errMsg:%s", source, pars, errMsg))
  505. reqSlice := make([]string, 0)
  506. reqSlice = append(reqSlice, fmt.Sprint("CodePage:", codePage, "\n"))
  507. reqSlice = append(reqSlice, fmt.Sprint("CodeScene:", codeScene, "\n"))
  508. reqSlice = append(reqSlice, fmt.Sprint("Source:", source, "\n"))
  509. reqSlice = append(reqSlice, fmt.Sprint("Version:", version, "\n"))
  510. reqSlice = append(reqSlice, fmt.Sprint("Pars:", pars, "\n"))
  511. go alarm_msg.SendAlarmMsg("CreatePosterFromSource生成分享海报失败, Msg:"+errMsg+";Err:"+err.Error()+"\n;Req:\n"+strings.Join(reqSlice, ";"), 3)
  512. }
  513. }()
  514. if codePage == "" || source == "" || pars == "" {
  515. errMsg = "参数有误"
  516. err = errors.New(errMsg)
  517. return
  518. }
  519. path := fmt.Sprint(codePage, "?", codeScene)
  520. // 非列表来源获取历史图片,无则生成
  521. if !strings.Contains(source, "list") && source != "price_driven" {
  522. poster, tmpErr := yb_poster_resource.GetPosterByCondition(path, "poster", version)
  523. if tmpErr != nil && tmpErr != utils.ErrNoRow {
  524. err = tmpErr
  525. return
  526. }
  527. if poster != nil && poster.ImgURL != "" {
  528. imgUrl = poster.ImgURL
  529. return
  530. }
  531. }
  532. ybPosterConfig, err := yb_poster_config.GetBySource(source)
  533. if err != nil {
  534. return
  535. }
  536. // 图片长宽
  537. //heightMap := map[string]int{
  538. // "activity_detail": 1210,
  539. // "activity_list": 1948,
  540. // "special_column_detail": 1480,
  541. // "special_column_list": 1548,
  542. // "chart_detail": 1536,
  543. // "chart_list": 1352,
  544. // "report_detail": 1420,
  545. // "report_list": 1344,
  546. //}
  547. //widthMap := map[string]int{
  548. // "activity_detail": 1280,
  549. // "activity_list": 1280,
  550. // "special_column_detail": 1176,
  551. // "special_column_list": 1176,
  552. // "chart_detail": 1176,
  553. // "chart_list": 1176,
  554. // "report_detail": 1176,
  555. // "report_list": 1176,
  556. //}
  557. width := ybPosterConfig.Width
  558. height := ybPosterConfig.Hight
  559. //生成太阳码
  560. sunCodeUrl, err := CreateAndUploadSunCode(codePage, codeScene, version)
  561. if err != nil {
  562. return
  563. }
  564. //sunCodeUrl := ``
  565. // 填充html内容
  566. contentStr, newHeight, err := fillContent2HtmlV2(source, pars, sunCodeUrl, height, *ybPosterConfig)
  567. if err != nil {
  568. errMsg = "html内容有误"
  569. return
  570. }
  571. global.LOG.Critical(contentStr)
  572. //return
  573. // 请求python服务htm2img
  574. htm2ImgReq := make(map[string]interface{})
  575. htm2ImgReq["html_content"] = contentStr
  576. htm2ImgReq["width"] = width
  577. htm2ImgReq["height"] = newHeight
  578. res, err := postHtml2Img(htm2ImgReq)
  579. if err != nil || res == nil {
  580. errMsg = "html转图片请求失败"
  581. return
  582. }
  583. if res.Code != 200 {
  584. errMsg = "html转图片请求失败"
  585. err = errors.New("html转图片失败: " + res.Msg)
  586. return
  587. }
  588. imgUrl = res.Data
  589. // 记录海报信息
  590. newPoster := &yb_poster_resource.YbPosterResource{
  591. Path: path,
  592. ImgURL: imgUrl,
  593. Type: "poster",
  594. Version: version,
  595. CreateTime: time.Now(),
  596. }
  597. err = newPoster.Create()
  598. return
  599. }
  600. // HtmlReplaceConfig html替换配置
  601. type HtmlReplaceConfig struct {
  602. TemplateStr string `json:"template_str"`
  603. ReplaceStr string `json:"replace_str"`
  604. }
  605. // DefaultValueConfig 默认值的配置
  606. type DefaultValueConfig struct {
  607. Key string `json:"key"`
  608. UseOtherKey string `json:"use_other_key"`
  609. Value string `json:"value"`
  610. ConditionKey string `json:"condition_key"`
  611. }
  612. // fillContent2Html 填充HTML动态内容
  613. func fillContent2HtmlV2(source, pars, sunCodeUrl string, height float64, ybPosterConfig yb_poster_config.YbPosterConfig) (contentStr string, newHeight float64, err error) {
  614. paramsMap := make(map[string]string)
  615. if err = json.Unmarshal([]byte(pars), &paramsMap); err != nil {
  616. return
  617. }
  618. //fmt.Println(paramsMap)
  619. //html替换规则
  620. htmlReplaceConfigList := make([]HtmlReplaceConfig, 0)
  621. if err = json.Unmarshal([]byte(ybPosterConfig.HTMLReplaceConfig), &htmlReplaceConfigList); err != nil {
  622. return
  623. }
  624. newHeight = height
  625. contentStr = ybPosterConfig.HTMLTemplate
  626. // 默认数据替换
  627. defaultValueConfigMap := make([]DefaultValueConfig, 0)
  628. if ybPosterConfig.DefaultValueConfig != `` {
  629. if err = json.Unmarshal([]byte(ybPosterConfig.DefaultValueConfig), &defaultValueConfigMap); err != nil {
  630. return
  631. }
  632. }
  633. // 列表的动态内容不完整的用默认内容的填充
  634. //var emptyTime1, emptyTime2 bool
  635. conditionKeyValMap := make(map[string]string)
  636. for _, v := range defaultValueConfigMap {
  637. if v.ConditionKey == `` {
  638. continue
  639. }
  640. conditionKeyVal, ok := conditionKeyValMap[v.ConditionKey]
  641. if !ok {
  642. conditionKeyVal = paramsMap[v.ConditionKey]
  643. conditionKeyValMap[v.ConditionKey] = conditionKeyVal
  644. }
  645. if conditionKeyVal == `` {
  646. paramsMap[v.Key] = v.Value
  647. if v.UseOtherKey != `` {
  648. if tmpVal, ok := paramsMap[v.UseOtherKey]; ok {
  649. paramsMap[v.Key] = tmpVal
  650. }
  651. }
  652. }
  653. }
  654. // 填充指定内容
  655. switch source {
  656. case "report_detail": //需要将简介处理下
  657. reportAbstract := paramsMap["report_abstract"]
  658. doc, tmpErr := goquery.NewDocumentFromReader(strings.NewReader(reportAbstract))
  659. if tmpErr != nil {
  660. err = tmpErr
  661. return
  662. }
  663. abstract := ""
  664. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  665. phtml, tmpErr := s.Html()
  666. if tmpErr != nil {
  667. err = tmpErr
  668. return
  669. }
  670. st := s.Text()
  671. if st != "" && st != "<br>" && st != "<br style=\"max-width: 100%;\">" && !strings.Contains(phtml, "iframe") {
  672. abstract = abstract + "<p>" + phtml + "</p>"
  673. }
  674. })
  675. paramsMap["report_abstract"] = abstract
  676. case "activity_list":
  677. bgColorMap := map[string]string{
  678. "未开始": "#E3B377",
  679. "进行中": "#3385FF",
  680. "已结束": "#A2A2A2",
  681. }
  682. statusItemMap := map[string]string{
  683. "未开始": "block",
  684. "进行中": "none",
  685. "已结束": "none",
  686. }
  687. offlineMap := map[string]string{
  688. "线上会议": "none",
  689. "线下沙龙": "block",
  690. }
  691. onlineMap := map[string]string{
  692. "线上会议": "block",
  693. "线下沙龙": "none",
  694. }
  695. listTitle := paramsMap["list_title"]
  696. status1 := paramsMap["status_1"]
  697. if status1 != "未开始" {
  698. newHeight = 1715
  699. }
  700. status2 := paramsMap["status_2"]
  701. paramsMap["list_title"] = "弘则FICC周度电话会安排"
  702. paramsMap["bg_color_1"] = bgColorMap[status1]
  703. paramsMap["show_item_1"] = statusItemMap[status1]
  704. paramsMap["show_offline_1"] = offlineMap[listTitle]
  705. paramsMap["show_online_1"] = onlineMap[listTitle]
  706. paramsMap["bg_color_2"] = bgColorMap[status2]
  707. paramsMap["show_item_2"] = statusItemMap[status2]
  708. paramsMap["show_offline_2"] = offlineMap[listTitle]
  709. paramsMap["show_online_2"] = onlineMap[listTitle]
  710. // 用默认内容填充的活动时间字体颜色调至看不见
  711. color1 := "#999"
  712. color2 := "#999"
  713. if paramsMap["empty_time_1"] == "true" {
  714. color1 = "#fff"
  715. }
  716. if paramsMap["empty_time_2"] == "true" {
  717. color2 = "#fff"
  718. }
  719. paramsMap["time_color_1"] = color1
  720. paramsMap["time_color_2"] = color2
  721. }
  722. contentStr = strings.Replace(contentStr, "{{SUN_CODE}}", sunCodeUrl, 1)
  723. for _, v := range htmlReplaceConfigList {
  724. tmpVal, ok := paramsMap[v.ReplaceStr]
  725. if !ok {
  726. tmpVal = ``
  727. }
  728. contentStr = strings.Replace(contentStr, v.TemplateStr, tmpVal, 1)
  729. }
  730. return
  731. }