第五天核心任务:权限校验链路闭环
第五天的开发聚焦于权限控制的核心实现,完成了角色权限中间件的开发,实现了接口级别的权限校验,并基于事务控制确保用户权限操作的数据一致性。通过这部分工作,系统的权限管理从设计阶段正式进入可运行阶段,为后续业务模块的安全接入提供了坚实基础。
一、角色权限中间件
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)
)减少数据库交互次数,配合事务提升效率
三、总结与次日计划
第五天成果
- 完成
RequirePermission
中间件,实现接口级权限校验,支持 "接口 - 权限 - 用户" 的联动验证 - 开发用户权限核心接口,通过 GORM 事务确保多表操作的数据一致性
- 形成完整的权限控制链路:JWT 认证→权限中间件校验→业务接口执行