假设我们有一个名为时间表的实体。为简单起见,我们假设 Timesheet 实体有三个属性( TimesheetID ,状态和小时)。
我们目前允许时间表对象的所有者通过此终点提交 Timesheets -
POST: /Users/{UserID}/Timesheets/{TimesheetID}
{UserID}目前是时间表对象的所有者。使用{TimesheetID},我还可以追溯数据库中时间表的所有者。
好的,现在问题就在这里 -
现在,我们希望让他们的经理能够将员工的时间表对象更新为该报告(例如批准/拒绝 Timesheets 更改状态,或覆盖小时)。
但是,管理员权限有不同级别。某些管理员只允许更新状态,其中一些管理员可以同时更新状态和时间。
我应该重复使用现有的终点来进行常规用户提交和管理员更新(我更喜欢)吗?或者我应该为每个“经理级别”创建一个不同的终点?
如果我希望重用所有用户提交和管理器更新的现有端点,我该如何处理错误,例如管理器是否配置为仅更新状态,但 TimeSheet 对象同时更改了状态和小时。我是否应该回复403并提供详细的错误说明,告诉管理员您无法更改 Hours 属性或更新状态并忽略小时?
答案 0 :(得分:3)
如果您关注HATEOAS constraint,那么时间表资源上的GET
将提供当前可用于互动的超媒体控件(链接和表单)用它。虽然这可以通过多种方式完成,但提供最低形式耦合的方式将包括它们有效参数的形式。
对于您的示例,我将包含两个具有相同端点/Users/{UserID}/Timesheets/{TimesheetID}
的经理表单。第一个将状态作为必填字段,第二个将具有状态(状态可以是可选的)和小时(需要几小时)。
如果提交者不允许提交小时,您可以让第二个表单以403 Forbidden
回复。或者,您可以过滤GET
中包含的表单,以便仅显示允许用户提交的表单。
<强>更新强>
例如GET
/Users/1234/Timesheets/24
目前可能会产生
<Timesheet>
<TimesheetID>24</TimesheetID>
<Status>Submitted</Status>
<Hours>40</Hours>
</Timesheet>
要应用HATEOAS约束,我们需要添加超媒体控件。我们暂时忽略这些URL,因为它们是实现细节。这可能会给我们类似的东西
<Timesheet>
<TimesheetID>24</TimesheetID>
<Status>Submitted</Status>
<Hours>40</Hours>
<link rel="self" href="{{selfUrl}}"/>
<form id="approve" action="{{approveUrl}}" method="PUT">
<Status cardinality="required">
<option value="Approve"/>
</Status>
</form>
<form id="reject" action="{{rejectUrl}}" method="PUT">
<Status cardinality="required">
<option value="Reject"/>
</Status>
</form>
<form id="update" action="{{updateUrl}}" method="PUT">
<Status cardinality="required">
<option value="Approve"/>
<option value="Reject"/>
</Status>
<Hours type="decimal" cardinality="required"/>
</form>
... there might be other forms too, like ...
<form id="cancel" action="{{cancelUrl}}" method="DELETE"/>
</Timesheet>
表单的作用(以及如何识别表单)是媒体类型中记录的内容。例如:
时间表资源上的
cancel
表单将取消时间表,将其状态更新为“已取消”。取消时间表后,将无法再更新批准或拒绝时间表。
同样在媒体类型中,您将记录资源的属性。如,
Timesheet 资源具有以下属性:
- TimesheetID 时间表的唯一标识符
- 状态时间表的当前状态。状态可能包括
- 已提交时间表已提交但未获批准
- 已批准时间表已获批准
- 已拒绝时间表已被拒绝
- 已取消时间表已取消
- 小时时间表记录的小时数(小数)
虽然这可以由模式指定,但应该避免这样做,因为这样做可能很难在以后扩展资源。例如,您可能决定添加“WeekEnding”日期属性。现有的调用者不应该关心新属性,如果他们只是选择他们感兴趣的数据就可以了。但是,如果你已经指定了架构而没有扩展的想法,那么添加属性会导致调用者出现验证错误添加属性时。
现在,就谁能做我们有几个选择而言。一种选择是仅包含所有控件,并使用403
响应对调用者未授权调用的任何请求。另一种选择是过滤控件,因此他们只能看到他们可以调用的控件。例如对于可以批准/拒绝的经理,他们可能会收到以下回复
<Timesheet>
<TimesheetID>24</TimesheetID>
<Status>Submitted</Status>
<Hours>40</Hours>
<link rel="self" href="{{selfUrl}}"/>
<form id="approve" action="{{approveUrl}}" method="PUT">
<Status cardinality="required">
<option value="Approve"/>
</Status>
</form>
<form id="reject" action="{{rejectUrl}}" method="PUT">
<Status cardinality="required">
<option value="Reject"/>
</Status>
</form>
</Timesheet>
可以更新小时的经理可以
<Timesheet>
<TimesheetID>24</TimesheetID>
<Status>Submitted</Status>
<Hours>40</Hours>
<link rel="self" href="{{selfUrl}}"/>
<form id="approve" action="{{approveUrl}}" method="PUT">
<Status cardinality="required">
<option value="Approve"/>
</Status>
</form>
<form id="reject" action="{{rejectUrl}}" method="PUT">
<Status cardinality="required">
<option value="Reject"/>
</Status>
</form>
<form id="update" action="{{updateUrl}}" method="PUT">
<Status cardinality="required">
<option value="Approve"/>
<option value="Reject"/>
</Status>
<Hours type="decimal" cardinality="required"/>
</form>
</Timesheet>
或者,您可以包含所有用户的所有表单,但添加一个他们无权调用它的指示符。例如对于无法更新小时数的经理:
<Timesheet>
<TimesheetID>24</TimesheetID>
<Status>Submitted</Status>
<Hours>40</Hours>
<link rel="self" href="{{selfUrl}}"/>
<form id="approve" action="{{approveUrl}}" method="PUT">
<Status cardinality="required">
<option value="Approve"/>
</Status>
</form>
<form id="reject" action="{{rejectUrl}}" method="PUT">
<Status cardinality="required">
<option value="Reject"/>
</Status>
</form>
<form id="update" action="{{updateUrl}}" method="PUT" authorised="false">
<Status cardinality="required">
<option value="Approve"/>
<option value="Reject"/>
</Status>
<Hours type="decimal" cardinality="required"/>
</form>
</Timesheet>
我更喜欢这种后续方法,因为您最终没有支持API调用,开发人员抱怨某个特定表单不存在。无论哪种方式(包括或过滤),如果来电者调用不允许的表格,您仍然会回复403
。
有点偏离主题,但为了完整性,HATEOAS真正脱颖而出,因为它根据资源的当前状态传达有效的超媒体控件集。例如,当时间表被取消时,批准/拒绝或更新它不再有效,因此取消的时间表上的GET
可能会返回
<Timesheet>
<TimesheetID>24</TimesheetID>
<Status>Cancelled</Status>
<Hours>40</Hours>
<link rel="self" href="{{selfUrl}}"/>
</Timesheet>
这清楚地告知调用者,此特定时间表不允许进一步操作。
你会注意到的另一件事是我们还没有实际指定任何或URL。它们可能全部相同(例如/Users/{UserID}/Timesheets/{TimesheetID}
)或者它们可能不同(例如selfUrl = /Users/{UserID}/Timesheets/{TimesheetID}
,updateURL = /Users/{UserID}/Timesheets/{TimesheetID}/update
等。)
最终调用者不应该关心,因为它将使用表单/链接中的任何内容。这为您提供了极大的灵活性,因为您可以根据实施需要进行更改。例如,如果您使用的是Command Query Responsibility Segregation(CQRS),那么将GET
个请求发送到//readonly.myserver.com/Users/{UserID}/Timesheets/{TimesheetID}
和POST
,PUT
和{{1}可能是有意义的请求DELETE
。
答案 1 :(得分:1)
是的,请使用403 Forbidden
。我匹配你描述的场景。 RFC 7231说:
403(禁止)状态代码表示服务器已理解 请求但拒绝授权。希望的服务器 公开为什么禁止请求可以描述 响应有效负载中的原因(如果有的话)。
作为替代方案,服务器可以执行允许当前用户的那些操作,并忽略其他所有操作。如果这是一个好主意取决于你的观点。我更愿意拒绝整个请求并返回403 Forbidden
。
答案 2 :(得分:1)
1)您使用POST进行创建,因此您可以在同一端点上使用PUT来更新数据(在请求的内容中提供新数据)。要限制/记录更新数据的人,您可以将其用户/经理ID作为查询参数或正文
传递2)403 Forbidden sound更好,以便让用户(经理)更清楚发生了什么,而不是让他认为数据已正确更新但实际上只是部分更新