From e476ed246ad2505a40f30825d13b14b9ffff5541 Mon Sep 17 00:00:00 2001 From: DengBiao <2319963317@qq.com> Date: Wed, 27 Mar 2024 17:53:35 +0800 Subject: [PATCH] update --- app/bigData/hdl/hdl_demo.go | 12 +++++ app/bigData/hdl/hdl_login.go | 59 +++++++++++++++++++++++ app/bigData/lib/auth/auth.go | 39 +++++++++++++++ app/bigData/lib/auth/base.go | 19 ++++++++ app/bigData/lib/validate/validate_comm.go | 33 +++++++++++++ app/bigData/md/md_app_redis_key.go | 7 +++ app/bigData/md/md_login.go | 29 +++++++++++ app/bigData/mw/mw_auth.go | 30 ++++++++++++ app/bigData/mw/mw_cors.go | 29 +++++++++++ app/bigData/mw/mw_limiter.go | 58 ++++++++++++++++++++++ app/bigData/mw/mw_recovery.go | 57 ++++++++++++++++++++++ app/bigData/svc/svc_auth.go | 44 +++++++++++++++++ app/bigData/svc/svc_login.go | 32 ++++++++++++ app/router/admin_router.go | 1 + app/router/big_data_router.go | 13 +++++ app/svc/svc_sys_cfg_get.go | 53 ++------------------ 16 files changed, 466 insertions(+), 49 deletions(-) create mode 100644 app/bigData/hdl/hdl_demo.go create mode 100644 app/bigData/hdl/hdl_login.go create mode 100644 app/bigData/lib/auth/auth.go create mode 100644 app/bigData/lib/auth/base.go create mode 100644 app/bigData/lib/validate/validate_comm.go create mode 100644 app/bigData/md/md_app_redis_key.go create mode 100644 app/bigData/md/md_login.go create mode 100644 app/bigData/mw/mw_auth.go create mode 100644 app/bigData/mw/mw_cors.go create mode 100644 app/bigData/mw/mw_limiter.go create mode 100644 app/bigData/mw/mw_recovery.go create mode 100644 app/bigData/svc/svc_auth.go create mode 100644 app/bigData/svc/svc_login.go create mode 100644 app/router/big_data_router.go diff --git a/app/bigData/hdl/hdl_demo.go b/app/bigData/hdl/hdl_demo.go new file mode 100644 index 0000000..5fa37f6 --- /dev/null +++ b/app/bigData/hdl/hdl_demo.go @@ -0,0 +1,12 @@ +package hdl + +import ( + "applet/app/e" + "github.com/gin-gonic/gin" +) + +// Demo 测试 +func Demo(c *gin.Context) { + e.OutSuc(c, "success", nil) + return +} diff --git a/app/bigData/hdl/hdl_login.go b/app/bigData/hdl/hdl_login.go new file mode 100644 index 0000000..591eadf --- /dev/null +++ b/app/bigData/hdl/hdl_login.go @@ -0,0 +1,59 @@ +package hdl + +import ( + "applet/app/bigData/lib/validate" + "applet/app/bigData/md" + "applet/app/bigData/svc" + "applet/app/e" + "applet/app/enum" + svc2 "applet/app/svc" + "applet/app/utils" + "fmt" + "github.com/gin-gonic/gin" +) + +func Login(c *gin.Context) { + var req md.LoginReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = validate.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + sysCfg := svc2.SysCfgFind(enum.BigDataScreenAccount, enum.BigDataScreenPassword) + if sysCfg[enum.BigDataScreenAccount] == "" || sysCfg[enum.BigDataScreenPassword] == "" { + e.OutErr(c, e.ERR_NO_DATA, "用户信息不存在") + return + } + + if sysCfg[enum.BigDataScreenAccount] != req.Account || sysCfg[enum.BigDataScreenPassword] != req.Password { + e.OutErr(c, e.ERR_NO_DATA, "账号或密码错误") + return + } + + ip := utils.GetIP(c.Request) + key := fmt.Sprintf(md.UserJwtTokenKey, ip) + token, err := svc.HandleLoginToken(key, &md.User{ + Account: req.Account, + Password: req.Password, + }) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, md.LoginResponse{ + Token: token, + }, nil) + return +} + +func UserInfo(c *gin.Context) { + //1、获取用户信息 + userInfo := svc.GetUser(c) + + e.OutSuc(c, map[string]interface{}{ + "user_info": userInfo, + }, nil) + return +} diff --git a/app/bigData/lib/auth/auth.go b/app/bigData/lib/auth/auth.go new file mode 100644 index 0000000..903a68f --- /dev/null +++ b/app/bigData/lib/auth/auth.go @@ -0,0 +1,39 @@ +package auth + +import ( + "errors" + "github.com/dgrijalva/jwt-go" + "time" +) + +// GenToken 生成JWT +func GenToken(account, password string) (string, error) { + // 创建一个我们自己的声明 + c := JWTUser{ + Account: account, + Password: password, + StandardClaims: jwt.StandardClaims{ + ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间 + Issuer: "bakery", // 签发人 + }, + } + // 使用指定的签名方法创建签名对象 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) + // 使用指定的secret签名并获得完整的编码后的字符串token + return token.SignedString(Secret) +} + +// ParseToken 解析JWT +func ParseToken(tokenString string) (*JWTUser, error) { + // 解析token + token, err := jwt.ParseWithClaims(tokenString, &JWTUser{}, func(token *jwt.Token) (i interface{}, err error) { + return Secret, nil + }) + if err != nil { + return nil, err + } + if claims, ok := token.Claims.(*JWTUser); ok && token.Valid { // 校验token + return claims, nil + } + return nil, errors.New("invalid token") +} diff --git a/app/bigData/lib/auth/base.go b/app/bigData/lib/auth/base.go new file mode 100644 index 0000000..ff1c346 --- /dev/null +++ b/app/bigData/lib/auth/base.go @@ -0,0 +1,19 @@ +package auth + +import ( + "time" + + "github.com/dgrijalva/jwt-go" +) + +// TokenExpireDuration is jwt 过期时间 +const TokenExpireDuration = time.Hour * 4380 + +var Secret = []byte("bakery_big_data") + +// JWTUser 如果想要保存更多信息,都可以添加到这个结构体中 +type JWTUser struct { + Account string `json:"account"` + Password string `json:"password"` + jwt.StandardClaims +} diff --git a/app/bigData/lib/validate/validate_comm.go b/app/bigData/lib/validate/validate_comm.go new file mode 100644 index 0000000..9305d9e --- /dev/null +++ b/app/bigData/lib/validate/validate_comm.go @@ -0,0 +1,33 @@ +package validate + +import ( + "applet/app/e" + "applet/app/utils" + "applet/app/utils/logx" + "encoding/json" + "fmt" + "github.com/go-playground/validator/v10" +) + +func HandleValidateErr(err error) error { + switch err.(type) { + case *json.UnmarshalTypeError: + return e.NewErr(e.ERR_UNMARSHAL, "参数格式错误") + case validator.ValidationErrors: + errs := err.(validator.ValidationErrors) + transMsgMap := errs.Translate(utils.ValidatorTrans) // utils.ValidatorTrans \app\utils\validator_err_trans.go::ValidatorTransInit初始化获得 + transMsgOne := transMsgMap[GetOneKeyOfMapString(transMsgMap)] + return e.NewErr(e.ERR_INVALID_ARGS, transMsgOne) + default: + _ = logx.Error(err) + return e.NewErr(e.ERR, fmt.Sprintf("validate request params, err:%v\n", err)) + } +} + +// GetOneKeyOfMapString 取出Map的一个key +func GetOneKeyOfMapString(collection map[string]string) string { + for k := range collection { + return k + } + return "" +} diff --git a/app/bigData/md/md_app_redis_key.go b/app/bigData/md/md_app_redis_key.go new file mode 100644 index 0000000..de9939a --- /dev/null +++ b/app/bigData/md/md_app_redis_key.go @@ -0,0 +1,7 @@ +package md + +// 缓存key统一管理 +const ( + UserJwtTokenKey = "%s:bakery_big_data_user_jwt_token" // jwt, 占位符:ip, user:id + JwtTokenCacheTime = 3600 * 24 * 1 +) diff --git a/app/bigData/md/md_login.go b/app/bigData/md/md_login.go new file mode 100644 index 0000000..771e96b --- /dev/null +++ b/app/bigData/md/md_login.go @@ -0,0 +1,29 @@ +package md + +type LoginReq struct { + Account string `json:"account" binding:"required" label:"账号"` + Password string `json:"password" binding:"required" label:"密码"` +} + +type User struct { + Account string `json:"account" label:"账号"` + Password string `json:"password" label:"密码"` +} + +type LoginResponse struct { + Token string `json:"token"` +} + +type WxAppletLoginResponse struct { + OpenId string `json:"open_id"` + UnionId string `json:"union_id"` +} + +type RegisterReq struct { + UserId string `json:"user_id" label:"支付宝用户的唯一userId"` + OpenId string `json:"open_id" label:"微信小程序openid"` + UnionId string `json:"union_id" label:"微信unionId"` + Nickname string `json:"nickname" label:"支付宝昵称"` + Avatar string `json:"avatar" label:"支付宝头像"` + Phone string `json:"phone" binding:"required" label:"手机号"` +} diff --git a/app/bigData/mw/mw_auth.go b/app/bigData/mw/mw_auth.go new file mode 100644 index 0000000..606b390 --- /dev/null +++ b/app/bigData/mw/mw_auth.go @@ -0,0 +1,30 @@ +package mw + +import ( + "applet/app/bigData/svc" + "applet/app/e" + "github.com/gin-gonic/gin" +) + +// 检查权限, 签名等等 +func Auth(c *gin.Context) { + user, err := svc.CheckUser(c) + if err != nil { + switch err.(type) { + case e.E: + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + default: + e.OutErr(c, e.ERR_UNAUTHORIZED, err.Error()) + return + } + } + if user == nil { + e.OutErr(c, e.ERR_NOT_AUTH, "当前用户信息失效") + return + } + // 将当前请求的username信息保存到请求的上下文c上 + c.Set("user", user) + c.Next() +} diff --git a/app/bigData/mw/mw_cors.go b/app/bigData/mw/mw_cors.go new file mode 100644 index 0000000..3433553 --- /dev/null +++ b/app/bigData/mw/mw_cors.go @@ -0,0 +1,29 @@ +package mw + +import ( + "github.com/gin-gonic/gin" +) + +// cors跨域 +func Cors(c *gin.Context) { + // 放行所有OPTIONS方法 + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + origin := c.Request.Header.Get("Origin") // 请求头部 + if origin != "" { + c.Header("Access-Control-Allow-Origin", origin) // 这是允许访问来源域 + c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE,UPDATE") // 服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求 + // header的类型 + c.Header("Access-Control-Allow-Headers", "Authorization,Content-Length,X-CSRF-Token,Token,session,X_Requested_With,Accept,Origin,Host,Connection,Accept-Encoding,Accept-Language,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Pragma,X-Mx-ReqToken") + // 允许跨域设置,可以返回其他子段 + // 跨域关键设置 让浏览器可以解析 + c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") + c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒 + c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true + c.Set("Content-Type", "Application/json") // 设置返回格式是json + } + c.Next() +} diff --git a/app/bigData/mw/mw_limiter.go b/app/bigData/mw/mw_limiter.go new file mode 100644 index 0000000..4eb5299 --- /dev/null +++ b/app/bigData/mw/mw_limiter.go @@ -0,0 +1,58 @@ +package mw + +import ( + "bytes" + "io/ioutil" + + "github.com/gin-gonic/gin" + + "applet/app/utils" + "applet/app/utils/cache" +) + +// 限流器 +func Limiter(c *gin.Context) { + limit := 100 // 限流次数 + ttl := 1 // 限流过期时间 + ip := c.ClientIP() + // 读取token或者ip + token := c.GetHeader("Authorization") + // 判断是否已经超出限额次数 + method := c.Request.Method + host := c.Request.Host + uri := c.Request.URL.String() + + buf := make([]byte, 2048) + num, _ := c.Request.Body.Read(buf) + body := buf[:num] + // Write body back + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + Md5 := utils.Md5(ip + token + method + host + uri + string(body)) + if cache.Exists(Md5) { + c.AbortWithStatusJSON(429, gin.H{ + "code": 429, + "msg": "don't repeat the request", + "data": struct{}{}, + }) + return + } + // 2s后没返回自动释放 + go cache.SetEx(Md5, "0", ttl) + key := "LIMITER_" + ip + reqs, _ := cache.GetInt(key) + if reqs >= limit { + c.AbortWithStatusJSON(429, gin.H{ + "code": 429, + "msg": "too many requests", + "data": struct{}{}, + }) + return + } + if reqs > 0 { + go cache.Incr(key) + } else { + go cache.SetEx(key, 1, ttl) + } + c.Next() + go cache.Del(Md5) +} diff --git a/app/bigData/mw/mw_recovery.go b/app/bigData/mw/mw_recovery.go new file mode 100644 index 0000000..b32cc82 --- /dev/null +++ b/app/bigData/mw/mw_recovery.go @@ -0,0 +1,57 @@ +package mw + +import ( + "net" + "net/http" + "net/http/httputil" + "os" + "runtime/debug" + "strings" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +func Recovery(logger *zap.Logger, stack bool) gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + var brokenPipe bool + if ne, ok := err.(*net.OpError); ok { + if se, ok := ne.Err.(*os.SyscallError); ok { + if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + brokenPipe = true + } + } + } + + httpRequest, _ := httputil.DumpRequest(c.Request, false) + if brokenPipe { + logger.Error(c.Request.URL.Path, + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + // If the connection is dead, we can't write a status to it. + c.Error(err.(error)) + c.Abort() + return + } + + if stack { + logger.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + zap.String("stack", string(debug.Stack())), + ) + } else { + logger.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + } + c.AbortWithStatus(http.StatusInternalServerError) + } + }() + c.Next() + } +} diff --git a/app/bigData/svc/svc_auth.go b/app/bigData/svc/svc_auth.go new file mode 100644 index 0000000..22e124c --- /dev/null +++ b/app/bigData/svc/svc_auth.go @@ -0,0 +1,44 @@ +package svc + +import ( + "applet/app/bigData/lib/auth" + "applet/app/bigData/md" + "errors" + "github.com/gin-gonic/gin" + "strings" +) + +func GetUser(c *gin.Context) *md.User { + user, _ := c.Get("user") + if user == nil { + return &md.User{ + Account: "", + Password: "", + } + } + return user.(*md.User) +} + +func CheckUser(c *gin.Context) (*md.User, error) { + token := c.GetHeader("Authorization") + if token == "" { + return nil, errors.New("token not exist") + } + // 按空格分割 + parts := strings.SplitN(token, " ", 2) + if !(len(parts) == 2 && parts[0] == "Bearer") { + return nil, errors.New("token format error") + } + // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 + mc, err := auth.ParseToken(parts[1]) + if err != nil { + return nil, err + } + + // 获取user + user := &md.User{ + Account: mc.Account, + Password: mc.Password, + } + return user, nil +} diff --git a/app/bigData/svc/svc_login.go b/app/bigData/svc/svc_login.go new file mode 100644 index 0000000..0a79a34 --- /dev/null +++ b/app/bigData/svc/svc_login.go @@ -0,0 +1,32 @@ +package svc + +import ( + "applet/app/bigData/lib/auth" + "applet/app/bigData/md" + "applet/app/utils/cache" + "applet/app/utils/logx" +) + +func HandleLoginToken(cacheKey string, user *md.User) (string, error) { + // 获取之前生成的token + token, err := cache.GetString(cacheKey) + if err != nil { + _ = logx.Error(err) + } + // 没有获取到 + if err != nil || token == "" { + // 生成token + token, err = auth.GenToken(user.Account, user.Password) + if err != nil { + return "", err + } + // 缓存token + _, err = cache.SetEx(cacheKey, token, md.JwtTokenCacheTime) + if err != nil { + return "", err + } + return token, nil + } + + return token, nil +} diff --git a/app/router/admin_router.go b/app/router/admin_router.go index f217172..b760a8a 100644 --- a/app/router/admin_router.go +++ b/app/router/admin_router.go @@ -37,6 +37,7 @@ func Init() *gin.Engine { r.Use(mw.Cors) AdminRoute(r.Group("/api/admin")) IpadInit(r.Group("/api/ipad")) + BigDataInit(r.Group("/api/bigData")) return r } diff --git a/app/router/big_data_router.go b/app/router/big_data_router.go new file mode 100644 index 0000000..62d9cee --- /dev/null +++ b/app/router/big_data_router.go @@ -0,0 +1,13 @@ +package router + +import ( + "applet/app/bigData/hdl" + "applet/app/ipad/mw" + "github.com/gin-gonic/gin" +) + +func BigDataInit(r *gin.RouterGroup) { + r.POST("/test", hdl.Demo) + r.Use(mw.Auth) //检测登录状态 + +} diff --git a/app/svc/svc_sys_cfg_get.go b/app/svc/svc_sys_cfg_get.go index 1edd7a0..324cffc 100644 --- a/app/svc/svc_sys_cfg_get.go +++ b/app/svc/svc_sys_cfg_get.go @@ -26,33 +26,18 @@ func SysCfgDel(c *gin.Context, key string) bool { } // 多条记录获取 -func SysCfgFind(c *gin.Context, keys ...string) map[string]string { - var masterId string - if c == nil { - masterId = "" - } else { - masterId = c.GetString("mid") - } - tmp := SysCfgFindComm(masterId, keys...) +func SysCfgFind(keys ...string) map[string]string { + tmp := SysCfgFindComm(keys...) return tmp } -// SysCfgGetByMasterId get one config by master id -func SysCfgGetByMasterId(masterId, key string) string { - res := SysCfgFindComm(masterId, key) - if _, ok := res[key]; !ok { - return "" - } - return res[key] -} - // SysCfgFindComm get cfg by master id -func SysCfgFindComm(masterId string, keys ...string) map[string]string { +func SysCfgFindComm(keys ...string) map[string]string { sysCfgDb := db.SysCfgDb{} sysCfgDb.Set() res := map[string]string{} - cfgKey := fmt.Sprintf("%s:cfg_cache", masterId) + cfgKey := fmt.Sprintf("%s:cfg_cache") err := cache.GetJson(cfgKey, &res) if err != nil || len(res) == 0 { @@ -79,36 +64,6 @@ func SysCfgFindComm(masterId string, keys ...string) map[string]string { return tmp } -// 多条记录获取 -func EgSysCfgFind(keys ...string) map[string]string { - sysCfgDb := db.SysCfgDb{} - sysCfgDb.Set() - res := map[string]string{} - if len(res) == 0 { - cfgList, _ := sysCfgDb.SysCfgGetAll() - if cfgList == nil { - return nil - } - for _, v := range *cfgList { - res[v.Key] = v.Val - } - // 先不设置缓存 - // cache.SetJson(md.KEY_SYS_CFG_CACHE, res, 60) - } - if len(keys) == 0 { - return res - } - tmp := map[string]string{} - for _, v := range keys { - if val, ok := res[v]; ok { - tmp[v] = val - } else { - tmp[v] = "" - } - } - return tmp -} - // 清理系统配置信息 func SysCfgCleanCache(c *gin.Context) { var tmp = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}