尝试通过gin+redis为hugo博客添加频率限制反爬

5 篇文章 0 订阅
2 篇文章 0 订阅

最近无意中查看nginx发现好多疑似爬虫ip,遂临时起意能不能搞个频率限制。

以下用我使用的刘志超先生开发的Leaveit主题为例

一. 配置文件更改baseURL

// hugo config.toml
baseURL = "/public"

avatar = "/public/images/me/avatar.jpeg" 

Wechat = "/public/images/me/wechat.png" 

二. 更改部分模板html中的href

// 更改themes-leaveit-layouts-partials-home_profile.html
<div class="avatar">
        <a href="/public/posts/"> <img src="{{ (printf "%s%s" $cdn_url $avatar)}}"> </a>
</div>

以上修改保证可以设置一个public静态文件夹可以访问所有内容(如果有上传图片保存在服务器本地的0.0,请记得修改markdown中的图片url)*

三. 编写网关代码

/*
目录结构:
	blog_ratelimit
		- apigw
			- common
				- code.go
			- conf
				- config.go
				- config.json
			- middleware
				- ratelimit.go
			- redisdb
				- redisdb.go
		- public
		- main.go

*/

code.go

package common

const (
	_ int32 = iota +999
	StatusBlackList    // 1000 黑名单限制
)

config.go

package conf

import (
	"encoding/json"
	"log"
	"os"
)

type Configuration struct {
	RedisAddr string `json:"redis_addr"` // redis地址
	RedisPass string `json:"redis_pass"` // redis密码
}


var DefaultConfig *Configuration

func InitConfig(configLoc string){
	_, err := os.Stat(configLoc)
	if err != nil {
		log.Panic("配置文件路径不存在")
	}
	file,_ := os.Open(configLoc)
	defer file.Close()

	decoder := json.NewDecoder(file)
	DefaultConfig = &Configuration{}

	err = decoder.Decode(DefaultConfig)
	if err != nil {
		log.Panic(err)
	}
}

config.json

{
  "redis_addr": "192.168.50.15:6379",
  "redis_pass": ""
}

ratelimit.go


package middleware

import (
	"blog_ratelimit/apigw/common"
	"blog_ratelimit/apigw/redisdb"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis"
	"net/http"
	"strconv"
	"time"
)

const (
	blackListKey = "blacklist"
)

func IpVerify() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		visitorIP := ctx.Request.Header.Get("X-real-ip")
		// fmt.Println(visitorIP)
		rdb := redisdb.GetRatePool()
		// 判断是否在黑名单
		timeOrigin := rdb.HGet(blackListKey, visitorIP).Val()
		err := blackListVerify(timeOrigin,visitorIP, rdb)
		if err != nil {
			ctx.JSON(http.StatusOK, gin.H{
				"code": common.StatusBlackList,
				"msg":  err.Error(),
			})
			ctx.Abort()
			return
		}
		// 如果不存在于黑名单
		lenList, _ := rdb.LLen(visitorIP).Result()
		// 如果第一次登陆或频率限制外
		if lenList == 0 {
			// 如果为空跳过,设置后跳过
			// 添加IP list
			rdb.LPush(visitorIP, visitorIP)
			// 设置过期时间
			rdb.Expire(visitorIP, 1000*1000*1000)
			ctx.Next()
		}else if lenList >0 && lenList <3 {
			rdb.LPush(visitorIP, visitorIP)
			ctx.Next()
		}else {
			// 加入黑名单
			rdb.HSet(blackListKey,visitorIP,time.Now().Local().Unix())
			ctx.Abort()
			return
		}
	}
}


func blackListVerify(ot,visitorIP string,rdb *redis.Client) error {
	// 如果有值,进一步判断
	if ot != "" {
		// 如果value的时间和当前时间差超过十分钟,解除限制
		timeOriginInt, _ := strconv.Atoi(ot)
		oTimeUnix := time.Unix(int64(timeOriginInt),0).Local()
		subTime := time.Now().Sub(oTimeUnix)
		if subTime > 10*time.Minute {
			// 超过限制时间 解除限制
			rdb.HDel(blackListKey,visitorIP)
			return nil
		}else {
			//
			return errors.New(fmt.Sprintf("您已被加入黑名单,剩余限制时间:%v",10*time.Minute-subTime))
		}
	}
	// 不存在返回nil
	return nil
}

redisdb.go

package redisdb

import (
	"blog_ratelimit/apigw/conf"
	"github.com/go-redis/redis"
	"log"
	"time"
)

var ratePool *redis.Client


func init(){
	conf.InitConfig("./apigw/conf/config.json")
	rateRedisInit()
}

func rateRedisInit(){
	client := redis.NewClient(&redis.Options{
		Addr:         conf.DefaultConfig.RedisAddr,
		Password:     conf.DefaultConfig.RedisPass,
		DB:           1,
		PoolSize:     50,
		MinIdleConns: 20,
		IdleTimeout:  30*time.Second,
	})
	_, err := client.Ping().Result()
	if err != nil {
		log.Panic("failed to connect redis.")
	}
	ratePool = client
}

func GetRatePool() * redis.Client {
	return ratePool
}

main.go

package main

import (
	"blog_ratelimit/apigw/middleware"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	router := gin.Default()
	router.Static("/public/","./public")
	router.Use(middleware.IpVerify())
	router.GET("/", func(context *gin.Context) {
		context.Redirect(http.StatusFound,"/public/index.html")
	})
	router.Run(":8080")
}

四. 修改markdown图片链接脚本

package main

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

const (
	DirLoc  = "/Users/huangxuchen/dvpos/content/posts"
	OldWord = "/images/blog/"
	NewWord = "/public/images/blog/"
)

func replaceLogs(errorInfo string) {
	file, _ := os.OpenFile("./utils/replacewords/logs.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
	log.New(io.MultiWriter(file, os.Stderr), "[Failed]", log.Ldate|log.Ltime|log.Llongfile).Println(errorInfo)
}

func getFileList() ([]string, error) {
	var fileList []string
	// 获取所有文件路径
	err := filepath.Walk(DirLoc, func(path string, info os.FileInfo, err error) error {
		if info == nil {
			return err
		}
		// 匹配以md结尾文件加入slice
		isMatch, _ := regexp.MatchString("^*.md$", info.Name())
		if isMatch{
			fileList = append(fileList, path)
		}
		return nil
	})
	return fileList, err
}

func replaceWord(fileList []string) error {
	for _, v := range fileList {
		//删除原文件替换后逐行写入新文件
		//file, err := os.OpenFile(v,os.O_RDWR,0766)
		//if err != nil {
		//	return errors.New(fmt.Sprintf("failed to open file:%v,Error:%v", v, err.Error()))
		//}
		//reader := bufio.NewReader(file)
		//defer file.Close()
		 删除源文件
		//os.Remove(v)
		//newFile, err := os.OpenFile(v,os.O_RDWR|os.O_CREATE,0766)
		//if err != nil {
		//	return errors.New(fmt.Sprintf("failed to create new file:%v,Error:%v", v, err.Error()))
		//}
		//defer newFile.Close()
		//for {
		//	line, _, err := reader.ReadLine()
		//	if err != nil {
		//		if err == io.EOF {
		//			break
		//		} else {
		//			return errors.New(fmt.Sprintf("failed to read file:%v,Error:%v", v, err.Error()))
		//		}
		//	}
		//	// 判断是否包含需要替换的内容
		//	if strings.Contains(string(line), OldWord){
		//		newLine := strings.Replace(string(line), OldWord, NewWord,-1)
		//		_, err = newFile.WriteString(newLine+"\n")
		//		if err != nil {
		//			return errors.New(fmt.Sprintf("failed to replace word at file:%v,Error:%v", v, err.Error()))
		//		}
		//	}else {
		//		_, err := newFile.WriteString(string(line)+"\n")
		//		if err != nil {
		//			return errors.New(fmt.Sprintf("failed to replace word at file:%v,Error:%v", v, err.Error()))
		//		}
		//	}
		//}
		// 简单粗暴(耗时短)
		file, err := ioutil.ReadFile(v)
		if err != nil {
			return errors.New(fmt.Sprintf("failed to read file:%v,Error:%v", v, err.Error()))
		}
		newFile := strings.Replace(string(file), OldWord, NewWord, -1)
		err = ioutil.WriteFile(v, []byte(newFile), 0)
		if err != nil {
			return errors.New(fmt.Sprintf("failed to replace word at file:%v,Error:%v", v, err.Error()))
		}
	}
	return nil
}

func main() {
	fmt.Println("Start to replace word...")
	fileList, err := getFileList()
	if err != nil {
		fmt.Println(err)
		return
	}
	//fmt.Println(fileList)
	now := time.Now()
	err = replaceWord(fileList)
	if err != nil {
		replaceLogs(err.Error())
		os.Exit(1)
	}
	costTime := time.Now().Sub(now)
	fmt.Printf("Finished! CostTime:%v",costTime)
}


五. nginx配置



worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;


    server {
        listen       80;
        server_name  域名1;
        server_name  域名2;
        charset utf-8;
      
        rewrite ^(.*)$ https://$host$1  permanent;

    }

    server {
        listen       443 ssl;
        server_name  域名1;
	   server_name 域名2;
	   root   root地址;
	   
        ssl_certificate      com.pem;
        ssl_certificate_key  com.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
            # 保存原始IP
        	  proxy_set_header X-real-ip           $remote_addr;
            proxy_pass http://127.0.0.1:8080/;
        }
    	   
    }

}



待解决

  • 编写脚本修改markdown文章的图片url(已解决)
  • gin获取静态文件是否可以将单次同一文章下的多个资源请求视为一个请求(已解决过滤图片cssjs等资源请求)
  • nginx配置修改,原来是基于hugo静态文件的配置(已解决)
  • 视情况修改增加限制,比如增加频率策略,设置令牌,浏览器校验等
  • 暂时想到这么多

持续更新、有经验的大神希望能分享下经验~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值