浏览代码

Merge branch 'master' into feature/v1.2

unknown 3 年之前
父节点
当前提交
f8b67b7abf
共有 87 个文件被更改,包括 2496 次插入425 次删除
  1. 20 0
      README.md
  2. 1 1
      common/gatecode/errcode.go
  3. 4 3
      common/gatecode/errcode_string.go
  4. 135 0
      common/redis-util.go
  5. 43 10
      common/util.go
  6. 27 31
      core/logs/init.go
  7. 10 0
      core/logs/internal/module.go
  8. 9 8
      core/logs/internal/notice/notice.go
  9. 44 0
      core/logs/internal/savedb/saveDb.go
  10. 0 85
      core/logs/output.go
  11. 0 125
      core/logs/saveDb.go
  12. 154 49
      core/logs/serverLogs.go
  13. 96 0
      core/proxy/filterPoly/blacklist.go
  14. 113 0
      core/proxy/filterPoly/filterPoly.go
  15. 73 0
      core/proxy/filterPoly/manager.go
  16. 22 0
      core/proxy/filterPoly/reqRule.go
  17. 30 0
      core/proxy/filterPoly/vars.go
  18. 97 0
      core/proxy/filterPoly/verify.go
  19. 87 0
      core/proxy/filterPoly/verifyDetail.go
  20. 174 0
      core/proxy/filterPoly/wordsclick.go
  21. 1 1
      core/proxy/middleware/errorHandler.go
  22. 41 0
      core/proxy/middleware/spiderPolyHandler.go
  23. 6 9
      core/proxy/proxyServer.go
  24. 6 5
      core/router/manager.go
  25. 6 2
      core/router/router.go
  26. 45 37
      etc/config.yaml
  27. 67 45
      gaway.sql
  28. 6 1
      go.mod
  29. 35 13
      go.sum
  30. 2 0
      main.go
  31. 577 0
      resources/antiRes/control.html
  32. 二进制
      resources/antiRes/images/flush.png
  33. 二进制
      resources/antiRes/images/hoverclick.png
  34. 二进制
      resources/antiRes/images/verify_logo.png
  35. 111 0
      resources/antiRes/js/mainHook.js
  36. 二进制
      resources/antiRes/lib/anti_v1.wasm
  37. 140 0
      resources/antiRes/page/app.html
  38. 314 0
      resources/antiRes/page/verify.html
  39. 二进制
      resources/verify/fontBg/100.jpg
  40. 二进制
      resources/verify/fontBg/101.jpg
  41. 二进制
      resources/verify/fontBg/102.jpg
  42. 二进制
      resources/verify/fontBg/81.jpg
  43. 二进制
      resources/verify/fontBg/82.jpg
  44. 二进制
      resources/verify/fontBg/83.jpg
  45. 二进制
      resources/verify/fontBg/84.jpg
  46. 二进制
      resources/verify/fontBg/85.jpg
  47. 二进制
      resources/verify/fontBg/86.jpg
  48. 二进制
      resources/verify/fontBg/87.jpg
  49. 二进制
      resources/verify/fontBg/88.jpg
  50. 二进制
      resources/verify/fontBg/90.jpg
  51. 二进制
      resources/verify/fontBg/91.jpg
  52. 二进制
      resources/verify/fontBg/92.jpg
  53. 二进制
      resources/verify/fontBg/93.jpg
  54. 二进制
      resources/verify/fontBg/94.jpg
  55. 二进制
      resources/verify/fontBg/95.jpg
  56. 二进制
      resources/verify/fontBg/96.jpg
  57. 二进制
      resources/verify/fontBg/97.jpg
  58. 二进制
      resources/verify/fontBg/98.jpg
  59. 二进制
      resources/verify/fontBg/99.jpg
  60. 二进制
      resources/verify/fontType/STZHONGS.TTF
  61. 二进制
      resources/verify/imageBg/10.jpg
  62. 二进制
      resources/verify/imageBg/15.jpg
  63. 二进制
      resources/verify/imageBg/17.jpg
  64. 二进制
      resources/verify/imageBg/18.jpg
  65. 二进制
      resources/verify/imageBg/2.jpg
  66. 二进制
      resources/verify/imageBg/23.jpg
  67. 二进制
      resources/verify/imageBg/24.jpg
  68. 二进制
      resources/verify/imageBg/3.jpg
  69. 二进制
      resources/verify/imageBg/32.jpg
  70. 二进制
      resources/verify/imageBg/37.jpg
  71. 二进制
      resources/verify/imageBg/39.jpg
  72. 二进制
      resources/verify/imageBg/4.jpg
  73. 二进制
      resources/verify/imageBg/65.jpg
  74. 二进制
      resources/verify/imageBg/66.jpg
  75. 二进制
      resources/verify/imageBg/67.jpg
  76. 二进制
      resources/verify/imageBg/68.jpg
  77. 二进制
      resources/verify/imageBg/69.jpg
  78. 二进制
      resources/verify/imageBg/7.jpg
  79. 二进制
      resources/verify/imageBg/70.jpg
  80. 二进制
      resources/verify/imageBg/72.jpg
  81. 二进制
      resources/verify/imageBg/73.jpg
  82. 二进制
      resources/verify/imageBg/74.jpg
  83. 二进制
      resources/verify/imageBg/75.jpg
  84. 二进制
      resources/verify/imageBg/76.jpg
  85. 二进制
      resources/verify/imageBg/77.jpg
  86. 二进制
      resources/verify/imageBg/78.jpg
  87. 二进制
      resources/verify/imageBg/8.jpg

+ 20 - 0
README.md

@@ -49,4 +49,24 @@ closeNotify()
     go watchNode.NewWatcher(ctx, resolver)
 ```
 
+## 反爬虫策略配置
 
+
+```json
+          checkIdBlock      是否开启用户session校验
+          idThreshold       用户请求阈值 [[a,b],[c,d],[m,n]] a秒b次、c秒d次请求出验证码,并清空次阈值内容;m秒n次出验证码,不清除阈值内容,每超出idFreq次,出现一次验证码
+          idTimeRange       阈值有效时长
+          idFreq            超出最大阈值后,每超出idFreq次,出现一次验证码
+          idMax             超出此阈值后直接进入临时黑名单
+          idMaxBlockTimes   临时黑名单超出此阈值后直接进入永久黑名单
+          
+          checkIpBlock      是否开启用户ip校验
+          ipThreshold       用户请求阈值 [[a,b],[c,d],[m,n]] a秒b次、c秒d次请求出验证码,并清空次阈值内容;m秒n次出验证码,不清除阈值内容,每超出idFreq次,出现一次验证码
+          ipTimeRange       阈值有效时长
+          ipFreq            超出最大阈值后,每超出idFreq次,出现一次验证码
+          ipMax             超出此阈值后直接进入临时黑名单
+          ipMaxBlockTimes   临时黑名单超出此阈值后直接进入永久黑名单
+          
+          vcodeErrorTimes   [a,b] a秒验证码出错b次,进入临时黑名单
+          tempBlockTime     临时黑名单时长
+```

+ 1 - 1
common/gatecode/errcode.go

@@ -46,7 +46,7 @@ const (
 	// OTHER_ERR_NIL 未捕获的异常
 	OTHER_ERR_NIL       ErrCode = 4000 + iota // server ok
 	OTHER_ERR_UNDEFINED                       // 未知异常
-
+	OTHER_ERR_OFTEN                           //请求频繁
 )
 
 //使用

+ 4 - 3
common/gatecode/errcode_string.go

@@ -37,20 +37,21 @@ func _() {
 	_ = x[SERVER_DEDUCT_PARAM_LACK-3002]
 	_ = x[OTHER_ERR_NIL-4000]
 	_ = x[OTHER_ERR_UNDEFINED-4001]
+	_ = x[OTHER_ERR_OFTEN-4002]
 }
 
 const (
 	_ErrCode_name_0 = "global OK无用户身份无选择企业没有权限正在开发中企业账户已被冻结企业未认证企业认证待审核企业认证未通过企业认证已过期无效资源code权限校验失败权益已过期权益余额不足没有权限"
 	_ErrCode_name_1 = "gateway ok未知节点节点无可用服务未注册路由地址接口维护中服务地址异常服务异常用户中心服务异常资源中心服务异常"
 	_ErrCode_name_2 = "server ok接口超时扣减异常"
-	_ErrCode_name_3 = "server ok未知异常"
+	_ErrCode_name_3 = "server ok未知异常请求频繁"
 )
 
 var (
 	_ErrCode_index_0 = [...]uint8{0, 9, 24, 39, 51, 66, 90, 105, 126, 147, 168, 184, 202, 217, 235, 247}
 	_ErrCode_index_1 = [...]uint8{0, 10, 22, 43, 64, 79, 97, 109, 133, 157}
 	_ErrCode_index_2 = [...]uint8{0, 9, 21, 33}
-	_ErrCode_index_3 = [...]uint8{0, 9, 21}
+	_ErrCode_index_3 = [...]uint8{0, 9, 21, 33}
 )
 
 func (i ErrCode) String() string {
@@ -64,7 +65,7 @@ func (i ErrCode) String() string {
 	case 3000 <= i && i <= 3002:
 		i -= 3000
 		return _ErrCode_name_2[_ErrCode_index_2[i]:_ErrCode_index_2[i+1]]
-	case 4000 <= i && i <= 4001:
+	case 4000 <= i && i <= 4002:
 		i -= 4000
 		return _ErrCode_name_3[_ErrCode_index_3[i]:_ErrCode_index_3[i+1]]
 	default:

+ 135 - 0
common/redis-util.go

@@ -0,0 +1,135 @@
+package common
+
+import (
+	"encoding/json"
+	"github.com/go-redis/redis"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/os/gctx"
+	"time"
+)
+
+type Redis struct {
+	Addr        string
+	MaxConn     int
+	Db          int
+	IdleTimeOut int
+	Client      *redis.Client
+}
+
+type RedisConf struct {
+	Addr     string `json:"address"`
+	PoolSize int    `json:"poolSize"`
+	DB       int    `json:"db"`
+}
+
+func InitRedis(conf RedisConf) *Redis {
+	r := &Redis{Addr: conf.Addr, MaxConn: conf.PoolSize, Db: conf.DB}
+	r.Init()
+	return r
+}
+
+func (r *Redis) Init() {
+	opt := &redis.Options{
+		Addr:               r.Addr,
+		DB:                 r.Db,
+		DialTimeout:        10 * time.Second,
+		ReadTimeout:        300 * time.Second,
+		WriteTimeout:       30 * time.Second,
+		PoolSize:           r.MaxConn,
+		PoolTimeout:        30 * time.Second,
+		IdleTimeout:        time.Minute,
+		IdleCheckFrequency: 5 * time.Second,
+	}
+	r.Client = redis.NewClient(opt)
+}
+
+func (r *Redis) Set(k string, v interface{}, timeout int64) bool {
+	bt, _ := json.Marshal(v)
+	cmd := r.Client.Set(k, bt, time.Duration(timeout)*time.Second)
+	b, err := cmd.Result()
+	if err != nil {
+		g.Log().Errorf(gctx.New(), "redis Set err %s", err.Error())
+	}
+	return "OK" == b
+}
+
+func (r *Redis) Get(k string) interface{} {
+	cmd := r.Client.Get(k)
+	bt, _ := cmd.Bytes()
+	if bt != nil && len(bt) > 0 {
+		var res interface{}
+		if err := json.Unmarshal(bt, &res); err != nil {
+			g.Log().Errorf(gctx.New(), "redis Get Unmarshal err %v", err)
+		}
+		return res
+	}
+	return nil
+}
+
+func (r *Redis) GetBytes(k string) []byte {
+	cmd := r.Client.Get(k)
+	bt, err := cmd.Bytes()
+	if err != nil {
+		if err.Error() != "redis: nil" {
+			g.Log().Errorf(gctx.New(), "redis GetBytes err %s", err.Error())
+		}
+		return nil
+	}
+	return bt
+}
+
+func (r *Redis) MGet(k ...string) []interface{} {
+	cmd := r.Client.MGet(k...)
+	arr, err := cmd.Result()
+	if err != nil {
+		g.Log().Errorf(gctx.New(), "redis MGet err %s", err.Error())
+	}
+	return arr
+}
+
+func (r *Redis) Incr(k string) int64 {
+	cmd := r.Client.Incr(k)
+	res, err := cmd.Result()
+	if err != nil {
+		g.Log().Errorf(gctx.New(), "redis Incr err %s", err.Error())
+	}
+	return res
+}
+
+func (r *Redis) Del(k string) bool {
+	_, err := r.Client.Del(k).Result()
+	if err != nil {
+		g.Log().Errorf(gctx.New(), "redis Del err %s", err.Error())
+		return false
+	}
+	return true
+}
+
+func (r *Redis) Exists(k string) bool {
+	res, err := r.Client.Exists(k).Result()
+	if err != nil {
+		g.Log().Errorf(gctx.New(), "redis Exists err %s", err.Error())
+	}
+	return res == 1
+}
+
+func (r *Redis) IncrTimeout(k string, duration time.Duration) int64 {
+	if r.Client.TTL(k).Val().Nanoseconds() < 0 {
+		r.Client.Set(k, 1, duration)
+		return 1
+	} else {
+		return r.Incr(k)
+	}
+}
+
+func (r *Redis) GetTTL(key string) int64 {
+	cmd := r.Client.TTL(key)
+	t, err := cmd.Result()
+	if err != nil {
+		g.Log().Errorf(gctx.New(), "redis GetTTL err %s", err.Error())
+	}
+	if t.Nanoseconds() < 0 {
+		return -1
+	}
+	return time.Unix(time.Now().Unix(), t.Nanoseconds()).Unix()
+}

+ 43 - 10
common/util.go

@@ -1,12 +1,45 @@
 package common
 
-//// SplitPower 多维权限拆解
-//// xyz【1需要 0 不需要】
-//// SplitPower(1001)=[]bool{true,false,false,true}
-//func SplitPower(power, num int) []bool {
-//	boolArr := make([]bool, num)
-//	for i := 0; i < num; i++ {
-//		boolArr[num-i-1] = power/int(math.Pow(10, float64(i)))%10 == 1
-//	}
-//	return boolArr
-//}
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+func CopyReqAndFormData(req *http.Request, copy ...bool) {
+	//获取请求体内容
+	bodyBytes, _ := ioutil.ReadAll(req.Body)
+	_ = req.Body.Close()
+	//将请求体内容重新写入请求体
+	req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
+	if strings.Index(req.Header.Get("Content-Type"), "json") > -1 {
+		_ = ParseJson(req)
+	} else {
+		_ = req.ParseForm() //格式化请求内容
+	}
+	//将请求体内容重新写入请求体
+	if len(copy) == 0 || copy[0] == false {
+		req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
+	}
+}
+
+func ParseJson(req *http.Request) error {
+	bytes, err := ioutil.ReadAll(req.Body)
+	if err != nil {
+		return err
+	}
+	jsonMap := map[string]interface{}{}
+	err = json.Unmarshal(bytes, &jsonMap)
+	if err != nil {
+		return err
+	}
+	req.Form = make(url.Values)
+	for k, v := range jsonMap {
+		req.Form.Set(k, fmt.Sprint(v))
+	}
+	return nil
+}

+ 27 - 31
core/logs/init.go

@@ -1,46 +1,42 @@
 package logs
 
 import (
-	"github.com/gogf/gf/v2/os/gcfg"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/common/db"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/core/logs/internal/notice"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/core/logs/internal/savedb"
+	"github.com/gogf/gf/v2/frame/g"
 	"github.com/gogf/gf/v2/os/gctx"
-	"github.com/gogf/gf/v2/os/glog"
-	"log"
 )
 
-var GInfo *glog.Logger // 系统日志输出
-var GReq *serverLog    // 服务请求记录
+var GateWayServerLog *serverLog
 
 func InitLogs() {
+	//初始化通知
+	var noticeConfig notice.NoticeConfig
 	ctx := gctx.New()
-	logCommon := &commonLogConfig{
-		Path:   gcfg.Instance().MustGet(ctx, "system.log.path", "./logs").String(),
-		Debug:  gcfg.Instance().MustGet(ctx, "system.log.debug", false).Bool(),
-		Stdout: gcfg.Instance().MustGet(ctx, "system.log.stdout", false).Bool(),
+	if err := g.Cfg().MustGet(ctx, "alarm").Scan(&noticeConfig); err != nil {
+		g.Log().Errorf(ctx, "nsq通知配置异常", err)
 	}
-	//初始化GInfo
-	gInfoName := gcfg.Instance().MustGet(ctx, "system.log.pattern", "system-{Ymd}.log").String()
-	GInfo = initBaseLog(logCommon, gInfoName)
-	//设置系统默认日志
-	glog.SetDefaultLogger(GInfo)
-	//将log输出转至GInfo
-	log.SetOutput(GInfo)
 
-	//初始化通知
-	var noticeConfig NoticeConfig
-	if err := gcfg.Instance().MustGet(ctx, "system.alarm").Scan(&noticeConfig); err != nil {
-		GInfo.Errorf(ctx, "nsq通知配置异常", err)
+	noticeObj, err := notice.NewNotice(noticeConfig)
+	if err != nil {
+		g.Log().Errorf(ctx, "nsq通知初始化异常异常", err)
+	}
+
+	// 持久化化存储
+	var sDB *savedb.DbLogs
+	if g.Cfg().MustGet(ctx, "logger.reqLog.saveToDb").Bool() {
+		sDB = new(savedb.DbLogs)
+		sDB.Db = db.MgoLog
 	}
 
-	//初始化请求记录
-	GReq = initServerLog(serverLogConfig{
-		commonLogConfig:        logCommon,
-		ServerErrorStack:       gcfg.Instance().MustGet(ctx, "system.log.serverErrorStack", false).Bool(),
-		ServerRequestLogSaveDb: gcfg.Instance().MustGet(ctx, "system.log.serverRequestLogSaveDb", false).Bool(),
-		ServerErrorLogEnabled:  gcfg.Instance().MustGet(ctx, "system.log.serverErrorLogEnabled", false).Bool(),
-		ServerErrorLogPattern:  gcfg.Instance().MustGet(ctx, "system.log.serverErrorLogPattern", "system-{Ymd}.log").String(),
-		ServerAccessLogEnabled: gcfg.Instance().MustGet(ctx, "system.log.serverAccessLogEnabled", false).Bool(),
-		ServerAccessLogPattern: gcfg.Instance().MustGet(ctx, "system.log.serverAccessLogPattern", "system-{Ymd}.log").String(),
-		ServerRequestTimeout:   gcfg.Instance().MustGet(ctx, "system.log.serverRequestTimeout", 500).Int64(),
-	}, noticeConfig)
+	GateWayServerLog = new(serverLog)
 
+	if noticeObj != nil {
+		GateWayServerLog.initNotice(noticeObj)
+	}
+
+	if sDB != nil {
+		GateWayServerLog.initSaveLog(sDB)
+	}
 }

+ 10 - 0
core/logs/internal/module.go

@@ -0,0 +1,10 @@
+package internal
+
+type SaveLogsDb interface {
+	AddLogToTask(data map[string]interface{}) //添加日志
+	SaveLogTask()                             //日志堆栈
+}
+
+type LogsNotice interface {
+	SendWarnMsg(data map[string]interface{}) error
+}

+ 9 - 8
core/logs/notice.go → core/logs/internal/notice/notice.go

@@ -1,8 +1,9 @@
-package logs
+package notice
 
 import (
 	"bp.jydev.jianyu360.cn/BP/jynsq/gonsq"
 	"encoding/json"
+	"github.com/gogf/gf/v2/frame/g"
 	"github.com/gogf/gf/v2/os/gctx"
 	"log"
 )
@@ -18,19 +19,19 @@ type Notice struct {
 	c NoticeConfig
 }
 
-func newNotice(c NoticeConfig) (n *Notice, err error) {
-	n = &Notice{
-		c: c,
-	}
-	var producer *gonsq.Producer
+func NewNotice(c NoticeConfig) (n *Notice, err error) {
 	if c.IsOpen {
+		n = &Notice{
+			c: c,
+		}
+		var producer *gonsq.Producer
 		producer, err = gonsq.NewProducer(c.Address, c.TopPic, c.IsJsonEncode)
 		if err != nil {
 			return
 		}
 		n.p = producer
 	}
-	return
+	return nil, nil
 }
 
 // SendWarnMsg 发送异常通知
@@ -43,7 +44,7 @@ func (n *Notice) SendWarnMsg(detail map[string]interface{}) error {
 	if err != nil {
 		return err
 	}
-	GInfo.Debugf(gctx.New(), "模拟发送信息----->", string(bs))
+	g.Log().Debugf(gctx.New(), "模拟发送信息----->", string(bs))
 	if err := n.p.Publish(bs); err != nil {
 		log.Println("nsq连接失败", err)
 	}

+ 44 - 0
core/logs/internal/savedb/saveDb.go

@@ -0,0 +1,44 @@
+package savedb
+
+import (
+	"app.yhyue.com/moapp/jybase/mongodb"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/common/db"
+	"log"
+	"sync"
+	"time"
+)
+
+type DbLogs struct {
+	Cache []map[string]interface{}
+	lock  sync.Mutex
+	Db    mongodb.MongodbSim
+}
+
+//SaveLogTask 定时保存日志
+func (s *DbLogs) SaveLogTask() {
+	s.lock.Lock()
+	if len(s.Cache) >= 1 {
+		tmp := s.Cache
+		s.Cache = make([]map[string]interface{}, 0)
+		go func() {
+			log.Println("timer..save..visit..log", len(tmp))
+			db.MgoLog.SaveBulk("jy_logs", tmp...)
+		}()
+	}
+
+	s.lock.Unlock()
+	time.AfterFunc(5*time.Minute, s.SaveLogTask)
+}
+
+func (s *DbLogs) AddLogToTask(data map[string]interface{}) {
+	s.lock.Lock()
+	s.Cache = append(s.Cache, data)
+	if len(s.Cache) >= 500 {
+		tmp := s.Cache
+		s.Cache = make([]map[string]interface{}, 0)
+		go func() {
+			log.Println("save..visit..log", len(tmp), s.Db.SaveBulk("jy_gateway_logs", tmp...))
+		}()
+	}
+	s.lock.Unlock()
+}

+ 0 - 85
core/logs/output.go

@@ -1,85 +0,0 @@
-package logs
-
-import (
-	. "bp.jydev.jianyu360.cn/BaseService/gateway/common/gatecode"
-	"bp.jydev.jianyu360.cn/BaseService/gateway/core/router"
-	"fmt"
-	"github.com/gogf/gf/v2/errors/gerror"
-	"github.com/gogf/gf/v2/net/ghttp"
-	"github.com/gogf/gf/v2/os/gtime"
-	"github.com/gogf/gf/v2/text/gstr"
-)
-
-// RecordLogAndNotice 记录请求日志及提醒
-func (s *serverLog) RecordLogAndNotice(r *ghttp.Request, err error) {
-
-	if !s.Config.ServerAccessLogEnabled && //关闭成功日志
-		!s.Config.ServerErrorLogEnabled && //关闭错误日志
-		!s.Config.ServerRequestLogSaveDb && //关闭保存数据库日志
-		s.Notice == nil { //关闭消息通知
-		return
-	}
-	handlerCtx := router.GetGContext(r.GetCtx())
-
-	//请求时间校验
-	tookTime := gtime.TimestampMilli() - r.EnterTime
-	if err == nil {
-		compareTime := handlerCtx.RouterRule.TimeOut
-		if compareTime == 0 {
-			compareTime = s.Config.ServerRequestTimeout
-		}
-		if tookTime > compareTime {
-			err = NewErrorWithCode(SERVER_DETAIL_TIMEOUT, fmt.Sprintf("接口超时: %d>%d", handlerCtx.RouterRule.TimeOut, tookTime))
-		}
-	}
-
-	var (
-		scheme        = "http"
-		proto         = r.Header.Get("X-Forwarded-Proto")
-		code          = gerror.Code(err)
-		codeDetail    = code.Detail()
-		codeDetailStr string
-	)
-	if r.TLS != nil || gstr.Equal(proto, "https") {
-		scheme = "https"
-	}
-
-	if codeDetail != nil { //存在错误信息
-		codeDetailStr = gstr.Replace(fmt.Sprintf(`%+v`, codeDetail), "\n", " ")
-	} else if err != nil {
-		codeDetailStr = err.Error()
-	}
-
-	contextDetail := fmt.Sprintf(
-		`%d %s "%s://%s%s proxy:%s  %s" %d ms, %s, "%s", "%s", %d, "%s", "%+v"`,
-		r.Response.Status, r.Method, scheme, r.Host, r.URL.String(), handlerCtx.ServerAddr, r.Proto,
-		tookTime, r.GetClientIp(), r.Referer(), r.UserAgent(),
-		code.Code(), code.Message(), codeDetailStr,
-	)
-
-	if err != nil {
-		// 发送提醒信息
-		if code.Code() > 2000 && s.Notice != nil {
-			_ = s.Notice.SendWarnMsg(map[string]interface{}{
-				"log": contextDetail,
-			})
-		}
-
-		//是否打印详细错误信息
-		if s.Config.ServerErrorStack {
-			if stack := gerror.Stack(err); stack != "" {
-				contextDetail += "\nStack:\n" + stack
-			}
-		}
-	}
-
-	if s.Config.ServerRequestLogSaveDb {
-		s.saveLogToDb(handlerCtx, r)
-	}
-
-	if err == nil && s.Config.ServerAccessLogEnabled { //记录成功
-		s.accessLog.Printf(r.Context(), contextDetail)
-	} else if err != nil && s.Config.ServerErrorLogEnabled { //记录异常日志
-		s.errorLog.Printf(r.Context(), contextDetail)
-	}
-}

+ 0 - 125
core/logs/saveDb.go

@@ -1,125 +0,0 @@
-package logs
-
-import (
-	"bp.jydev.jianyu360.cn/BaseService/gateway/common/db"
-	"bp.jydev.jianyu360.cn/BaseService/gateway/core/router"
-	"encoding/json"
-	"github.com/gogf/gf/v2/net/ghttp"
-	"log"
-	"strings"
-	"sync"
-	"time"
-)
-
-var (
-	arr = []map[string]interface{}{}
-	//对map的同步
-	lock = &sync.Mutex{}
-)
-
-//SaveLogTask 定时保存日志
-func (s *serverLog) SaveLogTask() {
-	lock.Lock()
-	if len(arr) >= 1 {
-		tmp := arr
-		arr = make([]map[string]interface{}, 0)
-		go func() {
-			log.Println("timer..save..visit..log", len(tmp))
-			db.MgoLog.SaveBulk("jy_logs", tmp...)
-		}()
-	}
-
-	lock.Unlock()
-	time.AfterFunc(5*time.Minute, s.SaveLogTask)
-}
-
-// saveLogToDb 日志存库
-func (s *serverLog) saveLogToDb(ctx *router.GContext, r *ghttp.Request) {
-	md, _ := json.Marshal(r.Request.Form)
-	m := map[string]interface{}{
-		"ip":        r.GetClientIp(),
-		"refer":     r.Referer(),
-		"mdescribe": string(md),
-		"client":    r.UserAgent(),
-		"os":        getOS(r.UserAgent()),
-		"browse":    getBrowse(r.UserAgent()),
-		"method":    r.Method,
-		"url":       r.URL.String(),
-	}
-	if ctx.Sess != nil {
-		if ctx.Sess.UserId != "" {
-			m["userid"] = ctx.Sess.UserId
-		}
-		if ctx.Sess.NewUid != 0 {
-			m["userid_new"] = ctx.Sess.NewUid
-		}
-		if ctx.Sess.EntId != 0 {
-			m["userid"] = ctx.Sess.EntId
-		}
-	}
-
-	timeNow := time.Now()
-	m["date"] = timeNow.Unix()
-	m["year"] = timeNow.Year()
-	m["month"] = timeNow.Month()
-	m["day"] = timeNow.Day()
-	m["hour"] = timeNow.Hour()
-	m["minutes"] = timeNow.Minute()
-
-	lock.Lock()
-	arr = append(arr, m)
-	if len(arr) >= 500 {
-		tmp := arr
-		arr = make([]map[string]interface{}, 0)
-		go func() {
-			log.Println("save..visit..log", len(tmp), db.MgoLog.SaveBulk("jy_gateway_logs", tmp...))
-		}()
-	}
-	lock.Unlock()
-}
-
-// getOS 获取平台类型
-func getOS(useros string) string {
-	osVersion := "其他"
-	if strings.Contains(useros, "NT 6.0") {
-		osVersion = "Windows Vista/Server 2008"
-	} else if strings.Contains(useros, "NT 5.2") {
-		osVersion = "Windows Server 2003"
-	} else if strings.Contains(useros, "NT 5.1") {
-		osVersion = "Windows XP"
-	} else if strings.Contains(useros, "NT 5") {
-		osVersion = "Windows 2000"
-	} else if strings.Contains(useros, "Mac") {
-		osVersion = "Mac"
-	} else if strings.Contains(useros, "Unix") {
-		osVersion = "UNIX"
-	} else if strings.Contains(useros, "Linux") {
-		osVersion = "Linux"
-	} else if strings.Contains(useros, "SunOS") {
-		osVersion = "SunOS"
-	} else if strings.Contains(useros, "NT 6.3") {
-		osVersion = "Window8"
-	} else if strings.Contains(useros, "NT 6.1") {
-		osVersion = "Window7"
-	} else if strings.Contains(useros, "NT 10.0") {
-		osVersion = "Window10"
-	}
-	return osVersion
-}
-
-// getBrowse 获取浏览器类型
-func getBrowse(userbrowser string) string {
-	browserVersion := "其他"
-	if strings.Contains(userbrowser, "MSIE") {
-		browserVersion = "IE"
-	} else if strings.Contains(userbrowser, "Firefox") {
-		browserVersion = "Firefox"
-	} else if strings.Contains(userbrowser, "Chrome") {
-		browserVersion = "Chrome"
-	} else if strings.Contains(userbrowser, "Safari") {
-		browserVersion = "Safari"
-	} else if strings.Contains(userbrowser, "rv:11.0") {
-		browserVersion = "IE11"
-	}
-	return browserVersion
-}

+ 154 - 49
core/logs/serverLogs.go

@@ -1,73 +1,178 @@
 package logs
 
 import (
-	"github.com/gogf/gf/v2/os/glog"
-	"log"
+	. "bp.jydev.jianyu360.cn/BaseService/gateway/common/gatecode"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/core/logs/internal/notice"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/core/logs/internal/savedb"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/core/router"
+	"encoding/json"
+	"fmt"
+	"github.com/gogf/gf/v2/errors/gerror"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
+	"github.com/gogf/gf/v2/os/gtime"
+	"github.com/gogf/gf/v2/text/gstr"
+	"strings"
+	"time"
 )
 
-type commonLogConfig struct {
-	Path   string //系统日志默认文件默认报错路径。默认为./logs
-	Debug  bool   //是否打印调试信息。默认false
-	Stdout bool   //是否输出到控制台。默认false
-}
+// server 请求日志
+//type serverLog struct {
+//	Notice internal.LogsNotice
+//	SDb    internal.SaveLogsDb
+//}
 
-func initBaseLog(c *commonLogConfig, fileName string) (l *glog.Logger) {
-	l = glog.New()
-	_ = l.SetPath(c.Path)
-	l.SetFile(fileName)
-	l.SetDebug(c.Debug)
-	l.SetStdoutPrint(c.Stdout)
-	return
+type serverLog struct {
+	Notice *notice.Notice
+	SDb    *savedb.DbLogs
 }
 
-// 请求记录配置
-type serverLogConfig struct {
-	*commonLogConfig
-	ServerErrorStack       bool   //当Server捕获到异常时是否记录堆栈信息到日志中。默认为true
-	ServerRequestLogSaveDb bool   //请求日志是否保存至数据库。默认false
-	ServerErrorLogEnabled  bool   //是否记录访问异常日志到日志中。默认为true
-	ServerErrorLogPattern  string //访问异常日志文件格式。默认为"error-{Ymd}.log"
-	ServerAccessLogEnabled bool   //是否记录访问日志。默认为false
-	ServerAccessLogPattern string //记录访问日志文件格式。默认为"access-{Ymd}.log"
-	ServerRequestTimeout   int64
+func (s *serverLog) initNotice(nc *notice.Notice) *serverLog {
+	s.Notice = nc
+	return s
 }
 
-// server 请求日志
-type serverLog struct {
-	accessLog, errorLog *glog.Logger
-	Config              serverLogConfig
-	Notice              *Notice
+func (s *serverLog) initSaveLog(sDB *savedb.DbLogs) *serverLog {
+	s.SDb = sDB
+	go s.SDb.SaveLogTask()
+	return s
 }
 
-func initServerLog(sc serverLogConfig, nc NoticeConfig) *serverLog {
-	accessLog, errorLog := glog.New(), glog.New()
-	_ = accessLog.SetPath(sc.Path)
-	_ = errorLog.SetPath(sc.Path)
+// RecordLogAndNotice 记录请求日志及提醒
+func (s *serverLog) RecordLogAndNotice(r *ghttp.Request, err error) {
 
-	accessLog.SetFile(sc.ServerAccessLogPattern)
-	errorLog.SetFile(sc.ServerErrorLogPattern)
+	handlerCtx := router.GetGContext(r.GetCtx())
 
-	accessLog.SetDebug(sc.Debug)
-	errorLog.SetDebug(sc.Debug)
+	//请求时间校验
+	tookTime := gtime.TimestampMilli() - r.EnterTime
+	if err == nil {
+		compareTime := handlerCtx.RouterRule.TimeOut
+		if compareTime == 0 {
+			compareTime = 2000
+		}
+		if tookTime > compareTime {
+			err = NewErrorWithCode(SERVER_DETAIL_TIMEOUT, fmt.Sprintf("接口超时: %d>%d", handlerCtx.RouterRule.TimeOut, tookTime))
+		}
+	}
+
+	var (
+		scheme        = "http"
+		proto         = r.Header.Get("X-Forwarded-Proto")
+		code          = gerror.Code(err)
+		codeDetail    = code.Detail()
+		codeDetailStr string
+	)
+	if r.TLS != nil || gstr.Equal(proto, "https") {
+		scheme = "https"
+	}
+
+	if codeDetail != nil { //存在错误信息
+		codeDetailStr = gstr.Replace(fmt.Sprintf(`%+v`, codeDetail), "\n", " ")
+	} else if err != nil {
+		codeDetailStr = err.Error()
+	}
 
-	accessLog.SetStdoutPrint(sc.Stdout)
-	errorLog.SetStdoutPrint(sc.Stdout)
+	contextDetail := fmt.Sprintf(
+		`%d %s "%s://%s%s proxy:%s  %s" %d ms, %s, "%s", "%s", %d, "%s", "%+v"`,
+		r.Response.Status, r.Method, scheme, r.Host, r.URL.String(), handlerCtx.ServerAddr, r.Proto,
+		tookTime, r.GetClientIp(), r.Referer(), r.UserAgent(),
+		code.Code(), code.Message(), codeDetailStr,
+	)
 
-	notice, err := newNotice(nc)
 	if err != nil {
-		log.Println("GNotice nsq通知初始化异常", err)
+		// 发送提醒信息
+		if code.Code() > 2000 && s.Notice != nil {
+			_ = s.Notice.SendWarnMsg(map[string]interface{}{
+				"log": contextDetail,
+			})
+		}
+		g.Log("reqLog").Debugf(r.Context(), gerror.Stack(err))
 	}
 
-	slog := &serverLog{
-		accessLog: accessLog,
-		errorLog:  errorLog,
-		Config:    sc,
-		Notice:    notice,
+	if s.SDb != nil {
+		s.SDb.AddLogToTask(PaseReq(handlerCtx, r))
 	}
 
-	if sc.ServerRequestLogSaveDb {
-		go slog.SaveLogTask()
+	//日志输出
+	g.Log("reqLog").Printf(r.Context(), contextDetail)
+}
+
+func PaseReq(ctx *router.GContext, r *ghttp.Request) map[string]interface{} {
+	md, _ := json.Marshal(r.Request.Form)
+	m := map[string]interface{}{
+		"ip":        r.GetClientIp(),
+		"refer":     r.Referer(),
+		"mdescribe": string(md),
+		"client":    r.UserAgent(),
+		"os":        getOS(r.UserAgent()),
+		"browse":    getBrowse(r.UserAgent()),
+		"method":    r.Method,
+		"url":       r.URL.String(),
+	}
+	if ctx.Sess != nil {
+		if ctx.Sess.UserId != "" {
+			m["userid"] = ctx.Sess.UserId
+		}
+		if ctx.Sess.NewUid != 0 {
+			m["userid_new"] = ctx.Sess.NewUid
+		}
+		if ctx.Sess.EntId != 0 {
+			m["userid"] = ctx.Sess.EntId
+		}
 	}
 
-	return slog
+	timeNow := time.Now()
+	m["date"] = timeNow.Unix()
+	m["year"] = timeNow.Year()
+	m["month"] = timeNow.Month()
+	m["day"] = timeNow.Day()
+	m["hour"] = timeNow.Hour()
+	m["minutes"] = timeNow.Minute()
+	return m
+}
+
+// getOS 获取平台类型
+func getOS(useros string) string {
+	osVersion := "其他"
+	if strings.Contains(useros, "NT 6.0") {
+		osVersion = "Windows Vista/Server 2008"
+	} else if strings.Contains(useros, "NT 5.2") {
+		osVersion = "Windows Server 2003"
+	} else if strings.Contains(useros, "NT 5.1") {
+		osVersion = "Windows XP"
+	} else if strings.Contains(useros, "NT 5") {
+		osVersion = "Windows 2000"
+	} else if strings.Contains(useros, "Mac") {
+		osVersion = "Mac"
+	} else if strings.Contains(useros, "Unix") {
+		osVersion = "UNIX"
+	} else if strings.Contains(useros, "Linux") {
+		osVersion = "Linux"
+	} else if strings.Contains(useros, "SunOS") {
+		osVersion = "SunOS"
+	} else if strings.Contains(useros, "NT 6.3") {
+		osVersion = "Window8"
+	} else if strings.Contains(useros, "NT 6.1") {
+		osVersion = "Window7"
+	} else if strings.Contains(useros, "NT 10.0") {
+		osVersion = "Window10"
+	}
+	return osVersion
+}
+
+// getBrowse 获取浏览器类型
+func getBrowse(userbrowser string) string {
+	browserVersion := "其他"
+	if strings.Contains(userbrowser, "MSIE") {
+		browserVersion = "IE"
+	} else if strings.Contains(userbrowser, "Firefox") {
+		browserVersion = "Firefox"
+	} else if strings.Contains(userbrowser, "Chrome") {
+		browserVersion = "Chrome"
+	} else if strings.Contains(userbrowser, "Safari") {
+		browserVersion = "Safari"
+	} else if strings.Contains(userbrowser, "rv:11.0") {
+		browserVersion = "IE11"
+	}
+	return browserVersion
 }

+ 96 - 0
core/proxy/filterPoly/blacklist.go

@@ -0,0 +1,96 @@
+package filterPoly
+
+import (
+	"app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mongodb"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/common/db"
+	"fmt"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/os/gctx"
+	"github.com/gogf/gf/v2/util/gconv"
+	"strings"
+	"time"
+)
+
+//syncBlackWhiteList 同步永久黑名单白名单
+func syncBlackWhiteList() {
+	mgSess := db.MgoJy.GetMgoConn()
+	defer func() {
+		db.MgoJy.DestoryMongoConn(mgSess)
+	}()
+	g.Log().Infof(gctx.New(), "开始同步黑白名单")
+	addBlackNum, addWhiteNum := 0, 0
+	// 从用户表同步黑名单
+	iter := mgSess.DB("qfw").C("user").Find(map[string]interface{}{"i_ban": 1}).Select(map[string]interface{}{"_id": 1}).Iter()
+	uData := map[string]interface{}{}
+	for iter.Next(&uData) {
+		uid := mongodb.BsonIdToSId(uData["_id"])
+		blackKey := fmt.Sprintf(BlackListKey, uid)
+		if !AllRedis.Exists(blackKey) {
+			AllRedis.Set(blackKey, 1, -1)
+			g.Log().Infof(gctx.New(), "同步黑名单-加入%s", uid)
+			addBlackNum++
+		}
+	}
+
+	// 从黑名单白名单列表同步
+	res := db.GateWatMySql.Query(`SELECT * FROM front_proxy_blackWhite Where status !=0`)
+	if res != nil && len(*res) > 0 {
+		for _, data := range *res {
+			value := gconv.String(data["value"])
+			status := gconv.Int(data["status"])
+			if key := fmt.Sprintf(WhiteListKey, value); status == 1 && !AllRedis.Exists(key) {
+				addWhiteNum++
+				AllRedis.Set(key, 1, -1)
+				g.Log().Infof(gctx.New(), "同步白名单-加入%s", value)
+			} else if key := fmt.Sprintf(BlackListKey, value); status == -1 && !AllRedis.Exists(key) {
+				addBlackNum++
+				AllRedis.Set(key, 1, -1)
+				g.Log().Infof(gctx.New(), "同步黑名单-加入%s", value)
+			}
+		}
+	}
+
+	g.Log().Infof(gctx.New(), "同步结束,共新增%d个黑名单,%d个白名单", addBlackNum, addWhiteNum)
+}
+
+//checkBackList 查询是否在白名单中
+func (a *ReqFilterPoly) checkWhiteList(k string) bool {
+	return AllRedis.Get(fmt.Sprintf(WhiteListKey, k)) != nil
+}
+
+//checkBackList 查询是否在黑名单中
+func (a *ReqFilterPoly) checkBackList(k string) bool {
+	return AllRedis.Get(fmt.Sprintf(BlackListKey, k)) != nil
+}
+
+//addBackList 加入黑名单
+func (a *ReqFilterPoly) addBackList(k, cause string) {
+	timeLong := a.Rule.TempBlockTime
+	blockTimes := a.Rule.IdMaxBlockTimes
+	isIp := strings.Index(k, ".") > -1
+	if isIp {
+		blockTimes = a.Rule.IpMaxBlockTimes
+	}
+	//统计临时黑名单次数,超过次数进入永久黑名单
+	if !RateOk(fmt.Sprintf(BlackTimesKey, k), blockTimes[1], time.Duration(blockTimes[0])*time.Second) {
+		g.Log().Infof(gctx.New(), "%s 多次进入临时黑名单,现转移至永久黑名单", k)
+		timeLong = -1
+	}
+	AllRedis.Set(fmt.Sprintf(BlackListKey, k), 1, timeLong)
+	go func() {
+		//保存黑名单记录
+		db.MgoJy.Save("anti_black", map[string]interface{}{
+			"s_userid":    common.If(isIp, "", k),
+			"s_ip":        common.If(isIp, k, ""),
+			"l_comeTime":  time.Now().Unix(),
+			"l_blackLong": timeLong,
+			"s_platform":  "gateway",
+			"s_alias":     a.Rule.Alias,
+			"s_cause":     cause,
+		})
+		if !isIp && timeLong == -1 {
+			db.MgoJy.UpdateById("user", k, map[string]interface{}{"$set": map[string]interface{}{"i_ban": 1}})
+		}
+	}()
+}

+ 113 - 0
core/proxy/filterPoly/filterPoly.go

@@ -0,0 +1,113 @@
+package filterPoly
+
+import (
+	"fmt"
+	"github.com/gogf/gf/v2/frame/g"
+)
+
+//ReqFilterPoly 代理应用下每一组action
+type ReqFilterPoly struct {
+	Code string   //别名【用于验证码分组统计】
+	Rule *ReqRule //action分组rule【需要配合Alias使用】
+}
+
+/*
+IdFilter 用户请求频率拦截
+param
+id:用户session值
+return
+statusCode:0通过 -1黑名单 1被拦截 2出现验证码
+key
+*/
+func (a *ReqFilterPoly) IdFilter(ctx g.Ctx, id string) (statusCode int, key string) {
+	//是否开启白名单(开启白名单且在白名单中直接跳过)
+	if a.Rule.CheckWhite && a.checkWhiteList(id) {
+		return 0, id
+	}
+	//检测是否在黑名单中
+	if a.Rule.CheckBlack && a.checkBackList(id) {
+		return -1, id
+	}
+	//检测是否处于验证码校验状态
+	if a.IsWaitVerify(id) {
+		return 2, id
+	}
+
+	// 阈值校验
+	for i, v := range a.Rule.IdThreshold {
+		if i != (len(a.Rule.IdThreshold) - 1) {
+			if !a.rateCheckOk(id, v) && statusCode != 2 {
+				statusCode = 2
+			}
+		} else {
+			if !a.finalRateCheckOk(id, v, "id") && statusCode != 2 {
+				statusCode = 2
+			}
+		}
+	}
+
+	// 设置验证码状态
+	if statusCode == 2 {
+		g.Log().Infof(ctx, "%s 超出验证码请求阈值,弹出验证码", id)
+		go a.SetWaitVerify(id, a.Rule.IdTimeRange)
+	}
+
+	//最大值校验
+	if !a.rateCheckOk(id, a.Rule.IdMax) {
+		g.Log().Infof(ctx, "%s 超出最大请求限制,加入临时黑名单", id)
+		a.addBackList(id, "exceed IdMax")
+		AllRedis.Del(fmt.Sprintf(RateDataKey, a.Rule.Alias, a.Rule.IdMax[0], id))
+		return -1, id
+	}
+	return statusCode, id
+}
+
+/*
+IpFilter ip请求频率拦截
+param
+ip:请求ip地址
+return
+statusCode:0通过 -1黑名单 1被拦截 2出现验证码
+*/
+func (a *ReqFilterPoly) IpFilter(ctx g.Ctx, ip string) (statusCode int, key string) {
+	if !a.Rule.BCheckIp {
+		return 0, ip
+	}
+	//是否开启白名单(开启白名单且在白名单中直接跳过)
+	if a.Rule.CheckWhite && a.checkWhiteList(ip) {
+		return 0, ip
+	}
+	//检测是否在黑名单中
+	if a.Rule.CheckBlack && a.checkBackList(ip) {
+		return -1, ip
+	}
+
+	//检测是否处于验证码校验状态
+	if a.IsWaitVerify(ip) {
+		return 2, ip
+	}
+
+	for i, v := range a.Rule.IpThreshold {
+		if i != (len(a.Rule.IdThreshold) - 1) {
+			if !a.rateCheckOk(ip, v) && statusCode != 2 {
+				statusCode = 2
+			}
+		} else {
+			if !a.finalRateCheckOk(ip, v, "ip") && statusCode != 2 {
+				statusCode = 2
+			}
+		}
+	}
+	if statusCode == 2 {
+		g.Log().Infof(ctx, "%s 超出验证码请求阈值,弹出验证码", ip)
+		go a.SetWaitVerify(ip, a.Rule.IdTimeRange)
+	}
+	//最大值校验
+	if !a.rateCheckOk(ip, a.Rule.IpMax) {
+		g.Log().Infof(ctx, "%s 超出最大请求限制,加入临时黑名单", ip)
+		a.addBackList(ip, "exceed IdMax")
+		AllRedis.Del(fmt.Sprintf(RateDataKey, a.Rule.Alias, a.Rule.IpMax[0], ip))
+		return -1, ip
+	}
+	return statusCode, ip
+}

+ 73 - 0
core/proxy/filterPoly/manager.go

@@ -0,0 +1,73 @@
+package filterPoly
+
+import (
+	"bp.jydev.jianyu360.cn/BaseService/gateway/common"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/common/db"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/os/gctx"
+	"github.com/gogf/gf/v2/util/gconv"
+)
+
+const DefaultRule = "default"
+
+type Manager struct {
+	data map[string]*ReqFilterPoly
+}
+
+func InitFilterPolyManager() *Manager {
+	manager := &Manager{
+		data: map[string]*ReqFilterPoly{},
+	}
+	res := db.GateWatMySql.Query(`SELECT * FROM front_proxy_ploy`)
+	if res != nil && len(*res) > 0 {
+		for _, rule := range *res {
+			code := gconv.String(rule["code"])
+			var reqRule ReqRule
+			if err := gconv.Scan(rule["ploy_rule"], &reqRule); err != nil {
+				g.Log().Errorf(gctx.New(), "格式化%s策略异常 %v", code, err)
+				continue
+			}
+			manager.AddRule(code, &reqRule)
+		}
+	}
+	manager.initCache()
+	syncBlackWhiteList()
+	return manager
+}
+
+func (receiver *Manager) initCache() {
+	var conf common.RedisConf
+	ctx := gctx.New()
+	if err := g.Cfg().MustGet(ctx, "databases.polyredis").Scan(&conf); err != nil {
+		g.Log().Errorf(ctx, "redis初始化异常", err)
+	}
+	AllRedis = common.InitRedis(conf)
+}
+
+// AddRule 添加规则
+func (receiver *Manager) AddRule(code string, rule *ReqRule) {
+	receiver.data[code] = &ReqFilterPoly{
+		Code: code,
+		Rule: rule,
+	}
+}
+
+// GetRule 获取规则
+func (receiver *Manager) GetRule(code string) (rule *ReqFilterPoly) {
+	var ok bool
+	rule, ok = receiver.data[code]
+	if !ok {
+		rule, ok = receiver.data[DefaultRule]
+		if !ok {
+			return &ReqFilterPoly{
+				Code: DefaultRule,
+				Rule: &ReqRule{
+					Alias:      "",
+					CheckWhite: false,
+					CheckBlack: false,
+				},
+			}
+		}
+	}
+	return rule
+}

+ 22 - 0
core/proxy/filterPoly/reqRule.go

@@ -0,0 +1,22 @@
+package filterPoly
+
+//ReqRule 请求规则
+type ReqRule struct {
+	Alias           string    `json:"alias"`           //别名
+	CheckWhite      bool      `json:"checkWhite"`      //白名单是否启用
+	CheckBlack      bool      `json:"checkBlack"`      //黑名单是否启用
+	BCheckId        bool      `json:"checkIdBlock"`    //开启用户名单验证
+	IdThreshold     [][]int64 `json:"idThreshold"`     //用户出验证码的阀值
+	IdTimeRange     int64     `json:"idTimeRange"`     //验证码等待时间
+	IdFrep          int64     `json:"idFreq"`          //超过阀值后,按频率出验证码
+	IdMax           []int64   `json:"idMax"`           //超过最大值,进临时黑名单
+	IdMaxBlockTimes []int64   `json:"idMaxBlockTimes"` //最大临时黑名单次数,进永久黑名单
+	BCheckIp        bool      `json:"checkIpBlock"`    //开启ip黑名单验证
+	IpThreshold     [][]int64 `json:"ipThreshold"`     //ip出验证码的阀值
+	IpTimeRange     int64     `json:"ipTimeRange"`     //验证码等待时间
+	IpFrep          int64     `json:"ipFreq"`          //超过阀值后,按频率出验证码
+	IpMax           []int64   `json:"ipMax"`           //超过最大值,进临时黑名单
+	IpMaxBlockTimes []int64   `json:"ipMaxBlockTimes"` //最大临时黑名单次数,进永久黑名单
+	VcodeErrorTimes []int64   `json:"vcodeErrorTimes"` //验证码错误次数,进临时黑名单
+	TempBlockTime   int64     `json:"tempBlockTime"`   //临时黑名单时间
+}

+ 30 - 0
core/proxy/filterPoly/vars.go

@@ -0,0 +1,30 @@
+package filterPoly
+
+import (
+	"bp.jydev.jianyu360.cn/BaseService/gateway/common"
+)
+
+const (
+	//BlackListKey 黑名单 black_[id/ip]
+	BlackListKey = "black_%s"
+	//WhiteListKey 白名单 black_[id]
+	WhiteListKey = "white_%s"
+
+	//RateDataKey 访问频率 rate_[alias]_[ladder]_[id/ip]
+	RateDataKey = "rate_%s_%d_%s"
+	//BlackTimesKey 临时黑名单次数 blackTimes_[id/ip]
+	BlackTimesKey = "blackTimes_%s"
+	//ExceedTimeKey 超阀次数记录 exceedTime_[alias]_[id/ip] 超过次阈值后每超过x次出现验证码
+	ExceedTimeKey = "exceedTime_%s_%s"
+
+	//VerifyRedisKey 验证码存储 verify_[key]
+	VerifyRedisKey = "verify_%s"
+	//WaitVerifyKey 验证码验证状态 exceedTime_[id/ip]
+	WaitVerifyKey = "waitVerify_%s"
+	//ErrVerifyKey 验证码错误统计
+	ErrVerifyKey = "errVerify_%s"
+)
+
+var (
+	AllRedis *common.Redis
+)

+ 97 - 0
core/proxy/filterPoly/verify.go

@@ -0,0 +1,97 @@
+package filterPoly
+
+import (
+	"fmt"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/os/gctx"
+	"time"
+)
+
+//IsWaitVerify 是否处于等待验证码校验
+func (a *ReqFilterPoly) IsWaitVerify(k string) bool {
+	return AllRedis.Get(fmt.Sprintf(WaitVerifyKey, k)) != nil
+}
+
+//SetWaitVerify 设置验证码
+func (a *ReqFilterPoly) SetWaitVerify(k string, t int64) {
+	AllRedis.Set(fmt.Sprintf(WaitVerifyKey, k), 1, t)
+}
+
+//ClearWaitVerify 清除验证码状态
+func (a *ReqFilterPoly) ClearWaitVerify(k string) {
+	AllRedis.Del(fmt.Sprintf(WaitVerifyKey, k))
+	AllRedis.Del(fmt.Sprintf(VerifyRedisKey, k))
+}
+
+//VerifyErrDetail 验证码输入出错统计
+func (a *ReqFilterPoly) VerifyErrDetail(k string) {
+	if len(a.Rule.VcodeErrorTimes) != 2 {
+		return
+	}
+	if !RateOk(fmt.Sprintf(ErrVerifyKey, k), a.Rule.VcodeErrorTimes[1], time.Duration(a.Rule.VcodeErrorTimes[0])*time.Second) {
+		g.Log().Infof(gctx.New(), "%s 验证码多次验证出错,加入临时黑名单", k)
+		AllRedis.Del(fmt.Sprintf(ErrVerifyKey, k))
+		a.addBackList(k, "exceed VerifyKey")
+	}
+}
+
+//rateCheckOk 访问频率统计
+func (a *ReqFilterPoly) rateCheckOk(k string, r []int64) bool {
+	if len(r) != 2 {
+		return true
+	}
+	ladder := r[0] //时间阶梯
+	timers := r[1] //访问次数
+
+	key := fmt.Sprintf(RateDataKey, a.Rule.Alias, ladder, k)
+	flag := RateOk(key, timers, time.Duration(ladder)*time.Second)
+	if !flag {
+		AllRedis.Del(key)
+	}
+	return flag
+}
+
+// finalRateCheckOk 超出最大阈值,每n次弹出验证码
+func (a *ReqFilterPoly) finalRateCheckOk(k string, r []int64, sign string) bool {
+	if len(r) != 2 {
+		return true
+	}
+	ladder := r[0] //时间阶梯
+	timers := r[1] //访问次数
+	key := fmt.Sprintf(RateDataKey, a.Rule.Alias, ladder, k)
+	flag := RateOk(key, timers, time.Duration(ladder)*time.Second)
+
+	if !flag { //超过阀值最大限制 根据请求频率显示验证码
+		if sign == "id" {
+			return !a.ShowCodeById(k)
+		}
+		return !a.ShowCodeByIp(k)
+	}
+	return flag
+}
+
+//RateOk 次数统计
+func RateOk(keys string, count int64, duration time.Duration) bool {
+	if AllRedis.IncrTimeout(keys, duration) >= count {
+		return false
+	}
+	return true
+}
+
+//ShowCodeById 超出最大阈值,每n次弹出验证码
+func (a *ReqFilterPoly) ShowCodeById(k string) bool {
+	c := AllRedis.IncrTimeout(fmt.Sprintf(ExceedTimeKey, a.Rule.Alias, k), time.Duration(a.Rule.IdTimeRange)*time.Second) //超出阀值请求次数
+	if (c-1)%a.Rule.IdFrep == 0 {
+		return true
+	}
+	return false
+}
+
+//ShowCodeByIp 超出最大阈值,每n次弹出验证码
+func (a *ReqFilterPoly) ShowCodeByIp(k string) bool {
+	c := AllRedis.IncrTimeout(fmt.Sprintf(ExceedTimeKey, a.Rule.Alias, k), time.Duration(a.Rule.IpTimeRange)*time.Second) //超出阀值请求次数
+	if (c-1)%a.Rule.IpFrep == 0 {
+		return true
+	}
+	return false
+}

+ 87 - 0
core/proxy/filterPoly/verifyDetail.go

@@ -0,0 +1,87 @@
+package filterPoly
+
+import (
+	"bp.jydev.jianyu360.cn/BaseService/gateway/common"
+	"encoding/json"
+	"fmt"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/util/gconv"
+	"math"
+	"net/http"
+	"strings"
+)
+
+//VerifyHandle 验证码处理
+func (a *ReqFilterPoly) VerifyHandle(req *http.Request, key string) map[string]interface{} {
+	//_ = req.ParseForm()
+	common.CopyReqAndFormData(req, false)
+	vc := req.Form.Get("antiVerifyCheck")
+	vKey := fmt.Sprintf(VerifyRedisKey, key)
+	result := map[string]interface{}{
+		"error_code": 0,
+	}
+	if vc != "" {
+		ok := func() bool {
+			va := AllRedis.GetBytes(vKey)
+			if va == nil {
+				return true
+			}
+			var result = [][]int{}
+			if err := json.Unmarshal(va, &result); err != nil {
+				g.Log().Errorf(req.Context(), "%s Unmarshal 出错 %v", key, err)
+				return false
+			}
+			vcArr := strings.Split(vc, ";")
+			if len(vcArr) != len(result) {
+				g.Log().Infof(req.Context(), "用户%s验证码校验失败 选择数量不足 \n正确坐标:%v\n输入坐标%v ", key, result, vcArr)
+				return false
+			}
+
+			imgWidth := gconv.Int(req.Form.Get("imgw")) //app端图片宽度改变
+			for index, pos := range vcArr {
+				p := strings.Split(pos, ",")
+				if len(p) != 2 {
+					return false
+				}
+				px := gconv.Int(p[0])
+				py := gconv.Int(p[1])
+				if imgWidth != 0 { //320 100
+					px = px * 320 / imgWidth
+					py = py * 320 / imgWidth
+				}
+
+				if d := Distance(px, py, result[index][0], result[index][1]); d > 15 {
+					g.Log().Infof(req.Context(), "用户%s验证码校验失败 w:%d 第(%d)校验结果%f\n正确坐标:%v\n输入坐标%v ", key, imgWidth, index, d, result, vcArr)
+					return false
+				}
+			}
+			g.Log().Infof(req.Context(), "用户%s验证码校验通过", key)
+			return true
+		}()
+		if ok {
+			a.ClearWaitVerify(key)
+			result["antiVerify"] = 1
+		} else {
+			result["antiVerify"] = -1
+			//统计验证码错误次数
+			go a.VerifyErrDetail(key)
+		}
+
+	} else {
+		result["antiVerify"] = -1
+	}
+	if result["antiVerify"] == -1 {
+		checkVerify := <-vCodeChan
+		AllRedis.Set(vKey, checkVerify.CheckArr, 60*5)
+		result["imgData"] = checkVerify.imgBase64
+		result["textVerify"] = checkVerify.Key
+	}
+	return result
+}
+
+func Distance(sx, sy, dx, dy int) float64 {
+	x := math.Abs(float64(sx - dx))
+	y := math.Abs(float64(sy - dy))
+	h := math.Hypot(x, y)
+	return h
+}

+ 174 - 0
core/proxy/filterPoly/wordsclick.go

@@ -0,0 +1,174 @@
+package filterPoly
+
+import (
+	"bytes"
+	"encoding/base64"
+	"github.com/golang/freetype"
+	"image"
+	"image/draw"
+	"image/jpeg"
+	"image/png"
+	"io/ioutil"
+	"math/rand"
+	"os"
+	"time"
+)
+
+type WordClickVerify struct {
+	Words     []rune  //生成图片的文字
+	Key       string  //点选字
+	ShowIndex []int   //点选顺序
+	imgBase64 string  //图片
+	CheckArr  [][]int //点选核对信息
+}
+type FontDot struct {
+	Dx   int
+	Dy   int
+	Text string
+}
+
+const (
+	width    = 320 //验证码宽度
+	height   = 100 //验证码高度
+	fontDPI  = 72  //屏幕每英寸的分辨率
+	fontSize = 24  //字体大小
+
+	bgImgFileDir  = "./resources/verify/imageBg/"
+	fontBgFileDir = "./resources/verify/fontBg/"
+	fontFileDir   = "./resources/verify/fontType/"
+
+	verifyTextLen  = 7 //验证码文字长度
+	verifyCheckNum = 3 //验证码点击选择数量
+)
+
+var (
+	bgImgList   []image.Image
+	fontImgList []image.Image
+	fontList    [][]byte
+	vCodeChan   = make(chan *WordClickVerify, 30)
+)
+
+func init() {
+	// 背景图片读入内存
+	bg_list, _ := ioutil.ReadDir(bgImgFileDir)
+	for _, val := range bg_list {
+		fileData, _ := os.Open(bgImgFileDir + val.Name())
+		imgData, _ := jpeg.Decode(fileData)
+		bgImgList = append(bgImgList, imgData)
+		_ = fileData.Close()
+	}
+	//字体背景读入内存
+	font_list, _ := ioutil.ReadDir(fontBgFileDir)
+	for _, val := range font_list {
+		fontfileData, _ := os.Open(fontBgFileDir + val.Name())
+		fontimgData, _ := jpeg.Decode(fontfileData)
+		fontImgList = append(fontImgList, fontimgData)
+		_ = fontfileData.Close()
+	}
+	// 字体库读入内存
+	fontInfoList, _ := ioutil.ReadDir(fontFileDir)
+	for _, val := range fontInfoList {
+		fontBytes, _ := ioutil.ReadFile(fontFileDir + val.Name())
+		fontList = append(fontList, fontBytes)
+	}
+	go func() {
+		for {
+			vObj := CreateVerify()
+			if vObj == nil {
+				continue
+			}
+			vCodeChan <- vObj
+		}
+	}()
+}
+
+//CreateVerify 生成验证码
+func CreateVerify() (verify *WordClickVerify) {
+	verify = &WordClickVerify{}
+	verify.Words = []rune(GenRandZh(verifyTextLen)) //验证码文字
+	//verify.Words = []rune("一弍褕呚騍鯽三") //琖弍褕呚騍鯽鳑
+	rand.Seed(time.Now().UnixNano())
+	//文字背景
+	fontBg := image.NewNRGBA(image.Rect(0, 0, width, height))      //字体背景
+	img := fontImgList[rand.Intn(len(fontImgList))].(*image.YCbCr) //获取背景图片
+	draw.Draw(fontBg, fontBg.Bounds(), img, image.ZP, draw.Src)
+	//文字图层
+	wordCanvas := image.NewNRGBA(image.Rect(0, 0, width, height))
+	fontBytes := fontList[rand.Intn(len(fontList))]
+	font, err := freetype.ParseFont(fontBytes)
+	if err != nil {
+		return nil
+	}
+
+	c := freetype.NewContext()
+	c.SetDPI(fontDPI)
+	c.SetClip(wordCanvas.Bounds())
+	c.SetDst(wordCanvas)
+	c.SetSrc(image.Black)
+
+	allDots := make(map[int]FontDot) //各个文字点位置
+	for i := 0; i < len(verify.Words); i++ {
+		c.SetFontSize(fontSize)
+		c.SetFont(font)
+		w := (width - fontSize) / len(verify.Words)
+		x := i*w + rand.Intn(w-fontSize)
+		y := rand.Intn(height-fontSize) + fontSize
+		pt := freetype.Pt(x, y) // 字出现的位置
+		text := string(verify.Words[i])
+		if _, err := c.DrawString(text, pt); err != nil {
+			return nil
+		}
+		allDots[i] = FontDot{x + fontSize/2, y - fontSize/2, text}
+	}
+	//确定点击顺序和点击文字
+	verify.CheckArr = [][]int{}
+	for _, i := range rand.Perm(len(allDots)) {
+		dot := allDots[i]
+		if len(verify.CheckArr) >= verifyCheckNum {
+			break
+		}
+		verify.Key += dot.Text
+		verify.ShowIndex = append(verify.ShowIndex, i)
+		verify.CheckArr = append(verify.CheckArr, []int{dot.Dx, dot.Dy})
+	}
+	imgRes := bgImgList[rand.Intn(len(bgImgList))]
+	verifyBg := image.NewNRGBA(image.Rect(0, 0, width, height))
+	draw.Draw(verifyBg, verifyBg.Bounds(), imgRes, image.ZP, draw.Src)
+	//合并图层
+	draw.DrawMask(verifyBg, verifyBg.Bounds(), fontBg, image.ZP, wordCanvas, image.ZP, draw.Over)
+	buf := new(bytes.Buffer)
+	_ = png.Encode(buf, verifyBg)
+	verify.imgBase64 = base64.StdEncoding.EncodeToString(buf.Bytes())
+	return
+}
+
+// GenRandZh 获取验证码随机文字
+func GenRandZh(leng int) string {
+	randInt := generateRandomNumber(19968, 40869, leng)
+	str := ""
+	for _, v := range randInt {
+		str += string(v)
+	}
+	return str
+}
+
+//generateRandomNumber 从start-end中获取count个随机数
+func generateRandomNumber(start int, end int, count int) []int {
+	nums := make([]int, 0)
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	for len(nums) < count {
+		num := r.Intn((end - start)) + start
+		//查重
+		exist := false
+		for _, v := range nums {
+			if v == num {
+				exist = true
+				break
+			}
+		}
+		if !exist {
+			nums = append(nums, num)
+		}
+	}
+	return nums
+}

+ 1 - 1
core/proxy/middleware/errorHandler.go

@@ -27,5 +27,5 @@ func ErrorHandler(r *ghttp.Request) {
 		})
 	}
 	// 记录到日志文件并通知
-	go logs.GReq.RecordLogAndNotice(r, err)
+	go logs.GateWayServerLog.RecordLogAndNotice(r, err)
 }

+ 41 - 0
core/proxy/middleware/spiderPolyHandler.go

@@ -0,0 +1,41 @@
+package middleware
+
+import (
+	. "bp.jydev.jianyu360.cn/BaseService/gateway/common/gatecode"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/core/proxy/filterPoly"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/core/router"
+	"fmt"
+	"github.com/gogf/gf/v2/net/ghttp"
+)
+
+var filterPolyManager *filterPoly.Manager
+
+func InitFilterPolyManager() {
+	filterPolyManager = filterPoly.InitFilterPolyManager()
+}
+
+func FilterPolyHandler(r *ghttp.Request) {
+	ctx := r.GetCtx()
+	gCtx := router.GetGContext(ctx)
+	//获取策略
+	poly := filterPolyManager.GetRule(gCtx.RouterRule.LimitPloy)
+	status, key := func() (int, string) {
+		if gCtx.Sess.UserId != "" && poly.Rule.BCheckId { //id请求频率校验
+			return poly.IdFilter(ctx, gCtx.Sess.UserId)
+		} else { //ip请求频率校验
+			return poly.IpFilter(ctx, r.GetClientIp())
+		}
+	}()
+	//是否开启黑名单
+	if status == -1 { //黑名单 不响应
+		r.SetError(NewErrorWithCode(OTHER_ERR_OFTEN, fmt.Sprintf("请求频繁限制")))
+		r.Response.ResponseWriter.WriteHeader(403)
+		return
+	}
+	if status == 2 { //处理验证码逻辑
+		rData := poly.VerifyHandle(r.Request, key)
+		r.Response.WriteJsonExit(rData)
+		return
+	}
+	r.Middleware.Next()
+}

+ 6 - 9
core/proxy/proxyServer.go

@@ -1,21 +1,18 @@
 package proxy
 
 import (
-	"fmt"
-	"log"
-	"net/http"
-
 	. "bp.jydev.jianyu360.cn/BaseService/gateway/common/gatecode"
-	"bp.jydev.jianyu360.cn/BaseService/gateway/core/logs"
 	"bp.jydev.jianyu360.cn/BaseService/gateway/core/node"
 	"bp.jydev.jianyu360.cn/BaseService/gateway/core/proxy/broker"
 	"bp.jydev.jianyu360.cn/BaseService/gateway/core/proxy/middleware"
 	"bp.jydev.jianyu360.cn/BaseService/gateway/core/proxy/proxyClient"
 	"bp.jydev.jianyu360.cn/BaseService/gateway/core/router"
+	"fmt"
 	"github.com/gogf/gf/v2/frame/g"
 	"github.com/gogf/gf/v2/net/ghttp"
 	"github.com/gogf/gf/v2/os/gcfg"
 	"github.com/gogf/gf/v2/os/gctx"
+	"net/http"
 )
 
 var bManager = broker.InitBroker()
@@ -34,12 +31,12 @@ func InitGateWayServer() *ghttp.Server {
 	var err error
 	routerManager, err = router.InitRouterManager()
 	if err != nil {
-		logs.GInfo.Error(initCtx, err)
+		g.Log().Error(initCtx, err)
 	}
 
 	gateWayServer := g.Server()
 	//关闭系统自带请求日志
-	gateWayServer.SetLogger(logs.GInfo)
+	gateWayServer.SetLogger(g.Log())
 	gateWayServer.SetErrorLogEnabled(false)
 
 	//注册中间件
@@ -48,7 +45,8 @@ func InitGateWayServer() *ghttp.Server {
 		r.SetError(routerManager.InfusionContext(r)) //context注入全局信息
 		r.Middleware.Next()
 	})
-	gateWayServer.Use(middleware.FilterHandler) //权限过滤
+	gateWayServer.Use(middleware.FilterPolyHandler) //反爬虫策略
+	gateWayServer.Use(middleware.FilterHandler)     //权限过滤
 
 	//加载代理客户端
 	proxyClient.ReLoadClient()
@@ -68,7 +66,6 @@ var proxyHandler = func(r *ghttp.Request) {
 	// 请求重试,防止某个服务中断不可用,导致接口不可用。
 	for i := 0; i < errTryTime; i++ {
 		// 根据负载规则获取服务地址
-		log.Println(r.GetClientIp(), gCtx.RouterRule.MiddleCode)
 		proxyAddr, err := bManager.GetServerAddr(gCtx.RouterRule.MiddleCode, r.GetClientIp())
 		if err != nil {
 			r.SetError(err)

+ 6 - 5
core/router/manager.go

@@ -1,15 +1,14 @@
 package router
 
 import (
-	"fmt"
-	"log"
-	"regexp"
-
 	"bp.jydev.jianyu360.cn/BaseService/gateway/common/db"
 	"bp.jydev.jianyu360.cn/BaseService/gateway/common/enum"
 	. "bp.jydev.jianyu360.cn/BaseService/gateway/common/gatecode"
+	"fmt"
 	"github.com/gogf/gf/v2/net/ghttp"
 	"github.com/gogf/gf/v2/util/gconv"
+	"log"
+	"regexp"
 )
 
 type Manager struct {
@@ -20,7 +19,8 @@ type Manager struct {
 // InitRouterManager 初始化系统代理路由
 // 支持完全匹配和正则匹配
 func InitRouterManager() (*Manager, error) {
-	res := db.GateWatMySql.Query("SELECT status,middleground_code,url,function_code,check_sess,check_power,check_auth,check_status,check_blacklist,timeout,remark,appid,deduct_source,power_type FROM front_proxy")
+	//加载规则
+	res := db.GateWatMySql.Query("SELECT status,middleground_code,url,function_code,check_sess,check_power,check_auth,check_status,check_blacklist,ploy_code,timeout,remark,appid,deduct_source,power_type FROM front_proxy")
 	if res == nil || len(*res) == 0 {
 		return nil, fmt.Errorf("未发现可用路由")
 	}
@@ -40,6 +40,7 @@ func InitRouterManager() (*Manager, error) {
 			AccountCheck: enum.NewAccountCheck(gconv.Int64(row["check_status"])),
 			AuthCheck:    enum.NewAuthCheck(gconv.Int64(row["check_auth"])),
 			BlackCheck:   gconv.Int(row["check_blacklist"]) == 1,
+			LimitPloy:    gconv.String(row["ploy_code"]), //策略
 			Deduct:       gconv.Int(row["deduct_source"]),
 			FuncCode:     gconv.String(row["function_code"]),
 			MiddleCode:   gconv.String(row["middleground_code"]),

+ 6 - 2
core/router/router.go

@@ -1,6 +1,8 @@
 package router
 
-import "bp.jydev.jianyu360.cn/BaseService/gateway/common/enum"
+import (
+	"bp.jydev.jianyu360.cn/BaseService/gateway/common/enum"
+)
 
 type Router struct {
 	Status       int                // 0:冻结不可用 1:正常可用
@@ -12,9 +14,11 @@ type Router struct {
 	TimeOut      int64              // 接口超时提醒,单位毫秒;默认500毫秒
 	AppId        string             // 平台标识
 	BlackCheck   bool               // 是否校验黑名单 【是否校验黑名单; 0:不需要 1:需要】
+	LimitPloy    string             // 策略
 	FuncCode     string             // 功能代码
 	MiddleCode   string             // 中台代码
 	ReqUrl       string             // 请求地址
 	Remark       string             // 路由备注信息
-	PowerType    int                //权益判断方式、0走资源中台  1前置代理判断,是否是付费用户  2前置代理判断,是否是超级订阅  3前置代理判断,是否是大会员  4前置代理判断,是否是商机管理
+	PowerType    int                // 权益判断方式、0走资源中台  1前置代理判断,是否是付费用户  2前置代理判断,是否是超级订阅  3前置代理判断,是否是大会员  4前置代理判断,是否是商机管理
+	//FilterRule *filterPoly.ReqFilterPoly //访问黑名单规则
 }

+ 45 - 37
etc/config.yaml

@@ -18,6 +18,7 @@ server:
 etcd:
   # 基础服务
   baseserver:
+    appid: 10000
     # 用户中台配置
     userCenter:
       key: usercenter.rpc
@@ -29,10 +30,25 @@ etcd:
       address:
         - 192.168.3.240:2379
 
+# 系统配置
+system:
+  # 监听服务注册etcd配置
+  etcdListen:
+    - 192.168.3.206:2379
+  loadProxyModule: 1 #0随机 1轮询 2哈希。默认随机
+  response:
+    head-clear: # 响应头删除
+      - Trace-Id
+      - deductNum
+      - deductIds
+
 #数据库配置
 databases:
   # redis配置
-  redis: session=192.168.3.206:1712,other=192.168.3.206:1712    # session用户获取剑鱼程序用户session  other用户获取剑鱼权益redis
+  redis: session=192.168.3.206:1712,other=192.168.3.206:1712,    # session用户获取剑鱼程序用户session  other用户获取剑鱼权益redis
+  polyredis:
+    address: 127.0.0.1:6379
+    poolsize: 30
 
   # mysql配置
   mysql:
@@ -68,42 +84,6 @@ databases:
     userName: admin
     password: 123456
 
-# 系统配置
-system:
-  # 监听服务注册etcd配置
-  etcdListen:
-    - 192.168.3.240:2379
-  loadProxyModule: 1 #0随机 1轮询 2哈希。默认随机
-  response:
-    head-clear: # 响应头删除
-      - Trace-Id
-      - deductNum
-      - deductIds
-
-  # 系统日志
-  log:
-    path: ./logs                                # 系统日志默认文件默认报错路径。默认为./logs
-    debug: false                                # 是否打印调试信息。默认false
-    stdout: true                                # 是否输出到控制台。默认false
-    systemLogPattern: system-{Ymd}.log          # 日志文件格式。默认为"system-{Ymd}.log"
-    serverErrorStack: false                     # 当Server捕获到异常时是否记录堆栈信息到日志中。默认为true
-    serverRequestLogSaveDb: true                # 请求日志是否保存至数据库。默认false
-    serverErrorLogEnabled: true                 # 是否记录异常日志信息到日志中。默认为true
-    serverErrorLogPattern: error-{Ymd}.log      # 异常错误日志文件格式。默认为"error-{Ymd}.log"
-    serverAccessLogEnabled: true                # 是否记录访问日志。默认为false
-    serverAccessLogPattern: access-{Ymd}.log    # 访问日志文件格式。默认为"access-{Ymd}.log"
-    serverRequestTimeout: 2000                  # 接口请求超时通知,若注册表中无配置超时提醒使用此配置。默认500毫秒
-
-  # 系统告警
-  alarm:
-    isOpen: false                       # 异常通知开关。默认关闭
-    address: 192.168.3.207:4150         # nsq消息通知地址。默认关闭
-    toppic: jyalert                     # 消息管道
-    isJsonEncode: false                 # 是否加密
-    id: pre_alert                       # id
-    title: 你有新的告警消息处理            # 消息标题
-    text: 前置代理告警请查看               # 消息正文
-
 proxy:
   timeout: 30                 # 连接超时。默认30秒
   keepAlive: 60               # 长链接超时。默认60秒
@@ -113,5 +93,33 @@ proxy:
   expectContinueTimeout: 1    # 100-continue 超时时间。默认1秒
   maxIdleConnsPerHost: 5      # 客户端可以持有的最大空闲连接。默认5
 
+#日志配置
+logger:
+  level: "PROD"                # ALL, DEV, PROD
+  default:
+    file: "{Y-m-d}.log"
+    rotateExpire: 10
+    rotateBackupLimit: 10
+    stdout: true
+    path: "./logs/system"
+  reqLog:
+    level: "PROD"
+    saveToDb: false
+    path: "./logs/request"
+    file: "{Y-m-d}.log"        # 日志文件格式
+    stdout: true               # 是否输出至控制台
+    rotateExpire: 7d           # 当日志文件超过7天都没有任何修改/写入时,glog模块将会自动将其进行滚动切分。同时进行压缩存储
+    rotateBackupExpire: 60s    # 切分文件的过期时间为7d,即七天后会自动删除该切分文件
+    rotateBackupCompress: 0   # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩
+
+#日志告警
+alarm:
+  isOpen: false                       # 异常通知开关。默认关闭
+  address: 192.168.3.207:4150         # nsq消息通知地址。默认关闭
+  toppic: jyalert                     # 消息管道
+  isJsonEncode: false                 # 是否加密
+  id: pre_alert                       # id
+  title: 你有新的告警消息处理            # 消息标题
+  text: 前置代理告警请查看               # 消息正文
 
 

+ 67 - 45
gaway.sql

@@ -1,50 +1,72 @@
-### final Table
-CREATE TABLE `front_proxy`
-(
-    `id`                int(11) NOT NULL AUTO_INCREMENT,
-    `function_code`     varchar(255) DEFAULT NULL COMMENT '功能代码',
-    `url`               varchar(255) DEFAULT NULL COMMENT '接口地址',
-    `status`            tinyint(4)   DEFAULT NULL COMMENT '0:冻结不可用 1:正常可用',
-    `ischeck`           tinyint(4)   DEFAULT NULL COMMENT '是否需要校验;1:需要判断session 2:需要判断权限',
-    `ischeck_blacklist` tinyint(4)   DEFAULT NULL COMMENT '是否校验黑名单;0:不需要 1:需要',
-    `middleground_code` varchar(255) DEFAULT NULL COMMENT '中台代码,需要根据中台代码,到etcd中找对应的中台api地址',
-    `describe`          varchar(255) DEFAULT NULL COMMENT '描述',
-    PRIMARY KEY (`id`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4;
+### 路由注册表
+CREATE TABLE `front_proxy` (
+    `id` int(11) NOT NULL AUTO_INCREMENT,
+    `appid` varchar(255) NOT NULL,
+    `function_code` varchar(255) NOT NULL DEFAULT '' COMMENT '功能代码',
+    `url` varchar(255) NOT NULL COMMENT '接口地址',
+    `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0:冻结不可用 1:正常可用',
+    `timeout` int(11) NOT NULL DEFAULT '0' COMMENT '超时时间,单位毫秒。为0时走代理完关配置',
+    `check_sess` tinyint(4) NOT NULL DEFAULT '1' COMMENT '权益session; 0不需要 1校验user 2 校验ent 3 全部',
+    `check_power` tinyint(4) NOT NULL DEFAULT '0' COMMENT '权益校验;0:不需要 1:需要',
+    `check_auth` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否需要认证;0不需要 1校验user 2 校验ent 3 全部',
+    `check_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否需要检查状态(冻结);0不需要 1校验user 2 校验ent 3 全部',
+    `ploy_code` varchar(255) NOT NULL DEFAULT '' COMMENT '拦截策略code',
+    `check_blacklist` tinyint(4) DEFAULT '0' COMMENT '此字段需要删除',
+    `deduct_source` tinyint(4) NOT NULL DEFAULT '0' COMMENT '扣减来源;0:不扣减 1:前置代理(默认:1) 2:后端应用(必须返回后端应用)3:应用调资源中台自行扣减',
+    `middleground_code` varchar(255) NOT NULL COMMENT '中台代码,需要根据中台代码,到etcd中找对应的中台api地址',
+    `remark` varchar(255) DEFAULT NULL COMMENT '备注',
+    `create_time` datetime NOT NULL COMMENT '创建时间',
+    `create_persion` varchar(255) NOT NULL COMMENT '创建人',
+    `update_time` datetime NOT NULL COMMENT '更新时间',
+    `update_person` varchar(255) NOT NULL COMMENT '更新人',
+    `power_type` tinyint(4) DEFAULT NULL COMMENT '权益判断方式、0走资源中台   \n1前置代理判断,是否是付费用户   \n2前置代理判断,是否是超级订阅\n   3前置代理判断,是否是大会员\n   4前置代理判断,是否是商机管理',
+     PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=73 DEFAULT CHARSET=utf8mb4 COMMENT='前置代理表';
 
+### 旧字段
+# UPDATE front_proxy SET ploy_code = ''
 
-### 路由注册表
-CREATE TABLE `gateway_router`
-(
-    `id`      bigint(20) NOT NULL AUTO_INCREMENT COMMENT '路由id',
-    `status`  tinyint(4) NOT NULL DEFAULT '1' COMMENT '1:已激活|-1:关闭请求',
-    `check`   tinyint(4) NOT NULL DEFAULT '1' COMMENT '<1:无拦截 1:需登录 2:需购买 3:次数校验>',
-    `module`  varchar(255)        DEFAULT '' COMMENT '所属模块儿',
-    `router`  varchar(255)        DEFAULT '' COMMENT '路由地址',
-    `timeout` tinyint(4)          DEFAULT '2' COMMENT '超时设置(单位秒)',
-    `method`  varchar(255)        DEFAULT 'POST' COMMENT '请求方式 <POST|GET|PUT>',
-    `desc`    varchar(255)        DEFAULT '' COMMENT '路由描述',
-    PRIMARY KEY (`id`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COLLATE = utf8mb4_unicode_ci;
+### 请求策略
+CREATE TABLE `front_proxy_poly` (
+    `id` int(11) NOT NULL AUTO_INCREMENT,
+    `code` varchar(255) NOT NULL COMMENT '策略编码',
+    `ploy_rule` json NOT NULL COMMENT '策略规则',
+    `remark` varchar(255) DEFAULT NULL COMMENT '备注',
+    `create_time` datetime NOT NULL COMMENT '创建时间',
+    `create_persion` varchar(255) NOT NULL COMMENT '创建人',
+    `update_time` datetime NOT NULL COMMENT '更新时间',
+    `update_person` varchar(255) NOT NULL COMMENT '更新人',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `onlyCode` (`code`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='前置代理-策略表';
 
 
-### 用户鉴权表
-CREATE TABLE `gateway_router_power`
-(
-    `router_id`   bigint(20)  NOT NULL COMMENT '路由id',
-    `user_id`     varchar(25) NOT NULL COMMENT '用户id',
-    `status`      tinyint(4)  NOT NULL DEFAULT '1' COMMENT '1:待激活|2:已激活(-(status)过期,-(status+10)冻结)',
-    `start_time`  datetime    NOT NULL COMMENT '权益开始时间',
-    `end_time`    datetime    NOT NULL COMMENT '权益结束时间',
-    `update_time` datetime    NOT NULL COMMENT '权益更新时间',
-    KEY `userid_index` (`user_id`) USING BTREE,
-    KEY `router_index` (`router_id`) USING BTREE,
-    KEY `rid_index` (`router_id`) USING BTREE,
-    CONSTRAINT `gateway_router_power_ibfk_1` FOREIGN KEY (`router_id`) REFERENCES `gateway_router` (`id`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COLLATE = utf8mb4_unicode_ci;
+### 白名单 && 黑名单
+CREATE TABLE `front_proxy_blackWhite` (
+    `id` int(11) NOT NULL AUTO_INCREMENT,
+    `value` varchar(255) NOT NULL COMMENT '用户id or 用户ip',
+    `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0:正常 1:白名单 -1:黑名单',
+    `remark` varchar(255) DEFAULT NULL COMMENT '备注',
+    `create_time` datetime NOT NULL COMMENT '创建时间',
+    `create_persion` varchar(255) NOT NULL COMMENT '创建人',
+    `update_time` datetime NOT NULL COMMENT '更新时间',
+    `update_person` varchar(255) NOT NULL COMMENT '更新人',
+    PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='前置代理-黑白名单';
+
+### 变更记录
+CREATE TABLE `front_proxy_history` (
+    `id` int(11) NOT NULL AUTO_INCREMENT,
+    `userId` varchar(255) NOT NULL COMMENT '用户id',
+    `before` varchar(255) DEFAULT NULL COMMENT '修改前',
+    `after` varchar(255) DEFAULT NULL COMMENT '修改后',
+    PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='前置代理-操作变更记录';
 
+### 管理员
+CREATE TABLE `front_proxy_admin` (
+   `id` int(11) NOT NULL AUTO_INCREMENT,
+   `account` varchar(255) NOT NULL COMMENT '账户',
+   `password` varchar(255) DEFAULT NULL COMMENT '密码',
+   PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='前置代理-管理员表';

+ 6 - 1
go.mod

@@ -7,7 +7,12 @@ require (
 	bp.jydev.jianyu360.cn/BP/jynsq v0.0.0-20220222052708-ebc43af90698
 	bp.jydev.jianyu360.cn/BaseService/resourceCenter v0.0.0-20220420075831-0b59892e9982
 	bp.jydev.jianyu360.cn/BaseService/userCenter v0.0.0-20220421015128-4a36f3eac5c5
-	github.com/gogf/gf/v2 v2.0.6
+	github.com/go-redis/redis v6.15.9+incompatible
+	//github.com/gogf/gf/v2 v2.0.6
+	github.com/gogf/gf/v2 v2.1.2
+	github.com/golang/freetype v0.0.0
 	github.com/zeromicro/go-zero v1.3.2
 	go.etcd.io/etcd/client/v3 v3.5.2
 )
+
+replace github.com/golang/freetype v0.0.0 => github.com/Kusers/freetype v0.0.0-20220714074437-04188d6f4e2c

+ 35 - 13
go.sum

@@ -57,11 +57,14 @@ github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935
 github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
 github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
 github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
+github.com/Kusers/freetype v0.0.0-20220714074437-04188d6f4e2c h1:CtjDzbgqA23vZIjhbRcgtVyMVvFC+oYRU5wDfEM53Ic=
+github.com/Kusers/freetype v0.0.0-20220714074437-04188d6f4e2c/go.mod h1:fx624NOLJWMWRjH02M5tcjS2ePD8+Akm9EiiibGs9j8=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
@@ -115,6 +118,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
 github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
@@ -144,8 +148,9 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
 github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM=
 github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -164,8 +169,9 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7
 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
 github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
@@ -176,8 +182,11 @@ github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL9
 github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
 github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
+github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
+github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
+github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
+github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
@@ -185,7 +194,6 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
 github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
 github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
 github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
@@ -212,8 +220,9 @@ github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGt
 github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
 github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gogf/gf/v2 v2.0.6 h1:2etb4FMpbQKWIJO+UjtIWrZUp01HUsFb6Po8pgizAWk=
 github.com/gogf/gf/v2 v2.0.6/go.mod h1:8uYzw7qNzuq8vrhVlWke1b1925FFqOJIgmyYW1sr/0M=
+github.com/gogf/gf/v2 v2.1.2 h1:3YHfbdfJl4SyBp+8R85jvQIa8jy/iR2fr7iHPqnxOWY=
+github.com/gogf/gf/v2 v2.1.2/go.mod h1:thvkyb43RWUu/m05sRm4CbH9r7t7/FrW2M56L9Ystwk=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -280,6 +289,7 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -308,6 +318,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
 github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
@@ -360,8 +371,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/longbridgeapp/sqlparser v0.3.1 h1:iWOZWGIFgQrJRgobLXUNJdvqGRpbVXkyKUKUA5CNJBE=
 github.com/longbridgeapp/sqlparser v0.3.1/go.mod h1:GIHaUq8zvYyHLCLMJJykx1CdM6LHtkUih/QaJXySSx4=
+github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
+github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@@ -411,12 +423,15 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
 github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
 github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
 github.com/openzipkin/zipkin-go v0.4.0 h1:CtfRrOVZtbDj8rt1WXjklw0kqqJQwICrCKmlfUuBUUw=
@@ -488,8 +503,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
@@ -527,18 +543,21 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
-go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y=
 go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
+go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
+go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
 go.opentelemetry.io/otel/exporters/jaeger v1.3.0 h1:HfydzioALdtcB26H5WHc4K47iTETJCdloL7VN579/L0=
 go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc=
 go.opentelemetry.io/otel/exporters/zipkin v1.3.0 h1:uOD28dZ7yIKITTcUS6MeAGNHYy3uhP7DTkhcJM6onlQ=
 go.opentelemetry.io/otel/exporters/zipkin v1.3.0/go.mod h1:LxGGfHIYbvsFnrJtBcazb0yG24xHdDGrT/H6RB9r3+8=
 go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM=
-go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI=
 go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
+go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0=
+go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU=
 go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs=
-go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY=
 go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
+go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
+go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
@@ -579,6 +598,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw=
+golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -719,8 +740,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
 golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

+ 2 - 0
main.go

@@ -4,6 +4,7 @@ import (
 	"bp.jydev.jianyu360.cn/BaseService/gateway/common/db"
 	"bp.jydev.jianyu360.cn/BaseService/gateway/core/logs"
 	"bp.jydev.jianyu360.cn/BaseService/gateway/core/proxy"
+	"bp.jydev.jianyu360.cn/BaseService/gateway/core/proxy/middleware"
 	"bp.jydev.jianyu360.cn/BaseService/gateway/core/proxy/rpc"
 	"github.com/gogf/gf/v2/frame/g"
 	"github.com/gogf/gf/v2/os/gcfg"
@@ -14,6 +15,7 @@ func init() {
 	logs.InitLogs()                                                           // 初始化日志组件
 	rpc.InitBaseServerRpc()                                                   // 初始化rpc服务连接
 	db.InitDatabases()                                                        // 初始化数据库
+	middleware.InitFilterPolyManager()                                        // 初始化
 }
 
 func main() {

+ 577 - 0
resources/antiRes/control.html

@@ -0,0 +1,577 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>剑鱼访问控制台</title>
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
+          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
+          crossorigin="anonymous">
+</head>
+<style>
+    body, html {
+        margin: 0;
+        padding: 0;
+        background: #eee;
+        min-height: 100vh;
+    }
+
+    .main {
+        width: 1000px;
+        margin: 0 auto;
+        min-height: 100vh;
+        background-color: #f9f9f9;
+    }
+
+    .main .title {
+        text-align: center;
+    }
+
+    .query {
+        display: flex;
+        justify-content: space-around;
+    }
+
+    .query .item {
+        display: flex;
+        flex-direction: row;
+    }
+
+    .query .item .left {
+        line-height: 46px;
+        height: 46px;
+        margin: 0 5px;
+        font-size: 20px;
+    }
+
+    #login {
+        width: 500px;
+        height: 270px;
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        margin-top: -200px;
+        margin-left: -250px;
+        padding: 20px;
+    }
+
+    #login .input-group {
+        margin: 20px 10px;
+    }
+
+    #login .btns {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-around;
+    }
+
+    #login .btns button {
+        width: 120px;
+        height: 35px;
+    }
+
+    .btns .submit {
+        background-color: #2196f3a1;
+    }
+
+    .btns .cancel {
+        background-color: #9e9e9e6e;
+    }
+
+    #content {
+        display: flex;
+        flex-direction: column;
+        padding: 20px 100px;
+    }
+
+    .line {
+        display: flex;
+        flex-direction: row;
+        line-height: 50px;
+        margin-top: 1px;
+    }
+
+    .line-title {
+        width: 170px;
+        padding: 0 20px;
+        background: #91d3cd;
+    }
+
+    .line-content {
+        width: 100%;
+        padding: 0 20px;
+        background: #b7d9f4;
+        margin-left: 1px;
+    }
+
+    .line-opera {
+        background: #b7d9f4;
+        width: 30px;
+        text-align: center;
+        line-height: 30px;
+    }
+
+    .line-opera span {
+        background: #FF5722;
+        padding: 2px;
+        border-radius: 5px;
+    }
+
+    .line-content .row_30 {
+        display: flex;
+        flex-direction: row;
+        line-height: 30px;
+    }
+
+    .line-content .row_normal {
+        display: flex;
+        flex-direction: row;
+    }
+
+    .line-content div.min {
+        margin-right: 20px;
+        width: 60px;
+    }
+
+    .line-content div.min_2 {
+        margin-right: 20px;
+        width: 120px;
+    }
+
+    .line-content div.min_4 {
+        margin-right: 20px;
+        width: 240px;
+    }
+
+    .alias-title {
+        margin-top: 10px;
+        text-align: center;
+        background-color: #68e3f0d1;
+        color: #a94442;
+        height: 20px;
+        line-height: 20px;
+        border-radius: 12px;
+    }
+
+    .alias-title-head {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+    }
+
+    .alias-title-head .line-opera {
+        padding-top: 10px;
+    }
+
+</style>
+<body>
+<div class="main">
+    <div class="panel panel-default">
+        <div class="panel-body title">
+            <h3>用户访问状态查询</h3>
+        </div>
+    </div>
+
+    <div class="tip">
+    </div>
+
+    <nav class="navbar navbar-default">
+        <div class="container-fluid">
+            <!-- Collect the nav links, forms, and other content for toggling -->
+            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
+                <input id="appid" type="text" style="display:none">
+                <ul class="nav navbar-nav">
+                    <li class="dropdown">
+                        <a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
+                           aria-expanded="false"><span
+                                    id="userSelect">选择应用</span>
+                            <span class="caret"></span></a>
+                        <ul class="dropdown-menu">
+                            <li><a onclick="ShowLogin()">请登录</a></li>
+                        </ul>
+                    </li>
+                </ul>
+                <div class="navbar-form navbar-right">
+                    <div class="form-group">
+                        <input type="text" id="queryValue" class="form-control" style="width: 500px;"
+                               placeholder="输入用户openid/userid/手机号进行查询">
+                    </div>
+                    <button onclick="doSearch()" class="btn btn-default">查询</button>
+                </div>
+            </div><!-- /.navbar-collapse -->
+        </div><!-- /.container-fluid -->
+    </nav>
+
+    <!--内容展示-->
+    <div id="content" style="display: none">
+        <div class="line" v-if="status">
+            <div class="line-title">用户状态</div>
+            <div v-if="status.flag === 1" class="line-content">
+                <div class="row_normal">
+                    <div class="min">验证码</div>
+                    <div class="min_4">{{fotmatData(status.flush)}}</div>
+                </div>
+            </div>
+            <div v-else-if="status.flag === 2" class="line-content">
+                <div class="row_normal">
+                    <div class="min">黑名单</div>
+                    <div class="min_4">{{fotmatData(status.flush)}}</div>
+                </div>
+            </div>
+            <div v-else class="line-content">
+                正常
+            </div>
+            <div class="line-opera" title="清除账户异常状态" onclick="doClear('1')">
+                <span class="glyphicon glyphicon glyphicon-remove"></span>
+            </div>
+        </div>
+        <div class="line" v-if="aliasReq">
+            <div class="line-title">请求频率</div>
+            <div class="line-content">
+                <div class="one" v-for="(alias, i) in aliasReq" v-bind:key="i">
+                    <div class="alias-title">{{i}}</div>
+                    <div>
+                        <div class="alias-title-head">
+                            <div class="">验证码阀值</div>
+                            <div class="line-opera" title="清除验证码阀值" v-on:click="doClear('2',i)">
+                                <span class="glyphicon glyphicon glyphicon-remove"></span>
+                            </div>
+                        </div>
+                        <div>
+                            <div class="row_30">
+                                <div class="min">时间</div>
+                                <div class="min">限制</div>
+                                <div class="min">当前次数</div>
+                                <div class="min_4">清除时间</div>
+                            </div>
+                            <div class="row_30" v-for="(item, i) in alias.allRates" v-bind:key="i">
+                                <div class="min">{{item.k}}</div>
+                                <div class="min">{{item.v}}</div>
+                                <div class="min">{{item.times}}</div>
+                                <div class="min_4">{{fotmatData(item.flush)}}</div>
+                            </div>
+                        </div>
+                    </div>
+                    <div>
+                        <div class="alias-title-head">
+                            <div class="">超出最大阈值次数</div>
+                            <div class="line-opera" title="清除最大请求次数" v-on:click="doClear('6',i)">
+                                <span class="glyphicon glyphicon glyphicon-remove"></span>
+                            </div>
+                        </div>
+                        <div>
+                            <div class="row_30">
+                                <div class="min_2">id超出最大阈值</div>
+                                <div class="min">当前次数</div>
+                                <div class="min_4">清除时间</div>
+                            </div>
+                            <div class="row_30">
+                                <div class="min_2">每{{alias.exceedTime.v}}次</div>
+                                <div class="min">{{alias.exceedTime.times}}</div>
+                                <div class="min_4">{{fotmatData(alias.exceedTime.flush)}}</div>
+                            </div>
+                        </div>
+                    </div>
+                    <div>
+                        <div class="alias-title-head">
+                            <div class="">最大请求次数</div>
+                            <div class="line-opera" title="清除最大请求次数" v-on:click="doClear('3',i)">
+                                <span class="glyphicon glyphicon glyphicon-remove"></span>
+                            </div>
+                        </div>
+                        <div>
+                            <div class="row_30">
+                                <div class="min">时间</div>
+                                <div class="min">限制</div>
+                                <div class="min">当前次数</div>
+                                <div class="min_4">清除时间</div>
+                            </div>
+                            <div class="row_30">
+                                <div class="min">{{alias.maxRate.k}}</div>
+                                <div class="min">{{alias.maxRate.v}}</div>
+                                <div class="min">{{alias.maxRate.times}}</div>
+                                <div class="min_4">{{fotmatData(alias.maxRate.flush)}}</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="line-opera">
+            </div>
+        </div>
+        <div class="line" v-if="verifyErr">
+            <div class="line-title">验证码错误次数</div>
+            <div class="line-content">
+                <div class="row_30">
+                    <div class="min">时间</div>
+                    <div class="min">限制</div>
+                    <div class="min">当前次数</div>
+                    <div class="min_4">清除时间</div>
+                </div>
+                <div class="row_30">
+                    <div class="min">{{verifyErr.k}}</div>
+                    <div class="min">{{verifyErr.v}}</div>
+                    <div class="min">{{verifyErr.times}}</div>
+                    <div class="min_4">{{fotmatData(verifyErr.flush)}}</div>
+                </div>
+            </div>
+            <div class="line-opera" title="清除错误验证码次数" onclick="doClear('4')">
+                <span class="glyphicon glyphicon glyphicon-remove"></span>
+            </div>
+        </div>
+        <div class="line" v-if="blackTmpTimes">
+            <div class="line-title">临时黑名单次数</div>
+            <div class="line-content">
+                <div class="row_30">
+                    <div class="min">时间</div>
+                    <div class="min">限制</div>
+                    <div class="min">当前次数</div>
+                    <div class="min_4">清除时间</div>
+                </div>
+                <div class="row_30">
+                    <div class="min">{{blackTmpTimes.k}}</div>
+                    <div class="min">{{blackTmpTimes.v}}</div>
+                    <div class="min">{{blackTmpTimes.times}}</div>
+                    <div class="min_4">{{fotmatData(blackTmpTimes.flush)}}</div>
+                </div>
+            </div>
+            <div class="line-opera" title="清除临时黑名单次数" onclick="doClear('5')">
+                <span class="glyphicon glyphicon glyphicon-remove"></span>
+            </div>
+        </div>
+    </div>
+
+    <div id="login" class="panel panel-default" style="display:none">
+        <button type="button" class="close cancel" data-dismiss="alert" aria-label="Close"><span
+                    aria-hidden="true">×</span></button>
+        <div class="panel-body ">
+            身份验证
+        </div>
+        <div class="input-group">
+            <span class="input-group-addon">账&nbsp;&nbsp;户:</span>
+            <input type="text" class="form-control" placeholder="请输入账户" id="userName">
+        </div>
+        <div class="input-group">
+            <span class="input-group-addon">密&nbsp;&nbsp;码:</span>
+            <input type="password" class="form-control" placeholder="请输入密码" id="passWord">
+        </div>
+        <div class="btns">
+            <button type="button" class="btn btn-default cancel">取消</button>
+            <button type="button" class="btn btn-default submit">登录</button>
+        </div>
+    </div>
+
+
+</div>
+</body>
+<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
+<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.js"></script>
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"
+        integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
+        crossorigin="anonymous"></script>
+<script type="text/javascript">
+
+    Date.prototype.pattern = function (fmt) {
+        var o = {
+            "y+": this.getFullYear(),
+            "M+": this.getMonth() + 1, //月份
+            "d+": this.getDate(), //日
+            "h+": this.getHours() % 12 === 0 ? 12 : this.getHours() % 12, //小时
+            "H+": this.getHours(), //小时
+            "m+": this.getMinutes(), //分
+            "s+": this.getSeconds(), //秒
+            "q+": Math.floor((this.getMonth() + 3) / 3), //季度
+            "S": this.getMilliseconds() //毫秒
+        };
+        var week = {
+            "0": "日",
+            "1": "一",
+            "2": "二",
+            "3": "三",
+            "4": "四",
+            "5": "五",
+            "6": "六"
+        };
+        if (/(y+)/.test(fmt)) {
+            fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
+        }
+        if (/(E+)/.test(fmt)) {
+            fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? "星期" : "周") : "") + week[this.getDay() +
+            ""]);
+        }
+        for (var k in o) {
+            if (new RegExp("(" + k + ")").test(fmt)) {
+                fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
+            }
+        }
+        return fmt;
+    }
+    var Control = new Vue({
+        el: '#content',
+        data: {
+            "allRates": false,
+            "blackTmpTimes": false,
+            "aliasReq": false,
+            //"maxRate": false,
+            "verifyErr": false
+        },
+        methods: {
+            fotmatData: function (time) {
+                if (time === 0) {
+                    return ""
+                } else if (time === -1) {
+                    return "永久"
+                }
+                return new Date(time * 1000).pattern('yyyy-MM-dd HH.mm.ss')
+            }
+        }
+    });
+    $(function () {
+        $("#login .cancel").on("click", function () {
+            $("#login").hide();
+        });
+
+        $("#login .submit").on("click", function () {
+            var userName = $("#userName").val();
+            var passWord = $("#passWord").val();
+            if (userName !== "" && passWord !== "") {
+                localStorage.UserName = userName;
+                localStorage.Password = passWord;
+                $(".tip").html("");
+                getAppList();
+                $("#login").hide();
+            } else {
+                tipMsg("输入不能为空")
+            }
+        });
+
+        if (!localStorage.UserName || !localStorage.UserName) {
+            ShowLogin()
+        } else {
+            getAppList()
+        }
+
+    });
+
+    function ShowLogin() {
+        $("#login").show();
+    }
+
+    function getAppList() {
+        var param = {};
+        addAuthentication(param);
+        $.post("/antiControl/getAppList", param, function (r) {
+            if (!r.errMsg) {
+                if (r.appList.length > 0) {
+                    var dropdown_mune = "";
+                    for (var i = 0; i < r.appList.length; i++) {
+                        dropdown_mune += "<li data_id=" + r.appList[i] + " onclick='updataSelect(this)'><a>" + r.appList[i] + "</a></li>"
+                    }
+                    $(".dropdown .dropdown-menu").html(dropdown_mune)
+                }
+            } else {
+                tipMsg(r.errMsg)
+            }
+        }, "json")
+    }
+
+    var Reqing = false;
+
+    //查询
+    function doSearch() {
+        var param = {
+            "appName": $("#appid").val(),
+            "searchV": $("#queryValue").val(),
+        };
+
+        if (!param.appName) {
+            tipMsg("请选择应用");
+            return
+        }
+
+        if (!(param.searchV.split(".").length === 4 || param.searchV.length === 11 || param.searchV.length === 24 || param.searchV.length === 28)) {
+            tipMsg("查询内容有误");
+            return
+        }
+
+        addAuthentication(param);
+
+        if (Reqing) {
+            return
+        }
+        $(".tip").html("");
+        Reqing = true;
+        $.post("/antiControl/query", param, function (r) {
+            Reqing = false;
+            if (!r.errMsg) {
+                $("#content").css("display", "");
+                Control.aliasReq = r.rateData;
+                // Control.allRates = r.allRates;
+                //Control.maxRate = r.maxRate;
+                Control.blackTmpTimes = r.blackTmpTimes;
+                Control.verifyErr = r.verifyErr;
+                Control.status = r.status;
+            } else {
+                tipMsg(r.errMsg)
+            }
+        }, "json")
+    }
+
+    //清除 -1清除所有;1用户状态 2验证码阀值 3最大请求次数 4验证码错误次数 5临时黑名单次数
+    function doClear(flag, other) {
+        var param = {
+            "appName": $("#appid").val(),
+            "searchV": $("#queryValue").val(),
+            "flag": flag,
+        };
+        if (other) {
+            param["alias"] = other
+        }
+        addAuthentication(param);
+        if (Reqing) {
+            return
+        }
+        Reqing = true;
+        $.post("/antiControl/clear", param, function (r) {
+            Reqing = false;
+            if (!r.errMsg) {
+                doSearch()
+            } else {
+                tipMsg(r.err)
+            }
+        }, "json")
+    }
+
+    function addAuthentication(param) {
+        if (!localStorage.UserName || !localStorage.UserName) {
+            ShowLogin()
+        } else {
+            param["userName"] = localStorage.UserName;
+            param["passWord"] = localStorage.Password;
+        }
+    }
+
+    function updataSelect(demo) {
+        var name = $(demo).find("a").text();
+        var appid = $(demo).attr("data_id");
+        $("#userSelect").text(name);
+        $("#appid").val(appid);
+    }
+
+
+    function tipMsg(type) {
+        var addHtml = "";
+        if (type === "用户校验失败") {
+            addHtml = '身份验证异常&nbsp;&nbsp;<span style="color:#000;cursor: pointer;" onclick="ShowLogin()">点击重新验证</span>'
+        } else {
+            addHtml = type
+        }
+        var tipHtml = '<div class="alert alert-warning alert-dismissible" role="alert">' +
+            '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
+            '<strong>Warning!</strong> <em>' + addHtml + '</em>' +
+            '</div>';
+        $(".tip").html(tipHtml);
+    }
+
+
+</script>
+</html>

二进制
resources/antiRes/images/flush.png


二进制
resources/antiRes/images/hoverclick.png


二进制
resources/antiRes/images/verify_logo.png


+ 111 - 0
resources/antiRes/js/mainHook.js

@@ -0,0 +1,111 @@
+if (typeof $ === 'function') {
+    $.ajaxPrefilter(function (options, orginOption, jqXHR) {
+        var beforeCallBack = options.success;
+        var beforeSettring = orginOption;
+        options.success = function (r) {
+            if (r.antiVerify) {
+                if (r.antiVerify === 1) {
+                    antiReload(); //重新请求数据
+                    //移除验证码
+                    var antiVerify = document.querySelector("#antiVerify");
+                    antiVerify.parentNode.removeChild(antiVerify);
+                } else {
+                    window.nextSetting = beforeSettring;//记录请求
+                    shwoCode(r.textVerify, r.imgData);//验证码展示
+                }
+            } else if (r.antiJump) {
+                window.location.href = r.href;
+            } else {
+                if (r.antiData && typeof (antiDecode) !== "function") {//等待初始化完毕
+                    var localInterval = setInterval(function () {
+                        if (typeof (antiDecode) === "function") {
+                            r = JSON.parse(antiDecode(r.antiData))
+                            if (beforeCallBack) beforeCallBack(r)
+                            clearInterval(localInterval)
+                        }
+                    }, 200)
+                } else {
+                    if (r.antiData) {
+                        r = JSON.parse(antiDecode(r.antiData))
+                    }
+                    if (beforeCallBack) beforeCallBack(r)
+                }
+            }
+        }
+    })
+}
+
+
+function antiReload(flag) {
+    try {
+        if (flag === 1) {
+            window.nextSetting.data.antiVerifyCheck = document.querySelector("#antivalue").value.substr(1);
+            window.nextSetting.data.imgw = document.querySelector("#antiimg").width;
+        } else {
+            if (window.nextSetting.data) {
+                delete window.nextSetting.data.antiVerifyCheck;
+            }
+        }
+    } catch (e) {
+        console.log(e)
+    }
+    $.ajax(window.nextSetting)
+}
+
+
+function shwoCode(text, imgdata) {
+    var htmlcode = '<div style="width: 100%;height: 100%;position:absolute;top:0;background-color: #382f3d;opacity: .1;z-index: 99999999999990;"></div>'
+        + '<div style="position: fixed;top: 50%;left: 50%;transform: translateX(-50%) translateY(-50%);z-index: 999999999999990;max-width: 360px;">'
+        + '<div>'
+        + '<img style="width: 90vw;max-width: 360px;display: block;" src="/antiRes/images/verify_logo.png">'
+        + '</div>'
+        + '<div style="border: #F5F5F5 solid 1px;margin: auto;width: 90vw;max-width: 360px;background-color: #FFFFFF;box-shadow: 1px 1px 1px 1px grey;padding: 10pt;display: flex;flex-direction: column;">'
+        + '<div style="margin-bottom:8pt"><div>请在下图依次点击:<span>' + text + '</span></div></div>'
+        + '<div style="position:relative;width:100%">'
+        + '<img id="antiimg" onclick="antiAdd(event,this);" src="data:image/png;base64,' + imgdata + '" style="width:100%">'
+        + '<input type="hidden" id="antivalue" value="">'
+        + '</div>'
+        + '<div style="margin-top: 1vh;display: flex;flex-direction: row;justify-content: space-between;">'
+        + '<img style="argin-left: 8pt;width: 25pt;height: 25pt;float: left;cursor: hand;" onclick="antiReload()" src="/antiRes/images/flush.png" \>'
+        + '<div><button style="background-color: #24C0D7;text-align: center;vertical-align: middle;touch-action: manipulation;cursor: pointer;background-image: none;border: 1px solid transparent;white-space: nowrap;padding: 8px 12px;font-size: 14px;line-height: 1.42857143;border-radius: 4px;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;color: #FFFFFF;position: relative;outline-width: 0px;box-shadow: none !important;" onclick="antiReload(1);">确定</button></div>'
+        + '</div>'
+        + '</div>'
+        + '</div>'
+    if (document.querySelector("#antiVerify") == null) {
+        var wrap = document.createElement("div");
+        wrap.id = "antiVerify";
+        wrap.innerHTML = htmlcode;
+        var insertParent = document.body
+        if (document.querySelector("#supersearchPage") && document.querySelector("#supersearchPage").className !== "hidden") {//搜索首页验证码要在搜索页内
+            insertParent = document.querySelector("#supersearchPage")
+        } else if (document.querySelector("#entsearchPage") && document.querySelector("#entsearchPage").className !== "hidden") {
+            insertParent = document.querySelector("#entsearchPage")
+        }
+        insertParent.appendChild(wrap);
+    } else {
+        document.querySelector("#antiVerify").innerHTML = htmlcode;
+    }
+    document.querySelector("#antiVerify div").addEventListener('touchmove', function (event) {
+        event.preventDefault();
+    })
+}
+
+function antiAdd(event, obj) {
+    if (obj.parentNode.querySelectorAll(".imgs").length < 3) {
+        var offsetX = event.clientX - obj.getBoundingClientRect().left;
+        var offsetY = event.clientY - obj.getBoundingClientRect().top;
+        var offx = parseInt(offsetX);
+        var offy = parseInt(offsetY);
+        var icon = "<img onclick='antiRemove(this)' class='imgs' src='/antiRes/images/hoverclick.png' "
+            + "style='position:absolute;top:" + (offsetY - 8) + "px;left:" + (offsetX - 8) + "px;'   offx=" + offx + " offy=" + offy + " />";
+        obj.parentNode.innerHTML += icon;
+        document.querySelector("#antivalue").value += (";" + offx + "," + offy)
+    }
+}
+
+function antiRemove(obj) {
+    var offx = obj.getAttribute("offx");
+    var offy = obj.getAttribute("offy");
+    document.querySelector("#antivalue").value = document.querySelector("#antivalue").value.replace((";" + offx + "," + offy), "");
+    obj.parentNode.removeChild(obj);
+}

二进制
resources/antiRes/lib/anti_v1.wasm


+ 140 - 0
resources/antiRes/page/app.html

@@ -0,0 +1,140 @@
+<html>
+<head>
+    <title>验证码</title>
+    <meta charset="UTF-8"/>
+    <meta name="format-detection" content="telephone=no, email=no"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+    <link rel="stylesheet" type="text/css" href="/jyapp/css/font.css?v=1429"/>
+    <link href="/jyapp/css/common.css?v=1429" rel="stylesheet">
+    <link href="/jyapp/css/font.css" rel="stylesheet">
+    <link href="/jyapp/css/layout.css" rel="stylesheet">
+    <script src="/jyapp/js/jquery.js"></script>
+    <script src="/jyapp/js/rem.js?v=1429" type="text/javascript" charset="utf-8"></script>
+    <script src="/jyapp/js/common.js?v=1429"></script>
+    <style>
+        .error-body {
+            width: 100%;
+            margin: 0;
+            padding: 0;
+            text-align: center;
+        }
+
+        .verify-body {
+            width: 90vw;
+            margin: 0 auto;
+            text-align: center;
+            padding: 55px 0;
+        }
+
+        .verify-img{
+            width: 100%;
+        }
+
+        .verify_logo {
+            width: 100%;
+            display: block;
+        }
+
+        .verify-content {
+            border: #F5F5F5 solid 1px;
+            margin: auto;
+            background-color: #FFFFFF;
+            box-shadow: 1px 1px 1px 1px grey;
+            padding: 3vw;
+            display: flex;
+            flex-direction: column;
+        }
+
+        .verify-content .word {
+            text-align: left;
+            margin: 0px;
+        }
+
+        #antiimg{
+            width: 100%;
+        }
+
+        .footBtn {
+            margin-top: 5pt;
+            display: flex;
+            flex-direction: row;
+            justify-content: space-between;
+        }
+    </style>
+</head>
+<body>
+<div class="error-body">
+    <div class="app-layout-header">
+        <span class="app-back jyapp-icon jyapp-icon-zuojiantou"></span>
+        验证码
+    </div>
+    <div class="app-layout-content-b">
+        <div class="verify-body">
+            <div class="verify-img">
+                <img class="verify_logo" src="/antiRes/images/verify_logo.png">
+            </div>
+            <div class="verify-content">
+                <div style="margin-bottom:8pt"><p class="word">请在下图依次点击:<span id="words">{{.textVerify}}</span></p></div>
+                <div style="position:relative;width: 100%" class="verify_img">
+                    <img id="antiimg" src="data:image/png;base64,{{.imgData}}" onclick="antiAdd(event,this);"/>
+                    <input type="hidden" value="" id="antivalue"/>
+                </div>
+                <div class="footBtn">
+                    <img onclick="flash()" src="/antiRes/images/flush.png" style="width: 25pt;height: 25pt" alt="">
+                    <div>
+                        <button class="comfirmbtn btn btn-success" onclick="checkdot();">确定</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+</body>
+<script>
+    //验证码方法
+    function antiAdd(event, obj) {
+        if (obj.parentNode.querySelectorAll(".imgs").length < 3) {
+            var offsetX = event.pageX - (obj.getBoundingClientRect().left + document.body.scrollLeft);
+            var offsetY = event.pageY - (obj.getBoundingClientRect().top + document.body.scrollTop);
+            var offx = parseInt(offsetX);
+            var offy = parseInt(offsetY);
+            var icon = "<img onclick='antiRemove(this)' class='imgs' src='/antiRes/images/hoverclick.png' "
+                + "style='position:absolute;top:" + (offsetY - 8) + "px;left:" + (offsetX - 8) + "px;'   offx=" + offx + " offy=" + offy + " />";
+            obj.parentNode.innerHTML += icon
+            document.querySelector("#antivalue").value += (";" + offx + "," + offy)
+        }
+    }
+
+    //删除验证
+    function antiRemove(obj) {
+        var offx = obj.getAttribute("offx");
+        var offy = obj.getAttribute("offy");
+        document.querySelector("#antivalue").value = document.querySelector("#antivalue").value.replace((";" + offx + "," + offy), "");
+        obj.parentNode.removeChild(obj);
+    }
+
+    //刷新验证码
+    function flash() {
+        window.location.reload()
+    }
+
+    //验证码提交
+    function checkdot() {
+        var param={};
+        param["antiVerifyCheck"]=document.querySelector("#antivalue").value.substr(1);
+        param["imgw"]= document.querySelector("#antiimg").width;
+        $.ajax({
+            type: "POST",
+            url: window.location.href,
+            dataType: "json",
+            data: param,
+            headers : {
+                'app': '{{.appName}}',
+            },
+            success: function(res){
+                window.location.reload()
+            }
+        });
+    }
+</script>
+</html>

+ 314 - 0
resources/antiRes/page/verify.html

@@ -0,0 +1,314 @@
+<html>
+<head>
+    <title>验证码</title>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1"/>
+    <meta name="renderer" content="webkit">
+    <meta name="viewport"
+          content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
+    <script src="/js/jquery.js"></script>
+    <meta content="telephone=no" name="format-detection"/>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1"/>
+    <meta name="renderer" content="webkit">
+    <meta name="baidu-site-verification" content="cSFG2PMaYX"/>
+    <meta name="applicable-device" content="pc,mobile"/>
+    <link href="/css/bootstrap.min.css" rel="stylesheet">
+    <link href="/css/bootswatch.min.css" rel="stylesheet">
+    <link href="/css/font.css" rel="stylesheet">
+    <link href="/css/jy.css" rel="stylesheet">
+    <link href="/css/common.css" rel="stylesheet">
+    <link rel="stylesheet" href="/css/unicorn.main.css"/>
+    <link rel="stylesheet" href="/css/unicorn.grey.css"/>
+    <script src="/js/jquery.js"></script>
+    <script src="/js/n_rem.js"></script>
+    <link href="/css/pc.css" rel="stylesheet">
+    <style>
+        body {
+            min-width: auto;
+        }
+
+        @media only screen and (min-width: 1200px) {
+            body {
+                min-width: 1200px;
+            }
+
+            .verify_logo {
+                width: 350px;
+                display: block;
+            }
+
+            .verify-body {
+                width: 350px;
+                margin: 0 auto;
+                text-align: center;
+                padding: 134px 0;
+            }
+
+            .verify-content {
+                border: #F5F5F5 solid 1px;
+                margin: auto;
+                width: 260pt;
+                background-color: #FFFFFF;
+                box-shadow: 1px 1px 1px 1px grey;
+                padding: 10pt;
+                display: flex;
+                flex-direction: column;
+            }
+
+            .public-nav {
+                display: "" !important;
+            }
+
+            .j-bottom {
+                display: "" !important;
+            }
+        }
+
+        @media only screen and (max-width: 1200px) {
+            .verify_logo {
+                width: 7rem;
+                display: block;
+            }
+
+            .verify-body {
+                width: 7rem;
+                margin: 0 auto;
+                text-align: center;
+                padding: 2.38rem 0 0 0;
+            }
+
+            #antiimg {
+                width: 100%;
+            }
+
+            .verify-content {
+                border: #F5F5F5 solid 1px;
+                margin: auto;
+                width: 7rem;
+                background-color: #FFFFFF;
+                box-shadow: 1px 1px 1px 1px grey;
+                padding: 10pt;
+                display: flex;
+                flex-direction: column;
+            }
+
+            .public-nav {
+                display: none !important;
+            }
+
+            .j-bottom {
+                display: none !important;
+            }
+
+        }
+
+        .public-nav {
+            border-bottom: 1px solid #e0e0e0 !important;
+        }
+
+        .fr {
+            float: right;
+        }
+
+        .logo img {
+            width: 74px;
+        }
+
+
+        .verify-content .word {
+            text-align: left;
+        }
+
+        .footBtn {
+            margin-top: 5pt;
+            display: flex;
+            flex-direction: row;
+            justify-content: space-between;
+        }
+
+        /*j-wx-code Start*/
+        .j-wx-code {
+            width: 335px;
+            height: 355px;
+            background-color: #fff;
+            -webkit-border-radius: 6px;
+            -moz-border-radius: 6px;
+            border-radius: 6px;
+            position: relative;
+            /*margin: 100px auto;*/
+        }
+
+        .j-wx-code > .code-close {
+            width: 40px;
+            height: 40px;
+            position: absolute;
+            right: -20px;
+            top: -20px;
+            cursor: pointer;
+            -webkit-transition: all 1s;
+            -o-transition: all 1s;
+            -moz-transition: all 1s;
+            transition: all 1s;
+        }
+
+        .j-wx-code > .code-close:hover {
+            -webkit-transform: scale(1.2);
+            -moz-transform: scale(1.2);
+            -ms-transform: scale(1.2);
+            -o-transform: scale(1.2);
+            transform: scale(1.2);
+        }
+
+        .j-wx-code > .code-title {
+            height: 82px;
+            background: url(/images/j-wx-code-title.png) center center no-repeat;
+            -webkit-animation: moveYun 15s infinite linear both;
+            -moz-animation: moveYun 15s infinite linear both;
+            -o-animation: moveYun 15s infinite linear both;
+            animation: moveYun 15s infinite linear both;
+        }
+
+        .j-wx-code > .code-wxm {
+            text-align: center;
+            margin-bottom: -6px;
+            margin-top: -16px;
+
+        }
+
+        .j-wx-code > .code-wxm > img {
+            width: 200px;
+            height: 200px;
+            margin-top: -5px;
+        }
+
+        .j-wx-code > .code-text {
+            text-align: center;
+        }
+
+        .j-wx-code > .code-bottom {
+            width: 470px;
+            height: 211px;
+            position: absolute;
+            bottom: -113px;
+            left: -73px;
+            background: url(/images/j-wx-code-bottom.png) 0 0 no-repeat;
+        }
+
+        .j-wx-code > .code-bottom > img {
+            position: absolute;
+            left: 280px;
+            top: 88px;
+            -webkit-animation: codeWxMove 10s linear both;
+            -moz-animation: codeWxMove 10s linear both;
+            -o-animation: codeWxMove 10s linear both;
+            animation: codeWxMove 10s linear both;
+            -webkit-animation-fill-mode: forwards;
+            -moz-animation-fill-mode: forwards;
+            -o-animation-fill-mode: forwards;
+            animation-fill-mode: forwards
+        }
+
+        #antiVerify > div:first-child {
+            display: none;
+        }
+
+        #antiVerify > div:nth-child(2) {
+            max-width: 360px !important;
+            margin: 0 auto !important;
+            position: unset !important;
+            top: unset !important;
+            left: unset !important;
+            z-index: unset !important;
+            transform:unset !important;
+        }
+
+        #antiVerify > div:nth-child(2) > div:nth-child(1),
+        #antiVerify > div:nth-child(2) > div:nth-child(2),
+        #antiVerify > div:nth-child(2) > div:nth-child(1) > img {
+            max-width: 360px !important;
+        }
+    </style>
+</head>
+<body>
+<!-- content -->
+<div class="verify-body">
+    <div id="antiVerify">
+        <div></div>
+        <div style="margin: 0 auto;max-width: 360px;">
+            <div style="width: 90vw;max-width: 360px;">
+                <img style="width: 90vw;max-width: 360px;" src="/antiRes/images/verify_logo.png">
+            </div>
+            <div style="width: 90vw;max-width: 360px;border: #F5F5F5 solid 1px;margin: auto;background-color: #FFFFFF;box-shadow: 1px 1px 1px 1px grey;padding: 10pt;display: flex;flex-direction: column;">
+                <div style="margin-bottom:8pt">
+                    <div>请在下图依次点击:<span>{{.textVerify}}</span></div>
+                </div>
+                <div style="position:relative;width:100%">
+                    <img id="antiimg" onclick="antiAdd(event,this);"
+                         src="data:image/png;base64,{{.imgData}}"
+                         style="width:100%"><input type="hidden" id="antivalue"
+                                                   value=""></div>
+                <div style="margin-top: 1vh;display: flex;flex-direction: row;justify-content: space-between;">
+                    <img style="argin-left: 8pt;width: 25pt;height: 25pt;float: left;cursor: hand;"
+                         onclick="antiReload()" src="/antiRes/images/flush.png">
+                    <div>
+                        <button style="background-color: #24C0D7;text-align: center;vertical-align: middle;touch-action: manipulation;cursor: pointer;background-image: none;border: 1px solid transparent;white-space: nowrap;padding: 8px 12px;font-size: 14px;line-height: 1.42857143;border-radius: 4px;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;color: #FFFFFF;position: relative;outline-width: 0px;box-shadow: none !important;"
+                                onclick="antiReload(1);">确定
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+</div>
+<img src="/images/pc_6.png" class="backTop" id="backTop">
+
+<script>
+    //验证码方法
+    function antiAdd(event, obj) {
+        if (obj.parentNode.querySelectorAll(".imgs").length < 3) {
+            var offsetX = event.pageX - (obj.getBoundingClientRect().left + document.body.scrollLeft);
+            var offsetY = event.pageY - (obj.getBoundingClientRect().top + document.body.scrollTop);
+            var offx = parseInt(offsetX);
+            var offy = parseInt(offsetY);
+            var icon = "<img onclick='antiRemove(this)' class='imgs' src='/antiRes/images/hoverclick.png' "
+                + "style='position:absolute;top:" + (offsetY - 8) + "px;left:" + (offsetX - 8) + "px;'   offx=" + offx + " offy=" + offy + " />";
+            obj.parentNode.innerHTML += icon
+            document.querySelector("#antivalue").value += (";" + offx + "," + offy)
+        }
+    }
+
+    //删除验证
+    function antiRemove(obj) {
+        var offx = obj.getAttribute("offx");
+        var offy = obj.getAttribute("offy");
+        document.querySelector("#antivalue").value = document.querySelector("#antivalue").value.replace((";" + offx + "," + offy), "");
+        obj.parentNode.removeChild(obj);
+    }
+
+    //验证码提交
+    function antiReload(flag) {
+        if (flag === 1) {
+            var param = {};
+            param["antiVerifyCheck"] = document.querySelector("#antivalue").value.substr(1);
+            param["imgw"] = $("#antiimg").width();
+            $.ajax({
+                type: "POST",
+                url: window.location.href,
+                dataType: "json",
+                data: param,
+                headers: {
+                    'app': '{{.appName}}',
+                },
+                success: function (res) {
+                    window.location.reload()
+                }
+            });
+        } else {
+            window.location.reload();
+        }
+    }
+
+</script>
+</body>
+</html>

二进制
resources/verify/fontBg/100.jpg


二进制
resources/verify/fontBg/101.jpg


二进制
resources/verify/fontBg/102.jpg


二进制
resources/verify/fontBg/81.jpg


二进制
resources/verify/fontBg/82.jpg


二进制
resources/verify/fontBg/83.jpg


二进制
resources/verify/fontBg/84.jpg


二进制
resources/verify/fontBg/85.jpg


二进制
resources/verify/fontBg/86.jpg


二进制
resources/verify/fontBg/87.jpg


二进制
resources/verify/fontBg/88.jpg


二进制
resources/verify/fontBg/90.jpg


二进制
resources/verify/fontBg/91.jpg


二进制
resources/verify/fontBg/92.jpg


二进制
resources/verify/fontBg/93.jpg


二进制
resources/verify/fontBg/94.jpg


二进制
resources/verify/fontBg/95.jpg


二进制
resources/verify/fontBg/96.jpg


二进制
resources/verify/fontBg/97.jpg


二进制
resources/verify/fontBg/98.jpg


二进制
resources/verify/fontBg/99.jpg


二进制
resources/verify/fontType/STZHONGS.TTF


二进制
resources/verify/imageBg/10.jpg


二进制
resources/verify/imageBg/15.jpg


二进制
resources/verify/imageBg/17.jpg


二进制
resources/verify/imageBg/18.jpg


二进制
resources/verify/imageBg/2.jpg


二进制
resources/verify/imageBg/23.jpg


二进制
resources/verify/imageBg/24.jpg


二进制
resources/verify/imageBg/3.jpg


二进制
resources/verify/imageBg/32.jpg


二进制
resources/verify/imageBg/37.jpg


二进制
resources/verify/imageBg/39.jpg


二进制
resources/verify/imageBg/4.jpg


二进制
resources/verify/imageBg/65.jpg


二进制
resources/verify/imageBg/66.jpg


二进制
resources/verify/imageBg/67.jpg


二进制
resources/verify/imageBg/68.jpg


二进制
resources/verify/imageBg/69.jpg


二进制
resources/verify/imageBg/7.jpg


二进制
resources/verify/imageBg/70.jpg


二进制
resources/verify/imageBg/72.jpg


二进制
resources/verify/imageBg/73.jpg


二进制
resources/verify/imageBg/74.jpg


二进制
resources/verify/imageBg/75.jpg


二进制
resources/verify/imageBg/76.jpg


二进制
resources/verify/imageBg/77.jpg


二进制
resources/verify/imageBg/78.jpg


二进制
resources/verify/imageBg/8.jpg