本文目录导读:

我来为你提供PHP用户协议更新通知的完整实现方案,包含数据库设计、核心代码和发送逻辑。
数据库设计
-- 用户协议表
CREATE TABLE `user_agreements` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`version` varchar(20) NOT NULL COMMENT '协议版本号', varchar(200) NOT NULL COMMENT '协议标题',
`content` text NOT NULL COMMENT '协议内容',
`effective_date` datetime NOT NULL COMMENT '生效日期',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_version` (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户协议接受记录表
CREATE TABLE `user_agreement_acceptances` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户ID',
`agreement_id` int(11) NOT NULL COMMENT '协议ID',
`accept_ip` varchar(45) DEFAULT NULL COMMENT '接受IP',
`accept_count` int(4) DEFAULT '1' COMMENT '查看次数',
`last_accept_at` datetime DEFAULT NULL COMMENT '最后接受时间',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_agreement` (`user_id`,`agreement_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户协议通知记录表
CREATE TABLE `agreement_notifications` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户ID',
`agreement_id` int(11) NOT NULL COMMENT '协议ID',
`notify_type` enum('email','sms','push') NOT NULL DEFAULT 'email' COMMENT '通知方式',
`status` tinyint(1) DEFAULT '0' COMMENT '0待发送 1成功 2失败',
`retry_count` int(4) DEFAULT '0' COMMENT '重试次数',
`error_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`sent_at` datetime DEFAULT NULL COMMENT '发送时间',
PRIMARY KEY (`id`),
KEY `idx_user_status` (`user_id`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
核心PHP类实现
1 协议管理类 AgreementManager.php
<?php
class AgreementManager {
private $db;
public function __construct($db) {
$this->db = $db;
}
/**
* 发布新版本协议
*/
public function publishNewAgreement($title, $content, $effectiveDate = null) {
$effectiveDate = $effectiveDate ?: date('Y-m-d H:i:s');
$version = 'v' . date('YmdHis');
try {
$stmt = $this->db->prepare(
"INSERT INTO user_agreements (version, title, content, effective_date)
VALUES (?, ?, ?, ?)"
);
$stmt->execute([$version, $title, $content, $effectiveDate]);
return $this->db->lastInsertId();
} catch (PDOException $e) {
error_log("发布协议失败: " . $e->getMessage());
return false;
}
}
/**
* 获取当前有效协议
*/
public function getLatestAgreement() {
$stmt = $this->db->prepare(
"SELECT * FROM user_agreements
WHERE effective_date <= NOW()
ORDER BY effective_date DESC
LIMIT 1"
);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* 获取用户未接受的协议
*/
public function getUnacceptedAgreements($userId) {
$sql = "
SELECT a.* FROM user_agreements a
WHERE a.effective_date <= NOW()
AND a.id NOT IN (
SELECT agreement_id FROM user_agreement_acceptances
WHERE user_id = ?
)
ORDER BY a.effective_date DESC
";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* 记录用户接受协议
*/
public function acceptAgreement($userId, $agreementId, $ip = null) {
try {
// 检查是否已存在记录
$stmt = $this->db->prepare(
"SELECT * FROM user_agreement_acceptances
WHERE user_id = ? AND agreement_id = ?"
);
$stmt->execute([$userId, $agreementId]);
if ($stmt->rowCount() > 0) {
// 更新现有记录
$updateStmt = $this->db->prepare(
"UPDATE user_agreement_acceptances
SET accept_count = accept_count + 1,
last_accept_at = NOW(),
accept_ip = ?
WHERE user_id = ? AND agreement_id = ?"
);
$updateStmt->execute([$ip, $userId, $agreementId]);
} else {
// 插入新记录
$insertStmt = $this->db->prepare(
"INSERT INTO user_agreement_acceptances
(user_id, agreement_id, accept_ip)
VALUES (?, ?, ?)"
);
$insertStmt->execute([$userId, $agreementId, $ip]);
}
// 删除通知记录
$deleteStmt = $this->db->prepare(
"DELETE FROM agreement_notifications
WHERE user_id = ? AND agreement_id = ?"
);
$deleteStmt->execute([$userId, $agreementId]);
return true;
} catch (PDOException $e) {
error_log("接受协议失败: " . $e->getMessage());
return false;
}
}
/**
* 检查用户是否已接受最新协议
*/
public function hasAcceptedLatestAgreement($userId) {
$latest = $this->getLatestAgreement();
if (!$latest) return true;
$stmt = $this->db->prepare(
"SELECT COUNT(*) FROM user_agreement_acceptances
WHERE user_id = ? AND agreement_id = ?"
);
$stmt->execute([$userId, $latest['id']]);
return $stmt->fetchColumn() > 0;
}
}
2 通知发送类 AgreementNotifier.php
<?php
class AgreementNotifier {
private $db;
private $mailer;
private $smsClient;
public function __construct($db, $mailer = null, $smsClient = null) {
$this->db = $db;
$this->mailer = $mailer;
$this->smsClient = $smsClient;
}
/**
* 发送协议更新通知给所有用户
*/
public function notifyAllUsers($agreementId, $userList = null) {
// 获取所有用户
if (!$userList) {
$userList = $this->getAllUsers();
}
foreach ($userList as $user) {
$this->createNotification($user['id'], $agreementId, 'email');
}
return $this->processPendingNotifications();
}
/**
* 创建通知记录
*/
public function createNotification($userId, $agreementId, $type = 'email') {
$stmt = $this->db->prepare(
"INSERT INTO agreement_notifications
(user_id, agreement_id, notify_type)
VALUES (?, ?, ?)"
);
$stmt->execute([$userId, $agreementId, $type]);
return $this->db->lastInsertId();
}
/**
* 处理待发送的通知队列
*/
public function processPendingNotifications($limit = 100) {
$stmt = $this->db->prepare(
"SELECT n.*, u.email, u.username
FROM agreement_notifications n
JOIN users u ON n.user_id = u.id
WHERE n.status = 0
LIMIT ?"
);
$stmt->execute([$limit]);
$notifications = $stmt->fetchAll(PDO::FETCH_ASSOC);
$successCount = 0;
foreach ($notifications as $noti) {
$result = $this->sendNotification($noti);
if ($result) {
$successCount++;
}
}
return $successCount;
}
/**
* 发送具体通知
*/
private function sendNotification($notification) {
try {
switch ($notification['notify_type']) {
case 'email':
$sent = $this->sendEmail($notification);
break;
case 'sms':
$sent = $this->sendSMS($notification);
break;
default:
$sent = false;
}
// 更新通知状态
$status = $sent ? 1 : 2;
$errorMsg = $sent ? null : '发送失败';
$updateStmt = $this->db->prepare(
"UPDATE agreement_notifications
SET status = ?, sent_at = NOW(), error_msg = ?,
retry_count = retry_count + 1
WHERE id = ?"
);
$updateStmt->execute([$status, $errorMsg, $notification['id']]);
return $sent;
} catch (Exception $e) {
$this->updateErrorStatus($notification['id'], $e->getMessage());
return false;
}
}
/**
* 发送邮件通知
*/
private function sendEmail($notification) {
// 获取协议信息
$stmt = $this->db->prepare(
"SELECT * FROM user_agreements WHERE id = ?"
);
$stmt->execute([$notification['agreement_id']]);
$agreement = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$agreement) return false;
$subject = '【重要通知】用户协议更新 - ' . $agreement['version'];
$emailContent = "
<html>
<body>
<h2>尊敬的用户,您好!</h2>
<p>我们的服务协议已更新至<strong>{$agreement['version']}</strong>版本。</p>
<p>更新内容摘要:</p>
<p>{$agreement['title']}</p>
<p>生效日期:{$agreement['effective_date']}</p>
<p>请点击以下链接查看完整的协议内容:</p>
<p><a href='https://yourdomain.com/agreement/{$agreement['id']}'>查看协议详情</a></p>
<p>如您未在7天内确认接受新协议,您的账户可能会受到限制。</p>
<p>如有疑问,请联系客服。</p>
</body>
</html>
";
// 使用你的邮件库发送,这里以PHPMailer为例
if ($this->mailer) {
$this->mailer->addAddress($notification['email']);
$this->mailer->Subject = $subject;
$this->mailer->isHTML(true);
$this->mailer->Body = $emailContent;
return $this->mailer->send();
}
return false;
}
/**
* 发送短信通知(可选)
*/
private function sendSMS($notification) {
// 如果需要短信通知,在这里实现
// 使用阿里云、腾讯云等短信服务
return true;
}
/**
* 更新错误状态
*/
private function updateErrorStatus($notiId, $errorMsg) {
$stmt = $this->db->prepare(
"UPDATE agreement_notifications
SET status = 2, error_msg = ?,
retry_count = retry_count + 1
WHERE id = ?"
);
$stmt->execute([$errorMsg, $notiId]);
}
/**
* 获取所有用户
*/
private function getAllUsers() {
$stmt = $this->db->query("SELECT id, email, username FROM users");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
3 用户协议管理控制器 AgreementController.php
<?php
class AgreementController {
private $agreementManager;
private $notifier;
public function __construct($db, $mailer = null) {
$this->agreementManager = new AgreementManager($db);
$this->notifier = new AgreementNotifier($db, $mailer);
}
/**
* 处理用户接受协议的请求
*/
public function handleAcceptance($userId, $agreementId) {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
return json_encode(['error' => 'Invalid request method']);
}
$ip = $_SERVER['REMOTE_ADDR'];
$result = $this->agreementManager->acceptAgreement($userId, $agreementId, $ip);
return json_encode([
'success' => $result,
'message' => $result ? '已接受协议' : '接受协议失败'
]);
}
/**
* 获取用户需要接受的协议列表
*/
public function getPendingAgreements($userId) {
$agreements = $this->agreementManager->getUnacceptedAgreements($userId);
return json_encode($agreements);
}
/**
* 发布新协议并通知用户
*/
public function publishAndNotify($title, $content, $effectiveDate = null) {
$agreementId = $this->agreementManager->publishNewAgreement(
$title, $content, $effectiveDate
);
if ($agreementId) {
// 后台任务异步处理(可以使用redis队列等)
$this->notifier->notifyAllUsers($agreementId);
return "协议发布成功,通知已发送";
}
return "协议发布失败";
}
}
前端页面示例 agreement.php
<?php
// 检查用户登录
session_start();
require_once 'config/database.php';
require_once 'classes/AgreementManager.php';
$db = getDB();
$agreementManager = new AgreementManager($db);
$userId = $_SESSION['user_id'] ?? null;
if (!$userId) {
header('Location: login.php');
exit;
}
$pendingAgreements = $agreementManager->getUnacceptedAgreements($userId);
// 如果用户已接受所有协议,重定向到主页
if (empty($pendingAgreements)) {
header('Location: dashboard.php');
exit;
}
$currentAgreement = $pendingAgreements[0]; // 取最新的一个
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">用户协议更新</title>
<meta name="csrf-token" content="<?php echo $_SESSION['csrf_token']; ?>">
<style>
body { font-family: 'Microsoft YaHei', sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 800px; margin: 0 auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header { text-align: center; margin-bottom: 30px; }
.header h1 { color: #333; }
.agreement-content {
background: #f9f9f9;
padding: 20px;
border-radius: 5px;
max-height: 400px;
overflow-y: auto;
margin-bottom: 20px;
line-height: 1.6;
}
.actions { display: flex; justify-content: center; gap: 20px; }
.btn {
padding: 10px 30px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
.btn-accept { background: #4CAF50; color: #fff; }
.btn-decline { background: #f44336; color: #fff; }
.checkbox-container { margin: 20px 0; }
.checkbox-container label { font-size: 14px; }
.error-msg { color: #f44336; text-align: center; margin-top: 10px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>用户协议更新通知</h1>
<p>版本:<?php echo htmlspecialchars($currentAgreement['version']); ?> |
生效日期:<?php echo htmlspecialchars($currentAgreement['effective_date']); ?>
</p>
</div>
<div class="agreement-content">
<?php echo nl2br(htmlspecialchars($currentAgreement['content'])); ?>
</div>
<div class="checkbox-container">
<label>
<input type="checkbox" id="acceptCheckbox_<?php echo $currentAgreement['id']; ?>">
我已认真阅读并同意以上协议内容
</label>
</div>
<div class="actions">
<button class="btn btn-accept" onclick="acceptAgreement(<?php echo $currentAgreement['id']; ?>)">
接受协议
</button>
<button class="btn btn-decline" onclick="declineAgreement()">
不同意
</button>
</div>
<div id="errorMsg" class="error-msg"></div>
</div>
<script>
function acceptAgreement(agreementId) {
const checkbox = document.getElementById('acceptCheckbox_' + agreementId);
if (!checkbox.checked) {
alert('请先勾选同意选项');
return;
}
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
fetch('accept_agreement.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRF-Token': csrfToken
},
body: 'agreement_id=' + agreementId
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('感谢您的确认!');
// 重新加载以检查是否有其他待接受的协议
window.location.reload();
} else {
document.getElementById('errorMsg').textContent = data.message;
}
})
.catch(error => {
console.error('Error:', error);
document.getElementById('errorMsg').textContent = '网络错误,请稍后重试';
});
}
function declineAgreement() {
if (confirm('您确定不同意此协议吗?不同意将无法使用我们的服务。')) {
// 处理拒绝逻辑
fetch('decline_agreement.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ action: 'decline' })
})
.then(response => response.json())
.then(data => {
window.location.href = 'logout.php';
});
}
}
</script>
</body>
</html>
后台任务脚本 cron/process_notifications.php
<?php
/**
* 定时处理待发送通知(可配置crontab)
* 建议每5分钟运行一次
*/
require_once '../config/database.php';
require_once '../classes/AgreementNotifier.php';
$db = getDB();
$notifier = new AgreementNotifier($db, $mailer);
// 处理待发送的通知
$processed = $notifier->processPendingNotifications();
// 记录日志
$logFile = '../logs/notifications_' . date('Y-m-d') . '.log';
file_put_contents($logFile,
date('Y-m-d H:i:s') . " - 处理了 {$processed} 条通知\n",
FILE_APPEND
);
echo "处理完成:{$processed} 条通知";
使用示例
// 发布新协议并通知所有用户
$controller = new AgreementController($db, $mailer);
$result = $controller->publishAndNotify(
'服务协议更新',
file_get_contents('agreement_content_2024.html'),
new DateTime('2024-01-01') // 可选
);
// 用户接受协议
$userId = 123;
$agreementId = 456;
$acceptance = $controller->handleAcceptance($userId, $agreementId);
推荐增强功能
- 邮件模板:配置美观的邮件HTML模板
- 多语言支持:根据用户语言设置发送不同语言的协议通知
- 重试机制:对未成功发送的通知进行多次重试
- 用户分组:针对不同用户群体发送不同的协议版本
- Webhook:通知第三方系统协议更新事件
- 审计日志:记录所有协议相关操作的完整日志
- 强制过期:可设置协议接受的超时时间,未接受则限制账户
这个方案提供了完整的协议更新通知流程,包含通知的生成、发送和跟踪,以及用户接受协议的前后端交互,你可以根据实际需求进行调整和扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播