上一篇文章我们的权限模型和权限检查中间件已经可以正常运行了。但此时,当新用户注册一个帐户时,他们没有任何权限。在本节中,我们将修改这个设置,使新用户在默认情况下自动被授予“movies:read”权限。
更新权限模型
为了给用户授予权限,我们需要更新PermissionModel,添加AddForUser()方法,为用户添加一个或多个权限码到数据库中。我们的想法是,按以下方式在处理程序中使用该函数:
//为ID = 2的用户添加"movies:read"和"movies:write"权限
app.models.Permissions.AddForUser(2, "movies:read", "movies:write")
该函数在数据库中执行的SQL语句如下所示:
INSERT INTO users_permissions
SELECT $1, permissions.id FROM permissions WHERE permissions.code = ANY($2)
在这个SQL语句中$1参数是用户ID,$2参数是我们需要为用户添加的权限码列表,类似{'movies:read', 'movies:write'}。
因此,这里发生的事是第二行的SELECT语句创建了一个“临时”表,其中的行由用户ID和数组中相应权限代码的ID组成。然后将这个临时表的内容插入到user_permissions表中。
下面我们在internal/data/permissions.go文件中创建AddForUser()方法:
File: internal/data/permissions.go
package data
...
//为特定用户添加授权码。这里我们使用可变参数。
func (m PermissionModel)AddForUser(userID int64, codes ...string) error {
query := `
INSERT INTO users_permissions
SELECT $1, permissions.id FROM permissions WHERE permissions.code = ANY($2)`
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
_, err := m.DB.ExecContext(ctx, query, userID, pq.Array(codes))
return err
}
更新注册处理程序(register handler)
现在数据库处理完成了,我们更新registerUserHandler这样在新用户注册的时自动为用户创建"movies:read"权限。如下所示:
File:cmd/api/users.go
package main
...
func (app *application) registerUserHandler(w http.ResponseWriter, r *http.Request) {
//创建匿名结构体接收客户端发送用户信息
var input struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
}
//解析请求内容到匿名结构体只能够
err := app.readJSON(w, r, &input)
if err != nil {
app.badRequestResponse(w, r, err)
return
}
//将input中到用户信息拷贝到User结构体。注意需要将激活信息设置为false,
//该操作是非必需的因为默认值就是false,单独设置下可读性更好。
user := &data.User{
Name: input.Name,
Email: input.Email,
Activated: false,
}
//使用Password.Set方法处理密码
err = user.Password.Set(input.Password)
if err != nil {
app.serverErrorResponse(w, r, err)
return
}
v := validator.New()
//校验user结构体
if data.ValidateUser(v, user); !v.Valid() {
app.failedValidationResponse(w, r, v.Errors)
return
}
//插入用户信息到数据库
err = app.models.Users.Insert(user)
if err != nil {
switch {
//如果错误是ErrDuplicateEmail,使用v.AddError()方法手动添加校验错误信息
case errors.Is(err, data.ErrDuplicateEmail):
v.AddError("email", "a user with this email address already exists")
app.failedValidationResponse(w, r, v.Errors)
default:
app.serverErrorResponse(w, r, err)
}
return
}
//为新注册用户添加"movies:read"权限
err = app.models.Permissions.AddForUser(user.ID, "movies:read")
if err != nil {
app.serverErrorResponse(w, r, err)
return
}
//用户数据插入表之后,为用户生成新的激活token
token, err := app.models.Tokens.New(user.ID, 3 * 24 * time.Hour, data.ScopeActivation)
if err != nil {
app.serverErrorResponse(w, r, err)
return
}
//使用background创建goroutine异步发送邮件
app.background(func() {
//现在要传入多个数据到邮件模版,我们创建一个map
data := map[string]interface{}{
"activationToken": token.Plaintext,
"userID": user.ID,
}
// 发送欢迎邮件,并传入map作为动态数据
err = app.mailer.Send(user.Email, "/user_welcome.tmpl", data)
if err != nil {
app.logger.Error(err, nil)
}
})
//将返回码改为202,表示客户端请求被接受,但处理没有完成。
err = app.writeJSON(w, http.StatusAccepted, envelope{"user": user}, nil)
if err != nil {
app.serverErrorResponse(w, r, err)
}
}
...
我们用grace@example.com邮箱新注册一个用户来测试下前面代码是否正常。
$ BODY='{"name": "Grace Smith", "email": "grace@example.com", "password": "pa55word"}'
$ curl -d "$BODY" localhost:4000/v1/users
{
"user": {
"id": 4,
"create_at": "2022-01-08T16:08:17+08:00",
"name": "Grace Smith",
"email": "grace@example.com",
"activated": false
}
}
如果你打开psql,执行以下SQL查询应该可以看到新注册的用户有movies:read权限。
greenlight=> select email, code from users
inner join users_permissions on users.id = users_permissions.user_id
inner join permissions on users_permissions.permission_id = permissions.id
where users.email = 'grace@example.com';
email | code
-------------------+-------------
grace@example.com | movies:read
(1 row)
网友评论