<think>
我们正在构建一个软件更新分发管理系统。根据需求,我们需要实现以下核心功能:
1. 用户认证系统(管理员登录、修改密码)
2. 软件版本管理(包括软件的上传、版本控制、文件管理,文件存储在腾讯云COS)
3. 文件存储系统(与腾讯云COS交互)
4. 客户端API接口(通过软件ID获取软件信息)
5. 数据统计分析(记录API请求,按IP分类统计)
6. 系统管理功能(存储配置、状态监控)
技术栈:PHP + MySQL + Bootstrap 5 + 腾讯云COS
由于这是一个复杂的系统,我们将分模块逐步实现。这里我将先给出整体的数据库设计,然后针对每个功能模块提供代码示例和解释。
数据库设计:
1. 管理员表(admins):
id (int, primary key, auto_increment)
username (varchar(50), unique)
password (varchar(255)) // 存储经过哈希的密码
created_at (datetime)
updated_at (datetime)
2. 软件表(softwares):
id (int, primary key, auto_increment) // 软件id,5位数,我们可以设置为从10000开始自增,或者使用5位随机数(需确保唯一),但需求是5位数字,我们可以使用5位数,范围10000-99999。在插入时生成一个不重复的5位数作为软件id。
name (varchar(255)) // 软件名称
created_at (datetime)
updated_at (datetime)
3. 软件版本表(software_versions):
id (int, primary key, auto_increment)
software_id (int, foreign key to softwares.id) // 关联软件
version (varchar(50)) // 版本号
stop (tinyint(1)) // 是否禁止使用,0-否,1-是,默认0
log (text) // 更新日志
created_at (datetime)
注意:我们设计软件版本表,每次更新版本都新增一条记录。每个软件的最新版本就是该软件id对应的最大id(或最新created_at)的记录。
4. 软件文件表(software_files):
id (int, primary key, auto_increment)
version_id (int, foreign key to software_versions.id) // 关联版本
name (varchar(255)) // 文件名
path (varchar(255)) // 文件路径(在存储桶中的相对路径)
md5 (varchar(32)) // 文件的md5值
open (tinyint(1)) // 更新后是否运行,0-否,1-是,默认0
download_url (varchar(500)) // 下载直链
created_at (datetime)
5. 腾讯云COS配置表(cos_config):
id (int, primary key, auto_increment)
secret_id (varchar(255))
secret_key (varchar(255))
region (varchar(100)) // 地域,如ap-guangzhou
bucket (varchar(255)) // 存储桶名称
status (tinyint(1)) // 配置状态,0-未配置或无效,1-有效
created_at (datetime)
updated_at (datetime)
6. API请求统计表(api_requests):
id (int, primary key, auto_increment)
software_id (int) // 请求的软件id
ip (varchar(45)) // 客户端IP
request_time (datetime) // 请求时间
注意:按日/周/月统计时,我们可以根据request_time进行分组统计。
接下来,我们逐步实现各个模块。
由于代码量较大,我在这里先给出关键部分的代码示例,并做详细解释。
一、用户认证系统
1.1 登录功能
使用会话(session)来管理登录状态。
示例代码(login.php):
</think>
下面我将根据需求规格说明书,为软件更新分发管理系统设计实现方案。我会提供核心模块的代码实现和详细解释。
### 1. 用户认证系统实现
```php
<?php
session_start();
// 数据库配置
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', 'password');
define('DB_NAME', 'update_system');
class Auth {
private $conn;
public function __construct() {
$this->conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($this->conn->connect_error) {
die("数据库连接失败: " . $this->conn->connect_error);
}
}
// 管理员登录
public function login($username, $password) {
$stmt = $this->conn->prepare("SELECT id, password FROM admins WHERE username = ?");
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 1) {
$user = $result->fetch_assoc();
if (password_verify($password, $user['password'])) {
$_SESSION['admin_id'] = $user['id'];
return true;
}
}
return false;
}
// 修改密码
public function changePassword($adminId, $newPassword) {
$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = $this->conn->prepare("UPDATE admins SET password = ? WHERE id = ?");
$stmt->bind_param("si", $hashedPassword, $adminId);
return $stmt->execute();
}
// 检查登录状态
public function isLoggedIn() {
return isset($_SESSION['admin_id']);
}
// 初始化默认账户
public function initializeDefaultAccount() {
$check = $this->conn->query("SELECT COUNT(*) as count FROM admins");
$row = $check->fetch_assoc();
if ($row['count'] == 0) {
$password = password_hash('admin123', PASSWORD_DEFAULT);
$this->conn->query("INSERT INTO admins (username, password) VALUES ('admin', '$password')");
}
}
}
// 使用示例
$auth = new Auth();
$auth->initializeDefaultAccount();
// 登录处理
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['login'])) {
$username = $_POST['username'];
$password = $_POST['password'];
if ($auth->login($username, $password)) {
header("Location: dashboard.php");
exit();
} else {
$error = "用户名或密码错误";
}
}
?>
```
**代码解释:**
1. 使用PHP的`password_hash`和`password_verify`函数安全处理密码
2. 通过预处理语句防止SQL注入攻击
3. 会话管理确保用户登录状态
4. 自动初始化默认管理员账户
5. 封装为类便于维护和扩展
### 2. 腾讯云COS文件操作类
```php
<?php
require 'vendor/autoload.php';
use Qcloud\Cos\Client;
class CosHandler {
private $client;
private $config;
public function __construct($secretId, $secretKey, $region, $bucket) {
$this->config = [
'region' => $region,
'credentials' => [
'secretId' => $secretId,
'secretKey' => $secretKey,
],
'bucket' => $bucket
];
$this->client = new Client([
'region' => $region,
'credentials' => [
'secretId' => $secretId,
'secretKey' => $secretKey,
],
]);
}
// 上传文件到COS
public function uploadFile($localPath, $cosPath) {
try {
$result = $this->client->upload(
bucket: $this->config['bucket'],
key: $cosPath,
body: fopen($localPath, 'rb')
);
return $result['ObjectURL'];
} catch (\Exception $e) {
error_log("COS上传失败: " . $e->getMessage());
return false;
}
}
// 从COS删除文件
public function deleteFile($cosPath) {
try {
$this->client->deleteObject([
'Bucket' => $this->config['bucket'],
'Key' => $cosPath
]);
return true;
} catch (\Exception $e) {
error_log("COS删除失败: " . $e->getMessage());
return false;
}
}
// 测试COS连接
public function testConnection() {
try {
$this->client->listObjects([
'Bucket' => $this->config['bucket'],
'MaxKeys' => 1
]);
return true;
} catch (\Exception $e) {
return false;
}
}
// 获取文件MD5
public static function calculateFileMd5($filePath) {
return md5_file($filePath);
}
}
?>
```
**代码解释:**
1. 使用腾讯云官方COS SDK进行文件操作
2. 封装上传、删除和连接测试等核心功能
3. 异常处理确保操作稳定性
4. MD5计算确保文件完整性
5. 配置参数集中管理
### 3. 软件版本管理API接口
```php
<?php
header('Content-Type: application/json');
// 数据库连接
$db = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if (isset($_GET['software_id'])) {
$softwareId = (int)$_GET['software_id'];
// 获取软件基本信息
$stmt = $db->prepare("SELECT name, stop, version, log FROM software WHERE id = ?");
$stmt->bind_param("i", $softwareId);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
http_response_code(404);
echo json_encode(['error' => 'Software not found']);
exit;
}
$software = $result->fetch_assoc();
// 获取软件文件
$files = [];
$fileStmt = $db->prepare("SELECT name, path, md5, open, download_url FROM software_files WHERE software_id = ?");
$fileStmt->bind_param("i", $softwareId);
$fileStmt->execute();
$fileResult = $fileStmt->get_result();
while ($file = $fileResult->fetch_assoc()) {
$files[] = $file;
}
// 记录API请求
$ip = $_SERVER['REMOTE_ADDR'];
$insertStmt = $db->prepare("INSERT INTO api_requests (software_id, ip) VALUES (?, ?)");
$insertStmt->bind_param("is", $softwareId, $ip);
$insertStmt->execute();
// 构建响应
$response = [
'update' => [
'version' => $software['version'],
'name' => $software['name'],
'stop' => (int)$software['stop'],
'log' => $software['log'],
'file' => $files
]
];
echo json_encode($response, JSON_UNESCAPED_SLASHES);
} else {
http_response_code(400);
echo json_encode(['error' => 'Missing software_id parameter']);
}
?>
```
**代码解释:**
1. 通过软件ID参数获取软件信息
2. 从数据库查询软件基本信息和文件列表
3. 记录每个API请求的客户端IP
4. 返回标准化的JSON响应
5. 错误处理机制完善
### 4. 文件上传前端实现 (Bootstrap)
```html
<div class="container mt-5">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">上传新版本文件</h5>
</div>
<div class="card-body">
<div class="mb-3">
<input type="file" class="form-control" id="fileInput" multiple webkitdirectory>
</div>
<div id="filePreview" class="mb-3">
<table class="table table-striped">
<thead>
<tr>
<th>文件名</th>
<th>路径</th>
<th>MD5</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="fileList">
<!-- 文件列表将动态生成 -->
</tbody>
</table>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="runAfterUpdate">
<label class="form-check-label" for="runAfterUpdate">
更新后运行此文件
</label>
</div>
</div>
<button id="uploadBtn" class="btn btn-success" disabled>
<span class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
开始上传
</button>
</div>
</div>
</div>
<script>
document.getElementById('fileInput').addEventListener('change', function(e) {
const files = Array.from(e.target.files);
const fileList = document.getElementById('fileList');
fileList.innerHTML = '';
if (files.length === 0) return;
files.forEach(file => {
const relativePath = file.webkitRelativePath || file.name;
const directory = relativePath.includes('/')
? relativePath.substring(0, relativePath.lastIndexOf('/') + 1)
: '/';
const row = document.createElement('tr');
row.innerHTML = `
<td>${file.name}</td>
<td>${directory}</td>
<td class="md5-cell">计算中...</td>
<td class="status-cell">等待上传</td>
<td><button class="btn btn-sm btn-danger remove-btn">移除</button></td>
`;
fileList.appendChild(row);
// 计算MD5(异步)
calculateMD5(file).then(md5 => {
row.querySelector('.md5-cell').textContent = md5;
});
});
document.getElementById('uploadBtn').disabled = false;
});
document.getElementById('uploadBtn').addEventListener('click', async function() {
const btn = this;
const spinner = btn.querySelector('.spinner-border');
const files = Array.from(document.getElementById('fileInput').files);
const runAfterUpdate = document.getElementById('runAfterUpdate').checked;
btn.disabled = true;
spinner.classList.remove('d-none');
for (const file of files) {
const relativePath = file.webkitRelativePath || file.name;
const directory = relativePath.includes('/')
? relativePath.substring(0, relativePath.lastIndexOf('/') + 1)
: '/';
const row = Array.from(document.querySelectorAll('#fileList tr')).find(
tr => tr.querySelector('td:first-child').textContent === file.name
);
if (row) {
row.querySelector('.status-cell').textContent = '上传中...';
try {
// 上传文件到服务器(后端再转发到COS)
const formData = new FormData();
formData.append('file', file);
formData.append('path', directory);
formData.append('run_after_update', runAfterUpdate ? '1' : '0');
const response = await fetch('/api/upload.php', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
row.querySelector('.status-cell').textContent = '上传成功';
row.querySelector('.status-cell').classList.add('text-success');
} else {
throw new Error(result.message || '上传失败');
}
} catch (error) {
row.querySelector('.status-cell').textContent = `错误: ${error.message}`;
row.querySelector('.status-cell').classList.add('text-danger');
}
}
}
spinner.classList.add('d-none');
});
// 计算文件MD5的函数
function calculateMD5(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
const md5 = CryptoJS.algo.MD5.create();
const chunkSize = 1024 * 1024 * 2; // 2MB
let offset = 0;
reader.onload = function(e) {
md5.update(CryptoJS.lib.WordArray.create(e.target.result));
offset += e.target.result.byteLength;
if (offset < file.size) {
readNext();
} else {
const hash = md5.finalize();
resolve(CryptoJS.enc.Hex.stringify(hash));
}
};
reader.onerror = reject;
function readNext() {
const slice = file.slice(offset, offset + chunkSize);
reader.readAsArrayBuffer(slice);
}
readNext();
});
}
</script>
```
**功能说明:**
1. 支持多文件和文件夹上传
2. 实时计算并显示文件MD5值
3. 显示上传进度和状态
4. 支持设置更新后是否运行文件
5. 响应式设计适配不同设备
### 5. 数据库结构设计
```sql
-- 管理员表
CREATE TABLE admins (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 软件表
CREATE TABLE software (
id INT(5) ZEROFILL AUTO_INCREMENT PRIMARY KEY, -- 5位数字ID
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 软件版本表
CREATE TABLE software_versions (
id INT AUTO_INCREMENT PRIMARY KEY,
software_id INT(5) ZEROFILL NOT NULL,
version VARCHAR(50) NOT NULL,
stop TINYINT(1) DEFAULT 0,
log TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (software_id) REFERENCES software(id) ON DELETE CASCADE
);
-- 软件文件表
CREATE TABLE software_files (
id INT AUTO_INCREMENT PRIMARY KEY,
version_id INT NOT NULL,
software_id INT(5) ZEROFILL NOT NULL,
name VARCHAR(255) NOT NULL,
path VARCHAR(255) NOT NULL,
md5 CHAR(32) NOT NULL,
open TINYINT(1) DEFAULT 0,
download_url VARCHAR(500) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (version_id) REFERENCES software_versions(id) ON DELETE CASCADE,
FOREIGN KEY (software_id) REFERENCES software(id) ON DELETE CASCADE
);
-- COS配置表
CREATE TABLE cos_config (
id INT AUTO_INCREMENT PRIMARY KEY,
secret_id VARCHAR(255) NOT NULL,
secret_key VARCHAR(255) NOT NULL,
region VARCHAR(50) NOT NULL,
bucket VARCHAR(100) NOT NULL,
status TINYINT(1) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- API请求统计表
CREATE TABLE api_requests (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
software_id INT(5) ZEROFILL NOT NULL,
ip VARCHAR(45) NOT NULL,
request_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建索引优化查询性能
CREATE INDEX idx_software_id ON software_files(software_id);
CREATE INDEX idx_request_time ON api_requests(request_time);
CREATE INDEX idx_ip ON api_requests(ip);
```
**设计说明:**
1. 使用5位数字作为软件ID (ZEROFILL AUTO_INCREMENT)
2. 规范化关系型数据库设计
3. 添加外键约束确保数据一致性
4. 为频繁查询字段创建索引
5. 使用TIMESTAMP自动记录时间信息
### 系统安全实现要点
```php
<?php
// SQL注入防护示例
function safeQuery($db, $sql, $params = []) {
$stmt = $db->prepare($sql);
if (!$stmt) return false;
if (!empty($params)) {
$types = '';
$values = [];
foreach ($params as $param) {
if (is_int($param)) $types .= 'i';
elseif (is_double($param)) $types .= 'd';
else $types .= 's';
$values[] = $param;
}
$stmt->bind_param($types, ...$values);
}
$stmt->execute();
return $stmt->get_result();
}
// XSS防护函数
function sanitizeInput($input) {
return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
// 目录遍历防护
function safePath($path) {
$path = str_replace(['../', './'], '', $path);
return trim($path, '/');
}
// 文件上传验证
function validateUploadedFile($file) {
$allowedTypes = ['exe', 'zip', '7z', 'dll', 'msi'];
$maxSize = 1024 * 1024 * 100; // 100MB
// 检查文件类型
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
if (!in_array(strtolower($ext), $allowedTypes)) {
return false;
}
// 检查文件大小
if ($file['size'] > $maxSize) {
return false;
}
// 检查是否为真实文件类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$validMimes = [
'application/x-msdownload', // .exe
'application/zip',
'application/x-7z-compressed'
];
return in_array($mime, $validMimes);
}
?>
```
**安全措施:**
1. 预处理语句防止SQL注入
2. HTML转义防止XSS攻击
3. 文件路径规范化防止目录遍历
4. 严格的文件上传验证
5. 文件类型白名单机制
6. 文件内容MIME类型验证
### 部署建议
1. 服务器环境配置:
```bash
# 安装PHP扩展
sudo apt install php8.1-mysql php8.1-curl php8.1-xml php8.1-fileinfo
# Nginx配置
server {
listen 80;
server_name updates.example.com;
root /var/www/update-system;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
# 禁止访问敏感文件
location ~ /\.(env|git) {
deny all;
}
}
```
2. 性能优化:
- 启用OPcache加速PHP执行
- 配置MySQL查询缓存
- 使用Redis缓存频繁访问的数据
- 启用Gzip压缩
- 配置浏览器缓存静态资源
3. 安全加固:
- 限制PHP执行权限
- 设置严格的文件权限
- 定期更新服务器组件
- 配置WAF防火墙
- 启用HTTPS加密