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/软件投稿
+
+
+
+
+
+
+
+
+
\ 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();
+?>
+
+
+
+
+
+
+ 安装向导 - 内容投稿系统
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
环境检查
+
正在检查服务器环境是否满足运行要求...
+
+
+ $status): ?>
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 环境检查未通过,请先解决上述问题后再继续安装。
+
+
+
+
+
+
数据库配置
+
请选择数据库类型并配置连接信息。
+
+
+
+
+
+
初始化数据库
+
数据库连接成功!现在将创建必要的数据表。
+
+
+
+
+
+
安装完成
+
+
+ 恭喜!内容投稿系统安装成功!
+
+
+
默认管理员账户
+
用户名:admin
+
密码:admin
+
⚠️ 请登录后台后立即修改默认密码!
+
+
下一步操作
+
+ - 删除或重命名 install.php 文件以确保安全
+ - 配置Web服务器(如Apache、Nginx)
+ - 设置适当的文件权限
+ - 登录管理后台修改默认密码
+
+
+
+
+
+
+
+
+
+
\ 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