XingYun blog
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具

XingYun

冲!
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具
  • leaflet改变坐标原点
  • 补间动画gsap与tween
  • 文字转语音mp3文件
  • JavaScript引入
  • JavaScript高级程序设计阅读笔记
  • Javascript函数参数按值传递传递
  • JS防抖和节流
  • 手写JS常用方法
  • 手写Promise
  • JS Event Loop
  • 重排和重绘
  • em、px、rem、vh、vw 区别
  • css也需要性能优化
  • 图解 js 原型链
  • js函数参数按值传递
  • BOM浏览器对象模型
  • DOM
  • 事件
  • js对象数组sort按需排序
  • 文件上传功能技术选型和前后端实现
    • 文件上传功能技术选型和前后端实现
      • 一、起因
      • 二、功能点
      • 三、实现方案
  • 前端图片处理
  • 让你的JavaScript代码简单又高效
  • BEM:class命名规范
  • 前端规范-CSS属性那么多(杂),怎么排序
  • TypeScript Tips
  • jsx
  • canvas基础
  • 前端日志
  • 浏览器存储
  • CSS世界阅读笔记
  • CSS揭秘阅读笔记
  • js变量命名常用规范词
  • 你不知道的JavaScript阅读笔记
  • js对象的底层数据结构
  • js判断数据类型
  • JS浮点数精度问题和解决办法
  • js作用域
  • js堆栈溢出和内存泄漏
  • 浏览器是如何渲染页面的
  • 疑难杂症和踩坑问题合集
  • 免费在线API收集
  • 原生JS实现Ajax请求
  • cookie、session、localStorage、sessionStorage的区别
  • Sass与Less
  • arrayBuffer、blob、file对象
  • TypeScript基础
  • 前端
XingYun
2021-12-09
目录

文件上传功能技术选型和前后端实现

# 文件上传功能技术选型和前后端实现

# 一、起因

上下文:利用vercel托管了一个前端页面what to eat (opens new window) 一个摇菜单的页面

xpy产品加了个需求:给菜单加上图片预览功能

# 二、功能点

  1. 前端实现图片上传
  2. node后端接受图片,存档服务器
  3. 前端回显需要考虑网络带宽问题,需要对上传的图片做压缩和裁切处理

# 三、实现方案

# 1. 直接存服务器上

图片应该不会很多,所以最开始尝试直接将图片存在vercel的node服务端

const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
// 引入multer
const multer = require('multer');

const storage = multer.diskStorage({
// 上传图片的存放位置在uploads
  destination: function(req, file, cb) {
    cb(null, './static/');
  },
  // windows下使用-代替:
  filename: function(req, file, cb) {
    cb(null, new Date().toISOString().replace(/:/g, '-') + file.originalname)
  }
}) 

const fileFilter = (req, file, cb) => {
  //判断上传图片类型
  cb(null, true);
  // if (file.mimetype === 'image/jpg' || file.mimetype === 'image/png') {
  //   cb(null, true);
  // } else {
  //   cb(null, false);
  // }
}
// limit上传图片尺寸限制
const uploads = multer({
  storage: storage, 
  limit: {
    fileSize: 1024 * 1024 * 5
  },
  fileFilter: fileFilter,
  // dest: './public/' 
});

// 接受一个文件。这个文件的信息保存在 req.file。最终存放在uploads
router.post('/', uploads.single('image'), (req, res, next) => {
  console.log(11111)
  console.log(req.file)
  const file = req.file
  if(file.size > 0) {
    res.status(200).json({
      code:'000000',
      data:file.filename
    })
  }
  const product = new Product({
    _id: new mongoose.Types.ObjectId(),
    name: req.body.name,
    price: req.body.price,
    productImage: req.file.path
  });

  product
    .save()
    .then(result => {
      res.status(201).json({
        message: "created product successfully",
        createdProduct: {
          name: result.name,
          price: result.price,
          _id: result._id,
          productImage: result.productImage,
          request: {
            type: 'GET',
            url: 'http://localhost:3000/products/' + result._id
          }
        }
      })
  })
})

module.exports = router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

利用multer做简单实现后发现一个问题,vercel对文件访问做了限制

// 开放静态目录
app.use(express.static('static'))
1
2

vercel部署的网站,用express开放的静态目录无法访问,只能放弃。

但是如果你是自己的服务器,可以考虑这个方案。

# 2. seafile私人云盘

本来觉得这个小网站就不需要整麻烦了,所以想偷懒把图片丢服务器文件夹完事,看来静态资源存储这个活逃不掉了。

然后,想到了我自己搭建的seafile私人云盘,可以作为图片服务器,虽然更麻烦,但是

这样图片静态资源存储和网站服务器解耦,更有利于提高网站扩展性和兼容性

实现步骤大概分为三步:

  1. 图片上传到seafile私人云盘,上传成功后返回图片URL
  2. mongoDB数据库存储URL相对路径即可
  3. 前端展示时:取出URL相对路径+云盘地址即可

有关私人云盘搭建可以访问搭建个人云盘seafile (opens new window)

有关seafile的坑 我踩了很多,甚至看了seafile jdk后解决了一个issue

这里直接说结论:不推荐将seafile作为云存储,原因有:官方js库很久都无人维护,官方文档接口调用描述与实际不符合等等问题。

但是还是推荐seafile作为私人云盘,虽然API做的很差,但是云盘很稳定, pc端移动端和web页面做的也很好。

# 3. 终极方案:七牛云

最终做了很多功课后,我选择了云服务商七牛云

  1. 全免费性能不缩水的10GB对象存储,完全够用了
  2. Github JDK star 1.3k 最近持续更新 issues也都有官方解决
  3. 官方文档一看就懂了,clone下来开箱即用

账号注册和安装引入过程不赘述了,给两个官方链接

七牛官网 (opens new window)

七牛 github js-sdk (opens new window)

服务端代码

/**
 * 获取七牛token
 */
const path = require('path')
const express = require('express')
const router = express.Router()

const qiniu = require('qiniu')
const AK = 'xxx' //在个人中心内
const SK = 'xxx'
const bucket = 'forespe' //空间名称
//鉴权对象mac
const mac = new qiniu.auth.digest.Mac(AK, SK)
//获取上传的token
const options = {
  scope: bucket,
  expires: 3600 * 24 //到期时间
}
const putPolicy = new qiniu.rs.PutPolicy(options)
const uploadToken = putPolicy.uploadToken(mac)

router.get('/qiniu-info', (req, res) => {
  res.status(200).json({
    token: uploadToken
  })
})

module.exports = router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

前端代码:

// 上传方法
import * as qiniu from 'qiniu-js'
import moment from 'moment'
import { client } from './index'

export const getQiniuToken = () => client.get('/system/qiniu')

export const uploadFile = function (file, token) {
  const fileName = moment().format('YYYYMMDDTHHmmss-') + file.name
  const putExtra: any = {
    fname: file.name, // 文件原文件名
    params: {}, // 用来放置自定义变量
    mimeType: null // 用来限制上传文件类型,为 null 时表示不对文件类型限制;eg: ["image/png", "image/jpeg"]
  }
  const config = {
    useCdnDomain: true, // cdn加速
    region: qiniu.region.z0 // 区域
  }
  const observable = qiniu.upload(file, fileName, token, putExtra, config)
  // 上传开始
  return observable
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

使用

  _uploadFile(file) {
    this.uploadImgLoading = true
    const that = this
    uploadFile(file, this.qiniuToken).subscribe({
      next(res) {
        // 进度
        console.log('next', res)
      },
      error(err) {
        console.log('err', err)
      },
      complete(res) {
        // 来到这里就是上传成功了。。
        that.$message.success('上传成功')
        that.uploadImgLoading = false
        that.uploadImageUrl = res.key
        console.log('complete', res)
      }
    })
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

回掉函数 next() 里有上传进度信息,上传大文件时很人性化

mongoDB数据库直接存储的文件名

前端回显为: 图片CDN url + 图片文件名

以后如果要迁移项目或者换地方存储,只需要转移图片,不需要修改数据库的值。

完

需求是研发的第一推动力,感谢xpy

上次更新: 2023/04/05, 09:41:10
js对象数组sort按需排序
前端图片处理

← js对象数组sort按需排序 前端图片处理→

最近更新
01
JavaScript-test
07-20
02
二维码的原理
07-20
03
利用ChatGPT优化代码
07-20
更多文章>
Theme by Vdoing | Copyright © 2021-2023 XingYun | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式