Bootstrap

笔记---华为OBS存储使用Nodejs版本

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

最近在一个新的项目上使用了nodejs,使用的框架是Fastify,其中需要上传附件到华为的OBS中,按照官方文档开发也是遇到了一些问题。


一、 获取AK和SK

购买后从官网获取

具体过程可以参考官方文档(https://support.huaweicloud.com/sdk-nodejs-devg-obs/obs_29_0102.html

二、过程

1 引入和注册插件

1.1 安装插件

npm install esdk-obs-nodejs

1.2 新建obs.js文件

// 引入obs库
// 使用npm安装
var ObsClient = require('esdk-obs-nodejs');
// 如果需要代理可以安装使用
// var ProxyAgent = require('proxy-agent');
// 使用源码安装(乳鸽)
// var ObsClient = require('./lib/obs');
const config = require('../config/config');

// 创建ObsClient实例
const obsClient = new ObsClient({
        //推荐通过环境变量获取AKSK,这里也可以使用其他外部引入方式传入,如果使用硬编码可能会存在泄露风险。
        //您可以登录访问管理控制台获取访问密钥AK/SK,获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
        access_key_id: `${config.huaweiObs['AccessKeyId']}`,
        secret_access_key: `${config.huaweiObs['SecretAccessKey']}`,
        //这里以华北-北京四为例,其他地区请按实际情况填写
        // 写你自己的地址
        server: 'https://obs.cn-north-4.myhuaweicloud.com',
        max_retry_count : 1,
        timeout : 200,
        ssl_verify : false,
        long_conn_param : 0,
        // http_agent : new ProxyAgent('*** Provide your Network proxy ***') // 配置网络代理
});
module.exports = obsClient;

注:这里的server地址不需要加上桶名,和调用华为OBS的API的方式不同,调API需要在使用的地址上拼接上桶名,但是nodejs使用的sdk的方式,桶名作为参数进行使用。

其中的配置config,我写在了config.js中

'use strict'

const dotenv = require('dotenv');
const path = require('path');
const envFile = process.env.NODE_ENV ? `.env.${process.env.NODE_ENV}` : '.env';
dotenv.config({ path: path.resolve(process.cwd(), envFile) });

const config = {
	huaweiObs: { 
	    BucketName: "your BucketName", 
	    AccessKeyId: "your AccessKeyId", 
	    SecretAccessKey: "your SecretAccessKey",
    },
}
module.exports = config;

1.3 注册插件

由于的目录结构如图所示
Fastify项目文件目录结构
因此在注册插件时使用的的方式时
在app.js文件中

'use strict'

const path = require('node:path')
const AutoLoad = require('@fastify/autoload')
const yourPlugin = require('/your_path/yourPlugin');
module.exports = async function (fastify, opts) {
  fastify.register(AutoLoad, {
    dir: path.join(__dirname, 'plugins'),
    options: Object.assign({}, opts)
  })
  fastify.register(AutoLoad, {
    dir: path.join(__dirname, 'routes'),
    options: Object.assign({}, opts)
  })
  // fastify.decorate('currentUser', null);
  
  //也可以采用直接引入注册的方式
  fastify.register(yourPlugin)
}

2 文件操作

在huaweiObsLib.js中统一处理obs的上传文件操作。

2.1 文件上传

//huaweiObsLib.js
'use strict'
const obsClient = require('../plugins/obs')
// 如果Obs桶中是按照目录来分类的话,需要设置文件夹目录
const obs_path ="folder_catalogue/"
const BucketName = 'BucketName ' // 可以取配置中的桶名
async function uploadFile(file) {
    try {
        const ext = file.filename.split(".")[1].toString() || ''
        const new_filename = `${file.uuid}.${ext}`
        const result = await obsClient.putObject({
            Bucket : BucketName,
            Key : obs_path + new_filename,
            // SourceFile : file.data,  // localfile为待上传的本地文件路径,需要指定到具体的文件名
            // Metadata:  { meta1: 'value1', meta2: 'value2'},
            Body: file.data,
            ContentType: file.file_type
        })
        if (result.CommonMsg.Status < 300) {
            return { status: true, statusCode: result.CommonMsg.Status, data: result.InterfaceResult };
        } else {
            return { status: false, statusCode: result.CommonMsg.Status, data: result.CommonMsg.Message };
        }
    } catch (err) {
        return { status: false, statusCode: 500, data: err }
    }
}

在请求接口中的调用,可以直接写在路由文件中,也可以写在控制器中,这基于不同的路由书写方式。

const Attachment = require('../models/Attachment')
const huaweiObsLib= require('../lib/huaweiObsLib')

module.exports = async function (fastify, opts) {
  fastify.get('/upload_file', async function (request, reply) {
    try {
        const data = await request.file();
        const uuid = crypto.randomBytes(16).toString('hex')
        let fileSize = data.file.bytesRead
        const attachment = new Attachment({filename: data.filename, metadata: data.mimetype, length: fileSize, uuid:uuid})
        const res = await huaweiObsLib.uploadFile({data: data.file, uuid: uuid, file_type: data.mimetype, filename: data.filename})
        if (res.statusCode < 300) {
        	await attachment.save()
            return reply.send({ status: true, statusCode: 200, message: 'File uploaded successfully', data: info });
        } else {
            return reply.status(res.statusCode).send({ status: false, statusCode: res.statusCode, message: res.message });
        }
    } catch (error) {
        throw boom.boomify(error);
    }
  })
}

2.2 文件下载

//huaweiObsLib.js
'use strict'
const obsClient = require('../plugins/obs')
// 如果Obs桶中是按照目录来分类的话,需要设置文件夹目录
const obs_path ="folder_catalogue/"
const BucketName = 'BucketName ' // 可以取配置中的桶名

async function downloadFile(file) {
    try {
        const ext = file.filename.split(".")[1].toString() || ''
        const new_filename = `${file.uuid}.${ext}`
        const result = await obsClient.getObject({ Bucket: BucketName, Key: obs_path + new_filename,
            // 是否将对象以流(stream.Readable)的形式返回。
            SaveAsStream: true,
            // SaveByType: 'file'
        })
        if (result.CommonMsg.Status < 300) {
            return { status: true, statusCode: result.CommonMsg.Status, data: result.InterfaceResult };
        } else {
            return { status: false, statusCode: result.CommonMsg.Status, data: result.CommonMsg.Message };
        }
    } catch (err) {
        return { status: false, statusCode: 500, data: err }
    }
}

在请求接口中的调用

const Attachment = require('../models/Attachment')
const huaweiObsLib= require('../lib/huaweiObsLib')

module.exports = async function (fastify, opts) {
  fastify.get('/download_file', async function (request, reply) {
    try {
        onst id = request.query.id
        const attachment = await Attachment.findById(id)
        if (!attachment) {
            return reply.status(404).send({ statusCode: 404, message: 'File not found' });
        }
        const res = await huaweiObsLib.downloadFile({uuid: attachment.uuid, filename: attachment.filename})
        if (res.statusCode < 300) {
            reply.header('Content-Type', res.data.ContentType);
            // const base64Data = Buffer.from(res.data.Content).toString('base64');
            // const dataUrl = `data:${res.data.ContentType};base64,${base64Data}`;
            return reply.send(res.data.Content);
        } else {
            return reply.status(res.statusCode).send({ status: false, statusCode: res.statusCode, message: res.message });
        }
    } catch (error) {
        throw boom.boomify(error);
    }
  })
}

2.3 文件删除

//huaweiObsLib.js
'use strict'
const obsClient = require('../plugins/obs')
// 如果Obs桶中是按照目录来分类的话,需要设置文件夹目录
const obs_path ="folder_catalogue/"
const BucketName = 'BucketName ' // 可以取配置中的桶名

async function deleteFile(file) {
    try {
        const ext = file.filename.split(".")[1] || ''
        const new_filename = `${file.uuid}.${ext}`
        const result = await obsClient.deleteObject({
            Bucket: BucketName,
            Key: obs_path + new_filename
        })
        if (result.CommonMsg.Status < 300) {
            return { status: true, statusCode: result.CommonMsg.Status, data: result.InterfaceResult };
        } else {
            return { status: false, statusCode: result.CommonMsg.Status, data: result.CommonMsg.Message };
        }
    } catch (err) {
        return { status: false, statusCode: 500, data: err }
    }
}

在请求接口中的调用

const Attachment = require('../models/Attachment')
const huaweiObsLib= require('../lib/huaweiObsLib')

module.exports = async function (fastify, opts) {
  fastify.get('/delete_file', async function (request, reply) {
    try {
        const id = request.query.id
        const attachment = await Attachment.findById(id)
        if (!attachment) {
            return reply.status(404).send({ statusCode: 404, message: 'File not found' });
        }
        const res = await huaweiObsLib.deleteFile({uuid: uuid, filename: filename})
        if (res.statusCode < 300) {
        	await this.findByIdAndDelete(id)
            return reply.status(res.statusCode).send(res);
        } else {
            return reply.status(res.statusCode).send({ status: false, statusCode: res.statusCode, message: res.message });
        }
    } catch (error) {
        throw boom.boomify(error);
    }
  })
}

三、路由

Fastify项目的路由注册方式的记录
在app.js中注册路由routes

'use strict'

const path = require('node:path')
const AutoLoad = require('@fastify/autoload')
const yourPlugin = require('/your_path/yourPlugin');
module.exports = async function (fastify, opts) {
  fastify.register(AutoLoad, {
    dir: path.join(__dirname, 'routes'),
    options: Object.assign({}, opts)
  })
}

1

'use strict'

module.exports = async function (fastify, opts) {
  fastify.get('/users', async function (request, reply) {
    return { status: true, statusCode: 200, message: 'Hello, World!' }
  })
}

2

'use strict'
const boom = require('boom')
const usersController = require('../controllers/usersController')

const routes = [
	{
        method: 'get',
        url: '/login',
        handler: usersController.login
    },
    {
        method: 'get',
        url: '/users/index',
        handler: usersController.getUser
    },

]
  
module.exports = async function (fastify, opts) {
    routes.forEach((route, index) => {
        fastify.route(route)
    })
}
;