keyword.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. package dao
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "time"
  7. "go-common/app/service/main/antispam/util"
  8. "go-common/library/database/sql"
  9. "go-common/library/log"
  10. )
  11. const (
  12. columnKeywords = "id, area, content, regexp_name, tag, hit_counts, state, origin_content, ctime, mtime"
  13. selectKeywordCountsSQL = `SELECT COUNT(1) FROM keywords %s`
  14. selectKeywordsByCondSQL = `SELECT ` + columnKeywords + ` FROM keywords %s`
  15. selectKeywordByIDsSQL = `SELECT ` + columnKeywords + ` FROM keywords WHERE id IN (%s)`
  16. selectKeywordNeedRecycledSQL = `SELECT ` + columnKeywords + ` FROM keywords FORCE INDEX(ix_ctime) WHERE state = %s AND hit_counts < %s AND tag IN(%s) AND ctime BETWEEN '%s' AND '%s' LIMIT %d`
  17. selectKeywordByOffsetLimitSQL = `SELECT ` + columnKeywords + ` FROM keywords WHERE area = %s AND id > %s AND tag IN(%s) AND state = 0 LIMIT %s`
  18. selectKeywordByAreaAndContentsSQL = `SELECT ` + columnKeywords + ` FROM keywords WHERE area = %s AND content IN(%s)`
  19. insertKeywordSQL = `INSERT INTO keywords(area, content, regexp_name, tag, hit_counts, origin_content) VALUES(?, ?, ?, ?, ?, ?)`
  20. updateKeywordSQL = `UPDATE keywords SET content = ?, regexp_name = ?, tag = ?, hit_counts = ?, state = ?, origin_content = ?, ctime = ?, mtime = ? WHERE id = ?`
  21. deleteKeywordByIDsSQL = `UPDATE keywords SET state = 1, hit_counts = 0, mtime = ? WHERE id IN (%s)`
  22. )
  23. const (
  24. // KeywordTagDefaultLimit .
  25. KeywordTagDefaultLimit int = iota
  26. // KeywordTagRestrictLimit .
  27. KeywordTagRestrictLimit
  28. // KeywordTagWhite .
  29. KeywordTagWhite
  30. // KeywordTagBlack .
  31. KeywordTagBlack
  32. )
  33. // KeywordDaoImpl .
  34. type KeywordDaoImpl struct{}
  35. // Keyword .
  36. type Keyword struct {
  37. ID int64 `db:"id"`
  38. Area int `db:"area"`
  39. Tag int `db:"tag"`
  40. State int `db:"state"`
  41. HitCounts int64 `db:"hit_counts"`
  42. RegexpName string `db:"regexp_name"`
  43. Content string `db:"content"`
  44. OriginContent string `db:"origin_content"`
  45. CTime time.Time `db:"ctime"`
  46. MTime time.Time `db:"mtime"`
  47. }
  48. // NewKeywordDao .
  49. func NewKeywordDao() *KeywordDaoImpl {
  50. return &KeywordDaoImpl{}
  51. }
  52. // GetRubbish .
  53. func (*KeywordDaoImpl) GetRubbish(ctx context.Context, cond *Condition) (keywords []*Keyword, err error) {
  54. querySQL := fmt.Sprintf(selectKeywordNeedRecycledSQL,
  55. cond.State,
  56. cond.HitCounts,
  57. util.StrSliToSQLVarchars(cond.Tags),
  58. cond.StartTime,
  59. cond.EndTime,
  60. cond.PerPage,
  61. )
  62. log.Info("get rubbish keywords rawSQL: %s", querySQL)
  63. ks, err := queryKeywords(ctx, db, querySQL)
  64. if err != nil {
  65. return nil, err
  66. }
  67. return ks, nil
  68. }
  69. // GetByOffsetLimit .
  70. func (*KeywordDaoImpl) GetByOffsetLimit(ctx context.Context, cond *Condition) (keywords []*Keyword, err error) {
  71. return queryKeywords(ctx, db, fmt.Sprintf(selectKeywordByOffsetLimitSQL, cond.Area,
  72. cond.Offset, util.StrSliToSQLVarchars(cond.Tags), cond.Limit))
  73. }
  74. // GetByCond .
  75. func (*KeywordDaoImpl) GetByCond(ctx context.Context, cond *Condition) (keywords []*Keyword, totalCounts int64, err error) {
  76. sqlConds := make([]string, 0)
  77. if cond.Search != "" {
  78. sqlConds = append(sqlConds, fmt.Sprintf("content LIKE '%%%s%%'", cond.Search))
  79. }
  80. if len(cond.Contents) > 0 {
  81. sqlConds = append(sqlConds, fmt.Sprintf("content IN (%s)", util.StrSliToSQLVarchars(cond.Tags)))
  82. }
  83. if cond.LastModifiedTime != "" {
  84. sqlConds = append(sqlConds, fmt.Sprintf("mtime >= '%s'", cond.LastModifiedTime))
  85. cond.OrderBy = ""
  86. }
  87. if cond.StartTime != "" || cond.EndTime != "" {
  88. if cond.StartTime != "" && cond.EndTime != "" {
  89. sqlConds = append(sqlConds, fmt.Sprintf("ctime BETWEEN '%s' AND '%s'", cond.StartTime, cond.EndTime))
  90. } else if cond.StartTime != "" {
  91. sqlConds = append(sqlConds, fmt.Sprintf("ctime >= '%s'", cond.StartTime))
  92. } else {
  93. sqlConds = append(sqlConds, fmt.Sprintf("ctime <= '%s'", cond.EndTime))
  94. }
  95. }
  96. if cond.State != "" {
  97. sqlConds = append(sqlConds, fmt.Sprintf("state = %s", cond.State))
  98. }
  99. if cond.Area != "" {
  100. sqlConds = append(sqlConds, fmt.Sprintf("area = %s", cond.Area))
  101. }
  102. if len(cond.Tags) > 0 {
  103. sqlConds = append(sqlConds, fmt.Sprintf("tag IN(%s)", util.StrSliToSQLVarchars(cond.Tags)))
  104. }
  105. var optionSQL string
  106. if len(sqlConds) > 0 {
  107. optionSQL = fmt.Sprintf("WHERE %s", strings.Join(sqlConds, " AND "))
  108. }
  109. var limitSQL string
  110. if cond.Pagination != nil {
  111. queryCountsSQL := fmt.Sprintf(selectKeywordCountsSQL, optionSQL)
  112. log.Info("queryCounts sql: %s", queryCountsSQL)
  113. totalCounts, err = GetTotalCounts(ctx, db, queryCountsSQL)
  114. if err != nil {
  115. return nil, 0, err
  116. }
  117. offset, limit := cond.OffsetLimit(totalCounts)
  118. if limit == 0 {
  119. return nil, 0, ErrResourceNotExist
  120. }
  121. limitSQL = fmt.Sprintf("LIMIT %d, %d", offset, limit)
  122. }
  123. if cond.OrderBy != "" {
  124. optionSQL = fmt.Sprintf("%s ORDER BY %s %s", optionSQL, cond.OrderBy, cond.Order)
  125. }
  126. if limitSQL != "" {
  127. optionSQL = fmt.Sprintf("%s %s", optionSQL, limitSQL)
  128. }
  129. querySQL := fmt.Sprintf(selectKeywordsByCondSQL, optionSQL)
  130. log.Info("OptionSQL(%s), GetByCondSQL(%s)", optionSQL, querySQL)
  131. keywords, err = queryKeywords(ctx, db, querySQL)
  132. if err != nil {
  133. return nil, 0, err
  134. }
  135. if totalCounts == 0 {
  136. totalCounts = int64(len(keywords))
  137. }
  138. return keywords, totalCounts, nil
  139. }
  140. // GetByAreaAndContents .
  141. func (*KeywordDaoImpl) GetByAreaAndContents(ctx context.Context,
  142. cond *Condition) ([]*Keyword, error) {
  143. querySQL := fmt.Sprintf(selectKeywordByAreaAndContentsSQL,
  144. cond.Area, util.StrSliToSQLVarchars(cond.Contents))
  145. ks, err := queryKeywords(ctx, db, querySQL)
  146. if err != nil {
  147. return nil, err
  148. }
  149. res := make([]*Keyword, len(cond.Contents))
  150. for i, c := range cond.Contents {
  151. for _, k := range ks {
  152. if strings.EqualFold(k.Content, c) {
  153. res[i] = k
  154. }
  155. }
  156. }
  157. return res, nil
  158. }
  159. // GetByAreaAndContent .
  160. func (kdi *KeywordDaoImpl) GetByAreaAndContent(ctx context.Context,
  161. cond *Condition) (*Keyword, error) {
  162. ks, err := kdi.GetByAreaAndContents(ctx, cond)
  163. if err != nil {
  164. return nil, err
  165. }
  166. if ks[0] == nil {
  167. return nil, ErrResourceNotExist
  168. }
  169. return ks[0], nil
  170. }
  171. // Update .
  172. func (kdi *KeywordDaoImpl) Update(ctx context.Context,
  173. k *Keyword) (*Keyword, error) {
  174. if err := updateKeyword(ctx, db, k); err != nil {
  175. return nil, err
  176. }
  177. return kdi.GetByID(ctx, k.ID)
  178. }
  179. // Insert .
  180. func (kdi *KeywordDaoImpl) Insert(ctx context.Context, k *Keyword) (*Keyword, error) {
  181. if err := insertKeyword(ctx, db, k); err != nil {
  182. return nil, err
  183. }
  184. return kdi.GetByID(ctx, k.ID)
  185. }
  186. // DeleteByIDs .
  187. func (kdi *KeywordDaoImpl) DeleteByIDs(ctx context.Context, ids []int64) ([]*Keyword, error) {
  188. if err := deleteKeywordByIDs(ctx, db, ids); err != nil {
  189. return nil, err
  190. }
  191. return kdi.GetByIDs(ctx, ids)
  192. }
  193. // GetByID .
  194. func (kdi *KeywordDaoImpl) GetByID(ctx context.Context, id int64) (*Keyword, error) {
  195. ks, err := kdi.GetByIDs(ctx, []int64{id})
  196. if err != nil {
  197. return nil, err
  198. }
  199. if ks[0] == nil {
  200. return nil, ErrResourceNotExist
  201. }
  202. return ks[0], nil
  203. }
  204. // GetByIDs .
  205. func (*KeywordDaoImpl) GetByIDs(ctx context.Context, ids []int64) ([]*Keyword, error) {
  206. ks, err := queryKeywords(ctx, db,
  207. fmt.Sprintf(selectKeywordByIDsSQL, util.IntSliToSQLVarchars(ids)))
  208. if err != nil {
  209. return nil, err
  210. }
  211. res := make([]*Keyword, len(ids))
  212. for i, id := range ids {
  213. for _, k := range ks {
  214. if k.ID == id {
  215. res[i] = k
  216. }
  217. }
  218. }
  219. return res, nil
  220. }
  221. func insertKeyword(ctx context.Context, executer Executer, k *Keyword) error {
  222. defaultHitCount := 1
  223. res, err := executer.Exec(ctx,
  224. insertKeywordSQL,
  225. k.Area,
  226. k.Content,
  227. k.RegexpName,
  228. k.Tag,
  229. defaultHitCount,
  230. k.OriginContent,
  231. )
  232. if err != nil {
  233. log.Error("%v", err)
  234. return err
  235. }
  236. lastID, err := res.LastInsertId()
  237. if err != nil {
  238. log.Error("%v", err)
  239. return err
  240. }
  241. k.ID = lastID
  242. return nil
  243. }
  244. func updateKeyword(ctx context.Context, executer Executer, k *Keyword) error {
  245. _, err := executer.Exec(ctx,
  246. updateKeywordSQL,
  247. k.Content,
  248. k.RegexpName,
  249. k.Tag,
  250. k.HitCounts,
  251. k.State,
  252. k.OriginContent,
  253. k.CTime,
  254. time.Now(),
  255. k.ID,
  256. )
  257. if err != nil {
  258. log.Error("%v", err)
  259. return err
  260. }
  261. return nil
  262. }
  263. func deleteKeywordByIDs(ctx context.Context, executer Executer, ids []int64) error {
  264. rawSQL := fmt.Sprintf(deleteKeywordByIDsSQL, util.IntSliToSQLVarchars(ids))
  265. if _, err := executer.Exec(ctx, rawSQL, time.Now()); err != nil {
  266. log.Error("Error: %v, RawSQL: %s", err, rawSQL)
  267. return err
  268. }
  269. return nil
  270. }
  271. func queryKeywords(ctx context.Context, q Querier, rawSQL string) ([]*Keyword, error) {
  272. // NOTICE: this MotherFucker Query() will never return `ErrNoRows` when there is no rows found !
  273. rows, err := q.Query(ctx, rawSQL)
  274. if err == sql.ErrNoRows {
  275. return nil, ErrResourceNotExist
  276. } else if err != nil {
  277. log.Error("ctx: %+v, Error: %v, RawSQL: %s", ctx, err, rawSQL)
  278. return nil, err
  279. }
  280. defer rows.Close()
  281. log.Info("Query sql: %q", rawSQL)
  282. ks, err := mapRowToKeywords(rows)
  283. if err != nil {
  284. return nil, err
  285. }
  286. if len(ks) == 0 {
  287. return nil, ErrResourceNotExist
  288. }
  289. return ks, nil
  290. }
  291. func mapRowToKeywords(rows *sql.Rows) (ks []*Keyword, err error) {
  292. for rows.Next() {
  293. k := Keyword{}
  294. err = rows.Scan(
  295. &k.ID,
  296. &k.Area,
  297. &k.Content,
  298. &k.RegexpName,
  299. &k.Tag,
  300. &k.HitCounts,
  301. &k.State,
  302. &k.OriginContent,
  303. &k.CTime,
  304. &k.MTime,
  305. )
  306. if err != nil {
  307. log.Error("%v", err)
  308. return nil, err
  309. }
  310. ks = append(ks, &k)
  311. }
  312. if err = rows.Err(); err != nil {
  313. log.Error("%v", err)
  314. return nil, err
  315. }
  316. return ks, nil
  317. }