share_poster.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. package services
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "hongze/hongze_yb/global"
  7. "hongze/hongze_yb/models/tables/yb_poster_resource"
  8. "hongze/hongze_yb/models/tables/yb_suncode_pars"
  9. "hongze/hongze_yb/services/wx_app"
  10. "hongze/hongze_yb/utils"
  11. "io/ioutil"
  12. "net/http"
  13. "os"
  14. "strings"
  15. "time"
  16. )
  17. // SharePosterReq 分享海报请求体
  18. type SharePosterReq struct {
  19. CodePage string `json:"code_page" description:"太阳码page"`
  20. CodeScene string `json:"code_scene" description:"太阳码scene"`
  21. Source string `json:"source" description:"来源"`
  22. Version string `json:"version" description:"海报版本号" `
  23. Pars string `json:"pars" description:"海报动态信息"`
  24. }
  25. // CreatePosterFromSource 生成分享海报
  26. func CreatePosterFromSource(codePage, codeScene, source, version, pars string) (imgUrl string, err error) {
  27. var errMsg string
  28. defer func() {
  29. if err != nil {
  30. global.LOG.Critical(fmt.Sprintf("CreatePosterFromSource: source=%s, pars:%s, errMsg:%s", source, pars, errMsg))
  31. go SendEmail(utils.APPNAME+"【"+global.CONFIG.Serve.RunMode+"】"+"失败提醒", "生成分享海报失败, "+errMsg+", CreatePosterFromSource: "+err.Error(), utils.EmailSendToUsers)
  32. }
  33. }()
  34. if codePage == "" || source == "" || pars == "" {
  35. errMsg = "参数有误"
  36. err = errors.New(errMsg)
  37. return
  38. }
  39. path := fmt.Sprint(codePage, "?", codeScene)
  40. // 非列表来源获取历史图片,无则生成
  41. if !strings.Contains(source, "list") {
  42. poster, tmpErr := yb_poster_resource.GetPosterByCondition(path, "poster", version)
  43. if tmpErr != nil && tmpErr != utils.ErrNoRow {
  44. err = tmpErr
  45. return
  46. }
  47. if poster != nil && poster.ImgURL != "" {
  48. imgUrl = poster.ImgURL
  49. return
  50. }
  51. }
  52. // 图片长宽
  53. heightMap := map[string]int{
  54. "activity_detail": 1210,
  55. "activity_list": 1948,
  56. "special_column_detail": 1480,
  57. "special_column_list": 1548,
  58. "chart_detail": 1536,
  59. "chart_list": 1352,
  60. "report_detail": 1420,
  61. "report_list": 1344,
  62. }
  63. widthMap := map[string]int{
  64. "activity_detail": 1280,
  65. "activity_list": 1280,
  66. "special_column_detail": 1176,
  67. "special_column_list": 1176,
  68. "chart_detail": 1176,
  69. "chart_list": 1176,
  70. "report_detail": 1176,
  71. "report_list": 1176,
  72. }
  73. width := widthMap[source]
  74. height := heightMap[source]
  75. if width == 0 || height == 0 {
  76. errMsg = "图片长度有误"
  77. err = errors.New(errMsg)
  78. return "", err
  79. }
  80. // 生成太阳码
  81. sunCodeUrl, err := CreateAndUploadSunCode(codePage, codeScene, version)
  82. if err != nil {
  83. return
  84. }
  85. // 填充html内容
  86. contentStr, err := fillContent2Html(source, pars, sunCodeUrl)
  87. if err != nil {
  88. errMsg = "html内容有误"
  89. return
  90. }
  91. // 请求python服务htm2img
  92. htm2ImgReq := make(map[string]interface{})
  93. htm2ImgReq["html_content"] = contentStr
  94. htm2ImgReq["width"] = width
  95. htm2ImgReq["height"] = height
  96. res, err := postHtml2Img(htm2ImgReq)
  97. if err != nil || res == nil {
  98. errMsg = "html转图片请求失败"
  99. return
  100. }
  101. if res.Code != 200 {
  102. errMsg = "html转图片请求失败"
  103. err = errors.New("html转图片失败: " + res.Msg)
  104. return
  105. }
  106. imgUrl = res.Data
  107. // 记录海报信息
  108. newPoster := &yb_poster_resource.YbPosterResource{
  109. Path: path,
  110. ImgURL: imgUrl,
  111. Type: "poster",
  112. Version: version,
  113. CreateTime: time.Now(),
  114. }
  115. err = newPoster.Create()
  116. return
  117. }
  118. type Html2ImgResp struct {
  119. Code int `json:"code"`
  120. Msg string `json:"msg"`
  121. Data string `json:"data"`
  122. }
  123. // postHtml2Img 请求htm2img接口
  124. func postHtml2Img(param map[string]interface{}) (resp *Html2ImgResp, err error) {
  125. pythonUrl := ""
  126. if global.CONFIG.Serve.RunMode == "release" {
  127. } else {
  128. pythonUrl = "http://127.0.0.1:5008/"
  129. }
  130. postUrl := pythonUrl + "htm2img"
  131. postData, err := json.Marshal(param)
  132. if err != nil {
  133. return
  134. }
  135. result, err := Html2ImgHttpPost(postUrl, string(postData), "application/json")
  136. if err != nil {
  137. return
  138. }
  139. if err = json.Unmarshal(result, &resp); err != nil {
  140. return
  141. }
  142. return resp, nil
  143. }
  144. // Html2ImgHttpPost post请求
  145. func Html2ImgHttpPost(url, postData string, params ...string) ([]byte, error) {
  146. body := ioutil.NopCloser(strings.NewReader(postData))
  147. client := &http.Client{}
  148. req, err := http.NewRequest("POST", url, body)
  149. if err != nil {
  150. return nil, err
  151. }
  152. contentType := "application/x-www-form-urlencoded;charset=utf-8"
  153. if len(params) > 0 && params[0] != "" {
  154. contentType = params[0]
  155. }
  156. req.Header.Set("Content-Type", contentType)
  157. resp, err := client.Do(req)
  158. if err != nil {
  159. return nil, err
  160. }
  161. defer resp.Body.Close()
  162. b, err := ioutil.ReadAll(resp.Body)
  163. fmt.Println("HttpPost:" + string(b))
  164. return b, err
  165. }
  166. // CreateAndUploadSunCode 生成太阳码并上传OSS
  167. func CreateAndUploadSunCode(page, scene, version string) (imgUrl string, err error) {
  168. if page == "" {
  169. err = errors.New("page不能为空")
  170. return
  171. }
  172. path := fmt.Sprint(page, "?", scene)
  173. exist, err := yb_poster_resource.GetPosterByCondition(path, "qrcode", version)
  174. if err != nil && err != utils.ErrNoRow {
  175. return
  176. }
  177. if exist != nil && exist.ImgURL != "" {
  178. return exist.ImgURL, nil
  179. }
  180. // scene超过32位会生成失败,md5处理至32位
  181. sceneMD5 := "a=1"
  182. if scene != "" {
  183. sceneMD5 = utils.MD5(scene)
  184. }
  185. picByte, err := wx_app.GetSunCode(page, sceneMD5)
  186. if err != nil {
  187. return
  188. }
  189. // 生成图片
  190. localPath := "./static/img"
  191. fileName := utils.GetRandStringNoSpecialChar(28) + ".png"
  192. fpath := fmt.Sprint(localPath, "/", fileName)
  193. f, err := os.Create(fpath)
  194. if err != nil {
  195. return
  196. }
  197. if _, err = f.Write(picByte); err != nil {
  198. return
  199. }
  200. defer func() {
  201. f.Close()
  202. os.Remove(fpath)
  203. }()
  204. // 上传OSS
  205. fileDir := "yb/suncode/"
  206. imgUrl, err = UploadAliyunToDir(fileName, fpath, fileDir)
  207. if err != nil {
  208. return
  209. }
  210. // 记录二维码信息
  211. newPoster := &yb_poster_resource.YbPosterResource{
  212. Path: path,
  213. ImgURL: imgUrl,
  214. Type: "qrcode",
  215. Version: version,
  216. CreateTime: time.Now(),
  217. }
  218. err = newPoster.Create()
  219. if err != nil {
  220. return
  221. }
  222. // 记录参数md5
  223. if scene != "" {
  224. newPars := &yb_suncode_pars.YbSuncodePars{
  225. Scene: scene,
  226. SceneKey: sceneMD5,
  227. CreateTime: time.Now(),
  228. }
  229. err = newPars.Create()
  230. }
  231. return
  232. }
  233. type PosterParsReq struct {
  234. ActivityTitle string `json:"activity_title"`
  235. ActivityAvatar string `json:"activity_avatar"`
  236. ActivitySpeaker string `json:"activity_speaker"`
  237. ActivityTime string `json:"activity_time"`
  238. ChartName string `json:"chart_name"`
  239. ChartImage string `json:"chart_image"`
  240. ReportType string `json:"report_type"`
  241. ReportAvatar string `json:"report_avatar"`
  242. ReportTitle string `json:"report_title"`
  243. ReportAbstract string `json:"report_abstract"`
  244. Stage1 string `json:"stage_1"`
  245. Avatar1 string `json:"avatar_1"`
  246. Title1 string `json:"title_1"`
  247. Author1 string `json:"author_1"`
  248. Tag1 string `json:"tag_1"`
  249. Img1 string `json:"img_1"`
  250. Time1 string `json:"time_1"`
  251. Abstract1 string `json:"abstract_1"`
  252. Status1 string `json:"status_1"`
  253. Speaker1 string `json:"speaker_1"`
  254. Stage2 string `json:"stage_2"`
  255. Avatar2 string `json:"avatar_2"`
  256. Title2 string `json:"title_2"`
  257. Author2 string `json:"author_2"`
  258. Tag2 string `json:"tag_2"`
  259. Img2 string `json:"img_2"`
  260. Abstract2 string `json:"abstract_2"`
  261. Time2 string `json:"time_2"`
  262. Status2 string `json:"status_2"`
  263. Speaker2 string `json:"speaker_2"`
  264. ListTitle string `json:"list_title"`
  265. }
  266. // fillContent2Html 填充HTML动态内容
  267. func fillContent2Html(source string, pars, sunCodeUrl string) (contentStr string, err error) {
  268. params := new(PosterParsReq)
  269. if err = json.Unmarshal([]byte(pars), &params); err != nil {
  270. return
  271. }
  272. template := fmt.Sprint("static/htm2img/", source, ".html")
  273. contentByte, err := ioutil.ReadFile(template)
  274. if err != nil {
  275. return
  276. }
  277. contentStr = string(contentByte)
  278. // 列表的动态内容不完整的用默认内容的填充
  279. emptyTime := false
  280. if strings.Contains(source, "list") {
  281. if params.Title1 == "" || params.Title2 == "" {
  282. defaultAvatar := "https://hzstatic.hzinsights.com/static/images/202112/20211210/wn6c3oYKTfT4NbTZgRGflRuIBZaa.png"
  283. switch source {
  284. case "activity_list":
  285. params.ListTitle = "线下沙龙"
  286. params.Status1 = "未开始"
  287. params.Status2 = "未开始"
  288. params.Avatar1 = defaultAvatar
  289. params.Avatar2 = defaultAvatar
  290. params.Title1 = "周度报告"
  291. params.Title2 = "周度报告"
  292. params.Speaker1 = "FICC研究员"
  293. params.Speaker2 = "FICC研究员"
  294. params.Time1 = "2022-5-10"
  295. params.Time2 = "2022-5-10"
  296. emptyTime = true
  297. case "special_column_list":
  298. params.Stage1 = "第1期"
  299. params.Stage2 = "第2期"
  300. params.Avatar1 = defaultAvatar
  301. params.Avatar2 = defaultAvatar
  302. params.Title1 = "弘则FICC专栏"
  303. params.Title2 = "弘则FICC专栏"
  304. params.Author1 = "弘则研究"
  305. params.Author2 = "弘则研究"
  306. params.Tag1 = "FICC研究员"
  307. params.Tag2 = "FICC研究员"
  308. case "chart_list":
  309. params.Title1 = "螺纹仓单-热卷仓单季节性"
  310. params.Title2 = "卷螺期货现货价差"
  311. params.Img1 = "https://hzstatic.hzinsights.com/static/images/202112/20211227/8VBIH1l6VraYpLCoBS6qOIXA5Zoq.png"
  312. params.Img2 = "https://hzstatic.hzinsights.com/static/images/202204/20220427/d8GRfdR3Xfrvk397SnYudcwVs9pV.png"
  313. case "report_list":
  314. params.Img1 = defaultAvatar
  315. params.Img2 = defaultAvatar
  316. params.Title1 = "弘则FICC研报"
  317. params.Title2 = "弘则FICC研报"
  318. params.Time1 = "1期"
  319. params.Time2 = "2期"
  320. }
  321. }
  322. }
  323. // 填充指定内容
  324. switch source {
  325. case "activity_detail":
  326. contentStr = strings.Replace(contentStr, "{{ACTIVITY_TITLE}}", params.ActivityTitle, 1)
  327. contentStr = strings.Replace(contentStr, "{{ACTIVITY_AVATAR}}", params.ActivityAvatar, 1)
  328. contentStr = strings.Replace(contentStr, "{{ACTIVITY_SPEAKER}}", params.ActivitySpeaker, 1)
  329. contentStr = strings.Replace(contentStr, "{{ACTIVITY_TIME}}", params.ActivityTime, 1)
  330. case "special_column_detail":
  331. contentStr = strings.Replace(contentStr, "{{REPORT_AVATAR}}", params.ReportAvatar, 1)
  332. contentStr = strings.Replace(contentStr, "{{REPORT_TITLE}}", params.ReportTitle, 1)
  333. contentStr = strings.Replace(contentStr, "{{REPORT_ABSTRACT}}", params.ReportAbstract, 1)
  334. case "chart_detail":
  335. contentStr = strings.Replace(contentStr, "{{CHART_NAME}}", params.ChartName, 1)
  336. contentStr = strings.Replace(contentStr, "{{CHART_IMAGE}}", params.ChartImage, 1)
  337. case "report_detail":
  338. contentStr = strings.Replace(contentStr, "{{REPORT_TYPE}}", params.ReportType, 1)
  339. contentStr = strings.Replace(contentStr, "{{REPORT_TITLE}}", params.ReportTitle, 1)
  340. contentStr = strings.Replace(contentStr, "{{REPORT_ABSTRACT}}", params.ReportAbstract, 1)
  341. case "special_column_list":
  342. contentStr = strings.Replace(contentStr, "{{LIST_TITLE}}", params.ListTitle, 1)
  343. contentStr = strings.Replace(contentStr, "{{STAGE_1}}", "第" + params.Stage1 + "期", 1)
  344. contentStr = strings.Replace(contentStr, "{{AVATAR_1}}", params.Avatar1, 1)
  345. contentStr = strings.Replace(contentStr, "{{TITLE_1}}", params.Title1, 1)
  346. contentStr = strings.Replace(contentStr, "{{AUTHOR_1}}", params.Author1, 1)
  347. contentStr = strings.Replace(contentStr, "{{TAG_1}}", params.Tag1, 1)
  348. contentStr = strings.Replace(contentStr, "{{STAGE_2}}", "第" + params.Stage2 + "期", 1)
  349. contentStr = strings.Replace(contentStr, "{{AVATAR_2}}", params.Avatar2, 1)
  350. contentStr = strings.Replace(contentStr, "{{TITLE_2}}", params.Title2, 1)
  351. contentStr = strings.Replace(contentStr, "{{AUTHOR_2}}", params.Author2, 1)
  352. contentStr = strings.Replace(contentStr, "{{TAG_2}}", params.Tag2, 1)
  353. case "chart_list":
  354. contentStr = strings.Replace(contentStr, "{{LIST_TITLE}}", params.ListTitle, 1)
  355. contentStr = strings.Replace(contentStr, "{{TITLE_1}}", params.Title1, 1)
  356. contentStr = strings.Replace(contentStr, "{{IMG_1}}", params.Img1, 1)
  357. contentStr = strings.Replace(contentStr, "{{TITLE_2}}", params.Title2, 1)
  358. contentStr = strings.Replace(contentStr, "{{IMG_2}}", params.Img2, 1)
  359. case "report_list":
  360. contentStr = strings.Replace(contentStr, "{{LIST_TITLE}}", params.ListTitle, 1)
  361. contentStr = strings.Replace(contentStr, "{{TITLE_1}}", params.Title1, 1)
  362. contentStr = strings.Replace(contentStr, "{{ABSTRACT_1}}", params.Abstract1, 1)
  363. contentStr = strings.Replace(contentStr, "{{IMG_1}}", params.Img1, 1)
  364. contentStr = strings.Replace(contentStr, "{{TIME_1}}", params.Time1, 1)
  365. contentStr = strings.Replace(contentStr, "{{TITLE_2}}", params.Title2, 1)
  366. contentStr = strings.Replace(contentStr, "{{ABSTRACT_2}}", params.Abstract2, 1)
  367. contentStr = strings.Replace(contentStr, "{{IMG_2}}", params.Img2, 1)
  368. contentStr = strings.Replace(contentStr, "{{TIME_2}}", params.Time2, 1)
  369. case "activity_list":
  370. bgColorMap := map[string]string{
  371. "未开始": "#E3B377",
  372. "进行中": "#3385FF",
  373. "已结束": "#A2A2A2",
  374. }
  375. statusItemMap := map[string]string{
  376. "未开始": "block",
  377. "进行中": "none",
  378. "已结束": "none",
  379. }
  380. offlineMap := map[string]string{
  381. "线上会议": "none",
  382. "线下沙龙": "block",
  383. }
  384. onlineMap := map[string]string{
  385. "线上会议": "block",
  386. "线下沙龙": "none",
  387. }
  388. contentStr = strings.Replace(contentStr, "{{LIST_TITLE}}", "弘则FICC周度电话会安排", 1)
  389. contentStr = strings.Replace(contentStr, "{{BG_COLORE_1}}", bgColorMap[params.Status1], 1)
  390. contentStr = strings.Replace(contentStr, "{{STATUS_1}}", params.Status1, 1)
  391. contentStr = strings.Replace(contentStr, "{{AVATAR_1}}", params.Avatar1, 1)
  392. contentStr = strings.Replace(contentStr, "{{TITLE_1}}", params.Title1, 1)
  393. contentStr = strings.Replace(contentStr, "{{SPEAKER_1}}", params.Speaker1, 1)
  394. contentStr = strings.Replace(contentStr, "{{TIME_1}}", params.Time1, 1)
  395. contentStr = strings.Replace(contentStr, "{{SHOW_ITEM_1}}", statusItemMap[params.Status1], 1)
  396. contentStr = strings.Replace(contentStr, "{{SHOW_OFFLINE_1}}", offlineMap[params.ListTitle], 1)
  397. contentStr = strings.Replace(contentStr, "{{SHOW_ONLINE_1}}", onlineMap[params.ListTitle], 1)
  398. contentStr = strings.Replace(contentStr, "{{BG_COLORE_2}}", bgColorMap[params.Status2], 1)
  399. contentStr = strings.Replace(contentStr, "{{STATUS_2}}", params.Status2, 1)
  400. contentStr = strings.Replace(contentStr, "{{AVATAR_2}}", params.Avatar2, 1)
  401. contentStr = strings.Replace(contentStr, "{{TITLE_2}}", params.Title2, 1)
  402. contentStr = strings.Replace(contentStr, "{{SPEAKER_2}}", params.Speaker2, 1)
  403. contentStr = strings.Replace(contentStr, "{{TIME_2}}", params.Time2, 1)
  404. contentStr = strings.Replace(contentStr, "{{SHOW_ITEM_2}}", statusItemMap[params.Status2], 1)
  405. contentStr = strings.Replace(contentStr, "{{SHOW_OFFLINE_2}}", offlineMap[params.ListTitle], 1)
  406. contentStr = strings.Replace(contentStr, "{{SHOW_ONLINE_2}}", onlineMap[params.ListTitle], 1)
  407. if emptyTime {
  408. contentStr = strings.Replace(contentStr, "{{TIME_COLOR_1}}", "#fff", 1)
  409. contentStr = strings.Replace(contentStr, "{{TIME_COLOR_2}}", "#fff", 1)
  410. } else {
  411. contentStr = strings.Replace(contentStr, "{{TIME_COLOR_1}}", "#999", 1)
  412. contentStr = strings.Replace(contentStr, "{{TIME_COLOR_2}}", "#999", 1)
  413. }
  414. }
  415. contentStr = strings.Replace(contentStr, "{{SUN_CODE}}", sunCodeUrl, 1)
  416. return
  417. }