本文目录导读:

我来详细说明如何用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'
];
优化建议
- 批量处理:大量用户时使用批量处理,减少数据库查询
- 队列系统:使用Redis/RabbitMQ处理发送任务
- 重试机制:发送失败时自动重试3次
- 个性化设置:允许用户设置提醒时间和方式
- A/B测试:测试不同提醒文案的效果
这个方案可以覆盖大部分续费提醒需求,根据实际业务规模可以适当调整实现方式。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播