JAVA后端上传图片至企微临时素材

1.使用场景

在使用企业微信API接口中,往往开发者需要使用自定义的资源,比如发送本地图片消息,设置通讯录自定义头像等。
为了实现同一资源文件,一次上传可以多次使用,这里提供了素材管理接口:以media_id来标识资源文件,实现文件的上传与下载。

以发送消息为示例:

image-20240202111547787

以JSSDK选图片上传为示例:

image-20240202111618496

上传的媒体文件限制

所有文件size必须大于5个字节

  • 图片(image):10MB,支持JPG,PNG格式
  • 语音(voice) :2MB,播放长度不超过60s,仅支持AMR格式
  • 视频(video) :10MB,支持MP4格式
  • 普通文件(file):20MB

HTTP上传文件方法简析

HTTP是文本协议,若需要传递二进制文件需要依赖于multipart/form-data格式

1. 构造HTTP请求包

单个文件的multipart/form-data格式,如下:

--分隔符[换行]
Content-Disposition: form-data; name="表单名"; filename="文件名"; filelength=文件内容大小[换行]
Content-Type: 类型[换行]
[换行]
文件的二进制内容[换行]
--分隔符--

Content-Type根据不同文件类型可以设置对应不同的值,如下表格:

文件类型Content-Type
普通文件application/octet-stream
jpg图片image/jpg
png图片image/png
bmp图片image/bmp
amr音频voice/amr
mp4视频video/mp4

若我们设置:分隔符为acebdf13572468,文件名为wework.txt,文件内容为mytext,由于上传临时素材要求name固定为media,那么构造的请求内容为:

--acebdf13572468
Content-Disposition: form-data; name="media";filename="wework.txt"; filelength=6
Content-Type: application/octet-streammytext
--acebdf13572468--
2. 设置HTTP头部信息
POST URL HTTP/1.1[换行]
Content-Type: multipart/form-data; boundary=分隔符[换行]
Content-Length: 请求体内容大小[换行]
[换行]1步构造的请求体内容

假定我们将第1步组装的文件内容上传到企业微信临时素材,分隔符取第1步设定值acebdf13572468,那么就得到如下:

POST https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=accesstoken001&type=file HTTP/1.1
Content-Type: multipart/form-data; boundary=acebdf13572468
Content-Length: 168--acebdf13572468
Content-Disposition: form-data; name="media";filename="wework.txt"; filelength=6
Content-Type: application/octet-streammytext
--acebdf13572468--

上传临时素材

素材上传得到media_id,该media_id仅三天内有效
media_id在同一企业内应用之间可以共享

**请求方式:**POST(HTTPS
**请求地址:**https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE

使用multipart/form-data POST上传文件, 文件标识名为"media"
参数说明:

参数必须说明
access_token调用接口凭证
type媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file)

POST的请求包中,form-data中媒体文件标识,应包含有 filename、filelength、content-type等信息

filename标识文件展示的名称。比如,使用该media_id发消息时,展示的文件名由该字段控制

请求示例:

POST https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=accesstoken001&type=file HTTP/1.1
Content-Type: multipart/form-data; boundary=-------------------------acebdf13572468
Content-Length: 220---------------------------acebdf13572468
Content-Disposition: form-data; name="media";filename="wework.txt"; filelength=6
Content-Type: application/octet-streammytext
---------------------------acebdf13572468--

返回数据:

{"errcode": 0,"errmsg": """type": "image","media_id": "1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0","created_at": "1380000000"
}

参数说明:

参数说明
type媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file)
media_id媒体文件上传后获取的唯一标识,3天内有效
created_at媒体文件上传时间戳

上传企微临时素材,对应企微api文档链接:https://developer.work.weixin.qq.com/document/path/90253

image-20240202105135565

2.控制层接口

控制层接收方式可以有文件方式接收,也可以前端用图片转换成base64格式字符串方式后端接收。

文件方式接收

@RequestMapping(method = RequestMethod.POST, value = "v1/uploadQwMedia")public ScaResponseParam<JSONObject> uploadQwMedia(String type, String title, int orgId, @RequestParam("file")MultipartFile file) {try {if(file == null){throw new IllegalArgumentException("没有上传图片");}if(StringUtils.isEmpty(type)){throw new IllegalArgumentException("缺少type参数");}InputStream inputStream = file.getInputStream();FindMediaIdReq req = new FindMediaIdReq();req.setTitle(title);//对应企微api文档中的fileName参数,用于控制展示的文件名称req.setType(type);//对应企微api文档中的type参数String mediaId = scaGuideOneCustOneCodeService.uploadQwMedia(orgId, req, inputStream);return ScaResponseParam.OK().fluentSetData(new JSONObject().fluentPut("mediaId", mediaId));} catch (IllegalArgumentException e) {log.error(e.getMessage(), e);return ScaResponseParam.ERROR(e.getMessage());} catch (Exception e) {log.error("上传客户专属码到企微临时素材异常:{}-{}",e.getMessage(), e);return ScaResponseParam.ERROR("上传客户专属码到企微临时素材异常");}}

base64方式接收

@RequestMapping(value = "/base64ImgUpload", method = RequestMethod.POST)public ScaResponseParam<JSONObject> base64ImgUpload(@RequestBody OneCustOneCodeUploadQwImgRequest request)  {try {BASE64Decoder decoder = new BASE64Decoder();byte[] bytes = decoder.decodeBuffer(request.getBase64Str());for (int i = 0; i < bytes.length; ++i) {if (bytes[i] < 0) {bytes[i] += 256;}}int orgId = request.getOrgId();InputStream inputStream = new ByteArrayInputStream(bytes);FindMediaIdReq req = new FindMediaIdReq();req.setTitle(request.getTitle());//对应企微api文档中的fileName参数,用于控制展示的文件名称req.setType(request.getType());//对应企微api文档中的type参数String mediaId = scaGuideOneCustOneCodeService.uploadQwMedia(orgId, req, inputStream);return ScaResponseParam.OK().fluentSetData(new JSONObject().fluentPut("mediaId", mediaId));} catch (IllegalArgumentException e) {log.error(e.getMessage(), e);return ScaResponseParam.ERROR(e.getMessage());} catch (Exception e) {log.error("base64上传客户专属码到企微临时素材异常:{}-{}",e.getMessage(), e);return ScaResponseParam.ERROR("base64上传客户专属码到企微临时素材异常");}}

请求参数OneCustOneCodeUploadQwImgRequest

@Data
@SuppressWarnings("all")
public class OneCustOneCodeUploadQwImgRequest {@ApiModelProperty(value = "文件类型,图片为image", required = true)private String type;@ApiModelProperty(value = "文件名称", required = true)private String title;@ApiModelProperty(value = "图片base64格式字符串", required = true)private String base64Str;@ApiModelProperty(value = "orgId", required = true)private int orgId;
}

4.Service层接口

    @Overridepublic String uploadQwMedia(Integer orgId, FindMediaIdReq reqBean, InputStream inputStream){Map<String, Object> map = qyWeiXinService.uploadMediaForKf(orgId, reqBean, inputStream);String mediaId = map.get("media_id").toString();String createdAt = DateUtil.date2Str(new Date(Long.valueOf(map.get("created_at").toString()) * 1000));log.info("上传企微素材返回,mediaId:{},createAt:{}", mediaId, createdAt);return mediaId;}

qyWeiXinService中的uploadMediaForKf方法

 @Overridepublic Map<String, Object> uploadMediaForKf(Integer orgId, FindMediaIdReq reqBean, InputStream inputStream) {ScaQyWxBuConfig scaQyWxBuConfig = ScaQyWxBuConfig.getConfigByOrgId(orgId);//获取配置的secret和corpId等信息if(scaQyWxBuConfig == null){log.error("客服企微参数配置为空, orgId:{}", orgId);throw new RuntimeException("获取客服企微配置参数失败");}String accessToken = this.getKfAccessToken(scaQyWxBuConfig);//获取accessTokenString title = reqBean.getTitle();//调用企微api上传图片文件到企微临时素材return WinXinMessageUtil.mediaUploadByInputStream(accessToken, inputStream, reqBean.getType(), title);}

调用企微api上传图片文件到企微临时素材方法,对应上面的WinXinMessageUtil.mediaUploadByInputStream方法

public static Map<String, Object> mediaUploadByInputStream(String accessToken, InputStream inputStream, String type, String fileName) {String upUrl = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=" + accessToken + "&type=" + type;StringBuffer buffer = new StringBuffer();BufferedReader reader = null;try {URL urlObj = new URL(upUrl);HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();con.setRequestMethod("POST"); // 以Post方式提交表单,默认get方式con.setDoInput(true);con.setDoOutput(true);con.setUseCaches(false); // post方式不能使用缓存// 设置请求头信息con.setRequestProperty("Connection", "Keep-Alive");con.setRequestProperty("Charset", "UTF-8");// 设置边界String BOUNDARY = "----------" + System.currentTimeMillis();con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);// 请求正文信息StringBuilder sb = new StringBuilder();sb.append("--"); // 必须多两道线sb.append(BOUNDARY);sb.append("\r\n");sb.append("Content-Disposition: form-data;name=\"media\";filename=\"" + fileName + "\"\r\n");sb.append("Content-Type:application/octet-stream\r\n\r\n");byte[] head = sb.toString().getBytes("utf-8");// 获得输出流OutputStream out = new DataOutputStream(con.getOutputStream());// 输出表头out.write(head);// 把文件已流文件的方式 推入到url中DataInputStream in = new DataInputStream(inputStream);int bytes;byte[] bufferOut = new byte[1024];while ((bytes = in.read(bufferOut)) != -1) {out.write(bufferOut, 0, bytes);}in.close();// 结尾部分byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");// 定义最后数据分隔线out.write(foot);out.flush();out.close();// 定义BufferedReader输入流来读取URL的响应InputStream conInputStream = con.getInputStream();reader = new BufferedReader(new InputStreamReader(conInputStream));String line;while ((line = reader.readLine()) != null) {buffer.append(line);}String result = buffer.toString();log.warn("{}上传临时素材结果:{}", fileName, result);Map<String, Object> map = JSON.parseObject(result, Map.class);if (!Objects.equals(map.get("errcode"), 0)) {throw new IllegalArgumentException("上传临时素材异常:" + map.get("errmsg"));}return map;} catch (IOException e) {log.warn("上传临时素材{}异常:{}-{}", fileName, e.getMessage(), e);throw new IllegalArgumentException("上传临时素材异常", e);} finally {try {if (reader != null) {reader.close();}} catch (IOException e) {e.printStackTrace();}}}

获取token的方法,对应上面的getKfAccessToken方法

这里先从redis缓存获取,获取不到再调用企微api接口获取,可以根据实际情况进行变通。

private String getKfAccessToken(ScaQyWxBuConfig scaQyWxBuConfig) {String corpId = scaQyWxBuConfig.getCorpId();//从配置中获取的corpIdString secret = scaQyWxBuConfig.getSecretKf();//从配置中获取的secretif (StringUtils.isEmpty(corpId) || StringUtils.isEmpty(secret)) {return null;}String key = "HYP_GUIDE_" + corpId + "AccessToken" + secret;//优先从redis缓存中获取String accessToken = jedisCluster.get(key);if (StringUtils.isEmpty(accessToken)) {//缓存中获取不到再调用企微api接口获取accessTokentry {accessToken = WinXinMessageUtil.getAccessToken(corpId, secret);jedisCluster.set(key, accessToken);jedisCluster.expire(key, 7000);//设置过期时间} catch (Exception e) {log.error(e.getMessage());}}return accessToken;
}

3.调用企微api接口获取accessToken信息,

对应上面的WinXinMessageUtil.getAccessToken

public static String getAccessToken(String CorpID, String Secret) throws Exception {String access_token = "";CloseableHttpClient httpclient = HttpClients.createDefault();try {HttpGet httpGet = new HttpGet("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + CorpID + "&corpsecret=" + Secret);CloseableHttpResponse response1 = httpclient.execute(httpGet);JSONObject resultJsonObject;try {HttpEntity httpEntity = response1.getEntity();if (httpEntity != null) {try {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "UTF-8"), 8 * 1024);StringBuilder entityStringBuilder = new StringBuilder();String line;while ((line = bufferedReader.readLine()) != null) {entityStringBuilder.append(line);}// 利用从HttpEntity中得到的String生成JsonObjectresultJsonObject = new JSONObject(entityStringBuilder.toString().trim());access_token = resultJsonObject.get("access_token") + "";} catch (Exception e) {log.warn("获取企微token异常:{}-{}", e.getMessage(), e);}}} finally {response1.close();}} finally {httpclient.close();}return access_token;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/449652.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

千万不要手贱删CentOS自带的Python

昨天准备在LINUX服务器上开始部署我写的langchainstreamlit代码&#xff0c;上了一台公司不用的服务器&#xff0c;因为langchain的环境是要用3.8以上的python&#xff0c;所以我上去看了一下python的版本 2.7&#xff0c;当时我就没有多想一下这个python是不是人家自带的&…

idea配置jdk

jdk1.8推荐链接&#xff1a;Jdk1.8的下载、安装及环境配置-CSDN博客 附本人下载的 jdk1.8 的百度网盘链接 链接&#xff1a;https://pan.baidu.com/s/1nOo7k7-f2fZojuyIOW6FvA 提取码&#xff1a;i5py 过程简述&#xff1a; 1&#xff0c;一路next安装完后&#xff08;我这…

红日三打靶!!!

红日三&#xff0c;黑盒测试 环境搭建一.外网打点1.网段探测2.端口服务扫描3.目录扫描4.网站漏洞扫描5.汇总&#xff0c;找破绽6.登陆MySQL改密码 7.进入后台&#xff0c;找能写马的地方8.蚁剑连接9.disable_functions绕过1.蚁剑插件绕过2.bypass_disablefunc_via_LD_PRELOAD绕…

【C/C++ 10】扫雷小游戏

一、题目 写一个扫雷小游戏&#xff0c;每次输入一个坐标&#xff0c;若该处是地雷&#xff0c;则游戏失败&#xff0c;若该处不是地雷&#xff0c;则显示周围地雷数量&#xff0c;若扫除全部非地雷区域&#xff0c;则扫雷成功。 二、算法 设置两张地图&#xff08;二维数组&…

Data Encryption Standard算法:历经考验的经典加密方案

在当今数字化时代&#xff0c;数据安全是一个至关重要的问题。为了保护敏感数据的机密性和完整性&#xff0c;加密算法成为了数据保护的关键技术。其中&#xff0c;DES&#xff08;Data Encryption Standard&#xff09;算法作为一种经典的对称密钥加密算法&#xff0c;具有广泛…

如何计算模型的复杂度(参数量,FLOPs)

参考 如何计算神经网络模型的复杂度 深度学习卷积、全连接层、深度可分离层参数量和FLOPs计算公式 概念 Params&#xff1a;模型的参数量。&#xff08;空间复杂度&#xff09;FLOPs&#xff1a;FLoating point Operations&#xff0c;前向推理的计算量。&#xff08;时间复…

MySQL中去除重复(十一)

MySQL中去除重复(十一) 一、相同的行 我们要去除相同行要使用DISTINCT关键字 SELECT DISTINCT 列名 FROM 表名; distinct 是针对查询的结果集合进行去重而不是针对某一行或者某一列。 二、查询中的行选择 用 WHERE 子句限制从查询返回的行。一个 WHERE 子句包含一个 必须满…

计算机网络——链路层(1)

计算机网络——链路层&#xff08;1&#xff09; 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU)前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff0c; [跳转到网站](https://www.captainbed.…

嵌入式中C 语言中的三块技术难点

C 语言在嵌入式学习中是必备的知识&#xff0c;甚至大部分操作系统都要围绕 C 语言进行&#xff0c;而其中有三块技术难点&#xff0c;几乎是公认级别的“难啃的硬骨头”。 今天就来带你将这三块硬骨头细细拆解开来&#xff0c;一定让你看明白了。 0x01 指针 指针是公认最难理…

智慧文旅:驱动文化与旅游融合发展的新动力

随着科技的快速发展和人们生活水平的提高&#xff0c;文化和旅游的融合成为了时代发展的必然趋势。智慧文旅作为这一趋势的引领者&#xff0c;通过先进的信息技术手段&#xff0c;推动文化与旅游的深度融合&#xff0c;为产业的发展注入新的活力。本文将深入探讨智慧文旅如何成…

element-ui icon 组件源码分享

今日简单分享 element-ui 源码中的 icon 组件&#xff0c;主要从以下两个方面来分享&#xff1a; 一、源码中 icon 设计思想是什么呢&#xff1f;主要从页面结构、数据、 icon 样式三个方面来分享。 1.1 源码中 icon 组件的页面结构&#xff0c;可以在 package 目录下找到 ico…

C++集群聊天服务器 数据模块+业务模块+CMake构建项目 笔记 (上)

跟着施磊老师做C项目&#xff0c;施磊老师_腾讯课堂 (qq.com) 本文在此篇博客的基础上继续实现数据模块和业务模块代码&#xff1a; C集群聊天服务器 网络模块业务模块CMake构建项目 笔记 &#xff08;上&#xff09;-CSDN博客https://blog.csdn.net/weixin_41987016/article…