Compare commits

..

10 Commits

4 changed files with 81 additions and 36 deletions

30
.gitignore vendored
View File

@@ -5,14 +5,25 @@
# https://github.com/SlideWave/gitignore-include?tab=readme-ov-file#examples # https://github.com/SlideWave/gitignore-include?tab=readme-ov-file#examples
# https://gitignore.io # https://gitignore.io
### .gitignore_global ### ### .gitignore.global.txt ###
# Self defined extension to ignore all files/folders containing .gitignore # Self defined pattern to ignore
*.gitignore.* ?*.gitignore
*.gitignore.*/ ?*.gitignore/
*.gitignore ?*.gitignore.*
*.gitignore/ ?*.gitignore.*/
*.gitomit
*.gitomit.*
*.gitomit/
*.gitomit.*/
*.nogit
*.nogit.*
*.nogit/
*.nogit.*/
# 保留
!.gitignore !.gitignore
!.gitignore.*
!.gitkeep
# 通用 # 通用
.svn/ .svn/
@@ -23,7 +34,9 @@
/test/unit/coverage/ /test/unit/coverage/
/test/e2e/reports/ /test/e2e/reports/
node_modules/ node_modules/
*.aab
*.apk *.apk
*.ipa
*.min.js *.min.js
*.min.css *.min.css
*.min.html *.min.html
@@ -96,8 +109,5 @@ _desktop.ini
package-lock.json package-lock.json
pages4loader.json5 pages4loader.json5
# 保留 ### .gitignore.local.txt ###
!.gitkeep
### .gitignore_local ###

View File

@@ -1,14 +1,17 @@
const multer = require('multer') const multer = require('multer')
const path = require('path') const path = require('path')
const fs = require('fs')
const crypto = require('crypto') 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 wo = global.wo
const my = {} const my = {}
module.exports = { module.exports = {
initMy ({ ipfsProvider, ipfsStore }) { initMy ({ ipfsProvider, ipfsStore, fileSizeLimit }) {
my.ipfsProvider = ipfsProvider my.ipfsProvider = ipfsProvider
my.ipfsStore = ipfsStore my.ipfsStore = ipfsStore
my.fileSizeLimit = fileSizeLimit || globalThis.wo?.envar?.fileSizeLimit || 10485760
return this return this
}, },
@@ -26,25 +29,30 @@ module.exports = {
// 虽然可以把本方法写成 async 的,并且在这里调用 wo.ipfsStore.ipfs.add(file.stream) 来获取 cid但是最后在本地存成的文件是 0字节的。因此还是用个随机数吧。 // 虽然可以把本方法写成 async 的,并且在这里调用 wo.ipfsStore.ipfs.add(file.stream) 来获取 cid但是最后在本地存成的文件是 0字节的。因此还是用个随机数吧。
// 或者,干脆利用这个缺陷,直接提交到 ipfs在本地就留着0字节文件不要使用就好了。同时在 api.receiveFile 里,就要相应的直接返回 IPFS 网址给前端。 // 或者,干脆利用这个缺陷,直接提交到 ipfs在本地就留着0字节文件不要使用就好了。同时在 api.receiveFile 里,就要相应的直接返回 IPFS 网址给前端。
const filename = const filename =
`${Date.now()}-${crypto.randomBytes(16).toString('hex')}` + (path.extname(file.originalname || '') || `.${file?.mimetype?.split?.('/')?.[1]}`) `${Date.now()}-${crypto.randomBytes(16).toString('hex')}` +
(path.extname(file.originalname || '').toLowerCase() || `.${mime.getExtension(file?.mimetype) || ''}`) // 注意,如果是拖拽上传的文件,这里收到的 originalname 形如 file-1732762348744不是真实的前端文件名也不包含后缀名因此用 mime 来默认一个后缀名。
//const _passtokenSource = webtoken.verifyToken(req.headers._passtoken) || {} //const _passtokenSource = webtoken.verifyToken(req.headers._passtoken) || {}
//const filename = `${req.path.replace(/^\/api\d*/, '')}_${_passtokenSource.usid}_${Date.now()}${fileNameExtension}` // 如果最终 filename 含有 / (例如当 req.path 为 Who/todo则必须已经存在该目录否则在这里就出错不会进入下面流程。 //const filename = `${req.path.replace(/^\/api\d*/, '')}_${_passtokenSource.usid}_${Date.now()}${fileNameExtension}` // 如果最终 filename 含有 / (例如当 req.path 为 Who/todo则必须已经存在该目录否则在这里就出错不会进入下面流程。
cb(null, filename) cb(null, filename)
}, },
}), }),
// fileFilter:function(req, file, cb) {}, // fileFilter:function(req, file, cb) {},
limits: { fileSize: 10485760 }, limits: { fileSize: my.fileSizeLimit },
}).single('file'), }).single('file'),
api: { api: {
async receiveFile ({ _file = wo?._req?.file, useIpfs = true } = {}) { async receiveFile ({ _file = wo?._req?.file, useIpfs = true, filenameOriginal, _passtokenSource } = {}) {
// req 被 multer 处理后req.file 为 { destination, filename, originialname, path, mimetype, size }, 其中 path 包括了 destination 和 filename 的文件相对路径,但不包括起头的 '/',例如 '_filestore/xxx.png' // req 被 multer 处理后req.file 为 { destination, filename, originialname, path, mimetype, size }, 其中 path 包括了 destination 和 filename 的文件相对路径,但不包括起头的 '/',例如 '_filestore/xxx.png'
const ipfsProvider = my.ipfsProvider || global.wo?.ipfsProvider const ipfsProvider = my.ipfsProvider || global.wo?.ipfsProvider
const ipfsStore = my.ipfsStore || global.wo?.ipfsStore const ipfsStore = my.ipfsStore || global.wo?.ipfsStore
if (_file?.path) {
if (!_file?.path) {
return { _state: 'WOBASE_FAIL_FILE_NOT_RECEIVED' }
}
_file.path = _file.path.replace('\\', '/') _file.path = _file.path.replace('\\', '/')
_file.baseUrl = `${global.wo?.envar?.servUrl?.replace?.(/\/$/, '')}/${_file.path}` // 传统的基于服务器的 url。在开发环境下不一定符合前端实际因为后台只知道预设的 servUrl而前端会再根据 location.origin 来调整。 _file.baseUrl = `${global.wo?.envar?.servUrl?.replace?.(/\/$/, '')}/${_file.path}` // 传统的基于服务器的 url。在开发环境下不一定符合前端实际因为后台只知道预设的 servUrl而前端会再根据 location.origin 来调整。
if (useIpfs && ipfsStore) { if (useIpfs && ipfsStore && ipfsProvider) {
// 为了在这里使用 wo.ipfsStore.add, 需要提供 FileContent不能直接用 req.file // 为了在这里使用 wo.ipfsStore.add, 需要提供 FileContent不能直接用 req.file
// 20230312: not working with nodejs above (not including) 18.2.1! https://github.com/nodejs/node/issues/46221 // 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, ''), { const { cid } = await ipfsStore?.add?.(ipfsProvider.globSource(_file.path, ''), {
@@ -56,12 +64,23 @@ module.exports = {
pin: false, // 用户第一次上传的,可能并不是最后想要的,不必须永存。 pin: false, // 用户第一次上传的,可能并不是最后想要的,不必须永存。
}) })
_file.cid = cid?.toString() // + path.extname(file.filename) _file.cid = cid?.toString() // + path.extname(file.filename)
_file.ipfsUrl = `${global.wo?.envar?.ipfsGateway?.replace?.(/\/$/, '')}/${_file.cid}` // 1) 前端自己选用 cid 或 ipfsUrl。2) 在本地测试成功,但是发现第一次上传的文件,作为 ipfs url 图片在前端显示比较慢不如作为传统服务器的快。第二次上传同样文件的ipfs前端显示就快了。 _file.ipfsUrl = `${global.wo?.envar?.ipfsLens?.replace?.(/\/$/, '')}/${_file.cid}` // 1) 前端自己选用 cid 或 ipfsUrl。2) 在本地测试成功,但是发现第一次上传的文件,作为 ipfs url 图片在前端显示比较慢不如作为传统服务器的快。第二次上传同样文件的ipfs前端显示就快了。
// rename the file to the cid
if (filenameOriginal) _file.originalname = filenameOriginal
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 } return { _state: 'SUCCESS', ..._file }
} else {
return { _state: 'WOBASE_FAIL_FILE_NOT_RECEIVED' }
}
}, },
}, },
} }

View File

@@ -4,6 +4,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"mime": "^2.6.0",
"multer": "^1.4.3" "multer": "^1.4.3"
}, },
"devDependencies": {}, "devDependencies": {},

View File

@@ -6,13 +6,21 @@
# 文件在服务器端的后续更改会被同步到客户端,如果客户端也同时修改了这些文件,系统会生成冲突文件。 # 文件在服务器端的后续更改会被同步到客户端,如果客户端也同时修改了这些文件,系统会生成冲突文件。
# seafile-ignore.txt 只能忽略还没有被同步的文件。对于已经被同步的文件,如果后来把它添加到 seafile-ignore.txt 中,系统只会忽略后续更改,已经上传的版本不会受影响。 # seafile-ignore.txt 只能忽略还没有被同步的文件。对于已经被同步的文件,如果后来把它添加到 seafile-ignore.txt 中,系统只会忽略后续更改,已经上传的版本不会受影响。
### seafile-ignore_global ### ### seafile-ignore.global.txt ###
# 自定义的后缀名,凡有 sfignore 后缀的都不进行同步 # 自定义的后缀名,凡有 sfignore 后缀的都不进行同步
*.sfignore *.sfignore
*.sfignore.*
*.sfignore/ *.sfignore/
*.sfignore.*
*.sfignore.*/ *.sfignore.*/
*.sfomit
*.sfomit.*
*.sfomit/
*.sfomit.*/
*.nosf
*.nosf.*
*.nosf/
*.nosf.*/
.DS_Store .DS_Store
*/.DS_Store */.DS_Store
@@ -53,5 +61,12 @@ unpackage/
Icon Icon
OneDrive/Icon OneDrive/Icon
### seafile-ignore_local ### # wrangler project
.dev.vars*
*/.dev.vars*
.wrangler/
*/.wrangler/
### seafile-ignore.local.txt ###