微信小程序踩坑记录,不定期更新中
【超硬小广告,大家可以关注一下我们美学福利社的小程序,我们会不定期推出明星单品试用。免费的噢。】
目前网易美学上线美学福利社小程序,是一个单独的消费工程。由于小程序是在微信容器中运行,诸多特性和API需要遵循微信的规范和标准。虽然微信正在不断努力地开放功能,完善标准,然而就现阶段而言,一些没有在文档中提到的要点仍然困扰着开发者。
本次微信小程序的开发,是一个痛苦的踩坑、摸索、完善的过程。现在把一些开发过程中遇到的注意点记录下来,希望能帮助到遇到同样问题的朋友们。
参考文档
- 小程序官方文档:https://mp.weixin.qq.com/debug/wxadoc/dev/index.html
- 微信公众平台开发文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432
openid & unionid
openid和unionid都作为微信用户身份凭证,可以用于区分用户。但是,在早些时候App端接入OAuth2做三方登录时,由于Android和iOS的openid不一致,导致两个操作系统的用户无法判别。所以需要识别用户身份或者要求唯一性的情况下,请使用unionid。
并且,在OAuth2拿到的openid和在小程序中拿到的openid会不一致,在发送模板消息的时候需要填入openid做参数,请使用小程序中的openid。
官方解释如下:
wx.getUserInfo(OBJECT)
前端获取用户信息的接口,如下官方所示
当用户是新微信并且没有关注过小程序关联的公众号or服务号时,该方法获取到的openid可能为null。
比较保险地获取openid和unionid的方法应该是拿到encryptedData,用iv做解密,然后反序列化拿到openid。
官方文档如下:
官方的解密demo示例宣称覆盖多种主流服务端开发语言,但是并没有提供Java版本,果然是有程序语言鄙视链存在。PHP是世界上最好的语言。
Java解密主要代码如下:
public class AESUtils {
public static boolean initialized = false;
/**
* AES解密
* @param content 密文
* @return
*/
public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws Exception {
initialize();
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Key sKeySpec = new SecretKeySpec(keyByte, "AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
byte[] result = cipher.doFinal(content);
return result;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void initialize(){
if (initialized) return;
Security.addProvider(new BouncyCastleProvider());
initialized = true;
}
//生成iv
public static AlgorithmParameters generateIV(byte[] iv) throws Exception{
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(iv));
return params;
}
}
Cookies
微信小程序不支持Cookie,但是支持request、response header,所以可以借用header来模拟Cookie机制。
response中设置“Set-Cookie”的header,request中再带上“Cookie”,再手动做解析、过期时间来处理就可以了。
虽然功能能实现,但是毕竟有些繁琐。
Template Message
模板消息,就是在微信用户关注了对应的服务号后,微信的会话列表会出现一个服务消息的会话栏,里面可以保存开发者向用户推送的消息。
下面是官方描述:
基于微信的通知渠道,我们为开发者提供了可以高效触达用户的模板消息能力,以便实现服务的闭环并提供更佳的体验。
模板推送位置:服务通知
模板下发条件:用户本人在微信体系内与页面有交互行为后触发,详见下发条件说明
模板跳转能力:点击查看详情仅能跳转下发模板的该帐号的各个页面
要想发送模板消息,首先得有模板。
创建模板的方式有两种
一种是人工在微信公众平台填写信息创建模板。
第二种是通过开发API去创建。
创建好模板后,准备发送模板消息。
首先要获取access_token,作为小程序全局唯一接口调用凭证,有效期2个小时。
简单存redis中。
@Override
public String getWxaAccessToken() throws Exception {
String key = "access_token";
String access_token = redisService.get(key);
if (StringUtils.isEmpty(access_token)) {
WXAccessTokenRequest request = new WXAccessTokenRequest();
WXAccessTokenResponse response = request.sendRequest();
if (StringUtils.isNotEmpty(response.getAccess_token())) {
redisService.setWithExpireTime(key, response.getAccess_token(), response.getExpires_in());
}
access_token = response.getAccess_token();
}
return access_token;
}
然后向微信的服务器POST一条请求就可以发送模板消息。
有一些参数需要说明一下。
- 如果你调试模板消息发现虽然收到模板消息,但是数据是空的,那么请检查你的data参数是一个JSONObject而不是一个String。
- form_id是由用户本人在微信体系内与页面有交互行为后触发,具体到代码中,是前端使用<form>组件,在formSubmit函数中获取form_id。form_id和openid是多对一的关系。只有由openid和小程序交互产生的form_id才能用于发送模板消息。
- form_id据微信的说法,有效期大概是7天,微信不保证。
看到这里,微信的想法应该是在于小程序交互时给用户发送提醒,微信并不希望模板消息用在主动给用户推送消息造成打扰。
那么,假如策划的需求是,在其他的运营后台系统(非微信)点击按钮(比如抽奖通知)想要通知用户,达到一个主动push的功能,该如何完成呢?
我们利用form_id有效期7天,可以做一些事情。我们把前端大部分的组件都包装成<form>,用户每次点击,收集一个openid、form_id的mapper,存入redis中,并将openid存入用户数据库。在运营想给用户推送一些数据时,查到所有的openid,顺便找到form_id,然后从form_id中找到一个最老的并且没有过期的id,发送模板消息并移出redis。
Java部分代码如下:
@Override
public ControllerResult<Void> gatherFormId(String form_id, long currentTime, String openid) {
LOGGER.info("WXATokenFacadeImpl#gatherFormId: {} openid : {}", form_id, openid);
if (StringUtils.isEmpty(openid)) {
return ControllerResult.fail(BeautyCode.SYS_ERROR, "openid为空,自动舍弃form_id");
}
String key = RedisKeyGenerator.keyForOpenIdFormIdMapper(openid);
redisService.zadd(key, Double.valueOf(currentTime), form_id);
return ControllerResult.ok(null);
}
@Override
public String findOldestFormIdAndRemove(String openid) {
if (StringUtils.isEmpty(openid)) {
LOGGER.info("WXATokenFacadeImpl#findOldestFormIdAndRemove:openid : {}", openid);
return null;
}
long currentTime = System.currentTimeMillis();
long lastestTime = currentTime - EXPIRE_TIME_MS - 1;// 比这个时间大才有效
String key = RedisKeyGenerator.keyForOpenIdFormIdMapper(openid);
redisService.zremrangebyscore(key, 0, lastestTime);
List<String> formIds = redisService.zrangebyscore(key, lastestTime, currentTime, 0, 1);
if (CollectionUtils.isNotEmpty(formIds)) {
String formId = formIds.get(0);
// 删除formid
if (StringUtils.isNotEmpty(formId)) {
redisService.zrem(key, formId);
}
return formId;
}
return null;
}
本文不定期更新~
超硬小广告:最后欢迎大家关注“美学福利社”,有很多好用的明星单品可以免费申领试用噢!!