租售同体的书屋项目——书籍系统(二)——第二部分(阅读量)

一、概述

书籍系统框架如图:

书籍系统.png

文件内容持续更新在GitHub上,可自行查看。

本篇主要是介绍:评论和阅读量中的阅读量统计

二、阅读量

思路

1.通过网页的访问次数来决定;

2.因为次数变化频繁,考虑使用redis做递增,再在某一时间点更新到数据库;

3.防止恶意刷流量,需要有个拦截器,同一个ip在一段时间内不计入次数;

代码

1.拦截器(中间件): 在Expire时间段内不计入次数,使用redis分布式锁进行判定,符合要求的相应文章阅读量则加1

traffic_statistics_middleware.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
golang复制代码package Middlewares

import (
"WebApi/Svc"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gomodule/redigo/redis"
"regexp"
)

var Expire = 10

func TrafficStatisticsMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
ip := c.ClientIP()
url := c.Request.URL
//redis 出错的情况下不记录阅读量并通知工作人员
if repeat, err := IsRepeat(ip + url.String()); err == nil {
if !repeat {
err = TrafficStatistics(url.String())
if err != nil {
fmt.Println(err)
}
}
} else {
fmt.Println(err)
}
c.Next()
}
}

//判斷訪問是否在指定時間內重複 true为重复,反之false
func IsRepeat(key string) (bool, error) {
ok, err := redis.Bool(Svc.SvcContext.Redis.Do("EXISTS", key))
if err != nil {
return false, err
}
if !ok {
_, err = Svc.SvcContext.Redis.Do("SET", key, []byte{}, "NX", "EX", Expire)
if err != nil {
return false, err
}
}
//重复
return ok, nil

}

//在redis记录访问量
func TrafficStatistics(key string) error {
re, err := regexp.Compile("[0-9]+") //解析出来哪本书哪个章节
if err != nil {
fmt.Println(err)
}
res := re.FindAll([]byte(key), -1)

key = "traffic_statistic"
if len(res) == 2 {
member := string(res[0]) + ":" + string(res[1])
_, err := Svc.SvcContext.Redis.Do("ZINCRBY", key, 1, member)
if err != nil {
return err
}
return nil
} else {
return errors.New("url不是正确的格式,无法用正则表达式匹配")
}
}

2.获取访问统计信息:所有访问量都从redis中获取。

ps: 这里我煞笔了,我一开始设想还统计某书某一章的阅读量,其实发现没啥大用,还多做了一层统计。–!

get_traffic_statistic_handler.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
golang复制代码package action

import (
"WebApi/Svc"
"github.com/gin-gonic/gin"
"github.com/gomodule/redigo/redis"
"net/http"
"strconv"
"strings"
)

func GetTrafficStatisticByBookIdAndChapterNumHandler(c *gin.Context) {
bookId := c.Query("bookId")
chapterNum := c.Query("chapterNum")

//找到redis访问量的内容
key := "traffic_statistic"
res, err := redis.String(Svc.SvcContext.Redis.Do("ZSCORE", key, bookId+":"+chapterNum))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
v, err := strconv.Atoi(res)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, v)
}

func GetAllTrafficStatisticHandler(c *gin.Context) {
//找到redis访问量的内容
key := "traffic_statistic"
res, err := redis.StringMap(Svc.SvcContext.Redis.Do("ZRANGE", key, 0, -1, "WITHSCORES"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, res)
}

func GetAllTrafficStatisticHandlerByBookId(c *gin.Context) {
//通过书籍ID找到redis访问量的内容
bookId := c.Query("bookId")
key := "traffic_statistic"
res, err := redis.StringMap(Svc.SvcContext.Redis.Do("ZRANGE", key, 0, -1, "WITHSCORES"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var count int64
for k, _ := range res {
if strings.Split(k, ":")[0] == bookId {
n, err := strconv.ParseInt(res[k], 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
count += n
}
}

c.JSON(http.StatusOK, count)
}

3.定时让redis和数据库交换数据:使用”github.com/robfig/cron”做定时任务.

cron.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
golang复制代码package Utils

import (
"WebApi/Pb/action"
"WebApi/Svc"
"context"
"fmt"
"github.com/gomodule/redigo/redis"
"github.com/robfig/cron"
"strconv"
"strings"
)

func init() {
c := cron.New()

//凌晨5点更新DB中书籍的访问量
if err := c.AddFunc("0 0 5 * * ?", func() {
_ = TrafficStatisticsImportDB()
}); err != nil {
fmt.Println(err)
}
fmt.Println("cron start")
c.Start()
}

//定时在缓存和数据库之间进行访问量的同步
func TrafficStatisticsImportDB() error {
var err error
//找到所有redis访问量的内容
res, err := redis.StringMap(Svc.SvcContext.Redis.Do("ZRANGE", "traffic_statistic", 0, -1, "WITHSCORES"))
if err != nil {
return err
}
fmt.Println(res)
// Update to DB
var bookId, chapterNum, trafficNumber int64
for key, _ := range res {
bookId, err = strconv.ParseInt(strings.Split(key, ":")[0], 10, 64)
if err != nil {
return err
}

chapterNum, err = strconv.ParseInt(strings.Split(key, ":")[1], 10, 64)
if err != nil {
return err
}

trafficNumber, err = strconv.ParseInt(res[key], 10, 64)
if err != nil {
return err
}

rep, err := Svc.SvcContext.Grpc.ActionGrpc.GetTrafficStatisticByBookIdAndChapterNum(context.Background(),
&action.TrafficStatisticReq{
BookId: bookId,
ChapterNum: chapterNum})
fmt.Println(rep, err)

if err != nil {
if err.Error() == "rpc error: code = Unknown desc = sql: no rows in result set" {
_, err := Svc.SvcContext.Grpc.ActionGrpc.CreateTrafficStatistic(context.Background(), &action.TrafficStatisticReq{
BookId: bookId,
ChapterNum: chapterNum,
TrafficNumber: trafficNumber,
})
if err != nil {
return err
}
} else {
return err
}
} else {
if trafficNumber > rep.TrafficNumber {
_, err := Svc.SvcContext.Grpc.ActionGrpc.UpdateTrafficStatistic(context.Background(), &action.TrafficStatisticReq{
Id: rep.Id,
BookId: bookId,
ChapterNum: chapterNum,
TrafficNumber: trafficNumber,
})
if err != nil {
return err
}
}
}
}

//DownLoad to Redis(防止Redis数据丢失)
if len(res) == 0 || res == nil {
resp, err := Svc.SvcContext.Grpc.ActionGrpc.GetAllTrafficStatistics(context.Background(), &action.Request{})
if err != nil {
return err
}
ts := resp.TrafficStatistics
key := "traffic_statistic"
for i, _ := range ts {
_, err = Svc.SvcContext.Redis.Do("ZADD", key, ts[i].TrafficNumber, strconv.FormatInt(ts[i].BookId, 10)+":"+strconv.FormatInt(ts[i].ChapterNum, 10))
if err != nil {
return err
}
}
}
return nil
}

4.结果展示

阅读量统计.png

三、Tips

最近工作中忙了起来,更新可能会比之前慢一些,请多多包涵。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%