如何用PHP项目实现用户续费提醒?

wen java案例 3

本文目录导读:

如何用PHP项目实现用户续费提醒?

  1. 核心设计方案
  2. 核心功能实现
  3. 定时任务实现
  4. Web触发接口
  5. 邮件发送类
  6. 配置示例
  7. 优化建议

我来详细说明如何用PHP实现用户续费提醒功能,包括多种实现方案和代码示例。

核心设计方案

数据库设计

-- 用户表
CREATE TABLE `users` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(50) NOT NULL,
    `email` varchar(100) NOT NULL,
    `phone` varchar(20) DEFAULT NULL,
    `status` tinyint(1) DEFAULT 1 COMMENT '1:正常 0:暂停',
    `created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
);
-- 会员/订阅表
CREATE TABLE `subscriptions` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `user_id` int(11) NOT NULL,
    `plan_name` varchar(50) NOT NULL COMMENT '套餐名称',
    `start_date` date NOT NULL,
    `expire_date` date NOT NULL,
    `auto_renew` tinyint(1) DEFAULT 0 COMMENT '是否自动续费',
    `reminded_7` tinyint(1) DEFAULT 0 COMMENT '7天前已提醒',
    `reminded_3` tinyint(1) DEFAULT 0 COMMENT '3天前已提醒',
    `reminded_1` tinyint(1) DEFAULT 0 COMMENT '1天前已提醒',
    `status` tinyint(1) DEFAULT 1 COMMENT '1:有效 0:已过期',
    PRIMARY KEY (`id`),
    FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
);
-- 提醒日志表
CREATE TABLE `reminder_logs` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `subscription_id` int(11) NOT NULL,
    `user_id` int(11) NOT NULL,
    `remind_type` varchar(20) COMMENT 'email/sms/wechat',
    `remind_day` int(11) COMMENT '提前天数',
    `sent_at` timestamp DEFAULT CURRENT_TIMESTAMP,
    `status` tinyint(1) DEFAULT 1 COMMENT '1:成功 0:失败',
    `error_msg` text,
    PRIMARY KEY (`id`)
);

核心功能实现

提醒服务类

<?php
// ReminderService.php
class ReminderService {
    private $db;
    private $mailer;
    private $smsService;
    public function __construct($db, $mailer = null, $smsService = null) {
        $this->db = $db;
        $this->mailer = $mailer;
        $this->smsService = $smsService;
    }
    /**
     * 检查并发送提醒
     */
    public function checkAndSendReminders() {
        $today = date('Y-m-d');
        $remindDates = [
            7 => 'reminded_7',
            3 => 'reminded_3', 
            1 => 'reminded_1'
        ];
        foreach ($remindDates as $days => $field) {
            $targetDate = date('Y-m-d', strtotime("+{$days} days"));
            // 查询需要提醒的用户
            $sql = "SELECT s.*, u.username, u.email, u.phone 
                    FROM subscriptions s 
                    JOIN users u ON s.user_id = u.id 
                    WHERE s.expire_date = :target_date 
                    AND s.status = 1 
                    AND s.{$field} = 0";
            $stmt = $this->db->prepare($sql);
            $stmt->execute(['target_date' => $targetDate]);
            $subscriptions = $stmt->fetchAll();
            foreach ($subscriptions as $sub) {
                $this->sendReminder($sub, $days);
                $this->updateReminderFlag($sub['id'], $field);
            }
        }
    }
    /**
     * 发送提醒
     */
    private function sendReminder($subscription, $days) {
        $template = $this->getReminderTemplate($subscription, $days);
        $success = true;
        $errorMsg = '';
        // 发送邮件
        if (!empty($subscription['email'])) {
            try {
                $this->mailer->send(
                    $subscription['email'],
                    $template['subject'],
                    $template['body']
                );
            } catch (Exception $e) {
                $success = false;
                $errorMsg = $e->getMessage();
            }
        }
        // 发送短信(可选)
        if (!empty($subscription['phone']) && $days <= 3) {
            try {
                $this->smsService->send(
                    $subscription['phone'],
                    $template['sms']
                );
            } catch (Exception $e) {
                $success = false;
                $errorMsg = $e->getMessage();
            }
        }
        // 记录日志
        $this->logReminder($subscription['id'], $subscription['user_id'], $days, $success, $errorMsg);
    }
    /**
     * 获取提醒模板
     */
    private function getReminderTemplate($subscription, $days) {
        $username = $subscription['username'];
        $planName = $subscription['plan_name'];
        $expireDate = $subscription['expire_date'];
        $templates = [
            7 => [
                'subject' => "尊敬的{$username},您的{$planName}即将到期",
                'body' => "您好 {$username}:<br><br>
                          您的 {$planName} 将在 7 天后({$expireDate})到期。<br>
                          请及时续费以继续享受服务。<br><br>
                          <a href='https://yourdomain.com/renew'>立即续费</a>",
                'sms' => "【您的应用名】{$username},您的{$planName}将在7天后到期,请及时续费。"
            ],
            3 => [
                'subject' => "重要提醒:{$planName} 还有3天到期",
                'body' => "您好 {$username}:<br><br>
                          紧急提醒!您的 {$planName} 将在 3 天后({$expireDate})到期。<br>
                          为避免服务中断,请尽快续费。<br><br>
                          <a href='https://yourdomain.com/renew'>立即续费</a>",
                'sms' => "【您的应用名】紧急提醒!{$username},您的{$planName}将在3天后到期,请尽快续费。"
            ],
            1 => [
                'subject' => "最后提醒:{$planName} 明天到期",
                'body' => "您好 {$username}:<br><br>
                          您的 {$planName} 将于明天({$expireDate})到期。<br>
                          今天是续费的最后机会,服务将于明天停止。<br><br>
                          <a href='https://yourdomain.com/renew'>立即续费</a>",
                'sms' => "【您的应用名】最后提醒!{$username},您的{$planName}明天到期,今天续费避免服务中断。"
            ]
        ];
        return $templates[$days] ?? $templates[7];
    }
    /**
     * 更新提醒标记
     */
    private function updateReminderFlag($subscriptionId, $field) {
        $sql = "UPDATE subscriptions SET {$field} = 1 WHERE id = :id";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['id' => $subscriptionId]);
    }
    /**
     * 记录提醒日志
     */
    private function logReminder($subscriptionId, $userId, $days, $success, $errorMsg = '') {
        $sql = "INSERT INTO reminder_logs (subscription_id, user_id, remind_type, remind_day, status, error_msg) 
                VALUES (:sub_id, :user_id, :type, :day, :status, :error)";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([
            'sub_id' => $subscriptionId,
            'user_id' => $userId,
            'type' => 'email',
            'day' => $days,
            'status' => $success ? 1 : 0,
            'error' => $errorMsg
        ]);
    }
}

用户端续费提醒页面

<?php
// user_reminder.php
class UserReminderController {
    private $db;
    public function __construct($db) {
        $this->db = $db;
    }
    /**
     * 获取用户续费信息
     */
    public function getUserRenewalInfo($userId) {
        $sql = "SELECT s.*, u.username, u.email 
                FROM subscriptions s 
                JOIN users u ON s.user_id = u.id 
                WHERE s.user_id = :user_id 
                AND s.status = 1 
                ORDER BY s.expire_date DESC 
                LIMIT 3";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['user_id' => $userId]);
        return $stmt->fetchAll();
    }
    /**
     * 渲染续费提醒页面
     */
    public function renderReminderPage($userId) {
        $subscriptions = $this->getUserRenewalInfo($userId);
        ?>
        <!DOCTYPE html>
        <html>
        <head>
            <title>续费管理</title>
            <style>
                .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
                .card { border: 1px solid #ddd; border-radius: 8px; padding: 20px; margin-bottom: 20px; }
                .expiring-soon { border-left: 4px solid #ff4444; }
                .expiring { border-left: 4px solid #ffaa00; }
                .btn-renew { background: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
                .countdown { font-size: 24px; font-weight: bold; color: #ff4444; }
            </style>
        </head>
        <body>
            <div class="container">
                <h1>我的订阅</h1>
                <?php foreach ($subscriptions as $sub): ?>
                    <?php 
                        $daysLeft = (strtotime($sub['expire_date']) - time()) / 86400;
                        $cardClass = $daysLeft <= 3 ? 'expiring-soon' : ($daysLeft <= 7 ? 'expiring' : '');
                    ?>
                    <div class="card <?php echo $cardClass; ?>">
                        <h3><?php echo htmlspecialchars($sub['plan_name']); ?></h3>
                        <p>到期时间:<?php echo $sub['expire_date']; ?></p>
                        <p>剩余天数:<span class="countdown"><?php echo ceil($daysLeft); ?></span> 天</p>
                        <?php if ($daysLeft <= 7): ?>
                            <div class="alert alert-warning">
                                ⚠️ 您的订阅即将到期,请及时续费!
                            </div>
                            <button class="btn-renew" onclick="renewSubscription(<?php echo $sub['id']; ?>)">
                                立即续费
                            </button>
                        <?php else: ?>
                            <button class="btn-renew" onclick="renewSubscription(<?php echo $sub['id']; ?>)">
                                续费
                            </button>
                        <?php endif; ?>
                    </div>
                <?php endforeach; ?>
            </div>
            <script>
            function renewSubscription(subscriptionId) {
                // 这里实现续费逻辑,可以是弹窗或跳转到支付页面
                if (confirm('确定要续费吗?')) {
                    window.location.href = '/renew.php?id=' + subscriptionId;
                }
            }
            // 实时刷新剩余天数
            setInterval(function() {
                location.reload();
            }, 3600000); // 每小时刷新一次
            </script>
        </body>
        </html>
        <?php
    }
}

定时任务实现

Cron任务配置

# 每天凌晨2点检查并发送提醒
0 2 * * * /usr/bin/php /path/to/your/project/cron/reminder.php
# 或每6小时检查一次
0 */6 * * * /usr/bin/php /path/to/your/project/cron/reminder.php

定时任务脚本

<?php
// cron/reminder.php
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../classes/ReminderService.php';
// 初始化数据库连接
$db = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 初始化邮件服务
$mailer = new Mailer($smtp_config);
// 初始化短信服务(可选)
$smsService = new SmsService($sms_config);
// 执行提醒检查
$reminder = new ReminderService($db, $mailer, $smsService);
try {
    $reminder->checkAndSendReminders();
    echo "[" . date('Y-m-d H:i:s') . "] Reminders sent successfully.\n";
} catch (Exception $e) {
    echo "[" . date('Y-m-d H:i:s') . "] Error: " . $e->getMessage() . "\n";
    error_log("Reminder cron error: " . $e->getMessage());
}

Web触发接口

<?php
// api/renewal_reminder.php
class ReminderAPI {
    private $reminderService;
    public function __construct($reminderService) {
        $this->reminderService = $reminderService;
    }
    /**
     * 管理员手动触发提醒
     */
    public function manualTrigger() {
        // 验证管理员权限
        if (!$this->checkAdminAuth()) {
            http_response_code(401);
            return json_encode(['error' => 'Unauthorized']);
        }
        try {
            $this->reminderService->checkAndSendReminders();
            return json_encode([
                'success' => true,
                'message' => '提醒已发送'
            ]);
        } catch (Exception $e) {
            http_response_code(500);
            return json_encode([
                'success' => false,
                'message' => $e->getMessage()
            ]);
        }
    }
    /**
     * 检查特定用户的续费状态
     */
    public function checkUserStatus($userId) {
        $sql = "SELECT s.*, DATEDIFF(s.expire_date, CURDATE()) as days_left 
                FROM subscriptions s 
                WHERE s.user_id = :user_id 
                AND s.status = 1 
                ORDER BY s.expire_date ASC LIMIT 1";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['user_id' => $userId]);
        $result = $stmt->fetch();
        if ($result) {
            $daysLeft = $result['days_left'];
            $alertLevel = $daysLeft <= 3 ? 'critical' : ($daysLeft <= 7 ? 'warning' : 'normal');
            return json_encode([
                'status' => 'active',
                'days_left' => $daysLeft,
                'alert_level' => $alertLevel,
                'expire_date' => $result['expire_date'],
                'should_renew' => $daysLeft <= 7
            ]);
        }
        return json_encode(['status' => 'no_subscription']);
    }
}

邮件发送类

<?php
// Mailer.php
class Mailer {
    private $config;
    public function __construct($config) {
        $this->config = $config;
    }
    /**
     * 发送邮件
     */
    public function send($to, $subject, $body) {
        // 使用PHP内置mail函数
        $headers = "MIME-Version: 1.0\r\n";
        $headers .= "Content-type: text/html; charset=utf-8\r\n";
        $headers .= "From: " . $this->config['from'] . "\r\n";
        $headers .= "Reply-To: " . $this->config['reply_to'] . "\r\n";
        return mail($to, $subject, $body, $headers);
    }
    /**
     * 使用SMTP发送(推荐)
     */
    public function sendSMTP($to, $subject, $body) {
        require_once 'PHPMailer/PHPMailer.php';
        require_once 'PHPMailer/SMTP.php';
        $mail = new PHPMailer\PHPMailer\PHPMailer(true);
        try {
            $mail->isSMTP();
            $mail->Host = $this->config['smtp_host'];
            $mail->SMTPAuth = true;
            $mail->Username = $this->config['smtp_user'];
            $mail->Password = $this->config['smtp_pass'];
            $mail->SMTPSecure = 'tls';
            $mail->Port = $this->config['smtp_port'];
            $mail->setFrom($this->config['from'], $this->config['from_name']);
            $mail->addAddress($to);
            $mail->isHTML(true);
            $mail->Subject = $subject;
            $mail->Body = $body;
            return $mail->send();
        } catch (Exception $e) {
            throw new Exception("邮件发送失败: " . $mail->ErrorInfo);
        }
    }
}

配置示例

<?php
// config.php
// 数据库配置
$db_host = 'localhost';
$db_name = 'your_database';
$db_user = 'your_user';
$db_pass = 'your_password';
// 邮件配置
$smtp_config = [
    'from' => 'noreply@yourdomain.com',
    'from_name' => '您的应用名',
    'reply_to' => 'support@yourdomain.com',
    'smtp_host' => 'smtp.yourdomain.com',
    'smtp_user' => 'your_smtp_user',
    'smtp_pass' => 'your_smtp_pass',
    'smtp_port' => 587
];
// 短信配置(可选)
$sms_config = [
    'api_key' => 'your_sms_api_key',
    'api_secret' => 'your_sms_api_secret',
    'sender' => 'YourApp'
];

优化建议

  1. 批量处理:大量用户时使用批量处理,减少数据库查询
  2. 队列系统:使用Redis/RabbitMQ处理发送任务
  3. 重试机制:发送失败时自动重试3次
  4. 个性化设置:允许用户设置提醒时间和方式
  5. A/B测试:测试不同提醒文案的效果

这个方案可以覆盖大部分续费提醒需求,根据实际业务规模可以适当调整实现方式。

抱歉,评论功能暂时关闭!