feat(auth): 添加用户登录功能
This commit is contained in:
1 parent
92a9e20934
commit
63c8f2980b
10 files changed
+210
-18
No files matched your search
@@ -0,0 +1,4 @@
|
||||
package auth_api
|
||||
|
||||
type AuthApi struct {
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package auth_api
|
||||
|
||||
import (
|
||||
"EnGin/internal/global"
|
||||
"EnGin/internal/middleware"
|
||||
"EnGin/internal/service/auth_ser"
|
||||
"EnGin/internal/utils/jwts"
|
||||
"EnGin/internal/utils/res"
|
||||
"errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required" label:"用户名"`
|
||||
Password string `json:"password" binding:"required" label:"密码"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
func (AuthApi) LoginView(c *gin.Context) {
|
||||
cr := middleware.GetBind[LoginRequest](c)
|
||||
|
||||
u, err := auth_ser.Login(c.Request.Context(), cr.Username, cr.Password)
|
||||
if err != nil {
|
||||
if errors.Is(err, auth_ser.ErrUserNotFound) {
|
||||
global.Log.Warn("用户不存在", zap.String("username", cr.Username), zap.Error(err))
|
||||
res.BadRequest("用户名或密码错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, auth_ser.ErrAccount) {
|
||||
res.FailWithMsg("用户名或密码错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
global.Log.Error("登陆系统异常", zap.Error(err))
|
||||
res.InternalError("登陆失败,请稍后重试", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 提取预加载的角色 ID 列表
|
||||
var roleIds []uint
|
||||
for _, role := range u.Edges.Roles {
|
||||
roleIds = append(roleIds, uint(role.ID))
|
||||
}
|
||||
|
||||
claims := jwts.Claims{
|
||||
UserID: uint(u.ID),
|
||||
Roles: roleIds, // 将关联查询出的角色 ID 塞进 token
|
||||
Email: u.Email,
|
||||
}
|
||||
|
||||
accessToken, err := jwts.GenAccessToken(claims)
|
||||
if err != nil {
|
||||
res.InternalError("生成 accessToken 失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
refreshToken, err := jwts.GenRefreshToken(uint(u.ID))
|
||||
if err != nil {
|
||||
res.InternalError("生成 refreshToken 失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
res.OkWithData(LoginResponse{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
}, c)
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package api
|
||||
|
||||
import "EnGin/internal/api/auth_api"
|
||||
|
||||
type Api struct {
|
||||
AuthApi auth_api.AuthApi
|
||||
}
|
||||
|
||||
// App 统一导出全局 API 实例
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package global
|
||||
|
||||
import "errors"
|
||||
|
||||
// 预定义全局通用的系统级/数据库级错误
|
||||
var (
|
||||
ErrDbQuery = errors.New("数据库查询异常")
|
||||
ErrDbSave = errors.New("数据保存失败")
|
||||
ErrDbDelete = errors.New("数据删除失败")
|
||||
ErrDbUpdate = errors.New("数据更新失败")
|
||||
)
|
||||
@@ -1,7 +1,18 @@
|
||||
package router
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import (
|
||||
"EnGin/internal/api"
|
||||
"EnGin/internal/api/auth_api"
|
||||
"EnGin/internal/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AuthRouter(g *gin.RouterGroup) {
|
||||
app := api.App.AuthApi
|
||||
|
||||
g.POST("login",
|
||||
middleware.BindJsonMiddleware[auth_api.LoginRequest],
|
||||
middleware.AuthRateLimit,
|
||||
app.LoginView)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package auth_ser
|
||||
|
||||
import (
|
||||
"EnGin/internal/db"
|
||||
"EnGin/internal/ent"
|
||||
"EnGin/internal/ent/user"
|
||||
"EnGin/internal/global"
|
||||
"EnGin/internal/utils/pwd"
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUserNotFound = errors.New("用户不存在")
|
||||
ErrAccount = errors.New("账号和密码错误")
|
||||
)
|
||||
|
||||
func Login(ctx context.Context, username, password string) (*ent.User, error) {
|
||||
u, err := db.Client.User.Query().Where(user.UsernameEQ(username)).WithRoles().Only(ctx)
|
||||
if err != nil {
|
||||
if ent.IsNotFound(err) {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
return nil, global.ErrDbQuery
|
||||
}
|
||||
|
||||
if !pwd.CompareHashAndPassword(u.Password, password) {
|
||||
return nil, ErrAccount
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package pwd
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func GenerateFromPassword(password string) (string, error) {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
zap.L().Error("密码加密失败", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
return string(hashedPassword), nil
|
||||
}
|
||||
|
||||
func CompareHashAndPassword(hashedPassword string, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
Reference in new issue
Block a user