GoLand 项目从 0 到 1:第五天 —— 角色权限中间件实现与事务控制
第五天核心任务:权限校验链路闭环
第五天的开发聚焦于权限控制的核心实现,完成了角色权限中间件的开发,实现了接口级别的权限校验,并基于事务控制确保用户权限操作的数据一致性。通过这部分工作,系统的权限管理从设计阶段正式进入可运行阶段,为后续业务模块的安全接入提供了坚实基础。
一、角色权限中间件
1. 中间件完整代码
func RequirePermission() gin.HandlerFunc {return func(c *gin.Context) {// 新建数据库连接实例db := postgresqldb.GetDB()// 1. 获取当前请求的路径和方法path := c.FullPath()fmt.Println("请求路径:", path)// 2. 查询该接口所需的权限IDvar interfacePerm postgresql.InterfacePermissionReferr := 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. 查询用户拥有的所有权限IDvar 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).Errorif 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.PermissionIDif _, hasPermission := userPermSet[requiredPermID]; !hasPermission {response.Forbidden(c, "没有访问该接口的权限")c.Abort()return}// 6. 验证通过,继续处理请求c.Next()}
}
2. 中间件集成方式
在路由注册时,为需要权限控制的接口绑定该中间件:
// 示例:为文件管理接口添加权限校验
files := authenticated.Group("/files")
files.Use(middleware.RequirePermission()) // 绑定权限中间件
{files.POST("/upload", fileHandler.UploadFile)files.GET("/list", fileHandler.ListFiles)
}
二、用户权限接口开发与事务控制
用户权限相关接口(如分配角色、修改权限)涉及多表操作,需通过事务确保数据一致性。以下是核心接口的实现示例:
1. 创建用户角色接口(带事务控制)
routes:
userHandler := handlers.NewUserHandler()user := postgresqlAPI.Group("/user"){user.GET("/create", userHandler.CreateUser)user.POST("/update", userHandler.UpdateUser)user.POST("/delete", userHandler.DeleteUser)//.....其它权限接口,这里展示增删改}
handler
func (h *UserHandler) CreateUser(c *gin.Context) {var req postgresql.CreateUserRoleRefRequestif 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
type User struct {BaseModelAccount 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
// 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, // 新创建用户的IDRoleID: 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.Roleif 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, // 用户IDCreateTime: 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
}
// 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)
)减少数据库交互次数,配合事务提升效率
三、总结与次日计划
第五天成果
- 完成
RequirePermission
中间件,实现接口级权限校验,支持 “接口 - 权限 - 用户” 的联动验证 - 开发用户权限核心接口,通过 GORM 事务确保多表操作的数据一致性
- 形成完整的权限控制链路:JWT 认证→权限中间件校验→业务接口执行