微信小程序的开发(非云服务)
开发背景
由于项目需要实现手机端推送消息的的功能,ios和安卓的开发学习成本太高了(尤其是安卓每年一个大版本,想想就脑瓜子疼),所以就想到了最近比较火的微信小程序,看了小程序的开发文档有订阅消息的功能,并且小程序免安装,跨平台,学习成本低(emmmmmmmmm),马上开干!!!
前期准备
- 有公网IP的服务器(我用的是腾讯云)
- ICP备过案的域名
- SSL证书(为了安全性,小程序的请求只支持https)
- 在微信公众平台注册一个小程序账号( https://mp.weixin.qq.com/ )
开发过程
第一步:环境的搭建
-
我是用Java开发的后台服务,所以我安装了JDK1.8和Tomcat8
启动一下tomcat,用自己的域名加8080端口看看能不能访问,如果能就表示域名可以解析到了!
-
考虑到后期会有登陆模块,所以还需要有redis做个缓存,存一下用户的session
第二步:下载并安装微信开发者工具
下载地址: https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
第三步:新建一个小程序工程
新建的时候会要求填写AppID,这个可以在微信公众平台的设置(设置—基本设置—账号信息)里找到
找到AppID新建一个小程序的工程
如果需要数据存储或者文件服务等可以选择云开发,因为我只是需要消息推送的功能,所以我我选择不使用云服务
看这个项目结构是不是跟vue很像,做过vue的同学应该上手很快,没做过的也没事,直接看微信公众平台的开发者文档 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/launchApp.html ,写的很详细!
第四步:小程序配置
小程序根目录下的 app.json文件用来对微信小程序进行全局配置。文件内容为一个 JSON 对象 ,可以参照开发者文档里的配置项根据自己的需求进行配置,其中最重要的就是pages是路由的配置
第五步:开发小程序页面
小程序有自己的dom节点,如果直接用html的dom节点,会识别不了,所以还是要查看开发者文档的组件,找到自己所需的节点。具体的数据流语法可以参照vue,常用的模板取值方式是{{}}。
小程序相关代码
app.js:
//app.js
App({
globalData: {
openid: "",
userInfo: null,
},
onLaunch: function () {
var that = this
// 展示本地存储能力
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
// 登录
wx.login({
success: res => {
var d = that.globalData
var l = 'https://www.songguopeng.com/getOpenId'
wx.request({
url: l,
data: {
code: res.code
},
method: 'GET',
success: function (res) {
console.log(res.data)
that.globalData.openid = res.data
}
})
}
})
// 获取用户信息
wx.getSetting({
success: res => {
if (res.authSetting['scope.userInfo']) {
// 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
wx.getUserInfo({
success: res => {
// 可以将 res 发送给后台解码出 unionId
this.globalData.userInfo = res.userInfo
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
if (this.userInfoReadyCallback) {
this.userInfoReadyCallback(res)
}
}
})
}
}
})
}
})
index.js
//index.js
//获取应用实例
const app = getApp()
Page({
data: {
motto: '欢迎使用perceive消息推送平台',
userInfo: {},
hasUserInfo: false,
canIUse: wx.canIUse('button.open-type.getUserInfo')
},
//事件处理函数
bindViewTap: function() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad: function () {
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo,
hasUserInfo: true
})
} else if (this.data.canIUse){
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
app.userInfoReadyCallback = res => {
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
} else {
// 在没有 open-type=getUserInfo 版本的兼容处理
wx.getUserInfo({
success: res => {
app.globalData.userInfo = res.userInfo
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
})
}
},
getUserInfo: function(e) {
console.log(e)
app.globalData.userInfo = e.detail.userInfo
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
},
bindService: function() {
wx.requestSubscribeMessage({
tmplIds: ['T6pW1mrK0V3voAL7JcMS-jboWAIxs8eCxMnYDJOj9u8'],
success(res) {
console.log("授权成功")
wx.request({
url: 'http://songguopeng.viphk.ngrok.org/push',
method: 'get',
data: {
openid: app.globalData.openid
},
success(res) {
}
})
},
fail(res){
console.log("授权失败")
}
})
}
})
index.wxml
<!--index.wxml-->
<view class="container">
<view class="userinfo">
<button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
<block wx:else>
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</block>
</view>
<view class="usermotto">
<button class="button" form-type='submit' type='primary' bindtap='bindService'>
<text>绑定perceive消息推送</text>
</button>
</view>
<view class="usermotto">
<text class="user-motto">{{motto}}</text>
</view>
</view>
第六步:开发服务器
由于需要进行订阅消息的推送,我们需要开发自己的服务平台进行消息的推送,微信提供了相关接口,我们只需要遵循他的请求形式去请求就可以,话不多说,直接上代码:
- 首先先获取openid,openid获取到了我们就相当于获取到了这个微信小程序中该用户的唯一标识id,一切的操作都依赖于这个openid
@RestController
public class OpenIdController {
public static final String URL_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=CODE&grant_type=authorization_code";
@GetMapping(value = "getOpenId")
public String getOpenId(@RequestParam(value = "code")String code){
String url = URL_ACCESS_TOKEN.replace("APPID", WeChatConfig.APP_ID).replace("SECRET", WeChatConfig.SECRET_ID).replace("CODE", code);
JSONObject jsonObject = UrlUtil.httpsRequest(url,"GET",null);
String openid = jsonObject.getString("openid");
return openid;
}
}
- 然后拿到openid我们就可以遵循微信小程序的接口去给这个用户发送订阅消息了(需要用户授权才能发送,授权的过程在小程序代码,授权一次就有一次的发送机会,机会可以叠加,暂时不支持永久授权)
@RestController
public class PushController {
@GetMapping("/push")
public static String push(@RequestParam("openid")String openid) {
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
+ WeChatConfig.APP_ID
+ "&secret="
+ WeChatConfig.SECRET_ID;
String result = HttpUtil.sendGet(url);
JSONObject object = JSON.parseObject(result);
String Access_Token = object.getString("access_token");
Template template = new Template();
template.setTemplate_id(WeChatConfig.TEMPLATE_ID);
template.setTouser(openid);
template.setPage("pages/index/index");
List<TemplateParam> paras = new ArrayList<>();
paras.add(new TemplateParam("time1", "2019-12-28 10:00:00"));
paras.add(new TemplateParam("thing2", "监控1发现2人"));
template.setTemplateParamList(paras);
String requestUrl = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN";
requestUrl = requestUrl.replace("ACCESS_TOKEN", Access_Token);
System.out.println(template.toJSON());
net.sf.json.JSONObject jsonResult = UrlUtil.httpsRequest(requestUrl, "POST", template.toJSON());
if (jsonResult != null) {
System.out.println(jsonResult);
int errorCode = jsonResult.getInt("errcode");
String errorMessage = jsonResult.getString("errmsg");
if (errorCode == 0) {
System.out.println("Send Success");
} else {
System.out.println("订阅消息发送失败:" + errorCode + "," + errorMessage);
}
}
return null;
}
}
- 模板消息就不要用了,2020年1月10日就没有模板消息这个功能了,所以用订阅消息去给用户发消息
效果图
如果有绑定微信服务器的需要可以用这段代码(一般公众号需要绑定服务端)
/**
* 微信小程序的后台服务绑定
*/
@RestController
public class WeChatBindController {
public static final String TOKEN = "beiyang";
/**
* 微信授权验证
*
* @param signature
* @param timestamp
* @param nonce
* @param echostr
* @return
* @author songgp
*/
@GetMapping(value = "/authWeChat")
public String authWeChat(@RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr) {
//排序
String[] arr = {TOKEN, timestamp, nonce};
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
//sha1Hex 加密
MessageDigest md = null;
String temp = null;
try {
md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(content.toString().getBytes());
temp = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
if ((temp.toLowerCase()).equals(signature)) {
return echostr;
}
return null;
}
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
private static String byteToHexStr(byte mByte) {
char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
}