微信小程序服务端开发遇到的一些坑

  • 开发环境
  • 问题集合
    • 小程序不支持基于进入客服事件推送消息
    • 小程序不支持永久素材
    • RestTemplate无法解析上传图片素材的返回
  • tips 其他小问题

开发环境

基于Spring Boot微信小程序服务端开发。

问题集合

小程序不支持基于进入客服事件推送消息

小程序客服开发过程中,往往需要在用户进入小程序客服时发送相关引导信息以吸引用户,防止潜在用户流失。根据微信官方的开发文档,可以发现微信会推送用户进入小程序客服的事件到服务端。那么只需要在推送时在服务端调用微信提供的发送信息的接口就能完成这一需求。然后就愉快地开始了编码。

public String miniCsMessage(@ApiParam(value = "签名", required = false) @RequestParam(value = "signature", required = false) String signature,@ApiParam(value = "时间戳", required = false) @RequestParam(value = "timestamp", required = false) String timestamp,@ApiParam(value = "验证参数", required = false) @RequestParam(value = "nonce", required = false) String nonce,@ApiParam(value = "openId", required = false) @RequestParam(value = "openid", required = false) String openId,@ApiParam(value = "回显验证参数", required = false) @RequestParam(value = "echostr", required = false) String echostr,@ApiIgnore HttpServletRequest request) throws Exception {if (StringUtils.isNoneEmpty(echostr)) {return echostr;}// 验证微信签名if (!WeChatUtils.checkSignature(signature, nonce, timestamp)) {return echostr;}WeChatMiniCSNotifyMessage weChatMiniCSNotifyMessage = WeChatUtils.getWeChatMiniCSNotifyMessage(openId, request.getInputStream());weChatService.handleWeChatMiniCSNotifyEvent(weChatMiniCSNotifyMessage);return echostr;}

这里采用了发布者-订阅者模式,将微信推送事件封装成WeChatMiniCSNotifyEvent然后交给订阅者处理。在订阅者中对具体事件进行处理,部分代码如下。

在这里插入代码片public void onApplicationEvent(WeChatMiniCSEvent weChatMiniCSEvent) {WeChatMiniCSNotifyMessage weChatMiniCSNotifyMessage = weChatMiniCSEvent.getWeChatMiniCSNotifyMessage();.............//用户点开客服等其他事件if ("event".equals(weChatMiniCSNotifyMessage.getType())) {//用户点开小程序客服事件if("user_enter_tempsession".equals(weChatMiniCSNotifyMessage.getEvent())) {.........sendWelcomeMsgToUser(openId);.........} else{log.info(String.format("用户openId:%s ,已发送小程序客服消息",openId));}}}

其中方法sendWelcomeMsgToUser(openId)中封装了调用微信api给用户发送文字消息和图片的欢迎消息。一切看似没有任何问题,并且在自己测试时由于同时测试了用户发消息给客服后再次进入客服界面的事件。(巨坑)所以进入时能成够发送消息。可是后来线上环境测试时,所有用户初次进入时都不能推送成功。而微信api返回的错误消息是{40003:invalid openid}。有点无语。。。。后来在社区咨询了一下才发现微信在4月9号后就不支持用户进入小程序客服界面时推送消息了,而由微信统一发送****为您服务。那么自测时为什么会成功呢?因为自测时主动给小程序客服发送了消息,微信规定收到用户消息后可以推送不超过五条下行消息!!!

小程序不支持永久素材

这个坑的根本原因应该是自己看开发文档没看仔细。背锅+1。由于小程序客服需要推送图片消息给用户,所以需要提前上传图片然后获取mediaId,之后通过将制定图片发送给用户。而素材的有效期是三天,所以在我上传图片三天后,突然就不能推送图片消息了,而文字消息却可以成功推送。图片消息推送返回的结果仍然是{40003:invalid openid}。可以说是非常之不友好了。。。百思不得其解只能去社区求助官方,官方的回复还是挺快的。明确这个素材是临时的后,貌似最直接的方案就是用定时任务来在缓存中维护一份最新的mediaId了。首先设定定时任务每天凌晨三点进行图片上传。

public class UploadImageTask {@Autowiredprivate WechatMiniCSEventLisener wechatMiniCSEventLisener;//每天凌晨三点上传小程序图片素材@Scheduled(cron = "0 0 3 * * ?")private void uploadTask(){log.info("执行微信小程序客服图片素材上传任务");wechatMiniCSEventLisener.processUploadTask();}
}

然后编写具体的任务,这里读取图片文件时本地执行是可以直接读取到你resource文件下的图片的,但是打包成jar包之后是不能直接读的。踩坑之路漫漫。

   /*** 上传所有微信客服消息图片*/public Map<String,WeChatTeacherImageDTO> uploadWechatTeacherImages(){Map<String,WeChatTeacherImageDTO> result = new HashMap<>();try {for (int i = 0; i < WECHAT_LEDU_MINI_TEACHER_NUMBER; i++) {String fileName = String.valueOf(i)+"_dic.jpg";String filePath = "/images/teacherImage/" +fileName;InputStream inputStream =this.getClass().getResourceAsStream(filePath);File file = new File(fileName);FileUtils.writeByteArrayToFile(file, toByteArray(inputStream));WeChatTeacherImageDTO weChatTeacherImageDTO = uploadOneImage(file);if(weChatTeacherImageDTO!=null) {weChatTeacherImageDTO.setId(String.valueOf(i));result.put(String.valueOf(i), weChatTeacherImageDTO);}else{log.info("上传单张图片失败,fileName:{},filePath:{}",fileName,filePath);}}return result;}catch (Exception e){log.error("上传微信客服消息图片失败",e);return new HashMap<>();}}

RestTemplate无法解析上传图片素材的返回

到这里感觉大坑已经踩得差不多了,信心满满地写完了定时任务的实现。一测。。。上传代码以及bug如下0.0

public <T> T wechatUploadFile(String url,File file,Class<T> responseType){if(!file.exists()){return null;}MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();FileSystemResource fileResource = new FileSystemResource(file);param.add("media", fileResource);HttpHeaders headers = new HttpHeaders();headers.add("Accept", MediaType.APPLICATION_JSON.toString());headers.setContentType(MediaType.parseMediaType("multipart/form-data; charset=UTF-8"));HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(param,headers);Map<String, Object> uriVariables = new HashMap<>();uriVariables.put("access_token",weChatComponent.getLeduAccessToken());return restTemplate.postForEntity(url, requestEntity, responseType,uriVariables).getBody();}
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [text/plain]at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:121)at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:994)at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:977)at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:737)at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:691)at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:454)at com.example.dewey.Controller.BaseController.uploadOneImage(BaseController.java:128)at com.example.dewey.Controller.BaseController.uploadWechatTeacherImages(BaseController.java:94)at com.example.dewey.Controller.BaseController.test(BaseController.java:78)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892)at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039)at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)

大致意思就是RestTemplate没法解析text/plain这种type的返回。又是没遇到过的问题,只能问度娘了。解决方法也还算简单,不能解析就给RestTemplate添加这种解析转换器。具体操作如下
首先定义转换器并让他支持text/plain

@Component
@Configuration
public class WxMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {public WxMappingJackson2HttpMessageConverter(){List<MediaType> mediaTypes = new ArrayList<>();mediaTypes.add(MediaType.TEXT_PLAIN);setSupportedMediaTypes(mediaTypes);}
}

然后在RestTemplate中追加该解析器

@Configuration
public class RestTemplateConfiguration {@Autowiredprivate WeChatHttpMessageConverter weChatHttpMessageConverter;@Autowiredprivate WxMappingJackson2HttpMessageConverter wxMappingJackson2HttpMessageConverter;@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();messageConverters.add(weChatHttpMessageConverter);messageConverters.add(wxMappingJackson2HttpMessageConverter);return restTemplate;}}

真是一波三折。

tips 其他小问题

  • AppID和AppSecret
    由于之前公众号存在一套AppID和AppSecret,然后小程序和公众号的获取AccessToken的接口是一样的。前面一直用着公众号的AppID和AppSecret获取AccessToken来给小程序发送消息,也是排查了很久。

  • AccessToken
    AccessToken的获取接口每次都是返回新的AccessToken并会将之前的AccessToken置为失效。所以有多台机器的情况下得将AccessToken放入缓存共用,而且在测试环境不能进行调用,不然会导致线上环境AccessToken不可用,导致整个服务不可用。

  • 解析请求流
    Java流数据只能读取一次,只能copy,流下了没技术的泪水。正确打开方式

 //request.getInputStream()ByteArrayOutputStream outputStream = new ByteArrayOutputStream();IOUtils.copy(request.getInputStream(),outputStream);InputStream inputStream1 = new ByteArrayInputStream(outputStream.toByteArray());InputStream inputStream2 = new ByteArrayInputStream(outputStream.toByteArray());

微信小程序服务端开发遇到的一些坑相关推荐

  1. 阿里云服务器上搭建微信小程序服务端环境。

    无论是搭建个人博客空间也好,微信小程序也罢,搭建环境必需的两点:云服务器.域名,下面一步步给搭建演示如果在一台阿里云服务器上搭建微信小程序服务端环境. 1.云服务器准备:可在阿里云购买ECS服务器   ...

  2. 婚礼邀请函 - 微信小程序+服务端

    婚礼邀请函 - 请柬 - 小程序端+服务端+安卓端 项目介绍 婚礼邀请函 效果图 项目说明 服务端架构:SpringMvc 服务器:阿里云服务 域名:pengmaster.com 数据库:在服务器上装 ...

  3. 微信小程序——服务端获取小程序二维码 永久有效 数量无限制

    因为现在做的小程序,想要分享小程序中的页面给微信好友,那就可以使用二维码,很方便. 而且通过后台接口可以获取小程序任意页面的小程序码 扫描该小程序码可以直接进入小程序对应的页面,所有生成的小程序码永久 ...

  4. 腾讯位置服务--微信小程序JSSDK地图开发

    腾讯位置服务–微信小程序JSSDK地图开发 1.腾讯位置服务 文档传送门:https://lbs.qq.com/miniProgram/jsSdk/jsSdkGuide/jsSdkOverview [ ...

  5. 3.1【微信小程序全栈开发课程】在本地搭建后端开发环境

    第二章将前端页面的框架基本搭建好了,第三章,我们来做登录功能,登录功能需要在后端获取到用户信息,返回到前端.所以先来搭建后端开发环境 1.后端开发环境介绍 我们的项目用的是前后端分离开发 前端可以理解 ...

  6. 微信小程序公众号开发

    微信小程序&公众号开发 一.什么是微信开发 二.微信开放平台 三.微信公众平台 四.小程序与公众号的区别 1. 用途不同 2. 运营方式不同 3. 操作方法不同 4. 用户体验不同(公众号操作 ...

  7. 微信小程序入门级实战开发指南

    微信小程序入门级实战开发指南 概述 微信小程序,简称小程序,英文名Mini Program,是一种"不需要下载安装"即可使用的应用(实际上是需要下载安装的,只是整个过程被简化到可以 ...

  8. 【调试模式】微信小程序和华为开发板通信

    文章目录 开发环境 开发板UDP接口 UDP服务器创建流程 开启UDP服务器完整代码(不含LED部分) 相关函数说明 微信UDP客户端创建流程 UDP客户端完整代码(不含LED部分) 效果图 参考资料 ...

  9. 微信小程序的云开发以及与传统开发的比较

    一.微信小程序的云开发概念 云开发就是一套解决小程序前后端开发的一种云端能力 它提供了一整套云服务及简单.易用的 API 和管理界面,以尽可能降低后端开发成本,让开发者能够专注于核心业务逻辑的开发.尽 ...

最新文章

  1. Android Studio 在Ubuntu 下快捷键失效
  2. Swift 4 无限滚动轮播图(UICollectionView实现)
  3. java并发编程之美-阅读记录7
  4. 小程序 text decode 真机无效_【移动端测试】APP自动化测试案例2:微信小程序自动化测试...
  5. 数字化技术浪潮下,医院临床科研如何「华丽变身」
  6. windwos下ffmpeg的安装
  7. 64位Office 2010 连接SOHU IMAP服务器遇到问题
  8. 解决Ubuntu “E: 软件包 vim 还没有可供安装的候选者“问题
  9. 焊接工时简便计算工具_焊接工时定额计算手册.doc
  10. 大一高数下册笔记整理_高等数学下册知识点总结.doc
  11. 轻松搞定JAVA选择排序
  12. mtk系统如何制作差分包且正确签名?
  13. 数据结构课程设计(选):连连看
  14. 数模技术转换应用于计算机控制,数模转换器的作用
  15. firefox非量子版自定义搜索引擎
  16. Android 贪食蛇
  17. 中国式审美真的太可怕了
  18. php实现金币提现,哪位php大神帮忙写个金币转换函数
  19. 四、函数的基本概念和使用
  20. 回溯法,回溯法解装载问题

热门文章

  1. multism中ui和uo应该怎么表示_如何求该运算放大电路uo和ui的关系表达式?
  2. 如何更好的培养下一代接班人?
  3. 跑步和写文章一样令人心情愉悦
  4. 全世界顶尖黑客排名!
  5. java 健康助手项目_GitHub - bohrqiu/watcher: watcher(守望者)提供java应用暴露监控/健康检查的能力。...
  6. 分享高质量CAD学习网站,帮助你快速掌握CAD技术
  7. 需求分析-原文分析法
  8. Drawing 2-Point Perspective 绘制2点透视图 Lynda课程中文字幕
  9. 如何在Linux终端使用录屏工具Asciinema?
  10. arecord -l 失败(cannot execute binary file) 重装alas-utils的两种方法