我遇到使用我们 API 的人需要在我的资源中进行部分更新的情况。我知道 HTTP 明确指定这是一个 PATCH 操作,即使我们这边的人习惯于为此发送 PUT 请求,这就是构建遗留代码的方式。
为了举例说明,想象一下以下简单的结构:
type Person struct {
Name string
Age int
Address string
}
在 POST 请求中,我将提供包含所有三个值(名称、年龄、地址)的有效负载,并在我的 Golang 后端相应地验证它们。简单。
但是,在 PUT/PATCH 请求中,我们知道,例如,name
永远不会改变。但是假设我想更改age
,那么我只需发送包含新age
的 JSON 有效负载:
PUT /person/1 {age:30}
现在说我真正的问题:在我们的 API 的使用者发送包含 name
字段的 JSON 有效负载时,防止有意或无意修改name
的最佳实践是什么?
例:
PUT /person/1 {name:"New Name", age:35}
我想到的可能的解决方案,但我实际上不喜欢它们,是:
在我的
validator
方法上,我要么强行删除不需要的字段name
,要么回复一条错误消息,指出不允许name
。例如,创建一个DTO对象/结构,它几乎是我的
Person
结构的扩展,然后将我的JSON有效负载解组到其中type PersonPut struct { Age int Address string }
在我看来,这将添加不必要的额外代码和逻辑来抽象问题,但是我没有看到任何其他优雅的解决方案。
老实说,我不喜欢这两种方法,我想知道你们是否面临同样的问题以及如何解决它。
谢谢!
你带来的第一个解决方案是一个很好的解决方案。一些众所周知的框架用于实现类似的逻辑。
例如,最新的 Rails 版本带有一个内置解决方案,以防止用户在请求中添加额外的数据,从而导致服务器更新数据库中的错误字段。它是ActionController::Parameters
类实现的一种白名单。
假设我们有一个控制器类作为波纹管。出于此说明的目的,它包含两个update
操作。但是你不会在实际代码中看到它。
class PeopleController < ActionController::Base
# 1st version - Unsafe, it will rise an exception. Don't do it
def update
person = current_account.people.find(params[:id])
person.update!(params[:person])
redirect_to person
end
# 2nd version - Updates only permitted parameters
def update
person = current_account.people.find(params[:id])
person.update!(person_params) # call to person_params method
redirect_to person
end
private
def person_params
params.require(:person).permit(:name, :age)
end
end
由于第二个版本仅允许允许的值,因此它将阻止用户更改有效负载并发送包含新密码值的 JSON:
{ name: "acme", age: 25, password: 'account-hacked' }
有关更多详细信息,请参阅 Rails 文档:动作控制器概述 和 动作控制器::P
如果无法写入名称,则为任何更新请求提供该名称都是无效的。如果名称存在,我会拒绝该请求。如果我想更宽容,我可能会考虑仅在名称与当前名称不同时才拒绝请求。
我不会默默地忽略一个与现在的名字不同的名字。
这可以通过先将 JSON 正文解码为map[string]json.RawMessage
来解决。json.RawMessage
类型对于延迟实际解码很有用。之后,可以在map[string]json.RawMessage
图上应用白名单,忽略不需要的属性,只解码我们想要保留的属性的json.RawMessage
。
reflect
包可以将列入白名单的 JSON 正文解码为结构的过程自动化;可以在此处找到示例实现。
精通 Golang,但我相信一个好的策略是将您的name
字段转换为只读字段。
例如,在像Java/.NET/C++这样严格的面向对象语言中,你可以只提供一个Getter,而不能提供一个Setter。
也许 Golang 有一些访问器配置,就像 Ruby 一样......
如果它是只读的,那么它不应该费心接收备用值,它应该忽略它。但同样,不确定 Golang 是否支持它。
我认为干净的方法是将此逻辑放在 PATCH 处理程序中。应该有一些逻辑只会更新您想要的字段。如果解压缩到map[string]string
中并仅循环访问要更新的字段,则更容易。此外,您可以将 json 解码为映射,删除所有不想更新的字段,在 json 中重新编码,然后解码为结构。