diff --git a/apns_push.py b/apns_push.py index 33ec095a..2e4f8085 100644 --- a/apns_push.py +++ b/apns_push.py @@ -156,6 +156,7 @@ def send_event_notification(event, device_tokens: List[str]) -> dict: # 自定义数据 data = { + 'type': 'event', 'event_id': event.id, 'importance': event.importance, 'title': event.title, @@ -173,6 +174,76 @@ def send_event_notification(event, device_tokens: List[str]) -> dict: ) +def send_social_notification(notification, device_tokens: List[str]) -> dict: + """ + 发送社交通知推送 + + Args: + notification: SocialNotification 模型实例或字典 + device_tokens: 设备 Token 列表 + + Returns: + dict: 推送结果 + """ + if not device_tokens: + return {'success': 0, 'failed': 0, 'errors': ['没有设备 Token']} + + # 支持字典或模型实例 + if isinstance(notification, dict): + notif_type = notification.get('type', 'system') + sender_name = notification.get('sender_name', '') + content = notification.get('content', '') + notif_id = notification.get('id') + target_type = notification.get('target_type') + target_id = notification.get('target_id') + else: + notif_type = notification.type + sender_name = notification.sender_name or '' + content = notification.content or '' + notif_id = notification.id + target_type = notification.target_type + target_id = notification.target_id + + # 根据通知类型设置标题 + type_labels = { + 'reply': '💬 新回复', + 'mention': '📢 有人@你', + 'like': '❤️ 收到点赞', + 'follow': '👋 新关注', + 'system': '📣 系统通知', + 'message': '💬 新消息', + } + title = type_labels.get(notif_type, '📣 新通知') + + # 通知内容 + if sender_name and content: + body = f"{sender_name}: {content[:80]}" + ('...' if len(content) > 80 else '') + elif sender_name: + body = f"{sender_name} {type_labels.get(notif_type, '发来通知')}" + elif content: + body = content[:100] + ('...' if len(content) > 100 else '') + else: + body = '你收到了一条新通知' + + # 自定义数据(传递给 App 用于跳转) + data = { + 'type': 'social', + 'notification_id': str(notif_id) if notif_id else None, + 'notification_type': notif_type, + 'target_type': target_type, + 'target_id': target_id, + } + + return send_push_notification( + device_tokens=device_tokens, + title=title, + body=body, + data=data, + badge=1, + priority=10 # 社交通知优先级高 + ) + + # ==================== 数据库操作(需要在 app.py 中实现) ==================== def get_all_device_tokens(db_session) -> List[str]: diff --git a/app.py b/app.py index 9d73bcd7..94259622 100755 --- a/app.py +++ b/app.py @@ -21249,6 +21249,119 @@ app.register_blueprint(prediction_bp) # ==================== 社交通知 API ==================== +def create_and_push_social_notification( + user_id: int, + notification_type: str, + content: str = None, + sender_id: int = None, + sender_name: str = None, + sender_avatar: str = None, + target_type: str = None, + target_id: str = None +) -> 'SocialNotification': + """ + 创建社交通知并发送推送 + + Args: + user_id: 接收通知的用户 ID + notification_type: 通知类型 (reply, mention, like, follow, system, message) + content: 通知内容 + sender_id: 发送者用户 ID(可选) + sender_name: 发送者名称(可选) + sender_avatar: 发送者头像(可选) + target_type: 关联目标类型 (post, reply, channel, message) + target_id: 关联目标 ID + + Returns: + SocialNotification: 创建的通知实例 + """ + try: + # 创建通知记录 + notification = SocialNotification( + user_id=user_id, + type=notification_type, + sender_id=sender_id, + sender_name=sender_name, + sender_avatar=sender_avatar, + content=content, + target_type=target_type, + target_id=target_id, + is_read=False + ) + db.session.add(notification) + db.session.commit() + + # 发送 APNs 推送 + try: + from apns_push import send_social_notification + + # 获取该用户的活跃设备 token + device_tokens_query = db.session.query(UserDeviceToken.device_token).filter( + UserDeviceToken.user_id == user_id, + UserDeviceToken.is_active == True + ).all() + + tokens = [t[0] for t in device_tokens_query] + + if tokens: + print(f'[APNs Social] 准备推送到用户 {user_id} 的 {len(tokens)} 个设备') + result = send_social_notification(notification, tokens) + print(f'[APNs Social] 推送结果: 成功 {result["success"]}, 失败 {result["failed"]}') + else: + print(f'[APNs Social] 用户 {user_id} 没有活跃的设备 token') + + except ImportError as e: + print(f'[APNs Social WARN] apns_push 模块未安装或配置: {e}') + except Exception as apns_error: + print(f'[APNs Social ERROR] 推送失败: {apns_error}') + + return notification + + except Exception as e: + db.session.rollback() + app.logger.error(f"创建社交通知失败: {e}") + raise + + +def broadcast_social_notification_to_users( + user_ids: list, + notification_type: str, + content: str = None, + sender_id: int = None, + sender_name: str = None, + sender_avatar: str = None, + target_type: str = None, + target_id: str = None +) -> int: + """ + 向多个用户广播社交通知(批量创建并推送) + + Args: + user_ids: 接收通知的用户 ID 列表 + 其他参数同 create_and_push_social_notification + + Returns: + int: 成功创建的通知数量 + """ + success_count = 0 + for user_id in user_ids: + try: + create_and_push_social_notification( + user_id=user_id, + notification_type=notification_type, + content=content, + sender_id=sender_id, + sender_name=sender_name, + sender_avatar=sender_avatar, + target_type=target_type, + target_id=target_id + ) + success_count += 1 + except Exception as e: + app.logger.error(f"向用户 {user_id} 发送通知失败: {e}") + return success_count + + @app.route('/api/social/notifications', methods=['GET']) @login_required def get_social_notifications(): @@ -21353,6 +21466,87 @@ def get_social_notifications_unread_count(): return jsonify({'code': 500, 'message': str(e)}), 500 +@app.route('/api/social/notifications/test', methods=['POST']) +@login_required +def test_social_notification(): + """ + 测试社交通知推送(仅供开发测试) + 向当前用户发送一条测试通知 + """ + try: + data = request.get_json() or {} + notification_type = data.get('type', 'system') + content = data.get('content', '这是一条测试通知') + + notification = create_and_push_social_notification( + user_id=current_user.id, + notification_type=notification_type, + content=content, + sender_name='系统测试', + target_type='test', + target_id='test_001' + ) + + return jsonify({ + 'code': 200, + 'message': '测试通知已发送', + 'data': notification.to_dict() + }) + except Exception as e: + app.logger.error(f"测试通知发送失败: {e}") + return jsonify({'code': 500, 'message': str(e)}), 500 + + +@app.route('/api/social/notifications/send', methods=['POST']) +@login_required +def send_social_notification_api(): + """ + 发送社交通知 API(供后端其他服务调用) + + 请求体: + { + "user_id": 123, // 必填,接收者用户 ID + "type": "message", // 必填,通知类型 + "content": "消息内容", // 可选 + "sender_id": 456, // 可选,发送者 ID + "sender_name": "张三", // 可选 + "sender_avatar": "url", // 可选 + "target_type": "post", // 可选 + "target_id": "post_123" // 可选 + } + """ + try: + data = request.get_json() + if not data: + return jsonify({'code': 400, 'message': '缺少请求体'}), 400 + + user_id = data.get('user_id') + notification_type = data.get('type') + + if not user_id or not notification_type: + return jsonify({'code': 400, 'message': 'user_id 和 type 为必填'}), 400 + + notification = create_and_push_social_notification( + user_id=user_id, + notification_type=notification_type, + content=data.get('content'), + sender_id=data.get('sender_id'), + sender_name=data.get('sender_name'), + sender_avatar=data.get('sender_avatar'), + target_type=data.get('target_type'), + target_id=data.get('target_id') + ) + + return jsonify({ + 'code': 200, + 'message': '通知已发送', + 'data': notification.to_dict() + }) + except Exception as e: + app.logger.error(f"发送社交通知失败: {e}") + return jsonify({'code': 500, 'message': str(e)}), 500 + + if __name__ == '__main__': # 创建数据库表 with app.app_context():