Skip to content

Commit 376e83d

Browse files
committed
fix: i18n && 界面样式 && api直传接口 bug
refactor: 统一存储接口抽象化
1 parent cb23413 commit 376e83d

File tree

38 files changed

+725
-915
lines changed

38 files changed

+725
-915
lines changed

backend/src/http/errors.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,27 @@ export class S3DriverError extends DriverError {
115115
}
116116
}
117117

118+
// 驱动契约错误:用于标记存储驱动在类型/能力/方法实现上的契约不一致
119+
export class DriverContractError extends DriverError {
120+
/**
121+
* @param {string} message
122+
* @param {object|any} optionsOrDetails - 同 DriverError,可覆盖 status/code/expose/details
123+
*/
124+
constructor(message = "存储驱动契约不符合规范", optionsOrDetails = null) {
125+
const base = {
126+
status: ApiStatus.INTERNAL_ERROR,
127+
code: "DRIVER_ERROR.INVALID_CONTRACT",
128+
expose: false,
129+
};
130+
const opts =
131+
optionsOrDetails && typeof optionsOrDetails === "object" && ("status" in optionsOrDetails || "code" in optionsOrDetails || "expose" in optionsOrDetails || "details" in optionsOrDetails)
132+
? { ...base, ...optionsOrDetails }
133+
: { ...base, details: optionsOrDetails };
134+
super(message, opts);
135+
this.name = "DriverContractError";
136+
}
137+
}
138+
118139
// maskSensitiveValue 用于 mask 敏感信息
119140
export const maskSensitiveValue = (value) => {
120141
if (!value || typeof value !== "string") {

backend/src/routes/fileViewRoutes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ app.get("/api/s/:slug", async (c) => {
5858

5959
// 内容交付逻辑统一由 FileViewService 处理:
6060
// - 当 use_proxy = 1 时,通过 ObjectStore 代理流式返回
61-
// - 当 use_proxy = 0 时,优先使用存储直链(custom_host / PRESIGNED),否则返回 501
61+
// - 当 use_proxy = 0 时,优先使用存储直链(custom_host / DirectLink 能力),否则返回 501
6262
return handleFileDownload(slug, db, encryptionSecret, c.req.raw, forceDownload, repositoryFactory);
6363
});
6464

backend/src/routes/shareUploadRoutes.js

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { usePolicy } from "../security/policies/policies.js";
55
import { resolvePrincipal } from "../security/helpers/principal.js";
66
import { getEncryptionSecret } from "../utils/environmentUtils.js";
77
import { FileShareService } from "../services/fileShareService.js";
8+
import { LinkService } from "../storage/link/LinkService.js";
9+
import { getFileBySlug, getPublicFileInfo } from "../services/fileService.js";
810
import { useRepositories } from "../utils/repositories.js";
911
import { getQueryBool, getQueryInt, jsonOk } from "../utils/common.js";
1012

@@ -54,7 +56,6 @@ router.put("/api/upload-direct/:filename", requireFilesCreate, async (c) => {
5456
expiresIn: getQueryInt(c, "expires_in", 0),
5557
maxViews: getQueryInt(c, "max_views", 0),
5658
override: getQueryBool(c, "override", false),
57-
// 仅当显式提供 use_proxy 时才传递,否则交由记录层按系统默认处理
5859
useProxy: c.req.query("use_proxy") != null ? getQueryBool(c, "use_proxy", true) : undefined,
5960
originalFilename: getQueryBool(c, "original_filename", false),
6061
contentType: c.req.header("content-type") || undefined,
@@ -72,7 +73,49 @@ router.put("/api/upload-direct/:filename", requireFilesCreate, async (c) => {
7273
{ ...shareParams, uploadId: uploadId || null }
7374
);
7475

75-
return jsonOk(c, result, "文件上传成功");
76+
// 对齐 /api/public/files/:slug:统一返回公开文件信息(含 rawUrl/linkType/documentPreview),不再返回分享页 URL
77+
// upload-direct 属于受信任调用场景:
78+
// - 即使设置了密码,这里也视为“已通过校验”,始终返回 rawUrl,方便调用方直接使用
79+
// - 对于代理模式(share 内容路由),自动在 rawUrl 上附加 password 查询参数,生成一条可直接使用的代理链接
80+
try {
81+
const file = await getFileBySlug(db, result.slug, encryptionSecret);
82+
const hasPassword = !!file.password;
83+
const linkService = new LinkService(db, encryptionSecret, repositoryFactory);
84+
const link = await linkService.getLinkForShare(file, null);
85+
const requestUrl = new URL(c.req.url);
86+
const publicInfo = await getPublicFileInfo(
87+
db,
88+
file,
89+
false,
90+
link,
91+
encryptionSecret,
92+
{ baseOrigin: requestUrl.origin },
93+
);
94+
95+
// 如果是代理模式且本次上传提供了密码,为 rawUrl 附加 password 查询参数
96+
let rawUrl = publicInfo.rawUrl || null;
97+
const linkType = publicInfo.linkType || null;
98+
const password = shareParams.password || null;
99+
if (rawUrl && password && (linkType === "proxy" || file.use_proxy)) {
100+
if (!rawUrl.includes("password=")) {
101+
const separator = rawUrl.includes("?") ? "&" : "?";
102+
rawUrl = `${rawUrl}${separator}password=${encodeURIComponent(password)}`;
103+
}
104+
}
105+
106+
const response = {
107+
...publicInfo,
108+
rawUrl,
109+
requires_password: hasPassword,
110+
};
111+
112+
return jsonOk(c, response, "文件上传成功");
113+
} catch (error) {
114+
// 兜底:生成公开信息失败时,返回基础分享记录但移除 url 字段,避免泄露分享页 URL
115+
console.warn("upload-direct: 生成公开文件信息失败,将返回基础分享记录:", error);
116+
const { url, ...rest } = result || {};
117+
return jsonOk(c, rest, "文件上传成功");
118+
}
76119
});
77120

78121
// 流式分享上传:通过 PUT /api/share/upload 使用原始 body 直传

backend/src/services/fileViewService.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export class FileViewService {
167167
});
168168
}
169169

170-
// use_proxy != 1 时,尝试走直链:custom_host 优先,其次 PRESIGNED;不再“代理直链”
170+
// use_proxy != 1 时,尝试走直链:custom_host 优先,其次直链能力(DirectLink,例如预签名 URL);不再“代理直链”
171171
let directUrl = null;
172172
try {
173173
const objectStore = new ObjectStore(this.db, this.encryptionSecret, this.repositoryFactory);

backend/src/storage/drivers/s3/S3StorageDriver.js

Lines changed: 13 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import { createS3Client, generateCustomHostDirectUrl } from "./utils/s3Utils.js"
1010
import { DeleteObjectCommand } from "@aws-sdk/client-s3";
1111
import { normalizeS3SubPath, isCompleteFilePath } from "./utils/S3PathUtils.js";
1212
import { updateMountLastUsed, findMountPointByPath } from "../../fs/utils/MountResolver.js";
13-
import { buildFullProxyUrl, buildSignedProxyUrl } from "../../../constants/proxy.js";
14-
import { ProxySignatureService } from "../../../services/ProxySignatureService.js";
13+
import { buildFullProxyUrl } from "../../../constants/proxy.js";
1514
import { S3DriverError, AppError } from "../../../http/errors.js";
1615

1716
// 导入各个操作模块
@@ -38,7 +37,7 @@ export class S3StorageDriver extends BaseDriver {
3837
this.capabilities = [
3938
CAPABILITIES.READER, // 读取能力:list, get, getInfo
4039
CAPABILITIES.WRITER, // 写入能力:put, mkdir, remove
41-
CAPABILITIES.PRESIGNED, // 预签名URL能力:generatePresignedUrl
40+
CAPABILITIES.DIRECT_LINK, // 直链能力(custom_host/预签名等):generateDownloadUrl/generateUploadUrl
4241
CAPABILITIES.MULTIPART, // 分片上传能力:multipart upload
4342
CAPABILITIES.ATOMIC, // 原子操作能力:rename, copy
4443
CAPABILITIES.PROXY, // 代理能力:generateProxyUrl
@@ -437,15 +436,11 @@ export class S3StorageDriver extends BaseDriver {
437436
async generateDownloadUrl(path, options = {}) {
438437
this._ensureInitialized();
439438

440-
const { mount, subPath, db } = options;
439+
const { subPath } = options;
441440
const s3SubPath = normalizeS3SubPath(subPath, false);
442441

443-
if (db && mount?.id) {
444-
await updateMountLastUsed(db, mount.id);
445-
}
446-
447442
try {
448-
const { expiresIn, forceDownload, userType, userId } = options;
443+
const { expiresIn, forceDownload, userType, userId, mount } = options;
449444
return await this.fileOps.generateDownloadUrl(s3SubPath, {
450445
expiresIn,
451446
forceDownload,
@@ -467,13 +462,9 @@ export class S3StorageDriver extends BaseDriver {
467462
async generateWebDavProxyUrl(path, options = {}) {
468463
this._ensureInitialized();
469464

470-
const { mount, subPath, db } = options;
465+
const { subPath } = options;
471466
const s3SubPath = normalizeS3SubPath(subPath, false);
472467

473-
if (db && mount?.id) {
474-
await updateMountLastUsed(db, mount.id);
475-
}
476-
477468
if (!this.customHost) {
478469
return null;
479470
}
@@ -898,44 +889,15 @@ export class S3StorageDriver extends BaseDriver {
898889
* @returns {Promise<Object>} 代理URL对象
899890
*/
900891
async generateProxyUrl(path, options = {}) {
901-
const { mount, request, download = false, db } = options;
902-
903-
// 检查挂载点是否启用代理
904-
if (!this.supportsProxyMode(mount)) {
905-
throw new AppError("此挂载点未启用代理访问", { status: ApiStatus.FORBIDDEN, code: "FORBIDDEN", expose: true });
906-
}
907-
908-
// 检查是否需要签名
909-
const signatureService = new ProxySignatureService(db, this.encryptionSecret);
910-
const signatureNeed = await signatureService.needsSignature(mount);
892+
const { request, download = false, channel = "web" } = options;
911893

912-
let proxyUrl;
913-
let signInfo = null;
914-
915-
if (signatureNeed.required) {
916-
// 生成签名
917-
signInfo = await signatureService.generateStorageSignature(path, mount);
918-
919-
// 生成带签名的代理URL
920-
proxyUrl = buildSignedProxyUrl(request, path, {
921-
download,
922-
signature: signInfo.signature,
923-
requestTimestamp: signInfo.requestTimestamp,
924-
needsSignature: true,
925-
});
926-
} else {
927-
// 生成普通代理URL
928-
proxyUrl = buildFullProxyUrl(request, path, download);
929-
}
894+
// 驱动层仅负责根据路径构造基础代理URL,不再做签名与策略判断
895+
const proxyUrl = buildFullProxyUrl(request, path, download);
930896

931897
return {
932898
url: proxyUrl,
933899
type: "proxy",
934-
signed: signatureNeed.required,
935-
signatureLevel: signatureNeed.level,
936-
expiresAt: signInfo?.expiresAt,
937-
isTemporary: signInfo?.isTemporary,
938-
policy: mount?.webdav_policy || "302_redirect",
900+
channel,
939901
};
940902
}
941903

@@ -944,19 +906,18 @@ export class S3StorageDriver extends BaseDriver {
944906
* @param {Object} mount - 挂载点信息
945907
* @returns {boolean} 是否支持代理模式
946908
*/
947-
supportsProxyMode(mount) {
948-
return mount && !!mount.web_proxy;
909+
supportsProxyMode() {
910+
return true;
949911
}
950912

951913
/**
952914
* 获取代理配置(ProxyCapable接口实现)
953915
* @param {Object} mount - 挂载点信息
954916
* @returns {Object} 代理配置对象
955917
*/
956-
getProxyConfig(mount) {
918+
getProxyConfig() {
957919
return {
958-
enabled: this.supportsProxyMode(mount),
959-
webdavPolicy: mount?.webdav_policy || "302_redirect",
920+
enabled: this.supportsProxyMode(),
960921
};
961922
}
962923

backend/src/storage/drivers/s3/operations/S3FileOperations.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ export class S3FileOperations {
286286

287287
// 统一在驱动层使用 canonical 字段 url,供上层 LinkStrategy/LinkService 消费
288288
const url = presignedUrl;
289-
const type = this.config.custom_host ? "custom_host" : "presigned";
289+
const type = this.config.custom_host ? "custom_host" : "native_direct";
290290

291291
return {
292292
success: true,

0 commit comments

Comments
 (0)