DRF API设计:为登录用户创建Annotation视图的最优方案选择
DRF 医疗标注场景API设计方案对比及最佳实践
两种方案优劣对比
自定义APIView方案(你当前在用的方案)
- 优势
- 权限安全性更高:完全隐式使用
request.user获取当前登录用户,没有用户ID传参环节,从根源避免了越权访问其他用户标注数据的风险,完全符合医疗数据的高权限要求 - 业务适配性强:接口语义明确,
/api/user-annotation-by-ic/{ic_id}直接对应「当前用户针对指定IC的标注」资源,内置的GET不存在返回空对象、POST自动做创建/更新(upsert)的逻辑,刚好匹配「每个用户每个IC仅一条标注」的业务规则,前端调用逻辑无需额外判断是创建还是更新,对接成本极低 - 符合最小权限原则:仅暴露业务必需的查询、写入能力,不会额外暴露删除、批量查询等不需要的接口,降低数据泄露风险
- 权限安全性更高:完全隐式使用
- 劣势
- 代码复用性较低,如果后续需要扩展「查询当前用户所有标注」「管理员批量导出标注」等能力,需要额外编写其他视图
- 不符合通用RESTful CRUD的默认约定,新接手的后端开发需要额外理解视图逻辑
ViewSet+过滤参数方案
- 优势
- 符合DRF通用开发约定,代码复用性高,后续要扩展分页、多维度过滤、批量查询等能力只需要修改配置,不需要重复编写逻辑
- 标准RESTful风格,
/api/annotations对应标注资源,参数过滤是通用操作,前后端开发者都能快速理解
- 劣势
- 存在越权风险,必须强制做参数校验:不能信任前端传递的user参数,需要在视图的过滤逻辑、序列化逻辑里强制将用户赋值为
request.user,否则会出现用户查询/修改其他人医疗标注的严重安全问题 - 要适配你的业务规则需要额外开发:标准ViewSet的创建、更新是分离的,创建调用POST
/api/annotations、更新调用PUT/api/annotations/{标注ID},前端需要先查询标注是否存在再决定调用哪个接口,或者你需要重写create方法实现upsert逻辑,反而增加了代码量
- 存在越权风险,必须强制做参数校验:不能信任前端传递的user参数,需要在视图的过滤逻辑、序列化逻辑里强制将用户赋值为
方案选择建议
结合你的医疗标注场景,你当前使用的自定义APIView方案更优,除非后续有明确的后台批量管理标注的需求,否则不需要切换为ViewSet方案。
对应DRF API设计最佳实践
- 隐私数据优先做隐式赋值:涉及用户隐私的业务,所有用户相关的字段赋值、过滤都在后端从
request.user获取,永远不要信任前端传递的用户ID参数,你当前的实现逻辑符合这个原则 - 语义优先于通用约定:不要为了用ViewSet而硬套通用CRUD规则,如果业务场景是窄范围的定制逻辑,自定义APIView的可读性、可维护性反而更高
- 小优化建议:你当前代码里直接修改
request.data的写法可以优化,避免修改不可变的请求原生数据,参考修改如下:
def post(self, request, ic_id): create = False context = {'request': self.request} # 重新构造请求数据,不修改原request.data request_data = {**request.data, 'ic': ic_id} try: obj = Annotation.objects.get(ic=ic_id, user=request.user) serializer = self.serializer_class(obj, data=request_data, context=context) except ObjectDoesNotExist: serializer = self.serializer_class(data=request_data, context=context) create = True serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED if create else status.HTTP_200_OK)
内容的提问来源于stack exchange,提问作者AlexanderLedovsky
相关产品推荐
相关产品推荐

