wo-base-fileloader/fileloader.js

86 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const multer = require('multer')
const path = require('path')
const fs = require('fs')
const crypto = require('crypto')
const mime = require('mime') // mime@4 do not support require, only support import. Therefore use mime@2.
const wo = global.wo
const my = {}
module.exports = {
initMy ({ ipfsProvider, ipfsStore, fileSizeLimit }) {
my.ipfsProvider = ipfsProvider
my.ipfsStore = ipfsStore
my.fileSizeLimit = fileSizeLimit || globalThis.wo?.envar?.fileSizeLimit || 10485760
return this
},
MulterStore: multer({
// dest:'./File/', // 这样,不能自定义文件名。
storage: multer.diskStorage({
destination (req, file, cb) {
// 如果直接提供字符串multer会负责创建该目录。如果提供函数你要负责确保该目录存在。
cb(null, wo?.envar?.fileStore) // 目录是相对于本应用的入口js的即相对于 server.js 的位置。
},
filename (req, file, cb) {
// file 为 {encoding, fieldname, mimetype, originalname, stream}
// 注意req.body 也许还没有信息因为这取决于客户端发送body和file的顺序。必要的信息请从 req.headers 传递,例如 _passtoken在multer时尚未进入路由不存在已装好的 _passtokenSource
// 虽然可以把本方法写成 async 的,并且在这里调用 wo.ipfsStore.ipfs.add(file.stream) 来获取 cid但是最后在本地存成的文件是 0字节的。因此还是用个随机数吧。
// 或者,干脆利用这个缺陷,直接提交到 ipfs在本地就留着0字节文件不要使用就好了。同时在 api.receiveFile 里,就要相应的直接返回 IPFS 网址给前端。
const filename =
`${Date.now()}-${crypto.randomBytes(16).toString('hex')}` +
(path.extname(file.originalname || '').toLowerCase() || `.${mime.getExtension(file?.mimetype)}`) // 注意到这时的 originalname 形如 file-1732762348744不知什么时机会变成 originalnameSystem而 originalname 变回正确的前端的文件名
//const _passtokenSource = webtoken.verifyToken(req.headers._passtoken) || {}
//const filename = `${req.path.replace(/^\/api\d*/, '')}_${_passtokenSource.usid}_${Date.now()}${fileNameExtension}` // 如果最终 filename 含有 / (例如当 req.path 为 Who/todo则必须已经存在该目录否则在这里就出错不会进入下面流程。
cb(null, filename)
},
}),
// fileFilter:function(req, file, cb) {},
limits: { fileSize: my.fileSizeLimit },
}).single('file'),
api: {
async receiveFile ({ _file = wo?._req?.file, useIpfs = true } = {}) {
// req 被 multer 处理后req.file 为 { destination, filename, originialname, path, mimetype, size }, 其中 path 包括了 destination 和 filename 的文件相对路径,但不包括起头的 '/',例如 '_filestore/xxx.png'
const ipfsProvider = my.ipfsProvider || global.wo?.ipfsProvider
const ipfsStore = my.ipfsStore || global.wo?.ipfsStore
if (!_file?.path) {
return { _state: 'WOBASE_FAIL_FILE_NOT_RECEIVED' }
}
_file.path = _file.path.replace('\\', '/')
_file.baseUrl = `${global.wo?.envar?.servUrl?.replace?.(/\/$/, '')}/${_file.path}` // 传统的基于服务器的 url。在开发环境下不一定符合前端实际因为后台只知道预设的 servUrl而前端会再根据 location.origin 来调整。
if (useIpfs && ipfsStore && ipfsProvider) {
// 为了在这里使用 wo.ipfsStore.add, 需要提供 FileContent不能直接用 req.file
// 20230312: not working with nodejs above (not including) 18.2.1! https://github.com/nodejs/node/issues/46221
const { cid } = await ipfsStore?.add?.(ipfsProvider.globSource(_file.path, ''), {
// __dirname 是本文件所在目录,但 path.resolve(_file.path) 得到的是正确路径。
// 20230713 不知为何今天在本机测试时ipfs-core 报错 Pattern must be a string之前没有过这个问题。跟踪后发现 ipfs-core 0.14.3 代码里 第二个参数 patter 必须是 string于是添加第二个参数为 '',现在可以上传文件给本地的 ipfs-core 了。经测试,加不加这个参数都不影响 ipfs-http-client 运行。
cidVersion: 1,
hashAlg: 'sha2-256',
onlyHash: false, // 多个备份是好的,而且能加快下次添加同样文件的速度。
pin: false, // 用户第一次上传的,可能并不是最后想要的,不必须永存。
})
_file.cid = cid?.toString() // + path.extname(file.filename)
_file.ipfsUrl = `${global.wo?.envar?.ipfsLens?.replace?.(/\/$/, '')}/${_file.cid}` // 1) 前端自己选用 cid 或 ipfsUrl。2) 在本地测试成功,但是发现第一次上传的文件,作为 ipfs url 图片在前端显示比较慢不如作为传统服务器的快。第二次上传同样文件的ipfs前端显示就快了。
// rename the file to the cid
const newFileName =
`${new Date().toJSON().replace(/[-:T]|\.\d\d\dZ$/g, '')}-${_file.cid}` +
(path.extname(_file.originalname || '').toLowerCase() || `.${mime.getExtension(_file?.mimetype)}`)
try {
await fs.renameSync(path.resolve(_file.path), path.resolve(_file.destination, newFileName))
// set all properties of _file containing the original file name to the new file name
_file.path = _file.path?.replace?.(_file.filename, newFileName)
_file.baseUrl = _file.baseUrl?.replace?.(_file.filename, newFileName)
_file.filename = newFileName
} catch (e) {
console.log('FileLoader: rename failed: ', path.resolve(_file.filename), e)
}
}
return { _state: 'SUCCESS', ..._file }
},
},
}