Django view 如何携带额外信息重定向到其他域名?
结论
Django 可以直接重定向到其他域名,但重定向本身不能附带 JSON 数据。
浏览器收到重定向响应后,会根据 Location 响应头访问目标地址。如果对方接收 GET 参数,需要把数据编码到 URL 查询字符串中;如果对方要求 POST 数据,则要提交表单,普通重定向无法做到这一点。
使用查询参数重定向
可以使用 Python 标准库中的 urllib.parse.urlencode() 编码参数,不必手动拼接 &、=,也不用单独处理特殊字符。
from decimal import Decimal
from urllib.parse import urlencode
from django.http import HttpResponseRedirect
def payment_view(request):
target_url = "https://ssl.dotpay.pl/test_payment/"
params = {
"id": "123456",
"amount": Decimal("123.00"),
"description": "Test",
}
redirect_url = f"{target_url}?{urlencode(params)}"
return HttpResponseRedirect(redirect_url)
浏览器会跳转到:
https://ssl.dotpay.pl/test_payment/?id=123456&amount=123.00&description=Test
也可以使用 redirect():
from django.shortcuts import redirect
from urllib.parse import urlencode
def payment_view(request):
params = {
"id": "123456",
"amount": "123.00",
"description": "Test",
}
url = "https://ssl.dotpay.pl/test_payment/?" + urlencode(params)
return redirect(url)
urlencode() 能正确处理空格、中文和其他特殊字符。例如:
urlencode({"description": "测试订单 A"})
这会生成经过 URL 编码的查询字符串。
将 URL 构造移出 view
如果不想在 view 中构造 URL,可以把相关逻辑放进独立函数:
from urllib.parse import urlencode
PAYMENT_URL = "https://ssl.dotpay.pl/test_payment/"
def build_payment_url(payment):
params = {
"id": payment.external_id,
"amount": format(payment.amount, ".2f"),
"description": payment.description,
}
return f"{PAYMENT_URL}?{urlencode(params)}"
view 只需负责调用:
from django.shortcuts import redirect
def payment_view(request, payment_id):
payment = Payment.objects.get(pk=payment_id)
return redirect(build_payment_url(payment))
这样写能让 view 更简洁,参数生成逻辑也可以单独测试。
为什么不能直接通过 JSON 重定向
普通 HTTP 重定向通常使用 302 或 303 状态码,并带有类似这样的响应头:
Location: https://ssl.dotpay.pl/test_payment/?id=123456&amount=123.00
重定向响应没有通用机制让浏览器在访问新地址时额外提交一段 JSON。即使 Django 把 JSON 写进重定向响应体,目标网站也不会自动收到这些数据。
下面的代码不会把 JSON 传给目标域名:
from django.http import JsonResponse
def payment_view(request):
return JsonResponse({
"redirect_url": "https://ssl.dotpay.pl/test_payment/",
"id": "123456",
})
它只会把数据返回给当前请求的调用方。除非前端 JavaScript 读取 JSON 并自行发起请求,否则浏览器不会跳转。
目标接口要求 POST 时
如果支付平台要求通过表单字段接收参数,可以返回一个自动提交的 HTML 表单:
from django.shortcuts import render
def payment_view(request):
context = {
"target_url": "https://ssl.dotpay.pl/test_payment/",
"fields": {
"id": "123456",
"amount": "123.00",
"description": "Test",
},
}
return render(request, "payment_redirect.html", context)
模板内容:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>正在跳转</title>
</head>
<body>
<form id="payment-form" method="post" action="{{ target_url }}">
{% for name, value in fields.items %}
<input type="hidden" name="{{ name }}" value="{{ value }}">
{% endfor %}
<noscript>
<button type="submit">继续支付</button>
</noscript>
</form>
<script>
document.getElementById("payment-form").submit();
</script>
</body>
</html>
这不是 HTTP 重定向,而是由浏览器向另一个域名发送一次 POST 请求。能否使用这种方式,取决于目标平台规定的请求方法、字段名称、签名方式和安全要求。
注意事项
- 不要把密码、访问令牌、银行卡信息等敏感数据放进 URL。查询参数可能记录在浏览器历史、服务器日志和 Referer 信息中。
- 金额建议使用
Decimal,并明确格式化为两位小数,以免出现浮点数精度问题。 - 如果重定向地址来自用户输入,必须通过域名白名单校验,否则可能造成开放重定向漏洞。
- 支付平台通常还会要求签名、回调地址或订单校验,具体字段应以目标平台当前的接口文档为准。
requests.post()等服务端 HTTP 请求不会让用户的浏览器跳转。这类请求适合服务器之间的调用,不能代替浏览器重定向。307或308可以保留原请求的方法和请求体,但不能凭空生成新的 JSON 或表单数据,因此通常不适合用来组装支付请求。
对方要求 GET 时,使用 urlencode() 生成查询字符串;要求 POST 时,使用表单提交。只有目标接口明确支持 JSON,才由前端或 Django 服务端按照其 API 规范发送 JSON。
备注:内容仅供参考。
