| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- import asyncio
- from typing import Dict, Any
- from redis.asyncio import Redis
- from app.services.wechat_service import WechatService
- from app.services.email_authorizations_service import EmailAuthorizationService
- from app.utils.redis_utils import redis_qpop
- async def notification_consumer(redis_client: Redis):
- """
- 异步消费 Redis 队列 vas_notification_queue
- """
- queue_name = "vas_notification_queue"
- return
- while True:
- try:
- # 阻塞获取队列消息
- message: Dict[str, Any] = await redis_qpop(redis_client, queue_name, timeout=5)
- if not message:
- await asyncio.sleep(1) # 队列为空,休眠
- continue
- channels = message.get("channels", [])
- template_id = message.get("template_id")
- payload = message.get("payload", {})
- user_id = message.get("user_id")
- # 按渠道发送
- if "email" in channels:
- # EmailService.create(user_id, template_id, payload) 是你自己实现的发送逻辑
- await EmailAuthorizationService.send(user_id=user_id, template_id=template_id, payload=payload)
- if "wechat" in channels:
- api_token = payload.get("api_token")
- content = payload.get("message") or payload.get("content")
- if api_token and content:
- await WechatService.push_to_wechat({"api_token": api_token, "message": content})
- print(f"✅ Notification sent: {message.get('notification_id')}")
- except Exception as e:
- print(f"⚠️ Notification consumer error: {e}")
- await asyncio.sleep(1) # 避免异常循环过快
- def template_for_bind_email(payload):
-
- template = '''
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>Email Verification</title>
- <style>
- body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; }
- .container { max-width: 600px; margin: 20px auto; background-color: #ffffff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
- .code { font-size: 24px; font-weight: bold; letter-spacing: 5px; color: #333; background-color: #f0f0f0; padding: 15px; text-align: center; border-radius: 4px; margin: 20px 0; }
- .footer { font-size: 12px; color: #888; margin-top: 30px; text-align: center; }
- </style>
- </head>
- <body>
- <div class="container">
- <h2>Verify your email address</h2>
- <p>Hello,</p>
- <p>You requested to bind this email address to your <strong>{{app_name}}</strong> account. Please use the verification code below to proceed:</p>
-
- <div class="code">{{code}}</div>
-
- <p>This code will expire in <strong>{{expiration_time}}</strong>.</p>
- <p>If you did not request this change, please ignore this email.</p>
-
- <br>
- <p>Best regards,<br>The {{app_name}} Team</p>
-
- <div class="footer">
- © 2025 {{app_name}}. All rights reserved.
- </div>
- </div>
- </body>
- </html>
- '''
- def template_for_reset_pwd(payload):
- template = '''
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>Reset Password</title>
- <style>
- body { font-family: 'Helvetica Neue', Arial, sans-serif; background-color: #f9f9f9; margin: 0; padding: 0; color: #333; }
- .container { max-width: 600px; margin: 40px auto; background-color: #ffffff; padding: 40px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }
- .header { border-bottom: 1px solid #eee; padding-bottom: 20px; margin-bottom: 30px; }
- .header h2 { margin: 0; color: #333; }
- .code { font-size: 32px; font-weight: bold; letter-spacing: 5px; color: #2563eb; background-color: #eff6ff; padding: 20px; text-align: center; border-radius: 8px; margin: 30px 0; border: 1px solid #dbeafe; }
- .warning { background-color: #fff7ed; border-left: 4px solid #f97316; padding: 15px; font-size: 14px; color: #c2410c; margin-top: 30px; }
- .footer { font-size: 12px; color: #999; margin-top: 40px; text-align: center; border-top: 1px solid #eee; padding-top: 20px; }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header">
- <h2>Password Reset Request</h2>
- </div>
-
- <p>Hello,</p>
- <p>We received a request to reset the password for your <strong>{{app_name}}</strong> account. Please use the following code to verify your identity:</p>
-
- <div class="code">{{code}}</div>
-
- <p>This code is valid for <strong>{{expiration_time}}</strong>.</p>
-
- <div class="warning">
- <strong>Security Tip:</strong> If you did not request a password reset, please ignore this email. No changes will be made to your account.
- </div>
-
- <br>
- <p>Best regards,<br>The {{app_name}} Team</p>
-
- <div class="footer">
- © 2025 {{app_name}}. All rights reserved.<br>
- This is an automated message, please do not reply.
- </div>
- </div>
- </body>
- </html>
- '''
-
- def template_for_login_credentials(payload):
- template = '''
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>Your Account Details</title>
- <style>
- body { font-family: 'Helvetica Neue', Arial, sans-serif; background-color: #f4f6f8; margin: 0; padding: 0; color: #333; }
- .container { max-width: 600px; margin: 40px auto; background-color: #ffffff; padding: 40px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
- .header { text-align: center; border-bottom: 1px solid #eee; padding-bottom: 20px; margin-bottom: 30px; }
- .header h1 { font-size: 24px; color: #1a1a1a; margin: 0; }
- .creds-box { background-color: #f0f7ff; border: 1px solid #dbeafe; border-radius: 8px; padding: 20px; margin: 25px 0; }
- .creds-item { margin-bottom: 10px; font-size: 16px; }
- .creds-label { font-weight: bold; color: #555; width: 100px; display: inline-block; }
- .creds-value { font-family: 'Courier New', Courier, monospace; font-weight: bold; color: #2563eb; }
- .btn { display: block; width: 200px; margin: 30px auto; background-color: #2563eb; color: #ffffff !important; text-align: center; padding: 12px 0; border-radius: 6px; text-decoration: none; font-weight: bold; }
- .note { font-size: 13px; color: #666; background-color: #fff4e5; padding: 10px; border-radius: 4px; border-left: 4px solid #f97316; }
- .footer { font-size: 12px; color: #999; margin-top: 40px; text-align: center; }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header">
- <h1>Welcome to {{app_name}}</h1>
- </div>
-
- <p>Dear User,</p>
- <p>Your account has been successfully set up. Below are your temporary login credentials.</p>
-
- <div class="creds-box">
- <div class="creds-item">
- <span class="creds-label">Username:</span>
- <span class="creds-value">{{username}}</span>
- </div>
- <div class="creds-item">
- <span class="creds-label">Password:</span>
- <span class="creds-value">{{password}}</span>
- </div>
- </div>
- <div class="note">
- <strong>Important:</strong> For your security, please change your password immediately after logging in.
- </div>
- <a href="{{login_url}}" class="btn">Log In Now</a>
-
- <p style="text-align: center; font-size: 14px;">
- Or copy this link: <a href="{{login_url}}">{{login_url}}</a>
- </p>
- <div class="footer">
- © 2025 {{app_name}}. All rights reserved.<br>
- If you did not request this account, please contact support.
- </div>
- </div>
- </body>
- </html>
- '''
-
- def template_ticket_open(payload):
- template = '''
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>Ticket Created</title>
- <style>
- body { font-family: 'Helvetica Neue', Arial, sans-serif; background-color: #f4f6f8; margin: 0; padding: 0; color: #333; }
- .container { max-width: 600px; margin: 40px auto; background-color: #ffffff; padding: 40px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
- .header { border-bottom: 1px solid #eee; padding-bottom: 20px; margin-bottom: 30px; }
- .header h1 { font-size: 22px; color: #1a1a1a; margin: 0; }
- .ticket-info { background-color: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; margin: 20px 0; }
- .info-row { margin-bottom: 10px; display: flex; justify-content: space-between; }
- .info-label { color: #64748b; font-size: 14px; }
- .info-value { font-weight: bold; color: #0f172a; font-size: 14px; }
- .btn { display: block; width: 200px; margin: 30px auto; background-color: #2563eb; color: #ffffff !important; text-align: center; padding: 12px 0; border-radius: 6px; text-decoration: none; font-weight: bold; font-size: 14px; }
- .footer { font-size: 12px; color: #94a3b8; margin-top: 40px; text-align: center; }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header">
- <h1>Support Request Received</h1>
- </div>
-
- <p>Hello {{username}},</p>
- <p>We wanted to let you know that we've received your request. Our team is currently reviewing the details.</p>
-
- <div class="ticket-info">
- <div class="info-row">
- <span class="info-label">Ticket ID:</span>
- <span class="info-value">#{{ticket_id}}</span>
- </div>
- <div class="info-row">
- <span class="info-label">Type:</span>
- <span class="info-value">{{ticket_type}}</span>
- </div>
- <div class="info-row" style="margin-bottom: 0;">
- <span class="info-label">Time:</span>
- <span class="info-value">{{created_at}}</span>
- </div>
- </div>
- <p>We usually reply within 24 hours. You will receive an email notification when our agent replies.</p>
- <a href="{{ticket_url}}" class="btn">View Ticket Details</a>
-
- <div class="footer">
- © 2025 {{app_name}}. All rights reserved.<br>
- Please do not reply to this email directly.
- </div>
- </div>
- </body>
- </html>
- '''
-
- def template_confirm_payment(payload):
- template = {
- "touser": "ADMIN_USER_ID",
- "msgtype": "textcard",
- "agentid": 1000001,
- "textcard": {
- "title": "💰 待确认:收到新的手动转账",
- "description": "<div class=\"gray\">2025-12-31 10:30:00</div> <br>订单号:ORD-20251231-001<br>用户:user@example.com<br><div class=\"highlight\">金额:¥ 3,500.00</div><br>请核实资金到账后,点击卡片确认收款。",
- "url": "https://admin.visafly.com/payment/confirm?payment_id=123&token=secure_token_abc",
- "btntxt": "立即确认"
- }
- }
|