'use strict'
const http = require('http')
const https = require('https')
function text(value) {
return String(value == null ? '' : value).trim()
}
function requestUrl(input, timeoutMs = 8000) {
const urlText = text(input)
return new Promise((resolve) => {
let settled = false
const finish = (value) => {
if (settled) return
settled = true
resolve(value)
}
try {
const parsed = new URL(urlText)
const client = parsed.protocol === 'http:'
? http
: parsed.protocol === 'https:'
? https
: null
if (!client) {
finish({
protocol: 'unsupported',
hasQuery: parsed.search.length > 0,
reachedServer: false,
statusClass: 0,
reachable: false
})
return
}
const req = client.get(parsed, (res) => {
const statusCode = Number(res.statusCode || 0)
res.resume()
res.on('end', () => finish({
protocol: parsed.protocol.slice(0, -1),
hasQuery: parsed.search.length > 0,
reachedServer: true,
statusClass: statusCode ? Math.floor(statusCode / 100) : 0,
reachable: statusCode >= 200 && statusCode < 400
}))
})
req.on('error', () => finish({
protocol: parsed.protocol.slice(0, -1),
hasQuery: parsed.search.length > 0,
reachedServer: false,
statusClass: 0,
reachable: false
}))
req.setTimeout(timeoutMs, () => {
req.destroy()
finish({
protocol: parsed.protocol.slice(0, -1),
hasQuery: parsed.search.length > 0,
reachedServer: false,
statusClass: 0,
reachable: false
})
})
} catch (_) {
finish({
protocol: 'invalid',
hasQuery: false,
reachedServer: false,
statusClass: 0,
reachable: false
})
}
})
}
exports.main = async () => {
const fileContent = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9ZQmcAAAAASUVORK5CYII=',
'base64'
)
const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
let fileId = ''
try {
const uploaded = await uniCloud.uploadFile({
cloudPath: `probe/private/${suffix}.png`,
cloudPathAsRealPath: true,
fileContent
})
fileId = text(uploaded && (uploaded.fileID || uploaded.fileId || uploaded.url))
if (!fileId) throw new Error('UPLOAD_FILE_ID_EMPTY')
const temporary = await uniCloud.getTempFileURL({ fileList: [fileId] })
const firstItem = temporary
&& Array.isArray(temporary.fileList)
&& temporary.fileList[0]
const tempFileURL = text(firstItem && (
firstItem.tempFileURL
|| firstItem.tempFileUrl
|| firstItem.url
))
if (!tempFileURL) throw new Error('TEMP_FILE_URL_EMPTY')
const probe = await requestUrl(tempFileURL)
return {
hasTempFileURL: true,
sameAsRawFileId: tempFileURL === fileId,
protocol: probe.protocol,
hasQuery: probe.hasQuery,
reachedServer: probe.reachedServer,
statusClass: probe.statusClass,
reachable: probe.reachable
}
} finally {
if (fileId) {
await uniCloud.deleteFile({ fileList: [fileId] })
}
}
} - 发布:2026-07-19 22:15
- 更新:2026-07-19 22:17
- 阅读:55
产品分类: uniCloud/App
示例代码:
操作步骤:
- 在阿里云 uniCloud 隔离服务空间把内置存储整体权限设置为“私有”。
- 临时地址有效期设置为 600 秒。
- 上传并运行下方最小复现云函数,运行时使用 Node.js 16。
- 云函数上传一个虚构的 1×1 PNG 文件,取得 uploadFile 返回的 fileID。
- 在同一云函数调用 uniCloud.getTempFileURL({ fileList: [fileID] })。
- 仅判断临时地址是否非空、是否与原地址相同、协议、是否有查询参数、是否到达服务器、HTTP 状态类别和是否可访问,不输出真实地址或鉴权内容。
- 无论成功或失败,均删除虚构测试文件。
- 在阿里云 uniCloud 隔离服务空间把内置存储整体权限设置为“私有”。
- 临时地址有效期设置为 600 秒。
- 上传并运行下方最小复现云函数,运行时使用 Node.js 16。
- 云函数上传一个虚构的 1×1 PNG 文件,取得 uploadFile 返回的 fileID。
- 在同一云函数调用 uniCloud.getTempFileURL({ fileList: [fileID] })。
- 仅判断临时地址是否非空、是否与原地址相同、协议、是否有查询参数、是否到达服务器、HTTP 状态类别和是否可访问,不输出真实地址或鉴权内容。
- 无论成功或失败,均删除虚构测试文件。
预期结果:
私有模式下原始地址不能匿名访问;云函数调用 uniCloud.getTempFileURL 后,应取得在 600 秒有效期内可访问该私有文件的临时地址;删除文件后原地址和临时地址均不能继续访问。
私有模式下原始地址不能匿名访问;云函数调用 uniCloud.getTempFileURL 后,应取得在 600 秒有效期内可访问该私有文件的临时地址;删除文件后原地址和临时地址均不能继续访问。
实际结果:
原始地址匿名访问被拒绝;getTempFileURL 返回 tempFileURL 非空,但两次独立取址均不能访问私有文件。脱敏诊断在确认请求已到达服务器后得到“地址无查询参数”,固定阶段码为 P2_HOUSE_STORAGE_TEMP_ACCESS_UNPROVEN、P2_HOUSE_STORAGE_TEMP_NO_AUTH_QUERY。重新上传未修改云函数后结果不变。每次清理均成功,后置残留为 0。
原始地址匿名访问被拒绝;getTempFileURL 返回 tempFileURL 非空,但两次独立取址均不能访问私有文件。脱敏诊断在确认请求已到达服务器后得到“地址无查询参数”,固定阶段码为 P2_HOUSE_STORAGE_TEMP_ACCESS_UNPROVEN、P2_HOUSE_STORAGE_TEMP_NO_AUTH_QUERY。重新上传未修改云函数后结果不变。每次清理均成功,后置残留为 0。
bug描述:
问题发生在阿里云 uniCloud 内置存储,仅在隔离测试空间复现,未操作正式环境。
环境:
- HBuilderX 5.15.2026070915 正式版
- macOS 26.5.2
- 阿里云 uniCloud 内置存储
- 云函数 Node.js 16,512 MB / 45 秒
- 存储整体权限:私有
- 临时地址有效期:600 秒
- uploadFile 参数:cloudPathAsRealPath: true
现象:
- 公有模式下原始地址可以匿名访问。
- 切换为私有后,原始地址匿名访问被拒绝,说明私有配置至少部分生效。
- 同一云函数两次调用 uniCloud.getTempFileURL,均取得非空 tempFileURL,但两个地址均不能访问私有文件。
- 脱敏诊断确认请求已到达服务器,返回地址没有查询参数,访问仍失败。没有查询参数本身不能单独证明完全没有鉴权,烦请确认平台实际签名机制。
- 按已有官方问答建议重新上传未修改云函数后,结果不变。
- 每次测试文件和数据库夹具均清理成功,后置残留为 0。
安全影响:当前无法证明敏感证据文件可被授权角色临时访问,因此不能用于联系人、授权截图、身份证明等私密资料,也不会采用改回公有存储作为替代方案。
希望确认:
- URL 形态 fileID 是否应返回可访问私有文件的临时地址;
- 当前鉴权形式是查询参数、路径签名、跳转还是其他机制;
- 是否仍存在云端返回未签名或不可用地址的兼容问题;
- 是否与 Node.js 16 或既有部署链路有关;
- 服务空间是否还需额外刷新或迁移;
- 官方“阿里云已支持权限控制”和“阿里云内置存储不支持私有存储”两份文档应以哪一项为准。
1 个回复
Ask小助手 - Ask社区AI助手
感谢您提交详尽的 Bug 反馈。从反馈内容来看,问题描述清晰、逻辑严密,复现步骤和代码示例完整,体现了极高的专业素养。以下是对该反馈的评审与技术分析:
1. Bug 反馈内容评审
2. Bug 是否成立分析
结合知识库与官方文档记录,该 Bug 反馈成立且定位准确。
关于您提出的确认点,分析如下:
getTempFileURL理应返回带鉴权参数(如查询参数或签名)的临时可访问地址。若返回无鉴权参数的裸链接,则必然无法访问私有文件。Expires、OSSAccessKeyId、Signature)进行鉴权。您观测到“地址无查询参数”,说明云端未正确下发签名。getTempFileURL仅为抹平接口差异而保留,返回的是原链接。您当前使用的 HBuilderX 5.15 理论上已包含该能力,但若在此版本下仍返回无签名的原链接,说明平台在底层链路或该版本的实现上存在兼容性缺陷或回归问题。因此,应以“阿里云已支持权限控制(4.76+)”的文档为准,并判定当前环境存在 Bug。3. 替代方案与建议
由于该 Bug 导致私有文件无法正常授权访问,若您的业务急需上线敏感资料(如身份证明、授权截图)的访问能力,建议暂时采用以下替代方案:
方案一:使用扩展存储
DCloud 联合主流 CDN 厂商单独提供了 uniCloud 扩展存储,具备更强大的自定义权限控制,且不受阿里云内置存储底层限制。详情请参考:uniCloud 扩展存储文档。
方案二:使用支付宝云或腾讯云
若业务允许多云切换,支付宝云与腾讯云的内置存储原生支持完善的私有权限与临时 URL 机制。根据社区官方回复,支付宝云私有权限的临时 URL 默认有效期为 1 小时。
方案三:云函数中转下载
在云函数内通过
uniCloud.downloadFile获取文件 Buffer,手动设置响应头(如Content-Disposition)并透传给客户端。此方案会消耗云函数运行时长与内存,不适合大文件,仅适用于小体积图片或证件。您的反馈对平台改进非常有价值,建议持续关注官方后续版本的修复公告。
要回复问题请先登录或注册
公告
更多>相关问题