跨域与预载
让我们首先解决跨域与预载问题。
跨域请见unjs/nitro - 速查攻略不废话之跨域 - 掘金 (juejin.cn),根据这篇文章,我们可以在中间件中一起把预载也处理了:
export default defineEventHandler((e) => {
handleCors(e, {
origin: '*',
methods: '*',
allowHeaders: '*',
})
if (e.node.req.method === 'OPTIONS') {
return null
}
})
如果返回值是null,则unjs/h3会设定status为204 - no content,以解决预载问题。
静态资源
配置一下静态资源的driver:
// nitro.config.ts
export default defineNitroConfig({
serverAssets: [
{
baseName: 'files',
dir: '/publicAssets',
},
],
})
这里的baseName是你给这个storage起的名字,dir是磁盘中的路径,对于这个配置,你可以像这样来使用他:useStorage('assets:files')
下载逻辑
这里使用了mime和unjs/pathe,其中mime用于根据后缀生成content-type,是个非常好用的包。pathe在这里只用来获取文件后缀,你也可以手写这个功能,只是他不是重点,在此不赘述。
直接上代码,接下来我会逐一解释:
// routes/foo.ts
import mime from 'mime'
import { extname } from 'pathe'
export default defineEventHandler(async (e) => {
const name = getQuery<{ filename: string }>(e).filename
const file = await useFilesStorage().getItemRaw(name)
appendResponseHeaders(e, {
'content-type': mime.getType(extname(name)),
'content-disposition': `attachment; filename=${name}`,
})
return file
})
const name = getQuery<{ filename: string }>(e).filename
这里通过getQuery方法,从前端的query中获取下载的文件名。
const file = await useFilesStorage().getItemRaw(name)
在这里获取文件。其中useFilesStorage是自定义的工具函数,其源码很简单:
const useFilesStorage = () => useStorage('assets:files')
这样就不需要你每次都敲storage名了。
值得一提的是,useStorage来自unjs/unStorage,其中storage.getItem函数所得到的结果是被格式化后的结果,比如文件会被读取为字符串。如果你想到读取文件本身,而非文件内容(比如你想获取一个txt的文件内容可以用getItem),应该使用storage.getItemRaw。
appendResponseHeaders
在这里添加了必要的响应头。content-type用来表示媒体类型,这里使用mime生成,content-disposition中,attachment用于指出这是需要被下载的文件,filename很显然表示文件名。为了方便前端通过content-disposition响应头获取到文件名,你还需要配置一个Access-Control-Expose-Headers响应头,这会在后面提到。
最后,直接返回getItemRaw的结果即可。其结果一般是个Buffer。
Access-Control-Expose-Headers
根据浏览器安全策略,不是所有headers都可以在前端的js中获取到,其中也包括了content-disposition。为了方便前端获取到文件名,使用如下代码实现:
// middleware/exposeHeaders.ts
// 我把它放在中间件中,这样比较方便
export default defineEventHandler((e) => {
setResponseHeaders(e, {
'Access-Control-Expose-Headers': ['content-disposition'],
})
})
在Access-Control-Expose-Headers头中指出你需要暴露content-disposition头即可。
报告完毕!