OSS 附件改为私有桶+内网Endpoint+服务器代理架构
背景:桶为私有、且只用内网 Endpoint(外网不可达),浏览器既无法直传、 也无法直连。原「浏览器直传 + 签名URL直连」两条路都失效,改为全站代理。 lib/oss.php: - endpoint 自动转内网(*-internal),use_internal_endpoint 开关控制,内网走 http - 移除浏览器直传凭证(PostObject),改为服务器端 PUT(流式 fb_oss_put_file) - 新增 fb_oss_stream_get 流式读取(回调式,支持 Range,不整文件进内存) - 签名支持 x-oss-process 子资源 + Authorization 头签名(Content-Type 入签) - 新增 fb_oss_probe 连通性自检、fb_oss_error_text 错误归因(含跨地域提示) media.php(新增): 附件代理输出,鉴权边界=对象键须在本站附件表(或本人待入库 登记)且帖子对访客可见,防止本站沦为能读整个共用桶的开放代理;透传 Range。 upload.php: local/oss 统一走两步(领键→action=put),OSS 模式由服务器代理 PUT。 lib/db.php: 新增 pending_attachments 表 + idx_att_key 索引(迁移幂等自动建)。 lib/attach.php: 待入库登记(mark/clear/mine/gc),解决上传后入库前的预览鉴权。 cron.php: 清理过期待入库登记 + 桶连通性自检输出。 admin.php: 设置页加「使用内网 Endpoint」开关、存储体检显示实际连接地址与探测按钮。 README/config: 全面更新为代理架构(无需CORS、无需自定义域名、同地域要求)。 验证:本地 e2e 25/25 + 附件专项 13/13(含防开放代理/Range/内网改写); 线上真机上传→内网PUT→media.php代理读回→Range 全通过。
This commit is contained in:
@@ -394,10 +394,63 @@ function fb_attach_save(PDO $pdo, $threadId, $replyId, array $list) {
|
||||
$i, $now,
|
||||
]);
|
||||
$n++;
|
||||
// 转正成功即从「待入库」表移除(media.php 之后改走 attachments 表鉴权)
|
||||
fb_attach_pending_clear($it['key']);
|
||||
}
|
||||
return $n;
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* 待入库附件登记
|
||||
* ------------------------------------------------------------------
|
||||
* 上传成功 → 登记;提交表单入库 → 转正并移除;一直没提交 → 由 cron 清理。
|
||||
* 只为 media.php 的代理鉴权服务(上传后、入库前的缩略图预览)。
|
||||
* ================================================================== */
|
||||
|
||||
/** 登记一条待入库附件 */
|
||||
function fb_attach_pending_mark($key, $kind, $mime, $size) {
|
||||
try {
|
||||
$pdo = fb_db();
|
||||
$pdo->prepare(
|
||||
'INSERT INTO pending_attachments(oss_key, kind, mime, size, ip_hash, created_at)
|
||||
VALUES(?,?,?,?,?,?)'
|
||||
)->execute([(string)$key, (string)$kind, (string)$mime, (int)$size, fb_ip_hash(), time()]);
|
||||
return true;
|
||||
} catch (Throwable $e) { return false; }
|
||||
}
|
||||
|
||||
/** 转正/放弃后移除登记(幂等) */
|
||||
function fb_attach_pending_clear($key) {
|
||||
try {
|
||||
$pdo = fb_db();
|
||||
$pdo->prepare('DELETE FROM pending_attachments WHERE oss_key = ?')->execute([(string)$key]);
|
||||
return true;
|
||||
} catch (Throwable $e) { return false; }
|
||||
}
|
||||
|
||||
/** 该对象键是否由【当前访客】在有效期内登记过(media.php 鉴权用) */
|
||||
function fb_attach_pending_mine($key, $ttl = 7200) {
|
||||
try {
|
||||
$pdo = fb_db();
|
||||
$st = $pdo->prepare(
|
||||
'SELECT id FROM pending_attachments
|
||||
WHERE oss_key = ? AND ip_hash = ? AND created_at >= ? LIMIT 1'
|
||||
);
|
||||
$st->execute([(string)$key, fb_ip_hash(), time() - (int)$ttl]);
|
||||
return (bool)$st->fetch();
|
||||
} catch (Throwable $e) { return false; }
|
||||
}
|
||||
|
||||
/** 清理过期的待入库登记(cron 调用;只删记录,不动 OSS 对象——那属于未提交的孤儿,另行处理) */
|
||||
function fb_attach_pending_gc($ttl = 7200) {
|
||||
try {
|
||||
$pdo = fb_db();
|
||||
$st = $pdo->prepare('DELETE FROM pending_attachments WHERE created_at < ?');
|
||||
$st->execute([time() - (int)$ttl]);
|
||||
return $st->rowCount();
|
||||
} catch (Throwable $e) { return 0; }
|
||||
}
|
||||
|
||||
/** 取某个主题的附件:['main' => [...], 'replies' => [reply_id => [...]]] */
|
||||
function fb_attach_for_thread(PDO $pdo, $threadId) {
|
||||
$st = $pdo->prepare('SELECT * FROM attachments WHERE thread_id = ? ORDER BY seq ASC, id ASC');
|
||||
|
||||
Reference in New Issue
Block a user