This commit is contained in:
赵鑫 2022-09-07 00:01:56 +08:00
parent b1b2b29689
commit 1a23273708
9 changed files with 138 additions and 1013 deletions

View File

@ -1,4 +1,4 @@
HOST="0.0.0.0" HOST="0.0.0.0"
PORT="3000" PORT="3000"
MONGDB="mongodb://user:pass@localhost/filesharing?authSource=admin" MONGODB_URL="mongodb://user:pass@localhost/filesharing?authSource=admin"
UPLOAD_SIZE_LIMIT=10485760 UPLOAD_SIZE_LIMIT=10485760

View File

@ -1,9 +0,0 @@
require('dotenv').config()
module.exports = {
SERVER_HOST: process.env.HOST || 'localhost',
SERVER_PORT: process.env.PORT || '3000',
MONGODB_URL: process.env.MONGDB || 'mongodb://localhost/filesharing',
UPLOAD_SIZE_LIMIT: process.env.UPLOAD_SIZE_LIMIT || 102400,
UPLOAD_TEMP_PATH: process.env.UPLOAD_TEMP_PATH || '/tmp',
UPLOAD_SAVE_PATH: process.env.UPLOAD_SAVE_PATH || 'upload',
}

912
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@
"author": "Zhao Xin <7176466@qq.com>", "author": "Zhao Xin <7176466@qq.com>",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bcrypt": "^5.0.1", "bcryptjs": "^2.4.3",
"cron": "^2.1.0", "cron": "^2.1.0",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"express": "^4.18.1", "express": "^4.18.1",

View File

@ -1,21 +1,24 @@
const { SERVER_HOST, SERVER_PORT, MONGODB_URL, UPLOAD_TEMP_PATH, UPLOAD_SAVE_PATH, UPLOAD_SIZE_LIMIT } = require('./config') require('dotenv').config()
const HOST = process.env.HOST || 'localhost'
const PORT = process.env.PORT || '3000'
const MONGODB_URL = process.env.MONGODB_URL || 'mongodb+srv://test:It6E1HC5p5K83bS8@test.ekqr7.azure.mongodb.net/test?retryWrites=true&w=majority'
const UPLOAD_SIZE_LIMIT = process.env.UPLOAD_SIZE_LIMIT || 102400
const UPLOAD_TEMP_PATH = process.env.UPLOAD_TEMP_PATH || '/tmp'
const UPLOAD_SAVE_PATH = process.env.UPLOAD_SAVE_PATH || 'upload'
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const multer = require('multer')
const bcrypt = require('bcrypt')
const md5file = require('md5-file') const md5file = require('md5-file')
const filesize = require('filesize') const filesize = require('filesize')
const express = require('express') const { hash, compare } = require('bcryptjs')
const mongoose = require('mongoose') const mongoose = require('mongoose')
const CronJob = require('cron').CronJob mongoose.connect(MONGODB_URL, { useNewUrlParser: true }).catch(error => console.error(error))
mongoose.connection.on('connected', () => console.info('mongodb is connected'))
mongoose.connection.on('error', (error) => console.error(error))
const Filesharing = require('./models/Filesharing') const Filesharing = require('./models/Filesharing')
mongoose.connect(MONGODB_URL, (error) => {
if (error) { const CronJob = require('cron').CronJob
console.error(error)
} else {
console.info('mongodb connected successfully')
}
})
const job = new CronJob( const job = new CronJob(
'0 * * * * *', '0 * * * * *',
async () => { async () => {
@ -35,21 +38,25 @@ const job = new CronJob(
null, null,
true true
) )
const express = require('express')
const app = express() const app = express()
app.set('view engine', 'pug')
app.use(express.static('public')) app.use(express.static('public'))
app.use(express.urlencoded({ extended: true })) app.set('view engine', 'pug')
app.use(express.urlencoded({ extended: false }))
app.use(express.json()) app.use(express.json())
app.locals.filesize = filesize.partial({ base: 2, standard: 'jedec' })
app.locals.moment = require('moment') app.locals.moment = require('moment')
app.locals.moment.locale('zh-cn') app.locals.moment.locale('zh-cn')
app.locals.filesize = filesize.partial({ base: 2, standard: 'jedec' })
app.get('/', async (req, res) => { app.get('/', async (req, res) => {
const filesharing = await Filesharing.find().sort({ createdAt: -1 }) const files = await Filesharing.find().sort({ createdAt: -1 })
res.render('index', { filesharing }) res.render('index', { title: '文件分享', files })
}) })
app.get('/upload', async (req, res) => { app.get('/upload', async (req, res) => {
res.render('upload') res.render('upload', { title: '文件上传' })
}) })
const multer = require('multer')
const upload = multer({ dest: UPLOAD_TEMP_PATH, limits: { fileSize: UPLOAD_SIZE_LIMIT } }) const upload = multer({ dest: UPLOAD_TEMP_PATH, limits: { fileSize: UPLOAD_SIZE_LIMIT } })
app.post('/upload', upload.single('file'), async (req, res) => { app.post('/upload', upload.single('file'), async (req, res) => {
const file_temp_path = path.join(UPLOAD_TEMP_PATH, req.file.filename) const file_temp_path = path.join(UPLOAD_TEMP_PATH, req.file.filename)
@ -67,17 +74,17 @@ app.post('/upload', upload.single('file'), async (req, res) => {
mimetype: req.file.mimetype, mimetype: req.file.mimetype,
// 解决了multer将中文文件名错误编码的问题 // 解决了multer将中文文件名错误编码的问题
filename: Buffer.from(req.file.originalname, 'latin1').toString('utf8'), filename: Buffer.from(req.file.originalname, 'latin1').toString('utf8'),
password: req.body.password ? await bcrypt.hash(req.body.password, 16) : '', password: req.body.password ? await hash(req.body.password, 16) : '',
}) })
console.log(`上传文件 ${file.filename}`) console.log(`上传文件 ${file.filename}`)
res.status(201).render('upload', { file }) res.status(201).render('upload', { title: '文件上传', file })
}) })
app.get('/download/:id', async (req, res) => { app.get('/download/:id', async (req, res) => {
try { try {
if (!req.params.id.match(/^[0-9a-fA-F]{24}$/)) throw '格式错误的ObjectId' if (!req.params.id.match(/^[0-9a-fA-F]{24}$/)) throw '格式错误的ObjectId'
const file = await Filesharing.findById(req.params.id) const file = await Filesharing.findById(req.params.id)
if (!file) throw '试图下载不存在的文件' if (!file) throw '试图下载不存在的文件'
res.render('download', { file }) res.render('download', { title: '文件下载', file })
} catch (error) { } catch (error) {
console.error(error) console.error(error)
res.sendStatus(404) res.sendStatus(404)
@ -88,7 +95,7 @@ app.post('/download/:id', async (req, res) => {
if (!req.params.id.match(/^[0-9a-fA-F]{24}$/)) throw '格式错误的ObjectId' if (!req.params.id.match(/^[0-9a-fA-F]{24}$/)) throw '格式错误的ObjectId'
const file = await Filesharing.findById(req.params.id) const file = await Filesharing.findById(req.params.id)
if (!file) throw '试图下载不存在的文件' if (!file) throw '试图下载不存在的文件'
if (file.password == '' || (await bcrypt.compare(req.body.password, file.password)) == true) { if (file.password == '' || (await compare(req.body.password, file.password)) == true) {
await Filesharing.findByIdAndUpdate(req.params.id, { $inc: { downloads: 1 } }) await Filesharing.findByIdAndUpdate(req.params.id, { $inc: { downloads: 1 } })
console.log(`下载文件 ${file.filename}`) console.log(`下载文件 ${file.filename}`)
res.status(200).download(path.join(UPLOAD_SAVE_PATH, file.md5), file.filename) res.status(200).download(path.join(UPLOAD_SAVE_PATH, file.md5), file.filename)
@ -111,6 +118,7 @@ app.get('/file/:md5', async (req, res) => {
res.sendStatus(404) res.sendStatus(404)
} }
}) })
const server = app.listen(SERVER_PORT, SERVER_HOST, () => {
console.info(`server is running at http://${SERVER_HOST}:${SERVER_PORT}`) app.listen(PORT, HOST, () => {
console.info(`server is running at http://${HOST}:${PORT}`)
}) })

View File

@ -1,41 +1,32 @@
doctype html extends layout
html(lang='zh') block main
head p
meta(charset='UTF-8') a(href='/') 返回首页
meta(http-equiv='X-UA-Compatible' content='IE=edge') form(method='POST')
meta(name='viewport' content='width=device-width, initial-scale=1.0') label 文件
link(rel='icon' href='/share/favicon.ico' type='image/x-icon') label= file.filename
link(rel='stylesheet' href='/share/style.css') label 类型
title #{file.filename} | 文件下载 label= file.mimetype
body label 大小
h1 文件下载 label= filesize(file.size)
p label 上传
a(href='/share') 返回首页 label= moment(file.createdAt).fromNow()
form(method='POST') label 热度
label 文件 label= file.downloads
label= file.filename | &nbsp; 次下载
label 类型 if file.password
label= file.mimetype label(for='password_input') 密码
label 大小 input(id='password_input' name='password' type='password' autocomplete='off' required)
label= filesize(file.size) button(id='download_button' type='submit') 下载
label 上传 if !file.password
label= moment(file.createdAt).fromNow() - const mimetype = file.mimetype.split('/')[0]
label 热度 - const src = '/file/' + file.md5
label= file.downloads if mimetype == 'image'
| &nbsp; 次下载 p 预览
if file.password img(src=src)
label(for='password_input') 密码 if mimetype == 'audio'
input(id='password_input' name='password' type='password' autocomplete='off' required) p 预览
button(id='download_button' type='submit') 下载 audio(controls src=src)
if !file.password if mimetype == 'video'
- const mimetype = file.mimetype.split('/')[0] p 预览
- const src = '/share/file/' + file.md5 video(controls src=src)
if mimetype == 'image'
p 预览
img(src=src)
if mimetype == 'audio'
p 预览
audio(controls src=src)
if mimetype == 'video'
p 预览
video(controls src=src)

View File

@ -1,23 +1,14 @@
doctype html extends layout
html(lang='zh') block main
head p
meta(charset='UTF-8') a(href='upload') 我要上传
meta(http-equiv='X-UA-Compatible' content='IE=edge') ul
meta(name='viewport' content='width=device-width, initial-scale=1.0') if files.length==0
link(rel='icon' href='/share/favicon.ico' type='image/x-icon') li 暂无文件
link(rel='stylesheet' href='/share/style.css') else
title 文件分享 for file in files
body li
h1 文件分享 a(href=`download/${file.id}`)= file.filename
p if file.password
a(href='upload') 我要上传 span &nbsp;㊙️
ul span.small &nbsp;(#{moment(file.createdAt).fromNow()}, #{file.downloads} 次下载)
if filesharing.length==0
li 暂无文件
else
for file in filesharing
li
a(href=`download/${file.id}`)= file.filename
if file.password
span &nbsp;㊙️
span.small &nbsp;(#{moment(file.createdAt).fromNow()}, #{file.downloads} 次下载)

13
views/layout.pug Normal file
View File

@ -0,0 +1,13 @@
doctype html
html(lang='zh')
head
meta(charset='UTF-8')
meta(http-equiv='X-UA-Compatible' content='IE=edge')
meta(name='viewport' content='width=device-width, initial-scale=1.0')
link(rel='icon' href='/favicon.ico' type='image/x-icon')
link(rel='stylesheet' href='/style.css')
title #{title}
body
.container
h1 #{title}
block main

View File

@ -1,28 +1,19 @@
doctype html extends layout
html(lang='zh') block main
head p
meta(charset='UTF-8') a(href='/') 返回首页
meta(http-equiv='X-UA-Compatible' content='IE=edge') if file
meta(name='viewport' content='width=device-width, initial-scale=1.0') section
link(rel='icon' href='/share/favicon.ico' type='image/x-icon') p
link(rel='stylesheet' href='/share/style.css') | 文件 &nbsp;
title 文件上传 var #{file.filename} &nbsp;
body | 分享成功!
h1 文件上传 p 链接 &nbsp;
p - const url = 'download/' + file.id
a(href='/share') 返回首页 a(href=url)= url
if file form(method='POST' action='upload' enctype='multipart/form-data')
section label(for='file_input') 文件
p input(id='file_input' name='file' type='file' required)
| 文件 &nbsp; label(for='password_input') 密码
var #{file.filename} &nbsp; input(id='password_input' name='password' type='password' autocomplete='off')
| 分享成功! button(id='share_button' type='submit') 分享
p 链接 &nbsp;
- const url = 'download/' + file.id
a(href=url)= url
form(method='POST' action='upload' enctype='multipart/form-data')
label(for='file_input') 文件
input(id='file_input' name='file' type='file' required)
label(for='password_input') 密码
input(id='password_input' name='password' type='password' autocomplete='off')
button(id='share_button' type='submit') 分享