
前言
临时传个文件给别人,不想注册网盘、又担心长期挂着隐私。今天分享一套原创教学用途的轻量 PHP 文件分享系统:上传生成提取码、可选下载码、按时过期自动删除,解压即可运行。
声明:本文源码为原创教学示例,请勿用于违法违规用途;请遵守当地法律法规与目标服务条款,禁止传播侵权或有害文件。
功能特性
- 拖拽上传,大小可配置(默认 20MB)
- 生成 6 位提取码,可选 4 位下载码
- 有效期 1 小时~7 天,过期自动删文件
- 下载计数、原文件名安全回传
- 拦截可执行脚本类扩展名
- SQLite 存储,深色 UI,手机可用
环境要求
- PHP 8.0+,需
pdo_sqlite - 磁盘可写目录
storage/与data/ - Nginx/Apache 或
php -S均可
目录结构
file-share/
├── index.php # 入口
├── lib.php # 核心逻辑
├── config.php # 配置
├── templates/ # 页面
├── storage/ # 上传文件(注意勿公开列目录)
└── data/ # SQLite(自动创建)
完整源码
config.php
<?php
/** 临时文件分享 · 配置 */
return [
'site_name' => '轻分享',
'db_path' => __DIR__ . '/data/share.db',
'upload_dir' => __DIR__ . '/storage',
'max_size' => 20 * 1024 * 1024, // 20MB
'default_hours' => 24, // 默认有效期(小时)
'max_hours' => 168, // 最长 7 天
'code_len' => 6, // 提取码长度
'download_code' => true, // 是否启用下载码
];
lib.php
<?php
/**
* 临时文件分享核心:上传 → 提取码 → 定时过期
*/
function cfg(string $key, $default = null)
{
static $c = null;
if ($c === null) {
$c = require __DIR__ . '/config.php';
}
return $c[$key] ?? $default;
}
function e(?string $s): string
{
return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function db(): PDO
{
static $pdo = null;
if ($pdo instanceof PDO) {
return $pdo;
}
$path = cfg('db_path');
if (!is_dir(dirname($path))) {
mkdir(dirname($path), 0755, true);
}
$upload = (string)cfg('upload_dir');
if (!is_dir($upload)) {
mkdir($upload, 0755, true);
}
$pdo = new PDO('sqlite:' . $path);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$pdo->exec(<<<SQL
CREATE TABLE IF NOT EXISTS shares (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT UNIQUE NOT NULL,
dl_code TEXT DEFAULT '',
original_name TEXT NOT NULL,
stored_name TEXT NOT NULL,
size INTEGER DEFAULT 0,
hits INTEGER DEFAULT 0,
expires_at TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
SQL);
return $pdo;
}
function random_code(int $len = 6): string
{
$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
$out = '';
for ($i = 0; $i < $len; $i++) {
$out .= $chars[random_int(0, strlen($chars) - 1)];
}
return $out;
}
function csrf_token(): string
{
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(16));
}
return $_SESSION['csrf'];
}
function csrf_check(): void
{
if (!hash_equals(csrf_token(), (string)($_POST['csrf'] ?? ''))) {
http_response_code(419);
exit('CSRF 校验失败');
}
}
function flash(string $key, ?string $msg = null)
{
if ($msg !== null) {
$_SESSION['flash'][$key] = $msg;
return null;
}
$v = $_SESSION['flash'][$key] ?? null;
unset($_SESSION['flash'][$key]);
return $v;
}
function format_bytes(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$i = 0;
$n = (float)$bytes;
while ($n >= 1024 && $i < count($units) - 1) {
$n /= 1024;
$i++;
}
return round($n, $i === 0 ? 0 : 1) . ' ' . $units[$i];
}
function clean_expired(): void
{
$db = db();
$rows = $db->query("SELECT stored_name FROM shares WHERE expires_at < datetime('now')")->fetchAll();
foreach ($rows as $row) {
$f = cfg('upload_dir') . '/' . $row['stored_name'];
if (is_file($f)) {
@unlink($f);
}
}
$db->exec("DELETE FROM shares WHERE expires_at < datetime('now')");
}
function save_upload(array $file, int $hours): array
{
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
return ['ok' => false, 'msg' => '上传失败,请重试'];
}
if ($file['size'] > (int)cfg('max_size')) {
return ['ok' => false, 'msg' => '文件不能超过 ' . format_bytes((int)cfg('max_size'))];
}
if ($file['size'] <= 0) {
return ['ok' => false, 'msg' => '空文件'];
}
$orig = (string)$file['name'];
$ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION));
// 可自行扩充白名单;此处禁止明显可执行类型
$deny = ['php', 'php3', 'php4', 'php5', 'phtml', 'phar', 'exe', 'sh', 'bat', 'cmd', 'cgi', 'pl'];
if (in_array($ext, $deny, true)) {
return ['ok' => false, 'msg' => '不支持该文件类型'];
}
$stored = date('Ymd') . '_' . bin2hex(random_bytes(8));
$dest = cfg('upload_dir') . '/' . $stored;
if (!move_uploaded_file($file['tmp_name'], $dest)) {
return ['ok' => false, 'msg' => '保存失败'];
}
$hours = max(1, min((int)cfg('max_hours', 168), $hours));
$code = random_code((int)cfg('code_len', 6));
$dl = cfg('download_code') ? random_code(4) : '';
db()->prepare(
"INSERT INTO shares (code, dl_code, original_name, stored_name, size, expires_at)
VALUES (?,?,?,?,?, datetime('now', ?))"
)->execute([$code, $dl, mb_substr($orig, 0, 180), $stored, (int)$file['size'], '+' . $hours . ' hours']);
return [
'ok' => true,
'msg' => '上传成功',
'code' => $code,
'dl_code' => $dl,
'hours' => $hours,
];
}
function find_share(string $code): ?array
{
clean_expired();
$st = db()->prepare("SELECT * FROM shares WHERE code = ? AND expires_at > datetime('now')");
$st->execute([strtoupper(trim($code))]);
return $st->fetch() ?: null;
}
function view(string $tpl, array $data = []): void
{
extract($data, EXTR_SKIP);
require __DIR__ . '/templates/' . $tpl . '.php';
}
index.php
<?php
declare(strict_types=1);
session_start();
require __DIR__ . '/lib.php';
$r = $_GET['r'] ?? 'home';
header('Content-Type: text/html; charset=utf-8');
clean_expired();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
csrf_check();
if ($r === 'upload') {
$hours = (int)($_POST['hours'] ?? cfg('default_hours', 24));
$res = save_upload($_FILES['file'] ?? [], $hours);
if ($res['ok']) {
$_SESSION['last_share'] = $res;
header('Location: index.php?r=done');
} else {
flash('msg', $res['msg']);
header('Location: index.php');
}
exit;
}
if ($r === 'download') {
$share = find_share((string)($_POST['code'] ?? ''));
if (!$share) {
flash('msg', '提取码无效或已过期');
header('Location: index.php?r=get');
exit;
}
if ($share['dl_code'] !== '' && strtoupper(trim((string)($_POST['dl_code'] ?? ''))) !== strtoupper($share['dl_code'])) {
flash('msg', '下载码错误');
header('Location: index.php?r=get&code=' . urlencode($share['code']));
exit;
}
$path = cfg('upload_dir') . '/' . $share['stored_name'];
if (!is_file($path)) {
flash('msg', '文件已丢失');
header('Location: index.php');
exit;
}
db()->prepare('UPDATE shares SET hits = hits + 1 WHERE id = ?')->execute([$share['id']]);
header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($path));
$name = str_replace(["r", "n"], '', $share['original_name']);
header('Content-Disposition: attachment; filename="' . $name . '"; filename*=UTF-8''' . rawurlencode($share['original_name']));
readfile($path);
exit;
}
}
if ($r === 'done') {
$last = $_SESSION['last_share'] ?? null;
unset($_SESSION['last_share']);
view('done', ['share' => $last, 'site_name' => cfg('site_name')]);
exit;
}
if ($r === 'get') {
$code = (string)($_GET['code'] ?? '');
$share = $code !== '' ? find_share($code) : null;
view('get', [
'site_name' => cfg('site_name'),
'share' => $share,
'code' => $code,
'msg' => flash('msg'),
]);
exit;
}
view('home', [
'site_name' => cfg('site_name'),
'max_size' => format_bytes((int)cfg('max_size')),
'default_hours' => (int)cfg('default_hours', 24),
'max_hours' => (int)cfg('max_hours', 168),
'use_dl_code' => (bool)cfg('download_code'),
'msg' => flash('msg'),
]);
templates/_top.php
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= e($site_name ?? '轻分享') ?></title>
<style>
:root { --bg:#0f1115; --panel:#1a1d24; --border:#2a2f3a; --text:#e8eaed; --muted:#8b93a7; --accent:#f04494; }
* { box-sizing:border-box; }
body { margin:0; font-family:system-ui,-apple-system,"PingFang SC",sans-serif; background:var(--bg); color:var(--text); min-height:100vh; }
.wrap { max-width:680px; margin:0 auto; padding:36px 16px 60px; }
.brand { display:flex; align-items:center; gap:12px; margin-bottom:18px; }
.logo { width:42px; height:42px; border-radius:12px; background:linear-gradient(135deg,#f04494,#ff8f3a); display:grid; place-items:center; color:#fff; font-weight:800; }
h1 { margin:0; font-size:26px; }
.sub { color:var(--muted); margin:6px 0 22px; }
.card { background:var(--panel); border:1px solid var(--border); border-radius:16px; padding:22px; }
.drop { border:2px dashed var(--border); border-radius:14px; padding:28px; text-align:center; color:var(--muted); cursor:pointer; }
.drop.hot { border-color:var(--accent); color:var(--accent); background:rgba(240,68,148,.08); }
label { display:block; font-size:13px; color:var(--muted); margin:14px 0 6px; }
input, select { width:100%; padding:11px 12px; border-radius:10px; border:1px solid var(--border); background:#12151c; color:var(--text); font:inherit; }
button, .btn { border:0; cursor:pointer; background:var(--accent); color:#fff; font-weight:600; border-radius:999px; padding:12px 18px; font:inherit; text-decoration:none; display:inline-flex; }
.btn-ghost { background:transparent; border:1px solid var(--border); color:var(--text); }
.msg { padding:11px 13px; border-radius:10px; background:rgba(240,68,148,.12); color:#ff8fbc; margin-bottom:12px; font-size:14px; }
.okbox { background:rgba(61,214,140,.1); border:1px solid rgba(61,214,140,.3); color:#7dffa3; }
.codebox { font:600 22px/1.4 ui-monospace,Consolas,monospace; letter-spacing:2px; background:#12151c; border:1px solid var(--border); border-radius:12px; padding:14px; text-align:center; margin:10px 0; }
.actions { display:flex; gap:10px; flex-wrap:wrap; margin-top:14px; }
footer { text-align:center; color:var(--muted); font-size:13px; margin-top:24px; }
footer a { color:var(--accent); text-decoration:none; }
</style>
</head>
<body>
<div class="wrap">
<div class="brand">
<div class="logo">F</div>
<div>
<h1><?= e($site_name ?? '轻分享') ?></h1>
<div class="sub" style="margin:0">临时文件分享 · 提取码 · 自动过期</div>
</div>
</div>
templates/_bottom.php
<footer>轻量 PHP 临时分享 · <a href="index.php?r=get">用提取码取文件</a></footer>
</div>
<script>
(function () {
var drop = document.getElementById('drop');
var file = document.getElementById('file');
if (!drop || !file) return;
drop.addEventListener('click', function () { file.click(); });
file.addEventListener('change', function () {
drop.textContent = file.files.length ? file.files[0].name : '点击选择或拖拽文件到此处';
});
drop.addEventListener('dragover', function (e) {
e.preventDefault(); drop.classList.add('hot');
});
drop.addEventListener('dragleave', function () {
drop.classList.remove('hot');
});
drop.addEventListener('drop', function (e) {
e.preventDefault(); drop.classList.remove('hot');
if (e.dataTransfer.files.length) {
file.files = e.dataTransfer.files;
drop.textContent = file.files[0].name;
}
});
})();
</script>
</body>
</html>
templates/home.php
<?php require __DIR__ . '/_top.php'; ?>
<?php if (!empty($msg)): ?><div class="msg"><?= e($msg) ?></div><?php endif; ?>
<div class="card">
<form method="post" action="index.php?r=upload" enctype="multipart/form-data">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<div class="drop" id="drop">点击选择或拖拽文件到此处(最大 <?= e($max_size) ?>)</div>
<input type="file" name="file" id="file" required style="display:none">
<label>有效期</label>
<select name="hours">
<?php foreach ([1, 6, 12, 24, 48, 72, 168] as $h): ?>
<option value="<?= $h ?>" <?= $h === (int)$default_hours ? 'selected' : '' ?>>
<?= $h >= 24 ? ($h / 24) . ' 天' : $h . ' 小时' ?>
</option>
<?php endforeach; ?>
</select>
<div style="margin-top:18px">
<button type="submit">上传并生成提取码</button>
</div>
<p class="sub" style="margin:14px 0 0;font-size:13px">
支持任意常规文件;禁止可执行脚本。链接过期后文件自动删除。
<?php if ($use_dl_code): ?>开启下载码保护。<?php endif; ?>
</p>
</form>
</div>
<?php require __DIR__ . '/_bottom.php'; ?>
templates/done.php
<?php require __DIR__ . '/_top.php'; ?>
<div class="card">
<h2 style="margin-top:0">上传成功</h2>
<?php if (empty($share) || empty($share['ok'])): ?>
<p>未找到上传结果,请重新上传。</p>
<a class="btn" href="index.php">返回上传</a>
<?php else: ?>
<p class="sub" style="margin:0">文件已生成提取码,有效期约 <?= (int)$share['hours'] ?> 小时。</p>
<label>提取码</label>
<div class="codebox"><?= e($share['code']) ?></div>
<?php if (($share['dl_code'] ?? '') !== ''): ?>
<label>下载码(打开文件时需要)</label>
<div class="codebox"><?= e($share['dl_code']) ?></div>
<?php endif; ?>
<label>分享链接(把提取码给对方)</label>
<div class="codebox" style="font-size:15px;letter-spacing:0">
<?= e((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http')
. '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost')
. rtrim(str_replace('\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/')), '/')
. '/index.php?r=get&code=' . urlencode($share['code'])) ?>
</div>
<div class="actions">
<button type="button" onclick="navigator.clipboard.writeText('<?= e($share['code']) ?>')">复制提取码</button>
<a class="btn btn-ghost" href="index.php">再传一个</a>
</div>
<?php endif; ?>
</div>
<?php require __DIR__ . '/_bottom.php'; ?>
templates/get.php
<?php require __DIR__ . '/_top.php'; ?>
<h1>提取文件</h1>
<?php if (!empty($msg)): ?><div class="msg"><?= e($msg) ?></div><?php endif; ?>
<div class="card">
<form method="post" action="index.php?r=download">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<label>提取码</label>
<input name="code" required maxlength="12" value="<?= e($code) ?>" placeholder="例如 A1B2C3">
<?php if ($share && $share['dl_code'] !== ''): ?>
<label>下载码</label>
<input name="dl_code" required maxlength="8" placeholder="4 位下载码">
<?php endif; ?>
<?php if ($share): ?>
<p class="sub" style="margin:12px 0 0;font-size:13px">
文件:<strong><?= e($share['original_name']) ?></strong>
(<?= e(format_bytes((int)$share['size'])) ?> · 过期时间 <?= e($share['expires_at']) ?>)
</p>
<?php endif; ?>
<div style="margin-top:16px">
<button type="submit">下载文件</button>
</div>
</form>
</div>
<div class="actions">
<a class="btn btn-ghost" href="index.php">← 去上传</a>
</div>
<?php require __DIR__ . '/_bottom.php'; ?>
部署步骤
- 上传整个目录到 PHP 空间(解压 zip 即可)。
- 确保
storage/、data/可写。 - 访问首页上传文件,把提取码发给对方。
- 对方打开
index.php?r=get输入提取码(如有下载码也要填)下载。 - 建议在 Nginx 禁止列目录:对
/storage/关闭 autoindex,并只允许脚本走index.php。
安全与合规说明
- 请勿分享木马、盗版、隐私侵权等违规文件;站长需对内容负责。
- 生产环境务必 HTTPS;可把
storage/挪到 Web 根目录外并用 PHP 读文件。 - 默认拦截
php/exe/sh等扩展名,请按业务继续收紧。 - 本源码仅供学习交流,使用后果自负。
小结
一套「上传—提取码—过期删除」闭环,适合团队临时传包、给客户交付文件。下一步可扩展:密码保护、下载次数上限、管理面板、邮件通知。需要增强版可在评论区留言。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END






暂无评论内容