1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > 手把手教你给女朋友编写一个公众号定时推送(java版本)

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

时间:2018-08-05 13:54:04

相关推荐

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

-08-15 开通微信云托管

在公众号推送的云服务器选择上,我选择的是微信云托管。 扫码登陆后,如果是第一次注册试用,没有环境,可以选择自己擅长的语言进行一键部署模板。此处我选择的是SpringBoot作为我的服务后端。 随后一路点击,等待它部署完成就可以了。出现“部署成功”四个字后,通过公网域名访问demo示例,如果能正常访问就表明搭建成功。部署的同时也会为你启用云mysql服务。如果不需要使用微信云托管的mysql服务,可以在后面前往控制台,关闭数据库服务。如果不是通过控制台部署模板代码,而是通过复制/下载模板代码后,手动新建一个服务并部署,需要在「服务设置」中补全环境变量,才可正常使用,否则会引发无法连接数据库,进而导致部署失败。

-08-15 配置代码仓库

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

至此我们就完成了开发环境的准备,准备可以开始敲代码啦!官方参考教程

-08-15 申请公众号

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

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

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

具体校验操作看如下

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

微信为了校验我们服务器的合法性,需要向我们服务器发起一个GET请求,若确认此次 GET 请求来自微信服务器,则原样返回 echostr 参数内容,则接入生效,成为开发者成功,否则接入失败微信发送的get请求参数有4个工具类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(puteSignatureError);}}}

异常类

```javapackage 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 "非法访问";}}

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

模板推送接口文档

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

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

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

//请求URL:https://api./cgi-bin/message/template/send?access_token=ACCESS_TOKEN(此处的ACCESS_TOKEN的获取会在后面进行补充)//请求体{//关注你公众号的用户ID"touser":"OPENID",//你创建的模板ID"template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",//点击消息模板跳转的地址"url":"/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"}}}

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

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

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

获取Access_Token工具类

/*** 获取微信token工具类*/@Componentpublic class WeChetAccessToken {//http请求类@Autowiredprivate RestTemplate restTemplate;//微信配置类文件@Autowiredprivate WxMpProperties wxMpProperties;public String getToken() {//如果缓存中的Token过期,则请求获取Tokenif (WxChatCache.AccessToken.expiration <= System.currentTimeMillis()) {//URL:https://api./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小时)}}

-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

-08-17 开启令牌调用

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

-08-17 实现定时推送

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

启动类

@SpringBootApplication@MapperScan(basePackages = {"com.tencent.wxcloudrun.dao"})//开启定时任务@EnableSchedulingpublic class WxCloudRunApplication {public static void main(String[] args) {SpringApplication.run(WxCloudRunApplication.class, args);}}

Controller层

@RestControllerpublic 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

@Componentpublic 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./cgi-bin/message/template/send";//拼接base参数System.out.println("调用的接口地址为:"+url);Map<String, Object> sendBody = new HashMap<>();sendBody.put("touser", openId);// openIdsendBody.put("url","");// 跳转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@AllArgsConstructorpublic class SendTypeRequest {/*** 推送类型* */private String type;/*** 接收人的openId* */private String openId;}

运行结果

-08-18 一些总结

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

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

.ssl.SSLException: Connection reset

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

errCode: 102002 | errMsg: 请求超时

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。