@@ -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) | |||
AdminRoute(r.Group("/api/admin")) | |||
IpadInit(r.Group("/api/ipad")) | |||
BigDataInit(r.Group("/api/bigData")) | |||
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 | |||
} | |||
// 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"} | |||