背景:获取已有微信小程序的码(小程序码/二维码),保存为图片上传至cdn服务器,供使用方自行下载使用。
查看微信小程序官方文档,获取小程序码分两个步骤,调用接口如下:
1、获取access_token
2、根据上面获取的access_token,请求小程序码:
上面是直接用postman请求的,下面看用Java程序获取:
首先看下设计思路:先将获取到的二进制流保存到本地图片,然后将图片文件上传到cdn上,就这么简单。(代码只展示了获取有限的永久小程序码)
package com.integration.wechat.impl; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import com.sunlands.si.exception.BusinessException; import com.sunlands.si.integration.wechat.WechatMiniApp; import com.sunlands.si.util.COSUtil; /** * * @author zuowenjie * */ @Component public class WechatMiniAppImpl implements WechatMiniApp { private Logger log = Logger.getLogger(WechatMiniAppImpl.class); @Value("${weixin.app_id}") private String appId; @Value("${weixin.app_secret}") private String secretKey; @Value("${weixin.grant_type_QRCode}") private String grantType; @Value("${weixin.get_access_token_url}") private String accessTokenUrl;// 获取小程序access_token @Value("${weixin.get_QRcode_url}") private String QRcodeUrl;// 获取小程序码 @Value("${weixin.path}") private String page; @Value("${weixin.width}") private int width; @Value("${weixin.localpath}") private String localpath; @Autowired private COSUtil cosUtil; private RestTemplate restTemplate = new RestTemplate();; public WechatMiniAppImpl() { List<MediaType> mediaTypes = new ArrayList<MediaType>(); mediaTypes.add(MediaType.APPLICATION_JSON); mediaTypes.add(MediaType.APPLICATION_JSON_UTF8); mediaTypes.add(MediaType.IMAGE_PNG); MappingJackson2HttpMessageConverter mj2hmc = new MappingJackson2HttpMessageConverter(); mj2hmc.setSupportedMediaTypes(mediaTypes); List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters(); messageConverters.add(mj2hmc); restTemplate.setMessageConverters(messageConverters); } /** * 获取access_token */ @Override public String getAccessToken() { Map<String, String> request = new HashMap<String, String>(); request.put("grant_type", grantType); request.put("appid", appId); request.put("secret", secretKey); log.info("获取微信小程序access_token地址:" + accessTokenUrl); log.info("获取微信小程序access_token入参:" + request.toString()); @SuppressWarnings("unchecked") HashMap<String, String> result = restTemplate.getForObject(accessTokenUrl, HashMap.class, request); log.info("获取微信小程序access_token回参:" + result.toString()); String accessToken = result.get("access_token"); if (null == accessToken) { throw new BusinessException("获取小程序access_token异常:" + result.get("errmsg")); } return accessToken; } /** * 获取小程序码 * * @param ehrAccount */ @Override public String getCodePicture(String ehrAccount) { InputStream inputStream = null; OutputStream outputStream = null; String cosPath = null; String accessToken = this.getAccessToken(); File file = null; try { Map<String, Object> param = new HashMap<>(); param.put("path", "page/home/index"); param.put("width", 430); param.put("auto_color", false); Map<String, Object> line_color = new HashMap<>(); line_color.put("r", 0); line_color.put("g", 0); line_color.put("b", 0); param.put("line_color", line_color); log.info("调用生成微信小程序码URL接口地址:" + QRcodeUrl); log.info("调用生成微信小程序码URL接口入参:" + param); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.IMAGE_PNG); HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<Map<String, Object>>(param, headers); ResponseEntity<byte[]> entity = restTemplate.exchange(QRcodeUrl, HttpMethod.POST, requestEntity, byte[].class, accessToken); log.info("调用小程序生成微信永久小程序码URL接口回参:" + entity); byte[] result = entity.getBody(); log.info("小程序码:" + Base64.encodeBase64String(result)); inputStream = new ByteArrayInputStream(result); //二维码临时存放 File dir = new File(localpath); if(!dir.isDirectory()) { dir.mkdirs(); } file = new File(localpath + "/" + ehrAccount + "QRcode.png"); if (!file.exists()) { file.createNewFile(); } outputStream = new FileOutputStream(file); outputStream.write(result, 0, result.length); outputStream.flush(); cosPath = cosUtil.upload(file.getAbsolutePath()); } catch (Exception e) { log.error("调用小程序生成微信永久小程序码URL接口异常",e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if(null != file) { file.delete();//删除临时文件 } } return cosPath; } }
上面用的是springboot提供的restTemplate方式请求的,为什么用这个呢。原因很简单,封装的好,好用。再一个就是,我们的技术总监强制要求我们开发用springboot的全家桶,能不引用第三方的api坚决不用。插播一下,编码要求到了苛刻的地步,代码风格要统一,不定期进行代码review,接口严格遵守restful风格,命名要达到见名知意的标准,不准自制黑科技。其实,这样做好的方面是代码规范性好,便于维护,当然使用的技术也能在大boos的掌控范围内;不好的方面就是给开发人员设置了很多屏障。在另一篇文章中我会全面讲述一下我们的编码规范:
package com.si.util; import java.util.Map; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSONObject; import com.qcloud.cos.COSClient; import com.qcloud.cos.ClientConfig; import com.qcloud.cos.meta.InsertOnly; import com.qcloud.cos.request.UploadFileRequest; import com.qcloud.cos.sign.Credentials; import com.sunlands.si.exception.BusinessException; /** * 腾讯云对象存储工具 * @author zuowenjie */ @Component public class COSUtil { @Value("${qcloud.appId}") private long appId; @Value("${qcloud.secretKey}") private String secretKey; @Value("${qcloud.secretId}") private String secretId; @Value("${qcloud.bucketName}") private String bucketName; @Value("${qcloud.region}") private String region; @Value("${qcloud.QRCodePath}") private String cosPath; private Credentials cred; private ClientConfig clientConfig; private COSClient cosClient; private void init() { // 初始化秘钥信息 cred = new Credentials(appId, secretId, secretKey); // 初始化客户端配置 clientConfig = new ClientConfig(); // 设置bucket所在的区域,比如华南园区:gz; 华北园区:tj;华东园区:sh ; clientConfig.setRegion(region); // 初始化cosClient cosClient = new COSClient(clientConfig, cred); } /** * 上传文件到腾讯云对象存储 * @param appId * @param secretId * @param secretKey * @param bucketName * @param cosPath * @param localPath * @return */ public String upload(String localPath) { init(); String fileName= localPath.substring(localPath.lastIndexOf("\\") + 1); UploadFileRequest uploadFileRequest = new UploadFileRequest(bucketName, cosPath + "/" + fileName, localPath); uploadFileRequest.setInsertOnly(InsertOnly.OVER_WRITE);//覆盖 String uploadFileRet = cosClient.uploadFile(uploadFileRequest); @SuppressWarnings("unchecked") Map<String,Object> result = (Map<String, Object>) JSONObject.parse(uploadFileRet); if(!"SUCCESS".equalsIgnoreCase((String)result.get("message"))) { throw new BusinessException("小程序码上传失败:" + (String)result.get("message")); } @SuppressWarnings("unchecked") Map<String,String> data = (Map<String, String>) result.get("data"); String accessUrl = data.get("access_url"); return accessUrl; } }
注意:
因为微信官方返回的是一个二进制流,所以这里要特别注意用字节数组去接,不然会乱码,且保存的图片文件无法打开;
还有一点给需要特别注意,也是细心的事情,在这里我就遇到了一个很傻的事情,由于原始代码是copy别人,可能之前请求的一个参数的命名被官方改了,就是这个参数:
param.put("path", "page/home/index");
我把path写成了page,导致获取到的内容是错的,而且保存的图片打不开,但是又是用byte数组接的官方回参,这样就导致明明官方返回了错误信息,但是没转码根本看不出来是报错了,后来三个同事一起排查问题,才找出来。所以对自己的代码也不要过分自信,说不定哪个字母大小写不对就能让你花很多冤枉的时间,还会把自己逼疯。