自从搭建起来博客之后,虽然博客用 markdown 格式来写就可以了,但是图片插入却是一个大问题,写了几篇 blog 里面的图片都是直接引用别人的,前几天还好好的,这几天图片链接却 403 了,估计是做了防盗链之类的。
自己手动 copy 的话在各个平台之间也挺麻烦,比如有道云笔记和博客之间都需要手动修改,国内的云存储厂商用着也不太放心。
本来这周还计划做点别的事情,看来只能先把图床搭建起来。
服务器是 serverless 的,所以通过 serverless Framework 和云函数来进行 cos 对象存储,上传到存储桶中,这里用到的技术是基于 node 的 koa2+vue,通过 koa 将图片对象上传到 cos 桶中
先来看一下做成的成品:serverless 图床

支持两种方式上传图片,通过本地上传和网络链接,右侧做了一个简单的列表展示和删除功能,别的一些功能比如图片压缩之类的还没做,之后可以慢慢优化。
这里假设域名,备案资源包,serverless 本地安装等等都已经好了
创建 koa2 应用
本地初始化项目
1 2 3 4 5 6 7 8 9 10 11
| ## 全局安装koa-generator npm install -g koa-generator ## 创建项目 koa2 项目名称 默认为jade模板,使用ejs的话 koa2 -e 项目名称 ## 安装依赖 npm install ## 启动 npm start 打开localhost:3000
|
通过以上命令初始化好项目,访问 localhost:3000 就可以看到默认的首页了,接下来需要频繁修改代码,每次修改都需要重启,所以通过 nodemon 来解决
之后修改 package.json 中的 scripts,把 start 中原来 node 启动的改为 nodemon,在后再修改代码就可以自动更新了
1 2 3 4 5 6
| "scripts": { "start": "nodemon bin/www", "dev": "./node_modules/.bin/nodemon bin/www", "prd": "pm2 start bin/www", "test": "echo \"Error: no test specified\" && exit 1" },
|
在根目录下新建 config 文件夹,创建 index.js 文件,来存放我们的一些配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const config = { // SecretId和SecretKey生产环境请从环境变量中获取 // SecretId: process.env.SecretId, // SecretKey: process.env.SecretKey, SecretId: 'xxxxx', SecretKey: 'xxxx' // 存储桶 Bucket: 'xxxx', // 地区 Region: 'xxxx' } module.exports = config
|
这里的配置是你自己腾讯云的 SecretId,SecretKey,以及创建好的存储桶,登录对象存储控制台创建存储桶后,获取存储桶名称和地域名称,这里在部署完成之后会使用环境变量来代替明文 SecretId 和 SecretKey,本地开发可以用明文
之后改造一下 router 下的路由文件和 views,views 创建一个 html 用来做前端展示,同时在根目录创建一个 controllers 文件夹,创建一个 upload.js 用来做控制器,这里通过 koa-sendfile 来渲染前端的界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| router-index.js const Router = require('koa-router'); const router = new Router(); const sendFile = require('koa-sendfile') const path = require('path') const { upload,list,deleteCos} = require('../controllers/upload')
//返回页面上传静态资源页面 router.get('/', async (ctx, next) => { await sendFile(ctx, path.join(__dirname, '../views/index.html')) })
router.get("/list",list); router.get('/delete',deleteCos) router.post("/upload",upload); module.exports = router;
|
之后在 controllers 下的 upload.js 新建一个 Upload 类,写好控制器方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const {SecretId,SecretKey,Bucket,Region} = require('../config'); class Upload { // 获取图片列表 async list(ctx) {
} // 上传接口 async upload(ctx) {
} // 删除单个图片 async deleteCos(ctx) {} } module.exports = new Upload()
|
总体思路为通过前端客户端可直接使用 multipart/form-data 方式上传文件给 koa,koa 再把获取到的二进制文件转为 Buffer 丢到 cos 对象存储中

为了能顺利接收前端的文件,还需要安装 koa-body,使用 koa-body 替换 koa-bodyparser,因为 koa-bodyparser 不支持获取文件
之后修改 app.js
1 2 3 4 5 6
| const KoaBody = require('koa-body')
app.use(KoaBody({multipart:true, formidable:{ uploadDir:path.resolve(__dirname,'./public/uploads'), // 设置上传目录 keepExtensions: true // 保留拓展名 }}))
|
cos 对象存储
做好以上工作之后,开始了解一下 cos 对象存储,官方是这样定义的:
对象存储(Cloud Object Storage,COS)是腾讯云提供的一种存储海量文件的分布式存储服务.
我们是后端对接 SCF 的 API,这里文件上传可以通过直接上传到 cos,也可以通过 api 网关经过云函数处理再存储到 cos 中,因为有可能图片会比较大,我们这里直接采用上传到 cos 中
腾讯云 api 网关文件上传文档
cos 对象存储 Nodejs SDK 文档
首先安装 SDK
1 2
| npm i cos-nodejs-sdk-v5 --save
|
这里可以采用永久秘钥和临时秘钥两种方式来实例化 cos,我们这里采用第一种永久秘钥初始化
腾讯云秘钥管理
获取到秘钥之后在 controllers-upload.js 中
1 2 3 4 5 6 7 8
| const {SecretId,SecretKey,Bucket,Region} = require('../config'); const COS = require('cos-nodejs-sdk-v5');
let cos = new COS({ SecretId: SecretId, SecretKey: SecretKey })
|
现在来实现图片上传逻辑
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
| const request = require('request'); const xxx = require(xxxx)
async upload(ctx) { // 获取上传的图片,imgSrc参数存在为网络连接 let file,postfix,tmpPath if (ctx.request.body.imgSrc) { postfix='jpg' const requestPromise = new Promise((resolve,reject) => { // 云函数写入只能写入到tmp的临时文件夹中 if (process.env.SERVERLESS == 1) { tmpPath = '/tmp/image.jpg' } else { tmpPath = '../public/uploads/image.jpg' } request(ctx.request.body.imgSrc).pipe(fs.createWriteStream(path.resolve(__dirname, tmpPath)).on('finish',() => { resolve(true) })) }) await requestPromise file = fs.readFileSync(path.resolve(__dirname, tmpPath)) } else { file = fs.readFileSync(ctx.request.files.file.path) postfix = ctx.request.files.file.type.split('/')[1] } }
// 将图片转为base64在转为Buffer流 const dataBuffer = new Buffer.from(file, 'base64'); const requestPromise = new Promise((resolve,reject) => { cos.putObject({ Bucket, // 存储桶 必须 Region, // 地区识别 必须 Key: `${randomWord(false,32)}.${postfix}`, // 文件名字(这里需要注意的是,如果名字是一样的,那么后面的会覆盖前面的,所以这个名字不可以重复)(必须) Body: dataBuffer, // 上传文件对象(可以不写)支持 Buffer|Stream|String }, (err, data) =>{ if (err) { reject(err) } else { resolve(data) } }); }) // 之后接口返回response try { const result = await requestPromise; ctx.body ={ code:0, message:'success', error:null, data:{ url:'https://'+result.Location } } } catch(err) { ctx.body ={ code:1, message:'fail', error:err, data:{ url:'' } } } // 上传完成清空上传的文件夹 本地测试 if (process.env.SERVERLESS != 1) { let tmpUploadPath = '../public/uploads' const images = fs.readdirSync(path.resolve(__dirname,tmpUploadPath)); images.forEach((v) => { let imagePath = path.resolve(__dirname,`${tmpUploadPath}/${v}`) fs.unlinkSync(imagePath); }) }
|
删除和获取的逻辑类似,就不在这里复制粘贴代码了
然后在根目录配置 serverless.yml 文件
serverless.yml 文件配置
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
| org: '1302216408' # 组织名称。留空则则使用默认值为用户 APPID app: img-bed # 应用名称。留空则默认取当前组件的实例名称为app名称。 stage: dev # 环境名称。默认值是 dev。建议使用 ${env.STAGE} 变量定义环境名称 component: koa (必选) 组件名称,在该实例中为 scf name: img-bed (必选) 组件实例名称。 inputs: #组件参数配置,根据每个组件,实现具体的资源信息配置 entryFile: app.js #以您实际入口文件名为准 src: src: ./ exclude: - .env region: ap-guangzhou runtime: Nodejs10.15 apigatewayConf: protocols: - https environment: release serviceTimeout: 60 customDomains: - domain: upload.niuhk.cn certificateId: j6OBq7dR isDefaultMapping: false pathMappingSet: - path: / environment: release protocols: - https autoCreateDns: false isAutoCiDeploy: false functionConf: eip: false timeout: 30 memorySize: 128
|
完整代码见 github
之后执行 serverless deploy 按照提示部署。
部署完成之后会增加一个 serverless Framework 应用和一个云函数,在云函数中的基础配置中配置好环境变量,同时,在云函数的触发管理中,把对应的 API 网关 Base64 编码开启,才能正常上传二进制文件

本文标题:使用serverless Framework自建图床服务
文章作者:Niuhk
发布时间:2021-01-24
最后更新:2022-02-19
原始链接:https://www.niuhk.cn/2021/01/24/使用serverless自建图床服务/
版权声明:转载请注明出处!