ios app
This commit is contained in:
175
app.py
175
app.py
@@ -1382,6 +1382,28 @@ class UserSubscription(db.Model):
|
||||
}
|
||||
|
||||
|
||||
class UserDeviceToken(db.Model):
|
||||
"""用户设备推送 Token - 用于 APNs/FCM 推送通知"""
|
||||
__tablename__ = 'user_device_tokens'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) # 可选,未登录也能注册
|
||||
device_token = db.Column(db.String(255), unique=True, nullable=False)
|
||||
platform = db.Column(db.String(20), default='ios') # ios / android
|
||||
app_version = db.Column(db.String(20), nullable=True)
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
|
||||
# 推送订阅设置
|
||||
subscribe_all = db.Column(db.Boolean, default=True) # 订阅所有事件
|
||||
subscribe_important = db.Column(db.Boolean, default=True) # 订阅重要事件 (S/A级)
|
||||
subscribe_types = db.Column(db.Text, nullable=True) # 订阅的事件类型,JSON 数组
|
||||
|
||||
created_at = db.Column(db.DateTime, default=beijing_now)
|
||||
updated_at = db.Column(db.DateTime, default=beijing_now, onupdate=beijing_now)
|
||||
|
||||
user = db.relationship('User', backref='device_tokens')
|
||||
|
||||
|
||||
class SubscriptionPlan(db.Model):
|
||||
"""订阅套餐表"""
|
||||
__tablename__ = 'subscription_plans'
|
||||
@@ -4528,6 +4550,129 @@ def unbind_email():
|
||||
return jsonify({'error': '解绑失败,请重试'}), 500
|
||||
|
||||
|
||||
# ==================== Device Token API (APNs 推送) ====================
|
||||
|
||||
@app.route('/api/device-token', methods=['POST'])
|
||||
def register_device_token():
|
||||
"""
|
||||
注册设备推送 Token
|
||||
App 启动时调用,保存设备 Token 用于推送
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
device_token = data.get('device_token')
|
||||
platform = data.get('platform', 'ios')
|
||||
app_version = data.get('app_version', '1.0.0')
|
||||
|
||||
if not device_token:
|
||||
return jsonify({'success': False, 'message': '缺少 device_token'}), 400
|
||||
|
||||
# 查找是否已存在
|
||||
existing = UserDeviceToken.query.filter_by(device_token=device_token).first()
|
||||
|
||||
if existing:
|
||||
# 更新现有记录
|
||||
existing.platform = platform
|
||||
existing.app_version = app_version
|
||||
existing.is_active = True
|
||||
existing.updated_at = beijing_now()
|
||||
|
||||
# 如果用户已登录,关联用户
|
||||
if session.get('logged_in'):
|
||||
existing.user_id = session.get('user_id')
|
||||
else:
|
||||
# 创建新记录
|
||||
new_token = UserDeviceToken(
|
||||
device_token=device_token,
|
||||
platform=platform,
|
||||
app_version=app_version,
|
||||
user_id=session.get('user_id') if session.get('logged_in') else None,
|
||||
is_active=True
|
||||
)
|
||||
db.session.add(new_token)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Device token 注册成功'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"[API] 注册 device token 失败: {e}")
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/device-token', methods=['DELETE'])
|
||||
def unregister_device_token():
|
||||
"""
|
||||
取消注册设备 Token(用户登出时调用)
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
device_token = data.get('device_token')
|
||||
|
||||
if not device_token:
|
||||
return jsonify({'success': False, 'message': '缺少 device_token'}), 400
|
||||
|
||||
token_record = UserDeviceToken.query.filter_by(device_token=device_token).first()
|
||||
|
||||
if token_record:
|
||||
token_record.is_active = False
|
||||
token_record.updated_at = beijing_now()
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Device token 已取消注册'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"[API] 取消注册 device token 失败: {e}")
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/push-subscription', methods=['POST'])
|
||||
def update_push_subscription():
|
||||
"""
|
||||
更新推送订阅设置
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
device_token = data.get('device_token')
|
||||
|
||||
if not device_token:
|
||||
return jsonify({'success': False, 'message': '缺少 device_token'}), 400
|
||||
|
||||
token_record = UserDeviceToken.query.filter_by(device_token=device_token).first()
|
||||
|
||||
if not token_record:
|
||||
return jsonify({'success': False, 'message': 'Device token 不存在'}), 404
|
||||
|
||||
# 更新订阅设置
|
||||
if 'subscribe_all' in data:
|
||||
token_record.subscribe_all = data['subscribe_all']
|
||||
if 'subscribe_important' in data:
|
||||
token_record.subscribe_important = data['subscribe_important']
|
||||
if 'subscribe_types' in data:
|
||||
token_record.subscribe_types = json.dumps(data['subscribe_types'])
|
||||
|
||||
token_record.updated_at = beijing_now()
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '订阅设置已更新'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"[API] 更新推送订阅失败: {e}")
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/auth/register/email', methods=['POST'])
|
||||
def register_with_email():
|
||||
"""邮箱注册 - 使用Session"""
|
||||
@@ -13530,6 +13675,36 @@ def broadcast_new_event(event):
|
||||
# 清除事件列表缓存,确保用户刷新页面时获取最新数据
|
||||
clear_events_cache()
|
||||
|
||||
# === APNs 推送(只推送重要事件 S/A 级)===
|
||||
if event.importance in ['S', 'A']:
|
||||
try:
|
||||
from apns_push import send_event_notification
|
||||
|
||||
# 获取所有活跃的设备 token(订阅了重要事件的)
|
||||
device_tokens_query = db.session.query(UserDeviceToken.device_token).filter(
|
||||
UserDeviceToken.is_active == True,
|
||||
db.or_(
|
||||
UserDeviceToken.subscribe_all == True,
|
||||
UserDeviceToken.subscribe_important == True
|
||||
)
|
||||
).all()
|
||||
|
||||
tokens = [t[0] for t in device_tokens_query]
|
||||
|
||||
if tokens:
|
||||
print(f'[APNs] 准备推送到 {len(tokens)} 个设备')
|
||||
result = send_event_notification(event, tokens)
|
||||
print(f'[APNs] 推送结果: 成功 {result["success"]}, 失败 {result["failed"]}')
|
||||
else:
|
||||
print('[APNs] 没有活跃的设备 token')
|
||||
|
||||
except ImportError as e:
|
||||
print(f'[APNs WARN] apns_push 模块未安装或配置: {e}')
|
||||
except Exception as apns_error:
|
||||
print(f'[APNs ERROR] 推送失败: {apns_error}')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print(f'[WebSocket DEBUG] ========== 广播完成 ==========\n')
|
||||
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user