PHP项目账号注销与数据清除实现方案
基础架构设计
1 数据库字段设计
-- 用户主表增加删除标记
ALTER TABLE users ADD COLUMN
is_deleted TINYINT(1) DEFAULT 0 COMMENT '软删除标记';
ALTER TABLE users ADD COLUMN
deleted_at TIMESTAMP NULL COMMENT '删除时间';
ALTER TABLE users ADD COLUMN
deletion_token VARCHAR(64) NULL COMMENT '注销确认令牌';
2 注销流程设计
// 1. 注销请求验证
class AccountDeletion {
private $userId;
private $pdo;
public function __construct($userId, PDO $pdo) {
$this->userId = $userId;
$this->pdo = $pdo;
}
// 验证当前用户密码
public function verifyPassword($password) {
$stmt = $this->pdo->prepare("SELECT password_hash FROM users WHERE id = ?");
$stmt->execute([$this->userId]);
$user = $stmt->fetch();
return password_verify($password, $user['password_hash']);
}
// 生成确认令牌
public function generateDeletionToken() {
$token = bin2hex(random_bytes(32));
$stmt = $this->pdo->prepare("UPDATE users SET deletion_token = ? WHERE id = ?");
$stmt->execute([$token, $this->userId]);
return $token;
}
}
安全注销流程
1 多步确认机制
class AccountDeletionHandler {
private $steps = [
'confirm', // 第一步:确认注销
'verify', // 第二步:验证身份
'process' // 第三步:执行注销
];
public function initiateDeletion($userId) {
// 1. 生成确认令牌
$token = $this->generateToken();
// 2. 发送确认邮件
$this->sendConfirmationEmail($userId, $token);
// 3. 设置会话标记
$_SESSION['deletion_pending'] = true;
$_SESSION['deletion_step'] = 'confirm';
return [
'status' => 'pending',
'message' => '请查收确认邮件,完成注销'
];
}
private function sendConfirmationEmail($userId, $token) {
$user = $this->getUserInfo($userId);
$subject = '确认账号注销';
$body = "点击链接确认注销:https://yourdomain.com/confirm-deletion?token=$token";
mail($user['email'], $subject, $body);
}
}
2 计时器机制
class DeletionTimer {
private $gracePeriod = 7 * 24 * 3600; // 7天宽限期
public function startGracePeriod($userId) {
$expiry = time() + $this->gracePeriod;
$stmt = $this->pdo->prepare("
UPDATE users SET
deletion_requested_at = NOW(),
deletion_expires_at = FROM_UNIXTIME(?),
account_status = 'pending_deletion'
WHERE id = ?
");
$stmt->execute([$expiry, $userId]);
// 设置定时任务检查
$this->scheduleDeletionTask($userId, $expiry);
}
public function cancelDeletion($userId) {
$stmt = $this->pdo->prepare("
UPDATE users SET
deletion_requested_at = NULL,
deletion_expires_at = NULL,
deletion_token = NULL,
account_status = 'active'
WHERE id = ?
");
return $stmt->execute([$userId]);
}
}
数据清除实现
1 软删除实现
class SoftDeletion {
public function softDeleteUser($userId) {
try {
$this->pdo->beginTransaction();
// 1. 标记用户删除
$stmt = $this->pdo->prepare("
UPDATE users SET
is_deleted = 1,
deleted_at = NOW(),
username = CONCAT('deleted_', id),
email = CONCAT('deleted_', id, '@removed.com')
WHERE id = ?
");
$stmt->execute([$userId]);
// 2. 匿名化个人数据
$this->anonymizePersonalData($userId);
// 3. 清除关联会话
$this->clearUserSessions($userId);
$this->pdo->commit();
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
throw $e;
}
}
private function anonymizePersonalData($userId) {
$anonymization = [
'phone' => NULL,
'address' => 'Data removed',
'birthday' => NULL,
'real_name' => 'Deleted User'
];
foreach ($anonymization as $field => $value) {
$stmt = $this->pdo->prepare("
UPDATE user_profiles SET $field = ? WHERE user_id = ?
");
$stmt->execute([$value, $userId]);
}
}
}
2 硬删除与数据清理
class HardDeletion {
private $tablesToClean = [
'user_logins',
'user_sessions',
'password_resets',
'email_verifications'
];
public function permanentDelete($userId) {
try {
$this->pdo->beginTransaction();
// 1. 记录删除日志(审计需求)
$this->logDeletion($userId);
// 2. 清除关联数据
foreach ($this->tablesToClean as $table) {
$stmt = $this->pdo->prepare("DELETE FROM $table WHERE user_id = ?");
$stmt->execute([$userId]);
}
// 3. 删除用户主记录
$stmt = $this->pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$userId]);
$this->pdo->commit();
// 4. 清理缓存
$this->clearUserCache($userId);
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
throw new DeletionException("数据清除失败: " . $e->getMessage());
}
}
private function clearUserCache($userId) {
// 清除Redis缓存
$redis = new Redis();
$redis->del("user:$userId");
$redis->del("user:sessions:$userId");
// 清除文件缓存
$cacheFiles = glob("/tmp/user_cache_$userId*");
foreach ($cacheFiles as $file) {
unlink($file);
}
}
}
完整控制器实现
class AccountController {
public function deleteAccount($userId, $password) {
try {
$deletion = new AccountDeletion($userId, $this->pdo);
// 1. 验证密码
if (!$deletion->verifyPassword($password)) {
return ['error' => '密码错误'];
}
// 2. 启动注销流程
$handler = new AccountDeletionHandler();
$result = $handler->initiateDeletion($userId);
// 3. 发送确认邮件
// ...
return $result;
} catch (Exception $e) {
return ['error' => $e->getMessage()];
}
}
public function confirmDeletion($token) {
// 验证令牌
$stmt = $this->pdo->prepare("
SELECT id FROM users
WHERE deletion_token = ?
AND deletion_expires_at > NOW()
");
$stmt->execute([$token]);
$user = $stmt->fetch();
if (!$user) {
return ['error' => '无效或过期的令牌'];
}
// 执行软删除
$softDeletion = new SoftDeletion();
$softDeletion->softDeleteUser($user['id']);
// 清除会话
session_destroy();
return ['success' => '账号已成功注销'];
}
public function cancelDeletion($userId) {
$timer = new DeletionTimer();
$timer->cancelDeletion($userId);
return ['success' => '注销已取消'];
}
}
后台定时任务
1 Cron作业配置
// delete_expired_accounts.php
require_once 'config.php';
class ExpiredDeletionCron {
public function process() {
$pdo = new PDO(DB_DSN, DB_USER, DB_PASS);
// 查找所有已过期的注销请求
$stmt = $pdo->query("
SELECT id FROM users
WHERE account_status = 'pending_deletion'
AND deletion_expires_at < NOW()
");
while ($user = $stmt->fetch()) {
$hardDeletion = new HardDeletion();
$hardDeletion->permanentDelete($user['id']);
// 记录日志
error_log("账号 {$user['id']} 已永久删除");
}
}
}
2 系统crontab配置
# 每天凌晨2点执行 0 2 * * * /usr/bin/php /path/to/delete_expired_accounts.php # 每周清理日志 0 3 * * 0 /usr/bin/php /path/to/cleanup_logs.php
API接口设计
// routes.php
$router->post('/api/account/delete', function() {
$userId = $_SESSION['user_id'];
$password = $_POST['password'];
$controller = new AccountController();
return $controller->deleteAccount($userId, $password);
});
$router->get('/api/account/confirm-deletion', function() {
$token = $_GET['token'];
$controller = new AccountController();
return $controller->confirmDeletion($token);
});
$router->post('/api/account/cancel-deletion', function() {
$userId = $_SESSION['user_id'];
$controller = new AccountController();
return $controller->cancelDeletion($userId);
});
数据合规考虑
1 GDPR合规检查清单
- [x] 实现可验证的用户同意机制
- [x] 提供注销前数据导出功能
- [x] 设置合理的保留期限
- [x] 记录数据处理活动
- [x] 实现数据传输加密
- [x] 提供数据可携带性
2 数据最小化原则
class DataMinimization {
public function retainOnlyRequired($userId) {
// 保留期限:法律要求至少保留3年
$requiredFields = [
'user_id',
'deleted_at',
'reason_for_deletion'
];
// 清除所有非必要数据
$this->purgeOptionalData($userId);
// 将保留数据移到独立归档表
$this->archiveRequiredData($userId);
}
}
前端交互实现
// account-deletion.js
class AccountDeletionUI {
constructor() {
this.currentStep = 0;
this.deletionToken = null;
}
initiateDeletion() {
if (!confirm('确定要永久删除账号吗?此操作不可撤销!')) {
return;
}
const password = prompt('请输入密码确认:');
fetch('/api/account/delete', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({password})
})
.then(response => response.json())
.then(data => {
if (data.success) {
this.showConfirmationMessage();
} else {
alert(data.error);
}
});
}
cancelDeletion() {
fetch('/api/account/cancel-deletion', {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('账号注销已取消');
location.reload();
}
});
}
}
安全注意事项
- CSRF防护:所有注销操作需要验证CSRF令牌
- 速率限制:限制注销请求频率(如1小时内最多3次)
- 日志记录:记录所有注销相关操作
- 双重确认:至少需要两步认证才能完成注销
- 数据备份:硬删除前必须有备份机制
- 异常处理:处理并记录所有删除过程中的异常
测试用例
class AccountDeletionTest extends PHPUnit\Framework\TestCase {
public function testSuccessfulDeletion() {
// 创建测试用户
// 执行注销流程
// 验证用户状态变更
// 验证数据清理
}
public function testCancelDeletion() {
// 发起注销
// 取消注销
// 验证用户可正常登录
}
public function testPermanentDeletion() {
// 等待宽限期
// 执行定时任务
// 验证数据已彻底删除
}
}
这个实现方案涵盖了账号注销的完整流程,包括安全验证、数据清除、合规要求和异常处理,根据实际项目需求,可以调整具体实现细节。
