creation_check.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. package service
  2. import (
  3. "context"
  4. "html"
  5. "regexp"
  6. "strconv"
  7. "strings"
  8. "unicode"
  9. "unicode/utf16"
  10. "unicode/utf8"
  11. "go-common/app/interface/openplatform/article/dao"
  12. "go-common/app/interface/openplatform/article/model"
  13. "go-common/library/ecode"
  14. "go-common/library/log"
  15. strip "github.com/grokify/html-strip-tags-go"
  16. )
  17. var (
  18. _zeroWidthReg = regexp.MustCompile(`[\x{200b}]+`)
  19. _nocharReg = []*regexp.Regexp{
  20. // regexp.MustCompile(`[\p{Hangul}]+`), // kr
  21. regexp.MustCompile(`[\p{Tibetan}]+`), // tibe
  22. regexp.MustCompile(`[\p{Arabic}]+`), // arabic
  23. }
  24. _chineseReg = regexp.MustCompile(`[\p{Han}]+`) // chinese
  25. )
  26. func (s *Service) allowRepeat(c context.Context, mid int64, title string) (ok bool) {
  27. log.Info("allowRepeat check start | mid(%d) title(%s).", mid, title)
  28. exist, _ := s.dao.SubmitCache(c, mid, title)
  29. log.Info("allowRepeat from cache | mid(%d) title(%s) exist(%d).", mid, title, exist)
  30. if !exist {
  31. log.Info("allowRepeat not exist | mid(%d) title(%s)", mid, title)
  32. s.dao.AddSubmitCache(c, mid, title)
  33. log.Info("allowRepeat add cache | mid(%d) title(%s).", mid, title)
  34. ok = true
  35. return
  36. }
  37. dao.PromInfo("creation:禁止重复标题")
  38. return
  39. }
  40. func (s *Service) preMust(c context.Context, art *model.Article) (err error) {
  41. var ok bool
  42. if art.Title, ok = s.checkTitle(art.Title); !ok || art.Title == "" {
  43. log.Error("s.checkTitle mid(%d) art.Title(%s) title contains illegal char or is empty", art.Author.Mid, art.Title)
  44. err = ecode.CreativeArticleTitleErr
  45. return
  46. }
  47. if art.Content, ok = s.checkContent(art.Content); !ok {
  48. log.Error("s.checkContent mid(%d) content too long", art.Author.Mid)
  49. err = ecode.CreativeArticleContentErr
  50. return
  51. }
  52. if !s.allowCategory(art.Category.ID) {
  53. log.Error("s.allowCategory mid(%d) art.Category(%d) not exists", art.Author.Mid, art.Category)
  54. err = ecode.CreativeArticleCategoryErr
  55. return
  56. }
  57. if !s.allowReprints(int8(art.Reprint)) {
  58. log.Error("s.allowReprints mid(%d) art.Reprint(%d) illegal reprint", art.Author.Mid, art.Reprint)
  59. err = ecode.CreativeArticleReprintErr
  60. return
  61. }
  62. if !s.allowTID(int8(art.TemplateID)) {
  63. log.Error("s.allowTID mid(%d) art.TemplateID(%d) illegal reprint", art.Author.Mid, art.TemplateID)
  64. err = ecode.CreativeArticleTIDErr
  65. return
  66. }
  67. if !model.ValidTemplate(art.TemplateID, art.ImageURLs) {
  68. err = ecode.ArtCreationTplErr
  69. return
  70. }
  71. if !s.allowTag(art.Tags) {
  72. log.Error("s.allowTag mid(%d) art.Tags(%s) tag name or number too large", art.Author.Mid, art.Tags)
  73. err = ecode.CreativeArticleTagErr
  74. }
  75. if art.Dynamic, ok = s.allowDynamicIntro(art.Dynamic); !ok {
  76. log.Error("s.checkDynamicIntro mid(%d) art.DynamicIntro(%s) title contains illegal char", art.Author.Mid, art.Dynamic)
  77. err = ecode.CreativeDynamicIntroErr
  78. return
  79. }
  80. return
  81. }
  82. func (s *Service) checkTitle(title string) (ct string, ok bool) {
  83. title = strings.TrimSpace(title)
  84. allCount := utf8.RuneCountInString(title)
  85. enCount := utf8.RuneCountInString(_chineseReg.ReplaceAllString(title, ""))
  86. chineseCount := allCount - enCount
  87. if chineseCount*2+enCount > 80 {
  88. return
  89. }
  90. for _, reg := range _nocharReg {
  91. if reg.MatchString(title) {
  92. return
  93. }
  94. }
  95. ct = _zeroWidthReg.ReplaceAllString(title, "")
  96. if utf8.RuneCountInString(ct) <= 0 {
  97. return
  98. }
  99. ok = true
  100. return
  101. }
  102. func (s *Service) contentStripSize(content string) (count int) {
  103. stripped := strip.StripTags(content)
  104. stripped = html.UnescapeString(stripped)
  105. stripped = strings.Map(func(r rune) rune {
  106. if unicode.IsSpace(r) {
  107. return -1
  108. }
  109. return r
  110. }, stripped)
  111. stripped = strings.Replace(stripped, "\u200B", "", -1)
  112. stripped = strings.Replace(stripped, "\u00a0", "", -1)
  113. // utf16 size
  114. for _, r := range stripped {
  115. count += len(utf16.Encode([]rune{rune(r)}))
  116. }
  117. // 图片计算为一个字
  118. offset := strings.Count(content, "<img") - strings.Count(stripped, "<img")
  119. count += offset
  120. return
  121. }
  122. func (s *Service) checkContent(content string) (ct string, ok bool) {
  123. ct = strings.TrimSpace(content)
  124. ct = _zeroWidthReg.ReplaceAllString(ct, "")
  125. if len(ct) > s.c.Article.MaxContentSize {
  126. return
  127. }
  128. size := s.contentStripSize(ct)
  129. if size < s.c.Article.MinContentLength || size > s.c.Article.MaxContentLength {
  130. return
  131. }
  132. ok = true
  133. return
  134. }
  135. func (s *Service) preArticleCheck(c context.Context, art *model.Article) (err error) {
  136. if !s.allowRepeat(c, art.Author.Mid, art.Title) {
  137. err = ecode.CreativeArticleCanNotRepeat
  138. return
  139. }
  140. if err = s.preMust(c, art); err != nil {
  141. return
  142. }
  143. return
  144. }
  145. func (s *Service) allowCategory(cid int64) (ok bool) {
  146. _, ok = s.categoriesMap[cid]
  147. return
  148. }
  149. func (s *Service) allowReprints(cp int8) (ok bool) {
  150. ok = model.InReprints(cp)
  151. return
  152. }
  153. func (s *Service) allowTID(tid int8) (ok bool) {
  154. ok = model.InTemplateID(tid)
  155. return
  156. }
  157. func (s *Service) allowTag(tags []*model.Tag) (ok bool) {
  158. if (len(tags) > 12) || (len(tags) == 0) {
  159. return
  160. }
  161. for _, tag := range tags {
  162. if _zeroWidthReg.MatchString(tag.Name) {
  163. return
  164. }
  165. if (utf8.RuneCountInString(tag.Name) > 20) || (tag.Name == "") {
  166. return
  167. }
  168. }
  169. return true
  170. }
  171. //allowDynamicIntro 移动端动态推荐语,选填,不能超过233字.
  172. func (s *Service) allowDynamicIntro(dynamicIntro string) (ct string, ok bool) {
  173. ct = strings.TrimSpace(dynamicIntro)
  174. if utf8.RuneCountInString(ct) > 233 {
  175. return
  176. }
  177. ok = true
  178. return
  179. }
  180. func (s *Service) preDraftCheck(c context.Context, art *model.Draft) (err error) {
  181. if art.Title == "" {
  182. art.Title = "无标题"
  183. }
  184. var ok bool
  185. if art.Title, ok = s.checkTitle(art.Title); !ok {
  186. log.Error("s.checkTitle mid(%d) art.Title(%s) title contains illegal char or is empty", art.Author.Mid, art.Title)
  187. err = ecode.CreativeArticleTitleErr
  188. }
  189. return
  190. }
  191. // ParseParam parse article param which type is int.
  192. func (s *Service) ParseParam(c context.Context, categoryStr, reprintStr, tidStr, imageURLs, originImageURLs string) (art *model.ArtParam, err error) {
  193. var (
  194. category int64
  195. tid, reprint int
  196. )
  197. category, err = strconv.ParseInt(categoryStr, 10, 64)
  198. if err != nil || category <= 0 { //文章要求必须传大于0的分类
  199. err = ecode.CreativeArticleCategoryErr
  200. return
  201. }
  202. tid, err = strconv.Atoi(tidStr)
  203. if err != nil || tid < 0 {
  204. err = ecode.CreativeArticleTIDErr
  205. return
  206. }
  207. reprint, err = strconv.Atoi(reprintStr)
  208. if err != nil || reprint < 0 {
  209. err = ecode.CreativeArticleReprintErr
  210. return
  211. }
  212. imgs, oimgs, err := ParseImageURLs(imageURLs, originImageURLs)
  213. if err != nil {
  214. return
  215. }
  216. art = &model.ArtParam{
  217. Category: category,
  218. TemplateID: int32(tid),
  219. Reprint: int32(reprint),
  220. ImageURLs: imgs,
  221. OriginImageURLs: oimgs,
  222. }
  223. return
  224. }
  225. // ParseDraftParam parse draft param which type is int.
  226. func (s *Service) ParseDraftParam(c context.Context, categoryStr, reprintStr, tidStr, imageURLs, originImageURLs string) (art *model.ArtParam, err error) {
  227. var (
  228. category int64
  229. tid, reprint int
  230. )
  231. if categoryStr != "" {
  232. category, err = strconv.ParseInt(categoryStr, 10, 64)
  233. if err != nil || category < 0 {
  234. err = ecode.CreativeArticleCategoryErr
  235. return
  236. }
  237. }
  238. if tidStr != "" {
  239. tid, err = strconv.Atoi(tidStr)
  240. if err != nil || tid < 0 {
  241. err = ecode.CreativeArticleTIDErr
  242. return
  243. }
  244. }
  245. if reprintStr != "" {
  246. reprint, err = strconv.Atoi(reprintStr)
  247. if err != nil || reprint < 0 {
  248. err = ecode.CreativeArticleReprintErr
  249. return
  250. }
  251. }
  252. imgs, oimgs, err := ParseImageURLs(imageURLs, originImageURLs)
  253. if err != nil {
  254. return
  255. }
  256. art = &model.ArtParam{
  257. Category: category,
  258. TemplateID: int32(tid),
  259. Reprint: int32(reprint),
  260. ImageURLs: imgs,
  261. OriginImageURLs: oimgs,
  262. }
  263. return
  264. }
  265. //ParseImageURLs parse img urls to []string.
  266. func ParseImageURLs(imageURLs, originImageURLs string) (imgs, oimgs []string, err error) {
  267. if originImageURLs == "" {
  268. originImageURLs = imageURLs
  269. }
  270. imgs = strings.Split(imageURLs, ",")
  271. oimgs = strings.Split(originImageURLs, ",")
  272. if len(imgs) != len(oimgs) {
  273. err = ecode.CreativeArticleImageURLsErr
  274. }
  275. return
  276. }