sms
All checks were successful
Deploy to Server / deploy (push) Successful in 3s

This commit is contained in:
jeremygan2021
2026-02-16 19:59:45 +08:00
parent 481a1d24f0
commit 91d82b78b5
46 changed files with 247 additions and 5482 deletions

View File

@@ -6,7 +6,7 @@ from django.urls import path, reverse
from django.shortcuts import redirect
from unfold.admin import ModelAdmin, TabularInline
from unfold.decorators import display
from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, VCCourse, ProductFeature, CommissionLog, WeChatUser, Distributor, Withdrawal, ServiceOrder, CourseEnrollment
from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, VCCourse, ProductFeature, CommissionLog, WeChatUser, Distributor, Withdrawal, ServiceOrder, CourseEnrollment, AdminPhoneNumber
import qrcode
from io import BytesIO
import base64
@@ -478,3 +478,9 @@ class WithdrawalAdmin(ModelAdmin):
'fields': ('created_at', 'updated_at')
}),
)
@admin.register(AdminPhoneNumber)
class AdminPhoneNumberAdmin(ModelAdmin):
list_display = ('name', 'phone_number', 'is_active', 'created_at')
list_filter = ('is_active',)
search_fields = ('name', 'phone_number')

View File

@@ -3,3 +3,7 @@ from django.apps import AppConfig
class ShopConfig(AppConfig):
name = 'shop'
verbose_name = "商城管理"
def ready(self):
import shop.signals

View File

@@ -0,0 +1,27 @@
# Generated by Django 6.0.1 on 2026-02-16 11:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('shop', '0030_alter_esp32config_options_alter_service_options_and_more'),
]
operations = [
migrations.CreateModel(
name='AdminPhoneNumber',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='管理员姓名')),
('phone_number', models.CharField(max_length=20, verbose_name='手机号')),
('is_active', models.BooleanField(default=True, verbose_name='是否接收通知')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
],
options={
'verbose_name': '管理员通知手机号',
'verbose_name_plural': '管理员通知手机号',
},
),
]

View File

@@ -402,3 +402,21 @@ class CourseEnrollment(models.Model):
class Meta:
verbose_name = "课程报名"
verbose_name_plural = "课程报名管理"
class AdminPhoneNumber(models.Model):
"""
管理员通知手机号配置
用于接收订单支付成功等重要通知
"""
name = models.CharField(max_length=50, verbose_name="管理员姓名")
phone_number = models.CharField(max_length=20, verbose_name="手机号")
is_active = models.BooleanField(default=True, verbose_name="是否接收通知")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
def __str__(self):
return f"{self.name} - {self.phone_number}"
class Meta:
verbose_name = "管理员通知手机号"
verbose_name_plural = "管理员通知手机号"

54
backend/shop/signals.py Normal file
View File

@@ -0,0 +1,54 @@
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from .models import Order
from .sms_utils import notify_admins_order_paid, notify_user_order_paid, notify_user_order_shipped
@receiver(pre_save, sender=Order)
def track_order_changes(sender, instance, **kwargs):
"""
在保存之前检查状态变化
"""
if instance.pk:
try:
old_instance = Order.objects.get(pk=instance.pk)
# 检查是否从非支付状态变为支付状态
if old_instance.status != 'paid' and instance.status == 'paid':
instance._was_paid = True
# 检查是否发货 (状态变为 shipped 且有单号)
# 或者已经是 shipped 状态但刚填入单号
if instance.status == 'shipped' and instance.tracking_number:
if old_instance.status != 'shipped' or not old_instance.tracking_number:
instance._was_shipped = True
except Order.DoesNotExist:
pass
@receiver(post_save, sender=Order)
def send_order_notifications(sender, instance, created, **kwargs):
"""
在保存之后发送通知
"""
if created:
return
# 1. 处理支付成功通知
if getattr(instance, '_was_paid', False):
try:
print(f"订单 {instance.id} 支付成功,触发短信通知流程...")
notify_admins_order_paid(instance)
notify_user_order_paid(instance)
# 清除标记防止重复发送 (虽然实例通常是新的,但保险起见)
instance._was_paid = False
except Exception as e:
print(f"发送支付成功短信失败: {str(e)}")
# 2. 处理发货通知
if getattr(instance, '_was_shipped', False):
try:
print(f"订单 {instance.id} 已发货,触发短信通知流程...")
notify_user_order_shipped(instance)
instance._was_shipped = False
except Exception as e:
print(f"发送发货短信失败: {str(e)}")

98
backend/shop/sms_utils.py Normal file
View File

@@ -0,0 +1,98 @@
import requests
import threading
import json
from .models import AdminPhoneNumber
# SMS API Configuration
SMS_API_URL = "https://data.tangledup-ai.com/api/send-sms/diy"
SIGN_NAME = "叠加态科技云南"
def send_sms(phone_number, template_code, template_params):
"""
通用发送短信函数 (异步)
"""
def _send():
try:
payload = {
"phone_number": phone_number,
"template_code": template_code,
"sign_name": SIGN_NAME,
"additionalProp1": template_params
}
headers = {
"Content-Type": "application/json",
"accept": "application/json"
}
# print(f"Sending SMS to {phone_number} with params: {template_params}")
response = requests.post(SMS_API_URL, json=payload, headers=headers, timeout=15)
print(f"SMS Response for {phone_number}: {response.status_code} - {response.text}")
except Exception as e:
print(f"发送短信异常: {str(e)}")
threading.Thread(target=_send).start()
def notify_admins_order_paid(order):
"""
通知管理员有新订单支付成功
"""
# 获取激活的管理员手机号最多3个
admins = AdminPhoneNumber.objects.filter(is_active=True)[:3]
if not admins.exists():
print("未配置管理员手机号,跳过管理员通知")
return
# 构造参数
# 模板变量: consignee, order_id, address
# order_id 格式要求: "订单编号/电话号码"
params = {
"consignee": order.customer_name or "未填写",
"order_id": f"{order.id}/{order.phone_number}",
"address": order.shipping_address or "无地址"
}
print(f"准备发送管理员通知,共 {admins.count()}")
for admin in admins:
send_sms(admin.phone_number, "SMS_501735480", params)
def notify_user_order_paid(order):
"""
通知用户下单成功 (支付成功)
"""
if not order.phone_number:
return
# 模板变量: user_nick, address
# 尝试获取用户昵称,如果没有则使用收货人姓名
user_nick = order.customer_name
if order.wechat_user and order.wechat_user.nickname:
user_nick = order.wechat_user.nickname
params = {
"user_nick": user_nick or "用户",
"address": order.shipping_address or "无地址"
}
print(f"准备发送用户支付成功通知: {order.phone_number}")
send_sms(order.phone_number, "SMS_501850529", params)
def notify_user_order_shipped(order):
"""
通知用户已发货
"""
if not order.phone_number:
return
# 模板变量: user_nick, address, delivery_company, order_id (这里指快递单号)
user_nick = order.customer_name
if order.wechat_user and order.wechat_user.nickname:
user_nick = order.wechat_user.nickname
params = {
"user_nick": user_nick or "用户",
"address": order.shipping_address or "无地址",
"delivery_company": order.courier_name or "快递",
"order_id": order.tracking_number or "暂无单号"
}
print(f"准备发送用户发货通知: {order.phone_number}")
send_sms(order.phone_number, "SMS_501650557", params)