@@ -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 | |||||
} |
@@ -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 | |||||
} |
@@ -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") | |||||
} |
@@ -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 | |||||
} |
@@ -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 "" | |||||
} |
@@ -0,0 +1,7 @@ | |||||
package md | |||||
// 缓存key统一管理 | |||||
const ( | |||||
UserJwtTokenKey = "%s:bakery_big_data_user_jwt_token" // jwt, 占位符:ip, user:id | |||||
JwtTokenCacheTime = 3600 * 24 * 1 | |||||
) |
@@ -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:"手机号"` | |||||
} |
@@ -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() | |||||
} |
@@ -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() | |||||
} |
@@ -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) | |||||
} |
@@ -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() | |||||
} | |||||
} |
@@ -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 | |||||
} |
@@ -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 | |||||
} |
@@ -37,6 +37,7 @@ func Init() *gin.Engine { | |||||
r.Use(mw.Cors) | r.Use(mw.Cors) | ||||
AdminRoute(r.Group("/api/admin")) | AdminRoute(r.Group("/api/admin")) | ||||
IpadInit(r.Group("/api/ipad")) | IpadInit(r.Group("/api/ipad")) | ||||
BigDataInit(r.Group("/api/bigData")) | |||||
return r | return r | ||||
} | } | ||||
@@ -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) //检测登录状态 | |||||
} |
@@ -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 | 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 | // 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 := db.SysCfgDb{} | ||||
sysCfgDb.Set() | sysCfgDb.Set() | ||||
res := map[string]string{} | res := map[string]string{} | ||||
cfgKey := fmt.Sprintf("%s:cfg_cache", masterId) | |||||
cfgKey := fmt.Sprintf("%s:cfg_cache") | |||||
err := cache.GetJson(cfgKey, &res) | err := cache.GetJson(cfgKey, &res) | ||||
if err != nil || len(res) == 0 { | if err != nil || len(res) == 0 { | ||||
@@ -79,36 +64,6 @@ func SysCfgFindComm(masterId string, keys ...string) map[string]string { | |||||
return tmp | 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) { | 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"} | 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"} | ||||