Compare commits

..

1 Commits

@ -1,3 +0,0 @@
{
"remote.autoForwardPortsFallback": 0
}

@ -7,7 +7,6 @@
- Docker & Docker Compose - Docker & Docker Compose
- PHP 7.4+ (需要扩展: php-mysql, php-sockets) - PHP 7.4+ (需要扩展: php-mysql, php-sockets)
- WSL2 (Windows环境) - WSL2 (Windows环境)
- netstat
## 快速开始 ## 快速开始
@ -59,7 +58,21 @@ docker-compose exec mysql mysql -umail_user -puser123 mail_server < scripts/crea
- **8080** - Web管理后台 - **8080** - Web管理后台
- **8088** - phpMyAdmin管理界面 - **8088** - phpMyAdmin管理界面
## 启动服务器
### SMTP服务器发送邮件
```bash
sudo php scripts/start_smtp.php
```
### POP3服务器接收邮件
```bash
sudo php scripts/start_pop3.php
```
**注意**两个服务器需要分别在两个终端运行都需要sudo权限因为使用25和110端口
## Web管理后台 ## Web管理后台
@ -69,7 +82,6 @@ docker-compose exec mysql mysql -umail_user -puser123 mail_server < scripts/crea
```bash ```bash
cd /mnt/d/mailserver/mailserver/public cd /mnt/d/mailserver/mailserver/public
php -S localhost:8080 php -S localhost:8080
#如果打不开可以尝试换端口为8888或者别的
``` ```
**方式2从项目根目录启动** **方式2从项目根目录启动**
@ -85,9 +97,74 @@ php -S localhost:8080 -t public
- 管理员:`admin@test.com` / `123456` - 管理员:`admin@test.com` / `123456`
- 普通用户:`user1@test.com` / `123456` - 普通用户:`user1@test.com` / `123456`
### 功能模块
## 测试服务器方法 #### 1. 用户注册
- **页面**`register.php`
- **功能**:新用户注册,邮箱域名限制为 @test.com
#### 2. 用户管理(管理员)
- **页面**`users.php`
- **功能**
- 创建新用户(设置密码、管理员权限、激活状态)
- 编辑用户信息(修改密码、权限、状态)
- 删除用户账号
- 查看用户列表
#### 3. 邮件管理
- **页面**`emails.php`
- **功能**
- 查看邮件(管理员查看全部,普通用户查看自己的收件箱)
- 查看邮件详情
- 标记邮件为已读
- 删除邮件
- 分页浏览
#### 4. 群发邮件(管理员)
- **页面**`broadcast.php`
- **功能**
- 发送给所有用户
- 发送给指定用户列表
- 自定义邮件主题和内容
#### 5. 过滤规则
- **页面**`filters.php`
- **功能**
- 创建邮箱过滤规则(阻止/允许特定邮箱)
- 创建IP地址过滤规则阻止/允许特定IP
- 启用/禁用过滤规则
- 删除过滤规则
#### 6. 系统设置(管理员)
- **页面**`settings.php`
- **功能**
- 设置SMTP端口默认25
- 设置POP3端口默认110
- 设置服务器域名默认test.com
- 设置用户邮箱大小限制
- 设置日志存储路径和最大大小
- 修改管理员密码
#### 7. 服务管理(管理员)
- **页面**`services.php`
- **功能**
- 查看SMTP服务状态
- 查看POP3服务状态
- 启动/停止服务(状态管理)
#### 8. 日志管理
- **页面**`logs.php`
- **功能**
- 查看所有日志
- 按类型过滤SMTP/POP3
- 查看日志统计信息
- 清除日志(管理员)
#### 9. 帮助
- **页面**`help.php`
- **功能**:提供系统使用帮助文档
## 测试方法
### 测试SMTP发送邮件 ### 测试SMTP发送邮件
@ -138,6 +215,39 @@ RETR 1
QUIT QUIT
``` ```
### 测试Web管理后台
1. **用户注册测试**
- 访问http://localhost:8080/register.php
- 注册新用户:`newuser@test.com` / `123456`
- 预期:注册成功,跳转到登录页
2. **用户管理测试(管理员)**
- 登录管理员账号
- 访问http://localhost:8080/users.php
- 创建、编辑、删除用户
3. **群发邮件测试(管理员)**
- 访问http://localhost:8080/broadcast.php
- 选择"发送给所有用户"或"发送给指定用户"
- 填写主题和内容,发送
- 预期:显示成功发送数量
4. **系统设置测试(管理员)**
- 访问http://localhost:8080/settings.php
- 修改端口、域名、邮箱大小等设置
- 修改管理员密码
5. **过滤规则测试**
- 访问http://localhost:8080/filters.php
- 创建邮箱过滤规则和IP过滤规则
- 测试启用/禁用功能
6. **日志管理测试**
- 访问http://localhost:8080/logs.php
- 查看日志列表,按类型过滤
- 测试清除日志功能(管理员)
## 查看数据 ## 查看数据
### 方法1phpMyAdmin推荐 ### 方法1phpMyAdmin推荐
@ -146,10 +256,10 @@ QUIT
### 方法2命令行 ### 方法2命令行
```bash ```bash
# 查看用户 # 查看用户
docker-compose exec mysql mysql -umail_user -puser123 mailserver -e "SELECT * FROM users;" docker-compose exec mysql mysql -umail_user -puser123 mail_server -e "SELECT * FROM users;"
# 查看邮件 # 查看邮件
docker-compose exec mysql mysql -umail_user -puser123 mailserver -e "SELECT id, sender, recipient, subject, created_at FROM emails ORDER BY id DESC;" docker-compose exec mysql mysql -umail_user -puser123 mail_server -e "SELECT id, sender, recipient, subject, created_at FROM emails ORDER BY id DESC;"
``` ```
## 重置数据库 ## 重置数据库
@ -163,33 +273,7 @@ docker-compose exec mysql mysql -umail_user -puser123 mail_server < scripts/crea
``` ```
## 常见问题 ## 常见问题
### web页面启停服务器显示端口未成功监听
- 确保web用户有进入目录执行开始脚本的能力可用下面命令测试观察输出。
```bash
#模拟www-data用户执行start_smtp.php脚本
sudo -u www-data php /home/clumxc/projects/mailserver/scripts/start_smtp.php
```
- 需要sudo权限的低端口25、110无法通过Web页面开启需要先改为25252、1100等其它端口
或给 /usr/bin/php 这个可执行文件贴一张“特许证”,以后不管谁运行 php都能绑低端口不需要 root。
一步一步做:
```bash
#给 PHP 贴特许证,打开终端,执行:
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/php
```
```bash
#确认贴上了
getcap /usr/bin/php
#看到输出
/usr/bin/php = cap_net_bind_service+ep
```
```bash
#重启你的 Web 服务(让新能力生效)
#如果你用 Apache
sudo systemctl restart apache2
#如果你用 Nginx + PHP-FPM
sudo systemctl restart php-fpm
```
### 端口被占用 ### 端口被占用
**SMTP服务器启动失败25端口** **SMTP服务器启动失败25端口**
@ -320,3 +404,57 @@ mailserver/
7. **✅ 帮助** 7. **✅ 帮助**
- 系统使用帮助文档 - 系统使用帮助文档
### ❌ 移动客户端功能(未完成)
根据课程设计说明书要求Android移动客户端尚未实现
1. **❌ 邮件的操作**
- 邮件的发送
- 邮件的接收
- 邮件的删除
2. **❌ 用户管理**
- 用户修改自己邮箱的账户密码
- 新用户注册功能
3. **❌ 管理员管理**
- 管理员远程登录
- 客户端用户管理(创建、删除、授权、消权、禁用)
## 简要操作指南
### 初始化项目
```bash
# 1. 启动数据库
cd /mnt/d/mailserver/mailserver
docker-compose up -d
sleep 15
# 2. 初始化管理功能数据库表
docker-compose exec mysql mysql -umail_user -puser123 mail_server < scripts/create_admin_tables.sql
# 3. 启动Web服务器
php -S localhost:8080 -t public
```
### 日常使用
```bash
# 启动SMTP服务器终端1
sudo php scripts/start_smtp.php
# 启动POP3服务器终端2
sudo php scripts/start_pop3.php
# 启动Web管理后台终端3
php -S localhost:8080 -t public
```
### 访问地址
- **Web管理后台**http://localhost:8080
- **phpMyAdmin**http://localhost:8088
- **SMTP服务器**localhost:25
- **POP3服务器**localhost:110

@ -1,20 +0,0 @@
启动最简POP3邮件服务器
==============================
POP3服务器启动在 0.0.0.0:1100
按 Ctrl+C 停止
数据库连接成功
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready
服务器: +OK POP3 Simple Server Ready

@ -1,7 +0,0 @@
启动最简SMTP邮件服务器
==============================
SMTP服务器启动在 0.0.0.0:25
按 Ctrl+C 停止
数据库连接成功

@ -116,12 +116,11 @@ $allUsers = $userRepo->getAll();
<div class="menu"> <div class="menu">
<a href="index.php">仪表盘</a> <a href="index.php">仪表盘</a>
<a href="users.php">用户管理</a> <a href="users.php">用户管理</a>
<a href="emails.php">邮件管理</a>
<a href="broadcast.php">群发邮件</a> <a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a> <a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a> <a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a> <a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div> </div>
<div class="container"> <div class="container">

@ -101,17 +101,19 @@ $totalPages = ceil($totalEmails / $perPage);
<div class="menu"> <div class="menu">
<a href="index.php">仪表盘</a> <a href="index.php">仪表盘</a>
<a href="users.php">用户管理</a> <?php if ($isAdmin): ?>
<a href="broadcast.php">群发邮件</a> <a href="users.php">用户管理</a>
<?php endif; ?>
<a href="emails.php">邮件管理</a>
<a href="filters.php">过滤规则</a> <a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a> <a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a> <?php if ($isAdmin): ?>
<a href="settings.php">系统设置</a> <a href="settings.php">系统设置</a>
<a href="help.php">帮助</a> <?php endif; ?>
</div> </div>
<div class="container"> <div class="container">
<h2>管理 <?php if ($isAdmin): ?>(全部邮件)<?php else: ?>(我的收件箱)<?php endif; ?></h2> <h2>管理 <?php if ($isAdmin): ?>(全部邮件)<?php else: ?>(我的收件箱)<?php endif; ?></h2>
<?php if ($message): ?> <?php if ($message): ?>
<div class="message"><?php echo $message; ?></div> <div class="message"><?php echo $message; ?></div>

@ -5,10 +5,6 @@ require_once __DIR__ . '/../src/storage/FilterRepository.php';
require_once __DIR__ . '/../src/utils/Validator.php'; require_once __DIR__ . '/../src/utils/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php'; require_once __DIR__ . '/../src/utils/Security.php';
//开启所有错误日志报告
error_reporting(E_ALL);
ini_set('display_errors', 1);
session_start(); session_start();
// 身份验证 // 身份验证
@ -21,18 +17,6 @@ $filterRepo = new FilterRepository();
$message = ''; $message = '';
$error = ''; $error = '';
if (isset($_POST['toggle_id'])) {
$id = (int)$_POST['toggle_id'];
$row = $filterRepo->getById($id);
if ($row) {
$filterRepo->updateStatus($id, !(bool)$row['is_active']);
}
header('Location: filters.php'); // 302 跳回干净地址
exit;
}
// 处理创建过滤规则 // 处理创建过滤规则
if (isset($_POST['create_filter'])) { if (isset($_POST['create_filter'])) {
$ruleType = $_POST['rule_type'] ?? ''; $ruleType = $_POST['rule_type'] ?? '';
@ -79,26 +63,29 @@ if (isset($_GET['delete'])) {
} }
} }
/*------------------------------------
// 处理切换规则状态 // 处理切换规则状态
if (isset($_GET['toggle'])) { if (isset($_GET['toggle'])) {
$id = (int)$_GET['toggle']; $id = (int)$_GET['toggle'];
$row = $filterRepo->getById($id); // 改用 public 方法 $rule = $filterRepo->getAll();
if ($row) { $currentRule = null;
$newStatus = !(bool)$row['is_active']; foreach ($rule as $r) {
if ($r['id'] == $id) {
$currentRule = $r;
break;
}
}
if ($currentRule) {
$newStatus = !$currentRule['is_active'];
if ($filterRepo->updateStatus($id, $newStatus)) { if ($filterRepo->updateStatus($id, $newStatus)) {
$message = "规则状态已更新"; $message = "规则状态已更新";
} else { } else {
$error = "更新失败"; $error = "更新失败";
} }
} else {
$error = "规则不存在";
} }
} }
------------------------------------ */
// 获取所有规则 // 获取所有规则
$rules = $filterRepo->getAll(); $rules = $filterRepo->getAll();
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@ -145,13 +132,15 @@ $rules = $filterRepo->getAll();
<div class="menu"> <div class="menu">
<a href="index.php">仪表盘</a> <a href="index.php">仪表盘</a>
<a href="users.php">用户管理</a> <?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="broadcast.php">群发邮件</a> <a href="users.php">用户管理</a>
<?php endif; ?>
<a href="emails.php">邮件管理</a>
<a href="filters.php">过滤规则</a> <a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a> <a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a> <?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="settings.php">系统设置</a> <a href="settings.php">系统设置</a>
<a href="help.php">帮助</a> <?php endif; ?>
</div> </div>
<div class="container"> <div class="container">
@ -238,12 +227,9 @@ $rules = $filterRepo->getAll();
</td> </td>
<td><?php echo $rule['created_at']; ?></td> <td><?php echo $rule['created_at']; ?></td>
<td> <td>
<form method="post" style="display:inline;"> <a href="?toggle=<?php echo $rule['id']; ?>" class="btn btn-warning">
<input type="hidden" name="toggle_id" value="<?php echo $rule['id']; ?>"> <?php echo $rule['is_active'] ? '禁用' : '启用'; ?>
<button type="submit" class="btn btn-warning"> </a>
<?php echo $rule['is_active'] ? '禁用' : '启用'; ?>
</button>
</form>
<a href="?delete=<?php echo $rule['id']; ?>" class="btn btn-danger" onclick="return confirm('确定要删除此规则吗?');">删除</a> <a href="?delete=<?php echo $rule['id']; ?>" class="btn btn-danger" onclick="return confirm('确定要删除此规则吗?');">删除</a>
</td> </td>
</tr> </tr>

@ -39,12 +39,15 @@ if (!isset($_SESSION['user_id'])) {
<div class="menu"> <div class="menu">
<a href="index.php">仪表盘</a> <a href="index.php">仪表盘</a>
<a href="users.php">用户管理</a> <?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="broadcast.php">群发邮件</a> <a href="users.php">用户管理</a>
<?php endif; ?>
<a href="emails.php">邮件管理</a>
<a href="filters.php">过滤规则</a> <a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a> <a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a> <?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="settings.php">系统设置</a> <a href="settings.php">系统设置</a>
<?php endif; ?>
<a href="help.php">帮助</a> <a href="help.php">帮助</a>
</div> </div>
@ -59,7 +62,7 @@ if (!isset($_SESSION['user_id'])) {
<div class="section"> <div class="section">
<h3>功能模块</h3> <h3>功能模块</h3>
<h4>1. 用户管理</h4> <h4>1. 用户管理(管理员功能)</h4>
<ul> <ul>
<li><strong>创建用户:</strong>可以创建新的用户账号,设置密码、管理员权限和激活状态</li> <li><strong>创建用户:</strong>可以创建新的用户账号,设置密码、管理员权限和激活状态</li>
<li><strong>编辑用户:</strong>可以修改用户密码、权限和状态</li> <li><strong>编辑用户:</strong>可以修改用户密码、权限和状态</li>
@ -67,22 +70,29 @@ if (!isset($_SESSION['user_id'])) {
<li><strong>用户列表:</strong>查看所有注册用户及其状态</li> <li><strong>用户列表:</strong>查看所有注册用户及其状态</li>
</ul> </ul>
<h4>2. 邮件管理</h4>
<ul>
<li><strong>查看邮件:</strong>管理员可以查看所有邮件,普通用户只能查看自己的收件箱</li>
<li><strong>标记已读:</strong>将未读邮件标记为已读</li>
<li><strong>删除邮件:</strong>删除不需要的邮件(软删除)</li>
<li><strong>邮件详情:</strong>点击邮件主题查看完整内容</li>
</ul>
<h4>2. 群发邮件</h4> <h4>3. 群发邮件(管理员功能)</h4>
<ul> <ul>
<li><strong>发送给所有用户:</strong>可以一次性向所有激活用户发送通知邮件</li> <li><strong>发送给所有用户:</strong>可以一次性向所有激活用户发送通知邮件</li>
<li><strong>发送给指定用户:</strong>可以选择特定用户进行群发</li> <li><strong>发送给指定用户:</strong>可以选择特定用户进行群发</li>
<li><strong>邮件内容:</strong>支持自定义主题和内容</li> <li><strong>邮件内容:</strong>支持自定义主题和内容</li>
</ul> </ul>
<h4>3. 过滤规则</h4> <h4>4. 过滤规则</h4>
<ul> <ul>
<li><strong>邮箱过滤:</strong>可以阻止或允许特定邮箱地址</li> <li><strong>邮箱过滤:</strong>可以阻止或允许特定邮箱地址</li>
<li><strong>IP过滤</strong>可以阻止或允许特定IP地址</li> <li><strong>IP过滤</strong>可以阻止或允许特定IP地址</li>
<li><strong>规则管理:</strong>可以启用、禁用或删除过滤规则</li> <li><strong>规则管理:</strong>可以启用、禁用或删除过滤规则</li>
</ul> </ul>
<h4>4. 系统设置</h4> <h4>5. 系统设置(管理员功能)</h4>
<ul> <ul>
<li><strong>端口设置:</strong>配置SMTP端口默认25和POP3端口默认110</li> <li><strong>端口设置:</strong>配置SMTP端口默认25和POP3端口默认110</li>
<li><strong>域名设置:</strong>设置邮件服务器域名默认test.com</li> <li><strong>域名设置:</strong>设置邮件服务器域名默认test.com</li>
@ -91,14 +101,14 @@ if (!isset($_SESSION['user_id'])) {
<li><strong>密码修改:</strong>管理员可以修改自己的密码</li> <li><strong>密码修改:</strong>管理员可以修改自己的密码</li>
</ul> </ul>
<h4>5. 服务管理</h4> <h4>6. 服务管理(管理员功能)</h4>
<ul> <ul>
<li><strong>SMTP服务</strong>查看和管理SMTP服务状态</li> <li><strong>SMTP服务</strong>查看和管理SMTP服务状态</li>
<li><strong>POP3服务</strong>查看和管理POP3服务状态</li> <li><strong>POP3服务</strong>查看和管理POP3服务状态</li>
<li><strong>服务起停:</strong>启动或停止邮件服务</li> <li><strong>服务起停:</strong>启动或停止邮件服务</li>
</ul> </ul>
<h4>6. 日志管理</h4> <h4>7. 日志管理</h4>
<ul> <ul>
<li><strong>查看日志:</strong>查看SMTP和POP3服务器日志</li> <li><strong>查看日志:</strong>查看SMTP和POP3服务器日志</li>
<li><strong>日志过滤:</strong>按类型过滤日志(全部/SMTP/POP3</li> <li><strong>日志过滤:</strong>按类型过滤日志(全部/SMTP/POP3</li>

@ -6,13 +6,11 @@ require_once __DIR__ . '/../src/utils/Security.php';
session_start(); session_start();
// 简单身份验证,检查用户是否已登录。如果未登录($_SESSION['user_id']不存在),则重定向到登录页面。 // 简单身份验证
function requireAuth() { function requireAuth() {
if (!isset($_SESSION['user_id'])) { if (!isset($_SESSION['user_id'])) {
if (basename($_SERVER['PHP_SELF']) !== 'index.php') { header('Location: index.php');
header('Location: index.php'); exit;
exit;
}
} }
} }
@ -29,7 +27,7 @@ if (isset($_POST['login'])) {
$userRepo = new UserRepository(); $userRepo = new UserRepository();
$user = $userRepo->verifyPassword($username, $password); $user = $userRepo->verifyPassword($username, $password);
if ($user && $user['is_active'] && $user['is_admin']) { if ($user && $user['is_active']) {
// 登录成功,清除尝试记录 // 登录成功,清除尝试记录
Security::clearLoginAttempts($username); Security::clearLoginAttempts($username);
@ -38,11 +36,7 @@ if (isset($_POST['login'])) {
$_SESSION['is_admin'] = $user['is_admin']; $_SESSION['is_admin'] = $user['is_admin'];
header('Location: index.php'); header('Location: index.php');
exit; exit;
}else if($user && !$user['is_active']){ } else {
$error = "用户被禁用";
}else if($user && $user['is_active'] && !$user['is_admin']){
$error = "没有权限";
}else{
// 登录失败,记录尝试 // 登录失败,记录尝试
Security::recordLoginAttempt($username); Security::recordLoginAttempt($username);
$error = "用户名或密码错误"; $error = "用户名或密码错误";
@ -59,8 +53,6 @@ if (basename($_SERVER['PHP_SELF']) === 'index.php' && !isset($_SESSION['user_id'
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>邮件服务器管理后台 - 登录</title> <title>邮件服务器管理后台 - 登录</title>
<style> <style>
body { font-family: Arial, sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; } body { font-family: Arial, sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; }
@ -85,6 +77,9 @@ if (basename($_SERVER['PHP_SELF']) === 'index.php' && !isset($_SESSION['user_id'
</div> </div>
<button type="submit" name="login">登录</button> <button type="submit" name="login">登录</button>
</form> </form>
<p style="margin-top: 20px; font-size: 12px; color: #666; text-align: center;">
还没有账号?<a href="register.php" style="color: #007bff; text-decoration: none;">立即注册</a>
</p>
<p style="margin-top: 10px; font-size: 12px; color: #666;"> <p style="margin-top: 10px; font-size: 12px; color: #666;">
测试账号: admin@test.com / 123456<br> 测试账号: admin@test.com / 123456<br>
普通账号: user1@test.com / 123456 普通账号: user1@test.com / 123456
@ -124,17 +119,19 @@ requireAuth();
<div class="menu"> <div class="menu">
<a href="index.php">仪表盘</a> <a href="index.php">仪表盘</a>
<?php?> <?php if ($_SESSION['is_admin']) { ?>
<a href="users.php">用户管理</a> <a href="users.php">用户管理</a>
<?php ?> <?php } ?>
<a href="broadcast.php">群发邮件</a> <a href="emails.php">邮件管理</a>
<?php ?> <?php if ($_SESSION['is_admin']) { ?>
<a href="broadcast.php">群发邮件</a>
<?php } ?>
<a href="filters.php">过滤规则</a> <a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a> <a href="logs.php">系统日志</a>
<?php ?> <?php if ($_SESSION['is_admin']) { ?>
<a href="services.php">服务管理</a> <a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a> <a href="settings.php">系统设置</a>
<?php ?> <?php } ?>
<a href="help.php">帮助</a> <a href="help.php">帮助</a>
</div> </div>

@ -1,428 +0,0 @@
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/UserRepository.php';
require_once __DIR__ . '/../src/utils/Security.php';
session_start();
// 简单身份验证,检查用户是否已登录。如果未登录($_SESSION['user_id']不存在),则重定向到登录页面。
function requireAuth() {
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
}
// 登录检查
if (isset($_POST['login'])) {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
try {
// 检查登录尝试次数(防止暴力破解)
if (!Security::checkLoginAttempts($username)) {
$error = "登录失败次数过多请5分钟后再试";
} else {
$userRepo = new UserRepository();
$user = $userRepo->verifyPassword($username, $password);
if ($user && $user['is_active'] && $user['is_admin']) {
// 登录成功,清除尝试记录
Security::clearLoginAttempts($username);
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['is_admin'] = $user['is_admin'];
header('Location: index.php');
exit;
} else {
// 登录失败,记录尝试
Security::recordLoginAttempt($username);
$error = "用户名或密码错误";
}
}
} catch (Exception $e) {
$error = "登录失败: " . $e->getMessage();
}
}
// 如果是登录页面
if (basename($_SERVER['PHP_SELF']) === 'index.php' && !isset($_SESSION['user_id'])) {
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>邮件服务器管理后台 - 登录</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-container {
width: 100%;
max-width: 420px;
animation: fadeIn 0.5s ease;
}
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-header h1 {
color: #007bff;
font-size: 28px;
font-weight: 600;
margin-bottom: 8px;
}
.login-header p {
color: #6c757d;
font-size: 14px;
}
.login-card {
background: white;
border-radius: 12px;
box-shadow: 0 8px 30px rgba(0, 123, 255, 0.15);
padding: 40px;
border: 1px solid rgba(0, 123, 255, 0.1);
}
.error-message {
background-color: #f8d7da;
color: #721c24;
padding: 12px;
border-radius: 6px;
margin-bottom: 20px;
border-left: 4px solid #dc3545;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.error-message:before {
content: "⚠";
font-size: 16px;
}
.form-group {
margin-bottom: 24px;
}
.form-group label {
display: block;
color: #495057;
font-weight: 500;
margin-bottom: 8px;
font-size: 14px;
}
.form-control {
width: 100%;
padding: 12px 16px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 15px;
transition: all 0.3s ease;
background-color: #f8f9fa;
}
.form-control:focus {
outline: none;
border-color: #007bff;
background-color: white;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
}
.form-control:hover {
border-color: #ced4da;
}
.login-btn {
width: 100%;
padding: 14px;
background: linear-gradient(to right, #007bff, #0056b3);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 10px;
}
.login-btn:hover {
background: linear-gradient(to right, #0069d9, #004085);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.2);
}
.login-btn:active {
transform: translateY(0);
}
.test-accounts {
margin-top: 25px;
padding-top: 20px;
border-top: 1px solid #e9ecef;
text-align: center;
}
.test-accounts p {
color: #6c757d;
font-size: 13px;
line-height: 1.5;
margin-bottom: 5px;
}
.test-accounts strong {
color: #495057;
font-weight: 600;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 480px) {
.login-card {
padding: 30px 25px;
}
.login-header h1 {
font-size: 24px;
}
body {
padding: 15px;
}
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-header">
<h1>邮件服务器管理</h1>
<p>安全登录到管理后台</p>
</div>
<div class="login-card">
<?php if (isset($error)): ?>
<div class="error-message">
<?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<form method="POST" action="" id="loginForm">
<div class="form-group">
<label for="username">用户名</label>
<input type="text"
id="username"
name="username"
class="form-control"
value="admin@test.com"
required
placeholder="请输入用户名或邮箱">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password"
id="password"
name="password"
class="form-control"
value="123456"
required
placeholder="请输入密码">
</div>
<button type="submit" name="login" class="login-btn">
登录系统
</button>
</form>
<div class="test-accounts">
<p><strong>测试账号</strong></p>
<p>管理员: admin@test.com / 123456</p>
<p>普通用户: user1@test.com / 123456</p>
</div>
</div>
</div>
<script>
// 简单的表单验证增强
document.getElementById('loginForm').addEventListener('submit', function(e) {
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value;
if (!username) {
e.preventDefault();
alert('请输入用户名');
return false;
}
if (!password) {
e.preventDefault();
alert('请输入密码');
return false;
}
// 登录按钮状态变化
const submitBtn = e.target.querySelector('button[type="submit"]');
submitBtn.disabled = true;
submitBtn.innerHTML = '登录中...';
submitBtn.style.opacity = '0.8';
});
</script>
</body>
</html>
<?php
exit;
}
requireAuth();
?>
<!DOCTYPE html>
<html>
<head>
<title>邮件服务器管理后台</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: #f8f9fa; padding: 10px; margin-bottom: 20px; }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 20px; }
.stat-box { border: 1px solid #ddd; padding: 15px; text-align: center; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #f8f9fa; }
</style>
</head>
<body>
<div class="header">
<h1>邮件服务器管理后台</h1>
<div>欢迎, <?php echo htmlspecialchars($_SESSION['username']); ?>
(<a href="logout.php" style="color: white;">退出</a>)
</div>
</div>
<div class="menu">
<a href="index.php">仪表盘</a>
<?php if ($_SESSION['is_admin']) { ?>
<a href="users.php">用户管理</a>
<?php } ?>
<a href="emails.php">邮件管理</a>
<?php if ($_SESSION['is_admin']) { ?>
<a href="broadcast.php">群发邮件</a>
<?php } ?>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<?php if ($_SESSION['is_admin']) { ?>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a>
<?php } ?>
<a href="help.php">帮助</a>
</div>
<div class="stats">
<?php
$db = Database::getInstance();
// 统计用户数
$stmt = $db->query("SELECT COUNT(*) as count FROM users");
$userCount = $stmt->fetch()['count'];
// 统计邮件数
$stmt = $db->query("SELECT COUNT(*) as count FROM emails WHERE is_deleted = 0");
$emailCount = $stmt->fetch()['count'];
// 统计今日日志
$stmt = $db->query("SELECT COUNT(*) as count FROM server_logs WHERE DATE(created_at) = CURDATE()");
$logCount = $stmt->fetch()['count'];
// 统计活跃会话(简化版)
$activeConnections = 0;
?>
<div class="stat-box">
<h3><?php echo $userCount; ?></h3>
<p>注册用户</p>
</div>
<div class="stat-box">
<h3><?php echo $emailCount; ?></h3>
<p>总邮件数</p>
</div>
<div class="stat-box">
<h3><?php echo $logCount; ?></h3>
<p>今日日志</p>
</div>
<div class="stat-box">
<h3><?php echo $activeConnections; ?></h3>
<p>活跃连接</p>
</div>
</div>
<h2>最近邮件</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>发件人</th>
<th>收件人</th>
<th>主题</th>
<th>时间</th>
</tr>
</thead>
<tbody>
<?php
$stmt = $db->query("
SELECT e.*,
COALESCE(u1.username, e.sender) as sender_name,
COALESCE(u2.username, e.recipient) as recipient_name
FROM emails e
LEFT JOIN users u1 ON e.sender_id = u1.id
LEFT JOIN users u2 ON e.recipient_id = u2.id
WHERE e.is_deleted = 0
ORDER BY e.created_at DESC
LIMIT 10
");
while ($email = $stmt->fetch()) {
echo "<tr>";
echo "<td>{$email['id']}</td>";
echo "<td>" . htmlspecialchars($email['sender_name'] ?? '未知') . "</td>";
echo "<td>" . htmlspecialchars($email['recipient_name'] ?? '未知') . "</td>";
echo "<td>" . htmlspecialchars($email['subject'] ?? '(无主题)') . "</td>";
echo "<td>{$email['created_at']}</td>";
echo "</tr>";
}
?>
</tbody>
</table>
</body>
</html>

@ -2,9 +2,6 @@
require_once __DIR__ . '/../config/database.php'; require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php'; require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php'; require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php';
//
error_reporting(E_ALL);
ini_set('display_errors', 1);
session_start(); session_start();
@ -145,13 +142,15 @@ $logMaxSize = $settingsRepo->get('log_max_size', 10485760);
<div class="menu"> <div class="menu">
<a href="index.php">仪表盘</a> <a href="index.php">仪表盘</a>
<a href="users.php">用户管理</a> <?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="broadcast.php">群发邮件</a> <a href="users.php">用户管理</a>
<?php endif; ?>
<a href="emails.php">邮件管理</a>
<a href="filters.php">过滤规则</a> <a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a> <a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a> <?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="settings.php">系统设置</a> <a href="settings.php">系统设置</a>
<a href="help.php">帮助</a> <?php endif; ?>
</div> </div>
<div class="container"> <div class="container">

@ -4,223 +4,57 @@ require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/ServiceRepository.php'; require_once __DIR__ . '/../src/storage/ServiceRepository.php';
require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php'; require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php';
//错误提示
error_reporting(E_ALL);
ini_set('display_errors', 1);
session_start(); session_start();
// 登录验证 // 身份验证
if (!isset($_SESSION['user_id'])) { if (!isset($_SESSION['user_id'])) {
header('Location: index.php'); header('Location: index.php');
exit; exit;
} }
// 检查管理员权限
if (!$_SESSION['is_admin']) {
die('权限不足:只有管理员可以访问此页面');
}
$serviceRepo = new ServiceRepository(); $serviceRepo = new ServiceRepository();
$settingsRepo = new SystemSettingsRepository(); $settingsRepo = new SystemSettingsRepository();
$message = ''; $message = '';
$error = ''; $error = '';
// 获取端口设置(放在函数定义之前)
$smtpPort = $settingsRepo->get('smtp_port', 25);
$pop3Port = $settingsRepo->get('pop3_port', 110);
function startService($serviceName, $port) {
$scriptPath = __DIR__ . "/../scripts/start_{$serviceName}.php";
$logFile = __DIR__ . "/../logs/{$serviceName}.log";
error_log("尝试启动服务: $serviceName , 端口: $port");
if (!file_exists($scriptPath)) {
return ['success' => false, 'message' => '启动脚本不存在'];
}
if (isServiceRunning($serviceName, $port)) {
return ['success' => false, 'message' => '服务已在运行'];
}
@file_put_contents($logFile, '');
//===== 修复:更稳健的命令执行 =====
$raw = shell_exec(sprintf(
'cd %s && (%s > %s 2>/dev/null </dev/null & echo $!)',
escapeshellarg(dirname($scriptPath, 2)),
'nohup php ' . escapeshellarg($scriptPath),
escapeshellarg($logFile)
));
/*$raw = shell_exec(sprintf(
'cd %s && (%s > /dev/null 2>&1 </dev/null & echo $!)',
escapeshellarg(dirname($scriptPath, 2)),
'nohup php ' . escapeshellarg($scriptPath),
escapeshellarg($logFile) // 这一行现在仅用于占位,实际不再写
));*/
// 修复trim(null)问题
$raw = (string)$raw; // 强制转换为字符串
$pid = trim($raw); // 现在可以安全trim
error_log("Shell命令执行结果: " . var_export($raw, true));
error_log("获取的PID: " . $pid); // 修复:这里$pid已定义
if (!is_numeric($pid) || $pid < 1) {
error_log("PID无效: $pid");
return ['success' => false, 'message' => '未能获取有效进程号'];
}
/* ===== 修复:添加进程状态检查 ===== */
sleep(1); // 先等待1秒
// 检查进程是否还在运行
$processExists = file_exists("/proc/$pid");
error_log("进程是否存在: " . ($processExists ? "是" : "否"));
if (!$processExists) {
// 查看脚本为什么退出了
if (file_exists($logFile)) {
$logContent = file_get_contents($logFile);
error_log("脚本输出日志:\n" . $logContent);
}
return ['success' => false, 'message' => '进程已退出'];
}
/* ===== 修复:更长的等待时间 ===== */
$maxChecks = 6;
for ($i = 0; $i < $maxChecks; $i++) {
sleep(1); // 每次检查等待1秒
if (isServiceRunning($serviceName, $port)) {
// 再次确认PID仍然有效
if (file_exists("/proc/$pid")) {
return ['success' => true, 'pid' => $pid];
} else {
// 进程已退出
return ['success' => false, 'message' => '进程已退出'];
}
}
error_log("第 " . ($i+1) . " 次检查:端口未监听");
}
// 启动失败 → 清理孤儿
if (file_exists("/proc/$pid")) {
@exec("kill -9 {$pid} 2>/dev/null");
}
// 输出失败原因
if (file_exists($logFile)) {
// 只读取最后10行避免读取大文件
$logContent = shell_exec("tail -10 " . escapeshellarg($logFile));
error_log("最终日志内容(最后100行):\n" . $logContent);
}
return ['success' => false, 'message' => '服务端口未监听,启动失败'];
}
function stopService($serviceName, $pid, $port) {
// ===== 改:优先用 DB 里的 PID没有再现场嗅探 =====
if (empty($pid) || !is_numeric($pid)) {
$pid = getServicePid($serviceName, $port);
}
if ($pid) {
@exec("kill {$pid} 2>/dev/null");
sleep(1);
if (isServiceRunning($serviceName, $port)) {
@exec("kill -9 {$pid} 2>/dev/null");
sleep(1);
}
}
return !isServiceRunning($serviceName, $port);
}
function isServiceRunning($serviceName, $port) {
// 方法1: 使用单引号确保变量正确展开
$cmd1 = "netstat -tlnp 2>/dev/null | grep ':" . $port . "' | grep LISTEN";
$result1 = shell_exec($cmd1);
// 方法2: 使用ss命令更可靠
$cmd2 = "ss -tlnp 2>/dev/null | grep ':" . $port . "'";
$result2 = shell_exec($cmd2);
// 方法3: 使用lsof你已验证有效
$cmd3 = "lsof -i :" . $port . " 2>/dev/null";
$result3 = shell_exec($cmd3);
// 方法4: 直接socket连接测试最可靠
$fp = @fsockopen('127.0.0.1', $port, $errno, $errstr, 1);
if ($fp) {
fclose($fp);
return true;
}
// 任意一个方法有结果都算服务在运行
return !empty(trim($result1 ?? '')) ||
!empty(trim($result2 ?? '')) ||
!empty(trim($result3 ?? ''));
}
function getServicePid($serviceName, $port) {
return trim(shell_exec("lsof -ti: {$port} 2>/dev/null | head -1"));
}
// ===================== 修改部分结束 =====================
// 处理服务起停 // 处理服务起停
if (isset($_GET['action'])) { if (isset($_GET['action'])) {
$serviceName = $_GET['service'] ?? ''; $serviceName = $_GET['service'] ?? '';
$action = $_GET['action'] ?? ''; $action = $_GET['action'] ?? '';
if ($serviceName === 'smtp' || $serviceName === 'pop3') { if ($serviceName === 'smtp' || $serviceName === 'pop3') {
$port = $serviceName === 'smtp' ? $smtpPort : $pop3Port;
if ($action === 'start') { if ($action === 'start') {
$result = startService($serviceName, $port); // 启动服务(实际应该通过系统服务管理,这里只是更新状态)
if ($result['success']) { $pid = null; // 实际应该获取进程ID
$pid = $result['pid']; $serviceRepo->updateStatus($serviceName, true, $pid);
$serviceRepo->updateStatus($serviceName, true, $pid); $message = strtoupper($serviceName) . "服务已启动";
$message = strtoupper($serviceName) . "服务已启动 (PID: {$pid})";
} else {
$error = strtoupper($serviceName) . "服务启动失败: " . $result['message'];
}
} elseif ($action === 'stop') { } elseif ($action === 'stop') {
// 停止服务
$status = $serviceRepo->getStatus($serviceName); $status = $serviceRepo->getStatus($serviceName);
$pid = $status['pid'] ?? getServicePid($serviceName, $port); if ($status && $status['pid']) {
// 尝试终止进程
if (stopService($serviceName, $pid, $port)) { @exec("kill {$status['pid']} 2>/dev/null");
$serviceRepo->updateStatus($serviceName, false, null);
$message = strtoupper($serviceName) . "服务已停止";
} else {
$error = strtoupper($serviceName) . "服务停止失败";
} }
// ===================== 修改部分结束 ===================== $serviceRepo->updateStatus($serviceName, false, null);
$message = strtoupper($serviceName) . "服务已停止";
} }
} }
} }
// 获取服务状态(修改判断逻辑) // 获取服务状态
// ===================== 修改部分开始 =====================
$smtpStatus = $serviceRepo->getStatus('smtp'); $smtpStatus = $serviceRepo->getStatus('smtp');
$pop3Status = $serviceRepo->getStatus('pop3'); $pop3Status = $serviceRepo->getStatus('pop3');
$smtpRunning = $serviceRepo->isRunning('smtp');
$pop3Running = $serviceRepo->isRunning('pop3');
// 实际检查服务是否运行(使用配置的端口) // 获取端口设置
$smtpRunning = isServiceRunning('smtp', $smtpPort); $smtpPort = $settingsRepo->get('smtp_port', 25);
$pop3Running = isServiceRunning('pop3', $pop3Port); $pop3Port = $settingsRepo->get('pop3_port', 110);
// 如果数据库状态与实际状态不一致,更新数据库
if ($smtpStatus['is_running'] != $smtpRunning) {
$pid = $smtpRunning ? getServicePid('smtp', $smtpPort) : null;
$serviceRepo->updateStatus('smtp', $smtpRunning, $pid);
}
if ($pop3Status['is_running'] != $pop3Running) {
$pid = $pop3Running ? getServicePid('pop3', $pop3Port) : null;
$serviceRepo->updateStatus('pop3', $pop3Running, $pid);
}
// 重新获取更新后的状态
$smtpStatus = $serviceRepo->getStatus('smtp');
$pop3Status = $serviceRepo->getStatus('pop3');
$smtpRunning = $smtpStatus['is_running'];
$pop3Running = $pop3Status['is_running'];
// ===================== 修改部分结束 =====================
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@ -258,12 +92,12 @@ $pop3Running = $pop3Status['is_running'];
<div class="menu"> <div class="menu">
<a href="index.php">仪表盘</a> <a href="index.php">仪表盘</a>
<a href="users.php">用户管理</a> <a href="users.php">用户管理</a>
<a href="emails.php">邮件管理</a>
<a href="broadcast.php">群发邮件</a> <a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a> <a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a> <a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a> <a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a> <a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div> </div>
<div class="container"> <div class="container">
@ -320,42 +154,13 @@ $pop3Running = $pop3Status['is_running'];
</div> </div>
<div class="note"> <div class="note">
<strong>注意:</strong> <strong>注意:</strong>此页面仅用于管理服务状态。实际启动服务需要使用命令行:
<ul> <ul>
<li>如果服务启动失败,请检查是否没有该端口访问权限或端口被占用</li> <li>SMTP服务<code>sudo php scripts/start_smtp.php</code></li>
<li>当前配置SMTP端口 <?php echo $smtpPort; ?>, POP3端口 <?php echo $pop3Port; ?></li> <li>POP3服务<code>sudo php scripts/start_pop3.php</code></li>
</ul> </ul>
</div> </div>
</div> </div>
<!-- ===================== 新增部分:自动刷新状态 =====================
<script>
// 每5秒自动检查服务状态
setInterval(function() {
fetch(window.location.href)
.then(response => response.text())
.then(html => {
// 从返回的HTML中提取运行状态
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
// 检查SMTP状态是否变化
const smtpStatusText = tempDiv.querySelector('.service-box:nth-child(1) .status')?.textContent;
const currentSmtpStatus = document.querySelector('.service-box:nth-child(1) .status')?.textContent;
// 检查POP3状态是否变化
const pop3StatusText = tempDiv.querySelector('.service-box:nth-child(2) .status')?.textContent;
const currentPop3Status = document.querySelector('.service-box:nth-child(2) .status')?.textContent;
// 如果有变化,刷新页面
if (smtpStatusText !== currentSmtpStatus || pop3StatusText !== currentPop3Status) {
console.log('服务状态变化,刷新页面...');
location.reload();
}
})
.catch(error => console.error('状态检查失败:', error));
}, 30000);
</script>**-->
</body> </body>
</html> </html>

@ -15,18 +15,18 @@ require_once __DIR__ . '/../src/storage/MailboxRepository.php';
require_once __DIR__ . '/../src/utils/Validator.php'; require_once __DIR__ . '/../src/utils/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php'; require_once __DIR__ . '/../src/utils/Security.php';
//开启报错提示
ini_set('log_errors', 1);
ini_set('error_log', '/dev/stdout');
session_start(); session_start();
// 是否登录验证 // 身份验证
if (!isset($_SESSION['user_id'])) { if (!isset($_SESSION['user_id'])) {
header('Location: index.php'); header('Location: index.php');
exit; exit;
} }
// 检查管理员权限
if (!$_SESSION['is_admin']) {
die('权限不足:只有管理员可以访问此页面');
}
/** @var SystemSettingsRepository */ /** @var SystemSettingsRepository */
$settingsRepo = new SystemSettingsRepository(); $settingsRepo = new SystemSettingsRepository();
@ -36,7 +36,6 @@ $userRepo = new UserRepository();
$mailboxRepo = new MailboxRepository(); $mailboxRepo = new MailboxRepository();
$message = ''; $message = '';
$error = ''; $error = '';
$domain = $settingsRepo->get('domain', 'test.com');
// 处理系统设置更新 // 处理系统设置更新
if (isset($_POST['update_settings'])) { if (isset($_POST['update_settings'])) {
@ -62,27 +61,9 @@ if (isset($_POST['update_settings'])) {
// 域名 // 域名
if (isset($_POST['domain'])) { if (isset($_POST['domain'])) {
$newDomain = trim($_POST['domain']); $domain = trim($_POST['domain']);
if (!empty($newDomain)) { if (!empty($domain)) {
/* **** 调试开始 **** */ $settingsRepo->set('domain', $domain);
$oldDomain = $settingsRepo->get('domain', 'test.com'); // 实时旧值
error_log('【调试】实时旧域名:' . $oldDomain);
error_log('【调试】提交新域名:' . $newDomain);
/* **** 调试结束 **** */
if ($newDomain !== $oldDomain) {
$settingsRepo->set('domain', $newDomain);
require_once __DIR__ . '/../src/admin/SyncDomainService.php';
$sync = new SyncDomainService();
$count = $sync->run($oldDomain, $newDomain);
$message .= " 已同步 $count 个用户邮箱后缀。";
/* **** 调试开始 **** */
error_log('【调试】REPLACE 影响行数:' . $count);
/* **** 调试结束 **** */
}
} }
} }
@ -198,12 +179,11 @@ $users = $userRepo->getAll();
<div class="menu"> <div class="menu">
<a href="index.php">仪表盘</a> <a href="index.php">仪表盘</a>
<a href="users.php">用户管理</a> <a href="users.php">用户管理</a>
<a href="emails.php">邮件管理</a>
<a href="broadcast.php">群发邮件</a> <a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a> <a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a> <a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a> <a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div> </div>
<div class="container"> <div class="container">
@ -241,10 +221,9 @@ $users = $userRepo->getAll();
<form method="POST"> <form method="POST">
<div class="form-group"> <div class="form-group">
<label>服务器域名默认test.com</label> <label>服务器域名默认test.com</label>
<input type="text" name="domain" value="<?php echo htmlspecialchars($settings['domain'] ?? 'test.com'); ?>" > <input type="text" name="domain" value="<?php echo htmlspecialchars($settings['domain'] ?? 'test.com'); ?>" required>
<small>邮件服务器域名,用户邮箱必须使用此域名</small> <small>邮件服务器域名,用户邮箱必须使用此域名</small>
</div> </div>
<button type="submit" name="update_settings" class="btn btn-primary">保存域名设置</button> <button type="submit" name="update_settings" class="btn btn-primary">保存域名设置</button>
</form> </form>
</div> </div>

@ -4,7 +4,6 @@ require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/UserRepository.php'; require_once __DIR__ . '/../src/storage/UserRepository.php';
require_once __DIR__ . '/../src/utils/Validator.php'; require_once __DIR__ . '/../src/utils/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php'; require_once __DIR__ . '/../src/utils/Security.php';
require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php';
session_start(); session_start();
@ -14,9 +13,10 @@ if (!isset($_SESSION['user_id'])) {
exit; exit;
} }
$settingsRepo = new SystemSettingsRepository(); // 检查管理员权限
// 获取域名设置(放在函数定义之前) if (!$_SESSION['is_admin']) {
$domain = $settingsRepo->get('domain', 'test.com'); die('权限不足:只有管理员可以访问此页面');
}
$userRepo = new UserRepository(); $userRepo = new UserRepository();
$message = ''; $message = '';
@ -33,8 +33,8 @@ if (isset($_POST['create_user'])) {
if (!$usernameValidation['valid']) { if (!$usernameValidation['valid']) {
$error = implode('<br>', $usernameValidation['errors']); $error = implode('<br>', $usernameValidation['errors']);
} else { } else {
if (!Validator::validateEmailDomain($username, $domain)) { if (!Validator::validateEmailDomain($username, 'test.com')) {
$error = "邮箱域名必须是 @".$domain; $error = "邮箱域名必须是 @test.com";
} else { } else {
$passwordValidation = Validator::validatePassword($password, 6); $passwordValidation = Validator::validatePassword($password, 6);
if (!$passwordValidation['valid']) { if (!$passwordValidation['valid']) {
@ -69,18 +69,13 @@ if (isset($_POST['update_user'])) {
} }
} }
/**if (isset($_POST['is_admin'])) { if (isset($_POST['is_admin'])) {
$data['is_admin'] = (int)$_POST['is_admin']; $data['is_admin'] = (int)$_POST['is_admin'];
} }
if (isset($_POST['is_active'])) { if (isset($_POST['is_active'])) {
$data['is_active'] = (int)$_POST['is_active']; $data['is_active'] = (int)$_POST['is_active'];
}**/ }
// 管理员权限总是更新
$data['is_admin'] = isset($_POST['is_admin']) ? 1 : 0;
// 激活状态也是
$data['is_active'] = isset($_POST['is_active']) ? 1 : 0;
if (empty($error) && !empty($data)) { if (empty($error) && !empty($data)) {
if ($userRepo->update($userId, $data)) { if ($userRepo->update($userId, $data)) {
@ -155,12 +150,10 @@ $users = $userRepo->getAll();
<div class="menu"> <div class="menu">
<a href="index.php">仪表盘</a> <a href="index.php">仪表盘</a>
<a href="users.php">用户管理</a> <a href="users.php">用户管理</a>
<a href="broadcast.php">群发邮件</a> <a href="emails.php">邮件管理</a>
<a href="filters.php">过滤规则</a> <a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a> <a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a> <a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div> </div>
<div class="container"> <div class="container">
@ -179,7 +172,7 @@ $users = $userRepo->getAll();
<form method="POST" class="form-inline"> <form method="POST" class="form-inline">
<div class="form-group"> <div class="form-group">
<label>邮箱地址</label> <label>邮箱地址</label>
<input type="email" name="username" placeholder="user@<?= htmlspecialchars($domain) ?>" required> <input type="email" name="username" placeholder="user@test.com" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>密码</label> <label>密码</label>

@ -1,19 +1,18 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
// 打印当前目录
error_log('start_smtp.php CWD: ' . getcwd());
error_log('start_smtp.php __DIR__: ' . __DIR__);
require_once __DIR__.'/../src/storage/SystemSettingsRepository.php';
require_once __DIR__ . '/../src/protocol/Pop3Server.php'; require_once __DIR__ . '/../src/protocol/Pop3Server.php';
$settingsRepo = new SystemSettingsRepository();
// 获取端口设置(放在函数定义之前)
$pop3Port = $settingsRepo->get('pop3_port', 110);
echo "启动最简POP3邮件服务器\n"; echo "启动最简POP3邮件服务器\n";
echo "==============================\n\n"; echo "==============================\n\n";
$server = new SimplePop3Server('0.0.0.0',$pop3Port); // 检查是否有权限监听110端口需要sudo
if (posix_getuid() != 0) {
echo "错误需要管理员权限监听110端口\n";
echo "请使用sudo php " . __FILE__ . "\n";
echo "或者使用其他端口(需要修改代码)\n";
exit(1);
}
$server = new SimplePop3Server();
$server->start(); $server->start();
?> ?>

@ -1,29 +1,18 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
// 打印当前目录
error_log('start_smtp.php CWD: ' . getcwd());
error_log('start_smtp.php __DIR__: ' . __DIR__);
require_once __DIR__.'/../src/storage/SystemSettingsRepository.php';
require_once __DIR__ . '/../src/protocol/SmtpServer.php'; require_once __DIR__ . '/../src/protocol/SmtpServer.php';
$settingsRepo = new SystemSettingsRepository();
// 获取端口设置(放在函数定义之前)
$smtpPort = $settingsRepo->get('smtp_port', 25);
echo "启动最简SMTP邮件服务器\n"; echo "启动最简SMTP邮件服务器\n";
echo "==============================\n\n"; echo "==============================\n\n";
// 检查端口权限 // 检查端口权限
/**if (posix_getuid() != 0) { if (posix_getuid() != 0) {
echo "错误:需要管理员权限监听".$smtpPort."端口\n"; echo "错误:需要管理员权限监听25端口\n";
echo "请使用sudo php " . __FILE__ . "\n"; echo "请使用sudo php " . __FILE__ . "\n";
echo "或者使用其他端口\n"; echo "或者使用其他端口(需要修改代码)\n";
exit(1); exit(1);
}**/ }
$server = new SimpleSmtpServer('0.0.0.0',$smtpPort); $server = new SimpleSmtpServer();
$server->start(); $server->start();
?> ?>

@ -0,0 +1 @@
//处理管理请求

@ -1,32 +0,0 @@
<?php
require_once __DIR__ . '/../storage/Database.php';
/**
* 一次性把 users.username 中 @oldDomain 批量替换成 @newDomain
*/
class SyncDomainService
{
private $db;
public function __construct()
{
$this->db = Database::getInstance();
}
/**
* @return int 实际被更新的用户数
*/
public function run(string $oldDomain, string $newDomain): int
{
// 防止注入,直接用 REPLACE 函数最省事
$sql = "UPDATE users
SET username = REPLACE(username, :old, :new)
WHERE username LIKE :like";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':old' => '@' . $oldDomain,
':new' => '@' . $newDomain,
':like' => '%@' . $oldDomain
]);
return $stmt->rowCount();
}
}

@ -0,0 +1 @@
// 管理后台网页" - 网页界面

@ -4,16 +4,6 @@
* 运行php src/Pop3Server.php * 运行php src/Pop3Server.php
*/ */
require_once __DIR__ . '/../storage/MailboxRepository.php';
require_once __DIR__ . '/../storage/UserRepository.php';
require_once __DIR__ . '/../storage/FilterRepository.php';
require_once __DIR__ . '/../storage/SystemSettingsRepository.php';
$settingsRepo = new SystemSettingsRepository();
// 获取端口设置(放在函数定义之前)
$smtpPort = $settingsRepo->get('smtp_port', 25);
$pop3Port = $settingsRepo->get('pop3_port', 110);
class SimplePop3Server class SimplePop3Server
{ {
private $socket; private $socket;
@ -24,18 +14,11 @@ class SimplePop3Server
private $userEmails = []; private $userEmails = [];
private $deletedEmails = []; // 存储待删除的邮件ID private $deletedEmails = []; // 存储待删除的邮件ID
// 新增:存储主机和端口
private $host;
private $port;
public function __construct($host = '0.0.0.0', $port = 110) public function __construct($host = '0.0.0.0', $port = 110)
{ {
echo "POP3服务器启动在 {$host}:{$port}\n"; echo "POP3服务器启动在 {$host}:{$port}\n";
echo "按 Ctrl+C 停止\n\n"; echo "按 Ctrl+C 停止\n\n";
$this->host = $host;
$this->port = $port;
$this->connectDB(); $this->connectDB();
} }
@ -59,7 +42,7 @@ class SimplePop3Server
{ {
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1); socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($this->socket,$this->host, $this->port); socket_bind($this->socket, '0.0.0.0', 110);
socket_listen($this->socket, 5); socket_listen($this->socket, 5);
$this->isRunning = true; $this->isRunning = true;
@ -97,7 +80,7 @@ class SimplePop3Server
$input_trimmed = trim($input); $input_trimmed = trim($input);
$command = strtoupper($input_trimmed); $command = strtoupper($input_trimmed);
//echo "客户端: {$input_trimmed}\n"; echo "客户端: {$input_trimmed}\n";
if ($state === 'AUTH') { if ($state === 'AUTH') {
// 认证阶段 // 认证阶段
@ -320,7 +303,7 @@ class SimplePop3Server
// 如果直接运行这个文件 // 如果直接运行这个文件
if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) { if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) {
$server = new SimplePop3Server('0.0.0.0',$pop3Port); $server = new SimplePop3Server();
$server->start(); $server->start();
} }
?> ?>

@ -0,0 +1,220 @@
<?php
require_once __DIR__ . '/../../config/database.php';
require_once __DIR__ . '/../storage/Database.php';
class SmtpHandler {
private $state = 'WAIT_HELO'; // 状态: WAIT_HELO, WAIT_MAIL, WAIT_RCPT, WAIT_DATA, IN_DATA
private $from = '';
private $to = [];
private $data = '';
private $currentUser = null;
public function processCommand($command) {
$command = strtoupper(trim($command));
switch ($this->state) {
case 'WAIT_HELO':
return $this->handleHelo($command);
case 'WAIT_MAIL':
return $this->handleMail($command);
case 'WAIT_RCPT':
return $this->handleRcpt($command);
case 'WAIT_DATA':
return $this->handleDataCommand($command);
case 'IN_DATA':
return $this->handleDataContent($command);
default:
return "500 Unknown state";
}
}
private function handleHelo($command) {
if (substr($command, 0, 4) === 'HELO' || substr($command, 0, 4) === 'EHLO') {
$this->state = 'WAIT_MAIL';
return "250 " . SERVER_DOMAIN . " Hello";
} elseif ($command === 'QUIT') {
return "221 Bye";
} else {
return "503 Send HELO/EHLO first";
}
}
private function handleMail($command) {
if (substr($command, 0, 10) === 'MAIL FROM:') {
$this->from = $this->extractEmail($command);
// 验证发件人是否存在(简单实现)
if ($this->validateEmail($this->from)) {
$this->state = 'WAIT_RCPT';
$this->to = []; // 清空收件人列表
return "250 Sender OK";
} else {
return "550 Sender not permitted";
}
} elseif ($command === 'QUIT') {
return "221 Bye";
} else {
return "503 Need MAIL command";
}
}
private function handleRcpt($command) {
if (substr($command, 0, 8) === 'RCPT TO:') {
$recipient = $this->extractEmail($command);
// 验证收件人是否存在
if ($this->validateEmail($recipient)) {
$this->to[] = $recipient;
$this->state = 'WAIT_RCPT';
return "250 Recipient OK";
} else {
return "550 Recipient not found";
}
} elseif (substr($command, 0, 4) === 'DATA') {
if (count($this->to) > 0) {
$this->state = 'WAIT_DATA';
return "354 Start mail input; end with <CRLF>.<CRLF>";
} else {
return "503 Need RCPT command";
}
} elseif ($command === 'QUIT') {
return "221 Bye";
} else {
return "503 Need RCPT or DATA command";
}
}
private function handleDataCommand($command) {
// 在WAIT_DATA状态我们等待真正的数据内容
// 任何命令都会进入数据处理状态
$this->state = 'IN_DATA';
$this->data = $command . "\r\n";
return null; // 不发送响应,等待数据结束
}
private function handleDataContent($command) {
// 检查是否收到结束标记(单独一行的.
if ($command === '.') {
// 保存邮件到数据库
$this->saveEmail();
// 重置状态
$this->state = 'WAIT_HELO';
$this->from = '';
$this->to = [];
$this->data = '';
return "250 Mail accepted for delivery";
} else {
// 累积邮件数据
$this->data .= $command . "\r\n";
return null; // 不发送响应,继续接收数据
}
}
private function extractEmail($command) {
// 从类似 "MAIL FROM:<user@domain.com>" 中提取邮箱
if (preg_match('/<([^>]+)>/', $command, $matches)) {
return $matches[1];
}
return '';
}
private function validateEmail($email) {
// 简单验证:检查格式和是否在用户表中存在
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return false;
}
// 查询数据库(简化实现)
try {
$db = Database::getInstance();
$stmt = $db->prepare("SELECT id FROM users WHERE username = ? AND is_active = 1");
$stmt->execute([$email]);
return $stmt->rowCount() > 0;
} catch (Exception $e) {
return false;
}
}
private function saveEmail() {
try {
$db = Database::getInstance();
// 解析邮件数据(简化版)
$lines = explode("\r\n", $this->data);
$headers = [];
$body = '';
$inBody = false;
foreach ($lines as $line) {
if (!$inBody && trim($line) === '') {
$inBody = true;
continue;
}
if ($inBody) {
$body .= $line . "\r\n";
} else {
$headers[] = $line;
}
}
// 获取发件人ID
$stmt = $db->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$this->from]);
$sender = $stmt->fetch();
if (!$sender) {
return false;
}
// 为每个收件人保存邮件
foreach ($this->to as $recipientEmail) {
$stmt = $db->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$recipientEmail]);
$recipient = $stmt->fetch();
if ($recipient) {
$subject = $this->extractHeader($headers, 'Subject') ?: '(No Subject)';
$insertStmt = $db->prepare("
INSERT INTO emails
(sender_id, recipient_id, subject, body, headers, size_bytes)
VALUES (?, ?, ?, ?, ?, ?)
");
$emailData = implode("\r\n", $headers) . "\r\n\r\n" . $body;
$insertStmt->execute([
$sender['id'],
$recipient['id'],
$subject,
$body,
implode("\r\n", $headers),
strlen($emailData)
]);
}
}
return true;
} catch (Exception $e) {
error_log("保存邮件失败: " . $e->getMessage());
return false;
}
}
private function extractHeader($headers, $name) {
foreach ($headers as $header) {
if (stripos($header, $name . ':') === 0) {
return trim(substr($header, strlen($name) + 1));
}
}
return null;
}
}

@ -1,32 +1,22 @@
<?php <?php
/** // 设置PHP内部编码为UTF-8解决乱码的关键
* 一体化的SMTP服务器
*
* 注意:本类同时包含:
* 1. 网络层Server功能- 负责socket通信
* 2. 协议层Handler功能- 负责SMTP协议处理
*
* 这种设计对于教学项目是合适的,保持了简单性。
* 生产环境可以考虑拆分为 SmtpServer + SmtpHandler。
*/
ini_set('default_charset', 'UTF-8');
mb_internal_encoding('UTF-8'); mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');
if (function_exists('iconv_set_encoding')) {
iconv_set_encoding("internal_encoding", "UTF-8");
iconv_set_encoding("output_encoding", "UTF-8");
}
// 设置默认时区
date_default_timezone_set('Asia/Shanghai');
/** /**
* 最简SMTP服务器 - 负责收信 * 最简SMTP服务器 - 负责收信
* 运行php src/SmtpServer.php * 运行php src/SmtpServer.php
*/ */
require_once __DIR__ . '/../storage/FilterRepository.php';
require_once __DIR__ . '/../storage/MailboxRepository.php'; require_once __DIR__ . '/../storage/MailboxRepository.php';
require_once __DIR__ . '/../storage/UserRepository.php'; require_once __DIR__ . '/../storage/UserRepository.php';
require_once __DIR__ . '/../storage/FilterRepository.php';
require_once __DIR__ . '/../storage/SystemSettingsRepository.php';
$settingsRepo = new SystemSettingsRepository();
// 获取端口设置(放在函数定义之前)
$smtpPort = $settingsRepo->get('smtp_port', 25);
$pop3Port = $settingsRepo->get('pop3_port', 110);
class SimpleSmtpServer class SimpleSmtpServer
{ {
@ -37,19 +27,11 @@ class SimpleSmtpServer
private $mailboxRepo; private $mailboxRepo;
private $userRepo; private $userRepo;
// 新增:存储主机和端口
private $host;
private $port;
private $clientIp;
public function __construct($host = '0.0.0.0', $port = 25) public function __construct($host = '0.0.0.0', $port = 25)
{ {
echo "SMTP服务器启动在 {$host}:{$port}\n"; echo "SMTP服务器启动在 {$host}:{$port}\n";
echo "按 Ctrl+C 停止\n\n"; echo "按 Ctrl+C 停止\n\n";
// 存储配置,而不是立即使用
$this->host = $host;
$this->port = $port;
// 连接数据库 // 连接数据库
$this->connectDB(); $this->connectDB();
@ -85,7 +67,7 @@ class SimpleSmtpServer
// 创建socket邮局开门 // 创建socket邮局开门
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1); socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($this->socket, '0.0.0.0',$this->port); socket_bind($this->socket, '0.0.0.0', 25);
socket_listen($this->socket, 5); socket_listen($this->socket, 5);
$this->isRunning = true; $this->isRunning = true;
@ -107,9 +89,9 @@ class SimpleSmtpServer
// 获取客户端IP地址 // 获取客户端IP地址
socket_getpeername($client, $clientIp); socket_getpeername($client, $clientIp);
$clientIp = $clientIp ?: 'unknown'; $clientIp = $clientIp ?: 'unknown';
$this->clientIp = $clientIp;
// 记录连接日志 // 记录连接日志
$this->log("客户端连接",$this->clientIp); $this->log("客户端连接", $clientIp);
try { try {
// 1. 说欢迎语 // 1. 说欢迎语
@ -149,17 +131,18 @@ class SimpleSmtpServer
if ($input === false) break; if ($input === false) break;
$input = trim($input); $input = trim($input);
//echo "客户端: {$input}\n"; echo "客户端: {$input}\n";
if (stripos($input, 'RCPT TO:') === 0) { if (stripos($input, 'RCPT TO:') === 0) {
// 提取收件人邮箱 // 提取收件人邮箱
if (preg_match('/<(.+?)>/', $input, $matches)) { if (preg_match('/<(.+?)>/', $input, $matches)) {
$to = $matches[1]; $to = $matches[1];
// 新增:收件人过滤
// 检查收件人邮箱过滤规则
if ($this->filterRepo->isEmailBlocked($to)) { if ($this->filterRepo->isEmailBlocked($to)) {
$this->send($client, "550 Recipient email blocked"); $this->send($client, "550 Recipient email blocked");
$this->log("收件人邮箱被过滤: {$to}", $this->clientIp); $this->log("收件人邮箱被阻止: {$to}", $clientIp);
continue; // 直接拒绝,不加入 $recipients continue;
} }
// 初步检查收件人邮箱大小限制(使用估算值,实际检查在接收邮件内容后) // 初步检查收件人邮箱大小限制(使用估算值,实际检查在接收邮件内容后)
@ -225,7 +208,7 @@ class SimpleSmtpServer
// 9. 保存到数据库(支持多收件人) // 9. 保存到数据库(支持多收件人)
$successCount = 0; $successCount = 0;
foreach ($validRecipients as $to) { foreach ($validRecipients as $to) {
if ($this->saveEmail($from, $to, $emailContent, $this->clientIp, $emailSize)) { if ($this->saveEmail($from, $to, $emailContent, $clientIp, $emailSize)) {
$successCount++; $successCount++;
} }
} }
@ -263,7 +246,7 @@ class SimpleSmtpServer
if ($input === false) break; if ($input === false) break;
$input = trim($input); $input = trim($input);
//echo "客户端: {$input}\n"; echo "客户端: {$input}\n";
if (stripos($input, $expected) === 0) { if (stripos($input, $expected) === 0) {
// 提取邮箱地址 // 提取邮箱地址
@ -449,14 +432,14 @@ class SimpleSmtpServer
// 如果直接运行这个文件 // 如果直接运行这个文件
if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) { if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) {
// 检查是否有权限监听端口需要sudo // 检查是否有权限监听25端口需要sudo
if (posix_getuid() != 0) { if (posix_getuid() != 0) {
echo "注意需要sudo权限监听端口\n"; echo "注意需要sudo权限监听25端口\n";
echo "请运行sudo php " . __FILE__ . "\n"; echo "请运行sudo php " . __FILE__ . "\n";
exit(1); exit(1);
} }
$server = new SimpleSmtpServer('0.0.0.0',$smtpPort); $server = new SimpleSmtpServer();
$server->start(); $server->start();
} }
?> ?>

@ -1,8 +1,4 @@
<?php <?php
/**
* 数据库配置文件
* 使用环境变量或默认配置
*/
require_once __DIR__ . '/../../config/database.php'; require_once __DIR__ . '/../../config/database.php';
class Database { class Database {
@ -11,8 +7,7 @@ class Database {
private function __construct() { private function __construct() {
$config = require __DIR__ . '/../../config/database.php'; $config = require __DIR__ . '/../../config/database.php';
//自动每一条SQL提交一次
// $config['options'][PDO::ATTR_AUTOCOMMIT] = true;
$dsn = "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']};charset={$config['charset']}"; $dsn = "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']};charset={$config['charset']}";
try { try {

@ -109,13 +109,6 @@ class FilterRepository {
public function updateStatus($id, $isActive) { public function updateStatus($id, $isActive) {
$stmt = $this->db->prepare("UPDATE filter_rules SET is_active = ? WHERE id = ?"); $stmt = $this->db->prepare("UPDATE filter_rules SET is_active = ? WHERE id = ?");
return $stmt->execute([$isActive ? 1 : 0, $id]); return $stmt->execute([$isActive ? 1 : 0, $id]);
$this->db->commit();
}
public function getById(int $id): ?array
{
$stmt = $this->db->prepare("SELECT * FROM filter_rules WHERE id = ? LIMIT 1");
$stmt->execute([$id]);
return $stmt->fetch() ?: null;
} }
} }

@ -1,11 +1,11 @@
<?php <?php
require_once __DIR__ . '/Database.php'; require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/../utils/Security.php'; require_once __DIR__ . '/../utils/Security.php';
/** /**
* 用户数据访问层 * 用户数据访问层
* 查客户信息、创建用户等 * 查客户信息、创建用户等
*/ */
class UserRepository { class UserRepository {
private $db; private $db;

Loading…
Cancel
Save