share_poster.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  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_resource"
  11. "hongze/hongze_yb/models/tables/yb_suncode_pars"
  12. "hongze/hongze_yb/services/alarm_msg"
  13. "hongze/hongze_yb/services/wx_app"
  14. "hongze/hongze_yb/utils"
  15. "io/ioutil"
  16. "net/http"
  17. "os"
  18. "strings"
  19. "time"
  20. )
  21. var (
  22. ServerUrl = "http://127.0.0.1:5008/"
  23. VoiceBroadcastShareImgSource = "voice_broadcast_share_img"
  24. )
  25. // SharePosterReq 分享海报请求体
  26. type SharePosterReq struct {
  27. CodePage string `json:"code_page" description:"太阳码page"`
  28. CodeScene string `json:"code_scene" description:"太阳码scene"`
  29. Source string `json:"source" description:"来源"`
  30. Version string `json:"version" description:"海报版本号" `
  31. Pars string `json:"pars" description:"海报动态信息"`
  32. }
  33. type Html2ImgResp struct {
  34. Code int `json:"code"`
  35. Msg string `json:"msg"`
  36. Data string `json:"data"`
  37. }
  38. // postHtml2Img 请求htm2img接口
  39. func postHtml2Img(param map[string]interface{}) (resp *Html2ImgResp, err error) {
  40. // 目前仅此处调用该接口,暂不加授权、校验等
  41. postUrl := ServerUrl + "htm2img"
  42. postData, err := json.Marshal(param)
  43. if err != nil {
  44. return
  45. }
  46. result, err := Html2ImgHttpPost(postUrl, string(postData), "application/json")
  47. if err != nil {
  48. return
  49. }
  50. if err = json.Unmarshal(result, &resp); err != nil {
  51. return
  52. }
  53. return resp, nil
  54. }
  55. // Html2ImgHttpPost post请求
  56. func Html2ImgHttpPost(url, postData string, params ...string) ([]byte, error) {
  57. body := ioutil.NopCloser(strings.NewReader(postData))
  58. client := &http.Client{}
  59. req, err := http.NewRequest("POST", url, body)
  60. if err != nil {
  61. return nil, err
  62. }
  63. contentType := "application/x-www-form-urlencoded;charset=utf-8"
  64. if len(params) > 0 && params[0] != "" {
  65. contentType = params[0]
  66. }
  67. req.Header.Set("Content-Type", contentType)
  68. resp, err := client.Do(req)
  69. if err != nil {
  70. return nil, err
  71. }
  72. defer resp.Body.Close()
  73. b, err := ioutil.ReadAll(resp.Body)
  74. fmt.Println("HttpPost:" + string(b))
  75. return b, err
  76. }
  77. // CreateAndUploadSunCode 生成太阳码并上传OSS
  78. func CreateAndUploadSunCode(page, scene, version string) (imgUrl string, err error) {
  79. if page == "" {
  80. err = errors.New("page不能为空")
  81. return
  82. }
  83. path := fmt.Sprint(page, "?", scene)
  84. exist, err := yb_poster_resource.GetPosterByCondition(path, "qrcode", version)
  85. if err != nil && err != utils.ErrNoRow {
  86. return
  87. }
  88. if exist != nil && exist.ImgURL != "" {
  89. return exist.ImgURL, nil
  90. }
  91. // scene超过32位会生成失败,md5处理至32位
  92. sceneMD5 := "a=1"
  93. if scene != "" {
  94. sceneMD5 = utils.MD5(scene)
  95. }
  96. picByte, err := wx_app.GetSunCode(page, sceneMD5)
  97. if err != nil {
  98. return
  99. }
  100. // 生成图片
  101. localPath := "./static/img"
  102. fileName := utils.GetRandStringNoSpecialChar(28) + ".png"
  103. fpath := fmt.Sprint(localPath, "/", fileName)
  104. f, err := os.Create(fpath)
  105. if err != nil {
  106. return
  107. }
  108. if _, err = f.Write(picByte); err != nil {
  109. return
  110. }
  111. defer func() {
  112. f.Close()
  113. os.Remove(fpath)
  114. }()
  115. // 上传OSS
  116. fileDir := "images/yb/suncode/"
  117. imgUrl, err = UploadAliyunToDir(fileName, fpath, fileDir)
  118. if err != nil {
  119. return
  120. }
  121. // 记录二维码信息
  122. newPoster := &yb_poster_resource.YbPosterResource{
  123. Path: path,
  124. ImgURL: imgUrl,
  125. Type: "qrcode",
  126. Version: version,
  127. CreateTime: time.Now(),
  128. }
  129. err = newPoster.Create()
  130. if err != nil {
  131. return
  132. }
  133. // 记录参数md5
  134. if scene != "" {
  135. newPars := &yb_suncode_pars.YbSuncodePars{
  136. Scene: scene,
  137. SceneKey: sceneMD5,
  138. CreateTime: time.Now(),
  139. }
  140. err = newPars.Create()
  141. }
  142. // 记录文件
  143. go func() {
  144. re := new(yb_resource.YbResource)
  145. re.ResourceUrl = imgUrl
  146. re.ResourceType = yb_resource.ResourceTypeImg
  147. re.CreateTime = time.Now().Local()
  148. if e := re.Create(); e != nil {
  149. return
  150. }
  151. }()
  152. return
  153. }
  154. // CreatePosterFromSourceV2 生成分享海报(通过配置获取相关信息)
  155. func CreatePosterFromSourceV2(codePage, codeScene, source, version, pars string) (imgUrl string, err error) {
  156. var errMsg string
  157. defer func() {
  158. if err != nil {
  159. global.LOG.Critical(fmt.Sprintf("CreatePosterFromSource: source=%s, pars:%s, errMsg:%s", source, pars, errMsg))
  160. reqSlice := make([]string, 0)
  161. reqSlice = append(reqSlice, fmt.Sprint("CodePage:", codePage, "\n"))
  162. reqSlice = append(reqSlice, fmt.Sprint("CodeScene:", codeScene, "\n"))
  163. reqSlice = append(reqSlice, fmt.Sprint("Source:", source, "\n"))
  164. reqSlice = append(reqSlice, fmt.Sprint("Version:", version, "\n"))
  165. reqSlice = append(reqSlice, fmt.Sprint("Pars:", pars, "\n"))
  166. go alarm_msg.SendAlarmMsg("CreatePosterFromSource生成分享海报失败, Msg:"+errMsg+";Err:"+err.Error()+"\n;Req:\n"+strings.Join(reqSlice, ";"), 3)
  167. }
  168. }()
  169. if codePage == "" || source == "" || pars == "" {
  170. errMsg = "参数有误"
  171. err = errors.New(errMsg)
  172. return
  173. }
  174. path := fmt.Sprint(codePage, "?", codeScene)
  175. // 非列表来源获取历史图片,无则生成
  176. if !strings.Contains(source, "list") && source != "price_driven" {
  177. poster, tmpErr := yb_poster_resource.GetPosterByCondition(path, "poster", version)
  178. if tmpErr != nil && tmpErr != utils.ErrNoRow {
  179. err = tmpErr
  180. return
  181. }
  182. if poster != nil && poster.ImgURL != "" {
  183. imgUrl = poster.ImgURL
  184. return
  185. }
  186. }
  187. ybPosterConfig, err := yb_poster_config.GetBySource(source)
  188. if err != nil {
  189. return
  190. }
  191. width := ybPosterConfig.Width
  192. height := ybPosterConfig.Hight
  193. //生成太阳码
  194. sunCodeUrl, err := CreateAndUploadSunCode(codePage, codeScene, version)
  195. if err != nil {
  196. return
  197. }
  198. //sunCodeUrl := ``
  199. // 填充html内容
  200. contentStr, newHeight, err := fillContent2HtmlV2(source, pars, sunCodeUrl, height, *ybPosterConfig)
  201. if err != nil {
  202. errMsg = "html内容有误"
  203. return
  204. }
  205. global.LOG.Critical(contentStr)
  206. //return
  207. // 请求python服务htm2img
  208. htm2ImgReq := make(map[string]interface{})
  209. htm2ImgReq["html_content"] = contentStr
  210. htm2ImgReq["width"] = width
  211. htm2ImgReq["height"] = newHeight
  212. res, err := postHtml2Img(htm2ImgReq)
  213. if err != nil || res == nil {
  214. errMsg = "html转图片请求失败"
  215. return
  216. }
  217. if res.Code != 200 {
  218. errMsg = "html转图片请求失败"
  219. err = errors.New("html转图片失败: " + res.Msg)
  220. return
  221. }
  222. imgUrl = res.Data
  223. // 记录海报信息
  224. newPoster := &yb_poster_resource.YbPosterResource{
  225. Path: path,
  226. ImgURL: imgUrl,
  227. Type: "poster",
  228. Version: version,
  229. CreateTime: time.Now(),
  230. }
  231. err = newPoster.Create()
  232. return
  233. }
  234. // HtmlReplaceConfig html替换配置
  235. type HtmlReplaceConfig struct {
  236. TemplateStr string `json:"template_str"`
  237. ReplaceStr string `json:"replace_str"`
  238. }
  239. // DefaultValueConfig 默认值的配置
  240. type DefaultValueConfig struct {
  241. Key string `json:"key"`
  242. UseOtherKey string `json:"use_other_key"`
  243. Value string `json:"value"`
  244. ConditionKey string `json:"condition_key"`
  245. }
  246. // fillContent2Html 填充HTML动态内容
  247. func fillContent2HtmlV2(source, pars, sunCodeUrl string, height float64, ybPosterConfig yb_poster_config.YbPosterConfig) (contentStr string, newHeight float64, err error) {
  248. paramsMap := make(map[string]string)
  249. if err = json.Unmarshal([]byte(pars), &paramsMap); err != nil {
  250. return
  251. }
  252. //fmt.Println(paramsMap)
  253. //html替换规则
  254. htmlReplaceConfigList := make([]HtmlReplaceConfig, 0)
  255. if err = json.Unmarshal([]byte(ybPosterConfig.HTMLReplaceConfig), &htmlReplaceConfigList); err != nil {
  256. return
  257. }
  258. newHeight = height
  259. contentStr = ybPosterConfig.HTMLTemplate
  260. // 默认数据替换
  261. defaultValueConfigMap := make([]DefaultValueConfig, 0)
  262. if ybPosterConfig.DefaultValueConfig != `` {
  263. if err = json.Unmarshal([]byte(ybPosterConfig.DefaultValueConfig), &defaultValueConfigMap); err != nil {
  264. return
  265. }
  266. }
  267. // 列表的动态内容不完整的用默认内容的填充
  268. //var emptyTime1, emptyTime2 bool
  269. conditionKeyValMap := make(map[string]string)
  270. for _, v := range defaultValueConfigMap {
  271. if v.ConditionKey == `` {
  272. continue
  273. }
  274. conditionKeyVal, ok := conditionKeyValMap[v.ConditionKey]
  275. if !ok {
  276. conditionKeyVal = paramsMap[v.ConditionKey]
  277. conditionKeyValMap[v.ConditionKey] = conditionKeyVal
  278. }
  279. if conditionKeyVal == `` {
  280. paramsMap[v.Key] = v.Value
  281. if v.UseOtherKey != `` {
  282. if tmpVal, ok := paramsMap[v.UseOtherKey]; ok {
  283. paramsMap[v.Key] = tmpVal
  284. }
  285. }
  286. }
  287. }
  288. // 填充指定内容
  289. switch source {
  290. case "report_detail": //需要将简介处理下
  291. reportAbstract := paramsMap["report_abstract"]
  292. doc, tmpErr := goquery.NewDocumentFromReader(strings.NewReader(reportAbstract))
  293. if tmpErr != nil {
  294. err = tmpErr
  295. return
  296. }
  297. abstract := ""
  298. doc.Find("p").Each(func(i int, s *goquery.Selection) {
  299. phtml, tmpErr := s.Html()
  300. if tmpErr != nil {
  301. err = tmpErr
  302. return
  303. }
  304. st := s.Text()
  305. if st != "" && st != "<br>" && st != "<br style=\"max-width: 100%;\">" && !strings.Contains(phtml, "iframe") {
  306. abstract = abstract + "<p>" + phtml + "</p>"
  307. }
  308. })
  309. paramsMap["report_abstract"] = abstract
  310. case "activity_list":
  311. bgColorMap := map[string]string{
  312. "未开始": "#E3B377",
  313. "进行中": "#3385FF",
  314. "已结束": "#A2A2A2",
  315. }
  316. statusItemMap := map[string]string{
  317. "未开始": "block",
  318. "进行中": "none",
  319. "已结束": "none",
  320. }
  321. offlineMap := map[string]string{
  322. "线上会议": "none",
  323. "线下沙龙": "block",
  324. }
  325. onlineMap := map[string]string{
  326. "线上会议": "block",
  327. "线下沙龙": "none",
  328. }
  329. listTitle := paramsMap["list_title"]
  330. status1 := paramsMap["status_1"]
  331. if status1 != "未开始" {
  332. newHeight = 1715
  333. }
  334. status2 := paramsMap["status_2"]
  335. paramsMap["list_title"] = "弘则FICC周度电话会安排"
  336. paramsMap["bg_color_1"] = bgColorMap[status1]
  337. paramsMap["show_item_1"] = statusItemMap[status1]
  338. paramsMap["show_offline_1"] = offlineMap[listTitle]
  339. paramsMap["show_online_1"] = onlineMap[listTitle]
  340. paramsMap["bg_color_2"] = bgColorMap[status2]
  341. paramsMap["show_item_2"] = statusItemMap[status2]
  342. paramsMap["show_offline_2"] = offlineMap[listTitle]
  343. paramsMap["show_online_2"] = onlineMap[listTitle]
  344. // 用默认内容填充的活动时间字体颜色调至看不见
  345. color1 := "#999"
  346. color2 := "#999"
  347. if paramsMap["empty_time_1"] == "true" {
  348. color1 = "#fff"
  349. }
  350. if paramsMap["empty_time_2"] == "true" {
  351. color2 = "#fff"
  352. }
  353. paramsMap["time_color_1"] = color1
  354. paramsMap["time_color_2"] = color2
  355. }
  356. contentStr = strings.Replace(contentStr, "{{SUN_CODE}}", sunCodeUrl, 1)
  357. for _, v := range htmlReplaceConfigList {
  358. tmpVal, ok := paramsMap[v.ReplaceStr]
  359. if !ok {
  360. tmpVal = ``
  361. }
  362. contentStr = strings.Replace(contentStr, v.TemplateStr, tmpVal, 1)
  363. }
  364. return
  365. }
  366. // GetDynamicShareImg 生成动态分享图
  367. func GetDynamicShareImg(source, pars string) (imgUrl string, err error) {
  368. if source == "" {
  369. err = errors.New("图片来源有误")
  370. return
  371. }
  372. imgConfig, e := yb_poster_config.GetBySource(source)
  373. if e != nil {
  374. err = errors.New("获取图片配置失败")
  375. return
  376. }
  377. // 填充html内容
  378. content, newHeight, e := fillContent2HtmlV2(source, pars, "", imgConfig.Hight, *imgConfig)
  379. if e != nil {
  380. err = errors.New("html内容有误")
  381. return
  382. }
  383. htm2ImgReq := make(map[string]interface{})
  384. htm2ImgReq["html_content"] = content
  385. htm2ImgReq["width"] = imgConfig.Width
  386. htm2ImgReq["height"] = newHeight
  387. res, e := postHtml2Img(htm2ImgReq)
  388. if e != nil || res == nil {
  389. err = errors.New("html转图片请求失败")
  390. return
  391. }
  392. if res.Code != 200 {
  393. err = errors.New("html转图片请求失败: " + res.Msg)
  394. return
  395. }
  396. imgUrl = res.Data
  397. return
  398. }