Compare commits

...

31 Commits

Author SHA1 Message Date
1a23273708 update 2022-09-07 00:01:56 +08:00
b1b2b29689 gitpush 2022-09-04 19:49:22 +08:00
35b84697cd 添加video预览 2022-08-27 11:42:33 +08:00
dc449886bf 添加filesize 2022-08-27 10:33:20 +08:00
e88d21e354 添加cronjob,每分钟检查并删除超过24h的分享 2022-08-27 01:47:08 +08:00
659aa9d111 实现文件预览功能 2022-08-27 00:46:04 +08:00
ab862d08fd 小修改 2022-08-26 23:51:32 +08:00
0ae0e1285b 新增 2022-08-26 23:47:03 +08:00
c1a5437532 基本实现了首页文件列表,完善了上传和下载功能 2022-08-26 23:45:07 +08:00
dce08a43a4 重命名 2022-08-26 23:32:31 +08:00
15321afae2 安装moment 2022-08-26 23:31:57 +08:00
87261e3d5c 重命名 2022-08-26 20:50:10 +08:00
acebcd95f4 重命名 2022-08-26 20:46:06 +08:00
503fae7cc7 实现了文件下载时界面及密码的输入 2022-08-26 20:29:00 +08:00
4006decb81 新建download.pug 2022-08-26 20:28:03 +08:00
9854bfeed6 清空 2022-08-26 20:27:22 +08:00
e757feed04 小修改 2022-08-26 20:26:21 +08:00
a1bff06a26 重命名 2022-08-26 20:26:06 +08:00
9f12482b30 实现了文件下载路由 2022-08-26 17:57:12 +08:00
a81b7c0a37 修改创建时间字段名 2022-08-26 17:55:24 +08:00
c34c1a2d38 创建fileshare模型,将上传文件的数据写入数据库 2022-08-26 16:33:49 +08:00
d26791a77f 重写POST upload/路由基本实现单文件上传并重命名的功能 2022-08-26 15:25:30 +08:00
67cd03f053 添加上传相关常数 2022-08-26 15:24:25 +08:00
c818880fb5 添加上传结果信息section 2022-08-26 15:23:34 +08:00
a503d3c9f8 安装依赖bcrypt,md5-file,multer 2022-08-26 15:22:49 +08:00
cc12a317d1 添加upload/ 2022-08-26 15:22:10 +08:00
4857119778 新建样式表、客户端脚本、上传表单及upload路由 2022-08-26 13:50:57 +08:00
24158f15a9 修改拼写错误 2022-08-26 12:34:04 +08:00
c81f6df88d 添加静态文件夹 2022-08-26 12:32:33 +08:00
d9c7a93aca 添加dotenv和mongoose,并连接数据库 2022-08-26 12:27:18 +08:00
6e3251ce25 添加基本meta元数据 2022-08-25 17:56:34 +08:00
13 changed files with 1145 additions and 18 deletions

4
.env.example Normal file
View File

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

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
upload/
# ---> Node # ---> Node
# Logs # Logs
logs logs

View File

@ -1 +1,3 @@
# 文件分享服务器 # 文件分享
一个文件分享服务应用

12
models/Filesharing.js Normal file
View File

@ -0,0 +1,12 @@
const mongoose = require('mongoose')
const Filesharing = new mongoose.Schema({
md5: { type: String, require: true },
size: { type: Number, require: true },
encoding: { type: String, require: true },
mimetype: { type: String, require: true },
filename: { type: String, require: true },
password: { type: String, require: true },
downloads: { type: Number, default: 0 },
createdAt: { type: Date, default: Date.now },
})
module.exports = mongoose.model('Filesharing', Filesharing)

870
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "fileshare", "name": "filesharing",
"version": "0.1.0", "version": "0.1.0",
"description": "文件分享服务器", "description": "文件分享",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"start": "nodemon", "start": "nodemon",
@ -9,13 +9,21 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "ssh://git@nas:2222/zhaoxin/fileshare.git" "url": "ssh://git@nas:2222/zhaoxin/filesharing.git"
}, },
"keywords": [], "keywords": [],
"author": "Zhao Xin <7176466@qq.com>", "author": "Zhao Xin <7176466@qq.com>",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bcryptjs": "^2.4.3",
"cron": "^2.1.0",
"dotenv": "^16.0.1",
"express": "^4.18.1", "express": "^4.18.1",
"filesize": "^9.0.11",
"md5-file": "^5.0.0",
"moment": "^2.29.4",
"mongoose": "^6.5.3",
"multer": "^1.4.5-lts.1",
"pug": "^3.0.2" "pug": "^3.0.2"
}, },
"devDependencies": { "devDependencies": {

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

48
public/style.css Normal file
View File

@ -0,0 +1,48 @@
* {
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
body {
user-select: none;
max-width: 480px;
margin: 0 auto;
}
p,
h1,
li,
form {
margin: 1rem auto;
}
form {
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
}
form button {
grid-column: span 2;
cursor: pointer;
}
.small {
color: gray;
font-size: xx-small;
}
audio,
video {
width: 480px;
}
img {
width: 480px;
object-fit: cover;
}
::marker {
content: '💾 ';
}

125
server.js
View File

@ -1,11 +1,124 @@
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 path = require('path')
const md5file = require('md5-file')
const filesize = require('filesize')
const { hash, compare } = require('bcryptjs')
const mongoose = require('mongoose')
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 CronJob = require('cron').CronJob
const job = new CronJob(
'0 * * * * *',
async () => {
const files = await Filesharing.find({ createdAt: { $lt: new Date(Date.now() - 24 * 3600 * 1000) } })
for (let file of files) {
const id = file.id
await Filesharing.findByIdAndDelete(id)
console.log(`删除共享 ${id}`)
const ref_number = await Filesharing.countDocuments({ md5: file.md5 })
if (ref_number == 0) {
const file_path = path.join(__dirname, UPLOAD_SAVE_PATH, file.md5)
fs.unlinkSync(file_path)
console.log(`删除文件 ${file_path}`)
}
}
},
null,
true
)
const express = require('express') const express = require('express')
const app = express() const app = express()
app.use(express.static('public'))
app.set('view engine', 'pug') app.set('view engine', 'pug')
app.get('/', (req, res) => { app.use(express.urlencoded({ extended: false }))
res.render('index', { title: 'hello, world!' }) app.use(express.json())
app.locals.moment = require('moment')
app.locals.moment.locale('zh-cn')
app.locals.filesize = filesize.partial({ base: 2, standard: 'jedec' })
app.get('/', async (req, res) => {
const files = await Filesharing.find().sort({ createdAt: -1 })
res.render('index', { title: '文件分享', files })
}) })
const host = 'localhost' app.get('/upload', async (req, res) => {
const port = 3000 res.render('upload', { title: '文件上传' })
const server = app.listen(port, host, () => { })
console.log(`server is running at http://${host}:${port}`)
const multer = require('multer')
const upload = multer({ dest: UPLOAD_TEMP_PATH, limits: { fileSize: UPLOAD_SIZE_LIMIT } })
app.post('/upload', upload.single('file'), async (req, res) => {
const file_temp_path = path.join(UPLOAD_TEMP_PATH, req.file.filename)
const md5 = await md5file(file_temp_path)
const file_save_path = path.join(__dirname, UPLOAD_SAVE_PATH, md5)
if (!fs.existsSync(file_save_path)) {
fs.cpSync(file_temp_path, file_save_path) // 复制临时文件到UPLOAD_PATH
}
fs.unlinkSync(file_temp_path) // 删除临时文件
// 写入数据库
const file = await Filesharing.create({
md5,
size: req.file.size,
encoding: req.file.encoding,
mimetype: req.file.mimetype,
// 解决了multer将中文文件名错误编码的问题
filename: Buffer.from(req.file.originalname, 'latin1').toString('utf8'),
password: req.body.password ? await hash(req.body.password, 16) : '',
})
console.log(`上传文件 ${file.filename}`)
res.status(201).render('upload', { title: '文件上传', file })
})
app.get('/download/:id', async (req, res) => {
try {
if (!req.params.id.match(/^[0-9a-fA-F]{24}$/)) throw '格式错误的ObjectId'
const file = await Filesharing.findById(req.params.id)
if (!file) throw '试图下载不存在的文件'
res.render('download', { title: '文件下载', file })
} catch (error) {
console.error(error)
res.sendStatus(404)
}
})
app.post('/download/:id', async (req, res) => {
try {
if (!req.params.id.match(/^[0-9a-fA-F]{24}$/)) throw '格式错误的ObjectId'
const file = await Filesharing.findById(req.params.id)
if (!file) throw '试图下载不存在的文件'
if (file.password == '' || (await compare(req.body.password, file.password)) == true) {
await Filesharing.findByIdAndUpdate(req.params.id, { $inc: { downloads: 1 } })
console.log(`下载文件 ${file.filename}`)
res.status(200).download(path.join(UPLOAD_SAVE_PATH, file.md5), file.filename)
} else {
res.sendStatus(401)
}
} catch (error) {
console.error(error)
res.sendStatus(404)
}
})
app.get('/file/:md5', async (req, res) => {
try {
const file = await Filesharing.findOne({ md5: req.params.md5 })
if (!file) throw '试图下载不存在的文件'
if (file.password != '') throw '访问的文件需要密码'
res.sendFile(path.join(__dirname, UPLOAD_SAVE_PATH, req.params.md5))
} catch (error) {
console.error(error)
res.sendStatus(404)
}
})
app.listen(PORT, HOST, () => {
console.info(`server is running at http://${HOST}:${PORT}`)
}) })

32
views/download.pug Normal file
View File

@ -0,0 +1,32 @@
extends layout
block main
p
a(href='/') 返回首页
form(method='POST')
label 文件
label= file.filename
label 类型
label= file.mimetype
label 大小
label= filesize(file.size)
label 上传
label= moment(file.createdAt).fromNow()
label 热度
label= file.downloads
| &nbsp; 次下载
if file.password
label(for='password_input') 密码
input(id='password_input' name='password' type='password' autocomplete='off' required)
button(id='download_button' type='submit') 下载
if !file.password
- const mimetype = file.mimetype.split('/')[0]
- const src = '/file/' + file.md5
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,6 +1,14 @@
doctype html extends layout
html(lang='zh') block main
head p
title #{title} a(href='upload') 我要上传
body ul
h1 #{title} if files.length==0
li 暂无文件
else
for file in files
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

19
views/upload.pug Normal file
View File

@ -0,0 +1,19 @@
extends layout
block main
p
a(href='/') 返回首页
if file
section
p
| 文件 &nbsp;
var #{file.filename} &nbsp;
| 分享成功!
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') 分享