跨域与预载
让我们首先解决跨域与预载问题。
跨域请见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
头即可。
报告完毕!