2022-08-15 开通微信云托管

  • 在公众号推送的云服务器选择上,我选择的是微信云托管。

  • 扫码登陆后,如果是第一次注册试用,没有环境,可以选择自己擅长的语言进行一键部署模板。此处我选择的是SpringBoot作为我的服务后端。

  • 随后一路点击,等待它部署完成就可以了。出现“部署成功”四个字后,通过公网域名访问demo示例,如果能正常访问就表明搭建成功。部署的同时也会为你启用云mysql服务。如果不需要使用微信云托管的mysql服务,可以在后面前往控制台,关闭数据库服务。如果不是通过控制台部署模板代码,而是通过复制/下载模板代码后,手动新建一个服务并部署,需要在「服务设置」中补全环境变量,才可正常使用,否则会引发无法连接数据库,进而导致部署失败。

2022-08-15 配置代码仓库

  • 微信云托管给我们提供了免运维服务,我们只需要配置流水线仓库,完成代码提交后,自动拉取仓库最新代码帮我们部署到服务器上。因此在我们专注开发之前,需要先配置我们的代码储存仓库。关于代码仓库的选择可以根据自己的需求自定,我选择的是gitee进行储存。
  • 采用模板开发的话,需要我们把模板代码拷贝到我们的仓库。拷贝完成后,在服务-服务设置-流水线中,配置代码仓库,选择指定的代码源、仓库、分支,选中推送到master时触发流水线(如果是有其它分支也可以选择其他分支)。
  • 至此我们就完成了开发环境的准备,准备可以开始敲代码啦!官方参考教程

2022-08-15 申请公众号

  • 由于我要完成的功能是公众号的定时推送,因此我需要有一个公众号。但是模板推送功能需要企业验证才可以使用,获取难度高,所以我选择了申请测试号,除了公众号名字,头像等内容是无法自定义的,在功能上都是一样的。微信公众测试号申请。

  • 申请完成后可以得到自己测试号的appId,appsecret信息。这两个信息主要用于获取公众号全局调用的唯一凭证Access_Token。怎么获取我们后面再说。

  • URL(即:你的公网访问域名+你的服务器对应的接口Url)代表的是你服务器对应的校验接口,Token是自己定义的字符串。

  • 具体校验操作看如下

2022-08-15 校验服务器合规性

  • 微信为了校验我们服务器的合法性,需要向我们服务器发起一个GET请求,若确认此次 GET 请求来自微信服务器,则原样返回 echostr 参数内容,则接入生效,成为开发者成功,否则接入失败
  • 微信发送的get请求参数有4个
signature timestamp nonce echostr
微信加密签名,signature结合了开发者填写的 token 参数和请求中的 timestamp 参数、nonce参数 时间戳 随机数 随机字符串
  • 工具类SHA1
public class SHA1 {/*** 用SHA1算法生成安全签名* @param token 票据* @param timestamp 时间戳* @param nonce 随机字符串* @param encrypt 密文* @return 安全签名* @throws AesException */public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException{try {String[] array = new String[] { token, timestamp, nonce, encrypt };StringBuffer sb = new StringBuffer();// 字符串排序Arrays.sort(array);for (int i = 0; i < 4; i++) {sb.append(array[i]);}String str = sb.toString();// SHA1签名生成MessageDigest md = MessageDigest.getInstance("SHA-1");md.update(str.getBytes());byte[] digest = md.digest();StringBuffer hexstr = new StringBuffer();String shaHex = "";for (int i = 0; i < digest.length; i++) {shaHex = Integer.toHexString(digest[i] & 0xFF);if (shaHex.length() < 2) {hexstr.append(0);}hexstr.append(shaHex);}return hexstr.toString();} catch (Exception e) {e.printStackTrace();throw new AesException(AesException.ComputeSignatureError);}}
}
  • 异常类

```java
package com.tencent.wxcloudrun.utils;@SuppressWarnings("serial")
public class AesException extends Exception {public final static int OK = 0;public final static int ValidateSignatureError = -40001;public final static int ParseXmlError = -40002;public final static int ComputeSignatureError = -40003;public final static int IllegalAesKey = -40004;public final static int ValidateAppidError = -40005;public final static int EncryptAESError = -40006;public final static int DecryptAESError = -40007;public final static int IllegalBuffer = -40008;//public final static int EncodeBase64Error = -40009;//public final static int DecodeBase64Error = -40010;//public final static int GenReturnXmlError = -40011;private int code;private static String getMessage(int code) {switch (code) {case ValidateSignatureError:return "签名验证错误";case ParseXmlError:return "xml解析失败";case ComputeSignatureError:return "sha加密生成签名失败";case IllegalAesKey:return "SymmetricKey非法";case ValidateAppidError:return "appid校验失败";case EncryptAESError:return "aes加密失败";case DecryptAESError:return "aes解密失败";case IllegalBuffer:return "解密后得到的buffer非法";
//      case EncodeBase64Error:
//          return "base64加密错误";
//      case DecodeBase64Error:
//          return "base64解密错误";
//      case GenReturnXmlError:
//          return "xml生成失败";default:return null; // cannot be}}public int getCode() {return code;}AesException(int code) {super(getMessage(code));this.code = code;}}
  • 校验接口
/*** 用于校验服务器是否合规,此处校验方式可以根据自己选择进行加密算法的选择* 理论上此处可以省略工具类的校验,直接返回请求中的参数。* 但是笔者直接返回却报错,不知道原因为何,看到这里的小伙伴可以尝试一下直接返回,即:return request.getEchostr();* @return String*/@GetMapping(value = "/checkToken")public String checkToken(VerifyRequest request) {//配置中自己填写的TokenString token = "网页端你填写的token";String sha1 = "";try {sha1 = SHA1.getSHA1(token, request.getTimestamp(), request.getNonce(), "");} catch (AesException e) {e.printStackTrace();}System.out.println("加密:"+sha1);System.out.println("本身:"+request.getSignature());//如果校验成功,则返回请求中的echostr参数。if(sha1.equals(request.getSignature())){return request.getEchostr();}else {return "非法访问";}}

2022-08-16 编写推送模板,获取关注的用户ID

  • 模板推送接口文档

  • 在我们的测试公众号平台上,我们可以配置我们的推送模板。其中模板参数统一格式为{{你的返回值.DATA}},如下所示。

  • 通过扫描二维码,邀请需要推送的朋友,获取他们的OPENID

  • 编写完模板,获取用户ID后,我们就可以调用微信公众号的模板推送接口,进行推送啦。以下为一个标准的POST请求格式。

//请求URL:https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN(此处的ACCESS_TOKEN的获取会在后面进行补充)
//请求体
{//关注你公众号的用户ID"touser":"OPENID",//你创建的模板ID"template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",//点击消息模板跳转的地址"url":"http://weixin.qq.com/download",//顶色"topcolor":"#FF0000",//相应的具体数据,此处的User,Date,Type就是你模板里面对应的{{XXX.DATA}},如{{User.DATA}},{{Date.DATA}}"data":{"User": {"value":"黄先生","color":"#173177"},"Date":{"value":"06月07日 19时24分","color":"#173177"},"CardNumber": {"value":"0426","color":"#173177"},"Type":{"value":"消费","color":"#173177"},"Money":{"value":"人民币260.00元","color":"#173177"},"DeadTime":{"value":"06月07日19时24分","color":"#173177"},"Left":{"value":"6504.09","color":"#173177"}}
}

2022-08-16 获取全局调用唯一凭证ACCESS_TOKEN

  • access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。关于Access_Token的说明请参考Access token

  • 此处获取Access_Token的方法也很简单,直接调用接口即可:https://api.weixin.qq.com/cgi-bin/token?appid=APPID&secret=APPSECRET。其中appId以及appSecret来自于测试号信息。

  • 获取Access_Token工具类

/*** 获取微信token工具类*/
@Component
public class WeChetAccessToken {//http请求类@Autowiredprivate RestTemplate restTemplate;//微信配置类文件@Autowiredprivate WxMpProperties wxMpProperties;public String getToken() {//如果缓存中的Token过期,则请求获取Tokenif (WxChatCache.AccessToken.expiration <= System.currentTimeMillis()) {//URL:https://api.weixin.qq.com/cgi-bin/token?appid=APPID&secret=APPSECRETString url = WxChatConstant.Url.ACCESS_TOKEN_URL.replace("APPID", wxMpProperties.getAppId()).replace("APPSECRET", wxMpProperties.getSecret());//使用restTemplate发起Http请求ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);JSONObject jsonObject = JSON.parseObject(forEntity.getBody());Object errcode = jsonObject.get("errcode");if (errcode != null && "40013".equals(errcode.toString())) {System.out.println("不合法的APPID");}// expiration:为当前执行到此处的时间+2小时有效时间为过期时间WxChatCache.AccessToken.token = jsonObject.get("access_token").toString();WxChatCache.AccessToken.expiration = System.currentTimeMillis()+7200000;return WxChatCache.AccessToken.token;}//如果没过期则使用缓存中的Tokenelse {System.out.println("返回缓存中的token:"+ WxChatCache.AccessToken.token);return WxChatCache.AccessToken.token;}}
}
  • restTemplate配置文件
/*** RestTemplate工具类,主要用来提供RestTemplate对象*/
@Configuration//加上这个注解作用,可以被Spring扫描
public class RestTemplateConfig {/*** 创建RestTemplate对象,将RestTemplate对象的生命周期的管理交给Spring----踩坑一,编码问题*/@Beanpublic RestTemplate restTemplate(){RestTemplate restTemplate = new RestTemplate();//设置中文乱码问题方式一//restTemplate.getMessageConverters().add(1,new StringHttpMessageConverter(Charset.forName("UTF-8")));// 设置中文乱码问题方式二restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));return restTemplate;}
}
  • 缓存类
public class WxChatCache {/*** 微信 accessToken缓存*/public static class AccessToken {public static String token = null;   // accessTokenpublic static Long expiration = 0L;  // accessToken 过期时间(获取的token 默认有效期2小时)}
}

2022-08-16 修改云托管容器时间为上海时区

  • 由于我们需要实现定时发送功能,因此我们必须获取系统的时间。容器系统时间默认为 UTC 协调世界时间 (Universal Time Coordinated),与本地所属时区 CST (上海时间)相差 8 个小时。因此我们需要修改容器时间。
  • 修改方法也很简单,只需要在项目的dockerfile中,将上海时区的注释打开即可。
# 容器默认时区为UTC,如需使用上海时间请启用以下时区设置命令
RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone

2022-08-17 开启令牌调用

  • 此处配置需要调用的微信的接口,允许我们服务器请求。相关说明

2022-08-17 实现定时推送

  • 在经过上面的步骤准备即可开始实现定时推送啦。以下展示一个完整的demo示例。

  • 启动类

@SpringBootApplication
@MapperScan(basePackages = {"com.tencent.wxcloudrun.dao"})
//开启定时任务
@EnableScheduling
public class WxCloudRunApplication {  public static void main(String[] args) {SpringApplication.run(WxCloudRunApplication.class, args);}
}
  • Controller层
@RestController
public class GirlFriendController {//上下文,用于策略模式获取对应策略@Autowiredprivate ApplicationContext applicationContext;//表示每个月星期一到星期五下午4点50分执行@Scheduled(cron = "0 50 16 ? * MON-FRI")public void sendOffWork() throws ExecutionException, InterruptedException {System.out.println("开始发送下班提醒");//参数一发送类型,参数二是推送的对象OpenIdSendTypeRequest sendTypeRequest = new SendTypeRequest("OffWorkSend","推送的对象OpenId");WxChatService chatService = applicationContext.getBean(sendTypeRequest.getType(),WxChatService.class);chatService.sendTest(sendTypeRequest);}
}
  • Service层
public interface WxChatService {/*** 向一个用户推送消息(测试)* @param*/void sendTest(SendTypeRequest send) throws ExecutionException, InterruptedException;
}
  • Service层实现类
/*** 下班推送策略* */
@Service("OffWorkSend")
public class OffWorkSend implements WxChatService {@Autowiredprotected WxSendMessageUtils wxSendMessageUtils;@Overridepublic void sendTest(SendTypeRequest send) {// 下班模板IdString templateId = "你的模板Id";// 模板参数Map<String, WeChatTemplateMsg> sendMag = new HashMap<String, WeChatTemplateMsg>();sendMag.put("offWork", new WeChatTemplateMsg("宝~马上就要下班咯,收拾好随身物品准备早退啦!","#b89485"));// 发送wxSendMessageUtils.send(send.getOpenId(), templateId, sendMag);}
}
  • 发送工具类WxSendMessageUtils
@Component
public class WxSendMessageUtils{//获取Access_Token工具类@Autowiredprotected WeChetAccessToken weChetAccessToken;//restTemplate的请求方式@Autowiredprotected RestTemplate restTemplate;/*** 发送方法* */public String send(String openId, String templateId, Map<String, WeChatTemplateMsg> data) {String accessToken = weChetAccessToken.getToken();//System.out.println("send方法里的token:"+accessToken);String url = WxChatConstant.Url.SEND_URL.replace("ACCESS_TOKEN", accessToken);//String url = "http://api.weixin.qq.com/cgi-bin/message/template/send";//拼接base参数System.out.println("调用的接口地址为:"+url);Map<String, Object> sendBody = new HashMap<>();sendBody.put("touser", openId);               // openIdsendBody.put("url","www.baidu.com");          // 跳转urlsendBody.put("topcolor", "#FF0000");          // 顶色sendBody.put("data", data);                   // 模板参数sendBody.put("template_id", templateId);      // 模板IdResponseEntity<String> forEntity = restTemplate.postForEntity(url, sendBody, String.class);System.out.println("响应:"+forEntity.getBody());return forEntity.getBody();}}
  • 策略请求类型
@Data
@AllArgsConstructor
public class SendTypeRequest {/*** 推送类型* */private String type;/*** 接收人的openId* */private String openId;}
  • 运行结果

2022-08-18 一些总结

  • 使用restTemplate请求第三方接口时,JSON序列化的时候,返回的body乱码,无法转换成对象。

关于这个问题,无解。尝试了网上的一些主流的方案,无果。最后还是使用了HttpClient进行http的请求调用

  • javax.net.ssl.SSLException: Connection reset

出现这个问题,请及时查看是否在“服务-服务列表”中开启是否允许公网访问。

  • errCode: 102002 | errMsg: 请求超时

由于动态扩容的原因,超过30分钟没有调用,实例缩容为0。此时服务停止。需要在「服务设置」中,将「实例副本数」的最小值设为1,保持服务常驻,无论服务是否被请求都不会缩容到0,从根本上避免冷启动。(会产生更多资源消耗及费用,请自行权衡,学生党可以将规格修改为0.25核,0.5G内存,够用了);

手把手教你给女朋友编写一个公众号定时推送(java版本)相关推荐

  1. 【微信早安定时推送消息】微信公众号定时推送早安消息 带天气、纪念日、生日、定时推送等(完整代码)

    我挥舞着键盘和本子,发誓要把世界写个明明白白. 简介 利用所学知识给他/她一个惊喜,是作为计算机专业的你最大的乐趣. 无计算机基础,5分钟即设置好 (定时推送 及 最新版 将在下期带来) 获取完整代码 ...

  2. 公众号定时推送早安、天气预报-保姆级教程

    效果图: 一.主要步骤如下: a.Linux操作系统(Ubuntu.Centos.Redhat都可) b.安装docker服务 c.gitee上下载源代码:微信推送 d.申请微信测试公众号:公众号开放 ...

  3. 微信公众号定时推送消息

    先上一波效果图! 一.微信公众号测试平台 地址: http://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=san ...

  4. 手把手教你使用nodejs编写一个【使用远程仓库模板,快速创建项目模块】的cli(命令行)

    目录 实现步骤 初始化cli项目 项目目录 创建交互式命令 拉取远程仓库代码,读取仓库中的模板 拉取远程仓库代码 ora 终端 loading 读取仓库中的模板 将选择的模板复制写入目标项目 Comm ...

  5. 用 Go 编写一个简单的 WebSocket 推送服务

    用 Go 编写一个简单的 WebSocket 推送服务 本文中代码可以在 github.com/alfred-zhon- 获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息都是通过短信,微 ...

  6. 微信公众号每日推送(小白网页版配置)

    微信公众号每日定时推送,天气推送(小白网页版配置) 0.提示 1. 前言 2. API接口申请 2.1 微信 2.2 百度 2.3 地区编码 3. 模板内容编写 4. 功能演示 4.1 创建定时早安推 ...

  7. 微信公众号信息推送实现业务解耦

    序 最近第三方服务需要增加微信模板信息推送能力,要与业务系统解耦.微服务架构的springCloud下第三方服务增加能力. 一.技术选型 看了下微信公众号的信息推送开发文档,发现不难,就是一个个接口对 ...

  8. 微信公众号怎么推送消息_微信公众号发送消息

    A.模板消息发送 模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中,如信用卡刷卡通知,商品购买成功通知等.不支持广告等营销类消息以及其它所有可能对用户造成骚扰的消息. 备注 ...

  9. python爬虫如何实现每天爬取微信公众号的推送文章

    python爬虫如何实现每天爬取微信公众号的推送文章 上上篇文章爬虫如何爬取微信公众号文章 上篇文章python爬虫如何爬取微信公众号文章(二) 上面的文章分别介绍了如何批量获取公众号的历史文章url ...

最新文章

  1. 前阿里程序员吐槽女友败家:开酒店必须400元起步,工资只有自己的1/3
  2. Linux下qt注册登录,QT学习笔记:登录对话框
  3. html 右侧滚动条,html中去掉textarea右侧滚动条和右下角拖拽
  4. 多序列比对的c语言程序,多序列比对的实际应用
  5. SPQuery查询语法介绍
  6. flume使用telnet作为入口出现connection refused
  7. Ghosts for Tea
  8. [2021-09-09 T3] 序列/luogu P3943 星空(异或差分+bfs最短路+状压dp)
  9. 自动计算尺寸列表功能案例ios源码
  10. [学习笔记]Javascript可选的分号
  11. java 8流在另一个流_Java 8流– Java流
  12. 4.大数据架构详解:从数据获取到深度学习 --- 数据获取
  13. 入门教程 | 5分钟从零构建第一个 Flink 应用
  14. ORA-01017 错误
  15. 黑客帝国代码雨以及其灵活使用——道家真言!!(附带相关知识点)
  16. 判断字符串是否为纯英文或纯中文
  17. ios7 未能连接到服务器,完美解决iTunes 未能连接到此 iPhone。发生未知错误(0xe8000003)和0xe8000084...
  18. 蚂蚁区块链投票案例(二)---投票合约设计开发
  19. 性能优化之道】每秒上万并发下的Spring Cloud参数优化实战
  20. JAVA实验二:设计一个教师类Teacher(属于cn.net.sdkd包)实现接口进行排序等

热门文章

  1. Pomodoro技术的失败如何使我成为2倍更好的程序员
  2. 简单的python程序实现99乘法口诀表
  3. c语言程序设计第二张答案,自学考试《C语言程序设计》习题及答案_第2页
  4. kaldi中文语音识别thchs30模型训练
  5. 一阶矩+二阶矩估计求解一个参数
  6. 每周分享第 38 期
  7. 关于书面辞职报告和试用期离职
  8. (linux)idr(integer ID management)机制
  9. 如何正确的向领导汇报工作?
  10. 分块上(下)三角矩阵的行列式