评论

云函数(nodejs)使用上传图片api获取MediaID

电商平台(云开发)填坑之路 ——图片上传接口:发送图片二进制数据获取MediaID

接口说明

  • 适用对象:服务商/电商平台
  • 请求URL:https://api.mch.weixin.qq.com/v3/merchant/media/upload
  • 逻辑流程:
    1. 从云储存或小程序端获取图片二进制数据
    2. 使用crypto生成文件摘要
    3. 使用crypto生成签名和授权信息(需要先有文件名和文件摘要)
    4. 自定义分割字符串boundary和HTTP头Content-Type
    5. 创建传输body的头部和尾部,并与文件拼接
    6. 发送请求

1. 获取图片二进制数据

可以从云储存获取,或小程序端传参
小程序获取的是ArrayBuffer,在后续使用中需要转型成Buffer

场景1: 从云储存获取图片buffer

  • 云函数代码
// [云储存文件id] 可以从小程序端传入
const fileRes = await cloud.downloadFile({fileID: '云储存文件id'})
const imgBuffer = fileRes.fileContent

场景2:从小程序端获取图片buffer

  • 小程序代码
const fileSystemManager = wx.getFileSystemManager()
const buffer = fileSystemManager.readFileSync('图片路径')
wx.cloud.callFunction({
    name: '云函数名称',
    data: {
        buffer,
        filename: '图片名.jpg'
    }
})
  • 云函数代码
let { buffer, filename } = event
const imgBuffer = Buffer.from(buffer)

2. 生成摘要、创建meta

const hash = crypto.createHash('sha256');
hash.update(imgBuffer);
let sha256 = hash.digest('hex')

let meta = { filename, sha256 }

3. 生成签名、创建授权信息

let getAuthorization = async function(meta) {
    
    // 获取商户私钥、证书序列号、商户号
    // (可以储存在云数据中,从云数据库获取)
    const priKey = '商户私钥'
    const serialNo = '商户证书序列号'
    const mchid= '商户号'
    
    // 生成随机序列
    let nonceStr = Math.random().toString()
    
    // 获取当前时间戳(这里需要的是秒数不是毫秒,要除以1000)
    let timestamp = Math.floor(Date.now() / 1000)
    
    // 创建待签名文本
    let signatureText = "POST\n"
        + "/v3/merchant/media/upload\n"
        + timestamp + "\n"
        + nonceStr + "\n"
        + JSON.stringify(meta)+"\n"
    
    // 生成签名
    let sign = crypto.createSign('RSA-SHA256')
    sign.update(signatureText)
    let signature = sign.sign(priKey, 'base64')
    
    // 合成授权信息
    let authorization = 'WECHATPAY2-SHA256-RSA2048'
        + ` mchid="${mchid}"`
        + `,nonce_str="${nonceStr}"`
        + `,timestamp="${timestamp}"`
        + `,serial_no="${serialNo}"`
        + `,signature="${signature}"`
    return authorization
}

4. 自定义分割字符串boundary和HTTP头Content-Type

// 自定义分割字符串(可以自行定义,和发送的内容不重复即可)
let boundary = 'miwoo-boundary-' + Math.random()

let contentType = 'multipart/form-data; boundary=' + boundary

5. 创建传输body的前半部分和尾部,并与文件拼接

// 创建body的前半部分并转换为buffer
// 注意反引号`与单引号'的区别
// 分割符要在boundary前加--
let beginBuffer = Buffer.from(	
    `--${boundary}\r\n`
    + 'Content-Disposition: form-data; name="meta";\r\n'
    + 'Content-Type: application/json\r\n'
    + '\r\n'
    + `${JSON.stringify(meta)}\r\n`
    + `--${boundary}\r\n`
    + `Content-Disposition: form-data; name="file"; filename="${filename}";\r\n`
    + 'Content-Type: image/jpg\r\n'
    + '\r\n'
)

// 创建body尾部(第一个\r\n是接在imgBuffer后面的换行)
// 结束分割符要在boundary两边加--
let endBuffer = Buffer.from(`\r\n--${boundary}--\r\n`)

// 将imgBuffer加入头部与尾部,拼接成完整body(不能直接使用+号连接)
let body = Buffer.concat([beginBuffer,imgBuffer,endBuffer])	

6. 发送请求

axios({
    method: 'POST',
    url: 'https://api.mch.weixin.qq.com/v3/merchant/media/upload',
    headers: {
        'Authorization': authorization,
        'Content-Type': contentType
    },
    data: body
})
.then(res => {
	console.log(res.data)
})

完整流程

const cloud = require('wx-server-sdk')
cloud.init()

const crypto = require('crypto')	//使用crypto生成文件摘要以及签名
const axios = require('axios')		//使用axios发送请求(npm install axios)

exports.main =async (event, context) => {
    
    // 1. 获取图片buffer
    const imgBuffer = '从云储存或小程序获取'

    // 获取文件名(请自行获取)
    const filename = '图片名.jpg'
    
    // 2. 生成文件摘要
    const hash = crypto.createHash('sha256');
    hash.update(imgBuffer);
    let sha256 = hash.digest('hex')

    let meta = { filename, sha256 }

    // 3. 获取签名(使用上面的签名函数)
    let authorization = await getAuthorization(meta)

    // 4. 自定义分割字符串和Content-Type
    let boundary = 'miwoo-boundary-' + Math.random()
    let contentType = 'multipart/form-data; boundary=' + boundary

    // 5. 创建(拼接)body
    let beginBuffer = Buffer.from(
        `--${boundary}\r\n`
        + 'Content-Disposition: form-data; name="meta";\r\n'
        + 'Content-Type: application/json\r\n'
        + '\r\n'
        + `${JSON.stringify(meta)}\r\n`
        + `--${boundary}\r\n`
        + `Content-Disposition: form-data; name="file"; filename="${filename}";\r\n`
        + 'Content-Type: image/jpg\r\n'
        + '\r\n'
    )
    let endBuffer = Buffer.from(`\r\n--${boundary}--\r\n`)
    let body = Buffer.concat([beginBuffer,imgBuffer,endBuffer])	

    // 6. 发送请求
    return await axios({
        method: 'POST',
        url: 'https://api.mch.weixin.qq.com/v3/merchant/media/upload',
        headers: {
            'Authorization': authorization,
            'Content-Type': contentType
        },
        data: body
    })
    .then(res => {
        console.log(res.data)
        return res.data.media_id
    })
          
}

欢迎留言

本文为**电商平台(云开发)**的填坑之作,欢迎提出不足之处、分享云开发的经验和坑。

最后一次编辑于  2021-06-02  
点赞 4
收藏
评论

2 个评论

  • 秋水小夕
    秋水小夕
    2022-04-13

    请问下有接口实现通过media_id进行预览图片吗?

    

    2022-04-13
    赞同
    回复
  • 胡超
    胡超
    2021-11-04

    简单易懂,写的太好了

    2021-11-04
    赞同
    回复
登录 后发表内容