Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"authority.go",
"cache.go",
"parameter.go",
"score.go",
"service.go",
"statistics.go",
"transfer.go",
],
importpath = "go-common/app/admin/main/up-rating/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/up-rating/conf:go_default_library",
"//app/admin/main/up-rating/dao:go_default_library",
"//app/admin/main/up-rating/dao/global:go_default_library",
"//app/admin/main/up-rating/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/xstr:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,13 @@
package service
import "context"
// AddAuthority .
func (s *Service) AddAuthority(c context.Context, mids []int64) (int64, error) {
return s.dao.AddAuthority(c, mids)
}
// RemoveAuthority .
func (s *Service) RemoveAuthority(c context.Context, mids []int64) (int64, error) {
return s.dao.RmAuthority(c, mids)
}

View File

@@ -0,0 +1,41 @@
package service
import (
"time"
)
// Cache simple cache
type Cache struct {
d int64 // duration seconds
mc map[string]interface{} // map cache
snap time.Time
}
// NewCache new cache
func NewCache(d int64) *Cache {
c := &Cache{
d: d,
mc: make(map[string]interface{}),
snap: time.Now(),
}
return c
}
// Get ...
func (c *Cache) Get(key string) (val interface{}) {
c.check()
return c.mc[key]
}
// Set ...
func (c *Cache) Set(key string, val interface{}) {
c.check()
c.mc[key] = val
}
func (c *Cache) check() {
if int64(time.Since(c.snap).Seconds()) > c.d {
c.mc = make(map[string]interface{})
c.snap = time.Now()
}
}

View File

@@ -0,0 +1,59 @@
package service
import (
"context"
"fmt"
"go-common/app/admin/main/up-rating/model"
)
// InsertParameter insert parameter
func (s *Service) InsertParameter(c context.Context, name, remark string, value int) (err error) {
_, err = s.dao.InsertParameter(c, fmt.Sprintf("('%s', %d, '%s')", name, value, remark))
return
}
func (s *Service) getTypeScore(c context.Context, ctype int64) (score int64, err error) {
params, err := s.getAllParameter(c)
if err != nil {
return
}
switch ctype {
case 0:
score = params.WCSR + params.HR + params.WISR
case 1:
score = params.WCSR
case 2:
score = params.WISR
case 3:
score = params.HR
}
return
}
func (s *Service) getAllParameter(c context.Context) (rp *model.RatingParameter, err error) {
parameters, err := s.dao.GetAllParameter(c)
if err != nil {
return
}
rp = &model.RatingParameter{
WDP: parameters["wdp"],
WDC: parameters["wdc"],
WDV: parameters["wdv"],
WMDV: parameters["wmdv"],
WCS: parameters["wcs"],
WCSR: parameters["wcsr"],
WMAAFans: parameters["wmaafans"],
WMAHFans: parameters["wmahfans"],
WIS: parameters["wis"],
WISR: parameters["wisr"],
HBASE: parameters["hbase"],
HR: parameters["hr"],
HV: parameters["hv"],
HVM: parameters["hvm"],
HL: parameters["hl"],
HLM: parameters["hlm"],
}
return
}

View File

@@ -0,0 +1,252 @@
package service
import (
"context"
"fmt"
"sort"
"time"
"go-common/app/admin/main/up-rating/dao/global"
"go-common/app/admin/main/up-rating/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/xstr"
)
// RatingList returns rating info list
func (s *Service) RatingList(c context.Context, arg *model.RatingListArg, date time.Time) (res []*model.RatingInfo, total int64, err error) {
var (
cdate = getStartMonthlyDate(date)
cdateStr = cDateStr(cdate)
mon = int(cdate.Month())
)
res = make([]*model.RatingInfo, 0)
q := scoreListQuery(arg)
total = 10000
// if total, err = s.dao.Total(c, mon, cdateStr, q); err != nil {
// log.Error("s.dao.Total error(%v)", err)
// return
// }
// if total == 0 {
// return
// }
// scores
q += fmt.Sprintf(" ORDER BY id ASC LIMIT %d, %d", arg.From, arg.Limit)
if res, err = s.dao.ScoreList(c, mon, cdateStr, q); err != nil {
log.Error("s.dao.ScoreList error(%v)", err)
return
}
if len(res) == 0 {
return
}
var (
m = make(map[int64]*model.RatingInfo, len(res))
mids = make([]int64, 0, len(res))
)
for _, v := range res {
mids = append(mids, v.Mid)
m[v.Mid] = v
}
// fans and avs
levelInfos, err := s.dao.LevelList(c, mon, cdateStr, mids)
if err != nil {
log.Error("s.dao.LevelList error(%v)", err)
return
}
for _, v := range levelInfos {
score := m[v.Mid]
score.TotalAvs = v.TotalAvs
score.TotalFans = v.TotalFans
}
// nicknames
r, err := global.Names(c, mids)
if err != nil {
log.Error("global.Names error(%v)", err)
return
}
for _, v := range res {
v.NickName = r[v.Mid]
v.Date = v.ScoreDate.Format("2006-01")
}
return
}
func scoreListQuery(arg *model.RatingListArg) (where string) {
if arg.Mid > 0 {
where += fmt.Sprintf(" AND mid=%d", arg.Mid)
}
if arg.ScoreMin > 0 {
where += fmt.Sprintf(" AND %s>=%d", scoreField(arg.ScoreType), arg.ScoreMin)
}
if arg.ScoreMax > 0 {
where += fmt.Sprintf(" AND %s<%d", scoreField(arg.ScoreType), arg.ScoreMax)
}
if len(arg.Tags) > 0 {
// if len(arg.Tags) > 1 {
// s := rand.NewSource(time.Now().Unix())
// r := rand.New(s) // initialize local pseudorandom generator
// i := r.Intn(len(arg.Tags))
// where += fmt.Sprintf(" AND tag_id=%d", arg.Tags[i])
// } else {
// where += fmt.Sprintf(" AND tag_id=%d", arg.Tags[0])
// }
where += fmt.Sprintf(" AND tag_id IN (%s)", xstr.JoinInts(arg.Tags))
}
where += " AND is_deleted=0"
return
}
// ScoreCurrent returns current rating info
func (s *Service) ScoreCurrent(c context.Context, mid int64) (res *model.ScoreCurrentResp, err error) {
current, err := s.lastestScore(c, mid)
if err != nil || current == nil {
return
}
res = &model.ScoreCurrentResp{
Date: current.ScoreDate.Unix(),
Credit: &model.ScoreCurrent{Current: current.CreditScore, Diff: current.CreditScore},
Influence: &model.ScoreCurrent{Current: current.InfluenceScore, Diff: current.InfluenceScore},
Creativity: &model.ScoreCurrent{Current: current.CreativityScore, Diff: current.CreativityScore},
}
prev, err := s.upScore(c, mid, prevComputation(current.ScoreDate))
if err != nil {
return
}
if prev != nil {
res.Credit.Diff = current.CreditScore - prev.CreditScore
res.Influence.Diff = current.InfluenceScore - prev.InfluenceScore
res.Creativity.Diff = current.CreativityScore - prev.CreativityScore
}
return
}
func (s *Service) upScoreHistory(c context.Context, mid int64, queryAll bool, count int) ([]*model.RatingInfo, error) {
if queryAll {
return s.upPastScoresAll(c, mid)
}
return s.upPastScores(c, mid, count)
}
// ScoreHistory returns score history
func (s *Service) ScoreHistory(c context.Context, types []model.ScoreType, mid int64, queryAll bool, limit int) (res []*model.UpScoreHistory, err error) {
res = make([]*model.UpScoreHistory, 0)
history, err := s.upScoreHistory(c, mid, queryAll, limit)
if err != nil || len(history) <= 0 {
return
}
sort.Slice(history, func(i, j int) bool {
return history[i].ScoreDate.Before(history[j].ScoreDate)
})
var (
dates = make([]int64, 0, len(history))
m = map[model.ScoreType][]int64{
model.Credit: make([]int64, 0, len(history)),
model.Creativity: make([]int64, 0, len(history)),
model.Influence: make([]int64, 0, len(history)),
}
)
for _, v := range history {
dates = append(dates, v.ScoreDate.Unix())
m[model.Creativity] = append(m[model.Creativity], v.CreativityScore)
m[model.Credit] = append(m[model.Credit], v.CreditScore)
m[model.Influence] = append(m[model.Influence], v.InfluenceScore)
}
for _, t := range types {
res = append(res, &model.UpScoreHistory{
ScoreType: t,
Date: dates,
Score: m[t],
})
}
return
}
// ExportScores exports scores
func (s *Service) ExportScores(ctx context.Context, arg *model.RatingListArg, date time.Time) (res []byte, err error) {
ratings, _, err := s.RatingList(ctx, arg, date)
if err != nil {
log.Error("s.RatingList error(%v)", err)
return
}
data := formatScores(ratings)
res, err = formatCSV(data)
if err != nil {
log.Error("up-rating FormatCSV error(%v)", err)
}
return
}
func (s *Service) upPastScoresAll(c context.Context, mid int64) (res []*model.RatingInfo, err error) {
res = make([]*model.RatingInfo, 0)
for f := 1; f <= 12; f++ {
var list []*model.RatingInfo
if list, err = s.dao.UpScores(c, f, mid); err != nil {
log.Error("s.dao.UpScores error(%v)", err)
return
}
res = append(res, list...)
}
return
}
func (s *Service) upPastScores(c context.Context, mid int64, count int) (res []*model.RatingInfo, err error) {
res = make([]*model.RatingInfo, 0)
var lastScore *model.RatingInfo
if lastScore, err = s.lastestScore(c, mid); err != nil || lastScore == nil {
return
}
res = append(res, lastScore)
for f := 1; f < count; f++ {
var v *model.RatingInfo
if v, err = s.upScore(c, mid, lastScore.ScoreDate.AddDate(0, -f, 0)); err != nil {
return
}
if v != nil {
res = append(res, v)
}
}
return
}
func (s *Service) lastestScore(c context.Context, mid int64) (score *model.RatingInfo, err error) {
cdate := prevComputation(time.Now())
var b bool
if b, err = s.taskFinished(c, cdate); err != nil {
return
}
if !b {
cdate = prevComputation(cdate)
if b, err = s.taskFinished(c, cdate); err != nil {
return
}
}
if !b {
log.Error("s.latestScore cdate(%s) no data available", cdate)
err = ecode.ServerErr
return
}
return s.upScore(c, mid, cdate)
}
func (s *Service) taskFinished(c context.Context, cdate time.Time) (bool, error) {
str := cDateStr(cdate)
status, err := s.dao.TaskStatus(c, str)
if err != nil {
log.Error("s.dao.TaskStasus date(%s) error(%v)", str, err)
return false, err
}
return status == 1, nil
}
// upScore wraps dao.upScore
func (s *Service) upScore(c context.Context, mid int64, t time.Time) (*model.RatingInfo, error) {
score, err := s.dao.UpScore(c, int(t.Month()), mid, cDateStr(t))
if err != nil {
log.Error("s.dao.UpScore mid(%d) date(%s) error(%v)", mid, t, err)
return nil, err
}
return score, nil
}

View File

@@ -0,0 +1,39 @@
package service
import (
"context"
"go-common/app/admin/main/up-rating/conf"
"go-common/app/admin/main/up-rating/dao"
"go-common/app/admin/main/up-rating/dao/global"
)
// Service struct
type Service struct {
conf *conf.Config
dao *dao.Dao
cache *Cache
}
// New fn
func New(c *conf.Config) (s *Service) {
global.Init(c)
s = &Service{
conf: c,
dao: dao.New(c),
cache: NewCache(60),
}
return s
}
// Ping fn
func (s *Service) Ping(c context.Context) (err error) {
return nil
}
// Close dao
func (s *Service) Close() {
if s.dao != nil {
s.dao.Close()
}
}

View File

@@ -0,0 +1,289 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/admin/main/up-rating/dao/global"
"go-common/app/admin/main/up-rating/model"
"go-common/library/log"
"go-common/library/xstr"
)
var (
_layout = "2006-01-02"
_segment = 10
)
// StatisGraph get statistics graph data
func (s *Service) StatisGraph(c context.Context, ctype int64, tagID string, Compare int) (data interface{}, err error) {
totalScore, err := s.getTypeScore(c, ctype)
if err != nil {
log.Error("s.getTypeScore error(%v)", err)
return
}
nowStatis, now, err := s.GetLastRatingStatis(c, ctype, tagID)
if err != nil {
log.Error("s.GetRatingStatis error(%v)", err)
return
}
last := now.AddDate(0, -1*Compare, 0)
lastStatis, err := s.GetRatingStatis(c, ctype, tagID, getStartMonthlyDate(last))
if err != nil {
log.Error("s.GetRatingStatis error(%v)", err)
return
}
data = map[string]interface{}{
"xAxis": getSections(int(totalScore)),
"this_month": statisSections(nowStatis, int(totalScore)),
"compare": statisSections(lastStatis, int(totalScore)),
}
return
}
// StatisList get statistics list
func (s *Service) StatisList(c context.Context, ctype int64, tagID string, Compare int) (list []*model.RatingStatis, err error) {
list = make([]*model.RatingStatis, 0)
totalScore, err := s.getTypeScore(c, ctype)
if err != nil {
log.Error("s.getTypeScore error(%v)", err)
return
}
nowStatis, now, err := s.GetLastRatingStatis(c, ctype, tagID)
if err != nil {
log.Error("s.GetRatingStatis error(%v)", err)
return
}
last := now.AddDate(0, -1*Compare, 0)
lastStatis, err := s.GetRatingStatis(c, ctype, tagID, getStartMonthlyDate(last))
if err != nil {
log.Error("s.GetRatingStatis error(%v)", err)
return
}
list = statisProportion(nowStatis, lastStatis, getSections(int(totalScore)), int(totalScore))
return
}
// StatisExport export statis
func (s *Service) StatisExport(c context.Context, ctype int64, tagID string, Compare int) (res []byte, err error) {
list, err := s.StatisList(c, ctype, tagID, Compare)
if err != nil {
log.Error("s.StatisList error(%v)", err)
return
}
res, err = formatCSV(formatStatis(list, ctype))
if err != nil {
log.Error("StatisExport formatCSV error(%v)", err)
}
return
}
func statisProportion(now, last []*model.RatingStatis, sections []string, totalScore int) (list []*model.RatingStatis) {
var totalUps, lastTotalUps int64
list = make([]*model.RatingStatis, _segment)
for i := 0; i < _segment; i++ {
list[i] = &model.RatingStatis{
Tips: sections[i],
}
}
offset := totalScore / _segment
for _, s := range now {
totalUps += s.Ups
idx := int(s.Section) * 10 / offset
if idx >= _segment {
idx--
}
list[idx].Ups += s.Ups
list[idx].Score += s.Score
list[idx].CreativityScore += s.CreativityScore
list[idx].InfluenceScore += s.InfluenceScore
list[idx].CreditScore += s.CreditScore
list[idx].Fans += s.Fans
list[idx].Avs += s.Avs
list[idx].Coin += s.Coin
list[idx].Play += s.Play
}
for _, s := range last {
lastTotalUps += s.Ups
idx := int(s.Section) * 10 / offset
if idx >= _segment {
idx--
}
list[idx].Compare += s.Ups
}
for i := 0; i < len(list); i++ {
if totalUps > 0 {
list[i].Proportion = fmt.Sprintf("%.02f", float64(list[i].Ups*100)/float64(totalUps))
}
if lastTotalUps > 0 {
list[i].ComparePropor = fmt.Sprintf("%.02f", float64(list[i].Compare*100)/float64(lastTotalUps))
}
if list[i].Ups > 0 {
list[i].Score /= list[i].Ups
list[i].CreativityScore /= list[i].Ups
list[i].InfluenceScore /= list[i].Ups
list[i].CreditScore /= list[i].Ups
list[i].Fans /= list[i].Ups
list[i].Avs /= list[i].Ups
list[i].Coin /= list[i].Ups
list[i].Play /= list[i].Ups
}
}
return
}
func getSections(score int) (sections []string) {
sections = make([]string, _segment)
offset := score / _segment
for i := 0; i < len(sections); i++ {
sections[i] = fmt.Sprintf("%d-%d", i*offset, (i+1)*offset)
}
return sections
}
func statisSections(statis []*model.RatingStatis, totalScore int) (ups []int64) {
ups = make([]int64, _segment)
offset := totalScore / _segment
for _, s := range statis {
idx := int(s.Section) * 10 / offset
if idx >= len(ups) {
idx--
}
ups[idx] += s.Ups
}
return
}
// GetLastRatingStatis get last rating statis
func (s *Service) GetLastRatingStatis(c context.Context, ctype int64, tagID string) (statis []*model.RatingStatis, date time.Time, err error) {
statis = make([]*model.RatingStatis, 0)
times := 0
date = getStartMonthlyDate(time.Now()).AddDate(0, -1, 0)
for times < 2 {
statis, err = s.GetRatingStatis(c, ctype, tagID, date)
if err != nil {
return
}
if len(statis) > 0 {
break
}
times++
date = date.AddDate(0, -1, 0)
}
if times == 2 && len(statis) == 0 {
err = fmt.Errorf("get last statis error")
return
}
return
}
// GetRatingStatis get rating statis
func (s *Service) GetRatingStatis(c context.Context, ctype int64, tagID string, date time.Time) (statis []*model.RatingStatis, err error) {
return s.dao.GetRatingStatis(c, ctype, date.Format(_layout), tagID)
}
func getStartMonthlyDate(date time.Time) time.Time {
return time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, time.Local)
}
// GetTrendAsc get trend asc
func (s *Service) GetTrendAsc(c context.Context, ctype string, tags []int64, date time.Time, frl, fru int, mid int64, offset, limit int) (total int, ts []*model.Trend, err error) {
q := query(ctype, tags, frl, mid)
dsr := getStartMonthlyDate(date).Format(_layout)
total, err = s.dao.AscTrendCount(c, dsr, q)
if err != nil {
return
}
q += fmt.Sprintf(" ORDER BY %s_diff DESC,id LIMIT %d,%d", ctype, offset, limit)
ts, err = s.dao.GetTrendAsc(c, ctype, dsr, q)
if err != nil {
return
}
err = fillUpNames(c, ts)
return
}
//GetTrendDesc get trend desc
func (s *Service) GetTrendDesc(c context.Context, ctype string, tags []int64, date time.Time, frl, fru int, mid int64, offset, limit int) (total int, ts []*model.Trend, err error) {
q := query(ctype, tags, frl, mid)
dsr := getStartMonthlyDate(date).Format(_layout)
total, err = s.dao.DescTrendCount(c, dsr, q)
if err != nil {
return
}
q += fmt.Sprintf(" ORDER BY %s_diff,id LIMIT %d,%d", ctype, offset, limit)
ts, err = s.dao.GetTrendDesc(c, ctype, dsr, q)
if err != nil {
return
}
err = fillUpNames(c, ts)
return
}
func fillUpNames(c context.Context, ts []*model.Trend) (err error) {
var mids []int64
for _, trend := range ts {
mids = append(mids, trend.MID)
}
if len(mids) == 0 {
return
}
ns, err := global.Names(c, mids)
if err != nil {
return
}
for _, trend := range ts {
trend.Nickname = ns[trend.MID]
}
return
}
func getSection(ctype string, frl int) (section int, typ int) {
if ctype == "magnetic" {
typ = 0
interval := 600 / 10
section = frl / interval
}
if ctype == "creativity" {
typ = 1
interval := 200 / 10
section = frl / interval
}
if ctype == "influence" {
typ = 2
interval := 200 / 10
section = frl / interval
}
if ctype == "credit" {
typ = 3
interval := 200 / 10
section = frl / interval
}
return
}
func query(ctype string, tags []int64, frl int, mid int64) (q string) {
if len(tags) > 0 {
q += fmt.Sprintf(" AND tag_id IN (%s)", xstr.JoinInts(tags))
}
section, typ := getSection(ctype, frl)
q += fmt.Sprintf(" AND section=%d", section)
q += fmt.Sprintf(" AND ctype=%d", typ)
if mid > 0 {
q += fmt.Sprintf(" AND mid=%d", mid)
}
return
}

View File

@@ -0,0 +1,139 @@
package service
import (
"bytes"
"encoding/csv"
"strconv"
"time"
"go-common/app/admin/main/up-rating/model"
)
var (
_avCategory = map[int]string{
0: "默认",
1: "动画",
3: "音乐",
129: "舞蹈",
4: "游戏",
36: "科技",
160: "生活",
119: "鬼畜",
155: "时尚",
23: "电影",
11: "电视剧",
13: "番剧",
167: "国创",
165: "广告",
5: "娱乐",
177: "纪录片",
181: "影视",
}
_scoreField = map[model.ScoreType]string{
model.Magnetic: "magnetic_score",
model.Creativity: "creativity_score",
model.Influence: "influence_score",
model.Credit: "credit_score",
}
)
// FormatCSV format to csv data
func formatCSV(records [][]string) (data []byte, err error) {
buf := new(bytes.Buffer)
// add utf bom
if len(records) > 0 {
buf.WriteString("\xEF\xBB\xBF")
}
w := csv.NewWriter(buf)
err = w.WriteAll(records)
if err != nil {
return
}
data = buf.Bytes()
return
}
func tagDesc(tagID int) string {
if v, ok := _avCategory[tagID]; ok {
return v
}
return _avCategory[0]
}
func formatScores(ratings []*model.RatingInfo) (data [][]string) {
if len(ratings) <= 0 {
return
}
data = make([][]string, len(ratings)+1)
data[0] = []string{"月份", "UID", "昵称", "分区", "总分", "创作力", "影响力", "信用分", "投稿量", "粉丝量"}
for i, v := range ratings {
data[i+1] = []string{
v.Date,
strconv.FormatInt(v.Mid, 10),
v.NickName,
tagDesc(v.TagID),
strconv.FormatInt(v.MagneticScore, 10),
strconv.FormatInt(v.CreativityScore, 10),
strconv.FormatInt(v.InfluenceScore, 10),
strconv.FormatInt(v.CreditScore, 10),
strconv.FormatInt(v.TotalAvs, 10),
strconv.FormatInt(v.TotalFans, 10),
}
}
return
}
func scoreField(st model.ScoreType) string {
if v, ok := _scoreField[st]; ok {
return v
}
return _scoreField[model.Magnetic]
}
func cDateStr(cdate time.Time) string {
return cdate.Format("2006-01-02")
}
func prevComputation(t time.Time) time.Time {
return time.Date(t.Year(), t.Month()-1, 1, 0, 0, 0, 0, time.Local)
}
func formatStatis(list []*model.RatingStatis, ctype int64) (data [][]string) {
if len(list) <= 0 {
return
}
data = make([][]string, len(list)+1)
data[0] = []string{"分数段", "本月", "占比", "对比", "占比", "平均分"}
switch ctype {
case 0:
data[0] = append(data[0], []string{"创造力", "影响力", "信用分"}...)
case 1:
data[0] = append(data[0], []string{"平均稿件数", "平均播放量", "平均互动量"}...)
case 2:
data[0] = append(data[0], []string{"平均粉丝量"}...)
}
for i, v := range list {
data[i+1] = []string{
v.Tips,
strconv.FormatInt(v.Ups, 10),
v.Proportion,
strconv.FormatInt(v.Compare, 10),
v.ComparePropor,
strconv.FormatInt(v.Score, 10),
}
switch ctype {
case 0:
data[i+1] = append(data[i+1], []string{strconv.FormatInt(v.CreativityScore, 10), strconv.FormatInt(v.InfluenceScore, 10), strconv.FormatInt(v.CreditScore, 10)}...)
case 1:
data[i+1] = append(data[i+1], []string{strconv.FormatInt(v.Avs, 10), strconv.FormatInt(v.Play, 10), strconv.FormatInt(v.Coin, 10)}...)
case 2:
data[i+1] = append(data[i+1], []string{strconv.FormatInt(v.Fans, 10)}...)
}
}
return
}