From bbf31428046155bbd7cc3fdd9211095113aaa705 Mon Sep 17 00:00:00 2001 From: dengbiao Date: Thu, 7 Nov 2024 17:57:23 +0800 Subject: [PATCH] add login api --- app/hdl/hdl_login.go | 59 ++++++++++++ app/lib/alipay/api.go | 1 - app/lib/auth/auth.go | 2 +- app/lib/validate/validate_comm.go | 33 +++++++ app/md/app_redis_key.go | 4 + app/md/md_login.go | 11 +++ app/router/router.go | 1 + app/svc/svc_login.go | 33 +++++++ app/utils/ip.go | 146 ++++++++++++++++++++++++++++++ 9 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 app/hdl/hdl_login.go create mode 100644 app/lib/validate/validate_comm.go create mode 100644 app/md/md_login.go create mode 100644 app/svc/svc_login.go create mode 100644 app/utils/ip.go diff --git a/app/hdl/hdl_login.go b/app/hdl/hdl_login.go new file mode 100644 index 0000000..57fa13b --- /dev/null +++ b/app/hdl/hdl_login.go @@ -0,0 +1,59 @@ +package hdl + +import ( + "applet/app/db" + "applet/app/e" + "applet/app/lib/validate" + "applet/app/md" + "applet/app/svc" + "applet/app/utils" + "code.fnuoos.com/EggPlanet/egg_models.git/src/implement" + "fmt" + "github.com/gin-gonic/gin" +) + +// Login 登陆 +// @Summary 登陆 +// @Tags 登录 +// @Description 登入 +// @Accept json +// @Produce json +// @Param req body md.LoginReq true "用户名密码" +// @Success 200 {object} md.LoginResponse "token" +// @Failure 400 {object} md.Response "具体错误" +// @Router /api/login [post] +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 + } + adminDb := implement.NewAdminDb(db.Db) + admin, err := adminDb.GetAdminByUserName(req.UserName) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err) + return + } + if admin == nil { + e.OutErr(c, e.ERR_NO_DATA, "账号不存在!") + return + } + if utils.Md5(req.PassWord) != admin.Password { + e.OutErr(c, e.ERR_INVALID_ARGS, "密码错误") + return + } + ip := utils.GetIP(c.Request) + key := fmt.Sprintf(md.JwtTokenKey, ip, utils.AnyToString(admin.AdmId)) + token, err := svc.HandleLoginToken(key, admin) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + e.OutSuc(c, md.LoginResponse{ + Token: token, + }, nil) + return +} diff --git a/app/lib/alipay/api.go b/app/lib/alipay/api.go index 9b54aac..8b71df2 100644 --- a/app/lib/alipay/api.go +++ b/app/lib/alipay/api.go @@ -2,7 +2,6 @@ package alipay import ( "applet/app/cfg" - "applet/app/pay/md" "applet/app/utils/logx" "fmt" diff --git a/app/lib/auth/auth.go b/app/lib/auth/auth.go index 6d86b8e..a5658db 100644 --- a/app/lib/auth/auth.go +++ b/app/lib/auth/auth.go @@ -14,7 +14,7 @@ func GenToken(admId int, username string) (string, error) { Username: username, StandardClaims: jwt.StandardClaims{ ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间 - Issuer: "smart_canteen", // 签发人 + Issuer: "egg_admin", // 签发人 }, } // 使用指定的签名方法创建签名对象 diff --git a/app/lib/validate/validate_comm.go b/app/lib/validate/validate_comm.go new file mode 100644 index 0000000..9305d9e --- /dev/null +++ b/app/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/md/app_redis_key.go b/app/md/app_redis_key.go index ac15417..7bf090c 100644 --- a/app/md/app_redis_key.go +++ b/app/md/app_redis_key.go @@ -2,6 +2,10 @@ package md // 缓存key统一管理, %s格式化为masterId const ( + JwtTokenKey = "%s:egg_admin_jwt_token:%s" // jwt, 占位符:ip, admin:id + + JwtTokenCacheTime = 3600 * 4 + AppCfgCacheKey = "cfg_cache:%s" // 占位符: key的第一个字母 CfgCacheTime = 86400 diff --git a/app/md/md_login.go b/app/md/md_login.go new file mode 100644 index 0000000..271a126 --- /dev/null +++ b/app/md/md_login.go @@ -0,0 +1,11 @@ +package md + +type LoginReq struct { + UserName string `json:"username" binding:"required" example:"登录账号"` + PassWord string `json:"password" binding:"required" example:"登录密码"` + Code string `json:"code" example:"验证码"` +} + +type LoginResponse struct { + Token string `json:"token"` +} diff --git a/app/router/router.go b/app/router/router.go index 4d7868d..a34b248 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -48,6 +48,7 @@ func Init() *gin.Engine { func route(r *gin.RouterGroup) { r.GET("/test", hdl.Demo) + r.POST("/login", hdl.Login) rInstitutionalManagement(r.Group("/institutionalManagement")) r.Use(mw.Auth) // 以下接口需要JWT验证 diff --git a/app/svc/svc_login.go b/app/svc/svc_login.go new file mode 100644 index 0000000..b3bc300 --- /dev/null +++ b/app/svc/svc_login.go @@ -0,0 +1,33 @@ +package svc + +import ( + "applet/app/lib/auth" + "applet/app/md" + "applet/app/utils/cache" + "applet/app/utils/logx" + "code.fnuoos.com/EggPlanet/egg_models.git/src/model" +) + +func HandleLoginToken(cacheKey string, admin *model.Admin) (string, error) { + // 获取之前生成的token + token, err := cache.GetString(cacheKey) + if err != nil { + _ = logx.Error(err) + } + // 没有获取到 + if err != nil || token == "" { + // 生成token + token, err = auth.GenToken(admin.AdmId, admin.Username) + 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/utils/ip.go b/app/utils/ip.go new file mode 100644 index 0000000..6ed8286 --- /dev/null +++ b/app/utils/ip.go @@ -0,0 +1,146 @@ +package utils + +import ( + "errors" + "math" + "net" + "net/http" + "strings" +) + +func GetIP(r *http.Request) string { + ip := ClientPublicIP(r) + if ip == "" { + ip = ClientIP(r) + } + if ip == "" { + ip = "0000" + } + return ip +} + +// HasLocalIPddr 检测 IP 地址字符串是否是内网地址 +// Deprecated: 此为一个错误名称错误拼写的函数,计划在将来移除,请使用 HasLocalIPAddr 函数 +func HasLocalIPddr(ip string) bool { + return HasLocalIPAddr(ip) +} + +// HasLocalIPAddr 检测 IP 地址字符串是否是内网地址 +func HasLocalIPAddr(ip string) bool { + return HasLocalIP(net.ParseIP(ip)) +} + +// HasLocalIP 检测 IP 地址是否是内网地址 +// 通过直接对比ip段范围效率更高,详见:https://github.com/thinkeridea/go-extend/issues/2 +func HasLocalIP(ip net.IP) bool { + if ip.IsLoopback() { + return true + } + + ip4 := ip.To4() + if ip4 == nil { + return false + } + + return ip4[0] == 10 || // 10.0.0.0/8 + (ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) || // 172.16.0.0/12 + (ip4[0] == 169 && ip4[1] == 254) || // 169.254.0.0/16 + (ip4[0] == 192 && ip4[1] == 168) // 192.168.0.0/16 +} + +// ClientIP 尽最大努力实现获取客户端 IP 的算法。 +// 解析 X-Real-IP 和 X-Forwarded-For 以便于反向代理(nginx 或 haproxy)可以正常工作。 +func ClientIP(r *http.Request) string { + ip := strings.TrimSpace(strings.Split(r.Header.Get("X-Forwarded-For"), ",")[0]) + if ip != "" { + return ip + } + + ip = strings.TrimSpace(r.Header.Get("X-Real-Ip")) + if ip != "" { + return ip + } + + if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil { + return ip + } + + return "" +} + +// ClientPublicIP 尽最大努力实现获取客户端公网 IP 的算法。 +// 解析 X-Real-IP 和 X-Forwarded-For 以便于反向代理(nginx 或 haproxy)可以正常工作。 +func ClientPublicIP(r *http.Request) string { + var ip string + for _, ip = range strings.Split(r.Header.Get("X-Forwarded-For"), ",") { + if ip = strings.TrimSpace(ip); ip != "" && !HasLocalIPAddr(ip) { + return ip + } + } + + if ip = strings.TrimSpace(r.Header.Get("X-Real-Ip")); ip != "" && !HasLocalIPAddr(ip) { + return ip + } + + if ip = RemoteIP(r); !HasLocalIPAddr(ip) { + return ip + } + + return "" +} + +// RemoteIP 通过 RemoteAddr 获取 IP 地址, 只是一个快速解析方法。 +func RemoteIP(r *http.Request) string { + ip, _, _ := net.SplitHostPort(r.RemoteAddr) + return ip +} + +// IPString2Long 把ip字符串转为数值 +func IPString2Long(ip string) (uint, error) { + b := net.ParseIP(ip).To4() + if b == nil { + return 0, errors.New("invalid ipv4 format") + } + + return uint(b[3]) | uint(b[2])<<8 | uint(b[1])<<16 | uint(b[0])<<24, nil +} + +// Long2IPString 把数值转为ip字符串 +func Long2IPString(i uint) (string, error) { + if i > math.MaxUint32 { + return "", errors.New("beyond the scope of ipv4") + } + + ip := make(net.IP, net.IPv4len) + ip[0] = byte(i >> 24) + ip[1] = byte(i >> 16) + ip[2] = byte(i >> 8) + ip[3] = byte(i) + + return ip.String(), nil +} + +// IP2Long 把net.IP转为数值 +func IP2Long(ip net.IP) (uint, error) { + b := ip.To4() + if b == nil { + return 0, errors.New("invalid ipv4 format") + } + + return uint(b[3]) | uint(b[2])<<8 | uint(b[1])<<16 | uint(b[0])<<24, nil +} + +// Long2IP 把数值转为net.IP +func Long2IP(i uint) (net.IP, error) { + if i > math.MaxUint32 { + return nil, errors.New("beyond the scope of ipv4") + } + + ip := make(net.IP, net.IPv4len) + ip[0] = byte(i >> 24) + ip[1] = byte(i >> 16) + ip[2] = byte(i >> 8) + ip[3] = byte(i) + + return ip, nil +}