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.