@@ -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 | |||||
} |
@@ -2,7 +2,6 @@ package alipay | |||||
import ( | import ( | ||||
"applet/app/cfg" | "applet/app/cfg" | ||||
"applet/app/pay/md" | |||||
"applet/app/utils/logx" | "applet/app/utils/logx" | ||||
"fmt" | "fmt" | ||||
@@ -14,7 +14,7 @@ func GenToken(admId int, username string) (string, error) { | |||||
Username: username, | Username: username, | ||||
StandardClaims: jwt.StandardClaims{ | StandardClaims: jwt.StandardClaims{ | ||||
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间 | ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间 | ||||
Issuer: "smart_canteen", // 签发人 | |||||
Issuer: "egg_admin", // 签发人 | |||||
}, | }, | ||||
} | } | ||||
// 使用指定的签名方法创建签名对象 | // 使用指定的签名方法创建签名对象 | ||||
@@ -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 "" | |||||
} |
@@ -2,6 +2,10 @@ package md | |||||
// 缓存key统一管理, %s格式化为masterId | // 缓存key统一管理, %s格式化为masterId | ||||
const ( | const ( | ||||
JwtTokenKey = "%s:egg_admin_jwt_token:%s" // jwt, 占位符:ip, admin:id | |||||
JwtTokenCacheTime = 3600 * 4 | |||||
AppCfgCacheKey = "cfg_cache:%s" // 占位符: key的第一个字母 | AppCfgCacheKey = "cfg_cache:%s" // 占位符: key的第一个字母 | ||||
CfgCacheTime = 86400 | CfgCacheTime = 86400 | ||||
@@ -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"` | |||||
} |
@@ -48,6 +48,7 @@ func Init() *gin.Engine { | |||||
func route(r *gin.RouterGroup) { | func route(r *gin.RouterGroup) { | ||||
r.GET("/test", hdl.Demo) | r.GET("/test", hdl.Demo) | ||||
r.POST("/login", hdl.Login) | |||||
rInstitutionalManagement(r.Group("/institutionalManagement")) | rInstitutionalManagement(r.Group("/institutionalManagement")) | ||||
r.Use(mw.Auth) // 以下接口需要JWT验证 | r.Use(mw.Auth) // 以下接口需要JWT验证 | ||||
@@ -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 | |||||
} |
@@ -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 | |||||
} |