commit d75163b2b44f4a1436bc0ed905a31ad73e7b3876 Author: Snowz <372492339@qq.com> Date: Mon May 26 15:23:18 2025 +0800 first commit diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..6c0c02d --- /dev/null +++ b/.htaccess @@ -0,0 +1,83 @@ +# Apache配置文件 +# 用于URL重写和安全设置 + +# 启用重写引擎 +RewriteEngine On + +# URL重写规则 +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ index.php [QSA,L] + +# 安全设置 - 禁止访问敏感目录 + + Order Allow,Deny + Deny from all + + + + Order Allow,Deny + Deny from all + + + + Order Allow,Deny + Deny from all + + +# 禁止访问敏感文件 + + Order Allow,Deny + Deny from all + + +# 禁止访问隐藏文件 + + Order Allow,Deny + Deny from all + + +# 禁止访问安装文件(安装完成后) + + Order Allow,Deny + Deny from all + + +# 设置默认字符集 +AddDefaultCharset UTF-8 + +# 启用GZIP压缩 + + AddOutputFilterByType DEFLATE text/plain + AddOutputFilterByType DEFLATE text/html + AddOutputFilterByType DEFLATE text/xml + AddOutputFilterByType DEFLATE text/css + AddOutputFilterByType DEFLATE application/xml + AddOutputFilterByType DEFLATE application/xhtml+xml + AddOutputFilterByType DEFLATE application/rss+xml + AddOutputFilterByType DEFLATE application/javascript + AddOutputFilterByType DEFLATE application/x-javascript + + +# 设置缓存策略 + + ExpiresActive On + ExpiresByType text/css "access plus 1 month" + ExpiresByType application/javascript "access plus 1 month" + ExpiresByType image/png "access plus 1 month" + ExpiresByType image/jpg "access plus 1 month" + ExpiresByType image/jpeg "access plus 1 month" + ExpiresByType image/gif "access plus 1 month" + ExpiresByType image/ico "access plus 1 month" + ExpiresByType image/icon "access plus 1 month" + ExpiresByType text/plain "access plus 1 month" + ExpiresByType application/pdf "access plus 1 month" + + +# 安全头设置 + + Header always set X-Content-Type-Options nosniff + Header always set X-Frame-Options DENY + Header always set X-XSS-Protection "1; mode=block" + Header always set Referrer-Policy "strict-origin-when-cross-origin" + \ No newline at end of file diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..691f50a --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,304 @@ +# 宝塔面板部署指南 + +本文档详细介绍如何在宝塔面板中部署内容投稿系统。 + +## 📋 部署前准备 + +### 服务器要求 +- 操作系统:Linux(推荐CentOS 7+/Ubuntu 18+) +- 内存:至少512MB(推荐1GB+) +- 硬盘:至少1GB可用空间 +- 网络:稳定的互联网连接 + +### 宝塔面板要求 +- 宝塔面板版本:7.0+ +- PHP版本:7.4+ +- Web服务器:Apache或Nginx +- 数据库:MySQL 5.7+(可选,也可使用SQLite) + +## 🚀 详细部署步骤 + +### 第一步:安装宝塔面板 + +如果还未安装宝塔面板,请先安装: + +```bash +# CentOS安装命令 +yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh + +# Ubuntu安装命令 +wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh +``` + +### 第二步:配置服务器环境 + +1. **登录宝塔面板** + - 访问 `http://服务器IP:8888` + - 使用安装时显示的用户名和密码登录 + +2. **安装LNMP/LAMP环境** + - 选择"软件商店" → "一键部署" + - 推荐安装:Nginx 1.20+ + MySQL 5.7+ + PHP 7.4+ + - 等待安装完成(约10-30分钟) + +3. **安装PHP扩展** + - 进入"软件商店" → "已安装" + - 找到PHP,点击"设置" + - 在"安装扩展"中安装以下扩展: + - `pdo_mysql`(MySQL支持) + - `pdo_sqlite`(SQLite支持) + - `gd`(图像处理) + - `curl`(网络请求) + - `fileinfo`(文件信息) + +### 第三步:创建网站 + +1. **添加站点** + - 点击"网站" → "添加站点" + - 域名:填入你的域名(如:example.com) + - 根目录:默认即可 + - PHP版本:选择7.4或更高版本 + - 数据库:选择MySQL(可选) + - 点击"提交" + +2. **配置域名解析** + - 在域名服务商处添加A记录 + - 将域名指向服务器IP地址 + +### 第四步:上传项目文件 + +1. **下载项目** + - 方式一:直接上传压缩包 + - 将项目打包为zip文件 + - 在宝塔面板"文件"中上传到网站根目录 + - 解压文件 + + - 方式二:使用Git(推荐) + - 在"终端"中执行: + ```bash + cd /www/wwwroot/your-domain.com + git clone https://github.com/your-repo/submission-system.git . + ``` + +2. **设置文件权限** + - 在"文件"管理中,选择网站根目录 + - 右键选择"权限",设置为755 + - 特别设置以下目录权限为777: + - `config/` + - `data/` + +### 第五步:配置数据库(MySQL方式) + +1. **创建数据库** + - 点击"数据库" → "添加数据库" + - 数据库名:`submission_system` + - 用户名:自定义 + - 密码:自动生成或自定义 + - 记录数据库信息 + +2. **配置数据库连接** + - 编辑 `config/database.php` + - 填入正确的数据库信息 + +### 第六步:运行安装向导 + +1. **访问安装页面** + - 浏览器访问:`http://your-domain.com/install.php` + +2. **环境检查** + - 系统会自动检查服务器环境 + - 确保所有检查项都通过 + +3. **数据库配置** + - 选择数据库类型(MySQL或SQLite) + - 填入数据库连接信息 + - 测试连接 + +4. **初始化数据库** + - 点击"初始化数据库" + - 等待数据表创建完成 + +5. **完成安装** + - 记录默认管理员账户信息 + - 删除或重命名 `install.php` 文件 + +### 第七步:安全配置 + +1. **SSL证书配置** + - 在"网站"中找到你的站点 + - 点击"设置" → "SSL" + - 申请Let's Encrypt免费证书 + - 开启"强制HTTPS" + +2. **防火墙设置** + - 在"安全"中配置防火墙 + - 开放80、443端口 + - 关闭不必要的端口 + +3. **文件安全** + - 删除 `install.php` + - 检查敏感文件权限 + - 定期备份数据 + +## 🔧 高级配置 + +### Nginx配置优化 + +在网站设置中添加以下Nginx配置: + +```nginx +# 安全设置 +location ~ ^/(config|includes|data)/ { + deny all; +} + +# 禁止访问敏感文件 +location ~* \.(sql|log|md|txt|conf)$ { + deny all; +} + +# 隐藏文件 +location ~ /\. { + deny all; +} + +# PHP配置 +location ~ \.php$ { + fastcgi_pass unix:/tmp/php-cgi-74.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; +} + +# 静态文件缓存 +location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { + expires 30d; + add_header Cache-Control "public, immutable"; +} +``` + +### PHP配置优化 + +在PHP设置中调整以下参数: + +```ini +; 上传限制 +upload_max_filesize = 10M +post_max_size = 10M + +; 执行时间 +max_execution_time = 60 + +; 内存限制 +memory_limit = 256M + +; 错误报告 +display_errors = Off +log_errors = On + +; 会话配置 +session.cookie_httponly = On +session.cookie_secure = On +``` + +### 定时任务配置 + +可以设置定时任务来清理过期数据: + +```bash +# 每天凌晨2点清理7天前的IP记录 +0 2 * * * /usr/bin/php /www/wwwroot/your-domain.com/cleanup.php +``` + +## 📊 性能优化 + +### 数据库优化 + +1. **索引优化** + ```sql + -- 为常用查询字段添加索引 + ALTER TABLE website_submissions ADD INDEX idx_status (status); + ALTER TABLE website_submissions ADD INDEX idx_created (created_at); + ALTER TABLE app_submissions ADD INDEX idx_status (status); + ALTER TABLE app_submissions ADD INDEX idx_created (created_at); + ``` + +2. **定期清理** + - 定期清理过期的IP限制记录 + - 归档或删除过旧的投稿记录 + +### 缓存配置 + +1. **开启OPcache** + - 在PHP设置中开启OPcache扩展 + - 提高PHP执行效率 + +2. **静态文件CDN** + - 将CSS、JS等静态文件上传到CDN + - 加速页面加载速度 + +## 🔍 故障排除 + +### 常见问题解决 + +1. **500错误** + - 检查PHP错误日志 + - 确认文件权限设置 + - 检查.htaccess配置 + +2. **数据库连接失败** + - 验证数据库配置信息 + - 检查数据库服务状态 + - 确认防火墙设置 + +3. **验证码不显示** + - 检查GD扩展是否安装 + - 确认PHP图像处理功能 + +4. **无法获取网站信息** + - 检查cURL扩展 + - 确认服务器网络连接 + - 检查目标网站可访问性 + +### 日志查看 + +- **PHP错误日志**:`/www/wwwroot/your-domain.com/php_errors.log` +- **Nginx访问日志**:`/www/wwwroot/your-domain.com/log/access.log` +- **Nginx错误日志**:`/www/wwwroot/your-domain.com/log/error.log` + +## 📈 监控与维护 + +### 定期维护任务 + +1. **系统更新** + - 定期更新宝塔面板 + - 更新PHP、MySQL版本 + - 更新系统安全补丁 + +2. **数据备份** + - 设置自动数据库备份 + - 定期下载备份文件 + - 测试备份恢复流程 + +3. **安全检查** + - 检查异常访问日志 + - 更新管理员密码 + - 检查文件完整性 + +### 性能监控 + +- 使用宝塔面板的监控功能 +- 关注CPU、内存、磁盘使用率 +- 监控网站访问速度 + +## 📞 技术支持 + +如果在部署过程中遇到问题: + +1. 查看本文档的故障排除部分 +2. 检查项目的GitHub Issues +3. 联系技术支持 + +--- + +**祝您部署顺利!如有问题,欢迎反馈。** \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f16f3ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Content Submission System + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..51f380a --- /dev/null +++ b/README.md @@ -0,0 +1,261 @@ +# 内容投稿系统 + +一个轻量级、易部署的内容投稿管理系统,支持网址和APP/软件投稿,具有完善的后台审核功能。 + +## ✨ 功能特性 + +### 前端功能 +- 🌐 **网址投稿**:自动获取网站TDK信息(标题、描述、关键词) +- 📱 **APP/软件投稿**:支持多平台应用投稿 +- 🎯 **多平台收录**:支持自媒体维基、zTab、SOSO平台选择 +- 🔒 **IP限制**:每IP每日最多提交3次 +- 🚫 **重复检测**:智能检测重复内容 +- 📱 **响应式设计**:完美适配移动端和桌面端 + +### 后端功能 +- 👨‍💼 **管理后台**:完善的内容审核管理 +- 🔐 **安全登录**:验证码保护,防暴力破解 +- 📊 **数据统计**:实时查看投稿统计数据 +- ✅ **状态管理**:待处理、已通过、已拒绝状态管理 +- 👤 **账户管理**:支持修改管理员用户名和密码 +- 🗂️ **分类管理**:网址和APP投稿分开管理 + +### 技术特性 +- 🗄️ **双数据库支持**:MySQL和SQLite可选 +- 🚀 **轻量化设计**:纯PHP开发,无复杂依赖 +- 🎨 **现代化UI**:参考大厂设计风格 +- 📦 **易于部署**:支持宝塔面板一键部署 +- 🔧 **安装向导**:图形化安装配置 + +## 🛠️ 环境要求 + +- PHP >= 7.4 +- PDO扩展 +- PDO MySQL扩展(必须使用MySQL或MariaDB) +- GD扩展(验证码功能) +- cURL扩展(网站信息抓取) + +## 📦 安装部署 + +### 方式一:宝塔面板部署(推荐) + +1. **下载源码** + ```bash + # 在宝塔面板文件管理中,进入网站根目录 + # 上传项目文件或使用Git克隆 + git clone https://github.com/your-repo/submission-system.git + ``` + +2. **设置网站** + - 在宝塔面板中创建新网站 + - 设置运行目录为项目根目录 + - PHP版本选择7.4或以上 + +3. **配置数据库** + - 在宝塔面板中创建MySQL数据库 + - 记录数据库名、用户名、密码 + +4. **设置文件权限** + ```bash + chmod 755 -R /www/wwwroot/your-domain/ + chmod 777 /www/wwwroot/your-domain/config/ + chmod 777 /www/wwwroot/your-domain/data/ + ``` + +5. **运行安装向导** + - 访问 `http://your-domain/install.php` + - 按照向导完成安装配置 + +6. **安全设置** + - 安装完成后删除或重命名 `install.php` + - 登录后台修改默认密码 + +### 方式二:手动部署 + +1. **上传文件** + - 将所有文件上传到Web服务器根目录 + +2. **配置Web服务器** + + **Apache配置**(.htaccess): + ```apache + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^(.*)$ index.php [QSA,L] + + # 安全设置 + + Order Allow,Deny + Deny from all + + ``` + + **Nginx配置**: + ```nginx + server { + listen 80; + server_name your-domain.com; + root /path/to/submission-system; + index index.php; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + # 安全设置 + location ~ ^/config/ { + deny all; + } + } + ``` + +3. **设置权限** + ```bash + chmod 755 -R /path/to/submission-system/ + chmod 777 /path/to/submission-system/config/ + chmod 777 /path/to/submission-system/data/ + ``` + +4. **运行安装** + - 访问安装向导完成配置 + +## 🎯 使用说明 + +### 前台投稿 + +1. **网址投稿** + - 输入网址URL(支持http/https) + - 点击"获取信息"自动填充网站信息 + - 选择收录平台 + - 填写联系方式(可选) + - 提交投稿 + +2. **APP投稿** + - 切换到"APP/软件投稿"标签 + - 填写应用名称、平台、版本等信息 + - 提供图标地址、下载链接、官网地址 + - 选择收录平台并提交 + +### 后台管理 + +1. **登录后台** + - 访问 `/admin/login.php` + - 默认账户:admin/admin + - 输入验证码登录 + +2. **内容审核** + - 查看待处理的投稿内容 + - 切换查看网址投稿和APP投稿 + - 批量或单个审核通过/拒绝 + - 添加审核备注 + +3. **账户管理** + - 点击"账户设置"修改用户名和密码 + - 建议首次登录后立即修改默认密码 + +## 🔧 配置说明 + +### 数据库配置 + +编辑 `config/database.php`: + +```php +// MySQL配置 +private $host = 'localhost'; +private $db_name = 'submission_system'; +private $username = 'root'; +private $password = ''; +``` + +### 功能配置 + +- **IP限制**:在 `includes/utils.php` 中修改每日提交次数限制 +- **验证码**:可在 `admin/captcha.php` 中自定义验证码样式 +- **平台选项**:在前端页面中修改收录平台选项 + +## 📁 目录结构 + +``` +submission-system/ +├── admin/ # 后台管理 +│ ├── index.php # 管理主页 +│ ├── login.php # 登录页面 +│ ├── logout.php # 退出登录 +│ └── captcha.php # 验证码生成 +├── api/ # API接口 +│ └── fetch_website_info.php # 获取网站信息 +├── config/ # 配置文件 +│ └── database.php # 数据库配置 +├── includes/ # 核心文件 +│ └── utils.php # 工具类 +├── data/ # 数据目录(SQLite) +├── index.php # 前台主页 +├── install.php # 安装向导 +├── README.md # 说明文档 +└── LICENSE # 开源协议 +``` + +## 🔒 安全建议 + +1. **修改默认密码**:首次登录后立即修改admin账户密码 +2. **删除安装文件**:安装完成后删除 `install.php` +3. **设置文件权限**:确保配置文件不可通过Web访问 +4. **定期备份**:定期备份数据库和配置文件 +5. **更新维护**:及时更新PHP版本和扩展 + +## 🐛 常见问题 + +### Q: 无法获取网站信息? +A: 检查服务器是否支持cURL扩展,确保目标网站可访问。 + +### Q: 验证码不显示? +A: 检查GD扩展是否安装,确保PHP支持图像处理。 + +### Q: 数据库连接失败? +A: 检查数据库配置信息,确保数据库服务正常运行。 + +### Q: 文件上传权限错误? +A: 检查目录权限设置,确保Web服务器有写入权限。 + +### Q: 页面样式异常? +A: 检查CDN资源是否正常加载,可考虑本地化CSS/JS文件。 + +## 🤝 贡献指南 + +欢迎提交Issue和Pull Request来改进项目! + +1. Fork 项目 +2. 创建功能分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m 'Add some AmazingFeature'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 开启 Pull Request + +## 📄 开源协议 + +本项目采用 MIT 协议开源,详见 [LICENSE](LICENSE) 文件。 + +## 🙏 致谢 + +- 感谢所有为开源社区做出贡献的开发者 +- 特别感谢提供设计灵感的各大互联网公司 +- 感谢使用本系统的每一位用户 + +## 📞 联系方式 + +如有问题或建议,欢迎通过以下方式联系: + +- 提交 Issue +- 发送邮件 +- 加入讨论群 + +--- + +**⭐ 如果这个项目对你有帮助,请给个Star支持一下!** \ No newline at end of file diff --git a/admin/arial.ttf b/admin/arial.ttf new file mode 100644 index 0000000..12cc15c Binary files /dev/null and b/admin/arial.ttf differ diff --git a/admin/captcha.php b/admin/captcha.php new file mode 100644 index 0000000..b4780a3 --- /dev/null +++ b/admin/captcha.php @@ -0,0 +1,60 @@ + \ No newline at end of file diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 0000000..9051119 --- /dev/null +++ b/admin/index.php @@ -0,0 +1,750 @@ +getConnection(); + +$message = ''; +$message_type = ''; + +// 处理操作 +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = $_POST['action'] ?? ''; + + if ($action === 'update_status') { + $type = $_POST['type'] ?? ''; + $id = $_POST['id'] ?? ''; + $status = $_POST['status'] ?? ''; + $note = $_POST['note'] ?? ''; + + if ($type === 'website') { + $stmt = $db->prepare("UPDATE website_submissions SET status = ?, admin_note = ? WHERE id = ?"); + } else { + $stmt = $db->prepare("UPDATE app_submissions SET status = ?, admin_note = ? WHERE id = ?"); + } + + if ($stmt->execute([$status, $note, $id])) { + $message = '状态更新成功'; + $message_type = 'success'; + } else { + $message = '状态更新失败'; + $message_type = 'error'; + } + } elseif ($action === 'update_account') { + $new_username = trim($_POST['new_username'] ?? ''); + $new_password = $_POST['new_password'] ?? ''; + $confirm_password = $_POST['confirm_password'] ?? ''; + + if (empty($new_username)) { + $message = '用户名不能为空'; + $message_type = 'error'; + } elseif (!empty($new_password) && $new_password !== $confirm_password) { + $message = '两次输入的密码不一致'; + $message_type = 'error'; + } else { + $admin_id = $_SESSION['admin_id']; + + if (!empty($new_password)) { + $hashed_password = password_hash($new_password, PASSWORD_DEFAULT); + $stmt = $db->prepare("UPDATE admins SET username = ?, password = ? WHERE id = ?"); + $stmt->execute([$new_username, $hashed_password, $admin_id]); + } else { + $stmt = $db->prepare("UPDATE admins SET username = ? WHERE id = ?"); + $stmt->execute([$new_username, $admin_id]); + } + + $_SESSION['admin_username'] = $new_username; + $message = '账户信息更新成功'; + $message_type = 'success'; + } + } +} + +// 获取统计数据 +$stats = [ + 'website_pending' => 0, + 'website_approved' => 0, + 'website_rejected' => 0, + 'app_pending' => 0, + 'app_approved' => 0, + 'app_rejected' => 0 +]; + +$stmt = $db->prepare("SELECT status, COUNT(*) as count FROM website_submissions GROUP BY status"); +$stmt->execute(); +while ($row = $stmt->fetch()) { + $stats['website_' . $row['status']] = $row['count']; +} + +$stmt = $db->prepare("SELECT status, COUNT(*) as count FROM app_submissions GROUP BY status"); +$stmt->execute(); +while ($row = $stmt->fetch()) { + $stats['app_' . $row['status']] = $row['count']; +} + +// 获取列表数据 +$filter = $_GET['filter'] ?? 'pending'; +$type = $_GET['type'] ?? 'website'; +$page = max(1, intval($_GET['page'] ?? 1)); +$limit = 10; +$offset = ($page - 1) * $limit; + +// 强制转换为整数 +$limit = (int)$limit; +$offset = (int)$offset; + +if ($type === 'website') { + $count_stmt = $db->prepare("SELECT COUNT(*) FROM website_submissions WHERE status = ?"); + $count_stmt->execute([$filter]); + $total = $count_stmt->fetchColumn(); + + $stmt = $db->prepare(" + SELECT id, url, title, description, platforms, contact, status, admin_note, created_at + FROM website_submissions + WHERE status = ? + ORDER BY created_at DESC + LIMIT ?, ? + "); + $stmt->bindValue(1, $filter, PDO::PARAM_STR); + $stmt->bindValue(2, $offset, PDO::PARAM_INT); + $stmt->bindValue(3, $limit, PDO::PARAM_INT); + $stmt->execute(); +} else { + $count_stmt = $db->prepare("SELECT COUNT(*) FROM app_submissions WHERE status = ?"); + $count_stmt->execute([$filter]); + $total = $count_stmt->fetchColumn(); + + $stmt = $db->prepare(" + SELECT id, name, platform, version, icon_url, download_url, website_url, description, platforms, contact, status, admin_note, created_at + FROM app_submissions + WHERE status = ? + ORDER BY created_at DESC + LIMIT ?, ? + "); + $stmt->bindValue(1, $filter, PDO::PARAM_STR); + $stmt->bindValue(2, $offset, PDO::PARAM_INT); + $stmt->bindValue(3, $limit, PDO::PARAM_INT); + $stmt->execute(); +} + +$submissions = $stmt->fetchAll(); +$total_pages = ceil($total / $limit); +?> + + + + + + + 管理后台 - 内容投稿系统 + + + + +
+
+

管理后台

+
+ 欢迎, + + + 前台 + + + 退出 + +
+
+
+ +
+ +
+ +
+ + + +
+

账户设置

+
+ +
+ + +
+
+ + +
+
+ + +
+ +
+
+ + +
+
+

+

网址待审核

+
+
+

+

网址已通过

+
+
+

+

应用待审核

+
+
+

+

应用已通过

+
+
+ + +
+ + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID网址标题应用名称平台版本收录平台联系方式状态提交时间操作
+ + 30 ? '...' : ''); ?> + + + + '待处理', + 'approved' => '已通过', + 'rejected' => '已拒绝' + ]; + echo $status_text[$submission['status']]; + ?> + + +
+ + + + + +
+
+
+ + + 1): ?> + + +
+ + + + + + + \ No newline at end of file diff --git a/admin/login.php b/admin/login.php new file mode 100644 index 0000000..3d72219 --- /dev/null +++ b/admin/login.php @@ -0,0 +1,280 @@ +getConnection(); + + $stmt = $db->prepare("SELECT id, username, password FROM admins WHERE username = ?"); + $stmt->execute([$username]); + $admin = $stmt->fetch(); + + if ($admin && password_verify($password, $admin['password'])) { + $_SESSION['admin_logged_in'] = true; + $_SESSION['admin_id'] = $admin['id']; + $_SESSION['admin_username'] = $admin['username']; + header('Location: index.php'); + exit; + } else { + $error = '用户名或密码错误'; + } + } +} +?> + + + + + + + 管理后台登录 + + + + +
+
+

管理后台

+

内容投稿系统管理中心

+
+ +
+ +
+ +
+ + +
+
+ + +
+ +
+ + +
+ +
+ +
+ + 验证码 +
+
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/admin/logout.php b/admin/logout.php new file mode 100644 index 0000000..e0dea78 --- /dev/null +++ b/admin/logout.php @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/api/fetch_website_info.php b/api/fetch_website_info.php new file mode 100644 index 0000000..a08d2d1 --- /dev/null +++ b/api/fetch_website_info.php @@ -0,0 +1,35 @@ + false, 'message' => 'Method not allowed']); + exit; +} + +$input = json_decode(file_get_contents('php://input'), true); +$url = $input['url'] ?? ''; + +if (empty($url)) { + echo json_encode(['success' => false, 'message' => 'URL is required']); + exit; +} + +$database = new Database(); +$db = $database->getConnection(); +$utils = new Utils($db); + +$result = $utils->getWebsiteInfo($url); + +echo json_encode($result); +?> \ No newline at end of file diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..fd11113 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,524 @@ +/* 投稿系统样式文件 */ +/* 现代化设计,参考互联网大厂设计思路 */ + +/* 全局样式重置 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* CSS变量定义 - 统一配色方案 */ +:root { + /* 主色调 - 现代蓝紫渐变 */ + --primary-color: #667eea; + --primary-dark: #5a67d8; + --primary-light: #7c3aed; + --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + + /* 辅助色 */ + --secondary-color: #f093fb; + --accent-color: #4facfe; + + /* 状态色 */ + --success-color: #10b981; + --warning-color: #f59e0b; + --error-color: #ef4444; + --info-color: #3b82f6; + + /* 中性色 */ + --text-primary: #1f2937; + --text-secondary: #6b7280; + --text-light: #9ca3af; + --border-color: #e5e7eb; + --bg-primary: #ffffff; + --bg-secondary: #f9fafb; + --bg-dark: #111827; + + /* 阴影 */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + + /* 圆角 */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + + /* 间距 */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + --spacing-2xl: 3rem; +} + +/* 基础样式 */ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + line-height: 1.6; + color: var(--text-primary); + background: var(--bg-secondary); + font-size: 16px; +} + +/* 容器样式 */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--spacing-md); +} + +.container-sm { + max-width: 600px; + margin: 0 auto; + padding: 0 var(--spacing-md); +} + +/* 卡片样式 */ +.card { + background: var(--bg-primary); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + padding: var(--spacing-xl); + margin-bottom: var(--spacing-lg); + border: 1px solid var(--border-color); + transition: all 0.3s ease; +} + +.card:hover { + box-shadow: var(--shadow-lg); + transform: translateY(-2px); +} + +.card-header { + margin-bottom: var(--spacing-lg); + padding-bottom: var(--spacing-md); + border-bottom: 2px solid var(--border-color); +} + +.card-title { + font-size: 1.5rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: var(--spacing-sm); +} + +.card-subtitle { + color: var(--text-secondary); + font-size: 0.875rem; +} + +/* 按钮样式 */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.75rem 1.5rem; + font-size: 0.875rem; + font-weight: 500; + border-radius: var(--radius-md); + border: none; + cursor: pointer; + text-decoration: none; + transition: all 0.2s ease; + min-height: 44px; + gap: var(--spacing-sm); +} + +.btn-primary { + background: var(--primary-gradient); + color: white; + box-shadow: var(--shadow-sm); +} + +.btn-primary:hover { + transform: translateY(-1px); + box-shadow: var(--shadow-md); + filter: brightness(1.05); +} + +.btn-secondary { + background: var(--bg-primary); + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +.btn-secondary:hover { + background: var(--bg-secondary); + border-color: var(--primary-color); +} + +.btn-success { + background: var(--success-color); + color: white; +} + +.btn-warning { + background: var(--warning-color); + color: white; +} + +.btn-danger { + background: var(--error-color); + color: white; +} + +.btn-sm { + padding: 0.5rem 1rem; + font-size: 0.75rem; + min-height: 36px; +} + +.btn-lg { + padding: 1rem 2rem; + font-size: 1rem; + min-height: 52px; +} + +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +/* 表单样式 */ +.form-group { + margin-bottom: var(--spacing-lg); +} + +.form-label { + display: block; + font-weight: 500; + color: var(--text-primary); + margin-bottom: var(--spacing-sm); + font-size: 0.875rem; +} + +.form-label.required::after { + content: ' *'; + color: var(--error-color); +} + +.form-control { + width: 100%; + padding: 0.75rem; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + font-size: 0.875rem; + transition: all 0.2s ease; + background: var(--bg-primary); + min-height: 44px; +} + +.form-control:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.form-control:invalid { + border-color: var(--error-color); +} + +textarea.form-control { + resize: vertical; + min-height: 100px; +} + +select.form-control { + cursor: pointer; +} + +/* 复选框样式 */ +.checkbox-group { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.checkbox-item { + display: flex; + align-items: center; + gap: var(--spacing-sm); + padding: var(--spacing-sm); + border-radius: var(--radius-sm); + transition: background-color 0.2s ease; +} + +.checkbox-item:hover { + background: var(--bg-secondary); +} + +.checkbox-item input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: var(--primary-color); +} + +.checkbox-item label { + font-size: 0.875rem; + cursor: pointer; + flex: 1; +} + +/* 警告提示样式 */ +.platform-warning { + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); + border: 1px solid #f59e0b; + border-radius: var(--radius-md); + padding: var(--spacing-md); + margin-top: var(--spacing-sm); + font-size: 0.75rem; + color: #92400e; + display: none; +} + +.platform-warning.show { + display: block; + animation: slideDown 0.3s ease; +} + +/* 切换按钮样式 */ +.tab-container { + display: flex; + background: var(--bg-secondary); + border-radius: var(--radius-lg); + padding: var(--spacing-xs); + margin-bottom: var(--spacing-xl); + border: 1px solid var(--border-color); +} + +.tab-btn { + flex: 1; + padding: var(--spacing-md); + background: transparent; + border: none; + border-radius: var(--radius-md); + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + color: var(--text-secondary); +} + +.tab-btn.active { + background: var(--bg-primary); + color: var(--primary-color); + box-shadow: var(--shadow-sm); +} + +/* 表单内容区域 */ +.form-content { + display: none; +} + +.form-content.active { + display: block; + animation: fadeIn 0.3s ease; +} + +/* 消息提示样式 */ +.alert { + padding: var(--spacing-md); + border-radius: var(--radius-md); + margin-bottom: var(--spacing-lg); + border: 1px solid transparent; + font-size: 0.875rem; +} + +.alert-success { + background: #ecfdf5; + border-color: #10b981; + color: #065f46; +} + +.alert-error { + background: #fef2f2; + border-color: #ef4444; + color: #991b1b; +} + +.alert-warning { + background: #fffbeb; + border-color: #f59e0b; + color: #92400e; +} + +.alert-info { + background: #eff6ff; + border-color: #3b82f6; + color: #1e40af; +} + +/* 加载状态 */ +.loading { + display: inline-flex; + align-items: center; + gap: var(--spacing-sm); +} + +.spinner { + width: 16px; + height: 16px; + border: 2px solid transparent; + border-top: 2px solid currentColor; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .container { + padding: 0 var(--spacing-sm); + } + + .card { + padding: var(--spacing-lg); + margin-bottom: var(--spacing-md); + } + + .btn { + width: 100%; + justify-content: center; + } + + .tab-container { + flex-direction: column; + } + + .tab-btn { + text-align: center; + } +} + +/* 动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideDown { + from { + opacity: 0; + max-height: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + max-height: 100px; + transform: translateY(0); + } +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* 页面头部样式 */ +.page-header { + background: var(--primary-gradient); + color: white; + padding: var(--spacing-2xl) 0; + margin-bottom: var(--spacing-xl); + text-align: center; +} + +.page-title { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: var(--spacing-sm); +} + +.page-subtitle { + font-size: 1.125rem; + opacity: 0.9; + font-weight: 400; +} + +/* 页脚样式 */ +.page-footer { + background: var(--bg-dark); + color: white; + padding: var(--spacing-xl) 0; + margin-top: var(--spacing-2xl); + text-align: center; +} + +.page-footer p { + margin-bottom: var(--spacing-sm); + opacity: 0.8; +} + +/* 工具类 */ +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } + +.mt-0 { margin-top: 0; } +.mt-1 { margin-top: var(--spacing-xs); } +.mt-2 { margin-top: var(--spacing-sm); } +.mt-3 { margin-top: var(--spacing-md); } +.mt-4 { margin-top: var(--spacing-lg); } +.mt-5 { margin-top: var(--spacing-xl); } + +.mb-0 { margin-bottom: 0; } +.mb-1 { margin-bottom: var(--spacing-xs); } +.mb-2 { margin-bottom: var(--spacing-sm); } +.mb-3 { margin-bottom: var(--spacing-md); } +.mb-4 { margin-bottom: var(--spacing-lg); } +.mb-5 { margin-bottom: var(--spacing-xl); } + +.hidden { display: none; } +.block { display: block; } +.inline-block { display: inline-block; } +.flex { display: flex; } +.inline-flex { display: inline-flex; } + +.justify-center { justify-content: center; } +.justify-between { justify-content: space-between; } +.items-center { align-items: center; } + +.w-full { width: 100%; } +.h-full { height: 100%; } + +.opacity-50 { opacity: 0.5; } +.opacity-75 { opacity: 0.75; } + +/* 深色模式支持 */ +@media (prefers-color-scheme: dark) { + :root { + --text-primary: #f9fafb; + --text-secondary: #d1d5db; + --text-light: #9ca3af; + --border-color: #374151; + --bg-primary: #1f2937; + --bg-secondary: #111827; + } + + .card { + border-color: var(--border-color); + } + + .form-control { + background: var(--bg-primary); + border-color: var(--border-color); + color: var(--text-primary); + } + + .btn-secondary { + background: var(--bg-primary); + color: var(--text-primary); + border-color: var(--border-color); + } +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..3967f38 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,612 @@ +/** + * 投稿系统前端交互脚本 + * 提供表单切换、验证、提交等功能 + */ + +// 全局变量 +let currentFormType = 'website'; +let isSubmitting = false; + +// DOM加载完成后初始化 +document.addEventListener('DOMContentLoaded', function() { + initializeApp(); +}); + +/** + * 初始化应用 + */ +function initializeApp() { + initializeFormSwitching(); + initializeFormValidation(); + initializeWebsiteInfoFetching(); + initializePlatformWarnings(); + initializeFormSubmission(); +} + +/** + * 初始化表单切换功能 + */ +function initializeFormSwitching() { + const tabButtons = document.querySelectorAll('.tab-btn'); + const formContents = document.querySelectorAll('.form-content'); + + tabButtons.forEach(button => { + button.addEventListener('click', function() { + const targetForm = this.dataset.form; + switchForm(targetForm); + }); + }); +} + +/** + * 切换表单 + * @param {string} formType - 表单类型 ('website' 或 'app') + */ +function switchForm(formType) { + currentFormType = formType; + + // 更新标签按钮状态 + document.querySelectorAll('.tab-btn').forEach(btn => { + btn.classList.remove('active'); + }); + document.querySelector(`[data-form="${formType}"]`).classList.add('active'); + + // 切换表单内容 + document.querySelectorAll('.form-content').forEach(content => { + content.classList.remove('active'); + }); + document.getElementById(`${formType}-form`).classList.add('active'); + + // 更新必填字段 + updateRequiredFields(formType); + + // 清除之前的错误信息 + clearFormErrors(); +} + +/** + * 更新必填字段 + * @param {string} formType - 表单类型 + */ +function updateRequiredFields(formType) { + // 清除所有必填标记 + document.querySelectorAll('input, textarea, select').forEach(field => { + field.removeAttribute('required'); + }); + + if (formType === 'website') { + // 网址投稿必填字段 + const requiredFields = ['url', 'platforms']; + requiredFields.forEach(fieldName => { + const field = document.querySelector(`[name="${fieldName}"]`); + if (field) { + if (fieldName === 'platforms') { + // 平台选择至少选一个 + const checkboxes = document.querySelectorAll('input[name="platforms[]"]'); + checkboxes.forEach(cb => cb.setAttribute('required', 'required')); + } else { + field.setAttribute('required', 'required'); + } + } + }); + } else if (formType === 'app') { + // APP投稿必填字段 + const requiredFields = ['app_name', 'platform', 'version', 'download_url']; + requiredFields.forEach(fieldName => { + const field = document.querySelector(`[name="${fieldName}"]`); + if (field) { + field.setAttribute('required', 'required'); + } + }); + } +} + +/** + * 初始化表单验证 + */ +function initializeFormValidation() { + // URL格式验证 + const urlInput = document.querySelector('input[name="url"]'); + if (urlInput) { + urlInput.addEventListener('blur', validateURL); + urlInput.addEventListener('input', debounce(validateURL, 500)); + } + + // 平台选择验证 + const platformCheckboxes = document.querySelectorAll('input[name="platforms[]"]'); + platformCheckboxes.forEach(checkbox => { + checkbox.addEventListener('change', validatePlatformSelection); + }); + + // APP表单验证 + const appNameInput = document.querySelector('input[name="app_name"]'); + if (appNameInput) { + appNameInput.addEventListener('blur', validateAppName); + } + + const downloadUrlInput = document.querySelector('input[name="download_url"]'); + if (downloadUrlInput) { + downloadUrlInput.addEventListener('blur', validateDownloadURL); + } +} + +/** + * 验证URL格式 + */ +function validateURL() { + const urlInput = document.querySelector('input[name="url"]'); + const url = urlInput.value.trim(); + + if (!url) return; + + // 自动添加协议 + if (url && !url.match(/^https?:\/\//)) { + urlInput.value = 'https://' + url; + } + + // 验证URL格式 + const urlPattern = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/; + + if (!urlPattern.test(urlInput.value)) { + showFieldError(urlInput, '请输入有效的网址格式'); + return false; + } else { + clearFieldError(urlInput); + return true; + } +} + +/** + * 验证平台选择 + */ +function validatePlatformSelection() { + const checkboxes = document.querySelectorAll('input[name="platforms[]"]'); + const checked = Array.from(checkboxes).some(cb => cb.checked); + + if (!checked) { + showFieldError(checkboxes[0].closest('.checkbox-group'), '请至少选择一个收录平台'); + return false; + } else { + clearFieldError(checkboxes[0].closest('.checkbox-group')); + return true; + } +} + +/** + * 验证APP名称 + */ +function validateAppName() { + const appNameInput = document.querySelector('input[name="app_name"]'); + const appName = appNameInput.value.trim(); + + if (appName.length < 2) { + showFieldError(appNameInput, 'APP名称至少需要2个字符'); + return false; + } else { + clearFieldError(appNameInput); + return true; + } +} + +/** + * 验证下载链接 + */ +function validateDownloadURL() { + const downloadUrlInput = document.querySelector('input[name="download_url"]'); + const url = downloadUrlInput.value.trim(); + + if (!url) return; + + const urlPattern = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/; + + if (!urlPattern.test(url)) { + showFieldError(downloadUrlInput, '请输入有效的下载链接'); + return false; + } else { + clearFieldError(downloadUrlInput); + return true; + } +} + +/** + * 显示字段错误 + * @param {Element} field - 字段元素 + * @param {string} message - 错误信息 + */ +function showFieldError(field, message) { + clearFieldError(field); + + const errorDiv = document.createElement('div'); + errorDiv.className = 'field-error'; + errorDiv.style.cssText = ` + color: var(--error-color); + font-size: 0.75rem; + margin-top: 0.25rem; + display: flex; + align-items: center; + gap: 0.25rem; + `; + errorDiv.innerHTML = ` + + + + ${message} + `; + + field.style.borderColor = 'var(--error-color)'; + field.parentNode.appendChild(errorDiv); +} + +/** + * 清除字段错误 + * @param {Element} field - 字段元素 + */ +function clearFieldError(field) { + const existingError = field.parentNode.querySelector('.field-error'); + if (existingError) { + existingError.remove(); + } + field.style.borderColor = ''; +} + +/** + * 清除所有表单错误 + */ +function clearFormErrors() { + document.querySelectorAll('.field-error').forEach(error => error.remove()); + document.querySelectorAll('.form-control').forEach(field => { + field.style.borderColor = ''; + }); +} + +/** + * 初始化网站信息获取功能 + */ +function initializeWebsiteInfoFetching() { + const urlInput = document.querySelector('input[name="url"]'); + const fetchBtn = document.querySelector('#fetch-info-btn'); + + if (fetchBtn) { + fetchBtn.addEventListener('click', fetchWebsiteInfo); + } + + if (urlInput) { + urlInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + e.preventDefault(); + fetchWebsiteInfo(); + } + }); + } +} + +/** + * 获取网站信息 + */ +async function fetchWebsiteInfo() { + const urlInput = document.querySelector('input[name="url"]'); + const fetchBtn = document.querySelector('#fetch-info-btn'); + const url = urlInput.value.trim(); + + if (!url) { + showAlert('请先输入网址', 'warning'); + return; + } + + if (!validateURL()) { + return; + } + + // 显示加载状态 + const originalText = fetchBtn.innerHTML; + fetchBtn.innerHTML = ' 获取中...'; + fetchBtn.disabled = true; + + try { + const response = await fetch('api/fetch_website_info.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ url: url }) + }); + + const data = await response.json(); + + if (data.success) { + // 填充表单字段 + fillWebsiteInfo(data.data); + showAlert('网站信息获取成功!', 'success'); + } else { + showAlert(data.message || '获取网站信息失败', 'error'); + } + } catch (error) { + console.error('获取网站信息失败:', error); + showAlert('网络错误,请稍后重试', 'error'); + } finally { + // 恢复按钮状态 + fetchBtn.innerHTML = originalText; + fetchBtn.disabled = false; + } +} + +/** + * 填充网站信息到表单 + * @param {Object} info - 网站信息 + */ +function fillWebsiteInfo(info) { + const fields = { + 'site_name': info.title || '', + 'site_description': info.description || '', + 'site_keywords': info.keywords || '' + }; + + Object.entries(fields).forEach(([fieldName, value]) => { + const field = document.querySelector(`[name="${fieldName}"]`); + if (field && value) { + field.value = value; + // 触发输入事件以更新UI + field.dispatchEvent(new Event('input', { bubbles: true })); + } + }); +} + +/** + * 初始化平台警告提示 + */ +function initializePlatformWarnings() { + const platformCheckboxes = document.querySelectorAll('input[name="platforms[]"]'); + + platformCheckboxes.forEach(checkbox => { + checkbox.addEventListener('change', updatePlatformWarnings); + }); +} + +/** + * 更新平台警告提示 + */ +function updatePlatformWarnings() { + const warnings = { + 'zmtwiki': '该平台需要合法合规的内容', + 'ztab': '该平台需要合法合规的内容', + 'soso': '该平台内容审查相当宽松' + }; + + // 清除现有警告 + document.querySelectorAll('.platform-warning').forEach(warning => { + warning.classList.remove('show'); + }); + + // 显示相关警告 + Object.entries(warnings).forEach(([platform, message]) => { + const checkbox = document.querySelector(`input[value="${platform}"]`); + if (checkbox && checkbox.checked) { + showPlatformWarning(platform, message); + } + }); +} + +/** + * 显示平台警告 + * @param {string} platform - 平台名称 + * @param {string} message - 警告信息 + */ +function showPlatformWarning(platform, message) { + let warningDiv = document.querySelector(`#warning-${platform}`); + + if (!warningDiv) { + warningDiv = document.createElement('div'); + warningDiv.id = `warning-${platform}`; + warningDiv.className = 'platform-warning'; + + const checkbox = document.querySelector(`input[value="${platform}"]`); + checkbox.closest('.checkbox-item').appendChild(warningDiv); + } + + warningDiv.innerHTML = ` + + + + ${message} + `; + warningDiv.classList.add('show'); +} + +/** + * 初始化表单提交 + */ +function initializeFormSubmission() { + const form = document.querySelector('#submission-form'); + + if (form) { + form.addEventListener('submit', handleFormSubmission); + } +} + +/** + * 处理表单提交 + * @param {Event} e - 提交事件 + */ +async function handleFormSubmission(e) { + e.preventDefault(); + + if (isSubmitting) return; + + // 验证表单 + if (!validateForm()) { + return; + } + + isSubmitting = true; + const submitBtn = document.querySelector('button[type="submit"]'); + const originalText = submitBtn.innerHTML; + + // 显示提交状态 + submitBtn.innerHTML = ' 提交中...'; + submitBtn.disabled = true; + + try { + const formData = new FormData(e.target); + formData.append('form_type', currentFormType); + + const response = await fetch('', { + method: 'POST', + body: formData + }); + + const result = await response.text(); + + // 检查响应中是否包含成功信息 + if (result.includes('提交成功') || result.includes('success')) { + showAlert('投稿提交成功!我们会尽快审核您的内容。', 'success'); + resetForm(); + } else if (result.includes('已存在') || result.includes('重复')) { + showAlert('该内容已存在,请勿重复提交。', 'warning'); + } else if (result.includes('超出限制') || result.includes('限制')) { + showAlert('今日提交次数已达上限,请明天再试。', 'warning'); + } else { + showAlert('提交失败,请稍后重试。', 'error'); + } + } catch (error) { + console.error('提交失败:', error); + showAlert('网络错误,请稍后重试。', 'error'); + } finally { + // 恢复按钮状态 + isSubmitting = false; + submitBtn.innerHTML = originalText; + submitBtn.disabled = false; + } +} + +/** + * 验证整个表单 + * @returns {boolean} 验证结果 + */ +function validateForm() { + let isValid = true; + + if (currentFormType === 'website') { + if (!validateURL()) isValid = false; + if (!validatePlatformSelection()) isValid = false; + } else if (currentFormType === 'app') { + if (!validateAppName()) isValid = false; + if (!validateDownloadURL()) isValid = false; + } + + return isValid; +} + +/** + * 重置表单 + */ +function resetForm() { + const form = document.querySelector('#submission-form'); + if (form) { + form.reset(); + clearFormErrors(); + + // 清除平台警告 + document.querySelectorAll('.platform-warning').forEach(warning => { + warning.classList.remove('show'); + }); + } +} + +/** + * 显示提示信息 + * @param {string} message - 提示信息 + * @param {string} type - 提示类型 (success, error, warning, info) + */ +function showAlert(message, type = 'info') { + // 移除现有提示 + const existingAlert = document.querySelector('.alert'); + if (existingAlert) { + existingAlert.remove(); + } + + const alertDiv = document.createElement('div'); + alertDiv.className = `alert alert-${type}`; + alertDiv.innerHTML = ` +
+ ${getAlertIcon(type)} + ${message} +
+ `; + + // 插入到表单顶部 + const form = document.querySelector('#submission-form'); + if (form) { + form.insertBefore(alertDiv, form.firstChild); + } + + // 自动隐藏 + setTimeout(() => { + if (alertDiv.parentNode) { + alertDiv.style.opacity = '0'; + alertDiv.style.transform = 'translateY(-10px)'; + setTimeout(() => alertDiv.remove(), 300); + } + }, 5000); +} + +/** + * 获取提示图标 + * @param {string} type - 提示类型 + * @returns {string} SVG图标 + */ +function getAlertIcon(type) { + const icons = { + success: '', + error: '', + warning: '', + info: '' + }; + + return icons[type] || icons.info; +} + +/** + * 防抖函数 + * @param {Function} func - 要防抖的函数 + * @param {number} wait - 等待时间 + * @returns {Function} 防抖后的函数 + */ +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +/** + * 节流函数 + * @param {Function} func - 要节流的函数 + * @param {number} limit - 限制时间 + * @returns {Function} 节流后的函数 + */ +function throttle(func, limit) { + let inThrottle; + return function() { + const args = arguments; + const context = this; + if (!inThrottle) { + func.apply(context, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; +} + +// 导出函数供其他脚本使用 +window.SubmissionSystem = { + switchForm, + validateForm, + fetchWebsiteInfo, + showAlert, + resetForm +}; \ No newline at end of file diff --git a/cleanup.php b/cleanup.php new file mode 100644 index 0000000..08b11cd --- /dev/null +++ b/cleanup.php @@ -0,0 +1,231 @@ +db = $database->getConnection(); + } + + /** + * 清理过期的IP限制记录 + * 删除7天前的记录 + */ + public function cleanupIPLimits() { + try { + $sql = "DELETE FROM ip_limits WHERE DATE(last_submit) < DATE_SUB(NOW(), INTERVAL 7 DAY)"; + $stmt = $this->db->prepare($sql); + $result = $stmt->execute(); + + $deletedRows = $stmt->rowCount(); + $this->log("清理IP限制记录: 删除了 {$deletedRows} 条过期记录"); + + return $deletedRows; + } catch (Exception $e) { + $this->log("清理IP限制记录失败: " . $e->getMessage(), 'ERROR'); + return false; + } + } + + /** + * 清理旧的投稿记录 + * 删除6个月前已处理的记录 + */ + public function cleanupOldSubmissions() { + $deletedWebsite = 0; + $deletedApp = 0; + + try { + // 清理网址投稿记录 + $sql = "DELETE FROM website_submissions WHERE status != 'pending' AND DATE(created_at) < DATE_SUB(NOW(), INTERVAL 6 MONTH)"; + $stmt = $this->db->prepare($sql); + $stmt->execute(); + $deletedWebsite = $stmt->rowCount(); + + // 清理APP投稿记录 + $sql = "DELETE FROM app_submissions WHERE status != 'pending' AND DATE(created_at) < DATE_SUB(NOW(), INTERVAL 6 MONTH)"; + $stmt = $this->db->prepare($sql); + $stmt->execute(); + $deletedApp = $stmt->rowCount(); + + $this->log("清理旧投稿记录: 网址投稿 {$deletedWebsite} 条, APP投稿 {$deletedApp} 条"); + + return ['website' => $deletedWebsite, 'app' => $deletedApp]; + } catch (Exception $e) { + $this->log("清理旧投稿记录失败: " . $e->getMessage(), 'ERROR'); + return false; + } + } + + /** + * 优化数据库表 + */ + public function optimizeTables() { + try { + $tables = ['admins', 'website_submissions', 'app_submissions', 'ip_limits']; + + foreach ($tables as $table) { + $sql = "OPTIMIZE TABLE {$table}"; + $stmt = $this->db->prepare($sql); + $stmt->execute(); + } + + $this->log("数据库表优化完成"); + return true; + } catch (Exception $e) { + $this->log("数据库表优化失败: " . $e->getMessage(), 'ERROR'); + return false; + } + } + + /** + * 获取数据库统计信息 + */ + public function getStatistics() { + try { + $stats = []; + + // 统计各表记录数 + $tables = [ + 'website_submissions' => '网址投稿', + 'app_submissions' => 'APP投稿', + 'ip_limits' => 'IP限制记录', + 'admins' => '管理员账户' + ]; + + foreach ($tables as $table => $name) { + $sql = "SELECT COUNT(*) as count FROM {$table}"; + $stmt = $this->db->prepare($sql); + $stmt->execute(); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $stats[$name] = $result['count']; + } + + // 统计各状态的投稿数量 + $statusStats = []; + $statuses = ['pending' => '待处理', 'approved' => '已通过', 'rejected' => '已拒绝']; + + foreach ($statuses as $status => $name) { + // 网址投稿 + $sql = "SELECT COUNT(*) as count FROM website_submissions WHERE status = ?"; + $stmt = $this->db->prepare($sql); + $stmt->execute([$status]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $statusStats["网址投稿-{$name}"] = $result['count']; + + // APP投稿 + $sql = "SELECT COUNT(*) as count FROM app_submissions WHERE status = ?"; + $stmt = $this->db->prepare($sql); + $stmt->execute([$status]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $statusStats["APP投稿-{$name}"] = $result['count']; + } + + return array_merge($stats, $statusStats); + } catch (Exception $e) { + $this->log("获取统计信息失败: " . $e->getMessage(), 'ERROR'); + return false; + } + } + + /** + * 记录日志 + */ + private function log($message, $level = 'INFO') { + $timestamp = date('Y-m-d H:i:s'); + $logMessage = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL; + + // 输出到控制台 + echo $logMessage; + + // 写入日志文件 + $logFile = 'data/cleanup.log'; + if (!file_exists('data')) { + mkdir('data', 0755, true); + } + file_put_contents($logFile, $logMessage, FILE_APPEND | LOCK_EX); + } + + /** + * 执行完整的清理流程 + */ + public function runFullCleanup() { + $this->log("开始执行数据清理任务"); + + // 显示清理前的统计信息 + $beforeStats = $this->getStatistics(); + if ($beforeStats) { + $this->log("清理前统计信息:"); + foreach ($beforeStats as $key => $value) { + $this->log(" {$key}: {$value}"); + } + } + + // 执行清理任务 + $ipCleanup = $this->cleanupIPLimits(); + $submissionCleanup = $this->cleanupOldSubmissions(); + $optimize = $this->optimizeTables(); + + // 显示清理后的统计信息 + $afterStats = $this->getStatistics(); + if ($afterStats) { + $this->log("清理后统计信息:"); + foreach ($afterStats as $key => $value) { + $this->log(" {$key}: {$value}"); + } + } + + $this->log("数据清理任务完成"); + + return [ + 'ip_cleanup' => $ipCleanup, + 'submission_cleanup' => $submissionCleanup, + 'optimize' => $optimize, + 'before_stats' => $beforeStats, + 'after_stats' => $afterStats + ]; + } +} + +// 如果直接运行此脚本 +if (php_sapi_name() === 'cli' || !isset($_SERVER['HTTP_HOST'])) { + $cleanup = new DataCleanup(); + $result = $cleanup->runFullCleanup(); + + echo "\n清理任务执行完成!\n"; + if ($result['ip_cleanup'] !== false) { + echo "IP限制记录清理: {$result['ip_cleanup']} 条\n"; + } + if ($result['submission_cleanup'] !== false) { + echo "投稿记录清理: 网址 {$result['submission_cleanup']['website']} 条, APP {$result['submission_cleanup']['app']} 条\n"; + } + echo "数据库优化: " . ($result['optimize'] ? '成功' : '失败') . "\n"; +} else { + // 如果通过Web访问,返回JSON格式结果 + header('Content-Type: application/json; charset=utf-8'); + + // 简单的安全检查 + if (!isset($_GET['token']) || $_GET['token'] !== 'cleanup_token_2024') { + http_response_code(403); + echo json_encode(['error' => '访问被拒绝']); + exit; + } + + $cleanup = new DataCleanup(); + $result = $cleanup->runFullCleanup(); + + echo json_encode([ + 'success' => true, + 'message' => '清理任务执行完成', + 'data' => $result + ], JSON_UNESCAPED_UNICODE); +} +?> \ No newline at end of file diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..a201149 --- /dev/null +++ b/config/database.php @@ -0,0 +1,147 @@ +conn = null; + + try { + if ($this->use_sqlite) { + // SQLite配置 + $this->conn = new PDO('sqlite:' . __DIR__ . '/../data/database.sqlite'); + } else { + // MySQL配置 + $this->conn = new PDO( + "mysql:host=" . $this->host . ";dbname=" . $this->db_name . ";charset=utf8mb4", + $this->username, + $this->password + ); + } + $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + } catch(PDOException $exception) { + echo "连接失败: " . $exception->getMessage(); + } + + return $this->conn; + } + + public function initDatabase() { + $conn = $this->getConnection(); + + // 创建管理员表 + $admin_table = " + CREATE TABLE IF NOT EXISTS admins ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + "; + + // 创建网址投稿表 + $website_table = " + CREATE TABLE IF NOT EXISTS website_submissions ( + id INT AUTO_INCREMENT PRIMARY KEY, + url VARCHAR(500) NOT NULL, + title VARCHAR(200), + description TEXT, + keywords VARCHAR(500), + platforms TEXT, + contact VARCHAR(100), + ip_address VARCHAR(45) NOT NULL, + status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending', + admin_note TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) + "; + + // 创建APP/软件投稿表 + $app_table = " + CREATE TABLE IF NOT EXISTS app_submissions ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(200) NOT NULL, + platform VARCHAR(100) NOT NULL, + version VARCHAR(50), + icon_url VARCHAR(500), + download_url VARCHAR(500), + website_url VARCHAR(500), + description TEXT, + platforms TEXT, + contact VARCHAR(100), + ip_address VARCHAR(45) NOT NULL, + status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending', + admin_note TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) + "; + + // 创建IP限制表 + $ip_limit_table = " + CREATE TABLE IF NOT EXISTS ip_submissions ( + id INT AUTO_INCREMENT PRIMARY KEY, + ip_address VARCHAR(45) NOT NULL, + submission_date DATE NOT NULL, + count INT DEFAULT 1, + UNIQUE KEY unique_ip_date (ip_address, submission_date) + ) + "; + + if ($this->use_sqlite) { + // SQLite语法调整 + $admin_table = str_replace('AUTO_INCREMENT', '', $admin_table); + $admin_table = str_replace('INT AUTO_INCREMENT PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT', $admin_table); + $admin_table = str_replace('TIMESTAMP DEFAULT CURRENT_TIMESTAMP', 'DATETIME DEFAULT CURRENT_TIMESTAMP', $admin_table); + + $website_table = str_replace('AUTO_INCREMENT', '', $website_table); + $website_table = str_replace('INT AUTO_INCREMENT PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT', $website_table); + $website_table = str_replace('ENUM(\'pending\', \'approved\', \'rejected\')', 'TEXT CHECK(status IN (\'pending\', \'approved\', \'rejected\'))', $website_table); + $website_table = str_replace('TIMESTAMP DEFAULT CURRENT_TIMESTAMP', 'DATETIME DEFAULT CURRENT_TIMESTAMP', $website_table); + $website_table = str_replace('ON UPDATE CURRENT_TIMESTAMP', '', $website_table); + + $app_table = str_replace('AUTO_INCREMENT', '', $app_table); + $app_table = str_replace('INT AUTO_INCREMENT PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT', $app_table); + $app_table = str_replace('ENUM(\'pending\', \'approved\', \'rejected\')', 'TEXT CHECK(status IN (\'pending\', \'approved\', \'rejected\'))', $app_table); + $app_table = str_replace('TIMESTAMP DEFAULT CURRENT_TIMESTAMP', 'DATETIME DEFAULT CURRENT_TIMESTAMP', $app_table); + $app_table = str_replace('ON UPDATE CURRENT_TIMESTAMP', '', $app_table); + + $ip_limit_table = str_replace('AUTO_INCREMENT', '', $ip_limit_table); + $ip_limit_table = str_replace('INT AUTO_INCREMENT PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT', $ip_limit_table); + } + + try { + $conn->exec($admin_table); + $conn->exec($website_table); + $conn->exec($app_table); + $conn->exec($ip_limit_table); + + // 插入默认管理员账户 + $check_admin = $conn->prepare("SELECT COUNT(*) FROM admins WHERE username = 'admin'"); + $check_admin->execute(); + + if ($check_admin->fetchColumn() == 0) { + $default_password = password_hash('admin', PASSWORD_DEFAULT); + $insert_admin = $conn->prepare("INSERT INTO admins (username, password) VALUES ('admin', ?)"); + $insert_admin->execute([$default_password]); + } + + return true; + } catch(PDOException $e) { + echo "数据库初始化失败: " . $e->getMessage(); + return false; + } + } +} +?> \ No newline at end of file diff --git a/includes/utils.php b/includes/utils.php new file mode 100644 index 0000000..e5bfc0d --- /dev/null +++ b/includes/utils.php @@ -0,0 +1,266 @@ +db = $database; + } + + /** + * 获取网站TDK信息 + */ + public function getWebsiteInfo($url) { + // 确保URL包含协议 + if (!preg_match('/^https?:\/\//i', $url)) { + $url = 'http://' . $url; + } + + $result = [ + 'title' => '', + 'description' => '', + 'keywords' => '', + 'url' => $url, + 'success' => false + ]; + + try { + $context = stream_context_create([ + 'http' => [ + 'timeout' => 10, + 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + ] + ]); + + $html = @file_get_contents($url, false, $context); + + if ($html === false) { + return $result; + } + + // 转换编码 + $html = mb_convert_encoding($html, 'UTF-8', 'auto'); + + // 解析HTML + $dom = new DOMDocument(); + @$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + + // 获取title + $titles = $dom->getElementsByTagName('title'); + if ($titles->length > 0) { + $result['title'] = trim($titles->item(0)->textContent); + } + + // 获取meta标签 + $metas = $dom->getElementsByTagName('meta'); + foreach ($metas as $meta) { + $name = $meta->getAttribute('name'); + $property = $meta->getAttribute('property'); + $content = $meta->getAttribute('content'); + + if (strtolower($name) === 'description' || strtolower($property) === 'og:description') { + if (empty($result['description'])) { + $result['description'] = trim($content); + } + } + + if (strtolower($name) === 'keywords') { + $result['keywords'] = trim($content); + } + } + + $result['success'] = true; + + } catch (Exception $e) { + // 静默处理错误 + } + + return $result; + } + + /** + * 检查IP提交限制 + */ + public function checkIPLimit($ip) { + $today = date('Y-m-d'); + + $stmt = $this->db->prepare(" + SELECT count FROM ip_submissions + WHERE ip_address = ? AND submission_date = ? + "); + $stmt->execute([$ip, $today]); + $result = $stmt->fetch(); + + if ($result) { + return $result['count'] < 3; + } + + return true; + } + + /** + * 记录IP提交 + */ + public function recordIPSubmission($ip) { + $today = date('Y-m-d'); + + $stmt = $this->db->prepare(" + INSERT INTO ip_submissions (ip_address, submission_date, count) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE count = count + 1 + "); + + try { + $stmt->execute([$ip, $today]); + } catch (PDOException $e) { + // SQLite兼容处理 + $check = $this->db->prepare(" + SELECT count FROM ip_submissions + WHERE ip_address = ? AND submission_date = ? + "); + $check->execute([$ip, $today]); + $existing = $check->fetch(); + + if ($existing) { + $update = $this->db->prepare(" + UPDATE ip_submissions + SET count = count + 1 + WHERE ip_address = ? AND submission_date = ? + "); + $update->execute([$ip, $today]); + } else { + $insert = $this->db->prepare(" + INSERT INTO ip_submissions (ip_address, submission_date, count) + VALUES (?, ?, 1) + "); + $insert->execute([$ip, $today]); + } + } + } + + /** + * 检查网址重复 + */ + public function checkWebsiteDuplicate($url) { + // 标准化URL + $normalized_url = $this->normalizeUrl($url); + $domain = $this->extractDomain($url); + + $stmt = $this->db->prepare(" + SELECT id FROM website_submissions + WHERE url = ? OR url LIKE ? + "); + $stmt->execute([$normalized_url, '%' . $domain . '%']); + + return $stmt->fetch() !== false; + } + + /** + * 检查APP重复 + */ + public function checkAppDuplicate($name, $platform) { + $stmt = $this->db->prepare(" + SELECT id FROM app_submissions + WHERE name = ? AND platform = ? + "); + $stmt->execute([$name, $platform]); + + return $stmt->fetch() !== false; + } + + /** + * 标准化URL + */ + private function normalizeUrl($url) { + // 移除协议 + $url = preg_replace('/^https?:\/\//i', '', $url); + // 移除www + $url = preg_replace('/^www\./i', '', $url); + // 移除尾部斜杠 + $url = rtrim($url, '/'); + // 转换为小写 + $url = strtolower($url); + + return $url; + } + + /** + * 提取域名 + */ + private function extractDomain($url) { + $parsed = parse_url($url); + $host = isset($parsed['host']) ? $parsed['host'] : $url; + + // 移除www + $host = preg_replace('/^www\./i', '', $host); + + return strtolower($host); + } + + /** + * 获取客户端IP + */ + public static function getClientIP() { + $ip_keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR']; + + foreach ($ip_keys as $key) { + if (!empty($_SERVER[$key])) { + $ip = $_SERVER[$key]; + if (strpos($ip, ',') !== false) { + $ip = trim(explode(',', $ip)[0]); + } + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + return $ip; + } + } + } + + return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'; + } + + /** + * 生成验证码 + */ + public static function generateCaptcha() { + session_start(); + + $code = ''; + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + for ($i = 0; $i < 4; $i++) { + $code .= $chars[rand(0, strlen($chars) - 1)]; + } + + $_SESSION['captcha'] = $code; + + // 创建图片 + $image = imagecreate(100, 40); + $bg_color = imagecolorallocate($image, 255, 255, 255); + $text_color = imagecolorallocate($image, 0, 0, 0); + $line_color = imagecolorallocate($image, 200, 200, 200); + + // 添加干扰线 + for ($i = 0; $i < 5; $i++) { + imageline($image, rand(0, 100), rand(0, 40), rand(0, 100), rand(0, 40), $line_color); + } + + // 添加文字 + imagestring($image, 5, 25, 12, $code, $text_color); + + header('Content-Type: image/png'); + imagepng($image); + imagedestroy($image); + } + + /** + * 验证验证码 + */ + public static function verifyCaptcha($input) { + session_start(); + return isset($_SESSION['captcha']) && strtoupper($input) === $_SESSION['captcha']; + } +} +?> \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..f8741a8 --- /dev/null +++ b/index.php @@ -0,0 +1,580 @@ +getConnection(); +$utils = new Utils($db); + +// 初始化数据库 +$database->initDatabase(); + +$message = ''; +$message_type = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $ip = Utils::getClientIP(); + + // 检查IP限制 + if (!$utils->checkIPLimit($ip)) { + $message = '您今天的提交次数已达上限(3次),请明天再试。'; + $message_type = 'error'; + } else { + $submission_type = $_POST['submission_type'] ?? 'website'; + + if ($submission_type === 'website') { + // 网址投稿处理 + $url = trim($_POST['url'] ?? ''); + $title = trim($_POST['title'] ?? ''); + $description = trim($_POST['description'] ?? ''); + $keywords = trim($_POST['keywords'] ?? ''); + $platforms = isset($_POST['platforms']) ? implode(',', $_POST['platforms']) : ''; + $contact = trim($_POST['contact'] ?? ''); + + if (empty($url)) { + $message = '请输入网址URL。'; + $message_type = 'error'; + } elseif ($utils->checkWebsiteDuplicate($url)) { + $message = '该网址已存在,请勿重复提交。'; + $message_type = 'error'; + } else { + try { + $stmt = $db->prepare(" + INSERT INTO website_submissions (url, title, description, keywords, platforms, contact, ip_address) + VALUES (?, ?, ?, ?, ?, ?, ?) + "); + $stmt->execute([$url, $title, $description, $keywords, $platforms, $contact, $ip]); + + $utils->recordIPSubmission($ip); + $message = '提交成功!我们会尽快审核您的内容。'; + $message_type = 'success'; + } catch (PDOException $e) { + $message = '提交失败,请稍后重试。'; + $message_type = 'error'; + } + } + } else { + // APP投稿处理 + $name = trim($_POST['app_name'] ?? ''); + $platform = trim($_POST['app_platform'] ?? ''); + $version = trim($_POST['app_version'] ?? ''); + $icon_url = trim($_POST['icon_url'] ?? ''); + $download_url = trim($_POST['download_url'] ?? ''); + $website_url = trim($_POST['website_url'] ?? ''); + $description = trim($_POST['app_description'] ?? ''); + $platforms = isset($_POST['platforms']) ? implode(',', $_POST['platforms']) : ''; + $contact = trim($_POST['contact'] ?? ''); + + if (empty($name) || empty($platform)) { + $message = '请填写应用名称和系统平台。'; + $message_type = 'error'; + } elseif ($utils->checkAppDuplicate($name, $platform)) { + $message = '该应用已存在,请勿重复提交。'; + $message_type = 'error'; + } else { + try { + $stmt = $db->prepare(" + INSERT INTO app_submissions (name, platform, version, icon_url, download_url, website_url, description, platforms, contact, ip_address) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "); + $stmt->execute([$name, $platform, $version, $icon_url, $download_url, $website_url, $description, $platforms, $contact, $ip]); + + $utils->recordIPSubmission($ip); + $message = '提交成功!我们会尽快审核您的内容。'; + $message_type = 'success'; + } catch (PDOException $e) { + $message = '提交失败,请稍后重试。'; + $message_type = 'error'; + } + } + } + } +} +?> + + + + + + + 内容投稿系统 + + + + +
+
+

内容投稿系统

+

分享优质内容,共建互联网生态

+
+ +
+
+ 网址投稿 +
+
+ APP/软件投稿 +
+
+ +
+ +
+ +
+ + +
+ + + +
+
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ 提醒:自媒体维基和zTab平台需要合法合规的内容,请确保您提交的内容符合相关法律法规。 +
+
+ 提醒:SOSO平台内容审查相对宽松,但仍需遵守基本的网络道德规范。 +
+
+ +
+ + +
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/install.php b/install.php new file mode 100644 index 0000000..096d8fd --- /dev/null +++ b/install.php @@ -0,0 +1,478 @@ +getMessage(); + $message_type = 'error'; + } + } elseif ($step == 3) { + // 初始化数据库 + require_once 'config/database.php'; + + $database = new Database(); + if ($database->initDatabase()) { + $message = '数据库初始化成功!'; + $message_type = 'success'; + $step = 4; + } else { + $message = '数据库初始化失败!'; + $message_type = 'error'; + } + } +} + +// 环境检查 +function checkEnvironment() { + $checks = [ + 'PHP版本 >= 7.4' => version_compare(PHP_VERSION, '7.4.0', '>='), + 'PDO扩展' => extension_loaded('pdo'), + 'PDO MySQL扩展' => extension_loaded('pdo_mysql'), + 'PDO SQLite扩展' => extension_loaded('pdo_sqlite'), + 'GD扩展' => extension_loaded('gd'), + 'cURL扩展' => extension_loaded('curl'), + 'config目录可写' => is_writable(__DIR__ . '/config'), + 'data目录可写' => is_writable(__DIR__ . '/data') || mkdir(__DIR__ . '/data', 0755, true) + ]; + + return $checks; +} + +$env_checks = checkEnvironment(); +?> + + + + + + + 安装向导 - 内容投稿系统 + + + + +
+
+

安装向导

+

内容投稿系统安装配置

+
+
1
+
2
+
3
+
4
+
+
+ +
+ +
+ +
+ + + + +

环境检查

+

正在检查服务器环境是否满足运行要求...

+ + + + + + +
+ + 环境检查未通过,请先解决上述问题后再继续安装。 +
+ + + + +

数据库配置

+

请选择数据库类型并配置连接信息。

+ +
+
+

MySQL数据库

+

适合生产环境,性能更好,支持并发访问

+
+ +
+

SQLite数据库

+

适合小型站点,无需额外配置,开箱即用

+
+ + + +
+

MySQL配置

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+

SQLite配置

+

SQLite数据库将自动创建在 data/database.sqlite 文件中,无需额外配置。

+
+ +
+ +
+
+ + + +

初始化数据库

+

数据库连接成功!现在将创建必要的数据表。

+ +
+
+ +
+
+ + + +

安装完成

+
+ + 恭喜!内容投稿系统安装成功! +
+ +

默认管理员账户

+

用户名:admin

+

密码:admin

+

⚠️ 请登录后台后立即修改默认密码!

+ +

下一步操作

+ + + + +
+
+ + + + \ No newline at end of file diff --git a/需求说明.md b/需求说明.md new file mode 100644 index 0000000..8f92990 --- /dev/null +++ b/需求说明.md @@ -0,0 +1,31 @@ +创建一个投稿系统,需求: + +网址投稿系统,能够通过URL快速获取网站的TDK内容,用户在填入网址URL的时候可以兼容http和https的链接。 + +获取的内容可以快速填入网站名称、网站描述、关键词、网址,另外为了细化到具体投稿至哪个平台,可以增加三个可以多选的选项,分别是:自媒体维基、zTab、SOSO;在用户勾选自媒体维基、zTab时提醒用户,该平台需要合法合规的内容;在勾选SOSO时提醒用户该平台内容审查相当宽松。用户可以选择是否留有联系方式,以便我们后续沟通。方便用户提交,无需注册登录就可以提交,但为了安全起见,每天每个IP最多只能提交三次,且数据库里有相同的内容则提示内容已存在;重复检测可以检测域名,二级域名,网址参数(有更好的方案你可以使用)。 + +所以用户表单部分的内容应该是: + +需要获取信息的URL输入框、网站名称、网站描述、网站关键词、收录平台、联系方式、提交。(如果你有好的思路可以补充完善) + +后台可以审核内容,前台提交完成后,可以告诉用户,我们会尽快审核内容。用户提交的内容只会显示在后台和数据库里,通过审核/拒绝的内容不会显示在列表里,默认显示的内容都是待处理的,当然也可以通过选单切换到已经审核的内容,比如审核通过的拒绝的内容。提交页面不会有显示。 + +后台需要账号密码登录,安全起见,需要增加一个验证码的功能;另外为了方便使用,首次登录默认账号/密码为admin/admin,当admin成功登陆到后台后,提示可以修改账户名称和密码,修改完的信息将写入数据库,然后折叠修改区域。 + +整体的样式UI需要参考互联网大厂的设计思路,简洁大气现代,配色方面需要统一性但需要比较前沿的方向设计。 + +补充部分:因为只有网址信息过于单一,可以支持APP、软件的投稿,但投递系统不存储图片和软件包,用户可以在投稿区域切换APP/软件投稿界面。 + +APP/软件的投稿表单需要用户手动填写软件名称、系统平台、版本号、图标地址、下载链接、落地页/官网。(关于这个部分你可以思考后补充。) + +关于APP/软件的投递,后台部分也要分开来显示,网址的部分和APP/软件的不能在同一个列表里显示,因为会导致后台审核区域的信息数据混乱。 + +APP/软件应该是单独的数据表。 + +数据库默认使用MySQL,如果部署的环境配置低,可以支持兼容使用SQLite3。 + +使用开源易部署的方案,我希望整体保持轻量化,我是中学生,所以需要你完成全部的工作。 + +部署方面,我使用的是宝塔面板部署,所以你要考虑到部署的过程和配置教程等。 + +最后写一份完整的readme和开源协议。 \ No newline at end of file