GoLand 项目从 0 到 1:第五天 —— 角色权限中间件实现与事务控制

第五天核心任务:权限校验链路闭环

第五天的开发聚焦于权限控制的核心实现,完成了角色权限中间件的开发,实现了接口级别的权限校验,并基于事务控制确保用户权限操作的数据一致性。通过这部分工作,系统的权限管理从设计阶段正式进入可运行阶段,为后续业务模块的安全接入提供了坚实基础。

一、角色权限中间件

1. 中间件完整代码

Go 复制代码
func RequirePermission() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 新建数据库连接实例
		db := postgresqldb.GetDB()

		// 1. 获取当前请求的路径和方法
		path := c.FullPath()
		fmt.Println("请求路径:", path)

		// 2. 查询该接口所需的权限ID
		var interfacePerm postgresql.InterfacePermissionRef
		err := db.Where("name = ? AND is_delete = 0", path).First(&interfacePerm).Error
		// 接口未配置权限,直接通过(无需权限校验)
		if err != nil && err == gorm.ErrRecordNotFound {
			c.Next()
			return
		}
		if err != nil {
			response.InternalServerError(c, "查询接口权限配置失败")
			c.Abort()
			return
		}

		// 3. 接口配置了权限,需要验证用户权限
		// 从上下文获取用户ID(由JWT中间件提前解析存入)
		userID, exists := c.Get("user_id")
		if !exists {
			response.Unauthorized(c, "未获取到用户信息,请先登录")
			c.Abort()
			return
		}
		uid, ok := userID.(int64)
		if !ok {
			response.InternalServerError(c, "用户ID格式错误")
			c.Abort()
			return
		}

		// 4. 查询用户拥有的所有权限ID
		var permissions []struct {
			PermissionID int64 `gorm:"column:permission_id"`
		}

		// 关联查询:用户->角色->权限
		err = db.Table("tb_user_role_ref AS ur").
			Joins("JOIN tb_role_permission AS rp ON ur.role_id = rp.role_id").
			Where("ur.user_id = ? AND ur.is_delete = 0 AND rp.is_delete = 0", uid).
			Select("rp.permission_id").
			Find(&permissions).Error

		if err != nil {
			response.InternalServerError(c, "查询用户权限失败")
			c.Abort()
			return
		}

		// 5. 权限比对:检查用户是否拥有接口所需权限
		userPermSet := make(map[int64]struct{})
		for _, perm := range permissions {
			userPermSet[perm.PermissionID] = struct{}{} // 用map存储权限ID,提高查询效率
		}

		requiredPermID := interfacePerm.PermissionID
		if _, hasPermission := userPermSet[requiredPermID]; !hasPermission {
			response.Forbidden(c, "没有访问该接口的权限")
			c.Abort()
			return
		}

		// 6. 验证通过,继续处理请求
		c.Next()
	}
}

2. 中间件集成方式

在路由注册时,为需要权限控制的接口绑定该中间件:

Go 复制代码
// 示例:为文件管理接口添加权限校验
files := authenticated.Group("/files")
files.Use(middleware.RequirePermission()) // 绑定权限中间件
{
    files.POST("/upload", fileHandler.UploadFile)
    files.GET("/list", fileHandler.ListFiles)
}

二、用户权限接口开发与事务控制

用户权限相关接口(如分配角色、修改权限)涉及多表操作,需通过事务确保数据一致性。以下是核心接口的实现示例:

1. 创建用户角色接口(带事务控制)

routes:

Go 复制代码
		userHandler := handlers.NewUserHandler()
		user := postgresqlAPI.Group("/user")
		{
			user.GET("/create", userHandler.CreateUser)
			user.POST("/update", userHandler.UpdateUser)
			user.POST("/delete", userHandler.DeleteUser)
            //.....其它权限接口,这里展示增删改
		}

handler

Go 复制代码
func (h *UserHandler) CreateUser(c *gin.Context) {
	var req postgresql.CreateUserRoleRefRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "无效的请求参数: "+err.Error())
		return
	}

	// 调用Service层处理业务逻辑(包含事务)
	result, err := h.service.CreateUser(&req)
	if err != nil {
		response.InternalServerError(c, "Failed to create user")
		return
	}

	response.Success(c, result)
}

model

Go 复制代码
type User struct {
	BaseModel
	Account  string `json:"account" gorm:"type:varchar(100);not null;uniqueIndex"`
	Username string `json:"username" gorm:"type:varchar(50);not null;uniqueIndex"`
	Password string `json:"password" gorm:"type:varchar(255);not null"`
}

// TableName 指定表名
func (User) TableName() string {
	return "tb_user"
}

// CreateUserRoleRefRequest 新建用户请求参数
type CreateUserRoleRefRequest struct {
	UserName string `json:"userName" binding:"required" validate:"required,min=3,max=50"`
	Password string `json:"password" binding:"required" validate:"required,min=6"`
	RoleId   int64  `json:"roleId" binding:"required" validate:"required,gt=0"`
}

// UpdateUserRequest 编辑用户请求参数
type UpdateUserRequest struct {
	UserId   int64  `json:"userId" binding:"required" validate:"required"`
	UserName string `json:"userName" binding:"required" validate:"required,min=3,max=50"`
	Password string `json:"password" binding:"required" validate:"required,min=6"`
	RoleId   int64  `json:"roleId" binding:"required" validate:"required,gt=0"`
}

service

Go 复制代码
// CreateUser 创建用户(包含角色关联,使用事务)
func (s *UserService) CreateUser(req *postgresql.CreateUserRoleRefRequest) (*postgresql.User, error) {
    // 获取数据库事务管理器,用于处理事务操作
    txManager := postgresqldb.GetTransactionManager()
    // 创建背景上下文,用于事务控制
    ctx := context.Background()

    // 执行带返回值的事务:所有数据库操作在同一事务中执行,确保原子性
    result, err := txManager.TransactionResult(ctx, func(tx *gorm.DB) (interface{}, error) {
        // 密码加密:使用工具函数对原始密码进行bcrypt加密处理
        hashPassword, err := utils.HashPassword(req.Password)
        if err != nil {
            return nil, fmt.Errorf("password encryption failed: %w", err)
        }

        // 生成唯一账号:格式为"hc_用户名_时间戳",确保账号唯一性
        timestamp := strconv.FormatInt(time.Now().Unix(), 10)  // 获取当前时间戳(秒级)
        account := "hc_" + req.UserName + "_" + timestamp       // 拼接账号字符串

        // 创建用户记录:初始化用户对象并保存到数据库
        user := &postgresql.User{
            Account:  account,    // 唯一账号
            Username: req.UserName, // 用户名(可重复)
            Password: hashPassword, // 加密后的密码
        }
        if err := tx.Create(user).Error; err != nil {
            return nil, fmt.Errorf("failed to create user: %w", err)
        }

        // 关联用户角色:建立用户与角色的多对多关系
        userRole := &postgresql.UserRoleRef{
            UserID: user.ID,  // 新创建用户的ID
            RoleID: req.RoleId, // 请求中指定的角色ID
        }
        if err := tx.Create(userRole).Error; err != nil {
            return nil, fmt.Errorf("failed to create user role relation: %w", err)
        }

        // 验证角色有效性:检查指定角色是否存在且未被删除(is_delete=0)
        var role postgresql.Role
        if err := tx.Where("id = ? AND is_delete = 0", req.RoleId).First(&role).Error; err != nil {
            return nil, fmt.Errorf("failed to get role: %w", err)
        }

        // 组装返回数据:仅返回必要的用户信息(基础字段+账号信息)
        return &postgresql.User{
            BaseModel: postgresql.BaseModel{
                ID:         user.ID,          // 用户ID
                CreateTime: user.CreateTime,  // 创建时间
                UpdateTime: user.UpdateTime,  // 更新时间(初始与创建时间一致)
            },
            Username: user.Username, // 用户名
            Account:  user.Account,  // 唯一账号
            Password: user.Password, // 加密后的密码(注意:生产环境可能需移除密码返回)
        }, nil
    })

    // 事务执行失败:返回错误信息
    if err != nil {
        return nil, err
    }

    // 事务执行成功:将结果转换为User类型并返回
    return result.(*postgresql.User), nil
}
Go 复制代码
// TransactionResult 执行带返回值的数据库事务
// 功能:在一个原子事务中执行数据库操作,并返回操作结果
// 参数:
//   ctx - 上下文对象,用于控制事务超时和取消
//   fn - 事务处理函数,接收 *gorm.DB 事务对象,返回操作结果和错误
// 返回值:
//   interface{} - 事务执行成功时返回的结果
//   error - 事务执行失败时返回的错误(包括数据库未初始化、事务开始失败、业务逻辑错误、提交失败等)
func (tm *TransactionManager) TransactionResult(ctx context.Context, fn TxResultFunc) (interface{}, error) {
    // 检查数据库连接是否已初始化
    if tm.db == nil {
        return nil, fmt.Errorf("database is not initialized")
    }

    // 开始事务:使用上下文创建新事务,确保事务操作受上下文控制
    tx := tm.db.WithContext(ctx).Begin()
    if tx.Error != nil {
        return nil, tx.Error // 事务开始失败(如数据库连接问题)
    }

    // 延迟处理:确保在函数退出前处理事务回滚(针对panic场景)
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback() // 发生panic时回滚事务
            panic(r)      // 重新抛出panic,让上层处理
        }
    }()

    // 执行事务逻辑:调用业务定义的事务处理函数
    result, err := fn(tx)
    if err != nil {
        tx.Rollback() // 业务逻辑执行失败,回滚事务
        return nil, err
    }

    // 提交事务:所有业务逻辑执行成功后提交事务
    if err := tx.Commit().Error; err != nil {
        return nil, err // 提交失败(如数据库异常)
    }

    // 返回事务执行结果
    return result, nil
}

2. 事务控制的核心作用

在用户权限操作中,事务确保了多表操作的原子性:

  • 删除旧关联 + 插入新关联:两个操作必须同时成功或同时失败,避免出现 "部分角色分配" 的中间状态
  • 异常处理 :通过tx.Rollback()在任何步骤失败时回滚所有操作,保证数据一致性
  • 性能优化 :批量插入(Create(&roleRefs))减少数据库交互次数,配合事务提升效率

三、总结与次日计划

第五天成果

  1. 完成RequirePermission中间件,实现接口级权限校验,支持 "接口 - 权限 - 用户" 的联动验证
  2. 开发用户权限核心接口,通过 GORM 事务确保多表操作的数据一致性
  3. 形成完整的权限控制链路:JWT 认证→权限中间件校验→业务接口执行
相关推荐
wxh_无香花自开3 小时前
chromedp 笔记
笔记·golang·网络爬虫·chromedp
mitt_7 小时前
go语言变量
开发语言·后端·golang
fbbqt12 小时前
Go语言 逃 逸 分 析
开发语言·golang
whhhhhhhhhw17 小时前
Go语言常量
开发语言·后端·golang
zwxu_1 天前
基于vscode连接服务器实现远程开发
java·开发语言·vscode·golang
java坤坤1 天前
GoLand 项目从 0 到 1:第四天 —— 技术选型落地与方案设计
golang·jwt
liulanba1 天前
八股取士-go
golang
raoxiaoya2 天前
Golang中的`io.Copy()`使用场景
开发语言·后端·golang
枫叶梨花2 天前
使用Go语言获取Windows系统信息:从CPU到电池的全维度监控
开发语言·windows·golang