获取微信小程序码

背景:获取已有微信小程序的码(小程序码/二维码),保存为图片上传至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;
	}


}

注意:

因为微信官方返回的是一个二进制流,所以这里要特别注意用字节数组去接,不然会乱码,且保存的图片文件无法打开;

扫描二维码关注公众号,回复: 857810 查看本文章

还有一点给需要特别注意,也是细心的事情,在这里我就遇到了一个很傻的事情,由于原始代码是copy别人,可能之前请求的一个参数的命名被官方改了,就是这个参数:

param.put("path", "page/home/index");

我把path写成了page,导致获取到的内容是错的,而且保存的图片打不开,但是又是用byte数组接的官方回参,这样就导致明明官方返回了错误信息,但是没转码根本看不出来是报错了,后来三个同事一起排查问题,才找出来。所以对自己的代码也不要过分自信,说不定哪个字母大小写不对就能让你花很多冤枉的时间,还会把自己逼疯。

猜你喜欢

转载自blog.csdn.net/u011063151/article/details/80320537