feat(auth): 添加用户登录功能
This commit is contained in:
1 parent
92a9e20934
commit
63c8f2980b
10 files changed
+209
-17
No files matched your search
+2
-2
@@ -20,8 +20,8 @@ Thumbs.db
|
|||||||
logs/
|
logs/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# 配置文件(如有敏感信息)
|
# 配置文件(含敏感信息,仅本地保留)
|
||||||
# configs/*.yaml
|
configs/config.yaml
|
||||||
|
|
||||||
# Go
|
# Go
|
||||||
vendor/
|
vendor/
|
||||||
|
|||||||
+39
-5
@@ -2,7 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"EnGin/internal/db"
|
"EnGin/internal/db"
|
||||||
|
"EnGin/internal/ent"
|
||||||
|
"EnGin/internal/ent/role"
|
||||||
"EnGin/internal/global"
|
"EnGin/internal/global"
|
||||||
|
"EnGin/internal/utils/pwd"
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -35,17 +38,48 @@ var userCreateCmd = &cobra.Command{
|
|||||||
fmt.Print("请输入密码: ")
|
fmt.Print("请输入密码: ")
|
||||||
password, _ := reader.ReadString('\n')
|
password, _ := reader.ReadString('\n')
|
||||||
password = strings.TrimSpace(password)
|
password = strings.TrimSpace(password)
|
||||||
|
hashedPwd, err := pwd.GenerateFromPassword(password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Errorf("密码加密错误 %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if username == "" || password == "" {
|
if username == "" || password == "" {
|
||||||
fmt.Println("用户名和密码不能为空")
|
fmt.Println("用户名和密码不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
// 直接使用 db.Client 跨包调用并创建数据
|
|
||||||
|
// 1. 尝试查询 admin 角色
|
||||||
|
adminRole, err := db.Client.Role.Query().
|
||||||
|
Where(role.CodeEQ("admin")).
|
||||||
|
Only(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if ent.IsNotFound(err) {
|
||||||
|
// 2. 如果不存在 admin 角色,自动创建
|
||||||
|
adminRole, err = db.Client.Role.Create().
|
||||||
|
SetCode("admin").
|
||||||
|
SetName("超级管理员").
|
||||||
|
Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
global.Log.Error("创建管理员角色失败", zap.Error(err))
|
||||||
|
fmt.Println("创建管理员角色失败: ", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("👉 系统中未发现 admin 角色,已自动创建。")
|
||||||
|
} else {
|
||||||
|
global.Log.Error("查询角色失败", zap.Error(err))
|
||||||
|
fmt.Println("查询角色失败: ", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 直接使用 db.Client 创建用户并挂载角色
|
||||||
u, err := db.Client.User.Create().
|
u, err := db.Client.User.Create().
|
||||||
// 根据您的 schema 字段做适当的设定,如:
|
SetUsername(username).
|
||||||
// SetUsername(username).
|
SetPassword(hashedPwd).
|
||||||
// SetPassword(password).
|
AddRoles(adminRole). // 💡 重点:挂载角色
|
||||||
Save(ctx)
|
Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Log.Error("创建用户失败", zap.Error(err))
|
global.Log.Error("创建用户失败", zap.Error(err))
|
||||||
@@ -53,7 +87,7 @@ var userCreateCmd = &cobra.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("✨ 用户创建成功!ID: %d, 用户名: %s\n", u.ID, username)
|
fmt.Printf("✨ 用户创建成功!ID: %d, 用户名: %s, 角色: admin\n", u.ID, username)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
# EnGin 项目主配置文件
|
# EnGin 项目开发环境配置模板
|
||||||
|
# 使用方式:复制为 config.yaml 并填入真实值
|
||||||
|
# cp configs/config.yaml.dev configs/config.yaml
|
||||||
|
|
||||||
# Web 服务配置
|
# Web 服务配置
|
||||||
server:
|
server:
|
||||||
port: 8080 # 服务监听的端口
|
port: 8080 # 服务监听的端口
|
||||||
mode: "release" # 运行模式:debug (开发模式,带控制台彩色日志) / release (生产模式,只输出 JSON 日志)
|
mode: "debug" # 运行模式:debug (开发模式,带控制台彩色日志) / release (生产模式,只输出 JSON 日志)
|
||||||
version: 2.0.4
|
version: 2.0.4
|
||||||
allow_origins:
|
allow_origins:
|
||||||
- http://localhost:3000
|
- http://localhost:3000
|
||||||
@@ -12,9 +14,9 @@ database:
|
|||||||
driver: "postgres" # 驱动,支持 mysql 或 postgres
|
driver: "postgres" # 驱动,支持 mysql 或 postgres
|
||||||
host: "127.0.0.1" # 数据库主机地址
|
host: "127.0.0.1" # 数据库主机地址
|
||||||
port: 5432 # 端口号:MySQL 默认 3306,PostgreSQL 默认 5432
|
port: 5432 # 端口号:MySQL 默认 3306,PostgreSQL 默认 5432
|
||||||
user: "unclesam" # 用户名
|
user: "your_user" # 用户名
|
||||||
password: "wangchan" # 密码
|
password: "your_password" # 密码
|
||||||
dbname: "test_db" # 数据库名
|
dbname: "your_db" # 数据库名
|
||||||
|
|
||||||
# Zap 日志配置
|
# Zap 日志配置
|
||||||
logger:
|
logger:
|
||||||
@@ -27,14 +29,14 @@ logger:
|
|||||||
compress: true # 是否对历史归档日志进行 gzip 压缩
|
compress: true # 是否对历史归档日志进行 gzip 压缩
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
issuer: "山姆叔叔"
|
issuer: "your_issuer"
|
||||||
access_secret: "django-insecure-pfgn+96(rq74los_7d#n8#b07+$23vnxy*#y^y=eu9&_1eqk$7"
|
access_secret: "your-access-secret-here"
|
||||||
refresh_secret: "3676f2d0e99f294aab75997e16340370"
|
refresh_secret: "your-refresh-secret-here"
|
||||||
access_expire: 2 # 小时
|
access_expire: 2 # 小时
|
||||||
refresh_expire: 168 # 小时(7天)
|
refresh_expire: 168 # 小时(7天)
|
||||||
|
|
||||||
# Redis 缓存配置
|
# Redis 缓存配置
|
||||||
redis:
|
redis:
|
||||||
addr: "127.0.0.1:6379" # Redis 连接地址
|
addr: "127.0.0.1:6379" # Redis 连接地址
|
||||||
password: "wangchan5" # Redis 密码 (没有则为空)
|
password: "" # Redis 密码 (没有则为空)
|
||||||
db: 0 # 数据库编号
|
db: 0 # 数据库编号
|
||||||
@@ -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
|
package api
|
||||||
|
|
||||||
|
import "EnGin/internal/api/auth_api"
|
||||||
|
|
||||||
type Api struct {
|
type Api struct {
|
||||||
|
AuthApi auth_api.AuthApi
|
||||||
}
|
}
|
||||||
|
|
||||||
// App 统一导出全局 API 实例
|
// 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
|
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) {
|
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