add: 统计关系

This commit is contained in:
eson
2020-08-24 19:07:02 +08:00
parent 524daccaf3
commit 4964c0c7af
9 changed files with 465 additions and 187 deletions

View File

@@ -11,11 +11,17 @@ import (
"github.com/gin-gonic/gin"
)
var tagCounter = &sync.Map{} // make(map[string]*tagcounter)
var tagCounter *tagCounterDict // make(map[string]*tagcounter)
type tagCounterDict struct {
Lock sync.Mutex
Data map[string]*tagcounter
CountMethodDict map[string]func(args ...string) (*tagcounter, error)
}
type taginfo struct {
Name string
Value int
Value int64
}
type tagcounter struct {
@@ -26,6 +32,13 @@ type tagcounter struct {
}
func init() {
tagCounter = &tagCounterDict{}
tagCounter.Data = map[string]*tagcounter{}
tagCounter.CountMethodDict = make(map[string]func(args ...string) (*tagcounter, error))
tagCounter.CountMethodDict["count"] = countTagWithCountMethod
tagCounter.CountMethodDict["followers"] = countTagWithFollersMethod
tagCounter.CountMethodDict["gratuity"] = countTagWithGratuityMethod
}
@@ -41,33 +54,21 @@ func CountTag(cxt *gin.Context) {
})
}
func countTagInfo(cxt *gin.Context, ret func(cxt *gin.Context, cw *tagcounter)) {
platform := cxt.Query("platform")
var cw *tagcounter
func countTagWithCountMethod(args ...string) (*tagcounter, error) {
if icw, ok := tagCounter.Load(platform); ok {
cw = icw.(*tagcounter)
if time.Now().Sub(cw.LastTime).Minutes() <= 10 {
ret(cxt, cw)
return
}
}
sql := fmt.Sprintf(SqlTag, platform)
rows, err := StoreStreamer.Query(sql)
sqlcount := fmt.Sprintf(SqlTagWithCount, args[0])
rows, err := StoreStreamer.Query(sqlcount)
if err != nil {
cxt.Error(err)
return
return nil, err
}
cw = &tagcounter{}
cw := &tagcounter{}
cw.CountWord = make(map[string]*taginfo)
cw.Name = platform
cw.LastTime = time.Now()
for rows.Next() {
var stag string
err = rows.Scan(&stag)
err := rows.Scan(&stag)
if err != nil {
log.Println(err)
}
@@ -82,6 +83,130 @@ func countTagInfo(cxt *gin.Context, ret func(cxt *gin.Context, cw *tagcounter))
}
}
return cw, nil
}
func countTagWithGratuityMethod(args ...string) (*tagcounter, error) {
sqlcount := fmt.Sprintf(SqlTagWithAttr, args[0])
rows, err := StoreStreamer.Query(sqlcount)
if err != nil {
return nil, err
}
cw := &tagcounter{}
cw.CountWord = make(map[string]*taginfo)
cw.LastTime = time.Now()
for rows.Next() {
var followers, views, gratuity int64
var tagsbuf []byte
var tags []string
rows.Scan(&tagsbuf, &followers, &views, &gratuity)
json.Unmarshal(tagsbuf, &tags)
if len(tags) > 0 {
for _, t := range tags {
if _, ok := cw.CountWord[t]; ok {
cw.CountWord[t].Value += gratuity
} else {
cw.CountWord[t] = &taginfo{Name: t, Value: gratuity}
}
}
}
}
return cw, nil
}
func countTagWithFollersMethod(args ...string) (*tagcounter, error) {
sqlcount := fmt.Sprintf(SqlTagWithAttr, args[0])
rows, err := StoreStreamer.Query(sqlcount)
if err != nil {
return nil, err
}
cw := &tagcounter{}
cw.CountWord = make(map[string]*taginfo)
cw.LastTime = time.Now()
for rows.Next() {
var followers, views, gratuity int64
var tagsbuf []byte
var tags []string
rows.Scan(&tagsbuf, &followers, &views, &gratuity)
json.Unmarshal(tagsbuf, &tags)
if len(tags) > 0 {
for _, t := range tags {
if _, ok := cw.CountWord[t]; ok {
cw.CountWord[t].Value += followers
} else {
cw.CountWord[t] = &taginfo{Name: t, Value: followers}
}
}
}
}
return cw, nil
}
func countTagInfo(cxt *gin.Context, ret func(cxt *gin.Context, cw *tagcounter)) {
platform := cxt.Query("platform")
key := cxt.Query("countkey")
var cw *tagcounter
// var err error
tagCounter.Lock.Lock()
defer tagCounter.Lock.Unlock()
if icw, ok := tagCounter.Data[platform+key]; ok {
cw = icw
if time.Now().Sub(cw.LastTime).Minutes() <= 10 {
ret(cxt, cw)
return
}
}
// sql := fmt.Sprintf(SqlTag, platform)
if countMehtod, ok := tagCounter.CountMethodDict[key]; ok {
icw, err := countMehtod(platform)
if err != nil {
cxt.Error(err)
return
}
cw = icw
}
// rows, err := StoreStreamer.Query(sql)
// if err != nil {
// cxt.Error(err)
// return
// }
// cw = &tagcounter{}
// cw.CountWord = make(map[string]*taginfo)
// cw.Name = platform
// cw.LastTime = time.Now()
// for rows.Next() {
// var stag string
// err = rows.Scan(&stag)
// if err != nil {
// log.Println(err)
// }
// var tag []string
// json.Unmarshal([]byte(stag), &tag)
// for _, t := range tag {
// if _, ok := cw.CountWord[t]; ok {
// cw.CountWord[t].Value++
// } else {
// cw.CountWord[t] = &taginfo{Name: t, Value: 1}
// }
// }
// }
cw.Name = platform
heap := heap.New(func(a, b interface{}) int {
if a.(*taginfo).Value >= b.(*taginfo).Value {
return 1
@@ -91,7 +216,6 @@ func countTagInfo(cxt *gin.Context, ret func(cxt *gin.Context, cw *tagcounter))
})
var other = &taginfo{Name: "Other...", Value: 0}
for _, v := range cw.CountWord {
heap.Put(v)
}
@@ -115,5 +239,5 @@ func countTagInfo(cxt *gin.Context, ret func(cxt *gin.Context, cw *tagcounter))
cw.PQueue = append(cw.PQueue, other)
ret(cxt, cw)
cw.LastTime = time.Now()
tagCounter.Store(platform, cw)
tagCounter.Data[platform+key] = cw
}

View File

@@ -2,11 +2,8 @@ package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
@@ -82,127 +79,6 @@ type ObjectQuery struct {
UpdateTime *time.Time
}
func Query(cxt *gin.Context, platform string) {
var err error
page, err := strconv.Atoi(cxt.Query("page"))
if err != nil {
cxt.Error(err)
return
}
psize, err := strconv.Atoi(cxt.Query("psize"))
if err != nil {
cxt.Error(err)
return
}
if psize > 5000 {
cxt.Error(fmt.Errorf("page size <= 5000"))
return
}
// filter := cxt.Query("filter")
orderfield := cxt.Query("orderfield")
ordertype := cxt.Query("ordertype")
log.Println(orderfield, ordertype)
start := (page - 1) * psize
// end := start + 200
var orderstr string
switch ordertype {
case "ascend":
orderstr = fmt.Sprintf("ORDER BY %s ASC", orderfield)
case "descend":
orderstr = fmt.Sprintf("ORDER BY %s DESC", orderfield)
default:
orderstr = ""
}
ssql := fmt.Sprintf(SqlQuery, platform, orderstr, strconv.Itoa(start), strconv.Itoa(psize))
rows, err := StoreStreamer.Query(ssql)
if err != nil {
cxt.Error(err)
return
}
var ots []*ObjectQuery
for rows.Next() {
ot := &ObjectQuery{}
var view, gratuity sql.NullInt64
var lstm, letm, utm sql.NullTime
var username, tags, livetitle, liveurl sql.NullString
err = rows.Scan(
&ot.Uid,
&ot.Platform,
&ot.UserId,
&username,
&liveurl,
&tags,
&ot.Followers,
&view,
&gratuity,
&livetitle,
&lstm,
&letm,
&utm,
)
if err != nil {
cxt.Error(err)
return
}
if !lstm.Valid {
ot.LiveStartTime = nil
} else {
ot.LiveStartTime = &lstm.Time
}
if !letm.Valid {
ot.LiveEndTime = nil
} else {
ot.LiveEndTime = &letm.Time
}
if !utm.Valid {
ot.UpdateTime = nil
} else {
ot.UpdateTime = &utm.Time
}
if livetitle.Valid {
ot.LiveTitle = livetitle.String
}
if view.Valid {
ot.Views = view.Int64
}
if gratuity.Valid {
ot.Gratuity = gratuity.Int64
}
if username.Valid {
ot.UserName = username.String
}
if liveurl.Valid {
ot.LiveUrl = liveurl.String
}
if err = json.Unmarshal([]byte(tags.String), &ot.Tags); err != nil {
// log.Println(tags)
}
ots = append(ots, ot)
}
r := &Result{Code: 200}
r.Data = ots
// log.Println(len(ots))
if retdata, err := json.Marshal(r); err != nil {
cxt.Error(err)
} else {
cxt.JSON(r.Code, string(retdata))
}
}
func main() {
engine := gin.New() //r := gin.Default() //使用默认中间件
engine.Use(gin.Logger())

View File

@@ -44,37 +44,52 @@ func estCountTag(t *testing.T) {
}
func estDupTag(t *testing.T) {
querysql := "select uid, tags from streamer where tags is not null"
rows, err := StoreStreamer.Query(querysql)
querysql := `SELECT
ie.tags ,
cl.followers ,
cl.views ,
cl.gratuity
From
(
SELECT
*
FROM
intimate_extractor.streamer
WHERE
platform = "%s"
AND operator = 0
AND latest_log_uid is not NULL ) ie
JOIN intimate_extractor.collect_log cl
WHERE
ie.latest_log_uid = cl.log_uid %s;`
sortstr := "order by cl.followers desc"
rows, err := StoreStreamer.Query(querysql, "twitch", sortstr)
if err != nil {
panic(err)
}
var counttag = make(map[string]int64)
for rows.Next() {
var uid int64
var followers, views, gratuity int64
var tagsbuf []byte
var tags []string
rows.Scan(&uid, &tagsbuf)
rows.Scan(&tagsbuf, &followers, &views, &gratuity)
json.Unmarshal(tagsbuf, &tags)
if len(tags) > 0 {
var newtags []string
m := make(map[string]int)
for _, t := range tags {
if _, ok := m[t]; !ok {
newtags = append(newtags, t)
m[t] = 1
if _, ok := counttag[t]; ok {
counttag[t] += gratuity
} else {
m[t]++
}
}
if len(newtags) != len(tags) {
t.Error(uid, tags)
newtagsbuf, err := json.Marshal(newtags)
if err == nil {
StoreStreamer.Exec("update streamer set tags = ? where uid = ?", newtagsbuf, uid)
} else {
panic(err)
counttag[t] = gratuity
}
}
}
}
}
func TestTagFollowers(t *testing.T) {
}

View File

@@ -2,6 +2,7 @@ package main
import "github.com/gin-gonic/gin"
// OpenrecQuery Openrec的查询API
func OpenrecQuery(cxt *gin.Context) {
Query(cxt, "openrec")
}

133
goserver/query_data.go Normal file
View File

@@ -0,0 +1,133 @@
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"strconv"
"github.com/gin-gonic/gin"
)
// Query 查询表数据的API 方法
func Query(cxt *gin.Context, platform string) {
var err error
page, err := strconv.Atoi(cxt.Query("page"))
if err != nil {
cxt.Error(err)
return
}
psize, err := strconv.Atoi(cxt.Query("psize"))
if err != nil {
cxt.Error(err)
return
}
if psize > 5000 {
cxt.Error(fmt.Errorf("page size <= 5000"))
return
}
// filter := cxt.Query("filter")
orderfield := cxt.Query("orderfield")
ordertype := cxt.Query("ordertype")
log.Println(orderfield, ordertype)
start := (page - 1) * psize
// end := start + 200
var orderstr string
switch ordertype {
case "ascend":
orderstr = fmt.Sprintf("ORDER BY %s ASC", orderfield)
case "descend":
orderstr = fmt.Sprintf("ORDER BY %s DESC", orderfield)
default:
orderstr = ""
}
ssql := fmt.Sprintf(SqlQuery, platform, orderstr, strconv.Itoa(start), strconv.Itoa(psize))
rows, err := StoreStreamer.Query(ssql)
if err != nil {
cxt.Error(err)
return
}
var ots []*ObjectQuery
for rows.Next() {
ot := &ObjectQuery{}
var views, gratuity sql.NullInt64
var lstm, letm, utm sql.NullTime
var username, tags, livetitle, liveurl sql.NullString
err = rows.Scan(
&ot.Uid,
&ot.Platform,
&ot.UserId,
&username,
&liveurl,
&tags,
&ot.Followers,
&views,
&gratuity,
&livetitle,
&lstm,
&letm,
&utm,
)
if err != nil {
cxt.Error(err)
return
}
if !lstm.Valid {
ot.LiveStartTime = nil
} else {
ot.LiveStartTime = &lstm.Time
}
if !letm.Valid {
ot.LiveEndTime = nil
} else {
ot.LiveEndTime = &letm.Time
}
if !utm.Valid {
ot.UpdateTime = nil
} else {
ot.UpdateTime = &utm.Time
}
if livetitle.Valid {
ot.LiveTitle = livetitle.String
}
if views.Valid {
ot.Views = views.Int64
}
if gratuity.Valid {
ot.Gratuity = gratuity.Int64
}
if username.Valid {
ot.UserName = username.String
}
if liveurl.Valid {
ot.LiveUrl = liveurl.String
}
if err = json.Unmarshal([]byte(tags.String), &ot.Tags); err != nil {
// log.Println(tags)
}
ots = append(ots, ot)
}
r := &Result{Code: 200}
r.Data = ots
// log.Println(len(ots))
if retdata, err := json.Marshal(r); err != nil {
cxt.Error(err)
} else {
cxt.JSON(r.Code, string(retdata))
}
}

77
goserver/screenlog.0 Normal file
View File

@@ -0,0 +1,77 @@
2020/08/24 16:31:09 find config: ./config.yaml
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /twitcasting/query --> main.TwitcastingQuery (3 handlers)
[GIN-debug] GET /openrec/query --> main.OpenrecQuery (3 handlers)
[GIN-debug] GET /twitch/query --> main.TwitchQuery (3 handlers)
[GIN-debug] GET /tag/count --> main.CountTag (3 handlers)
[GIN-debug] GET /tag/allcount --> main.CountTag (3 handlers)
[GIN-debug] Listening and serving HTTP on :5500
16:33:16 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: followers descend
[GIN] 2020/08/24 - 16:33:17 | 200 | 397.857448ms | 192.168.16.130 | GET  "/twitcasting/query?page=1&psize=20&orderfield=followers&ordertype=descend"
[GIN] 2020/08/24 - 16:33:17 | 200 | 630.949609ms | 192.168.16.130 | GET  "/tag/count?platform=twitcasting"
16:33:18 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: followers descend
[GIN] 2020/08/24 - 16:33:18 | 200 | 251.472068ms | 192.168.16.130 | GET  "/twitcasting/query?page=1&psize=20&orderfield=followers&ordertype=descend"
16:36:32 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 16:36:32 | 200 | 65.723084ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20"
16:36:32 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 16:36:32 | 200 | 67.82141ms | 192.168.16.103 | GET  "/openrec/query?page=1&psize=20"
16:36:33 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 16:36:33 | 200 | 62.965398ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20"
16:36:37 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 16:36:38 | 200 | 231.238484ms | 192.168.16.130 | GET  "/twitch/query?page=1&psize=20"
16:37:23 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 16:37:23 | 200 | 77.816922ms | 192.168.16.103 | GET  "/openrec/query?page=1&psize=20"
16:37:23 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 16:37:23 | 200 | 60.302602ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20"
16:37:24 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 16:37:24 | 200 | 55.982297ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20"
16:37:28 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: views descend
[GIN] 2020/08/24 - 16:37:28 | 200 | 72.116427ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20&orderfield=views&ordertype=descend"
16:37:32 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: views ascend
[GIN] 2020/08/24 - 16:37:32 | 200 | 60.815908ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20&orderfield=views&ordertype=ascend"
16:37:33 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: undefined
[GIN] 2020/08/24 - 16:37:33 | 200 | 49.692998ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20&orderfield=&ordertype=undefined"
16:37:35 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: undefined
[GIN] 2020/08/24 - 16:37:36 | 200 | 138.6536ms | 192.168.16.130 | GET  "/twitcasting/query?page=1&psize=20&orderfield=&ordertype=undefined"
16:37:42 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: views descend
[GIN] 2020/08/24 - 16:37:43 | 200 | 275.719131ms | 192.168.16.130 | GET  "/twitcasting/query?page=1&psize=20&orderfield=views&ordertype=descend"
16:37:44 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: views ascend
[GIN] 2020/08/24 - 16:37:44 | 200 | 300.06176ms | 192.168.16.130 | GET  "/twitcasting/query?page=1&psize=20&orderfield=views&ordertype=ascend"
16:37:45 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: undefined
[GIN] 2020/08/24 - 16:37:45 | 200 | 226.08364ms | 192.168.16.130 | GET  "/twitcasting/query?page=1&psize=20&orderfield=&ordertype=undefined"
16:37:46 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: views descend
[GIN] 2020/08/24 - 16:37:46 | 200 | 250.248182ms | 192.168.16.130 | GET  "/twitcasting/query?page=1&psize=20&orderfield=views&ordertype=descend"
16:37:54 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 16:37:54 | 200 | 54.738324ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20"
16:37:54 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 16:37:54 | 200 | 52.897355ms | 192.168.16.103 | GET  "/openrec/query?page=1&psize=20"
16:37:54 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 16:37:54 | 200 | 36.935769ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20"
16:37:56 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: views descend
[GIN] 2020/08/24 - 16:37:56 | 200 | 57.870346ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20&orderfield=views&ordertype=descend"
16:37:58 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: views descend
[GIN] 2020/08/24 - 16:37:59 | 200 | 315.446812ms | 192.168.16.130 | GET  "/twitch/query?page=1&psize=20&orderfield=views&ordertype=descend"
16:38:00 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: views ascend
[GIN] 2020/08/24 - 16:38:00 | 200 | 233.687395ms | 192.168.16.130 | GET  "/twitch/query?page=1&psize=20&orderfield=views&ordertype=ascend"
16:38:01 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: undefined
[GIN] 2020/08/24 - 16:38:01 | 200 | 226.263587ms | 192.168.16.130 | GET  "/twitch/query?page=1&psize=20&orderfield=&ordertype=undefined"
16:38:02 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: views descend
[GIN] 2020/08/24 - 16:38:02 | 200 | 219.708245ms | 192.168.16.130 | GET  "/twitch/query?page=1&psize=20&orderfield=views&ordertype=descend"
16:38:09 /home/eson/test/create-react-app-antd/goserver/query_data.go:35: views descend
[GIN] 2020/08/24 - 16:38:09 | 200 | 311.05848ms | 192.168.16.130 | GET  "/twitcasting/query?page=1&psize=20&orderfield=views&ordertype=descend"
[GIN] 2020/08/24 - 16:40:23 | 200 | 114.314µs | 192.168.16.130 | GET  "/tag/count?platform=twitcasting"
17:50:21 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 17:50:21 | 200 | 48.105852ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20"
17:50:21 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 17:50:21 | 200 | 29.279913ms | 192.168.16.103 | GET  "/openrec/query?page=1&psize=20"
17:50:36 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 17:50:36 | 200 | 28.115392ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20"
17:50:37 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 17:50:37 | 200 | 40.507783ms | 192.168.16.103 | GET  "/openrec/query?page=1&psize=20"
17:53:07 /home/eson/test/create-react-app-antd/goserver/query_data.go:35:
[GIN] 2020/08/24 - 17:53:07 | 200 | 22.445236ms | 192.168.16.130 | GET  "/openrec/query?page=1&psize=20"
[GIN] 2020/08/24 - 17:53:08 | 200 | 42.924993ms | 192.168.16.130 | GET  "/tag/count?platform=openrec&countkey=count"
^[[A^[[A^[[A^[[B^[[B^[[B^C

View File

@@ -1,7 +1,7 @@
package main
// SqlTag 获取 tag
var SqlTag string = `SELECT tags FROM intimate_extractor.streamer
// SqlTagWithCount 获取 tag
var SqlTagWithCount string = `SELECT tags FROM intimate_extractor.streamer
WHERE platform = "%s" AND tags IS NOT NULL`
//SqlQuery 获取 网站数据
@@ -32,3 +32,25 @@ WHERE
JOIN intimate_extractor.collect_log cl
WHERE
ie.latest_log_uid = cl.log_uid %s limit %s,%s;`
var SqlTagWithAttr = `SELECT
ie.tags ,
cl.followers ,
cl.views ,
cl.gratuity
From
(
SELECT
*
FROM
intimate_extractor.streamer
WHERE
platform = "%s"
AND operator = 0
AND latest_log_uid is not NULL ) ie
JOIN intimate_extractor.collect_log cl
WHERE
ie.latest_log_uid = cl.log_uid;`
// sortstr := "order by cl.followers desc"