如何实现Django管理员跨设备调用指定设备的Service Worker函数?
跨设备触发Service Worker函数实现方案
要实现管理员点击按钮后,在指定设备的Service Worker中调用needsToBeCalled(a,b),需要通过Web Push API完成后端到前端Service Worker的消息推送,以下是具体实现步骤:
1. 配置Web Push环境
安装依赖与生成VAPID密钥
在Django项目中安装django-webpush:
pip install django-webpush
生成VAPID密钥对(用于推送鉴权):
webpush generate_vapid_keys
将生成的公钥和私钥添加到Django项目的settings.py:
INSTALLED_APPS = [ # ... 其他应用 'webpush', ] WEBPUSH_SETTINGS = { "VAPID_PUBLIC_KEY": "你的公钥", "VAPID_PRIVATE_KEY": "你的私钥", "VAPID_ADMIN_EMAIL": "管理员邮箱" }
2. 存储设备的Push订阅信息
创建订阅存储模型
在应用的models.py中创建模型,保存用户设备的Push订阅信息:
from django.db import models from django.contrib.auth.models import User class DeviceSubscription(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) subscription = models.JSONField() device_name = models.CharField(max_length=100, blank=True) # 可选:用于标记设备 def __str__(self): return f"{self.user.username} - {self.device_name or '未知设备'}"
执行迁移:
python manage.py makemigrations python manage.py migrate
前端注册Push订阅并提交到后端
在用户访问的页面中添加JS代码,注册Push订阅并发送到后端:
// 注册Service Worker后,请求Push订阅 async function registerPushSubscription() { const swRegistration = await navigator.serviceWorker.ready; const subscription = await swRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array("你的VAPID公钥") }); // 将订阅信息提交到Django后端 await fetch('/save-subscription/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') // 需实现获取CSRF Token的函数 }, body: JSON.stringify({ subscription: subscription, device_name: prompt('请输入设备名称:') // 可选:让用户标记设备 }) }); } // 辅助函数:将Base64公钥转换为Uint8Array function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); return Uint8Array.from([...rawData].map(char => char.charCodeAt(0))); } // 获取CSRF Token的函数 function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } // 页面加载完成后注册订阅 window.addEventListener('load', () => { if ('serviceWorker' in navigator && 'PushManager' in window) { registerPushSubscription(); } });
后端接收订阅的视图
在应用的views.py中添加保存订阅的视图:
from django.http import JsonResponse from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_exempt from .models import DeviceSubscription import json @require_POST @csrf_exempt def save_subscription(request): data = json.loads(request.body) subscription = data.get('subscription') device_name = data.get('device_name', '') user = request.user # 检查是否已存在该订阅,存在则更新,否则创建 DeviceSubscription.objects.update_or_create( user=user, subscription=subscription, defaults={'device_name': device_name} ) return JsonResponse({'status': 'success'})
在urls.py中添加路由:
from django.urls import path from . import views urlpatterns = [ # ... 其他路由 path('save-subscription/', views.save_subscription, name='save_subscription'), ]
3. 管理员触发推送的后端API与前端按钮
后端推送视图
在views.py中添加处理管理员推送请求的视图:
from django.http import JsonResponse from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_exempt from .models import DeviceSubscription from webpush import send_user_notification import json @require_POST @csrf_exempt def trigger_function(request): data = json.loads(request.body) a = data.get('a') b = data.get('b') selected_device_ids = data.get('selected_device_ids', []) # 选中的设备ID列表 # 获取选中的设备订阅 subscriptions = DeviceSubscription.objects.filter(id__in=selected_device_ids) # 向每个订阅发送推送消息,携带参数a和b for sub in subscriptions: payload = json.dumps({ 'type': 'call_function', 'a': a, 'b': b }) send_user_notification( user=sub.user, subscription=sub.subscription, payload=payload, title='触发函数执行' # 可选:通知标题,若不需要可省略 ) return JsonResponse({'status': 'success', 'count': len(subscriptions)})
添加路由到urls.py:
path('trigger-function/', views.trigger_function, name='trigger_function'),
管理员页面的触发按钮
在管理员页面添加按钮和JS代码,用于选择设备并触发推送:
<!-- 设备选择下拉框 --> <select id="deviceSelect" multiple> {% for device in devices %} <option value="{{ device.id }}">{{ device.user.username }} - {{ device.device_name }}</option> {% endfor %} </select> <!-- 触发按钮 --> <button id="triggerBtn">触发函数执行</button> <script> document.getElementById('triggerBtn').addEventListener('click', async () => { const selectedDevices = Array.from(document.getElementById('deviceSelect').selectedOptions).map(opt => opt.value); const a = prompt('请输入参数a:'); const b = prompt('请输入参数b:'); if (!selectedDevices.length || !a || !b) { alert('请选择设备并输入参数'); return; } const response = await fetch('/trigger-function/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, body: JSON.stringify({ a: a, b: b, selected_device_ids: selectedDevices }) }); const result = await response.json(); alert(`已向${result.count}台设备发送触发指令`); }); // 复用之前的getCookie函数 function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } </script>
注意:管理员页面需要获取所有设备订阅数据,可通过视图查询DeviceSubscription.objects.all()并传递到模板。
4. 修改Service Worker监听Push事件
更新你的serviceworker.js,添加push事件监听,收到推送消息后调用needsToBeCalled:
var staticCacheName = 'djangopwa-v1'; self.addEventListener('install', function(event) { event.waitUntil( caches.open(staticCacheName).then(function(cache) { return cache.addAll([ '/base_layout' ]); }) ); }); self.addEventListener('fetch', function(event) { var requestUrl = new URL(event.request.url); if (requestUrl.origin === location.origin) { if ((requestUrl.pathname === '/')) { event.respondWith(caches.match('/base_layout')); return; } } event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request); }) ); }); // 监听Push事件 self.addEventListener('push', function(event) { if (!event.data) return; const payload = event.data.json(); // 判断消息类型为触发函数 if (payload.type === 'call_function') { const { a, b } = payload; // 调用目标函数 needsToBeCalled(a, b); } }); function needsToBeCalled(a, b) { console.log(a + b); // 可添加其他业务逻辑,比如显示通知 self.registration.showNotification('函数已执行', { body: `参数a: ${a}, 参数b: ${b}, 结果: ${a + b}` }); }
注意事项
- 确保网站使用HTTPS(localhost可例外),否则Service Worker和Push API无法正常工作
- 设备需允许浏览器通知权限,否则无法完成Push订阅
- 若不需要显示通知,可移除
self.registration.showNotification部分,但userVisibleOnly需保持true(浏览器要求推送消息必须对用户可见)
内容的提问来源于stack exchange,提问作者miaa26
相关产品推荐
相关产品推荐

