【微信小程序】服务端上传文件到微信云托管的对象存储中

需求分析

微信小程序服务端基于【微信云托管】进行部署,因此图片等文件资源需要上传到微信云托管的【对象存储】服务中,以便于小程序中获取这些资源

适用场景如下:

  1. wx.uploadFile上传图片到服务器
  2. 保存用户头像信息

下面以保存用户头像信息为例来介绍使用流程

具体实现

开通微信云托管功能

微信云托管官网:微信云托管 (qq.com)

相关文档:developers.weixin.qq.com/miniprogram/dev/wxcloudrun/src/basic/intro.html

简单来说就是提供小程序服务器的部署服务,开箱即用,方便快捷;

其计费方式为按量付费模式,首个环境赠送一定的免费额度,有效期为3个月,资源信息大致如下:

我们访问微信云托管的官网,登录对应的小程序账号即可开通云托管服务,开通之后即可进行服务器部署等操作,同时提供了MySQL数据库服务以及对象存储服务,接下来进行的操作就是基于微信云托管的对象存储服务来上传图片文件

上传文件

上传文件分为两个步骤:

  1. 获取文件上传链接
  2. 上传文件

相关文档如下:

开发指引 / 对象存储 / 服务端和其他客户端 / 上传文件 (qq.com)

接下来分别进行说明

1.准备工作

首先创建WXContent类,存储这些信息:

其中APPID和APPSECRET可以从微信开放平台中登录小程序来获取;

微信云托管的环境ID可以在【设置】——【环境设置】中查看:

对象存储服务的目录层级可以从【对象存储】中设置,用于指定文件上传的位置:

在默认路径下新建目录即可;


2.获取文件上传链接

按照官方文档的指示,需要在服务端对以下地址发起POST请求:

https://api.weixin.qq.com/tcb/uploadfile?access_token=ACCESS_TOKEN

请求参数如下:

其中access_token是接口调用凭证,需要另外获取;env是云环境ID;path是上传路径,格式目录+文件名

所以接下来需要获取access_token,相关文档如下:

接口调用凭证 / 获取接口调用凭据 (qq.com)

操作步骤为服务端向以下地址发起GET请求:

https://api.weixin.qq.com/cgi-bin/token

请求参数如下:

这里的APPID和APPSECRET在【准备工作】中都已经准备好了

然后在返回参数中即可获取到access_token

接下来需要在微信云托管控制台-「微信令牌权限配置」中添加/tcb/uploadfile

然后即可发送POST请求,来获取文件上传的链接,返回值信息如下:

其中tokenauthorization、和cos_file_id等参数在下面上传文件的请求中还要用

完整代码如下:

public class FilePath {
    /**
     * 获取accessToken
     * @return
     */
    public static String getAccessToken()
    {
        //1.获取access_token
        RestTemplate restTemplate = new RestTemplate();
        String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + WXContent.APPID + "&secret=" + WXContent.APPSECRET;
        ResponseEntity<Map> responseToken = restTemplate.getForEntity(tokenUrl, Map.class);
        String accessToken = (String) responseToken.getBody().get("access_token");
        return accessToken;
    }

    /**
     * 获取文件上传链接
     * @param accessToken
     * @param fileName
     * @return
     */
    public static Map getFilePath(String accessToken,String fileName)
    {
        RestTemplate restTemplate = new RestTemplate();
        String filePathUrl = "https://api.weixin.qq.com/tcb/uploadfile?access_token=" + accessToken;
        //2.1设置请求头
        HttpHeaders filePathHeaders = new HttpHeaders();
        filePathHeaders.setContentType(MediaType.APPLICATION_JSON);
        //2.2设置请求体
        Map<String, Object> filePathRequestData = new HashMap<>();
        filePathRequestData.put("env", WXContent.env);
        filePathRequestData.put("path", WXContent.filePathPre + fileName);
        //2.3将请求体转换为JSON字符串
        ObjectMapper objectMapper = new ObjectMapper();
        String filePathRequestDataToJSON = "";
        try {
            filePathRequestDataToJSON = objectMapper.writeValueAsString(filePathRequestData);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        //2.4创建请求实体
        org.springframework.http.HttpEntity<String> filePathRequestEntity = new org.springframework.http.HttpEntity<>(filePathRequestDataToJSON, filePathHeaders);
        System.out.println(filePathRequestEntity);
        //2.5发送请求并获取响应
        ResponseEntity<Map> filePathResponseEntity = restTemplate.postForEntity(filePathUrl, filePathRequestEntity, Map.class);
        //2.6打印响应数据
        if (filePathResponseEntity.getStatusCode().is2xxSuccessful()) {
            Map responseBody = filePathResponseEntity.getBody();
            if (responseBody != null) {
                System.out.println("上传链接获取成功:" + responseBody);
                return responseBody;
            } else {
                System.out.println("响应体为空");
                return null;
            }
        } else {
            System.out.println("请求失败:" + filePathResponseEntity.getStatusCode());
            return null;
        }
    }
}

主要使用了RestTemplate来进行网络请求,所需依赖如下:

		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.7.16</version>
		</dependency>

3.上传文件

在获取到文件上传链接之后,需拼装一个POST请求来进行文件的上传,请求参数如下:

其中 url 为返回包的url字段,Body 部分格式为multipart/form-data

Signaturex-cos-security-tokenx-cos-meta-fileid参数分别对应返回包的authorizationtoken、和cos_file_id字段

完整代码如下:

public class FileUpload {
    /**
     * 文件上传
     * @param fileName
     * @param response
     * @param bytes
     * @return
     */
    public static Integer uploadFile(String fileName, Map response, byte[] bytes) {
        String fileUploadUrl = (String) response.get("url");

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost uploadFilePost = new HttpPost(fileUploadUrl);

        //创建临时文件
        File tempFile = null;
        try {
            tempFile = File.createTempFile(fileName, ".tmp");
            FileOutputStream fos = new FileOutputStream(tempFile);
            fos.write(bytes);
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

//        File file = new File("src/main/resources/images/iceberg.jpg");
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.addTextBody("key", (WXContent.filePathPre + fileName));
        builder.addTextBody("Signature", (String) response.get("authorization"));
        builder.addTextBody("x-cos-security-token", (String) response.get("token"));
        builder.addTextBody("x-cos-meta-fileid", (String) response.get("cos_file_id"));
//        builder.addPart("file", new FileBody(file));
        builder.addPart("file", new FileBody(tempFile, ContentType.DEFAULT_BINARY));

        HttpEntity multipart = builder.build();
        uploadFilePost.setEntity(multipart);

        try (CloseableHttpResponse uploadResponse = httpClient.execute(uploadFilePost)) {
            int code = uploadResponse.getStatusLine().getStatusCode();
            System.out.println("Response Code: " + code);
            return code;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 主方法
     * @param fileName
     * @param bytes
     * @return
     */
    public static String uploadMain(String fileName,byte[] bytes) {
        String accessToken = FilePath.getAccessToken();
        Map response = FilePath.getFilePath(accessToken, fileName);
        if (!response.isEmpty()) {
            int code = FileUpload.uploadFile(fileName, response,bytes);
            if (code == 204) {
                System.out.println("文件--- " + fileName + " ---上传成功");
                return "success";
            }else {
                System.out.println("文件--- " + fileName + " ---上传失败");
                return "error";
            }
        } else {
            System.out.println("文件路径获取失败");
            return "error";
        }
    }
}

这里需要注意的是,对于请求体的封装必须严格符合multipart/form-data格式,否则会出现如下报错:

The body of your POST request is not well-formed multipart/form-data.

我在使用 RestTemplate 发送 multipart/form-data 请求时,一直出现如上报错,经过查询分析问题可能是因为multipart/form-data 请求体需要包含边界信息,而RestTemplate 在设置 Content-Type 为 multipart/form-data 时会自动处理边界问题,由此引发格式问题。

因此使用HttpClient库对multipart/form-data请求进行处理,其对于multipart 请求支持更全面。

相关依赖如下:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

此外这里我使用的文件格式是字节数组的形式,如果你的文件是其他的格式,则可能不需要创建临时文件进行bytes的写入操作,比如说你需要上传本地的一个文件,则相关代码如下:

File file = new File("src/main/resources/images/iceberg.jpg");
builder.addPart("file", new FileBody(file));

总结

至此服务端上传文件到微信云托管的对象存储服务中的全部操作已经完成

在指定目录下可以查看文件的具体信息:

如果想要了解文件下载的相关操作,可以参考以下链接:

开发指引 / 对象存储 / 服务端和其他客户端 / 获取文件下载链接 (qq.com)

如有问题欢迎讨论交流~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

THE WHY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值