Compare commits

...

9 Commits

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

@ -1,460 +1,322 @@
# 邮件服务器项目
基于POP3和SMTP协议的邮件服务端实现
## 环境要求
- Docker & Docker Compose
- PHP 7.4+ (需要扩展: php-mysql, php-sockets)
- WSL2 (Windows环境)
## 快速开始
### 1. 安装PHP扩展如果未安装
```bash
sudo apt update
sudo apt install php php-cli php-mysql php-sockets -y
```
### 2. 启动数据库
```bash
cd /mnt/d/mailserver/mailserver
# 首次启动或重置数据库
docker-compose down -v
docker-compose up -d
# 等待10-15秒让数据库初始化完成
sleep 15
```
### 3. 初始化管理功能数据库表(首次使用)
```bash
# 执行管理功能相关的数据库表创建
docker-compose exec mysql mysql -umail_user -puser123 mail_server < scripts/create_admin_tables.sql
```
### 4. 查看数据库phpMyAdmin
- 访问http://localhost:8088
- 登录信息:
- 服务器:`mysql`(或留空)
- 用户名:`root`
- 密码:`root123`
### 5. 测试账号
- 管理员:`admin@test.com` / `123456`
- 普通用户:`user1@test.com` / `123456`
## 端口说明
- **25** - SMTP服务器发送邮件
- **110** - POP3服务器接收邮件
- **3308** - MySQL数据库
- **8080** - Web管理后台
- **8088** - phpMyAdmin管理界面
## 启动服务器
### SMTP服务器发送邮件
```bash
sudo php scripts/start_smtp.php
```
### POP3服务器接收邮件
```bash
sudo php scripts/start_pop3.php
```
**注意**两个服务器需要分别在两个终端运行都需要sudo权限因为使用25和110端口
## Web管理后台
### 启动Web服务器
**方式1推荐从public目录启动**
```bash
cd /mnt/d/mailserver/mailserver/public
php -S localhost:8080
```
**方式2从项目根目录启动**
```bash
cd /mnt/d/mailserver/mailserver
php -S localhost:8080 -t public
```
### 访问管理后台
- 访问http://localhost:8080
- 登录账号:
- 管理员:`admin@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发送邮件
**终端1启动SMTP服务器**
```bash
sudo php scripts/start_smtp.php
```
**终端2连接测试**
```bash
telnet localhost 25
```
**输入命令:**
```
HELO test
MAIL FROM: <user1@test.com>
RCPT TO: <admin@test.com>
DATA
Subject: 测试邮件
From: user1@test.com
To: admin@test.com
这是一封测试邮件!
.
QUIT
```
### 测试POP3接收邮件
**终端1启动POP3服务器**
```bash
sudo php scripts/start_pop3.php
```
**终端2连接测试**
```bash
telnet localhost 110
```
**输入命令:**
```
USER admin@test.com
PASS 123456
STAT
LIST
RETR 1
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推荐
访问 http://localhost:8088选择 `mail_server` 数据库
### 方法2命令行
```bash
# 查看用户
docker-compose exec mysql mysql -umail_user -puser123 mail_server -e "SELECT * FROM users;"
# 查看邮件
docker-compose exec mysql mysql -umail_user -puser123 mail_server -e "SELECT id, sender, recipient, subject, created_at FROM emails ORDER BY id DESC;"
```
## 重置数据库
```bash
docker-compose down -v
docker-compose up -d
sleep 15
# 重新执行初始化脚本
docker-compose exec mysql mysql -umail_user -puser123 mail_server < scripts/create_admin_tables.sql
```
## 常见问题
### 端口被占用
**SMTP服务器启动失败25端口**
```bash
# 检查端口占用
sudo netstat -tlnp | grep 25
# 或使用
sudo lsof -i :25
```
**POP3服务器启动失败110端口**
```bash
sudo netstat -tlnp | grep 110
```
### 数据库连接失败
```bash
# 检查Docker容器状态
docker-compose ps
# 查看数据库日志
docker-compose logs mysql
# 重启数据库
docker-compose restart mysql
```
### telnet连接失败
```bash
# 安装telnet如果未安装
sudo apt install telnet
# 或使用nc替代
nc localhost 25
```
### 密码验证失败
- 确认使用正确的测试账号:`admin@test.com` / `123456`
- 如果重置了数据库,密码会恢复为 `123456`
## 项目结构
```
mailserver/
├── scripts/ # 启动脚本和SQL
│ ├── start_smtp.php # 启动SMTP服务器
│ ├── start_pop3.php # 启动POP3服务器
│ ├── create_tables.sql # 数据库初始化脚本
│ └── create_admin_tables.sql # 管理功能数据库表
├── src/ # 源代码
│ ├── protocol/ # SMTP/POP3协议实现
│ │ ├── SmtpServer.php
│ │ └── Pop3Server.php
│ ├── storage/ # 数据存储层
│ │ ├── Database.php
│ │ ├── UserRepository.php
│ │ ├── EmailRepository.php
│ │ ├── SystemSettingsRepository.php
│ │ ├── FilterRepository.php
│ │ ├── ServiceRepository.php
│ │ └── MailboxRepository.php
│ ├── admin/ # 管理后台逻辑
│ │ └── BroadcastService.php
│ └── utils/ # 工具类
│ ├── Security.php
│ └── Validator.php
├── public/ # Web管理界面
│ ├── index.php # 主页面(登录+仪表盘)
│ ├── register.php # 用户注册
│ ├── logout.php # 退出登录
│ ├── users.php # 用户管理
│ ├── emails.php # 邮件管理
│ ├── broadcast.php # 群发邮件
│ ├── filters.php # 过滤规则
│ ├── settings.php # 系统设置
│ ├── services.php # 服务管理
│ ├── logs.php # 日志管理
│ └── help.php # 帮助
├── config/ # 配置文件
│ ├── database.php # 数据库配置
│ └── constants.php # 常量定义
└── docker-compose.yml # Docker配置
```
## 功能完成情况
### ✅ 服务器端功能(已完成)
根据课程设计说明书要求,服务器端功能已全部实现:
1. **✅ 邮箱管理**
- 设置用户邮箱大小限制
- 查看用户邮箱使用情况
2. **✅ 客户管理**
- 创建新客户账号和密码
- 设置用户权限(管理员/普通用户)
- 启用/禁用用户
- 删除客户账号
- 编辑用户信息
3. **✅ 服务起停**
- SMTP服务状态管理
- POP3服务状态管理
- 服务启动/停止控制
4. **✅ 系统设置**
- SMTP端口设置默认25
- POP3端口设置默认110
- 服务器域名设置默认test.com
- 管理员密码修改
- 邮件过滤(账号过滤)
- IP地址过滤
5. **✅ 日志管理**
- SMTP日志查看
- POP3日志查看
- 日志清除功能
- 日志存储位置设置
- 日志文件大小管理
6. **✅ 日常管理**
- 群发邮件功能(发送给所有用户或指定用户)
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
# 邮件服务器项目
基于POP3和SMTP协议的邮件服务端实现
## 环境要求
- Docker & Docker Compose
- PHP 7.4+ (需要扩展: php-mysql, php-sockets)
- WSL2 (Windows环境)
- netstat
## 快速开始
### 1. 安装PHP扩展如果未安装
```bash
sudo apt update
sudo apt install php php-cli php-mysql php-sockets -y
```
### 2. 启动数据库
```bash
cd /mnt/d/mailserver/mailserver
# 首次启动或重置数据库
docker-compose down -v
docker-compose up -d
# 等待10-15秒让数据库初始化完成
sleep 15
```
### 3. 初始化管理功能数据库表(首次使用)
```bash
# 执行管理功能相关的数据库表创建
docker-compose exec mysql mysql -umail_user -puser123 mail_server < scripts/create_admin_tables.sql
```
### 4. 查看数据库phpMyAdmin
- 访问http://localhost:8088
- 登录信息:
- 服务器:`mysql`(或留空)
- 用户名:`root`
- 密码:`root123`
### 5. 测试账号
- 管理员:`admin@test.com` / `123456`
- 普通用户:`user1@test.com` / `123456`
## 端口说明
- **25** - SMTP服务器发送邮件
- **110** - POP3服务器接收邮件
- **3308** - MySQL数据库
- **8080** - Web管理后台
- **8088** - phpMyAdmin管理界面
## Web管理后台
### 启动Web服务器
**方式1推荐从public目录启动**
```bash
cd /mnt/d/mailserver/mailserver/public
php -S localhost:8080
#如果打不开可以尝试换端口为8888或者别的
```
**方式2从项目根目录启动**
```bash
cd /mnt/d/mailserver/mailserver
php -S localhost:8080 -t public
```
### 访问管理后台
- 访问http://localhost:8080
- 登录账号:
- 管理员:`admin@test.com` / `123456`
- 普通用户:`user1@test.com` / `123456`
## 测试服务器方法
### 测试SMTP发送邮件
**终端1启动SMTP服务器**
```bash
sudo php scripts/start_smtp.php
```
**终端2连接测试**
```bash
telnet localhost 25
```
**输入命令:**
```
HELO test
MAIL FROM: <user1@test.com>
RCPT TO: <admin@test.com>
DATA
Subject: 测试邮件
From: user1@test.com
To: admin@test.com
这是一封测试邮件!
.
QUIT
```
### 测试POP3接收邮件
**终端1启动POP3服务器**
```bash
sudo php scripts/start_pop3.php
```
**终端2连接测试**
```bash
telnet localhost 110
```
**输入命令:**
```
USER admin@test.com
PASS 123456
STAT
LIST
RETR 1
QUIT
```
## 查看数据
### 方法1phpMyAdmin推荐
访问 http://localhost:8088选择 `mail_server` 数据库
### 方法2命令行
```bash
# 查看用户
docker-compose exec mysql mysql -umail_user -puser123 mailserver -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;"
```
## 重置数据库
```bash
docker-compose down -v
docker-compose up -d
sleep 15
# 重新执行初始化脚本
docker-compose exec mysql mysql -umail_user -puser123 mail_server < scripts/create_admin_tables.sql
```
## 常见问题
### 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端口**
```bash
# 检查端口占用
sudo netstat -tlnp | grep 25
# 或使用
sudo lsof -i :25
```
**POP3服务器启动失败110端口**
```bash
sudo netstat -tlnp | grep 110
```
### 数据库连接失败
```bash
# 检查Docker容器状态
docker-compose ps
# 查看数据库日志
docker-compose logs mysql
# 重启数据库
docker-compose restart mysql
```
### telnet连接失败
```bash
# 安装telnet如果未安装
sudo apt install telnet
# 或使用nc替代
nc localhost 25
```
### 密码验证失败
- 确认使用正确的测试账号:`admin@test.com` / `123456`
- 如果重置了数据库,密码会恢复为 `123456`
## 项目结构
```
mailserver/
├── scripts/ # 启动脚本和SQL
│ ├── start_smtp.php # 启动SMTP服务器
│ ├── start_pop3.php # 启动POP3服务器
│ ├── create_tables.sql # 数据库初始化脚本
│ └── create_admin_tables.sql # 管理功能数据库表
├── src/ # 源代码
│ ├── protocol/ # SMTP/POP3协议实现
│ │ ├── SmtpServer.php
│ │ └── Pop3Server.php
│ ├── storage/ # 数据存储层
│ │ ├── Database.php
│ │ ├── UserRepository.php
│ │ ├── EmailRepository.php
│ │ ├── SystemSettingsRepository.php
│ │ ├── FilterRepository.php
│ │ ├── ServiceRepository.php
│ │ └── MailboxRepository.php
│ ├── admin/ # 管理后台逻辑
│ │ └── BroadcastService.php
│ └── utils/ # 工具类
│ ├── Security.php
│ └── Validator.php
├── public/ # Web管理界面
│ ├── index.php # 主页面(登录+仪表盘)
│ ├── register.php # 用户注册
│ ├── logout.php # 退出登录
│ ├── users.php # 用户管理
│ ├── emails.php # 邮件管理
│ ├── broadcast.php # 群发邮件
│ ├── filters.php # 过滤规则
│ ├── settings.php # 系统设置
│ ├── services.php # 服务管理
│ ├── logs.php # 日志管理
│ └── help.php # 帮助
├── config/ # 配置文件
│ ├── database.php # 数据库配置
│ └── constants.php # 常量定义
└── docker-compose.yml # Docker配置
```
## 功能完成情况
### ✅ 服务器端功能(已完成)
根据课程设计说明书要求,服务器端功能已全部实现:
1. **✅ 邮箱管理**
- 设置用户邮箱大小限制
- 查看用户邮箱使用情况
2. **✅ 客户管理**
- 创建新客户账号和密码
- 设置用户权限(管理员/普通用户)
- 启用/禁用用户
- 删除客户账号
- 编辑用户信息
3. **✅ 服务起停**
- SMTP服务状态管理
- POP3服务状态管理
- 服务启动/停止控制
4. **✅ 系统设置**
- SMTP端口设置默认25
- POP3端口设置默认110
- 服务器域名设置默认test.com
- 管理员密码修改
- 邮件过滤(账号过滤)
- IP地址过滤
5. **✅ 日志管理**
- SMTP日志查看
- POP3日志查看
- 日志清除功能
- 日志存储位置设置
- 日志文件大小管理
6. **✅ 日常管理**
- 群发邮件功能(发送给所有用户或指定用户)
7. **✅ 帮助**
- 系统使用帮助文档

@ -1,21 +1,21 @@
<?php
/**
* 数据库配置文件
* 使用环境变量或默认配置
*/
return [
'host' => getenv('DB_HOST') ?: '127.0.0.1', // 使用127.0.0.1连接映射端口
'port' => getenv('DB_PORT') ?: '3308', // Docker映射端口
'database' => getenv('DB_DATABASE') ?: 'mail_server',
'username' => getenv('DB_USERNAME') ?: 'mail_user',
'password' => getenv('DB_PASSWORD') ?: 'user123',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
]
];
<?php
/**
* 数据库配置文件
* 使用环境变量或默认配置
*/
return [
'host' => getenv('DB_HOST') ?: '127.0.0.1', // 使用127.0.0.1连接映射端口
'port' => getenv('DB_PORT') ?: '3308', // Docker映射端口
'database' => getenv('DB_DATABASE') ?: 'mail_server',
'username' => getenv('DB_USERNAME') ?: 'mail_user',
'password' => getenv('DB_PASSWORD') ?: 'user123',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
]
];

@ -1,44 +1,44 @@
services:
mysql:
image: mysql:8.0
container_name: mail-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: mail_server
MYSQL_USER: mail_user
MYSQL_PASSWORD: user123
ports:
- "3308:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./scripts/create_all_tables.sql:/docker-entrypoint-initdb.d/init.sql
command:
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--default-authentication-plugin=mysql_native_password
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot123"]
interval: 5s
timeout: 3s
retries: 10
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: mail-phpmyadmin
restart: unless-stopped
environment:
PMA_HOST: mysql
PMA_PORT: 3306
PMA_USER: root
PMA_PASSWORD: root123
UPLOAD_LIMIT: 64M
ports:
- "8088:80"
depends_on:
mysql:
condition: service_healthy
volumes:
services:
mysql:
image: mysql:8.0
container_name: mail-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: mail_server
MYSQL_USER: mail_user
MYSQL_PASSWORD: user123
ports:
- "3308:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./scripts/create_all_tables.sql:/docker-entrypoint-initdb.d/init.sql
command:
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--default-authentication-plugin=mysql_native_password
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot123"]
interval: 5s
timeout: 3s
retries: 10
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: mail-phpmyadmin
restart: unless-stopped
environment:
PMA_HOST: mysql
PMA_PORT: 3306
PMA_USER: root
PMA_PASSWORD: root123
UPLOAD_LIMIT: 64M
ports:
- "8088:80"
depends_on:
mysql:
condition: service_healthy
volumes:
mysql-data:

@ -0,0 +1,20 @@
启动最简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

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

@ -1,193 +1,194 @@
<?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/storage/EmailRepository.php';
require_once __DIR__ . '/../src/admin/BroadcastService.php';
require_once __DIR__ . '/../src/utils/Security.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
// 检查管理员权限
if (!$_SESSION['is_admin']) {
die('权限不足:只有管理员可以访问此页面');
}
$broadcastService = new BroadcastService();
$userRepo = new UserRepository();
$message = '';
$error = '';
// 处理群发邮件
if (isset($_POST['send_broadcast'])) {
$subject = trim($_POST['subject'] ?? '');
$body = trim($_POST['body'] ?? '');
$broadcastType = $_POST['broadcast_type'] ?? 'all';
$recipients = trim($_POST['recipients'] ?? '');
if (empty($subject)) {
$error = "主题不能为空";
} elseif (empty($body)) {
$error = "邮件内容不能为空";
} else {
$senderEmail = $_SESSION['username'];
try {
if ($broadcastType === 'all') {
// 群发给所有用户
$result = $broadcastService->broadcastToAll($senderEmail, $subject, $body);
} else {
// 群发给指定用户
$recipientList = array_filter(array_map('trim', explode(',', $recipients)));
if (empty($recipientList)) {
$error = "请指定收件人";
} else {
$result = $broadcastService->broadcastToUsers($senderEmail, $recipientList, $subject, $body);
}
}
if (isset($result)) {
if ($result['success'] > 0) {
$message = "群发成功!成功发送 {$result['success']} 封邮件";
if ($result['failed'] > 0) {
$message .= ",失败 {$result['failed']} 封";
}
if (!empty($result['errors'])) {
$error = "部分失败:" . implode('<br>', array_slice($result['errors'], 0, 5));
if (count($result['errors']) > 5) {
$error .= "<br>... 还有 " . (count($result['errors']) - 5) . " 个错误";
}
}
} else {
$error = "群发失败:" . implode('<br>', $result['errors']);
}
}
} catch (Exception $e) {
$error = "群发失败: " . $e->getMessage();
}
}
}
// 获取所有用户列表(用于选择收件人)
$allUsers = $userRepo->getAll();
?>
<!DOCTYPE html>
<html>
<head>
<title>群发邮件 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); max-width: 800px; }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 8px; font-weight: 500; }
.form-group input, .form-group textarea, .form-group select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.form-group textarea { min-height: 200px; font-family: monospace; }
.form-group small { color: #666; font-size: 12px; }
.btn { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }
.btn-primary { background: #007bff; color: white; }
.btn-primary:hover { background: #0056b3; }
.radio-group { display: flex; gap: 20px; margin-bottom: 15px; }
.radio-group label { display: flex; align-items: center; cursor: pointer; }
.radio-group input[type="radio"] { width: auto; margin-right: 5px; }
.user-list { max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; border-radius: 4px; background: #f8f9fa; }
.user-list-item { padding: 5px; }
</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>
<a href="users.php">用户管理</a>
<a href="emails.php">邮件管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="settings.php">系统设置</a>
</div>
<div class="container">
<h2>群发邮件</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label>发送方式</label>
<div class="radio-group">
<label>
<input type="radio" name="broadcast_type" value="all" checked onchange="toggleRecipients()">
发送给所有用户
</label>
<label>
<input type="radio" name="broadcast_type" value="selected" onchange="toggleRecipients()">
发送给指定用户
</label>
</div>
</div>
<div class="form-group" id="recipients-group" style="display: none;">
<label>收件人(多个邮箱用逗号分隔)</label>
<input type="text" name="recipients" placeholder="user1@test.com, user2@test.com">
<small>请输入邮箱地址,多个邮箱用逗号分隔</small>
<div class="user-list">
<strong>可用用户列表:</strong>
<?php foreach ($allUsers as $user): ?>
<?php if ($user['is_active'] && $user['username'] !== $_SESSION['username']): ?>
<div class="user-list-item"><?php echo htmlspecialchars($user['username']); ?></div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<div class="form-group">
<label>邮件主题 *</label>
<input type="text" name="subject" required placeholder="请输入邮件主题" value="<?php echo htmlspecialchars($_POST['subject'] ?? ''); ?>">
</div>
<div class="form-group">
<label>邮件内容 *</label>
<textarea name="body" required placeholder="请输入邮件内容"><?php echo htmlspecialchars($_POST['body'] ?? ''); ?></textarea>
</div>
<button type="submit" name="send_broadcast" class="btn btn-primary">发送群发邮件</button>
</form>
</div>
<script>
function toggleRecipients() {
const broadcastType = document.querySelector('input[name="broadcast_type"]:checked').value;
const recipientsGroup = document.getElementById('recipients-group');
if (broadcastType === 'selected') {
recipientsGroup.style.display = 'block';
} else {
recipientsGroup.style.display = 'none';
}
}
</script>
</body>
</html>
<?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/storage/EmailRepository.php';
require_once __DIR__ . '/../src/admin/BroadcastService.php';
require_once __DIR__ . '/../src/utils/Security.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
// 检查管理员权限
if (!$_SESSION['is_admin']) {
die('权限不足:只有管理员可以访问此页面');
}
$broadcastService = new BroadcastService();
$userRepo = new UserRepository();
$message = '';
$error = '';
// 处理群发邮件
if (isset($_POST['send_broadcast'])) {
$subject = trim($_POST['subject'] ?? '');
$body = trim($_POST['body'] ?? '');
$broadcastType = $_POST['broadcast_type'] ?? 'all';
$recipients = trim($_POST['recipients'] ?? '');
if (empty($subject)) {
$error = "主题不能为空";
} elseif (empty($body)) {
$error = "邮件内容不能为空";
} else {
$senderEmail = $_SESSION['username'];
try {
if ($broadcastType === 'all') {
// 群发给所有用户
$result = $broadcastService->broadcastToAll($senderEmail, $subject, $body);
} else {
// 群发给指定用户
$recipientList = array_filter(array_map('trim', explode(',', $recipients)));
if (empty($recipientList)) {
$error = "请指定收件人";
} else {
$result = $broadcastService->broadcastToUsers($senderEmail, $recipientList, $subject, $body);
}
}
if (isset($result)) {
if ($result['success'] > 0) {
$message = "群发成功!成功发送 {$result['success']} 封邮件";
if ($result['failed'] > 0) {
$message .= ",失败 {$result['failed']} 封";
}
if (!empty($result['errors'])) {
$error = "部分失败:" . implode('<br>', array_slice($result['errors'], 0, 5));
if (count($result['errors']) > 5) {
$error .= "<br>... 还有 " . (count($result['errors']) - 5) . " 个错误";
}
}
} else {
$error = "群发失败:" . implode('<br>', $result['errors']);
}
}
} catch (Exception $e) {
$error = "群发失败: " . $e->getMessage();
}
}
}
// 获取所有用户列表(用于选择收件人)
$allUsers = $userRepo->getAll();
?>
<!DOCTYPE html>
<html>
<head>
<title>群发邮件 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); max-width: 800px; }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 8px; font-weight: 500; }
.form-group input, .form-group textarea, .form-group select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.form-group textarea { min-height: 200px; font-family: monospace; }
.form-group small { color: #666; font-size: 12px; }
.btn { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }
.btn-primary { background: #007bff; color: white; }
.btn-primary:hover { background: #0056b3; }
.radio-group { display: flex; gap: 20px; margin-bottom: 15px; }
.radio-group label { display: flex; align-items: center; cursor: pointer; }
.radio-group input[type="radio"] { width: auto; margin-right: 5px; }
.user-list { max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; border-radius: 4px; background: #f8f9fa; }
.user-list-item { padding: 5px; }
</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>
<a href="users.php">用户管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div>
<div class="container">
<h2>群发邮件</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label>发送方式</label>
<div class="radio-group">
<label>
<input type="radio" name="broadcast_type" value="all" checked onchange="toggleRecipients()">
发送给所有用户
</label>
<label>
<input type="radio" name="broadcast_type" value="selected" onchange="toggleRecipients()">
发送给指定用户
</label>
</div>
</div>
<div class="form-group" id="recipients-group" style="display: none;">
<label>收件人(多个邮箱用逗号分隔)</label>
<input type="text" name="recipients" placeholder="user1@test.com, user2@test.com">
<small>请输入邮箱地址,多个邮箱用逗号分隔</small>
<div class="user-list">
<strong>可用用户列表:</strong>
<?php foreach ($allUsers as $user): ?>
<?php if ($user['is_active'] && $user['username'] !== $_SESSION['username']): ?>
<div class="user-list-item"><?php echo htmlspecialchars($user['username']); ?></div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<div class="form-group">
<label>邮件主题 *</label>
<input type="text" name="subject" required placeholder="请输入邮件主题" value="<?php echo htmlspecialchars($_POST['subject'] ?? ''); ?>">
</div>
<div class="form-group">
<label>邮件内容 *</label>
<textarea name="body" required placeholder="请输入邮件内容"><?php echo htmlspecialchars($_POST['body'] ?? ''); ?></textarea>
</div>
<button type="submit" name="send_broadcast" class="btn btn-primary">发送群发邮件</button>
</form>
</div>
<script>
function toggleRecipients() {
const broadcastType = document.querySelector('input[name="broadcast_type"]:checked').value;
const recipientsGroup = document.getElementById('recipients-group');
if (broadcastType === 'selected') {
recipientsGroup.style.display = 'block';
} else {
recipientsGroup.style.display = 'none';
}
}
</script>
</body>
</html>

@ -1,240 +1,238 @@
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/EmailRepository.php';
require_once __DIR__ . '/../src/utils/Security.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$emailRepo = new EmailRepository();
$message = '';
$error = '';
// 处理删除邮件
if (isset($_GET['delete'])) {
$emailId = (int)$_GET['delete'];
if ($emailRepo->delete($emailId)) {
$message = "邮件删除成功";
} else {
$error = "删除失败";
}
}
// 处理标记已读
if (isset($_GET['mark_read'])) {
$emailId = (int)$_GET['mark_read'];
if ($emailRepo->markAsRead($emailId)) {
$message = "邮件已标记为已读";
}
}
// 获取邮件列表
$isAdmin = $_SESSION['is_admin'] ?? false;
$userId = $_SESSION['user_id'];
// 分页参数
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$perPage = 20;
$offset = ($page - 1) * $perPage;
// 获取邮件
if ($isAdmin) {
$emails = $emailRepo->getAll($perPage, $offset);
$totalEmails = $emailRepo->getCount();
} else {
$emails = $emailRepo->getInbox($userId, $perPage, $offset);
$totalEmails = $emailRepo->getCount($userId);
}
$totalPages = ceil($totalEmails / $perPage);
?>
<!DOCTYPE html>
<html>
<head>
<title>邮件管理 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #f8f9fa; font-weight: 600; }
tr:hover { background: #f8f9fa; }
.btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-primary { background: #007bff; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn-success { background: #28a745; color: white; }
.btn-small { padding: 4px 8px; font-size: 12px; }
.badge { padding: 4px 8px; border-radius: 3px; font-size: 12px; font-weight: 500; }
.badge-read { background: #6c757d; color: white; }
.badge-unread { background: #007bff; color: white; }
.email-unread { font-weight: bold; }
.pagination { margin-top: 20px; text-align: center; }
.pagination a { display: inline-block; padding: 8px 12px; margin: 0 4px; text-decoration: none; border: 1px solid #ddd; border-radius: 4px; }
.pagination a:hover { background: #f8f9fa; }
.pagination .current { background: #007bff; color: white; border-color: #007bff; }
.email-preview { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); overflow: auto; }
.modal-content { background: white; margin: 50px auto; padding: 20px; width: 80%; max-width: 800px; border-radius: 5px; }
.close { float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
.email-body { white-space: pre-wrap; background: #f8f9fa; padding: 15px; border-radius: 5px; margin-top: 10px; }
</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 ($isAdmin): ?>
<a href="users.php">用户管理</a>
<?php endif; ?>
<a href="emails.php">邮件管理</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<?php if ($isAdmin): ?>
<a href="settings.php">系统设置</a>
<?php endif; ?>
</div>
<div class="container">
<h2>邮件管理 <?php if ($isAdmin): ?>(全部邮件)<?php else: ?>(我的收件箱)<?php endif; ?></h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<p><?php echo $totalEmails; ?> 封邮件</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>发件人</th>
<th>收件人</th>
<th>主题</th>
<th>状态</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (empty($emails)): ?>
<tr>
<td colspan="7" style="text-align: center; padding: 40px;">
暂无邮件
</td>
</tr>
<?php else: ?>
<?php foreach ($emails as $email): ?>
<tr class="<?php echo $email['is_read'] ? '' : 'email-unread'; ?>">
<td><?php echo $email['id']; ?></td>
<td><?php echo htmlspecialchars($email['sender_name'] ?? $email['sender'] ?? '未知'); ?></td>
<td><?php echo htmlspecialchars($email['recipient_name'] ?? $email['recipient'] ?? '未知'); ?></td>
<td class="email-preview">
<a href="#" onclick="viewEmail(<?php echo htmlspecialchars(json_encode($email)); ?>); return false;">
<?php echo htmlspecialchars($email['subject'] ?? '(无主题)'); ?>
</a>
</td>
<td>
<?php if ($email['is_read']): ?>
<span class="badge badge-read">已读</span>
<?php else: ?>
<span class="badge badge-unread">未读</span>
<?php endif; ?>
</td>
<td><?php echo $email['created_at']; ?></td>
<td>
<a href="#" onclick="viewEmail(<?php echo htmlspecialchars(json_encode($email)); ?>); return false;" class="btn btn-primary btn-small">查看</a>
<?php if (!$email['is_read']): ?>
<a href="?mark_read=<?php echo $email['id']; ?>" class="btn btn-success btn-small">标记已读</a>
<?php endif; ?>
<a href="?delete=<?php echo $email['id']; ?>" class="btn btn-danger btn-small" onclick="return confirm('确定要删除此邮件吗?');">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<!-- 分页 -->
<?php if ($totalPages > 1): ?>
<div class="pagination">
<?php if ($page > 1): ?>
<a href="?page=<?php echo $page - 1; ?>">上一页</a>
<?php endif; ?>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<?php if ($i == $page): ?>
<span class="current"><?php echo $i; ?></span>
<?php else: ?>
<a href="?page=<?php echo $i; ?>"><?php echo $i; ?></a>
<?php endif; ?>
<?php endfor; ?>
<?php if ($page < $totalPages): ?>
<a href="?page=<?php echo $page + 1; ?>">下一页</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<!-- 查看邮件模态框 -->
<div id="emailModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeEmailModal()">&times;</span>
<h3 id="email-subject">邮件详情</h3>
<div>
<strong>发件人:</strong><span id="email-sender"></span><br>
<strong>收件人:</strong><span id="email-recipient"></span><br>
<strong>时间:</strong><span id="email-time"></span><br>
<strong>主题:</strong><span id="email-subject-text"></span>
</div>
<div class="email-body" id="email-body"></div>
</div>
</div>
<script>
function viewEmail(email) {
document.getElementById('email-subject').textContent = email.subject || '(无主题)';
document.getElementById('email-subject-text').textContent = email.subject || '(无主题)';
document.getElementById('email-sender').textContent = email.sender_name || email.sender || '未知';
document.getElementById('email-recipient').textContent = email.recipient_name || email.recipient || '未知';
document.getElementById('email-time').textContent = email.created_at;
document.getElementById('email-body').textContent = email.body || '(无内容)';
document.getElementById('emailModal').style.display = 'block';
}
function closeEmailModal() {
document.getElementById('emailModal').style.display = 'none';
}
window.onclick = function(event) {
var modal = document.getElementById('emailModal');
if (event.target == modal) {
closeEmailModal();
}
}
</script>
</body>
</html>
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/EmailRepository.php';
require_once __DIR__ . '/../src/utils/Security.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$emailRepo = new EmailRepository();
$message = '';
$error = '';
// 处理删除邮件
if (isset($_GET['delete'])) {
$emailId = (int)$_GET['delete'];
if ($emailRepo->delete($emailId)) {
$message = "邮件删除成功";
} else {
$error = "删除失败";
}
}
// 处理标记已读
if (isset($_GET['mark_read'])) {
$emailId = (int)$_GET['mark_read'];
if ($emailRepo->markAsRead($emailId)) {
$message = "邮件已标记为已读";
}
}
// 获取邮件列表
$isAdmin = $_SESSION['is_admin'] ?? false;
$userId = $_SESSION['user_id'];
// 分页参数
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$perPage = 20;
$offset = ($page - 1) * $perPage;
// 获取邮件
if ($isAdmin) {
$emails = $emailRepo->getAll($perPage, $offset);
$totalEmails = $emailRepo->getCount();
} else {
$emails = $emailRepo->getInbox($userId, $perPage, $offset);
$totalEmails = $emailRepo->getCount($userId);
}
$totalPages = ceil($totalEmails / $perPage);
?>
<!DOCTYPE html>
<html>
<head>
<title>邮件管理 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #f8f9fa; font-weight: 600; }
tr:hover { background: #f8f9fa; }
.btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-primary { background: #007bff; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn-success { background: #28a745; color: white; }
.btn-small { padding: 4px 8px; font-size: 12px; }
.badge { padding: 4px 8px; border-radius: 3px; font-size: 12px; font-weight: 500; }
.badge-read { background: #6c757d; color: white; }
.badge-unread { background: #007bff; color: white; }
.email-unread { font-weight: bold; }
.pagination { margin-top: 20px; text-align: center; }
.pagination a { display: inline-block; padding: 8px 12px; margin: 0 4px; text-decoration: none; border: 1px solid #ddd; border-radius: 4px; }
.pagination a:hover { background: #f8f9fa; }
.pagination .current { background: #007bff; color: white; border-color: #007bff; }
.email-preview { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); overflow: auto; }
.modal-content { background: white; margin: 50px auto; padding: 20px; width: 80%; max-width: 800px; border-radius: 5px; }
.close { float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
.email-body { white-space: pre-wrap; background: #f8f9fa; padding: 15px; border-radius: 5px; margin-top: 10px; }
</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>
<a href="users.php">用户管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div>
<div class="container">
<h2>邮箱管理 <?php if ($isAdmin): ?>(全部邮件)<?php else: ?>(我的收件箱)<?php endif; ?></h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<p><?php echo $totalEmails; ?> 封邮件</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>发件人</th>
<th>收件人</th>
<th>主题</th>
<th>状态</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (empty($emails)): ?>
<tr>
<td colspan="7" style="text-align: center; padding: 40px;">
暂无邮件
</td>
</tr>
<?php else: ?>
<?php foreach ($emails as $email): ?>
<tr class="<?php echo $email['is_read'] ? '' : 'email-unread'; ?>">
<td><?php echo $email['id']; ?></td>
<td><?php echo htmlspecialchars($email['sender_name'] ?? $email['sender'] ?? '未知'); ?></td>
<td><?php echo htmlspecialchars($email['recipient_name'] ?? $email['recipient'] ?? '未知'); ?></td>
<td class="email-preview">
<a href="#" onclick="viewEmail(<?php echo htmlspecialchars(json_encode($email)); ?>); return false;">
<?php echo htmlspecialchars($email['subject'] ?? '(无主题)'); ?>
</a>
</td>
<td>
<?php if ($email['is_read']): ?>
<span class="badge badge-read">已读</span>
<?php else: ?>
<span class="badge badge-unread">未读</span>
<?php endif; ?>
</td>
<td><?php echo $email['created_at']; ?></td>
<td>
<a href="#" onclick="viewEmail(<?php echo htmlspecialchars(json_encode($email)); ?>); return false;" class="btn btn-primary btn-small">查看</a>
<?php if (!$email['is_read']): ?>
<a href="?mark_read=<?php echo $email['id']; ?>" class="btn btn-success btn-small">标记已读</a>
<?php endif; ?>
<a href="?delete=<?php echo $email['id']; ?>" class="btn btn-danger btn-small" onclick="return confirm('确定要删除此邮件吗?');">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<!-- 分页 -->
<?php if ($totalPages > 1): ?>
<div class="pagination">
<?php if ($page > 1): ?>
<a href="?page=<?php echo $page - 1; ?>">上一页</a>
<?php endif; ?>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<?php if ($i == $page): ?>
<span class="current"><?php echo $i; ?></span>
<?php else: ?>
<a href="?page=<?php echo $i; ?>"><?php echo $i; ?></a>
<?php endif; ?>
<?php endfor; ?>
<?php if ($page < $totalPages): ?>
<a href="?page=<?php echo $page + 1; ?>">下一页</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<!-- 查看邮件模态框 -->
<div id="emailModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeEmailModal()">&times;</span>
<h3 id="email-subject">邮件详情</h3>
<div>
<strong>发件人:</strong><span id="email-sender"></span><br>
<strong>收件人:</strong><span id="email-recipient"></span><br>
<strong>时间:</strong><span id="email-time"></span><br>
<strong>主题:</strong><span id="email-subject-text"></span>
</div>
<div class="email-body" id="email-body"></div>
</div>
</div>
<script>
function viewEmail(email) {
document.getElementById('email-subject').textContent = email.subject || '(无主题)';
document.getElementById('email-subject-text').textContent = email.subject || '(无主题)';
document.getElementById('email-sender').textContent = email.sender_name || email.sender || '未知';
document.getElementById('email-recipient').textContent = email.recipient_name || email.recipient || '未知';
document.getElementById('email-time').textContent = email.created_at;
document.getElementById('email-body').textContent = email.body || '(无内容)';
document.getElementById('emailModal').style.display = 'block';
}
function closeEmailModal() {
document.getElementById('emailModal').style.display = 'none';
}
window.onclick = function(event) {
var modal = document.getElementById('emailModal');
if (event.target == modal) {
closeEmailModal();
}
}
</script>
</body>
</html>

@ -1,243 +1,257 @@
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/FilterRepository.php';
require_once __DIR__ . '/../src/utils/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$filterRepo = new FilterRepository();
$message = '';
$error = '';
// 处理创建过滤规则
if (isset($_POST['create_filter'])) {
$ruleType = $_POST['rule_type'] ?? '';
$ruleValue = trim($_POST['rule_value'] ?? '');
$action = $_POST['action'] ?? 'block';
$description = trim($_POST['description'] ?? '');
if (empty($ruleValue)) {
$error = "规则值不能为空";
} else {
if ($ruleType === 'email') {
if (!Validator::validateEmail($ruleValue)) {
$error = "邮箱格式无效";
}
} elseif ($ruleType === 'ip') {
if (!Validator::validateIP($ruleValue)) {
$error = "IP地址格式无效";
}
} else {
$error = "规则类型无效";
}
if (empty($error)) {
try {
if ($filterRepo->create($ruleType, $ruleValue, $action, $description)) {
$message = "过滤规则创建成功";
} else {
$error = "创建失败,可能已存在相同规则";
}
} catch (Exception $e) {
$error = "创建失败: " . $e->getMessage();
}
}
}
}
// 处理删除规则
if (isset($_GET['delete'])) {
$id = (int)$_GET['delete'];
if ($filterRepo->delete($id)) {
$message = "规则删除成功";
} else {
$error = "删除失败";
}
}
// 处理切换规则状态
if (isset($_GET['toggle'])) {
$id = (int)$_GET['toggle'];
$rule = $filterRepo->getAll();
$currentRule = null;
foreach ($rule as $r) {
if ($r['id'] == $id) {
$currentRule = $r;
break;
}
}
if ($currentRule) {
$newStatus = !$currentRule['is_active'];
if ($filterRepo->updateStatus($id, $newStatus)) {
$message = "规则状态已更新";
} else {
$error = "更新失败";
}
}
}
// 获取所有规则
$rules = $filterRepo->getAll();
?>
<!DOCTYPE html>
<html>
<head>
<title>过滤规则 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: 500; }
.form-group input, .form-group select, .form-group textarea { width: 100%; max-width: 500px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.form-inline { display: flex; gap: 10px; align-items: flex-end; }
.form-inline .form-group { flex: 1; margin-bottom: 0; }
.btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-primary { background: #007bff; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn-success { background: #28a745; color: white; }
.btn-warning { background: #ffc107; color: #000; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #f8f9fa; }
.badge { padding: 4px 8px; border-radius: 3px; font-size: 12px; font-weight: 500; }
.badge-email { background: #17a2b8; color: white; }
.badge-ip { background: #6c757d; color: white; }
.badge-block { background: #dc3545; color: white; }
.badge-allow { background: #28a745; color: white; }
.badge-active { background: #28a745; color: white; }
.badge-inactive { background: #6c757d; color: white; }
</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'] ?? false): ?>
<a href="users.php">用户管理</a>
<?php endif; ?>
<a href="emails.php">邮件管理</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="settings.php">系统设置</a>
<?php endif; ?>
</div>
<div class="container">
<h2>过滤规则管理</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<!-- 创建过滤规则 -->
<h3>创建过滤规则</h3>
<form method="POST" class="form-inline">
<div class="form-group">
<label>规则类型</label>
<select name="rule_type" required>
<option value="email">邮箱过滤</option>
<option value="ip">IP地址过滤</option>
</select>
</div>
<div class="form-group">
<label>规则值</label>
<input type="text" name="rule_value" placeholder="邮箱或IP地址" required>
</div>
<div class="form-group">
<label>动作</label>
<select name="action" required>
<option value="block">阻止</option>
<option value="allow">允许</option>
</select>
</div>
<div class="form-group">
<label>描述</label>
<input type="text" name="description" placeholder="规则描述(可选)">
</div>
<div class="form-group">
<button type="submit" name="create_filter" class="btn btn-primary">创建规则</button>
</div>
</form>
<!-- 规则列表 -->
<h3>过滤规则列表 (<?php echo count($rules); ?>)</h3>
<table>
<thead>
<tr>
<th>ID</th>
<th>类型</th>
<th>规则值</th>
<th>动作</th>
<th>描述</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (empty($rules)): ?>
<tr>
<td colspan="8" style="text-align: center; padding: 40px;">暂无过滤规则</td>
</tr>
<?php else: ?>
<?php foreach ($rules as $rule): ?>
<tr>
<td><?php echo $rule['id']; ?></td>
<td>
<span class="badge badge-<?php echo $rule['rule_type']; ?>">
<?php echo $rule['rule_type'] === 'email' ? '邮箱' : 'IP'; ?>
</span>
</td>
<td><?php echo htmlspecialchars($rule['rule_value']); ?></td>
<td>
<span class="badge badge-<?php echo $rule['action']; ?>">
<?php echo $rule['action'] === 'block' ? '阻止' : '允许'; ?>
</span>
</td>
<td><?php echo htmlspecialchars($rule['description'] ?? '-'); ?></td>
<td>
<span class="badge badge-<?php echo $rule['is_active'] ? 'active' : 'inactive'; ?>">
<?php echo $rule['is_active'] ? '激活' : '禁用'; ?>
</span>
</td>
<td><?php echo $rule['created_at']; ?></td>
<td>
<a href="?toggle=<?php echo $rule['id']; ?>" class="btn btn-warning">
<?php echo $rule['is_active'] ? '禁用' : '启用'; ?>
</a>
<a href="?delete=<?php echo $rule['id']; ?>" class="btn btn-danger" onclick="return confirm('确定要删除此规则吗?');">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</body>
</html>
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/FilterRepository.php';
require_once __DIR__ . '/../src/utils/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php';
//开启所有错误日志报告
error_reporting(E_ALL);
ini_set('display_errors', 1);
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$filterRepo = new FilterRepository();
$message = '';
$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'])) {
$ruleType = $_POST['rule_type'] ?? '';
$ruleValue = trim($_POST['rule_value'] ?? '');
$action = $_POST['action'] ?? 'block';
$description = trim($_POST['description'] ?? '');
if (empty($ruleValue)) {
$error = "规则值不能为空";
} else {
if ($ruleType === 'email') {
if (!Validator::validateEmail($ruleValue)) {
$error = "邮箱格式无效";
}
} elseif ($ruleType === 'ip') {
if (!Validator::validateIP($ruleValue)) {
$error = "IP地址格式无效";
}
} else {
$error = "规则类型无效";
}
if (empty($error)) {
try {
if ($filterRepo->create($ruleType, $ruleValue, $action, $description)) {
$message = "过滤规则创建成功";
} else {
$error = "创建失败,可能已存在相同规则";
}
} catch (Exception $e) {
$error = "创建失败: " . $e->getMessage();
}
}
}
}
// 处理删除规则
if (isset($_GET['delete'])) {
$id = (int)$_GET['delete'];
if ($filterRepo->delete($id)) {
$message = "规则删除成功";
} else {
$error = "删除失败";
}
}
/*------------------------------------
// 处理切换规则状态
if (isset($_GET['toggle'])) {
$id = (int)$_GET['toggle'];
$row = $filterRepo->getById($id); // 改用 public 方法
if ($row) {
$newStatus = !(bool)$row['is_active'];
if ($filterRepo->updateStatus($id, $newStatus)) {
$message = "规则状态已更新";
} else {
$error = "更新失败";
}
} else {
$error = "规则不存在";
}
}
------------------------------------ */
// 获取所有规则
$rules = $filterRepo->getAll();
?>
<!DOCTYPE html>
<html>
<head>
<title>过滤规则 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: 500; }
.form-group input, .form-group select, .form-group textarea { width: 100%; max-width: 500px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.form-inline { display: flex; gap: 10px; align-items: flex-end; }
.form-inline .form-group { flex: 1; margin-bottom: 0; }
.btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-primary { background: #007bff; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn-success { background: #28a745; color: white; }
.btn-warning { background: #ffc107; color: #000; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #f8f9fa; }
.badge { padding: 4px 8px; border-radius: 3px; font-size: 12px; font-weight: 500; }
.badge-email { background: #17a2b8; color: white; }
.badge-ip { background: #6c757d; color: white; }
.badge-block { background: #dc3545; color: white; }
.badge-allow { background: #28a745; color: white; }
.badge-active { background: #28a745; color: white; }
.badge-inactive { background: #6c757d; color: white; }
</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>
<a href="users.php">用户管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div>
<div class="container">
<h2>过滤规则管理</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<!-- 创建过滤规则 -->
<h3>创建过滤规则</h3>
<form method="POST" class="form-inline">
<div class="form-group">
<label>规则类型</label>
<select name="rule_type" required>
<option value="email">邮箱过滤</option>
<option value="ip">IP地址过滤</option>
</select>
</div>
<div class="form-group">
<label>规则值</label>
<input type="text" name="rule_value" placeholder="邮箱或IP地址" required>
</div>
<div class="form-group">
<label>动作</label>
<select name="action" required>
<option value="block">阻止</option>
<option value="allow">允许</option>
</select>
</div>
<div class="form-group">
<label>描述</label>
<input type="text" name="description" placeholder="规则描述(可选)">
</div>
<div class="form-group">
<button type="submit" name="create_filter" class="btn btn-primary">创建规则</button>
</div>
</form>
<!-- 规则列表 -->
<h3>过滤规则列表 (<?php echo count($rules); ?>)</h3>
<table>
<thead>
<tr>
<th>ID</th>
<th>类型</th>
<th>规则值</th>
<th>动作</th>
<th>描述</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (empty($rules)): ?>
<tr>
<td colspan="8" style="text-align: center; padding: 40px;">暂无过滤规则</td>
</tr>
<?php else: ?>
<?php foreach ($rules as $rule): ?>
<tr>
<td><?php echo $rule['id']; ?></td>
<td>
<span class="badge badge-<?php echo $rule['rule_type']; ?>">
<?php echo $rule['rule_type'] === 'email' ? '邮箱' : 'IP'; ?>
</span>
</td>
<td><?php echo htmlspecialchars($rule['rule_value']); ?></td>
<td>
<span class="badge badge-<?php echo $rule['action']; ?>">
<?php echo $rule['action'] === 'block' ? '阻止' : '允许'; ?>
</span>
</td>
<td><?php echo htmlspecialchars($rule['description'] ?? '-'); ?></td>
<td>
<span class="badge badge-<?php echo $rule['is_active'] ? 'active' : 'inactive'; ?>">
<?php echo $rule['is_active'] ? '激活' : '禁用'; ?>
</span>
</td>
<td><?php echo $rule['created_at']; ?></td>
<td>
<form method="post" style="display:inline;">
<input type="hidden" name="toggle_id" value="<?php echo $rule['id']; ?>">
<button type="submit" class="btn btn-warning">
<?php echo $rule['is_active'] ? '禁用' : '启用'; ?>
</button>
</form>
<a href="?delete=<?php echo $rule['id']; ?>" class="btn btn-danger" onclick="return confirm('确定要删除此规则吗?');">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</body>
</html>

@ -1,196 +1,186 @@
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
?>
<!DOCTYPE html>
<html>
<head>
<title>帮助 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); max-width: 900px; }
.section { margin-bottom: 30px; }
.section h3 { color: #007bff; border-bottom: 2px solid #007bff; padding-bottom: 10px; }
.section h4 { color: #333; margin-top: 20px; }
.code-block { background: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 4px solid #007bff; margin: 10px 0; font-family: monospace; }
ul, ol { line-height: 1.8; }
.highlight { background: #fff3cd; padding: 2px 4px; border-radius: 3px; }
</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'] ?? false): ?>
<a href="users.php">用户管理</a>
<?php endif; ?>
<a href="emails.php">邮件管理</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="settings.php">系统设置</a>
<?php endif; ?>
<a href="help.php">帮助</a>
</div>
<div class="container">
<h2>使用帮助</h2>
<div class="section">
<h3>系统概述</h3>
<p>这是一个基于POP3和SMTP协议的邮件服务器管理系统支持用户注册、邮件收发、系统管理等功能。</p>
</div>
<div class="section">
<h3>功能模块</h3>
<h4>1. 用户管理(管理员功能)</h4>
<ul>
<li><strong>创建用户:</strong>可以创建新的用户账号,设置密码、管理员权限和激活状态</li>
<li><strong>编辑用户:</strong>可以修改用户密码、权限和状态</li>
<li><strong>删除用户:</strong>可以删除用户账号(不能删除自己)</li>
<li><strong>用户列表:</strong>查看所有注册用户及其状态</li>
</ul>
<h4>2. 邮件管理</h4>
<ul>
<li><strong>查看邮件:</strong>管理员可以查看所有邮件,普通用户只能查看自己的收件箱</li>
<li><strong>标记已读:</strong>将未读邮件标记为已读</li>
<li><strong>删除邮件:</strong>删除不需要的邮件(软删除)</li>
<li><strong>邮件详情:</strong>点击邮件主题查看完整内容</li>
</ul>
<h4>3. 群发邮件(管理员功能)</h4>
<ul>
<li><strong>发送给所有用户:</strong>可以一次性向所有激活用户发送通知邮件</li>
<li><strong>发送给指定用户:</strong>可以选择特定用户进行群发</li>
<li><strong>邮件内容:</strong>支持自定义主题和内容</li>
</ul>
<h4>4. 过滤规则</h4>
<ul>
<li><strong>邮箱过滤:</strong>可以阻止或允许特定邮箱地址</li>
<li><strong>IP过滤</strong>可以阻止或允许特定IP地址</li>
<li><strong>规则管理:</strong>可以启用、禁用或删除过滤规则</li>
</ul>
<h4>5. 系统设置(管理员功能)</h4>
<ul>
<li><strong>端口设置:</strong>配置SMTP端口默认25和POP3端口默认110</li>
<li><strong>域名设置:</strong>设置邮件服务器域名默认test.com</li>
<li><strong>邮箱管理:</strong>设置用户邮箱大小限制</li>
<li><strong>日志设置:</strong>配置日志存储路径和最大大小</li>
<li><strong>密码修改:</strong>管理员可以修改自己的密码</li>
</ul>
<h4>6. 服务管理(管理员功能)</h4>
<ul>
<li><strong>SMTP服务</strong>查看和管理SMTP服务状态</li>
<li><strong>POP3服务</strong>查看和管理POP3服务状态</li>
<li><strong>服务起停:</strong>启动或停止邮件服务</li>
</ul>
<h4>7. 日志管理</h4>
<ul>
<li><strong>查看日志:</strong>查看SMTP和POP3服务器日志</li>
<li><strong>日志过滤:</strong>按类型过滤日志(全部/SMTP/POP3</li>
<li><strong>清除日志:</strong>管理员可以清除日志记录</li>
</ul>
</div>
<div class="section">
<h3>启动服务器</h3>
<p>要启动邮件服务器,需要在命令行执行以下命令:</p>
<div class="code-block">
# 启动SMTP服务器需要sudo权限<br>
sudo php scripts/start_smtp.php<br><br>
# 启动POP3服务器需要sudo权限<br>
sudo php scripts/start_pop3.php
</div>
<p><span class="highlight">注意:</span>两个服务器需要分别在两个终端运行。</p>
</div>
<div class="section">
<h3>测试邮件服务器</h3>
<h4>测试SMTP发送邮件</h4>
<div class="code-block">
telnet localhost 25<br>
HELO test<br>
MAIL FROM: &lt;user1@test.com&gt;<br>
RCPT TO: &lt;admin@test.com&gt;<br>
DATA<br>
Subject: 测试邮件<br>
From: user1@test.com<br>
To: admin@test.com<br><br>
这是一封测试邮件!<br>
.<br>
QUIT
</div>
<h4>测试POP3接收邮件</h4>
<div class="code-block">
telnet localhost 110<br>
USER admin@test.com<br>
PASS 123456<br>
STAT<br>
LIST<br>
RETR 1<br>
QUIT
</div>
</div>
<div class="section">
<h3>常见问题</h3>
<h4>Q: 端口被占用怎么办?</h4>
<p>A: 检查端口占用情况:</p>
<div class="code-block">
sudo netstat -tlnp | grep 25 # 检查SMTP端口<br>
sudo netstat -tlnp | grep 110 # 检查POP3端口
</div>
<h4>Q: 数据库连接失败?</h4>
<p>A: 确保Docker容器正在运行</p>
<div class="code-block">
docker-compose ps<br>
docker-compose up -d mysql
</div>
<h4>Q: 如何重置数据库?</h4>
<p>A: 执行以下命令:</p>
<div class="code-block">
docker-compose down -v<br>
docker-compose up -d<br>
sleep 15
</div>
</div>
<div class="section">
<h3>默认账号</h3>
<ul>
<li><strong>管理员:</strong>admin@test.com / 123456</li>
<li><strong>普通用户:</strong>user1@test.com / 123456</li>
</ul>
</div>
</div>
</body>
</html>
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
?>
<!DOCTYPE html>
<html>
<head>
<title>帮助 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); max-width: 900px; }
.section { margin-bottom: 30px; }
.section h3 { color: #007bff; border-bottom: 2px solid #007bff; padding-bottom: 10px; }
.section h4 { color: #333; margin-top: 20px; }
.code-block { background: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 4px solid #007bff; margin: 10px 0; font-family: monospace; }
ul, ol { line-height: 1.8; }
.highlight { background: #fff3cd; padding: 2px 4px; border-radius: 3px; }
</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>
<a href="users.php">用户管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div>
<div class="container">
<h2>使用帮助</h2>
<div class="section">
<h3>系统概述</h3>
<p>这是一个基于POP3和SMTP协议的邮件服务器管理系统支持用户注册、邮件收发、系统管理等功能。</p>
</div>
<div class="section">
<h3>功能模块</h3>
<h4>1. 用户管理</h4>
<ul>
<li><strong>创建用户:</strong>可以创建新的用户账号,设置密码、管理员权限和激活状态</li>
<li><strong>编辑用户:</strong>可以修改用户密码、权限和状态</li>
<li><strong>删除用户:</strong>可以删除用户账号(不能删除自己)</li>
<li><strong>用户列表:</strong>查看所有注册用户及其状态</li>
</ul>
<h4>2. 群发邮件</h4>
<ul>
<li><strong>发送给所有用户:</strong>可以一次性向所有激活用户发送通知邮件</li>
<li><strong>发送给指定用户:</strong>可以选择特定用户进行群发</li>
<li><strong>邮件内容:</strong>支持自定义主题和内容</li>
</ul>
<h4>3. 过滤规则</h4>
<ul>
<li><strong>邮箱过滤:</strong>可以阻止或允许特定邮箱地址</li>
<li><strong>IP过滤</strong>可以阻止或允许特定IP地址</li>
<li><strong>规则管理:</strong>可以启用、禁用或删除过滤规则</li>
</ul>
<h4>4. 系统设置</h4>
<ul>
<li><strong>端口设置:</strong>配置SMTP端口默认25和POP3端口默认110</li>
<li><strong>域名设置:</strong>设置邮件服务器域名默认test.com</li>
<li><strong>邮箱管理:</strong>设置用户邮箱大小限制</li>
<li><strong>日志设置:</strong>配置日志存储路径和最大大小</li>
<li><strong>密码修改:</strong>管理员可以修改自己的密码</li>
</ul>
<h4>5. 服务管理</h4>
<ul>
<li><strong>SMTP服务</strong>查看和管理SMTP服务状态</li>
<li><strong>POP3服务</strong>查看和管理POP3服务状态</li>
<li><strong>服务起停:</strong>启动或停止邮件服务</li>
</ul>
<h4>6. 日志管理</h4>
<ul>
<li><strong>查看日志:</strong>查看SMTP和POP3服务器日志</li>
<li><strong>日志过滤:</strong>按类型过滤日志(全部/SMTP/POP3</li>
<li><strong>清除日志:</strong>管理员可以清除日志记录</li>
</ul>
</div>
<div class="section">
<h3>启动服务器</h3>
<p>要启动邮件服务器,需要在命令行执行以下命令:</p>
<div class="code-block">
# 启动SMTP服务器需要sudo权限<br>
sudo php scripts/start_smtp.php<br><br>
# 启动POP3服务器需要sudo权限<br>
sudo php scripts/start_pop3.php
</div>
<p><span class="highlight">注意:</span>两个服务器需要分别在两个终端运行。</p>
</div>
<div class="section">
<h3>测试邮件服务器</h3>
<h4>测试SMTP发送邮件</h4>
<div class="code-block">
telnet localhost 25<br>
HELO test<br>
MAIL FROM: &lt;user1@test.com&gt;<br>
RCPT TO: &lt;admin@test.com&gt;<br>
DATA<br>
Subject: 测试邮件<br>
From: user1@test.com<br>
To: admin@test.com<br><br>
这是一封测试邮件!<br>
.<br>
QUIT
</div>
<h4>测试POP3接收邮件</h4>
<div class="code-block">
telnet localhost 110<br>
USER admin@test.com<br>
PASS 123456<br>
STAT<br>
LIST<br>
RETR 1<br>
QUIT
</div>
</div>
<div class="section">
<h3>常见问题</h3>
<h4>Q: 端口被占用怎么办?</h4>
<p>A: 检查端口占用情况:</p>
<div class="code-block">
sudo netstat -tlnp | grep 25 # 检查SMTP端口<br>
sudo netstat -tlnp | grep 110 # 检查POP3端口
</div>
<h4>Q: 数据库连接失败?</h4>
<p>A: 确保Docker容器正在运行</p>
<div class="code-block">
docker-compose ps<br>
docker-compose up -d mysql
</div>
<h4>Q: 如何重置数据库?</h4>
<p>A: 执行以下命令:</p>
<div class="code-block">
docker-compose down -v<br>
docker-compose up -d<br>
sleep 15
</div>
</div>
<div class="section">
<h3>默认账号</h3>
<ul>
<li><strong>管理员:</strong>admin@test.com / 123456</li>
<li><strong>普通用户:</strong>user1@test.com / 123456</li>
</ul>
</div>
</div>
</body>
</html>

@ -1,214 +1,217 @@
<?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();
// 简单身份验证
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']) {
// 登录成功,清除尝试记录
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>
<head>
<title>邮件服务器管理后台 - 登录</title>
<style>
body { font-family: Arial, sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; }
.login-box { border: 1px solid #ddd; padding: 20px; border-radius: 5px; }
input { width: 100%; padding: 8px; margin: 5px 0 15px 0; }
button { background: #007bff; color: white; padding: 10px; border: none; width: 100%; }
.error { color: red; margin-bottom: 15px; }
</style>
</head>
<body>
<div class="login-box">
<h2>邮件服务器管理后台</h2>
<?php if (isset($error)) echo "<div class='error'>$error</div>"; ?>
<form method="POST">
<div>
<label>用户名:</label>
<input type="text" name="username" value="admin@test.com" required>
</div>
<div>
<label>密码:</label>
<input type="password" name="password" value="123456" required>
</div>
<button type="submit" name="login">登录</button>
</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;">
测试账号: admin@test.com / 123456<br>
普通账号: user1@test.com / 123456
</p>
</div>
</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>
<?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'])) {
if (basename($_SERVER['PHP_SELF']) !== 'index.php') {
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 if($user && !$user['is_active']){
$error = "用户被禁用";
}else if($user && $user['is_active'] && !$user['is_admin']){
$error = "没有权限";
}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>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>邮件服务器管理后台 - 登录</title>
<style>
body { font-family: Arial, sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; }
.login-box { border: 1px solid #ddd; padding: 20px; border-radius: 5px; }
input { width: 100%; padding: 8px; margin: 5px 0 15px 0; }
button { background: #007bff; color: white; padding: 10px; border: none; width: 100%; }
.error { color: red; margin-bottom: 15px; }
</style>
</head>
<body>
<div class="login-box">
<h2>邮件服务器管理后台</h2>
<?php if (isset($error)) echo "<div class='error'>$error</div>"; ?>
<form method="POST">
<div>
<label>用户名:</label>
<input type="text" name="username" value="admin@test.com" required>
</div>
<div>
<label>密码:</label>
<input type="password" name="password" value="123456" required>
</div>
<button type="submit" name="login">登录</button>
</form>
<p style="margin-top: 10px; font-size: 12px; color: #666;">
测试账号: admin@test.com / 123456<br>
普通账号: user1@test.com / 123456
</p>
</div>
</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?>
<a href="users.php">用户管理</a>
<?php ?>
<a href="broadcast.php">群发邮件</a>
<?php ?>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<?php ?>
<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>

@ -0,0 +1,428 @@
<?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>

@ -1,18 +1,18 @@
<?php
session_start();
// 清除所有会话数据
$_SESSION = array();
// 销毁会话cookie
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time()-3600, '/');
}
// 销毁会话
session_destroy();
// 重定向到登录页面
header('Location: index.php');
exit;
<?php
session_start();
// 清除所有会话数据
$_SESSION = array();
// 销毁会话cookie
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time()-3600, '/');
}
// 销毁会话
session_destroy();
// 重定向到登录页面
header('Location: index.php');
exit;

@ -1,256 +1,257 @@
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$db = Database::getInstance();
$settingsRepo = new SystemSettingsRepository();
$message = '';
$error = '';
// 处理清除日志
if (isset($_GET['clear_logs'])) {
$logType = $_GET['clear_logs'] ?? '';
if ($logType === 'all') {
$stmt = $db->prepare("DELETE FROM server_logs");
$stmt->execute();
$message = "所有日志已清除";
} elseif ($logType === 'smtp') {
$stmt = $db->prepare("DELETE FROM server_logs WHERE log_type = 'SMTP'");
$stmt->execute();
$message = "SMTP日志已清除";
} elseif ($logType === 'pop3') {
$stmt = $db->prepare("DELETE FROM server_logs WHERE log_type = 'POP3'");
$stmt->execute();
$message = "POP3日志已清除";
}
}
// 获取日志统计
$stmt = $db->query("SELECT COUNT(*) as count FROM server_logs");
$totalLogs = $stmt->fetch()['count'];
$stmt = $db->query("SELECT COUNT(*) as count FROM server_logs WHERE log_type = 'SMTP'");
$smtpLogs = $stmt->fetch()['count'];
$stmt = $db->query("SELECT COUNT(*) as count FROM server_logs WHERE log_type = 'POP3'");
$pop3Logs = $stmt->fetch()['count'];
// 分页参数
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$perPage = 50;
$offset = ($page - 1) * $perPage;
// 过滤参数
$filterType = $_GET['type'] ?? 'all';
// 获取日志列表
if ($filterType === 'smtp') {
$stmt = $db->prepare("
SELECT l.*, u.username
FROM server_logs l
LEFT JOIN users u ON l.user_id = u.id
WHERE l.log_type = 'SMTP'
ORDER BY l.created_at DESC
LIMIT ? OFFSET ?
");
$stmt->execute([$perPage, $offset]);
$countStmt = $db->prepare("SELECT COUNT(*) as count FROM server_logs WHERE log_type = 'SMTP'");
$countStmt->execute();
$totalLogs = $countStmt->fetch()['count'];
} elseif ($filterType === 'pop3') {
$stmt = $db->prepare("
SELECT l.*, u.username
FROM server_logs l
LEFT JOIN users u ON l.user_id = u.id
WHERE l.log_type = 'POP3'
ORDER BY l.created_at DESC
LIMIT ? OFFSET ?
");
$stmt->execute([$perPage, $offset]);
$countStmt = $db->prepare("SELECT COUNT(*) as count FROM server_logs WHERE log_type = 'POP3'");
$countStmt->execute();
$totalLogs = $countStmt->fetch()['count'];
} else {
$stmt = $db->prepare("
SELECT l.*, u.username
FROM server_logs l
LEFT JOIN users u ON l.user_id = u.id
ORDER BY l.created_at DESC
LIMIT ? OFFSET ?
");
$stmt->execute([$perPage, $offset]);
}
$logs = $stmt->fetchAll();
$totalPages = ceil($totalLogs / $perPage);
// 获取日志设置
$logPath = $settingsRepo->get('log_path', '/var/log/mailserver');
$logMaxSize = $settingsRepo->get('log_max_size', 10485760);
?>
<!DOCTYPE html>
<html>
<head>
<title>系统日志 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 20px; }
.stat-box { border: 1px solid #ddd; padding: 15px; text-align: center; border-radius: 5px; }
.stat-box h3 { margin: 0; font-size: 24px; color: #007bff; }
.stat-box p { margin: 5px 0 0 0; color: #666; }
.filters { margin-bottom: 20px; }
.filters a { display: inline-block; padding: 8px 16px; margin-right: 10px; text-decoration: none; border: 1px solid #ddd; border-radius: 4px; }
.filters a.active { background: #007bff; color: white; border-color: #007bff; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #f8f9fa; }
.btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-danger { background: #dc3545; color: white; }
.pagination { margin-top: 20px; text-align: center; }
.pagination a { display: inline-block; padding: 8px 12px; margin: 0 4px; text-decoration: none; border: 1px solid #ddd; border-radius: 4px; }
.pagination a:hover { background: #f8f9fa; }
.pagination .current { background: #007bff; color: white; border-color: #007bff; }
.log-info { background: #f8f9fa; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
</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'] ?? false): ?>
<a href="users.php">用户管理</a>
<?php endif; ?>
<a href="emails.php">邮件管理</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="settings.php">系统设置</a>
<?php endif; ?>
</div>
<div class="container">
<h2>系统日志管理</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<!-- 日志统计 -->
<div class="stats">
<div class="stat-box">
<h3><?php echo $totalLogs; ?></h3>
<p>总日志数</p>
</div>
<div class="stat-box">
<h3><?php echo $smtpLogs; ?></h3>
<p>SMTP日志</p>
</div>
<div class="stat-box">
<h3><?php echo $pop3Logs; ?></h3>
<p>POP3日志</p>
</div>
</div>
<!-- 日志信息 -->
<div class="log-info">
<strong>日志存储路径:</strong><?php echo htmlspecialchars($logPath); ?><br>
<strong>日志文件最大大小:</strong><?php echo round($logMaxSize / 1048576, 2); ?> MB
</div>
<!-- 过滤和操作 -->
<div class="filters">
<a href="?type=all" class="<?php echo $filterType === 'all' ? 'active' : ''; ?>">全部</a>
<a href="?type=smtp" class="<?php echo $filterType === 'smtp' ? 'active' : ''; ?>">SMTP日志</a>
<a href="?type=pop3" class="<?php echo $filterType === 'pop3' ? 'active' : ''; ?>">POP3日志</a>
<?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="?clear_logs=smtp" class="btn btn-danger" onclick="return confirm('确定要清除SMTP日志吗');">清除SMTP日志</a>
<a href="?clear_logs=pop3" class="btn btn-danger" onclick="return confirm('确定要清除POP3日志吗');">清除POP3日志</a>
<a href="?clear_logs=all" class="btn btn-danger" onclick="return confirm('确定要清除所有日志吗?');">清除所有日志</a>
<?php endif; ?>
</div>
<!-- 日志列表 -->
<table>
<thead>
<tr>
<th>ID</th>
<th>类型</th>
<th>消息</th>
<th>用户</th>
<th>IP地址</th>
<th>时间</th>
</tr>
</thead>
<tbody>
<?php if (empty($logs)): ?>
<tr>
<td colspan="6" style="text-align: center; padding: 40px;">暂无日志</td>
</tr>
<?php else: ?>
<?php foreach ($logs as $log): ?>
<tr>
<td><?php echo $log['id']; ?></td>
<td><?php echo htmlspecialchars($log['log_type'] ?? '-'); ?></td>
<td><?php echo htmlspecialchars($log['message'] ?? '-'); ?></td>
<td><?php echo htmlspecialchars($log['username'] ?? '-'); ?></td>
<td><?php echo htmlspecialchars($log['client_ip'] ?? '-'); ?></td>
<td><?php echo $log['created_at']; ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<!-- 分页 -->
<?php if ($totalPages > 1): ?>
<div class="pagination">
<?php if ($page > 1): ?>
<a href="?page=<?php echo $page - 1; ?>&type=<?php echo $filterType; ?>">上一页</a>
<?php endif; ?>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<?php if ($i == $page): ?>
<span class="current"><?php echo $i; ?></span>
<?php else: ?>
<a href="?page=<?php echo $i; ?>&type=<?php echo $filterType; ?>"><?php echo $i; ?></a>
<?php endif; ?>
<?php endfor; ?>
<?php if ($page < $totalPages): ?>
<a href="?page=<?php echo $page + 1; ?>&type=<?php echo $filterType; ?>">下一页</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</body>
</html>
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php';
//
error_reporting(E_ALL);
ini_set('display_errors', 1);
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$db = Database::getInstance();
$settingsRepo = new SystemSettingsRepository();
$message = '';
$error = '';
// 处理清除日志
if (isset($_GET['clear_logs'])) {
$logType = $_GET['clear_logs'] ?? '';
if ($logType === 'all') {
$stmt = $db->prepare("DELETE FROM server_logs");
$stmt->execute();
$message = "所有日志已清除";
} elseif ($logType === 'smtp') {
$stmt = $db->prepare("DELETE FROM server_logs WHERE log_type = 'SMTP'");
$stmt->execute();
$message = "SMTP日志已清除";
} elseif ($logType === 'pop3') {
$stmt = $db->prepare("DELETE FROM server_logs WHERE log_type = 'POP3'");
$stmt->execute();
$message = "POP3日志已清除";
}
}
// 获取日志统计
$stmt = $db->query("SELECT COUNT(*) as count FROM server_logs");
$totalLogs = $stmt->fetch()['count'];
$stmt = $db->query("SELECT COUNT(*) as count FROM server_logs WHERE log_type = 'SMTP'");
$smtpLogs = $stmt->fetch()['count'];
$stmt = $db->query("SELECT COUNT(*) as count FROM server_logs WHERE log_type = 'POP3'");
$pop3Logs = $stmt->fetch()['count'];
// 分页参数
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$perPage = 50;
$offset = ($page - 1) * $perPage;
// 过滤参数
$filterType = $_GET['type'] ?? 'all';
// 获取日志列表
if ($filterType === 'smtp') {
$stmt = $db->prepare("
SELECT l.*, u.username
FROM server_logs l
LEFT JOIN users u ON l.user_id = u.id
WHERE l.log_type = 'SMTP'
ORDER BY l.created_at DESC
LIMIT ? OFFSET ?
");
$stmt->execute([$perPage, $offset]);
$countStmt = $db->prepare("SELECT COUNT(*) as count FROM server_logs WHERE log_type = 'SMTP'");
$countStmt->execute();
$totalLogs = $countStmt->fetch()['count'];
} elseif ($filterType === 'pop3') {
$stmt = $db->prepare("
SELECT l.*, u.username
FROM server_logs l
LEFT JOIN users u ON l.user_id = u.id
WHERE l.log_type = 'POP3'
ORDER BY l.created_at DESC
LIMIT ? OFFSET ?
");
$stmt->execute([$perPage, $offset]);
$countStmt = $db->prepare("SELECT COUNT(*) as count FROM server_logs WHERE log_type = 'POP3'");
$countStmt->execute();
$totalLogs = $countStmt->fetch()['count'];
} else {
$stmt = $db->prepare("
SELECT l.*, u.username
FROM server_logs l
LEFT JOIN users u ON l.user_id = u.id
ORDER BY l.created_at DESC
LIMIT ? OFFSET ?
");
$stmt->execute([$perPage, $offset]);
}
$logs = $stmt->fetchAll();
$totalPages = ceil($totalLogs / $perPage);
// 获取日志设置
$logPath = $settingsRepo->get('log_path', '/var/log/mailserver');
$logMaxSize = $settingsRepo->get('log_max_size', 10485760);
?>
<!DOCTYPE html>
<html>
<head>
<title>系统日志 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 20px; }
.stat-box { border: 1px solid #ddd; padding: 15px; text-align: center; border-radius: 5px; }
.stat-box h3 { margin: 0; font-size: 24px; color: #007bff; }
.stat-box p { margin: 5px 0 0 0; color: #666; }
.filters { margin-bottom: 20px; }
.filters a { display: inline-block; padding: 8px 16px; margin-right: 10px; text-decoration: none; border: 1px solid #ddd; border-radius: 4px; }
.filters a.active { background: #007bff; color: white; border-color: #007bff; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #f8f9fa; }
.btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-danger { background: #dc3545; color: white; }
.pagination { margin-top: 20px; text-align: center; }
.pagination a { display: inline-block; padding: 8px 12px; margin: 0 4px; text-decoration: none; border: 1px solid #ddd; border-radius: 4px; }
.pagination a:hover { background: #f8f9fa; }
.pagination .current { background: #007bff; color: white; border-color: #007bff; }
.log-info { background: #f8f9fa; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
</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>
<a href="users.php">用户管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div>
<div class="container">
<h2>系统日志管理</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<!-- 日志统计 -->
<div class="stats">
<div class="stat-box">
<h3><?php echo $totalLogs; ?></h3>
<p>总日志数</p>
</div>
<div class="stat-box">
<h3><?php echo $smtpLogs; ?></h3>
<p>SMTP日志</p>
</div>
<div class="stat-box">
<h3><?php echo $pop3Logs; ?></h3>
<p>POP3日志</p>
</div>
</div>
<!-- 日志信息 -->
<div class="log-info">
<strong>日志存储路径:</strong><?php echo htmlspecialchars($logPath); ?><br>
<strong>日志文件最大大小:</strong><?php echo round($logMaxSize / 1048576, 2); ?> MB
</div>
<!-- 过滤和操作 -->
<div class="filters">
<a href="?type=all" class="<?php echo $filterType === 'all' ? 'active' : ''; ?>">全部</a>
<a href="?type=smtp" class="<?php echo $filterType === 'smtp' ? 'active' : ''; ?>">SMTP日志</a>
<a href="?type=pop3" class="<?php echo $filterType === 'pop3' ? 'active' : ''; ?>">POP3日志</a>
<?php if ($_SESSION['is_admin'] ?? false): ?>
<a href="?clear_logs=smtp" class="btn btn-danger" onclick="return confirm('确定要清除SMTP日志吗');">清除SMTP日志</a>
<a href="?clear_logs=pop3" class="btn btn-danger" onclick="return confirm('确定要清除POP3日志吗');">清除POP3日志</a>
<a href="?clear_logs=all" class="btn btn-danger" onclick="return confirm('确定要清除所有日志吗?');">清除所有日志</a>
<?php endif; ?>
</div>
<!-- 日志列表 -->
<table>
<thead>
<tr>
<th>ID</th>
<th>类型</th>
<th>消息</th>
<th>用户</th>
<th>IP地址</th>
<th>时间</th>
</tr>
</thead>
<tbody>
<?php if (empty($logs)): ?>
<tr>
<td colspan="6" style="text-align: center; padding: 40px;">暂无日志</td>
</tr>
<?php else: ?>
<?php foreach ($logs as $log): ?>
<tr>
<td><?php echo $log['id']; ?></td>
<td><?php echo htmlspecialchars($log['log_type'] ?? '-'); ?></td>
<td><?php echo htmlspecialchars($log['message'] ?? '-'); ?></td>
<td><?php echo htmlspecialchars($log['username'] ?? '-'); ?></td>
<td><?php echo htmlspecialchars($log['client_ip'] ?? '-'); ?></td>
<td><?php echo $log['created_at']; ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<!-- 分页 -->
<?php if ($totalPages > 1): ?>
<div class="pagination">
<?php if ($page > 1): ?>
<a href="?page=<?php echo $page - 1; ?>&type=<?php echo $filterType; ?>">上一页</a>
<?php endif; ?>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<?php if ($i == $page): ?>
<span class="current"><?php echo $i; ?></span>
<?php else: ?>
<a href="?page=<?php echo $i; ?>&type=<?php echo $filterType; ?>"><?php echo $i; ?></a>
<?php endif; ?>
<?php endfor; ?>
<?php if ($page < $totalPages): ?>
<a href="?page=<?php echo $page + 1; ?>&type=<?php echo $filterType; ?>">下一页</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</body>
</html>

@ -1,288 +1,288 @@
<?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/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php';
session_start();
$error = '';
$success = '';
// 处理注册请求
if (isset($_POST['register'])) {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
// 验证CSRF令牌
if (!Security::verifyCSRFToken($_POST['csrf_token'] ?? '')) {
$error = "安全验证失败,请重试";
} else {
// 验证输入
$usernameValidation = Validator::validateUsername($username);
if (!$usernameValidation['valid']) {
$error = implode('<br>', $usernameValidation['errors']);
} else {
// 验证邮箱域名默认test.com
$domain = 'test.com';
if (!Validator::validateEmailDomain($username, $domain)) {
$error = "邮箱域名必须是 @{$domain}";
} else {
// 验证密码
$passwordValidation = Validator::validatePassword($password, 6);
if (!$passwordValidation['valid']) {
$error = implode('<br>', $passwordValidation['errors']);
} else {
// 验证密码确认
$matchValidation = Validator::validatePasswordMatch($password, $confirmPassword);
if (!$matchValidation['valid']) {
$error = implode('<br>', $matchValidation['errors']);
} else {
// 尝试创建用户
try {
$userRepo = new UserRepository();
// 检查用户名是否已存在
if ($userRepo->usernameExists($username)) {
$error = "该邮箱已被注册";
} else {
// 创建新用户(默认非管理员,激活状态)
$user = $userRepo->create($username, $password, false, true);
if ($user) {
$success = "注册成功!请使用您的账号登录。";
// 3秒后跳转到登录页面
header("Refresh: 3; url=index.php");
} else {
$error = "注册失败,请稍后重试";
}
}
} catch (Exception $e) {
$error = "注册失败: " . $e->getMessage();
}
}
}
}
}
}
}
// 生成CSRF令牌
$csrfToken = Security::generateCSRFToken();
?>
<!DOCTYPE html>
<html>
<head>
<title>用户注册 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.register-container {
background: white;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 40px;
width: 100%;
max-width: 450px;
}
h1 {
color: #333;
margin-bottom: 10px;
text-align: center;
}
.subtitle {
color: #666;
text-align: center;
margin-bottom: 30px;
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
font-size: 14px;
}
input[type="text"],
input[type="email"],
input[type="password"] {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
transition: border-color 0.3s;
}
input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus {
outline: none;
border-color: #667eea;
}
.help-text {
font-size: 12px;
color: #999;
margin-top: 5px;
}
.error {
background: #fee;
color: #c33;
padding: 12px;
border-radius: 5px;
margin-bottom: 20px;
font-size: 14px;
border-left: 4px solid #c33;
}
.success {
background: #efe;
color: #3c3;
padding: 12px;
border-radius: 5px;
margin-bottom: 20px;
font-size: 14px;
border-left: 4px solid #3c3;
}
button[type="submit"] {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
button[type="submit"]:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
button[type="submit"]:active {
transform: translateY(0);
}
.login-link {
text-align: center;
margin-top: 20px;
font-size: 14px;
color: #666;
}
.login-link a {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.login-link a:hover {
text-decoration: underline;
}
.domain-hint {
display: inline-block;
background: #f0f0f0;
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
font-size: 12px;
}
</style>
</head>
<body>
<div class="register-container">
<h1>用户注册</h1>
<p class="subtitle">创建您的邮件服务器账号</p>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="success"><?php echo $success; ?></div>
<?php else: ?>
<form method="POST" action="">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
<div class="form-group">
<label for="username">邮箱地址</label>
<input
type="email"
id="username"
name="username"
value="<?php echo htmlspecialchars($_POST['username'] ?? ''); ?>"
placeholder="example@test.com"
required
autofocus
>
<div class="help-text">请输入您的邮箱地址(域名必须是 <span class="domain-hint">@test.com</span></div>
</div>
<div class="form-group">
<label for="password">密码</label>
<input
type="password"
id="password"
name="password"
placeholder="至少6个字符"
required
minlength="6"
>
<div class="help-text">密码长度至少需要6个字符</div>
</div>
<div class="form-group">
<label for="confirm_password">确认密码</label>
<input
type="password"
id="confirm_password"
name="confirm_password"
placeholder="请再次输入密码"
required
minlength="6"
>
</div>
<button type="submit" name="register">注册</button>
</form>
<?php endif; ?>
<div class="login-link">
已有账号?<a href="index.php">立即登录</a>
</div>
</div>
</body>
</html>
<?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/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php';
session_start();
$error = '';
$success = '';
// 处理注册请求
if (isset($_POST['register'])) {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
// 验证CSRF令牌
if (!Security::verifyCSRFToken($_POST['csrf_token'] ?? '')) {
$error = "安全验证失败,请重试";
} else {
// 验证输入
$usernameValidation = Validator::validateUsername($username);
if (!$usernameValidation['valid']) {
$error = implode('<br>', $usernameValidation['errors']);
} else {
// 验证邮箱域名默认test.com
$domain = 'test.com';
if (!Validator::validateEmailDomain($username, $domain)) {
$error = "邮箱域名必须是 @{$domain}";
} else {
// 验证密码
$passwordValidation = Validator::validatePassword($password, 6);
if (!$passwordValidation['valid']) {
$error = implode('<br>', $passwordValidation['errors']);
} else {
// 验证密码确认
$matchValidation = Validator::validatePasswordMatch($password, $confirmPassword);
if (!$matchValidation['valid']) {
$error = implode('<br>', $matchValidation['errors']);
} else {
// 尝试创建用户
try {
$userRepo = new UserRepository();
// 检查用户名是否已存在
if ($userRepo->usernameExists($username)) {
$error = "该邮箱已被注册";
} else {
// 创建新用户(默认非管理员,激活状态)
$user = $userRepo->create($username, $password, false, true);
if ($user) {
$success = "注册成功!请使用您的账号登录。";
// 3秒后跳转到登录页面
header("Refresh: 3; url=index.php");
} else {
$error = "注册失败,请稍后重试";
}
}
} catch (Exception $e) {
$error = "注册失败: " . $e->getMessage();
}
}
}
}
}
}
}
// 生成CSRF令牌
$csrfToken = Security::generateCSRFToken();
?>
<!DOCTYPE html>
<html>
<head>
<title>用户注册 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.register-container {
background: white;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 40px;
width: 100%;
max-width: 450px;
}
h1 {
color: #333;
margin-bottom: 10px;
text-align: center;
}
.subtitle {
color: #666;
text-align: center;
margin-bottom: 30px;
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
font-size: 14px;
}
input[type="text"],
input[type="email"],
input[type="password"] {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
transition: border-color 0.3s;
}
input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus {
outline: none;
border-color: #667eea;
}
.help-text {
font-size: 12px;
color: #999;
margin-top: 5px;
}
.error {
background: #fee;
color: #c33;
padding: 12px;
border-radius: 5px;
margin-bottom: 20px;
font-size: 14px;
border-left: 4px solid #c33;
}
.success {
background: #efe;
color: #3c3;
padding: 12px;
border-radius: 5px;
margin-bottom: 20px;
font-size: 14px;
border-left: 4px solid #3c3;
}
button[type="submit"] {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
button[type="submit"]:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
button[type="submit"]:active {
transform: translateY(0);
}
.login-link {
text-align: center;
margin-top: 20px;
font-size: 14px;
color: #666;
}
.login-link a {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.login-link a:hover {
text-decoration: underline;
}
.domain-hint {
display: inline-block;
background: #f0f0f0;
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
font-size: 12px;
}
</style>
</head>
<body>
<div class="register-container">
<h1>用户注册</h1>
<p class="subtitle">创建您的邮件服务器账号</p>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="success"><?php echo $success; ?></div>
<?php else: ?>
<form method="POST" action="">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
<div class="form-group">
<label for="username">邮箱地址</label>
<input
type="email"
id="username"
name="username"
value="<?php echo htmlspecialchars($_POST['username'] ?? ''); ?>"
placeholder="example@test.com"
required
autofocus
>
<div class="help-text">请输入您的邮箱地址(域名必须是 <span class="domain-hint">@test.com</span></div>
</div>
<div class="form-group">
<label for="password">密码</label>
<input
type="password"
id="password"
name="password"
placeholder="至少6个字符"
required
minlength="6"
>
<div class="help-text">密码长度至少需要6个字符</div>
</div>
<div class="form-group">
<label for="confirm_password">确认密码</label>
<input
type="password"
id="confirm_password"
name="confirm_password"
placeholder="请再次输入密码"
required
minlength="6"
>
</div>
<button type="submit" name="register">注册</button>
</form>
<?php endif; ?>
<div class="login-link">
已有账号?<a href="index.php">立即登录</a>
</div>
</div>
</body>
</html>

@ -1,166 +1,361 @@
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/ServiceRepository.php';
require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
// 检查管理员权限
if (!$_SESSION['is_admin']) {
die('权限不足:只有管理员可以访问此页面');
}
$serviceRepo = new ServiceRepository();
$settingsRepo = new SystemSettingsRepository();
$message = '';
$error = '';
// 处理服务起停
if (isset($_GET['action'])) {
$serviceName = $_GET['service'] ?? '';
$action = $_GET['action'] ?? '';
if ($serviceName === 'smtp' || $serviceName === 'pop3') {
if ($action === 'start') {
// 启动服务(实际应该通过系统服务管理,这里只是更新状态)
$pid = null; // 实际应该获取进程ID
$serviceRepo->updateStatus($serviceName, true, $pid);
$message = strtoupper($serviceName) . "服务已启动";
} elseif ($action === 'stop') {
// 停止服务
$status = $serviceRepo->getStatus($serviceName);
if ($status && $status['pid']) {
// 尝试终止进程
@exec("kill {$status['pid']} 2>/dev/null");
}
$serviceRepo->updateStatus($serviceName, false, null);
$message = strtoupper($serviceName) . "服务已停止";
}
}
}
// 获取服务状态
$smtpStatus = $serviceRepo->getStatus('smtp');
$pop3Status = $serviceRepo->getStatus('pop3');
$smtpRunning = $serviceRepo->isRunning('smtp');
$pop3Running = $serviceRepo->isRunning('pop3');
// 获取端口设置
$smtpPort = $settingsRepo->get('smtp_port', 25);
$pop3Port = $settingsRepo->get('pop3_port', 110);
?>
<!DOCTYPE html>
<html>
<head>
<title>服务管理 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.service-box { border: 1px solid #ddd; padding: 20px; margin-bottom: 20px; border-radius: 5px; }
.service-box h3 { margin-top: 0; }
.status { display: inline-block; padding: 6px 12px; border-radius: 4px; font-weight: 500; margin-right: 10px; }
.status-running { background: #28a745; color: white; }
.status-stopped { background: #dc3545; color: white; }
.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-success { background: #28a745; color: white; }
.btn-danger { background: #dc3545; color: white; }
.info { color: #666; font-size: 14px; margin-top: 10px; }
.note { background: #fff3cd; border: 1px solid #ffc107; padding: 15px; border-radius: 5px; margin-top: 20px; }
</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>
<a href="users.php">用户管理</a>
<a href="emails.php">邮件管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a>
</div>
<div class="container">
<h2>服务管理</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<!-- SMTP服务 -->
<div class="service-box">
<h3>SMTP服务邮件发送</h3>
<p>
<span class="status status-<?php echo $smtpRunning ? 'running' : 'stopped'; ?>">
<?php echo $smtpRunning ? '运行中' : '已停止'; ?>
</span>
<?php if ($smtpRunning): ?>
<a href="?service=smtp&action=stop" class="btn btn-danger" onclick="return confirm('确定要停止SMTP服务吗');">停止服务</a>
<?php else: ?>
<a href="?service=smtp&action=start" class="btn btn-success" onclick="return confirm('确定要启动SMTP服务吗');">启动服务</a>
<?php endif; ?>
</p>
<div class="info">
<strong>端口:</strong><?php echo $smtpPort; ?><br>
<strong>进程ID</strong><?php echo $smtpStatus['pid'] ?? '-'; ?><br>
<strong>最后启动:</strong><?php echo $smtpStatus['last_started_at'] ?? '-'; ?><br>
<strong>最后停止:</strong><?php echo $smtpStatus['last_stopped_at'] ?? '-'; ?>
</div>
</div>
<!-- POP3服务 -->
<div class="service-box">
<h3>POP3服务邮件接收</h3>
<p>
<span class="status status-<?php echo $pop3Running ? 'running' : 'stopped'; ?>">
<?php echo $pop3Running ? '运行中' : '已停止'; ?>
</span>
<?php if ($pop3Running): ?>
<a href="?service=pop3&action=stop" class="btn btn-danger" onclick="return confirm('确定要停止POP3服务吗');">停止服务</a>
<?php else: ?>
<a href="?service=pop3&action=start" class="btn btn-success" onclick="return confirm('确定要启动POP3服务吗');">启动服务</a>
<?php endif; ?>
</p>
<div class="info">
<strong>端口:</strong><?php echo $pop3Port; ?><br>
<strong>进程ID</strong><?php echo $pop3Status['pid'] ?? '-'; ?><br>
<strong>最后启动:</strong><?php echo $pop3Status['last_started_at'] ?? '-'; ?><br>
<strong>最后停止:</strong><?php echo $pop3Status['last_stopped_at'] ?? '-'; ?>
</div>
</div>
<div class="note">
<strong>注意:</strong>此页面仅用于管理服务状态。实际启动服务需要使用命令行:
<ul>
<li>SMTP服务<code>sudo php scripts/start_smtp.php</code></li>
<li>POP3服务<code>sudo php scripts/start_pop3.php</code></li>
</ul>
</div>
</div>
</body>
</html>
<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/ServiceRepository.php';
require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php';
//错误提示
error_reporting(E_ALL);
ini_set('display_errors', 1);
session_start();
// 登录验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$serviceRepo = new ServiceRepository();
$settingsRepo = new SystemSettingsRepository();
$message = '';
$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'])) {
$serviceName = $_GET['service'] ?? '';
$action = $_GET['action'] ?? '';
if ($serviceName === 'smtp' || $serviceName === 'pop3') {
$port = $serviceName === 'smtp' ? $smtpPort : $pop3Port;
if ($action === 'start') {
$result = startService($serviceName, $port);
if ($result['success']) {
$pid = $result['pid'];
$serviceRepo->updateStatus($serviceName, true, $pid);
$message = strtoupper($serviceName) . "服务已启动 (PID: {$pid})";
} else {
$error = strtoupper($serviceName) . "服务启动失败: " . $result['message'];
}
} elseif ($action === 'stop') {
$status = $serviceRepo->getStatus($serviceName);
$pid = $status['pid'] ?? getServicePid($serviceName, $port);
if (stopService($serviceName, $pid, $port)) {
$serviceRepo->updateStatus($serviceName, false, null);
$message = strtoupper($serviceName) . "服务已停止";
} else {
$error = strtoupper($serviceName) . "服务停止失败";
}
// ===================== 修改部分结束 =====================
}
}
}
// 获取服务状态(修改判断逻辑)
// ===================== 修改部分开始 =====================
$smtpStatus = $serviceRepo->getStatus('smtp');
$pop3Status = $serviceRepo->getStatus('pop3');
// 实际检查服务是否运行(使用配置的端口)
$smtpRunning = isServiceRunning('smtp', $smtpPort);
$pop3Running = isServiceRunning('pop3', $pop3Port);
// 如果数据库状态与实际状态不一致,更新数据库
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>
<html>
<head>
<title>服务管理 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.service-box { border: 1px solid #ddd; padding: 20px; margin-bottom: 20px; border-radius: 5px; }
.service-box h3 { margin-top: 0; }
.status { display: inline-block; padding: 6px 12px; border-radius: 4px; font-weight: 500; margin-right: 10px; }
.status-running { background: #28a745; color: white; }
.status-stopped { background: #dc3545; color: white; }
.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-success { background: #28a745; color: white; }
.btn-danger { background: #dc3545; color: white; }
.info { color: #666; font-size: 14px; margin-top: 10px; }
.note { background: #fff3cd; border: 1px solid #ffc107; padding: 15px; border-radius: 5px; margin-top: 20px; }
</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>
<a href="users.php">用户管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div>
<div class="container">
<h2>服务管理</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<!-- SMTP服务 -->
<div class="service-box">
<h3>SMTP服务邮件发送</h3>
<p>
<span class="status status-<?php echo $smtpRunning ? 'running' : 'stopped'; ?>">
<?php echo $smtpRunning ? '运行中' : '已停止'; ?>
</span>
<?php if ($smtpRunning): ?>
<a href="?service=smtp&action=stop" class="btn btn-danger" onclick="return confirm('确定要停止SMTP服务吗');">停止服务</a>
<?php else: ?>
<a href="?service=smtp&action=start" class="btn btn-success" onclick="return confirm('确定要启动SMTP服务吗');">启动服务</a>
<?php endif; ?>
</p>
<div class="info">
<strong>端口:</strong><?php echo $smtpPort; ?><br>
<strong>进程ID</strong><?php echo $smtpStatus['pid'] ?? '-'; ?><br>
<strong>最后启动:</strong><?php echo $smtpStatus['last_started_at'] ?? '-'; ?><br>
<strong>最后停止:</strong><?php echo $smtpStatus['last_stopped_at'] ?? '-'; ?>
</div>
</div>
<!-- POP3服务 -->
<div class="service-box">
<h3>POP3服务邮件接收</h3>
<p>
<span class="status status-<?php echo $pop3Running ? 'running' : 'stopped'; ?>">
<?php echo $pop3Running ? '运行中' : '已停止'; ?>
</span>
<?php if ($pop3Running): ?>
<a href="?service=pop3&action=stop" class="btn btn-danger" onclick="return confirm('确定要停止POP3服务吗');">停止服务</a>
<?php else: ?>
<a href="?service=pop3&action=start" class="btn btn-success" onclick="return confirm('确定要启动POP3服务吗');">启动服务</a>
<?php endif; ?>
</p>
<div class="info">
<strong>端口:</strong><?php echo $pop3Port; ?><br>
<strong>进程ID</strong><?php echo $pop3Status['pid'] ?? '-'; ?><br>
<strong>最后启动:</strong><?php echo $pop3Status['last_started_at'] ?? '-'; ?><br>
<strong>最后停止:</strong><?php echo $pop3Status['last_stopped_at'] ?? '-'; ?>
</div>
</div>
<div class="note">
<strong>注意:</strong>
<ul>
<li>如果服务启动失败,请检查是否没有该端口访问权限或端口被占用</li>
<li>当前配置SMTP端口 <?php echo $smtpPort; ?>, POP3端口 <?php echo $pop3Port; ?></li>
</ul>
</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>
</html>

@ -1,320 +1,341 @@
<?php
/**
* 系统设置页面
*
* @uses SystemSettingsRepository
* @uses UserRepository
* @uses MailboxRepository
*/
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php';
require_once __DIR__ . '/../src/storage/UserRepository.php';
require_once __DIR__ . '/../src/storage/MailboxRepository.php';
require_once __DIR__ . '/../src/utils/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
// 检查管理员权限
if (!$_SESSION['is_admin']) {
die('权限不足:只有管理员可以访问此页面');
}
/** @var SystemSettingsRepository */
$settingsRepo = new SystemSettingsRepository();
/** @var UserRepository */
$userRepo = new UserRepository();
/** @var MailboxRepository */
$mailboxRepo = new MailboxRepository();
$message = '';
$error = '';
// 处理系统设置更新
if (isset($_POST['update_settings'])) {
// SMTP端口
if (isset($_POST['smtp_port'])) {
$port = (int)$_POST['smtp_port'];
if (Validator::validatePort($port)) {
$settingsRepo->set('smtp_port', $port);
} else {
$error = "SMTP端口无效1-65535";
}
}
// POP3端口
if (isset($_POST['pop3_port'])) {
$port = (int)$_POST['pop3_port'];
if (Validator::validatePort($port)) {
$settingsRepo->set('pop3_port', $port);
} else {
$error = "POP3端口无效1-65535";
}
}
// 域名
if (isset($_POST['domain'])) {
$domain = trim($_POST['domain']);
if (!empty($domain)) {
$settingsRepo->set('domain', $domain);
}
}
// 默认邮箱大小限制
if (isset($_POST['mailbox_size_limit'])) {
$size = (int)$_POST['mailbox_size_limit'];
if ($size > 0) {
$settingsRepo->set('mailbox_size_limit', $size);
}
}
// 日志路径
if (isset($_POST['log_path'])) {
$settingsRepo->set('log_path', trim($_POST['log_path']));
}
// 日志最大大小
if (isset($_POST['log_max_size'])) {
$size = (int)$_POST['log_max_size'];
if ($size > 0) {
$settingsRepo->set('log_max_size', $size);
}
}
if (empty($error)) {
$message = "系统设置已更新";
}
}
// 处理管理员密码修改
if (isset($_POST['change_admin_password'])) {
$oldPassword = $_POST['old_password'] ?? '';
$newPassword = $_POST['new_password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
$user = $userRepo->findById($_SESSION['user_id']);
if (!Security::verifyPassword($oldPassword, $user['password_hash'])) {
$error = "原密码错误";
} else {
$passwordValidation = Validator::validatePassword($newPassword, 6);
if (!$passwordValidation['valid']) {
$error = implode('<br>', $passwordValidation['errors']);
} elseif ($newPassword !== $confirmPassword) {
$error = "两次输入的密码不一致";
} else {
if ($userRepo->update($_SESSION['user_id'], ['password' => $newPassword])) {
$message = "管理员密码已更新";
} else {
$error = "密码更新失败";
}
}
}
}
// 处理用户邮箱大小设置
if (isset($_POST['set_mailbox_size'])) {
$userId = (int)$_POST['user_id'];
$sizeBytes = (int)$_POST['mailbox_size'];
if ($sizeBytes > 0) {
if ($mailboxRepo->setSizeLimit($userId, $sizeBytes)) {
$message = "邮箱大小限制已更新";
} else {
$error = "更新失败";
}
} else {
$error = "邮箱大小必须大于0";
}
}
// 获取当前设置
$settings = $settingsRepo->getAll();
$users = $userRepo->getAll();
?>
<!DOCTYPE html>
<html>
<head>
<title>系统设置 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.section { margin-bottom: 30px; padding-bottom: 20px; border-bottom: 1px solid #ddd; }
.section:last-child { border-bottom: none; }
.section h3 { margin-top: 0; color: #333; }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: 500; }
.form-group input, .form-group select { width: 100%; max-width: 400px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.form-group small { color: #666; font-size: 12px; }
.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; }
.btn-primary { background: #007bff; color: white; }
.btn-success { background: #28a745; color: white; }
table { width: 100%; border-collapse: collapse; margin-top: 15px; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background: #f8f9fa; }
.size-input { width: 150px; }
</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>
<a href="users.php">用户管理</a>
<a href="emails.php">邮件管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="settings.php">系统设置</a>
</div>
<div class="container">
<h2>系统设置</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<!-- 服务器端口设置 -->
<div class="section">
<h3>服务器端口设置</h3>
<form method="POST">
<div class="form-group">
<label>SMTP端口默认25</label>
<input type="number" name="smtp_port" value="<?php echo htmlspecialchars($settings['smtp_port'] ?? '25'); ?>" min="1" max="65535" required>
<small>SMTP服务器监听端口</small>
</div>
<div class="form-group">
<label>POP3端口默认110</label>
<input type="number" name="pop3_port" value="<?php echo htmlspecialchars($settings['pop3_port'] ?? '110'); ?>" min="1" max="65535" required>
<small>POP3服务器监听端口</small>
</div>
<button type="submit" name="update_settings" class="btn btn-primary">保存端口设置</button>
</form>
</div>
<!-- 域名设置 -->
<div class="section">
<h3>域名设置</h3>
<form method="POST">
<div class="form-group">
<label>服务器域名默认test.com</label>
<input type="text" name="domain" value="<?php echo htmlspecialchars($settings['domain'] ?? 'test.com'); ?>" required>
<small>邮件服务器域名,用户邮箱必须使用此域名</small>
</div>
<button type="submit" name="update_settings" class="btn btn-primary">保存域名设置</button>
</form>
</div>
<!-- 邮箱管理 -->
<div class="section">
<h3>邮箱大小管理</h3>
<form method="POST">
<div class="form-group">
<label>默认邮箱大小限制(字节)</label>
<input type="number" name="mailbox_size_limit" value="<?php echo htmlspecialchars($settings['mailbox_size_limit'] ?? '104857600'); ?>" min="1" required>
<small>默认值104857600 (100MB)</small>
</div>
<button type="submit" name="update_settings" class="btn btn-primary">保存默认大小</button>
</form>
<h4>用户邮箱大小设置</h4>
<table>
<thead>
<tr>
<th>用户</th>
<th>当前使用</th>
<th>限制大小</th>
<th>使用率</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<?php
$usage = $mailboxRepo->getUsage($user['id']);
$usedMB = round($usage['used'] / 1048576, 2);
$limitMB = round($usage['limit'] / 1048576, 2);
?>
<tr>
<td><?php echo htmlspecialchars($user['username']); ?></td>
<td><?php echo $usedMB; ?> MB</td>
<td><?php echo $limitMB; ?> MB</td>
<td><?php echo $usage['percentage']; ?>%</td>
<td>
<form method="POST" style="display: inline;">
<input type="hidden" name="user_id" value="<?php echo $user['id']; ?>">
<input type="number" name="mailbox_size" value="<?php echo $usage['limit']; ?>" class="size-input" min="1" required>
<button type="submit" name="set_mailbox_size" class="btn btn-success">设置</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- 日志设置 -->
<div class="section">
<h3>日志设置</h3>
<form method="POST">
<div class="form-group">
<label>日志文件存储路径</label>
<input type="text" name="log_path" value="<?php echo htmlspecialchars($settings['log_path'] ?? '/var/log/mailserver'); ?>" required>
<small>日志文件存储的目录路径</small>
</div>
<div class="form-group">
<label>日志文件最大大小(字节)</label>
<input type="number" name="log_max_size" value="<?php echo htmlspecialchars($settings['log_max_size'] ?? '10485760'); ?>" min="1" required>
<small>默认值10485760 (10MB)</small>
</div>
<button type="submit" name="update_settings" class="btn btn-primary">保存日志设置</button>
</form>
</div>
<!-- 管理员密码修改 -->
<div class="section">
<h3>修改管理员密码</h3>
<form method="POST">
<div class="form-group">
<label>原密码</label>
<input type="password" name="old_password" required>
</div>
<div class="form-group">
<label>新密码</label>
<input type="password" name="new_password" required minlength="6">
<small>密码长度至少6个字符</small>
</div>
<div class="form-group">
<label>确认新密码</label>
<input type="password" name="confirm_password" required minlength="6">
</div>
<button type="submit" name="change_admin_password" class="btn btn-primary">修改密码</button>
</form>
</div>
</div>
</body>
</html>
<?php
/**
* 系统设置页面
*
* @uses SystemSettingsRepository
* @uses UserRepository
* @uses MailboxRepository
*/
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../src/storage/Database.php';
require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php';
require_once __DIR__ . '/../src/storage/UserRepository.php';
require_once __DIR__ . '/../src/storage/MailboxRepository.php';
require_once __DIR__ . '/../src/utils/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php';
//开启报错提示
ini_set('log_errors', 1);
ini_set('error_log', '/dev/stdout');
session_start();
// 是否登录验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
/** @var SystemSettingsRepository */
$settingsRepo = new SystemSettingsRepository();
/** @var UserRepository */
$userRepo = new UserRepository();
/** @var MailboxRepository */
$mailboxRepo = new MailboxRepository();
$message = '';
$error = '';
$domain = $settingsRepo->get('domain', 'test.com');
// 处理系统设置更新
if (isset($_POST['update_settings'])) {
// SMTP端口
if (isset($_POST['smtp_port'])) {
$port = (int)$_POST['smtp_port'];
if (Validator::validatePort($port)) {
$settingsRepo->set('smtp_port', $port);
} else {
$error = "SMTP端口无效1-65535";
}
}
// POP3端口
if (isset($_POST['pop3_port'])) {
$port = (int)$_POST['pop3_port'];
if (Validator::validatePort($port)) {
$settingsRepo->set('pop3_port', $port);
} else {
$error = "POP3端口无效1-65535";
}
}
// 域名
if (isset($_POST['domain'])) {
$newDomain = trim($_POST['domain']);
if (!empty($newDomain)) {
/* **** 调试开始 **** */
$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);
/* **** 调试结束 **** */
}
}
}
// 默认邮箱大小限制
if (isset($_POST['mailbox_size_limit'])) {
$size = (int)$_POST['mailbox_size_limit'];
if ($size > 0) {
$settingsRepo->set('mailbox_size_limit', $size);
}
}
// 日志路径
if (isset($_POST['log_path'])) {
$settingsRepo->set('log_path', trim($_POST['log_path']));
}
// 日志最大大小
if (isset($_POST['log_max_size'])) {
$size = (int)$_POST['log_max_size'];
if ($size > 0) {
$settingsRepo->set('log_max_size', $size);
}
}
if (empty($error)) {
$message = "系统设置已更新";
}
}
// 处理管理员密码修改
if (isset($_POST['change_admin_password'])) {
$oldPassword = $_POST['old_password'] ?? '';
$newPassword = $_POST['new_password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
$user = $userRepo->findById($_SESSION['user_id']);
if (!Security::verifyPassword($oldPassword, $user['password_hash'])) {
$error = "原密码错误";
} else {
$passwordValidation = Validator::validatePassword($newPassword, 6);
if (!$passwordValidation['valid']) {
$error = implode('<br>', $passwordValidation['errors']);
} elseif ($newPassword !== $confirmPassword) {
$error = "两次输入的密码不一致";
} else {
if ($userRepo->update($_SESSION['user_id'], ['password' => $newPassword])) {
$message = "管理员密码已更新";
} else {
$error = "密码更新失败";
}
}
}
}
// 处理用户邮箱大小设置
if (isset($_POST['set_mailbox_size'])) {
$userId = (int)$_POST['user_id'];
$sizeBytes = (int)$_POST['mailbox_size'];
if ($sizeBytes > 0) {
if ($mailboxRepo->setSizeLimit($userId, $sizeBytes)) {
$message = "邮箱大小限制已更新";
} else {
$error = "更新失败";
}
} else {
$error = "邮箱大小必须大于0";
}
}
// 获取当前设置
$settings = $settingsRepo->getAll();
$users = $userRepo->getAll();
?>
<!DOCTYPE html>
<html>
<head>
<title>系统设置 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.section { margin-bottom: 30px; padding-bottom: 20px; border-bottom: 1px solid #ddd; }
.section:last-child { border-bottom: none; }
.section h3 { margin-top: 0; color: #333; }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: 500; }
.form-group input, .form-group select { width: 100%; max-width: 400px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.form-group small { color: #666; font-size: 12px; }
.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; }
.btn-primary { background: #007bff; color: white; }
.btn-success { background: #28a745; color: white; }
table { width: 100%; border-collapse: collapse; margin-top: 15px; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background: #f8f9fa; }
.size-input { width: 150px; }
</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>
<a href="users.php">用户管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div>
<div class="container">
<h2>系统设置</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<!-- 服务器端口设置 -->
<div class="section">
<h3>服务器端口设置</h3>
<form method="POST">
<div class="form-group">
<label>SMTP端口默认25</label>
<input type="number" name="smtp_port" value="<?php echo htmlspecialchars($settings['smtp_port'] ?? '25'); ?>" min="1" max="65535" required>
<small>SMTP服务器监听端口</small>
</div>
<div class="form-group">
<label>POP3端口默认110</label>
<input type="number" name="pop3_port" value="<?php echo htmlspecialchars($settings['pop3_port'] ?? '110'); ?>" min="1" max="65535" required>
<small>POP3服务器监听端口</small>
</div>
<button type="submit" name="update_settings" class="btn btn-primary">保存端口设置</button>
</form>
</div>
<!-- 域名设置 -->
<div class="section">
<h3>域名设置</h3>
<form method="POST">
<div class="form-group">
<label>服务器域名默认test.com</label>
<input type="text" name="domain" value="<?php echo htmlspecialchars($settings['domain'] ?? 'test.com'); ?>" >
<small>邮件服务器域名,用户邮箱必须使用此域名</small>
</div>
<button type="submit" name="update_settings" class="btn btn-primary">保存域名设置</button>
</form>
</div>
<!-- 邮箱管理 -->
<div class="section">
<h3>邮箱大小管理</h3>
<form method="POST">
<div class="form-group">
<label>默认邮箱大小限制(字节)</label>
<input type="number" name="mailbox_size_limit" value="<?php echo htmlspecialchars($settings['mailbox_size_limit'] ?? '104857600'); ?>" min="1" required>
<small>默认值104857600 (100MB)</small>
</div>
<button type="submit" name="update_settings" class="btn btn-primary">保存默认大小</button>
</form>
<h4>用户邮箱大小设置</h4>
<table>
<thead>
<tr>
<th>用户</th>
<th>当前使用</th>
<th>限制大小</th>
<th>使用率</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<?php
$usage = $mailboxRepo->getUsage($user['id']);
$usedMB = round($usage['used'] / 1048576, 2);
$limitMB = round($usage['limit'] / 1048576, 2);
?>
<tr>
<td><?php echo htmlspecialchars($user['username']); ?></td>
<td><?php echo $usedMB; ?> MB</td>
<td><?php echo $limitMB; ?> MB</td>
<td><?php echo $usage['percentage']; ?>%</td>
<td>
<form method="POST" style="display: inline;">
<input type="hidden" name="user_id" value="<?php echo $user['id']; ?>">
<input type="number" name="mailbox_size" value="<?php echo $usage['limit']; ?>" class="size-input" min="1" required>
<button type="submit" name="set_mailbox_size" class="btn btn-success">设置</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- 日志设置 -->
<div class="section">
<h3>日志设置</h3>
<form method="POST">
<div class="form-group">
<label>日志文件存储路径</label>
<input type="text" name="log_path" value="<?php echo htmlspecialchars($settings['log_path'] ?? '/var/log/mailserver'); ?>" required>
<small>日志文件存储的目录路径</small>
</div>
<div class="form-group">
<label>日志文件最大大小(字节)</label>
<input type="number" name="log_max_size" value="<?php echo htmlspecialchars($settings['log_max_size'] ?? '10485760'); ?>" min="1" required>
<small>默认值10485760 (10MB)</small>
</div>
<button type="submit" name="update_settings" class="btn btn-primary">保存日志设置</button>
</form>
</div>
<!-- 管理员密码修改 -->
<div class="section">
<h3>修改管理员密码</h3>
<form method="POST">
<div class="form-group">
<label>原密码</label>
<input type="password" name="old_password" required>
</div>
<div class="form-group">
<label>新密码</label>
<input type="password" name="new_password" required minlength="6">
<small>密码长度至少6个字符</small>
</div>
<div class="form-group">
<label>确认新密码</label>
<input type="password" name="confirm_password" required minlength="6">
</div>
<button type="submit" name="change_admin_password" class="btn btn-primary">修改密码</button>
</form>
</div>
</div>
</body>
</html>

@ -1,292 +1,299 @@
<?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/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
// 检查管理员权限
if (!$_SESSION['is_admin']) {
die('权限不足:只有管理员可以访问此页面');
}
$userRepo = new UserRepository();
$message = '';
$error = '';
// 处理创建用户
if (isset($_POST['create_user'])) {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$isAdmin = isset($_POST['is_admin']) ? 1 : 0;
$isActive = isset($_POST['is_active']) ? 1 : 0;
$usernameValidation = Validator::validateUsername($username);
if (!$usernameValidation['valid']) {
$error = implode('<br>', $usernameValidation['errors']);
} else {
if (!Validator::validateEmailDomain($username, 'test.com')) {
$error = "邮箱域名必须是 @test.com";
} else {
$passwordValidation = Validator::validatePassword($password, 6);
if (!$passwordValidation['valid']) {
$error = implode('<br>', $passwordValidation['errors']);
} else {
try {
if ($userRepo->usernameExists($username)) {
$error = "用户名已存在";
} else {
$userRepo->create($username, $password, $isAdmin, $isActive);
$message = "用户创建成功";
}
} catch (Exception $e) {
$error = "创建失败: " . $e->getMessage();
}
}
}
}
}
// 处理更新用户
if (isset($_POST['update_user'])) {
$userId = (int)$_POST['user_id'];
$data = [];
if (!empty($_POST['new_password'])) {
$passwordValidation = Validator::validatePassword($_POST['new_password'], 6);
if (!$passwordValidation['valid']) {
$error = implode('<br>', $passwordValidation['errors']);
} else {
$data['password'] = $_POST['new_password'];
}
}
if (isset($_POST['is_admin'])) {
$data['is_admin'] = (int)$_POST['is_admin'];
}
if (isset($_POST['is_active'])) {
$data['is_active'] = (int)$_POST['is_active'];
}
if (empty($error) && !empty($data)) {
if ($userRepo->update($userId, $data)) {
$message = "用户更新成功";
} else {
$error = "更新失败";
}
}
}
// 处理删除用户
if (isset($_GET['delete'])) {
$userId = (int)$_GET['delete'];
if ($userId != $_SESSION['user_id']) { // 不能删除自己
if ($userRepo->delete($userId)) {
$message = "用户删除成功";
} else {
$error = "删除失败";
}
} else {
$error = "不能删除自己的账号";
}
}
// 获取所有用户
$users = $userRepo->getAll();
?>
<!DOCTYPE html>
<html>
<head>
<title>用户管理 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #f8f9fa; font-weight: 600; }
tr:hover { background: #f8f9fa; }
.btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-primary { background: #007bff; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn-success { background: #28a745; color: white; }
.btn-small { padding: 4px 8px; font-size: 12px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: 500; }
.form-group input, .form-group select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.form-inline { display: flex; gap: 10px; align-items: flex-end; }
.form-inline .form-group { flex: 1; margin-bottom: 0; }
.badge { padding: 4px 8px; border-radius: 3px; font-size: 12px; font-weight: 500; }
.badge-admin { background: #ffc107; color: #000; }
.badge-active { background: #28a745; color: white; }
.badge-inactive { background: #6c757d; color: white; }
.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); }
.modal-content { background: white; margin: 50px auto; padding: 20px; width: 500px; border-radius: 5px; }
.close { float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
</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>
<a href="users.php">用户管理</a>
<a href="emails.php">邮件管理</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="settings.php">系统设置</a>
</div>
<div class="container">
<h2>用户管理</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<!-- 创建用户表单 -->
<h3>创建新用户</h3>
<form method="POST" class="form-inline">
<div class="form-group">
<label>邮箱地址</label>
<input type="email" name="username" placeholder="user@test.com" required>
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" placeholder="至少6个字符" required minlength="6">
</div>
<div class="form-group">
<label>管理员</label>
<input type="checkbox" name="is_admin" value="1">
</div>
<div class="form-group">
<label>激活</label>
<input type="checkbox" name="is_active" value="1" checked>
</div>
<div class="form-group">
<button type="submit" name="create_user" class="btn btn-primary">创建用户</button>
</div>
</form>
<!-- 用户列表 -->
<h3>用户列表 (<?php echo count($users); ?>)</h3>
<table>
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>角色</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo $user['id']; ?></td>
<td><?php echo htmlspecialchars($user['username']); ?></td>
<td>
<?php if ($user['is_admin']): ?>
<span class="badge badge-admin">管理员</span>
<?php else: ?>
<span>普通用户</span>
<?php endif; ?>
</td>
<td>
<?php if ($user['is_active']): ?>
<span class="badge badge-active">激活</span>
<?php else: ?>
<span class="badge badge-inactive">禁用</span>
<?php endif; ?>
</td>
<td><?php echo $user['created_at']; ?></td>
<td>
<a href="#" onclick="editUser(<?php echo htmlspecialchars(json_encode($user)); ?>); return false;" class="btn btn-primary btn-small">编辑</a>
<?php if ($user['id'] != $_SESSION['user_id']): ?>
<a href="?delete=<?php echo $user['id']; ?>" class="btn btn-danger btn-small" onclick="return confirm('确定要删除此用户吗?');">删除</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- 编辑用户模态框 -->
<div id="editModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
<h3>编辑用户</h3>
<form method="POST">
<input type="hidden" name="user_id" id="edit_user_id">
<div class="form-group">
<label>用户名</label>
<input type="text" id="edit_username" readonly style="background: #f5f5f5;">
</div>
<div class="form-group">
<label>新密码(留空则不修改)</label>
<input type="password" name="new_password" placeholder="留空则不修改">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_admin" id="edit_is_admin" value="1"> 管理员
</label>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_active" id="edit_is_active" value="1"> 激活
</label>
</div>
<button type="submit" name="update_user" class="btn btn-success">保存</button>
<button type="button" onclick="closeModal()" class="btn">取消</button>
</form>
</div>
</div>
<script>
function editUser(user) {
document.getElementById('edit_user_id').value = user.id;
document.getElementById('edit_username').value = user.username;
document.getElementById('edit_is_admin').checked = user.is_admin == 1;
document.getElementById('edit_is_active').checked = user.is_active == 1;
document.getElementById('editModal').style.display = 'block';
}
function closeModal() {
document.getElementById('editModal').style.display = 'none';
}
window.onclick = function(event) {
var modal = document.getElementById('editModal');
if (event.target == modal) {
closeModal();
}
}
</script>
</body>
</html>
<?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/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php';
require_once __DIR__ . '/../src/storage/SystemSettingsRepository.php';
session_start();
// 身份验证
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$settingsRepo = new SystemSettingsRepository();
// 获取域名设置(放在函数定义之前)
$domain = $settingsRepo->get('domain', 'test.com');
$userRepo = new UserRepository();
$message = '';
$error = '';
// 处理创建用户
if (isset($_POST['create_user'])) {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$isAdmin = isset($_POST['is_admin']) ? 1 : 0;
$isActive = isset($_POST['is_active']) ? 1 : 0;
$usernameValidation = Validator::validateUsername($username);
if (!$usernameValidation['valid']) {
$error = implode('<br>', $usernameValidation['errors']);
} else {
if (!Validator::validateEmailDomain($username, $domain)) {
$error = "邮箱域名必须是 @".$domain;
} else {
$passwordValidation = Validator::validatePassword($password, 6);
if (!$passwordValidation['valid']) {
$error = implode('<br>', $passwordValidation['errors']);
} else {
try {
if ($userRepo->usernameExists($username)) {
$error = "用户名已存在";
} else {
$userRepo->create($username, $password, $isAdmin, $isActive);
$message = "用户创建成功";
}
} catch (Exception $e) {
$error = "创建失败: " . $e->getMessage();
}
}
}
}
}
// 处理更新用户
if (isset($_POST['update_user'])) {
$userId = (int)$_POST['user_id'];
$data = [];
if (!empty($_POST['new_password'])) {
$passwordValidation = Validator::validatePassword($_POST['new_password'], 6);
if (!$passwordValidation['valid']) {
$error = implode('<br>', $passwordValidation['errors']);
} else {
$data['password'] = $_POST['new_password'];
}
}
/**if (isset($_POST['is_admin'])) {
$data['is_admin'] = (int)$_POST['is_admin'];
}
if (isset($_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 ($userRepo->update($userId, $data)) {
$message = "用户更新成功";
} else {
$error = "更新失败";
}
}
}
// 处理删除用户
if (isset($_GET['delete'])) {
$userId = (int)$_GET['delete'];
if ($userId != $_SESSION['user_id']) { // 不能删除自己
if ($userRepo->delete($userId)) {
$message = "用户删除成功";
} else {
$error = "删除失败";
}
} else {
$error = "不能删除自己的账号";
}
}
// 获取所有用户
$users = $userRepo->getAll();
?>
<!DOCTYPE html>
<html>
<head>
<title>用户管理 - 邮件服务器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.header { background: #007bff; color: white; padding: 15px; margin: -20px -20px 20px -20px; }
.menu { background: white; padding: 10px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.menu a { margin-right: 15px; text-decoration: none; color: #007bff; }
.container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message { background: #d4edda; color: #155724; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 5px; margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #f8f9fa; font-weight: 600; }
tr:hover { background: #f8f9fa; }
.btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-primary { background: #007bff; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn-success { background: #28a745; color: white; }
.btn-small { padding: 4px 8px; font-size: 12px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: 500; }
.form-group input, .form-group select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.form-inline { display: flex; gap: 10px; align-items: flex-end; }
.form-inline .form-group { flex: 1; margin-bottom: 0; }
.badge { padding: 4px 8px; border-radius: 3px; font-size: 12px; font-weight: 500; }
.badge-admin { background: #ffc107; color: #000; }
.badge-active { background: #28a745; color: white; }
.badge-inactive { background: #6c757d; color: white; }
.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); }
.modal-content { background: white; margin: 50px auto; padding: 20px; width: 500px; border-radius: 5px; }
.close { float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
</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>
<a href="users.php">用户管理</a>
<a href="broadcast.php">群发邮件</a>
<a href="filters.php">过滤规则</a>
<a href="logs.php">系统日志</a>
<a href="services.php">服务管理</a>
<a href="settings.php">系统设置</a>
<a href="help.php">帮助</a>
</div>
<div class="container">
<h2>用户管理</h2>
<?php if ($message): ?>
<div class="message"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<!-- 创建用户表单 -->
<h3>创建新用户</h3>
<form method="POST" class="form-inline">
<div class="form-group">
<label>邮箱地址</label>
<input type="email" name="username" placeholder="user@<?= htmlspecialchars($domain) ?>" required>
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" placeholder="至少6个字符" required minlength="6">
</div>
<div class="form-group">
<label>管理员</label>
<input type="checkbox" name="is_admin" value="1">
</div>
<div class="form-group">
<label>激活</label>
<input type="checkbox" name="is_active" value="1" checked>
</div>
<div class="form-group">
<button type="submit" name="create_user" class="btn btn-primary">创建用户</button>
</div>
</form>
<!-- 用户列表 -->
<h3>用户列表 (<?php echo count($users); ?>)</h3>
<table>
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>角色</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo $user['id']; ?></td>
<td><?php echo htmlspecialchars($user['username']); ?></td>
<td>
<?php if ($user['is_admin']): ?>
<span class="badge badge-admin">管理员</span>
<?php else: ?>
<span>普通用户</span>
<?php endif; ?>
</td>
<td>
<?php if ($user['is_active']): ?>
<span class="badge badge-active">激活</span>
<?php else: ?>
<span class="badge badge-inactive">禁用</span>
<?php endif; ?>
</td>
<td><?php echo $user['created_at']; ?></td>
<td>
<a href="#" onclick="editUser(<?php echo htmlspecialchars(json_encode($user)); ?>); return false;" class="btn btn-primary btn-small">编辑</a>
<?php if ($user['id'] != $_SESSION['user_id']): ?>
<a href="?delete=<?php echo $user['id']; ?>" class="btn btn-danger btn-small" onclick="return confirm('确定要删除此用户吗?');">删除</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- 编辑用户模态框 -->
<div id="editModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
<h3>编辑用户</h3>
<form method="POST">
<input type="hidden" name="user_id" id="edit_user_id">
<div class="form-group">
<label>用户名</label>
<input type="text" id="edit_username" readonly style="background: #f5f5f5;">
</div>
<div class="form-group">
<label>新密码(留空则不修改)</label>
<input type="password" name="new_password" placeholder="留空则不修改">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_admin" id="edit_is_admin" value="1"> 管理员
</label>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_active" id="edit_is_active" value="1"> 激活
</label>
</div>
<button type="submit" name="update_user" class="btn btn-success">保存</button>
<button type="button" onclick="closeModal()" class="btn">取消</button>
</form>
</div>
</div>
<script>
function editUser(user) {
document.getElementById('edit_user_id').value = user.id;
document.getElementById('edit_username').value = user.username;
document.getElementById('edit_is_admin').checked = user.is_admin == 1;
document.getElementById('edit_is_active').checked = user.is_active == 1;
document.getElementById('editModal').style.display = 'block';
}
function closeModal() {
document.getElementById('editModal').style.display = 'none';
}
window.onclick = function(event) {
var modal = document.getElementById('editModal');
if (event.target == modal) {
closeModal();
}
}
</script>
</body>
</html>

@ -1,11 +1,11 @@
#!/bin/bash
echo "重置数据库..."
docker-compose down -v
echo "启动数据库SQL会自动执行..."
docker-compose up -d
echo "等待数据库初始化10秒..."
sleep 10
echo "验证数据..."
docker-compose exec mysql mysql -umail_user -puser123 mail_server -e "SELECT id, username, is_admin FROM users; SELECT COUNT(*) as email_count FROM emails;"
echo "完成!"
#!/bin/bash
echo "重置数据库..."
docker-compose down -v
echo "启动数据库SQL会自动执行..."
docker-compose up -d
echo "等待数据库初始化10秒..."
sleep 10
echo "验证数据..."
docker-compose exec mysql mysql -umail_user -puser123 mail_server -e "SELECT id, username, is_admin FROM users; SELECT COUNT(*) as email_count FROM emails;"
echo "完成!"

@ -1,135 +1,135 @@
-- ============================================
-- 邮件服务器完整数据库初始化脚本
-- ============================================
-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS mail_server
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE mail_server;
-- 创建用户并授权(关键步骤!)
CREATE USER IF NOT EXISTS 'mail_user'@'%' IDENTIFIED BY 'user123';
GRANT ALL PRIVILEGES ON mail_server.* TO 'mail_user'@'%';
FLUSH PRIVILEGES;
-- ============================================
-- 核心功能表
-- ============================================
-- 用户表
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
is_admin TINYINT(1) DEFAULT 0,
is_active TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 邮件表兼容两种存储方式SimpleSmtpServer用sender/recipientSmtpHandler用sender_id/recipient_id
CREATE TABLE IF NOT EXISTS emails (
id INT AUTO_INCREMENT PRIMARY KEY,
sender VARCHAR(100),
recipient VARCHAR(100),
sender_id INT,
recipient_id INT,
subject VARCHAR(200),
body TEXT,
headers TEXT,
size_bytes INT DEFAULT 0,
is_deleted TINYINT(1) DEFAULT 0,
is_read TINYINT(1) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (recipient_id) REFERENCES users(id) ON DELETE SET NULL
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- 系统日志表
CREATE TABLE IF NOT EXISTS server_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
log_type VARCHAR(50),
message TEXT,
client_ip VARCHAR(45),
user_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- ============================================
-- 管理功能表
-- ============================================
-- 系统设置表
CREATE TABLE IF NOT EXISTS system_settings (
id INT AUTO_INCREMENT PRIMARY KEY,
setting_key VARCHAR(100) UNIQUE NOT NULL,
setting_value TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- 过滤规则表
CREATE TABLE IF NOT EXISTS filter_rules (
id INT AUTO_INCREMENT PRIMARY KEY,
rule_type ENUM('email', 'ip') NOT NULL,
rule_value VARCHAR(255) NOT NULL,
action ENUM('block', 'allow') DEFAULT 'block',
description TEXT,
is_active TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_rule (rule_type, rule_value)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- 服务状态表
CREATE TABLE IF NOT EXISTS service_status (
id INT AUTO_INCREMENT PRIMARY KEY,
service_name VARCHAR(50) UNIQUE NOT NULL,
is_running TINYINT(1) DEFAULT 0,
pid INT,
last_started_at TIMESTAMP NULL,
last_stopped_at TIMESTAMP NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- 用户邮箱大小限制表
CREATE TABLE IF NOT EXISTS user_mailbox_limits (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
size_limit_bytes BIGINT DEFAULT 104857600, -- 100MB
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE KEY unique_user (user_id)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- ============================================
-- 插入默认数据
-- ============================================
-- 插入测试用户密码都是123456
INSERT INTO users (username, password_hash, is_admin, is_active) VALUES
('admin@test.com', '$2y$10$jB21V61k9aLAyp5.5qBpV.L70Aq6.XrtJrvlNI28bOXeJboLBJwoq', 1, 1),
('user1@test.com', '$2y$10$jB21V61k9aLAyp5.5qBpV.L70Aq6.XrtJrvlNI28bOXeJboLBJwoq', 0, 1)
ON DUPLICATE KEY UPDATE username = VALUES(username);
-- 插入测试邮件
INSERT INTO emails (sender, recipient, subject, body) VALUES
('admin@test.com', 'user1@test.com', '欢迎使用邮件系统', '这是第一封测试邮件'),
('user1@test.com', 'admin@test.com', '回复测试', '我收到了,谢谢!')
ON DUPLICATE KEY UPDATE id = id;
-- 插入系统默认设置
INSERT INTO system_settings (setting_key, setting_value) VALUES
('smtp_port', '25'),
('pop3_port', '110'),
('domain', 'test.com'),
('mailbox_size_limit', '104857600'), -- 100MB
('smtp_enabled', '1'),
('pop3_enabled', '1'),
('log_path', '/var/log/mailserver'),
('log_max_size', '10485760') -- 10MB
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value);
-- 插入服务状态初始记录
INSERT INTO service_status (service_name, is_running) VALUES
('smtp', 0),
('pop3', 0)
ON DUPLICATE KEY UPDATE service_name = VALUES(service_name);
-- ============================================
-- 邮件服务器完整数据库初始化脚本
-- ============================================
-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS mail_server
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE mail_server;
-- 创建用户并授权(关键步骤!)
CREATE USER IF NOT EXISTS 'mail_user'@'%' IDENTIFIED BY 'user123';
GRANT ALL PRIVILEGES ON mail_server.* TO 'mail_user'@'%';
FLUSH PRIVILEGES;
-- ============================================
-- 核心功能表
-- ============================================
-- 用户表
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
is_admin TINYINT(1) DEFAULT 0,
is_active TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 邮件表兼容两种存储方式SimpleSmtpServer用sender/recipientSmtpHandler用sender_id/recipient_id
CREATE TABLE IF NOT EXISTS emails (
id INT AUTO_INCREMENT PRIMARY KEY,
sender VARCHAR(100),
recipient VARCHAR(100),
sender_id INT,
recipient_id INT,
subject VARCHAR(200),
body TEXT,
headers TEXT,
size_bytes INT DEFAULT 0,
is_deleted TINYINT(1) DEFAULT 0,
is_read TINYINT(1) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (recipient_id) REFERENCES users(id) ON DELETE SET NULL
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- 系统日志表
CREATE TABLE IF NOT EXISTS server_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
log_type VARCHAR(50),
message TEXT,
client_ip VARCHAR(45),
user_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- ============================================
-- 管理功能表
-- ============================================
-- 系统设置表
CREATE TABLE IF NOT EXISTS system_settings (
id INT AUTO_INCREMENT PRIMARY KEY,
setting_key VARCHAR(100) UNIQUE NOT NULL,
setting_value TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- 过滤规则表
CREATE TABLE IF NOT EXISTS filter_rules (
id INT AUTO_INCREMENT PRIMARY KEY,
rule_type ENUM('email', 'ip') NOT NULL,
rule_value VARCHAR(255) NOT NULL,
action ENUM('block', 'allow') DEFAULT 'block',
description TEXT,
is_active TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_rule (rule_type, rule_value)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- 服务状态表
CREATE TABLE IF NOT EXISTS service_status (
id INT AUTO_INCREMENT PRIMARY KEY,
service_name VARCHAR(50) UNIQUE NOT NULL,
is_running TINYINT(1) DEFAULT 0,
pid INT,
last_started_at TIMESTAMP NULL,
last_stopped_at TIMESTAMP NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- 用户邮箱大小限制表
CREATE TABLE IF NOT EXISTS user_mailbox_limits (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
size_limit_bytes BIGINT DEFAULT 104857600, -- 100MB
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE KEY unique_user (user_id)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB;
-- ============================================
-- 插入默认数据
-- ============================================
-- 插入测试用户密码都是123456
INSERT INTO users (username, password_hash, is_admin, is_active) VALUES
('admin@test.com', '$2y$10$jB21V61k9aLAyp5.5qBpV.L70Aq6.XrtJrvlNI28bOXeJboLBJwoq', 1, 1),
('user1@test.com', '$2y$10$jB21V61k9aLAyp5.5qBpV.L70Aq6.XrtJrvlNI28bOXeJboLBJwoq', 0, 1)
ON DUPLICATE KEY UPDATE username = VALUES(username);
-- 插入测试邮件
INSERT INTO emails (sender, recipient, subject, body) VALUES
('admin@test.com', 'user1@test.com', '欢迎使用邮件系统', '这是第一封测试邮件'),
('user1@test.com', 'admin@test.com', '回复测试', '我收到了,谢谢!')
ON DUPLICATE KEY UPDATE id = id;
-- 插入系统默认设置
INSERT INTO system_settings (setting_key, setting_value) VALUES
('smtp_port', '25'),
('pop3_port', '110'),
('domain', 'test.com'),
('mailbox_size_limit', '104857600'), -- 100MB
('smtp_enabled', '1'),
('pop3_enabled', '1'),
('log_path', '/var/log/mailserver'),
('log_max_size', '10485760') -- 10MB
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value);
-- 插入服务状态初始记录
INSERT INTO service_status (service_name, is_running) VALUES
('smtp', 0),
('pop3', 0)
ON DUPLICATE KEY UPDATE service_name = VALUES(service_name);

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

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

@ -1,106 +1,106 @@
<?php
/**
* 测试用户注册功能
* 用法: php scripts/test_register.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/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php';
echo "=== 用户注册功能测试 ===\n\n";
try {
$userRepo = new UserRepository();
// 测试1: 验证邮箱格式
echo "测试1: 验证邮箱格式\n";
$testEmails = [
'valid@test.com' => true,
'invalid-email' => false,
'test@test.com' => true,
'user@wrong.com' => false,
];
foreach ($testEmails as $email => $expected) {
$isValid = Validator::validateEmail($email);
$domainValid = Validator::validateEmailDomain($email, 'test.com');
$result = $isValid && ($expected ? $domainValid : !$domainValid);
echo " {$email}: " . ($result ? "✓" : "✗") . "\n";
}
// 测试2: 验证密码强度
echo "\n测试2: 验证密码强度\n";
$testPasswords = [
'12345' => false, // 太短
'123456' => true, // 符合最小长度
'password123' => true,
];
foreach ($testPasswords as $password => $expected) {
$validation = Validator::validatePassword($password, 6);
$result = $validation['valid'] === $expected;
echo " '{$password}': " . ($result ? "✓" : "✗") . "\n";
if (!$validation['valid']) {
echo " 错误: " . implode(', ', $validation['errors']) . "\n";
}
}
// 测试3: 检查用户名是否存在
echo "\n测试3: 检查用户名是否存在\n";
$existingUser = $userRepo->findByUsername('admin@test.com');
if ($existingUser) {
echo " admin@test.com 存在: ✓\n";
} else {
echo " admin@test.com 不存在: ✗\n";
}
// 测试4: 创建测试用户(如果不存在)
echo "\n测试4: 创建测试用户\n";
$testUsername = 'testuser@test.com';
if ($userRepo->usernameExists($testUsername)) {
echo " 测试用户已存在,跳过创建\n";
} else {
try {
$newUser = $userRepo->create($testUsername, 'test123456', false, true);
echo " 创建用户成功: ✓\n";
echo " 用户ID: {$newUser['id']}\n";
echo " 用户名: {$newUser['username']}\n";
echo " 是否管理员: " . ($newUser['is_admin'] ? '是' : '否') . "\n";
} catch (Exception $e) {
echo " 创建用户失败: ✗ - " . $e->getMessage() . "\n";
}
}
// 测试5: 验证密码
echo "\n测试5: 验证密码\n";
$testUser = $userRepo->findByUsername($testUsername);
if ($testUser) {
$verified = $userRepo->verifyPassword($testUsername, 'test123456');
if ($verified) {
echo " 密码验证成功: ✓\n";
} else {
echo " 密码验证失败: ✗\n";
}
}
// 测试6: 获取所有用户
echo "\n测试6: 获取用户列表\n";
$users = $userRepo->getAll(10);
echo " 用户总数: " . count($users) . "\n";
foreach ($users as $user) {
echo " - {$user['username']} (ID: {$user['id']}, " .
($user['is_admin'] ? '管理员' : '普通用户') . ", " .
($user['is_active'] ? '激活' : '禁用') . ")\n";
}
echo "\n=== 测试完成 ===\n";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
echo "堆栈跟踪:\n" . $e->getTraceAsString() . "\n";
}
<?php
/**
* 测试用户注册功能
* 用法: php scripts/test_register.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/Validator.php';
require_once __DIR__ . '/../src/utils/Security.php';
echo "=== 用户注册功能测试 ===\n\n";
try {
$userRepo = new UserRepository();
// 测试1: 验证邮箱格式
echo "测试1: 验证邮箱格式\n";
$testEmails = [
'valid@test.com' => true,
'invalid-email' => false,
'test@test.com' => true,
'user@wrong.com' => false,
];
foreach ($testEmails as $email => $expected) {
$isValid = Validator::validateEmail($email);
$domainValid = Validator::validateEmailDomain($email, 'test.com');
$result = $isValid && ($expected ? $domainValid : !$domainValid);
echo " {$email}: " . ($result ? "✓" : "✗") . "\n";
}
// 测试2: 验证密码强度
echo "\n测试2: 验证密码强度\n";
$testPasswords = [
'12345' => false, // 太短
'123456' => true, // 符合最小长度
'password123' => true,
];
foreach ($testPasswords as $password => $expected) {
$validation = Validator::validatePassword($password, 6);
$result = $validation['valid'] === $expected;
echo " '{$password}': " . ($result ? "✓" : "✗") . "\n";
if (!$validation['valid']) {
echo " 错误: " . implode(', ', $validation['errors']) . "\n";
}
}
// 测试3: 检查用户名是否存在
echo "\n测试3: 检查用户名是否存在\n";
$existingUser = $userRepo->findByUsername('admin@test.com');
if ($existingUser) {
echo " admin@test.com 存在: ✓\n";
} else {
echo " admin@test.com 不存在: ✗\n";
}
// 测试4: 创建测试用户(如果不存在)
echo "\n测试4: 创建测试用户\n";
$testUsername = 'testuser@test.com';
if ($userRepo->usernameExists($testUsername)) {
echo " 测试用户已存在,跳过创建\n";
} else {
try {
$newUser = $userRepo->create($testUsername, 'test123456', false, true);
echo " 创建用户成功: ✓\n";
echo " 用户ID: {$newUser['id']}\n";
echo " 用户名: {$newUser['username']}\n";
echo " 是否管理员: " . ($newUser['is_admin'] ? '是' : '否') . "\n";
} catch (Exception $e) {
echo " 创建用户失败: ✗ - " . $e->getMessage() . "\n";
}
}
// 测试5: 验证密码
echo "\n测试5: 验证密码\n";
$testUser = $userRepo->findByUsername($testUsername);
if ($testUser) {
$verified = $userRepo->verifyPassword($testUsername, 'test123456');
if ($verified) {
echo " 密码验证成功: ✓\n";
} else {
echo " 密码验证失败: ✗\n";
}
}
// 测试6: 获取所有用户
echo "\n测试6: 获取用户列表\n";
$users = $userRepo->getAll(10);
echo " 用户总数: " . count($users) . "\n";
foreach ($users as $user) {
echo " - {$user['username']} (ID: {$user['id']}, " .
($user['is_admin'] ? '管理员' : '普通用户') . ", " .
($user['is_active'] ? '激活' : '禁用') . ")\n";
}
echo "\n=== 测试完成 ===\n";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
echo "堆栈跟踪:\n" . $e->getTraceAsString() . "\n";
}

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

@ -1,150 +1,150 @@
<?php
require_once __DIR__ . '/../storage/Database.php';
require_once __DIR__ . '/../storage/UserRepository.php';
require_once __DIR__ . '/../storage/EmailRepository.php';
/**
* 群发邮件服务
*/
class BroadcastService {
private $db;
private $userRepo;
private $emailRepo;
public function __construct() {
$this->db = Database::getInstance();
$this->userRepo = new UserRepository();
$this->emailRepo = new EmailRepository();
}
/**
* 群发邮件给所有用户
* @param string $senderEmail 发件人邮箱
* @param string $subject 主题
* @param string $body 内容
* @return array ['success' => int, 'failed' => int, 'errors' => array]
*/
public function broadcastToAll($senderEmail, $subject, $body) {
$users = $this->userRepo->getAll();
$sender = $this->userRepo->findByUsername($senderEmail);
if (!$sender) {
return ['success' => 0, 'failed' => 0, 'errors' => ['发件人不存在']];
}
$success = 0;
$failed = 0;
$errors = [];
foreach ($users as $user) {
// 跳过发件人自己
if ($user['username'] === $senderEmail) {
continue;
}
// 跳过禁用的用户
if (!$user['is_active']) {
continue;
}
try {
$this->sendEmail($sender['id'], $user['id'], $subject, $body);
$success++;
} catch (Exception $e) {
$failed++;
$errors[] = "发送给 {$user['username']} 失败: " . $e->getMessage();
}
}
return [
'success' => $success,
'failed' => $failed,
'errors' => $errors
];
}
/**
* 群发邮件给指定用户列表
* @param string $senderEmail 发件人邮箱
* @param array $recipientEmails 收件人邮箱列表
* @param string $subject 主题
* @param string $body 内容
* @return array ['success' => int, 'failed' => int, 'errors' => array]
*/
public function broadcastToUsers($senderEmail, $recipientEmails, $subject, $body) {
$sender = $this->userRepo->findByUsername($senderEmail);
if (!$sender) {
return ['success' => 0, 'failed' => 0, 'errors' => ['发件人不存在']];
}
$success = 0;
$failed = 0;
$errors = [];
foreach ($recipientEmails as $email) {
$recipient = $this->userRepo->findByUsername(trim($email));
if (!$recipient) {
$failed++;
$errors[] = "用户 {$email} 不存在";
continue;
}
if (!$recipient['is_active']) {
$failed++;
$errors[] = "用户 {$email} 已被禁用";
continue;
}
try {
$this->sendEmail($sender['id'], $recipient['id'], $subject, $body);
$success++;
} catch (Exception $e) {
$failed++;
$errors[] = "发送给 {$email} 失败: " . $e->getMessage();
}
}
return [
'success' => $success,
'failed' => $failed,
'errors' => $errors
];
}
/**
* 发送邮件
* @param int $senderId 发件人ID
* @param int $recipientId 收件人ID
* @param string $subject 主题
* @param string $body 内容
* @return bool 是否成功
*/
private function sendEmail($senderId, $recipientId, $subject, $body) {
$sender = $this->userRepo->findById($senderId);
$recipient = $this->userRepo->findById($recipientId);
if (!$sender || !$recipient) {
throw new Exception("发件人或收件人不存在");
}
$sizeBytes = strlen($subject) + strlen($body);
$stmt = $this->db->prepare("
INSERT INTO emails (sender_id, recipient_id, sender, recipient, subject, body, size_bytes, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
");
return $stmt->execute([
$senderId,
$recipientId,
$sender['username'],
$recipient['username'],
$subject,
$body,
$sizeBytes
]);
}
}
<?php
require_once __DIR__ . '/../storage/Database.php';
require_once __DIR__ . '/../storage/UserRepository.php';
require_once __DIR__ . '/../storage/EmailRepository.php';
/**
* 群发邮件服务
*/
class BroadcastService {
private $db;
private $userRepo;
private $emailRepo;
public function __construct() {
$this->db = Database::getInstance();
$this->userRepo = new UserRepository();
$this->emailRepo = new EmailRepository();
}
/**
* 群发邮件给所有用户
* @param string $senderEmail 发件人邮箱
* @param string $subject 主题
* @param string $body 内容
* @return array ['success' => int, 'failed' => int, 'errors' => array]
*/
public function broadcastToAll($senderEmail, $subject, $body) {
$users = $this->userRepo->getAll();
$sender = $this->userRepo->findByUsername($senderEmail);
if (!$sender) {
return ['success' => 0, 'failed' => 0, 'errors' => ['发件人不存在']];
}
$success = 0;
$failed = 0;
$errors = [];
foreach ($users as $user) {
// 跳过发件人自己
if ($user['username'] === $senderEmail) {
continue;
}
// 跳过禁用的用户
if (!$user['is_active']) {
continue;
}
try {
$this->sendEmail($sender['id'], $user['id'], $subject, $body);
$success++;
} catch (Exception $e) {
$failed++;
$errors[] = "发送给 {$user['username']} 失败: " . $e->getMessage();
}
}
return [
'success' => $success,
'failed' => $failed,
'errors' => $errors
];
}
/**
* 群发邮件给指定用户列表
* @param string $senderEmail 发件人邮箱
* @param array $recipientEmails 收件人邮箱列表
* @param string $subject 主题
* @param string $body 内容
* @return array ['success' => int, 'failed' => int, 'errors' => array]
*/
public function broadcastToUsers($senderEmail, $recipientEmails, $subject, $body) {
$sender = $this->userRepo->findByUsername($senderEmail);
if (!$sender) {
return ['success' => 0, 'failed' => 0, 'errors' => ['发件人不存在']];
}
$success = 0;
$failed = 0;
$errors = [];
foreach ($recipientEmails as $email) {
$recipient = $this->userRepo->findByUsername(trim($email));
if (!$recipient) {
$failed++;
$errors[] = "用户 {$email} 不存在";
continue;
}
if (!$recipient['is_active']) {
$failed++;
$errors[] = "用户 {$email} 已被禁用";
continue;
}
try {
$this->sendEmail($sender['id'], $recipient['id'], $subject, $body);
$success++;
} catch (Exception $e) {
$failed++;
$errors[] = "发送给 {$email} 失败: " . $e->getMessage();
}
}
return [
'success' => $success,
'failed' => $failed,
'errors' => $errors
];
}
/**
* 发送邮件
* @param int $senderId 发件人ID
* @param int $recipientId 收件人ID
* @param string $subject 主题
* @param string $body 内容
* @return bool 是否成功
*/
private function sendEmail($senderId, $recipientId, $subject, $body) {
$sender = $this->userRepo->findById($senderId);
$recipient = $this->userRepo->findById($recipientId);
if (!$sender || !$recipient) {
throw new Exception("发件人或收件人不存在");
}
$sizeBytes = strlen($subject) + strlen($body);
$stmt = $this->db->prepare("
INSERT INTO emails (sender_id, recipient_id, sender, recipient, subject, body, size_bytes, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
");
return $stmt->execute([
$senderId,
$recipientId,
$sender['username'],
$recipient['username'],
$subject,
$body,
$sizeBytes
]);
}
}

@ -0,0 +1,32 @@
<?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();
}
}

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

@ -1,309 +1,326 @@
<?php
/**
* 最简POP3服务器 - 负责取信
* 运行php src/Pop3Server.php
*/
class SimplePop3Server
{
private $socket;
private $isRunning = false;
private $db;
private $currentUser = null;
private $currentUserId = null;
private $userEmails = [];
private $deletedEmails = []; // 存储待删除的邮件ID
public function __construct($host = '0.0.0.0', $port = 110)
{
echo "POP3服务器启动在 {$host}:{$port}\n";
echo "按 Ctrl+C 停止\n\n";
$this->connectDB();
}
private function connectDB()
{
try {
$this->db = new PDO(
'mysql:host=127.0.0.1;port=3308;dbname=mail_server',
'mail_user',
'user123'
);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "数据库连接成功\n";
} catch (PDOException $e) {
echo "数据库连接失败: " . $e->getMessage() . "\n";
exit(1);
}
}
public function start()
{
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($this->socket, '0.0.0.0', 110);
socket_listen($this->socket, 5);
$this->isRunning = true;
while ($this->isRunning) {
$client = socket_accept($this->socket);
if ($client !== false) {
$this->handleClient($client);
socket_close($client);
$this->currentUser = null; // 重置用户状态
}
}
}
private function handleClient($client)
{
// 获取客户端IP地址
socket_getpeername($client, $clientIp);
$clientIp = $clientIp ?: 'unknown';
// 记录连接日志
$this->log("客户端连接", $clientIp);
$this->send($client, "+OK POP3 Simple Server Ready");
$state = 'AUTH'; // 状态AUTH -> TRANSACTION -> UPDATE
$this->deletedEmails = []; // 重置删除列表
try {
while (true) {
$input = socket_read($client, 1024);
if ($input === false || trim($input) === '') {
break;
}
$input_trimmed = trim($input);
$command = strtoupper($input_trimmed);
echo "客户端: {$input_trimmed}\n";
if ($state === 'AUTH') {
// 认证阶段
if (strpos($command, 'USER ') === 0) {
$username = trim(substr($input_trimmed, 5)); // 使用原始输入,保持大小写
if ($this->userExists($username)) {
$this->currentUser = $username;
$this->send($client, "+OK User found");
$this->log("用户认证: USER {$username}", $clientIp);
} else {
$this->send($client, "-ERR User not found");
$this->log("用户不存在: {$username}", $clientIp);
}
} elseif (strpos($command, 'PASS ') === 0) {
if ($this->currentUser) {
// 提取密码
$password = substr($input, 5); // 保留原始大小写
$password = trim($password);
// 验证密码
if ($this->verifyPassword($this->currentUser, $password)) {
$this->loadUserEmails();
$this->send($client, "+OK Logged in, {$this->userEmails['count']} messages");
$state = 'TRANSACTION';
$this->log("登录成功: {$this->currentUser}", $clientIp, $this->currentUserId);
} else {
$this->send($client, "-ERR Invalid password");
$this->currentUser = null; // 重置用户状态
$this->log("密码错误: {$this->currentUser}", $clientIp);
}
} else {
$this->send($client, "-ERR USER first");
}
} elseif ($command === 'QUIT') {
$this->send($client, "+OK Bye");
$this->log("客户端断开连接", $clientIp);
break;
}
} elseif ($state === 'TRANSACTION') {
// 事务阶段
if ($command === 'STAT') {
// 统计时排除已标记删除的邮件
$activeCount = $this->userEmails['count'] - count($this->deletedEmails);
$activeSize = $this->userEmails['total_size'];
foreach ($this->deletedEmails as $deletedId) {
if (isset($this->userEmails['emails'][$deletedId])) {
$activeSize -= $this->userEmails['emails'][$deletedId]['size'];
}
}
$this->send($client, "+OK {$activeCount} {$activeSize}");
} elseif ($command === 'LIST') {
$activeCount = $this->userEmails['count'] - count($this->deletedEmails);
$response = "+OK {$activeCount} messages\n";
foreach ($this->userEmails['emails'] as $id => $email) {
// 跳过已标记删除的邮件
if (!in_array($id, $this->deletedEmails)) {
$response .= "{$id} {$email['size']}\n";
}
}
$response .= ".";
$this->send($client, $response);
} elseif (strpos($command, 'RETR ') === 0) {
$msgId = intval(substr($command, 5));
if (isset($this->userEmails['emails'][$msgId]) && !in_array($msgId, $this->deletedEmails)) {
$email = $this->userEmails['emails'][$msgId];
$response = "+OK {$email['size']} octets\n";
$response .= "From: {$email['sender']}\n";
$response .= "To: {$email['recipient']}\n";
$response .= "Subject: {$email['subject']}\n";
$response .= "Date: {$email['date']}\n\n";
$response .= $email['body'] . "\n.";
$this->send($client, $response);
$this->log("读取邮件: ID {$msgId}", $clientIp, $this->currentUserId);
} else {
$this->send($client, "-ERR No such message");
}
} elseif (strpos($command, 'DELE ') === 0) {
// 删除邮件命令
$msgId = intval(substr($command, 5));
if (isset($this->userEmails['emails'][$msgId]) && !in_array($msgId, $this->deletedEmails)) {
$this->deletedEmails[] = $msgId;
$this->send($client, "+OK Message {$msgId} deleted");
$this->log("标记删除邮件: ID {$msgId}", $clientIp, $this->currentUserId);
} else {
$this->send($client, "-ERR No such message");
}
} elseif ($command === 'QUIT') {
// 在QUIT时执行实际删除
$this->processDeletions();
$this->send($client, "+OK Bye");
$this->log("客户端断开连接", $clientIp, $this->currentUserId);
$state = 'UPDATE';
break;
} else {
$this->send($client, "-ERR Unknown command");
}
}
}
} catch (Exception $e) {
$this->log("处理客户端错误: " . $e->getMessage(), $clientIp);
} finally {
// 重置状态
$this->currentUser = null;
$this->currentUserId = null;
$this->deletedEmails = [];
}
}
private function send($client, $message)
{
socket_write($client, $message . "\r\n");
echo "服务器: {$message}\n";
}
private function userExists($username)
{
$stmt = $this->db->prepare("SELECT id FROM users WHERE username = ? AND is_active = 1");
$stmt->execute([$username]);
return $stmt->rowCount() > 0;
}
private function verifyPassword($username, $password)
{
try {
$stmt = $this->db->prepare("SELECT password_hash FROM users WHERE username = ? AND is_active = 1");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && isset($user['password_hash'])) {
return password_verify($password, $user['password_hash']);
}
return false;
} catch (Exception $e) {
echo "密码验证错误: " . $e->getMessage() . "\n";
return false;
}
}
private function loadUserEmails()
{
$stmt = $this->db->prepare(
"SELECT id, sender, recipient, subject, body, created_at, size_bytes
FROM emails
WHERE recipient = ? AND is_deleted = 0
ORDER BY created_at"
);
$stmt->execute([$this->currentUser]);
$this->userEmails = ['count' => 0, 'total_size' => 0, 'emails' => []];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$id = $row['id'];
// 使用数据库中的size_bytes如果没有则估算
$size = $row['size_bytes'] > 0 ? $row['size_bytes'] : (strlen($row['body']) + 100);
$this->userEmails['emails'][$id] = [
'sender' => $row['sender'],
'recipient' => $row['recipient'],
'subject' => $row['subject'],
'body' => $row['body'],
'date' => $row['created_at'],
'size' => $size
];
$this->userEmails['count']++;
$this->userEmails['total_size'] += $size;
}
// 获取用户ID
$userStmt = $this->db->prepare("SELECT id FROM users WHERE username = ?");
$userStmt->execute([$this->currentUser]);
$user = $userStmt->fetch();
$this->currentUserId = $user ? $user['id'] : null;
echo "为用户 {$this->currentUser} 加载了 {$this->userEmails['count']} 封邮件\n";
}
/**
* 处理删除操作在QUIT时执行
*/
private function processDeletions()
{
if (empty($this->deletedEmails)) {
return;
}
try {
$placeholders = implode(',', array_fill(0, count($this->deletedEmails), '?'));
$stmt = $this->db->prepare(
"UPDATE emails SET is_deleted = 1 WHERE id IN ({$placeholders}) AND recipient = ?"
);
$params = array_merge($this->deletedEmails, [$this->currentUser]);
$stmt->execute($params);
$deletedCount = $stmt->rowCount();
echo "删除了 {$deletedCount} 封邮件\n";
} catch (Exception $e) {
echo "删除邮件失败: " . $e->getMessage() . "\n";
error_log("删除邮件错误: " . $e->getMessage());
}
}
/**
* 记录日志到数据库
*/
private function log($message, $clientIp = 'unknown', $userId = null)
{
try {
$stmt = $this->db->prepare(
"INSERT INTO server_logs (log_type, message, client_ip, user_id) VALUES (?, ?, ?, ?)"
);
$stmt->execute(['POP3', $message, $clientIp, $userId]);
} catch (Exception $e) {
// 日志记录失败不影响主流程
error_log("日志记录失败: " . $e->getMessage());
}
}
}
// 如果直接运行这个文件
if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) {
$server = new SimplePop3Server();
$server->start();
}
<?php
/**
* 最简POP3服务器 - 负责取信
* 运行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
{
private $socket;
private $isRunning = false;
private $db;
private $currentUser = null;
private $currentUserId = null;
private $userEmails = [];
private $deletedEmails = []; // 存储待删除的邮件ID
// 新增:存储主机和端口
private $host;
private $port;
public function __construct($host = '0.0.0.0', $port = 110)
{
echo "POP3服务器启动在 {$host}:{$port}\n";
echo "按 Ctrl+C 停止\n\n";
$this->host = $host;
$this->port = $port;
$this->connectDB();
}
private function connectDB()
{
try {
$this->db = new PDO(
'mysql:host=127.0.0.1;port=3308;dbname=mail_server',
'mail_user',
'user123'
);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "数据库连接成功\n";
} catch (PDOException $e) {
echo "数据库连接失败: " . $e->getMessage() . "\n";
exit(1);
}
}
public function start()
{
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($this->socket,$this->host, $this->port);
socket_listen($this->socket, 5);
$this->isRunning = true;
while ($this->isRunning) {
$client = socket_accept($this->socket);
if ($client !== false) {
$this->handleClient($client);
socket_close($client);
$this->currentUser = null; // 重置用户状态
}
}
}
private function handleClient($client)
{
// 获取客户端IP地址
socket_getpeername($client, $clientIp);
$clientIp = $clientIp ?: 'unknown';
// 记录连接日志
$this->log("客户端连接", $clientIp);
$this->send($client, "+OK POP3 Simple Server Ready");
$state = 'AUTH'; // 状态AUTH -> TRANSACTION -> UPDATE
$this->deletedEmails = []; // 重置删除列表
try {
while (true) {
$input = socket_read($client, 1024);
if ($input === false || trim($input) === '') {
break;
}
$input_trimmed = trim($input);
$command = strtoupper($input_trimmed);
//echo "客户端: {$input_trimmed}\n";
if ($state === 'AUTH') {
// 认证阶段
if (strpos($command, 'USER ') === 0) {
$username = trim(substr($input_trimmed, 5)); // 使用原始输入,保持大小写
if ($this->userExists($username)) {
$this->currentUser = $username;
$this->send($client, "+OK User found");
$this->log("用户认证: USER {$username}", $clientIp);
} else {
$this->send($client, "-ERR User not found");
$this->log("用户不存在: {$username}", $clientIp);
}
} elseif (strpos($command, 'PASS ') === 0) {
if ($this->currentUser) {
// 提取密码
$password = substr($input, 5); // 保留原始大小写
$password = trim($password);
// 验证密码
if ($this->verifyPassword($this->currentUser, $password)) {
$this->loadUserEmails();
$this->send($client, "+OK Logged in, {$this->userEmails['count']} messages");
$state = 'TRANSACTION';
$this->log("登录成功: {$this->currentUser}", $clientIp, $this->currentUserId);
} else {
$this->send($client, "-ERR Invalid password");
$this->currentUser = null; // 重置用户状态
$this->log("密码错误: {$this->currentUser}", $clientIp);
}
} else {
$this->send($client, "-ERR USER first");
}
} elseif ($command === 'QUIT') {
$this->send($client, "+OK Bye");
$this->log("客户端断开连接", $clientIp);
break;
}
} elseif ($state === 'TRANSACTION') {
// 事务阶段
if ($command === 'STAT') {
// 统计时排除已标记删除的邮件
$activeCount = $this->userEmails['count'] - count($this->deletedEmails);
$activeSize = $this->userEmails['total_size'];
foreach ($this->deletedEmails as $deletedId) {
if (isset($this->userEmails['emails'][$deletedId])) {
$activeSize -= $this->userEmails['emails'][$deletedId]['size'];
}
}
$this->send($client, "+OK {$activeCount} {$activeSize}");
} elseif ($command === 'LIST') {
$activeCount = $this->userEmails['count'] - count($this->deletedEmails);
$response = "+OK {$activeCount} messages\n";
foreach ($this->userEmails['emails'] as $id => $email) {
// 跳过已标记删除的邮件
if (!in_array($id, $this->deletedEmails)) {
$response .= "{$id} {$email['size']}\n";
}
}
$response .= ".";
$this->send($client, $response);
} elseif (strpos($command, 'RETR ') === 0) {
$msgId = intval(substr($command, 5));
if (isset($this->userEmails['emails'][$msgId]) && !in_array($msgId, $this->deletedEmails)) {
$email = $this->userEmails['emails'][$msgId];
$response = "+OK {$email['size']} octets\n";
$response .= "From: {$email['sender']}\n";
$response .= "To: {$email['recipient']}\n";
$response .= "Subject: {$email['subject']}\n";
$response .= "Date: {$email['date']}\n\n";
$response .= $email['body'] . "\n.";
$this->send($client, $response);
$this->log("读取邮件: ID {$msgId}", $clientIp, $this->currentUserId);
} else {
$this->send($client, "-ERR No such message");
}
} elseif (strpos($command, 'DELE ') === 0) {
// 删除邮件命令
$msgId = intval(substr($command, 5));
if (isset($this->userEmails['emails'][$msgId]) && !in_array($msgId, $this->deletedEmails)) {
$this->deletedEmails[] = $msgId;
$this->send($client, "+OK Message {$msgId} deleted");
$this->log("标记删除邮件: ID {$msgId}", $clientIp, $this->currentUserId);
} else {
$this->send($client, "-ERR No such message");
}
} elseif ($command === 'QUIT') {
// 在QUIT时执行实际删除
$this->processDeletions();
$this->send($client, "+OK Bye");
$this->log("客户端断开连接", $clientIp, $this->currentUserId);
$state = 'UPDATE';
break;
} else {
$this->send($client, "-ERR Unknown command");
}
}
}
} catch (Exception $e) {
$this->log("处理客户端错误: " . $e->getMessage(), $clientIp);
} finally {
// 重置状态
$this->currentUser = null;
$this->currentUserId = null;
$this->deletedEmails = [];
}
}
private function send($client, $message)
{
socket_write($client, $message . "\r\n");
echo "服务器: {$message}\n";
}
private function userExists($username)
{
$stmt = $this->db->prepare("SELECT id FROM users WHERE username = ? AND is_active = 1");
$stmt->execute([$username]);
return $stmt->rowCount() > 0;
}
private function verifyPassword($username, $password)
{
try {
$stmt = $this->db->prepare("SELECT password_hash FROM users WHERE username = ? AND is_active = 1");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && isset($user['password_hash'])) {
return password_verify($password, $user['password_hash']);
}
return false;
} catch (Exception $e) {
echo "密码验证错误: " . $e->getMessage() . "\n";
return false;
}
}
private function loadUserEmails()
{
$stmt = $this->db->prepare(
"SELECT id, sender, recipient, subject, body, created_at, size_bytes
FROM emails
WHERE recipient = ? AND is_deleted = 0
ORDER BY created_at"
);
$stmt->execute([$this->currentUser]);
$this->userEmails = ['count' => 0, 'total_size' => 0, 'emails' => []];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$id = $row['id'];
// 使用数据库中的size_bytes如果没有则估算
$size = $row['size_bytes'] > 0 ? $row['size_bytes'] : (strlen($row['body']) + 100);
$this->userEmails['emails'][$id] = [
'sender' => $row['sender'],
'recipient' => $row['recipient'],
'subject' => $row['subject'],
'body' => $row['body'],
'date' => $row['created_at'],
'size' => $size
];
$this->userEmails['count']++;
$this->userEmails['total_size'] += $size;
}
// 获取用户ID
$userStmt = $this->db->prepare("SELECT id FROM users WHERE username = ?");
$userStmt->execute([$this->currentUser]);
$user = $userStmt->fetch();
$this->currentUserId = $user ? $user['id'] : null;
echo "为用户 {$this->currentUser} 加载了 {$this->userEmails['count']} 封邮件\n";
}
/**
* 处理删除操作在QUIT时执行
*/
private function processDeletions()
{
if (empty($this->deletedEmails)) {
return;
}
try {
$placeholders = implode(',', array_fill(0, count($this->deletedEmails), '?'));
$stmt = $this->db->prepare(
"UPDATE emails SET is_deleted = 1 WHERE id IN ({$placeholders}) AND recipient = ?"
);
$params = array_merge($this->deletedEmails, [$this->currentUser]);
$stmt->execute($params);
$deletedCount = $stmt->rowCount();
echo "删除了 {$deletedCount} 封邮件\n";
} catch (Exception $e) {
echo "删除邮件失败: " . $e->getMessage() . "\n";
error_log("删除邮件错误: " . $e->getMessage());
}
}
/**
* 记录日志到数据库
*/
private function log($message, $clientIp = 'unknown', $userId = null)
{
try {
$stmt = $this->db->prepare(
"INSERT INTO server_logs (log_type, message, client_ip, user_id) VALUES (?, ?, ?, ?)"
);
$stmt->execute(['POP3', $message, $clientIp, $userId]);
} catch (Exception $e) {
// 日志记录失败不影响主流程
error_log("日志记录失败: " . $e->getMessage());
}
}
}
// 如果直接运行这个文件
if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) {
$server = new SimplePop3Server('0.0.0.0',$pop3Port);
$server->start();
}
?>

@ -1,220 +0,0 @@
<?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,445 +1,462 @@
<?php
// 设置PHP内部编码为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服务器 - 负责收信
* 运行php src/SmtpServer.php
*/
require_once __DIR__ . '/../storage/FilterRepository.php';
require_once __DIR__ . '/../storage/MailboxRepository.php';
require_once __DIR__ . '/../storage/UserRepository.php';
class SimpleSmtpServer
{
private $socket;
private $isRunning = false;
private $db; // 数据库连接
private $filterRepo;
private $mailboxRepo;
private $userRepo;
public function __construct($host = '0.0.0.0', $port = 25)
{
echo "SMTP服务器启动在 {$host}:{$port}\n";
echo "按 Ctrl+C 停止\n\n";
// 连接数据库
$this->connectDB();
// 初始化Repository
$this->filterRepo = new FilterRepository();
$this->mailboxRepo = new MailboxRepository();
$this->userRepo = new UserRepository();
}
private function connectDB()
{
try {
$this->db = new PDO(
'mysql:host=127.0.0.1;port=3308;dbname=mail_server',
'mail_user',
'user123',
[
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
// $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "数据库连接成功\n";
} catch (PDOException $e) {
echo "数据库连接失败: " . $e->getMessage() . "\n";
exit(1);
}
}
public function start()
{
// 创建socket邮局开门
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($this->socket, '0.0.0.0', 25);
socket_listen($this->socket, 5);
$this->isRunning = true;
while ($this->isRunning) {
// 等待客户连接(有人来寄信)
$client = socket_accept($this->socket);
if ($client !== false) {
$this->handleClient($client);
socket_close($client);
}
}
socket_close($this->socket);
}
private function handleClient($client)
{
// 获取客户端IP地址
socket_getpeername($client, $clientIp);
$clientIp = $clientIp ?: 'unknown';
// 记录连接日志
$this->log("客户端连接", $clientIp);
try {
// 1. 说欢迎语
$this->send($client, "220 mail.simple.com SMTP Ready");
// 2. 等待客户说 HELO
$this->waitForCommand($client, 'HELO', 'HELO或EHLO');
// 3. 检查IP过滤规则
if ($this->filterRepo->isIPBlocked($clientIp)) {
$this->send($client, "550 IP address blocked");
$this->log("IP被阻止: {$clientIp}", $clientIp);
return;
}
$this->send($client, "250 OK - Hello");
// 4. 问:谁寄的?
$from = $this->waitForCommand($client, 'MAIL FROM:', '发件人邮箱');
// 检查发件人邮箱过滤规则
if ($this->filterRepo->isEmailBlocked($from)) {
$this->send($client, "550 Sender email blocked");
$this->log("发件人邮箱被阻止: {$from}", $clientIp);
return;
}
$this->send($client, "250 Sender OK");
// 5. 支持多收件人
$recipients = [];
$this->send($client, "250 Recipient OK");
// 循环接收多个RCPT TO命令
while (true) {
$input = socket_read($client, 1024);
if ($input === false) break;
$input = trim($input);
echo "客户端: {$input}\n";
if (stripos($input, 'RCPT TO:') === 0) {
// 提取收件人邮箱
if (preg_match('/<(.+?)>/', $input, $matches)) {
$to = $matches[1];
// 检查收件人邮箱过滤规则
if ($this->filterRepo->isEmailBlocked($to)) {
$this->send($client, "550 Recipient email blocked");
$this->log("收件人邮箱被阻止: {$to}", $clientIp);
continue;
}
// 初步检查收件人邮箱大小限制(使用估算值,实际检查在接收邮件内容后)
$user = $this->userRepo->findByUsername($to);
if ($user) {
$usage = $this->mailboxRepo->getUsage($user['id']);
$estimatedSize = 50000; // 估算邮件大小50KB
if ($usage['used'] + $estimatedSize > $usage['limit']) {
$this->send($client, "552 Mailbox full");
$this->log("收件人邮箱已满(初步检查): {$to}", $clientIp);
continue;
}
}
$recipients[] = $to;
$this->send($client, "250 Recipient OK");
}
} elseif (stripos($input, 'DATA') === 0) {
// 收到DATA命令跳出循环
break;
} elseif (strtoupper($input) === 'QUIT') {
$this->send($client, "221 Bye");
return;
} else {
$this->send($client, "500 Error: Expected RCPT TO or DATA");
}
}
if (empty($recipients)) {
$this->send($client, "503 No valid recipients");
$this->log("没有有效收件人", $clientIp);
return;
}
// 6. 说:开始写内容吧
$this->send($client, "354 Start mail input, end with <CRLF>.<CRLF>");
// 7. 接收邮件内容
$emailContent = $this->receiveEmailContent($client);
$emailSize = strlen($emailContent);
// 8. 再次检查邮箱大小限制(使用实际邮件大小)
$validRecipients = [];
foreach ($recipients as $to) {
$user = $this->userRepo->findByUsername($to);
if ($user) {
$usage = $this->mailboxRepo->getUsage($user['id']);
if ($usage['used'] + $emailSize > $usage['limit']) {
$this->log("收件人邮箱已满(实际检查): {$to}", $clientIp);
continue;
}
}
$validRecipients[] = $to;
}
if (empty($validRecipients)) {
$this->send($client, "552 All recipients' mailboxes are full");
$this->log("所有收件人邮箱已满", $clientIp);
return;
}
// 9. 保存到数据库(支持多收件人)
$successCount = 0;
foreach ($validRecipients as $to) {
if ($this->saveEmail($from, $to, $emailContent, $clientIp, $emailSize)) {
$successCount++;
}
}
// 10. 告诉客户:收到了
if ($successCount > 0) {
$this->send($client, "250 Mail accepted");
$this->log("邮件发送成功: {$from} -> " . implode(', ', $validRecipients) . " ({$successCount}个收件人)", $clientIp);
} else {
$this->send($client, "550 Mail delivery failed");
$this->log("邮件发送失败: {$from} -> " . implode(', ', $validRecipients), $clientIp);
}
// 11. 等客户说再见
$this->waitForCommand($client, 'QUIT', 'QUIT');
$this->send($client, "221 Bye");
echo "收到一封邮件:{$from} -> " . implode(', ', $validRecipients) . "\n";
} catch (Exception $e) {
$this->log("处理客户端错误: " . $e->getMessage(), $clientIp);
$this->send($client, "500 Internal server error");
}
}
private function send($client, $message)
{
socket_write($client, $message . "\r\n");
}
private function waitForCommand($client, $expected, $description)
{
while (true) {
$input = socket_read($client, 1024);
if ($input === false) break;
$input = trim($input);
echo "客户端: {$input}\n";
if (stripos($input, $expected) === 0) {
// 提取邮箱地址
if (preg_match('/<(.+?)>/', $input, $matches)) {
return $matches[1];
}
return $input;
}
// 如果收到QUIT直接退出
if (strtoupper($input) === 'QUIT') {
$this->send($client, "221 Bye");
exit(0);
}
$this->send($client, "500 Error: Expected {$description}");
}
}
private function receiveEmailContent($client)
{
$content = "";
$buffer = "";
while (true) {
$data = socket_read($client, 1024, PHP_BINARY_READ);
if ($data === false || $data === '') {
break;
}
$buffer .= $data;
// 按行处理
while (($pos = strpos($buffer, "\r\n")) !== false) {
$line = substr($buffer, 0, $pos);
$buffer = substr($buffer, $pos + 2);
// 如果遇到单独一行的 '.' 就结束
if (trim($line) === '.') {
return $content;
}
$content .= $line . "\r\n";
}
}
return $content;
}
private function saveEmail($from, $to, $content, $clientIp = 'unknown', $emailSize = null)
{
try {
// 1. 解析邮件内容(先不转换编码)
$lines = explode("\r\n", $content);
$subject = "无主题";
$body = "";
$inBody = false;
foreach ($lines as $line) {
if (!$inBody && stripos($line, 'Subject:') === 0) {
$subject = trim(substr($line, 8));
// 解码MIME编码的主题
$subject = $this->decodeMimeHeader($subject);
}
if (!$inBody && trim($line) === '') {
$inBody = true;
continue;
}
if ($inBody) {
$body .= $line . "\n";
}
}
// 2. 清理正文
$body = trim($body);
// 3. 检测当前编码 - 直接假设为UTF-8
$detectedEncoding = 'UTF-8'; // 直接指定,不检测了
echo "使用编码: {$detectedEncoding}\n";
// 4. 验证确实是UTF-8如果不是就转换
if (!mb_check_encoding($subject, 'UTF-8')) {
$subject = mb_convert_encoding($subject, 'UTF-8', 'auto');
}
if (!mb_check_encoding($body, 'UTF-8')) {
$body = mb_convert_encoding($body, 'UTF-8', 'auto');
}
// 5. 计算邮件大小(如果未提供则计算)
if ($emailSize === null) {
$emailSize = strlen($content);
}
// 6. 获取收件人用户ID
$user = $this->userRepo->findByUsername($to);
$recipientId = $user ? $user['id'] : null;
// 7. 使用参数绑定确保UTF-8传输
$stmt = $this->db->prepare(
"INSERT INTO emails (sender, recipient, recipient_id, subject, body, size_bytes) VALUES (?, ?, ?, ?, ?, ?)"
);
// 绑定参数时指定字符集
$stmt->bindValue(1, $from, PDO::PARAM_STR);
$stmt->bindValue(2, $to, PDO::PARAM_STR);
$stmt->bindValue(3, $recipientId, PDO::PARAM_INT);
$stmt->bindValue(4, $subject, PDO::PARAM_STR);
$stmt->bindValue(5, $body, PDO::PARAM_STR);
$stmt->bindValue(6, $emailSize, PDO::PARAM_INT);
$stmt->execute();
echo "邮件保存成功:{$from} -> {$to}\n";
echo " 主题: {$subject}\n";
echo " 长度: {$emailSize} 字节\n";
return true;
} catch (Exception $e) {
echo "保存邮件失败: " . $e->getMessage() . "\n";
error_log("邮件保存错误: " . $e->getMessage() . "\n" . $e->getTraceAsString());
$this->log("保存邮件失败: " . $e->getMessage(), $clientIp);
return false;
}
}
/**
* 记录日志到数据库
*/
private function log($message, $clientIp = 'unknown', $userId = null)
{
try {
$stmt = $this->db->prepare(
"INSERT INTO server_logs (log_type, message, client_ip, user_id) VALUES (?, ?, ?, ?)"
);
$stmt->execute(['SMTP', $message, $clientIp, $userId]);
} catch (Exception $e) {
// 日志记录失败不影响主流程
error_log("日志记录失败: " . $e->getMessage());
}
}
// 添加MIME头解码方法
private function decodeMimeHeader($header)
{
// 处理 =?UTF-8?B?5L2g5aW9?= 这样的MIME编码
$decoded = '';
$parts = preg_split('/(=\?[^?]+\?[BQ]\?[^?]+\?=)/i', $header, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach ($parts as $part) {
if (preg_match('/=\?([^\?]+)\?([BQ])\?([^\?]+)\?=/i', $part, $matches)) {
$charset = $matches[1];
$encoding = strtoupper($matches[2]);
$text = $matches[3];
if ($encoding === 'B') {
// Base64解码
$decodedText = base64_decode($text);
} elseif ($encoding === 'Q') {
// Quoted-Printable解码
$decodedText = quoted_printable_decode(str_replace('_', ' ', $text));
}
if (isset($decodedText)) {
$decoded .= mb_convert_encoding($decodedText, 'UTF-8', $charset);
}
} else {
$decoded .= $part;
}
}
return $decoded ?: $header;
}
public function stop()
{
$this->isRunning = false;
}
}
// 如果直接运行这个文件
if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) {
// 检查是否有权限监听25端口需要sudo
if (posix_getuid() != 0) {
echo "注意需要sudo权限监听25端口\n";
echo "请运行sudo php " . __FILE__ . "\n";
exit(1);
}
$server = new SimpleSmtpServer();
$server->start();
}
<?php
/**
* 一体化的SMTP服务器
*
* 注意:本类同时包含:
* 1. 网络层Server功能- 负责socket通信
* 2. 协议层Handler功能- 负责SMTP协议处理
*
* 这种设计对于教学项目是合适的,保持了简单性。
* 生产环境可以考虑拆分为 SmtpServer + SmtpHandler。
*/
ini_set('default_charset', 'UTF-8');
mb_internal_encoding('UTF-8');
/**
* 最简SMTP服务器 - 负责收信
* 运行php src/SmtpServer.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 SimpleSmtpServer
{
private $socket;
private $isRunning = false;
private $db; // 数据库连接
private $filterRepo;
private $mailboxRepo;
private $userRepo;
// 新增:存储主机和端口
private $host;
private $port;
private $clientIp;
public function __construct($host = '0.0.0.0', $port = 25)
{
echo "SMTP服务器启动在 {$host}:{$port}\n";
echo "按 Ctrl+C 停止\n\n";
// 存储配置,而不是立即使用
$this->host = $host;
$this->port = $port;
// 连接数据库
$this->connectDB();
// 初始化Repository
$this->filterRepo = new FilterRepository();
$this->mailboxRepo = new MailboxRepository();
$this->userRepo = new UserRepository();
}
private function connectDB()
{
try {
$this->db = new PDO(
'mysql:host=127.0.0.1;port=3308;dbname=mail_server',
'mail_user',
'user123',
[
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
// $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "数据库连接成功\n";
} catch (PDOException $e) {
echo "数据库连接失败: " . $e->getMessage() . "\n";
exit(1);
}
}
public function start()
{
// 创建socket邮局开门
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($this->socket, '0.0.0.0',$this->port);
socket_listen($this->socket, 5);
$this->isRunning = true;
while ($this->isRunning) {
// 等待客户连接(有人来寄信)
$client = socket_accept($this->socket);
if ($client !== false) {
$this->handleClient($client);
socket_close($client);
}
}
socket_close($this->socket);
}
private function handleClient($client)
{
// 获取客户端IP地址
socket_getpeername($client, $clientIp);
$clientIp = $clientIp ?: 'unknown';
$this->clientIp = $clientIp;
// 记录连接日志
$this->log("客户端连接",$this->clientIp);
try {
// 1. 说欢迎语
$this->send($client, "220 mail.simple.com SMTP Ready");
// 2. 等待客户说 HELO
$this->waitForCommand($client, 'HELO', 'HELO或EHLO');
// 3. 检查IP过滤规则
if ($this->filterRepo->isIPBlocked($clientIp)) {
$this->send($client, "550 IP address blocked");
$this->log("IP被阻止: {$clientIp}", $clientIp);
return;
}
$this->send($client, "250 OK - Hello");
// 4. 问:谁寄的?
$from = $this->waitForCommand($client, 'MAIL FROM:', '发件人邮箱');
// 检查发件人邮箱过滤规则
if ($this->filterRepo->isEmailBlocked($from)) {
$this->send($client, "550 Sender email blocked");
$this->log("发件人邮箱被阻止: {$from}", $clientIp);
return;
}
$this->send($client, "250 Sender OK");
// 5. 支持多收件人
$recipients = [];
$this->send($client, "250 Recipient OK");
// 循环接收多个RCPT TO命令
while (true) {
$input = socket_read($client, 1024);
if ($input === false) break;
$input = trim($input);
//echo "客户端: {$input}\n";
if (stripos($input, 'RCPT TO:') === 0) {
// 提取收件人邮箱
if (preg_match('/<(.+?)>/', $input, $matches)) {
$to = $matches[1];
// 新增:收件人过滤
if ($this->filterRepo->isEmailBlocked($to)) {
$this->send($client, "550 Recipient email blocked");
$this->log("收件人邮箱被过滤: {$to}", $this->clientIp);
continue; // 直接拒绝,不加入 $recipients
}
// 初步检查收件人邮箱大小限制(使用估算值,实际检查在接收邮件内容后)
$user = $this->userRepo->findByUsername($to);
if ($user) {
$usage = $this->mailboxRepo->getUsage($user['id']);
$estimatedSize = 50000; // 估算邮件大小50KB
if ($usage['used'] + $estimatedSize > $usage['limit']) {
$this->send($client, "552 Mailbox full");
$this->log("收件人邮箱已满(初步检查): {$to}", $clientIp);
continue;
}
}
$recipients[] = $to;
$this->send($client, "250 Recipient OK");
}
} elseif (stripos($input, 'DATA') === 0) {
// 收到DATA命令跳出循环
break;
} elseif (strtoupper($input) === 'QUIT') {
$this->send($client, "221 Bye");
return;
} else {
$this->send($client, "500 Error: Expected RCPT TO or DATA");
}
}
if (empty($recipients)) {
$this->send($client, "503 No valid recipients");
$this->log("没有有效收件人", $clientIp);
return;
}
// 6. 说:开始写内容吧
$this->send($client, "354 Start mail input, end with <CRLF>.<CRLF>");
// 7. 接收邮件内容
$emailContent = $this->receiveEmailContent($client);
$emailSize = strlen($emailContent);
// 8. 再次检查邮箱大小限制(使用实际邮件大小)
$validRecipients = [];
foreach ($recipients as $to) {
$user = $this->userRepo->findByUsername($to);
if ($user) {
$usage = $this->mailboxRepo->getUsage($user['id']);
if ($usage['used'] + $emailSize > $usage['limit']) {
$this->log("收件人邮箱已满(实际检查): {$to}", $clientIp);
continue;
}
}
$validRecipients[] = $to;
}
if (empty($validRecipients)) {
$this->send($client, "552 All recipients' mailboxes are full");
$this->log("所有收件人邮箱已满", $clientIp);
return;
}
// 9. 保存到数据库(支持多收件人)
$successCount = 0;
foreach ($validRecipients as $to) {
if ($this->saveEmail($from, $to, $emailContent, $this->clientIp, $emailSize)) {
$successCount++;
}
}
// 10. 告诉客户:收到了
if ($successCount > 0) {
$this->send($client, "250 Mail accepted");
$this->log("邮件发送成功: {$from} -> " . implode(', ', $validRecipients) . " ({$successCount}个收件人)", $clientIp);
} else {
$this->send($client, "550 Mail delivery failed");
$this->log("邮件发送失败: {$from} -> " . implode(', ', $validRecipients), $clientIp);
}
// 11. 等客户说再见
$this->waitForCommand($client, 'QUIT', 'QUIT');
$this->send($client, "221 Bye");
echo "收到一封邮件:{$from} -> " . implode(', ', $validRecipients) . "\n";
} catch (Exception $e) {
$this->log("处理客户端错误: " . $e->getMessage(), $clientIp);
$this->send($client, "500 Internal server error");
}
}
private function send($client, $message)
{
socket_write($client, $message . "\r\n");
}
private function waitForCommand($client, $expected, $description)
{
while (true) {
$input = socket_read($client, 1024);
if ($input === false) break;
$input = trim($input);
//echo "客户端: {$input}\n";
if (stripos($input, $expected) === 0) {
// 提取邮箱地址
if (preg_match('/<(.+?)>/', $input, $matches)) {
return $matches[1];
}
return $input;
}
// 如果收到QUIT直接退出
if (strtoupper($input) === 'QUIT') {
$this->send($client, "221 Bye");
exit(0);
}
$this->send($client, "500 Error: Expected {$description}");
}
}
private function receiveEmailContent($client)
{
$content = "";
$buffer = "";
while (true) {
$data = socket_read($client, 1024, PHP_BINARY_READ);
if ($data === false || $data === '') {
break;
}
$buffer .= $data;
// 按行处理
while (($pos = strpos($buffer, "\r\n")) !== false) {
$line = substr($buffer, 0, $pos);
$buffer = substr($buffer, $pos + 2);
// 如果遇到单独一行的 '.' 就结束
if (trim($line) === '.') {
return $content;
}
$content .= $line . "\r\n";
}
}
return $content;
}
private function saveEmail($from, $to, $content, $clientIp = 'unknown', $emailSize = null)
{
try {
// 1. 解析邮件内容(先不转换编码)
$lines = explode("\r\n", $content);
$subject = "无主题";
$body = "";
$inBody = false;
foreach ($lines as $line) {
if (!$inBody && stripos($line, 'Subject:') === 0) {
$subject = trim(substr($line, 8));
// 解码MIME编码的主题
$subject = $this->decodeMimeHeader($subject);
}
if (!$inBody && trim($line) === '') {
$inBody = true;
continue;
}
if ($inBody) {
$body .= $line . "\n";
}
}
// 2. 清理正文
$body = trim($body);
// 3. 检测当前编码 - 直接假设为UTF-8
$detectedEncoding = 'UTF-8'; // 直接指定,不检测了
echo "使用编码: {$detectedEncoding}\n";
// 4. 验证确实是UTF-8如果不是就转换
if (!mb_check_encoding($subject, 'UTF-8')) {
$subject = mb_convert_encoding($subject, 'UTF-8', 'auto');
}
if (!mb_check_encoding($body, 'UTF-8')) {
$body = mb_convert_encoding($body, 'UTF-8', 'auto');
}
// 5. 计算邮件大小(如果未提供则计算)
if ($emailSize === null) {
$emailSize = strlen($content);
}
// 6. 获取收件人用户ID
$user = $this->userRepo->findByUsername($to);
$recipientId = $user ? $user['id'] : null;
// 7. 使用参数绑定确保UTF-8传输
$stmt = $this->db->prepare(
"INSERT INTO emails (sender, recipient, recipient_id, subject, body, size_bytes) VALUES (?, ?, ?, ?, ?, ?)"
);
// 绑定参数时指定字符集
$stmt->bindValue(1, $from, PDO::PARAM_STR);
$stmt->bindValue(2, $to, PDO::PARAM_STR);
$stmt->bindValue(3, $recipientId, PDO::PARAM_INT);
$stmt->bindValue(4, $subject, PDO::PARAM_STR);
$stmt->bindValue(5, $body, PDO::PARAM_STR);
$stmt->bindValue(6, $emailSize, PDO::PARAM_INT);
$stmt->execute();
echo "邮件保存成功:{$from} -> {$to}\n";
echo " 主题: {$subject}\n";
echo " 长度: {$emailSize} 字节\n";
return true;
} catch (Exception $e) {
echo "保存邮件失败: " . $e->getMessage() . "\n";
error_log("邮件保存错误: " . $e->getMessage() . "\n" . $e->getTraceAsString());
$this->log("保存邮件失败: " . $e->getMessage(), $clientIp);
return false;
}
}
/**
* 记录日志到数据库
*/
private function log($message, $clientIp = 'unknown', $userId = null)
{
try {
$stmt = $this->db->prepare(
"INSERT INTO server_logs (log_type, message, client_ip, user_id) VALUES (?, ?, ?, ?)"
);
$stmt->execute(['SMTP', $message, $clientIp, $userId]);
} catch (Exception $e) {
// 日志记录失败不影响主流程
error_log("日志记录失败: " . $e->getMessage());
}
}
// 添加MIME头解码方法
private function decodeMimeHeader($header)
{
// 处理 =?UTF-8?B?5L2g5aW9?= 这样的MIME编码
$decoded = '';
$parts = preg_split('/(=\?[^?]+\?[BQ]\?[^?]+\?=)/i', $header, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach ($parts as $part) {
if (preg_match('/=\?([^\?]+)\?([BQ])\?([^\?]+)\?=/i', $part, $matches)) {
$charset = $matches[1];
$encoding = strtoupper($matches[2]);
$text = $matches[3];
if ($encoding === 'B') {
// Base64解码
$decodedText = base64_decode($text);
} elseif ($encoding === 'Q') {
// Quoted-Printable解码
$decodedText = quoted_printable_decode(str_replace('_', ' ', $text));
}
if (isset($decodedText)) {
$decoded .= mb_convert_encoding($decodedText, 'UTF-8', $charset);
}
} else {
$decoded .= $part;
}
}
return $decoded ?: $header;
}
public function stop()
{
$this->isRunning = false;
}
}
// 如果直接运行这个文件
if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) {
// 检查是否有权限监听端口需要sudo
if (posix_getuid() != 0) {
echo "注意需要sudo权限监听该端口\n";
echo "请运行sudo php " . __FILE__ . "\n";
exit(1);
}
$server = new SimpleSmtpServer('0.0.0.0',$smtpPort);
$server->start();
}
?>

@ -1,4 +1,8 @@
<?php
/**
* 数据库配置文件
* 使用环境变量或默认配置
*/
require_once __DIR__ . '/../../config/database.php';
class Database {
@ -6,8 +10,9 @@ class Database {
private $connection;
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']}";
try {

@ -1,176 +1,176 @@
<?php
require_once __DIR__ . '/Database.php';
/**
* 邮件数据访问层
* 信件管理员 - 存信、取信
*/
class EmailRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 根据ID查找邮件
* @param int $id 邮件ID
* @return array|null 邮件信息或null
*/
public function findById($id) {
$stmt = $this->db->prepare("
SELECT e.*,
u1.username as sender_username,
u2.username as recipient_username
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.id = ? AND e.is_deleted = 0
");
$stmt->execute([$id]);
return $stmt->fetch();
}
/**
* 获取用户的收件箱邮件
* @param int $userId 用户ID
* @param int $limit 限制数量
* @param int $offset 偏移量
* @return array 邮件列表
*/
public function getInbox($userId, $limit = null, $offset = 0) {
$sql = "
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.recipient_id = ? OR e.recipient = (SELECT username FROM users WHERE id = ?))
AND e.is_deleted = 0
ORDER BY e.created_at DESC
";
if ($limit !== null) {
$sql .= " LIMIT ? OFFSET ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId, $userId, $limit, $offset]);
} else {
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId, $userId]);
}
return $stmt->fetchAll();
}
/**
* 获取用户的发件箱邮件
* @param int $userId 用户ID
* @param int $limit 限制数量
* @param int $offset 偏移量
* @return array 邮件列表
*/
public function getSent($userId, $limit = null, $offset = 0) {
$sql = "
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.sender_id = ? OR e.sender = (SELECT username FROM users WHERE id = ?))
AND e.is_deleted = 0
ORDER BY e.created_at DESC
";
if ($limit !== null) {
$sql .= " LIMIT ? OFFSET ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId, $userId, $limit, $offset]);
} else {
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId, $userId]);
}
return $stmt->fetchAll();
}
/**
* 获取所有邮件(管理员功能)
* @param int $limit 限制数量
* @param int $offset 偏移量
* @return array 邮件列表
*/
public function getAll($limit = null, $offset = 0) {
$sql = "
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
";
if ($limit !== null) {
$sql .= " LIMIT ? OFFSET ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$limit, $offset]);
} else {
$stmt = $this->db->query($sql);
}
return $stmt->fetchAll();
}
/**
* 获取邮件总数
* @param int|null $userId 用户ID如果提供只统计该用户的邮件
* @return int 邮件总数
*/
public function getCount($userId = null) {
if ($userId !== null) {
$stmt = $this->db->prepare("
SELECT COUNT(*) as count FROM emails
WHERE (recipient_id = ? OR sender_id = ?) AND is_deleted = 0
");
$stmt->execute([$userId, $userId]);
} else {
$stmt = $this->db->query("SELECT COUNT(*) as count FROM emails WHERE is_deleted = 0");
}
$result = $stmt->fetch();
return (int)$result['count'];
}
/**
* 标记邮件为已读
* @param int $id 邮件ID
* @return bool 是否成功
*/
public function markAsRead($id) {
$stmt = $this->db->prepare("UPDATE emails SET is_read = 1 WHERE id = ?");
return $stmt->execute([$id]);
}
/**
* 删除邮件(软删除)
* @param int $id 邮件ID
* @return bool 是否成功
*/
public function delete($id) {
$stmt = $this->db->prepare("UPDATE emails SET is_deleted = 1 WHERE id = ?");
return $stmt->execute([$id]);
}
/**
* 永久删除邮件
* @param int $id 邮件ID
* @return bool 是否成功
*/
public function permanentDelete($id) {
$stmt = $this->db->prepare("DELETE FROM emails WHERE id = ?");
return $stmt->execute([$id]);
}
}
<?php
require_once __DIR__ . '/Database.php';
/**
* 邮件数据访问层
* 信件管理员 - 存信、取信
*/
class EmailRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 根据ID查找邮件
* @param int $id 邮件ID
* @return array|null 邮件信息或null
*/
public function findById($id) {
$stmt = $this->db->prepare("
SELECT e.*,
u1.username as sender_username,
u2.username as recipient_username
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.id = ? AND e.is_deleted = 0
");
$stmt->execute([$id]);
return $stmt->fetch();
}
/**
* 获取用户的收件箱邮件
* @param int $userId 用户ID
* @param int $limit 限制数量
* @param int $offset 偏移量
* @return array 邮件列表
*/
public function getInbox($userId, $limit = null, $offset = 0) {
$sql = "
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.recipient_id = ? OR e.recipient = (SELECT username FROM users WHERE id = ?))
AND e.is_deleted = 0
ORDER BY e.created_at DESC
";
if ($limit !== null) {
$sql .= " LIMIT ? OFFSET ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId, $userId, $limit, $offset]);
} else {
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId, $userId]);
}
return $stmt->fetchAll();
}
/**
* 获取用户的发件箱邮件
* @param int $userId 用户ID
* @param int $limit 限制数量
* @param int $offset 偏移量
* @return array 邮件列表
*/
public function getSent($userId, $limit = null, $offset = 0) {
$sql = "
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.sender_id = ? OR e.sender = (SELECT username FROM users WHERE id = ?))
AND e.is_deleted = 0
ORDER BY e.created_at DESC
";
if ($limit !== null) {
$sql .= " LIMIT ? OFFSET ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId, $userId, $limit, $offset]);
} else {
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId, $userId]);
}
return $stmt->fetchAll();
}
/**
* 获取所有邮件(管理员功能)
* @param int $limit 限制数量
* @param int $offset 偏移量
* @return array 邮件列表
*/
public function getAll($limit = null, $offset = 0) {
$sql = "
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
";
if ($limit !== null) {
$sql .= " LIMIT ? OFFSET ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$limit, $offset]);
} else {
$stmt = $this->db->query($sql);
}
return $stmt->fetchAll();
}
/**
* 获取邮件总数
* @param int|null $userId 用户ID如果提供只统计该用户的邮件
* @return int 邮件总数
*/
public function getCount($userId = null) {
if ($userId !== null) {
$stmt = $this->db->prepare("
SELECT COUNT(*) as count FROM emails
WHERE (recipient_id = ? OR sender_id = ?) AND is_deleted = 0
");
$stmt->execute([$userId, $userId]);
} else {
$stmt = $this->db->query("SELECT COUNT(*) as count FROM emails WHERE is_deleted = 0");
}
$result = $stmt->fetch();
return (int)$result['count'];
}
/**
* 标记邮件为已读
* @param int $id 邮件ID
* @return bool 是否成功
*/
public function markAsRead($id) {
$stmt = $this->db->prepare("UPDATE emails SET is_read = 1 WHERE id = ?");
return $stmt->execute([$id]);
}
/**
* 删除邮件(软删除)
* @param int $id 邮件ID
* @return bool 是否成功
*/
public function delete($id) {
$stmt = $this->db->prepare("UPDATE emails SET is_deleted = 1 WHERE id = ?");
return $stmt->execute([$id]);
}
/**
* 永久删除邮件
* @param int $id 邮件ID
* @return bool 是否成功
*/
public function permanentDelete($id) {
$stmt = $this->db->prepare("DELETE FROM emails WHERE id = ?");
return $stmt->execute([$id]);
}
}

@ -1,114 +1,121 @@
<?php
require_once __DIR__ . '/Database.php';
/**
* 过滤规则数据访问层
*/
class FilterRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 创建过滤规则
* @param string $type 规则类型 ('email' 或 'ip')
* @param string $value 规则值
* @param string $action 动作 ('block' 或 'allow')
* @param string $description 描述
* @return bool 是否成功
*/
public function create($type, $value, $action = 'block', $description = '') {
$stmt = $this->db->prepare("
INSERT INTO filter_rules (rule_type, rule_value, action, description, is_active)
VALUES (?, ?, ?, ?, 1)
");
return $stmt->execute([$type, $value, $action, $description]);
}
/**
* 获取所有过滤规则
* @return array 规则列表
*/
public function getAll() {
$stmt = $this->db->query("
SELECT * FROM filter_rules
ORDER BY rule_type, created_at DESC
");
return $stmt->fetchAll();
}
/**
* 获取激活的过滤规则
* @return array 规则列表
*/
public function getActive() {
$stmt = $this->db->query("
SELECT * FROM filter_rules
WHERE is_active = 1
ORDER BY rule_type, created_at DESC
");
return $stmt->fetchAll();
}
/**
* 检查邮箱是否被过滤
* @param string $email 邮箱地址
* @return bool 是否被阻止
*/
public function isEmailBlocked($email) {
$stmt = $this->db->prepare("
SELECT action FROM filter_rules
WHERE rule_type = 'email'
AND is_active = 1
AND (rule_value = ? OR rule_value LIKE ?)
ORDER BY action DESC
LIMIT 1
");
$stmt->execute([$email, $email]);
$result = $stmt->fetch();
return $result && $result['action'] === 'block';
}
/**
* 检查IP是否被过滤
* @param string $ip IP地址
* @return bool 是否被阻止
*/
public function isIPBlocked($ip) {
$stmt = $this->db->prepare("
SELECT action FROM filter_rules
WHERE rule_type = 'ip'
AND is_active = 1
AND rule_value = ?
ORDER BY action DESC
LIMIT 1
");
$stmt->execute([$ip]);
$result = $stmt->fetch();
return $result && $result['action'] === 'block';
}
/**
* 删除规则
* @param int $id 规则ID
* @return bool 是否成功
*/
public function delete($id) {
$stmt = $this->db->prepare("DELETE FROM filter_rules WHERE id = ?");
return $stmt->execute([$id]);
}
/**
* 更新规则状态
* @param int $id 规则ID
* @param bool $isActive 是否激活
* @return bool 是否成功
*/
public function updateStatus($id, $isActive) {
$stmt = $this->db->prepare("UPDATE filter_rules SET is_active = ? WHERE id = ?");
return $stmt->execute([$isActive ? 1 : 0, $id]);
}
}
<?php
require_once __DIR__ . '/Database.php';
/**
* 过滤规则数据访问层
*/
class FilterRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 创建过滤规则
* @param string $type 规则类型 ('email' 或 'ip')
* @param string $value 规则值
* @param string $action 动作 ('block' 或 'allow')
* @param string $description 描述
* @return bool 是否成功
*/
public function create($type, $value, $action = 'block', $description = '') {
$stmt = $this->db->prepare("
INSERT INTO filter_rules (rule_type, rule_value, action, description, is_active)
VALUES (?, ?, ?, ?, 1)
");
return $stmt->execute([$type, $value, $action, $description]);
}
/**
* 获取所有过滤规则
* @return array 规则列表
*/
public function getAll() {
$stmt = $this->db->query("
SELECT * FROM filter_rules
ORDER BY rule_type, created_at DESC
");
return $stmt->fetchAll();
}
/**
* 获取激活的过滤规则
* @return array 规则列表
*/
public function getActive() {
$stmt = $this->db->query("
SELECT * FROM filter_rules
WHERE is_active = 1
ORDER BY rule_type, created_at DESC
");
return $stmt->fetchAll();
}
/**
* 检查邮箱是否被过滤
* @param string $email 邮箱地址
* @return bool 是否被阻止
*/
public function isEmailBlocked($email) {
$stmt = $this->db->prepare("
SELECT action FROM filter_rules
WHERE rule_type = 'email'
AND is_active = 1
AND (rule_value = ? OR rule_value LIKE ?)
ORDER BY action DESC
LIMIT 1
");
$stmt->execute([$email, $email]);
$result = $stmt->fetch();
return $result && $result['action'] === 'block';
}
/**
* 检查IP是否被过滤
* @param string $ip IP地址
* @return bool 是否被阻止
*/
public function isIPBlocked($ip) {
$stmt = $this->db->prepare("
SELECT action FROM filter_rules
WHERE rule_type = 'ip'
AND is_active = 1
AND rule_value = ?
ORDER BY action DESC
LIMIT 1
");
$stmt->execute([$ip]);
$result = $stmt->fetch();
return $result && $result['action'] === 'block';
}
/**
* 删除规则
* @param int $id 规则ID
* @return bool 是否成功
*/
public function delete($id) {
$stmt = $this->db->prepare("DELETE FROM filter_rules WHERE id = ?");
return $stmt->execute([$id]);
}
/**
* 更新规则状态
* @param int $id 规则ID
* @param bool $isActive 是否激活
* @return bool 是否成功
*/
public function updateStatus($id, $isActive) {
$stmt = $this->db->prepare("UPDATE filter_rules SET is_active = ? WHERE 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,84 +1,84 @@
<?php
require_once __DIR__ . '/Database.php';
/**
* 邮箱管理数据访问层
*
* @package storage
*/
class MailboxRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 获取用户邮箱大小限制
* @param int $userId 用户ID
* @return int 大小限制(字节)
*/
public function getSizeLimit($userId) {
$stmt = $this->db->prepare("
SELECT size_limit_bytes FROM user_mailbox_limits WHERE user_id = ?
");
$stmt->execute([$userId]);
$result = $stmt->fetch();
if ($result) {
return (int)$result['size_limit_bytes'];
}
// 返回默认值
return 104857600; // 100MB
}
/**
* 设置用户邮箱大小限制
* @param int $userId 用户ID
* @param int $sizeBytes 大小限制(字节)
* @return bool 是否成功
*/
public function setSizeLimit($userId, $sizeBytes) {
$stmt = $this->db->prepare("
INSERT INTO user_mailbox_limits (user_id, size_limit_bytes)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE size_limit_bytes = VALUES(size_limit_bytes)
");
return $stmt->execute([$userId, $sizeBytes]);
}
/**
* 获取用户当前邮箱使用大小
* @param int $userId 用户ID
* @return int 已使用大小(字节)
*/
public function getUsedSize($userId) {
$stmt = $this->db->prepare("
SELECT COALESCE(SUM(size_bytes), 0) as total_size
FROM emails
WHERE recipient_id = ? AND is_deleted = 0
");
$stmt->execute([$userId]);
$result = $stmt->fetch();
return (int)($result['total_size'] ?? 0);
}
/**
* 获取用户邮箱使用情况
* @param int $userId 用户ID
* @return array ['limit' => int, 'used' => int, 'percentage' => float]
*/
public function getUsage($userId) {
$limit = $this->getSizeLimit($userId);
$used = $this->getUsedSize($userId);
$percentage = $limit > 0 ? ($used / $limit) * 100 : 0;
return [
'limit' => $limit,
'used' => $used,
'percentage' => round($percentage, 2)
];
}
}
<?php
require_once __DIR__ . '/Database.php';
/**
* 邮箱管理数据访问层
*
* @package storage
*/
class MailboxRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 获取用户邮箱大小限制
* @param int $userId 用户ID
* @return int 大小限制(字节)
*/
public function getSizeLimit($userId) {
$stmt = $this->db->prepare("
SELECT size_limit_bytes FROM user_mailbox_limits WHERE user_id = ?
");
$stmt->execute([$userId]);
$result = $stmt->fetch();
if ($result) {
return (int)$result['size_limit_bytes'];
}
// 返回默认值
return 104857600; // 100MB
}
/**
* 设置用户邮箱大小限制
* @param int $userId 用户ID
* @param int $sizeBytes 大小限制(字节)
* @return bool 是否成功
*/
public function setSizeLimit($userId, $sizeBytes) {
$stmt = $this->db->prepare("
INSERT INTO user_mailbox_limits (user_id, size_limit_bytes)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE size_limit_bytes = VALUES(size_limit_bytes)
");
return $stmt->execute([$userId, $sizeBytes]);
}
/**
* 获取用户当前邮箱使用大小
* @param int $userId 用户ID
* @return int 已使用大小(字节)
*/
public function getUsedSize($userId) {
$stmt = $this->db->prepare("
SELECT COALESCE(SUM(size_bytes), 0) as total_size
FROM emails
WHERE recipient_id = ? AND is_deleted = 0
");
$stmt->execute([$userId]);
$result = $stmt->fetch();
return (int)($result['total_size'] ?? 0);
}
/**
* 获取用户邮箱使用情况
* @param int $userId 用户ID
* @return array ['limit' => int, 'used' => int, 'percentage' => float]
*/
public function getUsage($userId) {
$limit = $this->getSizeLimit($userId);
$used = $this->getUsedSize($userId);
$percentage = $limit > 0 ? ($used / $limit) * 100 : 0;
return [
'limit' => $limit,
'used' => $used,
'percentage' => round($percentage, 2)
];
}
}

@ -1,79 +1,79 @@
<?php
require_once __DIR__ . '/Database.php';
/**
* 服务状态数据访问层
*/
class ServiceRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 获取服务状态
* @param string $serviceName 服务名称 ('smtp' 或 'pop3')
* @return array|null 服务状态或null
*/
public function getStatus($serviceName) {
$stmt = $this->db->prepare("SELECT * FROM service_status WHERE service_name = ?");
$stmt->execute([$serviceName]);
return $stmt->fetch();
}
/**
* 更新服务状态
* @param string $serviceName 服务名称
* @param bool $isRunning 是否运行
* @param int|null $pid 进程ID
* @return bool 是否成功
*/
public function updateStatus($serviceName, $isRunning, $pid = null) {
$stmt = $this->db->prepare("
INSERT INTO service_status (service_name, is_running, pid, last_started_at, last_stopped_at)
VALUES (?, ?, ?, NOW(), NULL)
ON DUPLICATE KEY UPDATE
is_running = VALUES(is_running),
pid = VALUES(pid),
last_started_at = IF(VALUES(is_running) = 1, NOW(), last_started_at),
last_stopped_at = IF(VALUES(is_running) = 0, NOW(), last_stopped_at)
");
return $stmt->execute([$serviceName, $isRunning ? 1 : 0, $pid]);
}
/**
* 获取所有服务状态
* @return array 服务状态列表
*/
public function getAllStatus() {
$stmt = $this->db->query("SELECT * FROM service_status ORDER BY service_name");
return $stmt->fetchAll();
}
/**
* 检查服务是否运行
* @param string $serviceName 服务名称
* @return bool 是否运行
*/
public function isRunning($serviceName) {
$status = $this->getStatus($serviceName);
if (!$status) {
return false;
}
// 检查进程是否真的在运行
if ($status['pid'] && $status['is_running']) {
// 检查进程是否存在
$result = shell_exec("ps -p {$status['pid']} -o pid= 2>/dev/null");
if (empty(trim($result))) {
// 进程不存在,更新状态
$this->updateStatus($serviceName, false, null);
return false;
}
}
return (bool)$status['is_running'];
}
}
<?php
require_once __DIR__ . '/Database.php';
/**
* 服务状态数据访问层
*/
class ServiceRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 获取服务状态
* @param string $serviceName 服务名称 ('smtp' 或 'pop3')
* @return array|null 服务状态或null
*/
public function getStatus($serviceName) {
$stmt = $this->db->prepare("SELECT * FROM service_status WHERE service_name = ?");
$stmt->execute([$serviceName]);
return $stmt->fetch();
}
/**
* 更新服务状态
* @param string $serviceName 服务名称
* @param bool $isRunning 是否运行
* @param int|null $pid 进程ID
* @return bool 是否成功
*/
public function updateStatus($serviceName, $isRunning, $pid = null) {
$stmt = $this->db->prepare("
INSERT INTO service_status (service_name, is_running, pid, last_started_at, last_stopped_at)
VALUES (?, ?, ?, NOW(), NULL)
ON DUPLICATE KEY UPDATE
is_running = VALUES(is_running),
pid = VALUES(pid),
last_started_at = IF(VALUES(is_running) = 1, NOW(), last_started_at),
last_stopped_at = IF(VALUES(is_running) = 0, NOW(), last_stopped_at)
");
return $stmt->execute([$serviceName, $isRunning ? 1 : 0, $pid]);
}
/**
* 获取所有服务状态
* @return array 服务状态列表
*/
public function getAllStatus() {
$stmt = $this->db->query("SELECT * FROM service_status ORDER BY service_name");
return $stmt->fetchAll();
}
/**
* 检查服务是否运行
* @param string $serviceName 服务名称
* @return bool 是否运行
*/
public function isRunning($serviceName) {
$status = $this->getStatus($serviceName);
if (!$status) {
return false;
}
// 检查进程是否真的在运行
if ($status['pid'] && $status['is_running']) {
// 检查进程是否存在
$result = shell_exec("ps -p {$status['pid']} -o pid= 2>/dev/null");
if (empty(trim($result))) {
// 进程不存在,更新状态
$this->updateStatus($serviceName, false, null);
return false;
}
}
return (bool)$status['is_running'];
}
}

@ -1,56 +1,56 @@
<?php
require_once __DIR__ . '/Database.php';
/**
* 系统设置数据访问层
*/
class SystemSettingsRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 获取设置值
* @param string $key 设置键
* @param mixed $default 默认值
* @return mixed 设置值
*/
public function get($key, $default = null) {
$stmt = $this->db->prepare("SELECT setting_value FROM system_settings WHERE setting_key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch();
return $result ? $result['setting_value'] : $default;
}
/**
* 设置值
* @param string $key 设置键
* @param mixed $value 设置值
* @return bool 是否成功
*/
public function set($key, $value) {
$stmt = $this->db->prepare("
INSERT INTO system_settings (setting_key, setting_value)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)
");
return $stmt->execute([$key, $value]);
}
/**
* 获取所有设置
* @return array 所有设置
*/
public function getAll() {
$stmt = $this->db->query("SELECT setting_key, setting_value FROM system_settings");
$results = $stmt->fetchAll();
$settings = [];
foreach ($results as $row) {
$settings[$row['setting_key']] = $row['setting_value'];
}
return $settings;
}
}
<?php
require_once __DIR__ . '/Database.php';
/**
* 系统设置数据访问层
*/
class SystemSettingsRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 获取设置值
* @param string $key 设置键
* @param mixed $default 默认值
* @return mixed 设置值
*/
public function get($key, $default = null) {
$stmt = $this->db->prepare("SELECT setting_value FROM system_settings WHERE setting_key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch();
return $result ? $result['setting_value'] : $default;
}
/**
* 设置值
* @param string $key 设置键
* @param mixed $value 设置值
* @return bool 是否成功
*/
public function set($key, $value) {
$stmt = $this->db->prepare("
INSERT INTO system_settings (setting_key, setting_value)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)
");
return $stmt->execute([$key, $value]);
}
/**
* 获取所有设置
* @return array 所有设置
*/
public function getAll() {
$stmt = $this->db->query("SELECT setting_key, setting_value FROM system_settings");
$results = $stmt->fetchAll();
$settings = [];
foreach ($results as $row) {
$settings[$row['setting_key']] = $row['setting_value'];
}
return $settings;
}
}

@ -1,173 +1,173 @@
<?php
require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/../utils/Security.php';
/**
* 用户数据访问层
* 查客户信息、创建用户等
*/
class UserRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 根据用户名查找用户
* @param string $username 用户名(邮箱)
* @return array|null 用户信息或null
*/
public function findByUsername($username) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
return $stmt->fetch();
}
/**
* 根据ID查找用户
* @param int $id 用户ID
* @return array|null 用户信息或null
*/
public function findById($id) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
/**
* 检查用户名是否已存在
* @param string $username 用户名
* @return bool 是否存在
*/
public function usernameExists($username) {
$user = $this->findByUsername($username);
return $user !== false;
}
/**
* 创建新用户
* @param string $username 用户名(邮箱)
* @param string $password 明文密码
* @param bool $isAdmin 是否为管理员
* @param bool $isActive 是否激活
* @return array 创建的用户信息
* @throws Exception 如果创建失败
*/
public function create($username, $password, $isAdmin = false, $isActive = true) {
// 检查用户名是否已存在
if ($this->usernameExists($username)) {
throw new Exception("用户名已存在");
}
// 加密密码
$passwordHash = Security::hashPassword($password);
// 插入数据库
$stmt = $this->db->prepare("
INSERT INTO users (username, password_hash, is_admin, is_active, created_at)
VALUES (?, ?, ?, ?, NOW())
");
$stmt->execute([$username, $passwordHash, $isAdmin ? 1 : 0, $isActive ? 1 : 0]);
// 返回创建的用户信息
$userId = $this->db->lastInsertId();
return $this->findById($userId);
}
/**
* 更新用户信息
* @param int $id 用户ID
* @param array $data 要更新的数据 ['password' => string, 'is_admin' => bool, 'is_active' => bool]
* @return bool 是否成功
*/
public function update($id, $data) {
$updates = [];
$params = [];
if (isset($data['password'])) {
$updates[] = "password_hash = ?";
$params[] = Security::hashPassword($data['password']);
}
if (isset($data['is_admin'])) {
$updates[] = "is_admin = ?";
$params[] = $data['is_admin'] ? 1 : 0;
}
if (isset($data['is_active'])) {
$updates[] = "is_active = ?";
$params[] = $data['is_active'] ? 1 : 0;
}
if (empty($updates)) {
return false;
}
$params[] = $id;
$sql = "UPDATE users SET " . implode(", ", $updates) . " WHERE id = ?";
$stmt = $this->db->prepare($sql);
return $stmt->execute($params);
}
/**
* 删除用户
* @param int $id 用户ID
* @return bool 是否成功
*/
public function delete($id) {
$stmt = $this->db->prepare("DELETE FROM users WHERE id = ?");
return $stmt->execute([$id]);
}
/**
* 获取所有用户列表
* @param int $limit 限制数量
* @param int $offset 偏移量
* @return array 用户列表
*/
public function getAll($limit = null, $offset = 0) {
$sql = "SELECT id, username, is_admin, is_active, created_at FROM users ORDER BY created_at DESC";
if ($limit !== null) {
$sql .= " LIMIT ? OFFSET ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$limit, $offset]);
} else {
$stmt = $this->db->query($sql);
}
return $stmt->fetchAll();
}
/**
* 获取用户总数
* @return int 用户总数
*/
public function getCount() {
$stmt = $this->db->query("SELECT COUNT(*) as count FROM users");
$result = $stmt->fetch();
return (int)$result['count'];
}
/**
* 验证用户密码
* @param string $username 用户名
* @param string $password 明文密码
* @return array|null 用户信息或null如果验证失败
*/
public function verifyPassword($username, $password) {
$user = $this->findByUsername($username);
if (!$user) {
return null;
}
if (!Security::verifyPassword($password, $user['password_hash'])) {
return null;
}
return $user;
}
}
<?php
require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/../utils/Security.php';
/**
* 用户数据访问层
* 查客户信息、创建用户等
*/
class UserRepository {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
/**
* 根据用户名查找用户
* @param string $username 用户名(邮箱)
* @return array|null 用户信息或null
*/
public function findByUsername($username) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
return $stmt->fetch();
}
/**
* 根据ID查找用户
* @param int $id 用户ID
* @return array|null 用户信息或null
*/
public function findById($id) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
/**
* 检查用户名是否已存在
* @param string $username 用户名
* @return bool 是否存在
*/
public function usernameExists($username) {
$user = $this->findByUsername($username);
return $user !== false;
}
/**
* 创建新用户
* @param string $username 用户名(邮箱)
* @param string $password 明文密码
* @param bool $isAdmin 是否为管理员
* @param bool $isActive 是否激活
* @return array 创建的用户信息
* @throws Exception 如果创建失败
*/
public function create($username, $password, $isAdmin = false, $isActive = true) {
// 检查用户名是否已存在
if ($this->usernameExists($username)) {
throw new Exception("用户名已存在");
}
// 加密密码
$passwordHash = Security::hashPassword($password);
// 插入数据库
$stmt = $this->db->prepare("
INSERT INTO users (username, password_hash, is_admin, is_active, created_at)
VALUES (?, ?, ?, ?, NOW())
");
$stmt->execute([$username, $passwordHash, $isAdmin ? 1 : 0, $isActive ? 1 : 0]);
// 返回创建的用户信息
$userId = $this->db->lastInsertId();
return $this->findById($userId);
}
/**
* 更新用户信息
* @param int $id 用户ID
* @param array $data 要更新的数据 ['password' => string, 'is_admin' => bool, 'is_active' => bool]
* @return bool 是否成功
*/
public function update($id, $data) {
$updates = [];
$params = [];
if (isset($data['password'])) {
$updates[] = "password_hash = ?";
$params[] = Security::hashPassword($data['password']);
}
if (isset($data['is_admin'])) {
$updates[] = "is_admin = ?";
$params[] = $data['is_admin'] ? 1 : 0;
}
if (isset($data['is_active'])) {
$updates[] = "is_active = ?";
$params[] = $data['is_active'] ? 1 : 0;
}
if (empty($updates)) {
return false;
}
$params[] = $id;
$sql = "UPDATE users SET " . implode(", ", $updates) . " WHERE id = ?";
$stmt = $this->db->prepare($sql);
return $stmt->execute($params);
}
/**
* 删除用户
* @param int $id 用户ID
* @return bool 是否成功
*/
public function delete($id) {
$stmt = $this->db->prepare("DELETE FROM users WHERE id = ?");
return $stmt->execute([$id]);
}
/**
* 获取所有用户列表
* @param int $limit 限制数量
* @param int $offset 偏移量
* @return array 用户列表
*/
public function getAll($limit = null, $offset = 0) {
$sql = "SELECT id, username, is_admin, is_active, created_at FROM users ORDER BY created_at DESC";
if ($limit !== null) {
$sql .= " LIMIT ? OFFSET ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$limit, $offset]);
} else {
$stmt = $this->db->query($sql);
}
return $stmt->fetchAll();
}
/**
* 获取用户总数
* @return int 用户总数
*/
public function getCount() {
$stmt = $this->db->query("SELECT COUNT(*) as count FROM users");
$result = $stmt->fetch();
return (int)$result['count'];
}
/**
* 验证用户密码
* @param string $username 用户名
* @param string $password 明文密码
* @return array|null 用户信息或null如果验证失败
*/
public function verifyPassword($username, $password) {
$user = $this->findByUsername($username);
if (!$user) {
return null;
}
if (!Security::verifyPassword($password, $user['password_hash'])) {
return null;
}
return $user;
}
}

@ -1,129 +1,129 @@
<?php
/**
* 安全工具类
* 提供密码加密、防攻击等功能
*/
class Security {
/**
* 加密密码
* @param string $password 明文密码
* @return string 加密后的密码哈希
*/
public static function hashPassword($password) {
return password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]);
}
/**
* 验证密码
* @param string $password 明文密码
* @param string $hash 密码哈希
* @return bool 是否匹配
*/
public static function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
/**
* 清理输入防止XSS攻击
* @param string $input 用户输入
* @return string 清理后的字符串
*/
public static function sanitizeInput($input) {
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
}
/**
* 生成CSRF令牌
* @return string CSRF令牌
*/
public static function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
/**
* 验证CSRF令牌
* @param string $token 待验证的令牌
* @return bool 是否有效
*/
public static function verifyCSRFToken($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
/**
* 获取客户端IP地址
* @return string IP地址
*/
public static function getClientIP() {
$ipKeys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
foreach ($ipKeys as $key) {
if (array_key_exists($key, $_SERVER) === true) {
foreach (explode(',', $_SERVER[$key]) as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
return $ip;
}
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
/**
* 防止暴力破解:检查登录尝试次数
* @param string $username 用户名
* @param int $maxAttempts 最大尝试次数
* @param int $lockoutTime 锁定时间(秒)
* @return bool 是否允许登录
*/
public static function checkLoginAttempts($username, $maxAttempts = 5, $lockoutTime = 300) {
$key = 'login_attempts_' . md5($username);
if (!isset($_SESSION[$key])) {
$_SESSION[$key] = ['count' => 0, 'time' => time()];
return true;
}
$attempts = $_SESSION[$key];
// 如果超过锁定时间,重置计数
if (time() - $attempts['time'] > $lockoutTime) {
$_SESSION[$key] = ['count' => 0, 'time' => time()];
return true;
}
// 检查是否超过最大尝试次数
if ($attempts['count'] >= $maxAttempts) {
return false;
}
return true;
}
/**
* 记录登录失败尝试
* @param string $username 用户名
*/
public static function recordLoginAttempt($username) {
$key = 'login_attempts_' . md5($username);
if (!isset($_SESSION[$key])) {
$_SESSION[$key] = ['count' => 1, 'time' => time()];
} else {
$_SESSION[$key]['count']++;
$_SESSION[$key]['time'] = time();
}
}
/**
* 清除登录尝试记录
* @param string $username 用户名
*/
public static function clearLoginAttempts($username) {
$key = 'login_attempts_' . md5($username);
unset($_SESSION[$key]);
}
}
<?php
/**
* 安全工具类
* 提供密码加密、防攻击等功能
*/
class Security {
/**
* 加密密码
* @param string $password 明文密码
* @return string 加密后的密码哈希
*/
public static function hashPassword($password) {
return password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]);
}
/**
* 验证密码
* @param string $password 明文密码
* @param string $hash 密码哈希
* @return bool 是否匹配
*/
public static function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
/**
* 清理输入防止XSS攻击
* @param string $input 用户输入
* @return string 清理后的字符串
*/
public static function sanitizeInput($input) {
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
}
/**
* 生成CSRF令牌
* @return string CSRF令牌
*/
public static function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
/**
* 验证CSRF令牌
* @param string $token 待验证的令牌
* @return bool 是否有效
*/
public static function verifyCSRFToken($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
/**
* 获取客户端IP地址
* @return string IP地址
*/
public static function getClientIP() {
$ipKeys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
foreach ($ipKeys as $key) {
if (array_key_exists($key, $_SERVER) === true) {
foreach (explode(',', $_SERVER[$key]) as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
return $ip;
}
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
/**
* 防止暴力破解:检查登录尝试次数
* @param string $username 用户名
* @param int $maxAttempts 最大尝试次数
* @param int $lockoutTime 锁定时间(秒)
* @return bool 是否允许登录
*/
public static function checkLoginAttempts($username, $maxAttempts = 5, $lockoutTime = 300) {
$key = 'login_attempts_' . md5($username);
if (!isset($_SESSION[$key])) {
$_SESSION[$key] = ['count' => 0, 'time' => time()];
return true;
}
$attempts = $_SESSION[$key];
// 如果超过锁定时间,重置计数
if (time() - $attempts['time'] > $lockoutTime) {
$_SESSION[$key] = ['count' => 0, 'time' => time()];
return true;
}
// 检查是否超过最大尝试次数
if ($attempts['count'] >= $maxAttempts) {
return false;
}
return true;
}
/**
* 记录登录失败尝试
* @param string $username 用户名
*/
public static function recordLoginAttempt($username) {
$key = 'login_attempts_' . md5($username);
if (!isset($_SESSION[$key])) {
$_SESSION[$key] = ['count' => 1, 'time' => time()];
} else {
$_SESSION[$key]['count']++;
$_SESSION[$key]['time'] = time();
}
}
/**
* 清除登录尝试记录
* @param string $username 用户名
*/
public static function clearLoginAttempts($username) {
$key = 'login_attempts_' . md5($username);
unset($_SESSION[$key]);
}
}

@ -1,157 +1,157 @@
<?php
/**
* 输入验证工具类
* 检查输入是否合法
*/
class Validator {
/**
* 验证邮箱格式
* @param string $email 邮箱地址
* @return bool 是否有效
*/
public static function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* 验证邮箱域名(检查是否属于指定域名)
* @param string $email 邮箱地址
* @param string $domain 允许的域名test.com
* @return bool 是否属于指定域名
*/
public static function validateEmailDomain($email, $domain = 'test.com') {
if (!self::validateEmail($email)) {
return false;
}
$emailDomain = substr(strrchr($email, "@"), 1);
return strtolower($emailDomain) === strtolower($domain);
}
/**
* 验证密码强度
* @param string $password 密码
* @param int $minLength 最小长度
* @return array ['valid' => bool, 'errors' => array] 验证结果和错误信息
*/
public static function validatePassword($password, $minLength = 6) {
$errors = [];
if (strlen($password) < $minLength) {
$errors[] = "密码长度至少需要 {$minLength} 个字符";
}
if (preg_match('/^[a-zA-Z0-9]+$/', $password) && strlen($password) < 8) {
// 如果密码只包含字母和数字且长度小于8建议使用更复杂的密码
// 但不强制要求
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
/**
* 验证用户名格式
* @param string $username 用户名(邮箱格式)
* @return array ['valid' => bool, 'errors' => array] 验证结果和错误信息
*/
public static function validateUsername($username) {
$errors = [];
if (empty($username)) {
$errors[] = "用户名不能为空";
} elseif (!self::validateEmail($username)) {
$errors[] = "用户名必须是有效的邮箱格式";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
/**
* 验证IP地址格式
* @param string $ip IP地址
* @return bool 是否有效
*/
public static function validateIP($ip) {
return filter_var($ip, FILTER_VALIDATE_IP) !== false;
}
/**
* 验证端口号
* @param int $port 端口号
* @return bool 是否有效1-65535
*/
public static function validatePort($port) {
return is_numeric($port) && $port >= 1 && $port <= 65535;
}
/**
* 验证非空字符串
* @param string $value 待验证的值
* @param string $fieldName 字段名称(用于错误提示)
* @return array ['valid' => bool, 'errors' => array] 验证结果和错误信息
*/
public static function validateRequired($value, $fieldName = '字段') {
$errors = [];
if (empty(trim($value))) {
$errors[] = "{$fieldName}不能为空";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
/**
* 验证字符串长度
* @param string $value 待验证的值
* @param int $min 最小长度
* @param int $max 最大长度
* @param string $fieldName 字段名称
* @return array ['valid' => bool, 'errors' => array] 验证结果和错误信息
*/
public static function validateLength($value, $min, $max, $fieldName = '字段') {
$errors = [];
$length = mb_strlen($value, 'UTF-8');
if ($length < $min) {
$errors[] = "{$fieldName}长度不能少于 {$min} 个字符";
}
if ($length > $max) {
$errors[] = "{$fieldName}长度不能超过 {$max} 个字符";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
/**
* 验证两个密码是否匹配
* @param string $password 密码
* @param string $confirmPassword 确认密码
* @return array ['valid' => bool, 'errors' => array] 验证结果和错误信息
*/
public static function validatePasswordMatch($password, $confirmPassword) {
$errors = [];
if ($password !== $confirmPassword) {
$errors[] = "两次输入的密码不一致";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
}
<?php
/**
* 输入验证工具类
* 检查输入是否合法
*/
class Validator {
/**
* 验证邮箱格式
* @param string $email 邮箱地址
* @return bool 是否有效
*/
public static function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* 验证邮箱域名(检查是否属于指定域名)
* @param string $email 邮箱地址
* @param string $domain 允许的域名test.com
* @return bool 是否属于指定域名
*/
public static function validateEmailDomain($email, $domain = 'test.com') {
if (!self::validateEmail($email)) {
return false;
}
$emailDomain = substr(strrchr($email, "@"), 1);
return strtolower($emailDomain) === strtolower($domain);
}
/**
* 验证密码强度
* @param string $password 密码
* @param int $minLength 最小长度
* @return array ['valid' => bool, 'errors' => array] 验证结果和错误信息
*/
public static function validatePassword($password, $minLength = 6) {
$errors = [];
if (strlen($password) < $minLength) {
$errors[] = "密码长度至少需要 {$minLength} 个字符";
}
if (preg_match('/^[a-zA-Z0-9]+$/', $password) && strlen($password) < 8) {
// 如果密码只包含字母和数字且长度小于8建议使用更复杂的密码
// 但不强制要求
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
/**
* 验证用户名格式
* @param string $username 用户名(邮箱格式)
* @return array ['valid' => bool, 'errors' => array] 验证结果和错误信息
*/
public static function validateUsername($username) {
$errors = [];
if (empty($username)) {
$errors[] = "用户名不能为空";
} elseif (!self::validateEmail($username)) {
$errors[] = "用户名必须是有效的邮箱格式";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
/**
* 验证IP地址格式
* @param string $ip IP地址
* @return bool 是否有效
*/
public static function validateIP($ip) {
return filter_var($ip, FILTER_VALIDATE_IP) !== false;
}
/**
* 验证端口号
* @param int $port 端口号
* @return bool 是否有效1-65535
*/
public static function validatePort($port) {
return is_numeric($port) && $port >= 1 && $port <= 65535;
}
/**
* 验证非空字符串
* @param string $value 待验证的值
* @param string $fieldName 字段名称(用于错误提示)
* @return array ['valid' => bool, 'errors' => array] 验证结果和错误信息
*/
public static function validateRequired($value, $fieldName = '字段') {
$errors = [];
if (empty(trim($value))) {
$errors[] = "{$fieldName}不能为空";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
/**
* 验证字符串长度
* @param string $value 待验证的值
* @param int $min 最小长度
* @param int $max 最大长度
* @param string $fieldName 字段名称
* @return array ['valid' => bool, 'errors' => array] 验证结果和错误信息
*/
public static function validateLength($value, $min, $max, $fieldName = '字段') {
$errors = [];
$length = mb_strlen($value, 'UTF-8');
if ($length < $min) {
$errors[] = "{$fieldName}长度不能少于 {$min} 个字符";
}
if ($length > $max) {
$errors[] = "{$fieldName}长度不能超过 {$max} 个字符";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
/**
* 验证两个密码是否匹配
* @param string $password 密码
* @param string $confirmPassword 确认密码
* @return array ['valid' => bool, 'errors' => array] 验证结果和错误信息
*/
public static function validatePasswordMatch($password, $confirmPassword) {
$errors = [];
if ($password !== $confirmPassword) {
$errors[] = "两次输入的密码不一致";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
}

@ -1,30 +1,30 @@
#!/bin/bash
echo "测试SMTP服务器..."
# 启动服务器(后台运行)
sudo php scripts/start_smtp.php &
SERVER_PID=$!
sleep 3 # 等待更长时间确保服务器启动
echo "发送测试邮件..."
# 使用timeout防止telnet无限等待
timeout 5 telnet localhost 25 << 'EOF'
HELO test
MAIL FROM: <user1@test.com>
RCPT TO: <admin@test.com>
DATA
Subject: 自动测试邮件
From: user1@test.com
To: admin@test.com
这是自动发送的测试邮件
.
QUIT
EOF
# 等待服务器处理完成
sleep 2
kill $SERVER_PID 2>/dev/null
#!/bin/bash
echo "测试SMTP服务器..."
# 启动服务器(后台运行)
sudo php scripts/start_smtp.php &
SERVER_PID=$!
sleep 3 # 等待更长时间确保服务器启动
echo "发送测试邮件..."
# 使用timeout防止telnet无限等待
timeout 5 telnet localhost 25 << 'EOF'
HELO test
MAIL FROM: <user1@test.com>
RCPT TO: <admin@test.com>
DATA
Subject: 自动测试邮件
From: user1@test.com
To: admin@test.com
这是自动发送的测试邮件
.
QUIT
EOF
# 等待服务器处理完成
sleep 2
kill $SERVER_PID 2>/dev/null
echo "测试完成"
Loading…
Cancel
Save