An overview,
The book system framework is shown as follows:
File content is continuously updated inGitHubOn, you can check by yourself.
This article mainly introduces: reviews and readingsIn theReading statistics
Second, reading quantity
Train of thought
1. Determine by the number of visits to the web page;
2. Because the number changes frequently, consider using Redis to do incremental, and then update to the database at a certain point in time;
3. To prevent malicious traffic flushing, an interceptor is required. The same IP address is not counted in the number of times within a period of time.
code
1. Interceptor (middleware) : the number of times will not be counted in the Expire period. Redis distributed lock will be used for judgment
traffic_statistics_middleware.go
package Middlewares
import (
"WebApi/Svc"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gomodule/redigo/redis"
"regexp"
)
var Expire = 10
func TrafficStatisticsMiddleware(a) func(c *gin.Context) {
return func(c *gin.Context) {
ip := c.ClientIP()
url := c.Request.URL
// Redis does not record the number of readings and notify the staff in case of errors
if repeat, err := IsRepeat(ip + url.String()); err == nil {
if! repeat { err = TrafficStatistics(url.String())iferr ! =nil {
fmt.Println(err)
}
}
} else {
fmt.Println(err)
}
c.Next()
}
}
// Check whether the access is repeated within the specified time
func IsRepeat(key string) (bool, error) {
ok, err := redis.Bool(Svc.SvcContext.Redis.Do("EXISTS", key))
iferr ! =nil {
return false, err
}
if! ok { _, err = Svc.SvcContext.Redis.Do("SET", key, []byte{}, "NX"."EX", Expire)
iferr ! =nil {
return false, err
}
}
/ / repeat
return ok, nil
}
// Log traffic in Redis
func TrafficStatistics(key string) error {
re, err := regexp.Compile("[0-9] +") // Parse out which book which chapter
iferr ! =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)
iferr ! =nil {
return err
}
return nil
} else {
return errors.New("Url not in correct format and cannot be matched with regular expression")}}Copy the code
2. Access statistics: All visits are obtained from Redis.
Ps: THIS is a bit of a joke. When I first thought about counting how much I read a chapter of a book, IT didn’t help. – -!
get_traffic_statistic_handler.go
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")
// Find the content of redis visits
key := "traffic_statistic"
res, err := redis.String(Svc.SvcContext.Redis.Do("ZSCORE", key, bookId+":"+chapterNum))
iferr ! =nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
v, err := strconv.Atoi(res)
iferr ! =nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, v)
}
func GetAllTrafficStatisticHandler(c *gin.Context) {
// Find the content of redis visits
key := "traffic_statistic"
res, err := redis.StringMap(Svc.SvcContext.Redis.Do("ZRANGE", key, 0.- 1."WITHSCORES"))
iferr ! =nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, res)
}
func GetAllTrafficStatisticHandlerByBookId(c *gin.Context) {
// Use the book ID to find the content of redis visits
bookId := c.Query("bookId")
key := "traffic_statistic"
res, err := redis.StringMap(Svc.SvcContext.Redis.Do("ZRANGE", key, 0.- 1."WITHSCORES"))
iferr ! =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)
iferr ! =nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
count += n
}
}
c.JSON(http.StatusOK, count)
}
Copy the code
3. Periodically enable Redis to exchange data with the database: use “github.com/robfig/cron” to perform a scheduled task.
cron.go
package Utils
import (
"WebApi/Pb/action"
"WebApi/Svc"
"context"
"fmt"
"github.com/gomodule/redigo/redis"
"github.com/robfig/cron"
"strconv"
"strings"
)
func init(a) {
c := cron.New()
Update the number of visits to books in DB at 5am
if err := c.AddFunc("0, 0, 5 * *?.func(a){ _ = TrafficStatisticsImportDB() }); err ! =nil {
fmt.Println(err)
}
fmt.Println("cron start")
c.Start()
}
// Periodically synchronize traffic between the cache and the database
func TrafficStatisticsImportDB(a) error {
var err error
// Find all redis visits
res, err := redis.StringMap(Svc.SvcContext.Redis.Do("ZRANGE"."traffic_statistic".0.- 1."WITHSCORES"))
iferr ! =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)
iferr ! =nil {
return err
}
chapterNum, err = strconv.ParseInt(strings.Split(key, ":") [1].10.64)
iferr ! =nil {
return err
}
trafficNumber, err = strconv.ParseInt(res[key], 10.64)
iferr ! =nil {
return err
}
rep, err := Svc.SvcContext.Grpc.ActionGrpc.GetTrafficStatisticByBookIdAndChapterNum(context.Background(),
&action.TrafficStatisticReq{
BookId: bookId,
ChapterNum: chapterNum})
fmt.Println(rep, err)
iferr ! =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,
})
iferr ! =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,
})
iferr ! =nil {
return err
}
}
}
}
//DownLoad to Redis
if len(res) == 0 || res == nil {
resp, err := Svc.SvcContext.Grpc.ActionGrpc.GetAllTrafficStatistics(context.Background(), &action.Request{})
iferr ! =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))
iferr ! =nil {
return err
}
}
}
return nil
}
Copy the code
4. Results presentation
Three, Tips,
I have been busy with my work recently, so the update may be slower than before, please bear with me.