今天学习了下小程序,感觉与web开发差别不大,如果学过vue react angluar等mvvm框架的话,基本无门槛,只需要熟悉一下微信小程序的开发模式和api即可;
路由有点区别,需要注意下看看API。
本地测试时是没有https的,需要在开发工具右上角详情里勾选不校验..以及HTTPS证书。有时候需要真机测试,但是自己把后台上传到公网太麻烦,这里安利一个内外网穿透工具,直接将本地ip:port映射成一个临时公网地址,虽然这个临时公网地址比较慢,但是足够了,很方便
效果图:
今天学习的demo结构如下
1、app.js
//app.js
App({
onLaunch: function () {
// 展示本地存储能力
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
},
globalData: {
userInfo: null,//用于存储用户信息
token: null//后台用户凭证,一般就是openId或openId+unionId
},
onPageNotFound(res) {
wx.redirectTo({
url: 'pages/notFound/notFound'
}) // 如果是 tabbar 页面,请使用 wx.switchTab
},
serverUrl: 'http://3s.dkys.org:16912/wxapp'
})
2、index
index.wxml
<!--index.wxml-->
<view class="container">
<view wx:if="{{userInfo==null}}">
<button wx:if="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo">授权登录</button>
<view wx:else>请升级微信版本</view>
</view>
<view wx:if="{{userInfo!=null&&token==null}}">
<button bindtap='regist'>请先注册</button>
</view>
<view wx:if="{{userInfo!=null&&token!=null}}" class="userinfo">
<image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
<text class="userinfo-nickname">性别:{{userInfo.gender === 1 ? '男':'女'}}</text>
<text class="userinfo-nickname">城市:{{userInfo.city}}</text>
<text class="userinfo-nickname">省份:{{userInfo.province}}</text>
<text class="userinfo-nickname">国家:{{userInfo.country}}</text>
<text class="userinfo-nickname">使用语言:{{userInfo.language}}</text>
</view>
</view>
index.wxss
/**index.wxss**/
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
}
.userinfo-avatar {
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
.userinfo-nickname {
color: #aaa;
}
index.js
//index.js
//获取应用实例
const app = getApp()
Page({
data: {
userInfo: app.globalData.userInfo,
token: app.globalData.token,
//判断小程序的API,回调,参数,组件等是否在当前版本可用。
canIUse: wx.canIUse('button.open-type.getUserInfo')
},
bindGetUserInfo: function(){
var index = this;
wx.getUserInfo({
success: function (res) {
app.globalData.userInfo = res.userInfo;
index.setData({
userInfo: res.userInfo,
});
index.checkToken();
}
});
},
onLoad: function () {
console.log("index onLoad");
// 查看是否授权
this.checkAuth();
},
regist: function(){
wx.navigateTo({
url: '../regist/regist'
})
},
checkAuth: function(){
console.log("index checkAuth");
var index = this;
wx.getSetting({
success: function (res) {
if (res.authSetting['scope.userInfo']) {
wx.getUserInfo({
success: function (res) {
//用户已经授权过
app.globalData.userInfo = res.userInfo;
index.setData({
userInfo: res.userInfo,
});
index.checkToken();
}
})
}
}
})
},
checkToken: function(){
var index = this;
console.log("index checkToken");
//初次登录需要验证token,即后台查不到这个openId的用户,就表示没有这个用户,跳到注册页面
if (app.globalData.token == "" || app.globalData.token == null) {
//验证用户信息
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
if (res.code) {
app.globalData.code = res.code;
// 发送请求,服务端能获取到openid和unionid,之前登录过则可以获取到之前的用户信息。
wx.request({
url: app.serverUrl + '/login?code=' + res.code, //请求路径
method: "GET",
success: function (res) {
//转到注册
if (res.data == "") {
wx.navigateTo({
url: '../regist/regist'
})
} else {
app.globalData.token = res.data;
index.setData({
token: app.globalData.token
});
}
}
})
}
}
})
} else {
//有token就不管了
console.log("用户验证通过");
this.setData({
token: app.globalData.token,
userInfo: app.globalData.userInfo
});
console.log(app.globalData.token);
console.log(app.globalData.userInfo);
}
},
onShow:function(){
console.log("show index");
}
})
2、regist
regist.wxml
<view wx:if="{{!success}}">
<view class='row'>
<view class='center'>
您还未注册,请先注册!
</view>
<view class='info'>
<input class= 'info-input1' bindinput="handleInputPhone" placeholder="请输入你的手机号" />
</view>
<button class='button' bindtap='doGetCode' disabled='{{disabled}}' style="background-color:{{color}}" >{{text}}</button>
</view>
<view class='row'>
<view class='info'>
<input class= 'info-input' bindinput="handleVerificationCode" placeholder="请输入你的验证码" />
</view>
</view>
<view class='row'>
<view class='info'>
<input type='password' class= 'info-input' bindinput="handleNewChanges" placeholder="请输入你的密码" />
</view>
</view>
<view class='row'>
<view class='info'>
<input type='password' class= 'info-input' bindinput="handleNewChangesAgain" placeholder="请重新输入你的密码" />
</view>
</view>
<button class='submit' bindtap='submit'>提交</button>
</view>
<view class = 'success' wx:if="{{success}}">
<view class='cheer'><icon type="success" size="24"/> 恭喜您注册成功!</view>
<button type = "default" class = 'return' bindtap='return_home'>返回首页</button>
</view>
regist.wxss
page{
background: #F0F0F0 ;
}
.row{
margin-top: 20rpx;
overflow: hidden;
line-height: 100rpx;
border-bottom: 1rpx solid #ccc;
margin-left: 20rpx;
margin-right: 20rpx;
color: #777;
background: #fff;
}
.center{
text-align: center;
background-color: gainsboro
}
.info-input{
height: 100rpx;
margin-left: 50rpx;
color: #777;
float: left;
}
.info-input1{
height: 100rpx;
margin-left: 50rpx;
color: #777;
float: left;
width: 420rpx;
}
.button{
width: 200rpx;
height: 70rpx;
line-height: 70rpx;
font-size: 28rpx;
background: #33FF99;
float: left;
margin-left: 10rpx;
margin-top: 15rpx;
color: #FFFFFF;
}
.submit{
margin-top: 50rpx;
margin-left: 20rpx;
margin-right: 20rpx;
background: #00CCFF;
color: #FFFFFF;
}
.success{
background: #ffffff;
}
.cheer{
text-align: center;
line-height: 400rpx;
font-size: 60rpx;
position: relative;
}
.return{
margin: 20rpx;
}
regist.js
var app = getApp();
Page({
/**
* 页面的初始数据
*/
data: {
text: '获取验证码', //按钮文字
currentTime: 61, //倒计时
disabled: false, //按钮是否禁用
phone: '', //获取到的手机栏中的值
VerificationCode: '',
Code: '',
NewChanges: '',
NewChangesAgain: '',
success: false,
state: ''
},
/**
* 获取验证码
*/
return_home: function (e) {
wx.navigateTo({
url: '../index/index',
})
},
handleInputPhone: function (e) {
this.setData({
phone: e.detail.value
})
},
handleVerificationCode: function (e) {
console.log(e);
this.setData({
Code: e.detail.value
})
},
handleNewChanges: function (e) {
console.log(e);
this.setData({
NewChanges: e.detail.value
})
},
handleNewChangesAgain: function (e) {
console.log(e);
this.setData({
NewChangesAgain: e.detail.value
})
},
doGetCode: function () {
var that = this;
that.setData({
disabled: true, //只要点击了按钮就让按钮禁用 (避免正常情况下多次触发定时器事件)
color: '#ccc',
})
var phone = that.data.phone;
var currentTime = that.data.currentTime //把手机号跟倒计时值变例成js值
var warn = null; //warn为当手机号为空或格式不正确时提示用户的文字,默认为空
var phone = that.data.phone;
var currentTime = that.data.currentTime //把手机号跟倒计时值变例成js值
var warn = null; //warn为当手机号为空或格式不正确时提示用户的文字,默认为空
wx.request({
url: app.serverUrl + '/checkPhoneRegist?phone=' + phone, //后端判断是否已被注册, 已被注册返回1 ,未被注册返回0
method: "GET",
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: function (res) {
that.setData({
state: res.data
})
if (phone == '') {
warn = "号码不能为空";
} else if (phone.trim().length != 11 || !/^1[3|4|5|6|7|8|9]\d{9}$/.test(phone)) {
warn = "手机号格式不正确";
} //手机号已被注册提示信息
else if (that.data.state == 1) { //判断是否被注册
warn = "手机号已被注册";
}
else {
wx.request({
url: app.serverUrl + '/sendCodeMsg?phone=' + phone, //填写发送验证码接口
method: "POST",
data: {
coachid: that.data.phone
},
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: function (res) {
console.log(res.data)
that.setData({
VerificationCode: res.data
})
//当手机号正确的时候提示用户短信验证码已经发送
wx.showToast({
title: '短信验证码已发送',
icon: 'none',
duration: 2000
});
//设置一分钟的倒计时
var interval = setInterval(function () {
currentTime--; //每执行一次让倒计时秒数减一
that.setData({
text: currentTime + 's', //按钮文字变成倒计时对应秒数
})
//如果当秒数小于等于0时 停止计时器 且按钮文字变成重新发送 且按钮变成可用状态 倒计时的秒数也要恢复成默认秒数 即让获取验证码的按钮恢复到初始化状态只改变按钮文字
if (currentTime <= 0) {
clearInterval(interval)
that.setData({
text: '重新发送',
currentTime: 61,
disabled: false,
color: '#33FF99'
})
}
}, 100);
}
})
};
//判断 当提示错误信息文字不为空 即手机号输入有问题时提示用户错误信息 并且提示完之后一定要让按钮为可用状态 因为点击按钮时设置了只要点击了按钮就让按钮禁用的情况
if (warn != null) {
wx.showModal({
title: '提示',
content: warn
})
that.setData({
disabled: false,
color: '#33FF99'
})
return;
}
}
})
},
submit: function (e) {
var that = this
if (this.data.Code == '') {
wx.showToast({
title: '请输入验证码',
image: '/images/error.png',
duration: 2000
})
return
} else if (this.data.Code != this.data.VerificationCode) {
wx.showToast({
title: '验证码错误',
image: '/images/error.png',
duration: 2000
})
return
}
else if (this.data.NewChanges == '') {
wx.showToast({
title: '请输入密码',
image: '/images/error.png',
duration: 2000
})
return
} else if (this.data.NewChangesAgain != this.data.NewChanges) {
wx.showToast({
title: '两次密码不一致',
image: '/images/error.png',
duration: 2000
})
return
} else {
var that = this
var phone = that.data.phone;
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
if (res.code) {
wx.request({
url: app.serverUrl + '/regist',
method: "POST",
data: {
phone: phone,
passwd: that.data.NewChanges,
avatarUrl: app.globalData.userInfo.avatarUrl,
nickName: app.globalData.userInfo.nickName,
gender: app.globalData.userInfo.gender,
city: app.globalData.userInfo.city,
province: app.globalData.userInfo.province,
country: app.globalData.userInfo.country,
language: app.globalData.userInfo.language,
code: res.code
},
header: {
"content-type": "application/x-www-form-urlencoded"
},
success: function (res) {
app.globalData.token = res.data;
wx.showToast({
title: '提交成功~',
icon: 'loading',
duration: 2000
});
that.setData({
success: true
})
}
})
}
}
});
}
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})
后台controller代码
@Slf4j
@RestController
@RequestMapping("/wxapp")
public class WxLoginController extends BaseController {
/**
* 假数据
*/
private static Map<String, WxUser> users = new HashMap<>();
@Value("${weixin.secret.appid}")
private String appid;
@Value("${weixin.secret.key}")
private String key;
/**
* 登录,验证数据库用户信息
* <p>Title: login</p>
* <p>Description: </p>
* @param code
* @return
*/
@RequestMapping("/login")
@ResponseBody
public String login(String code)
{
String url = "https://api.weixin.qq.com/sns/jscode2session?"
+"appid=" + appid
+"&secret=" + key
+"&js_code=" + code
+"&grant_type=authorization_code";
JSONObject object = JSONObject.parseObject(HttpClientUtils.sendGet(url));
log.info(object.toJSONString());
// 请求,获取openid或unionid
// 从数据库中查询是否存储
// 成功获取
String unionid = (String) object.get("unionid");
String openid = (String) object.get("openid");//用户唯一标识
String token = openid+"-" + unionid;
//通过token查找user,找到说明已注册,否则提示请注册
boolean findUser = false;
if(users.get(token)!=null) {
findUser = true;
}
if(findUser) {
return token;
}else {
return "";
}
}
/**
* 判断号码是否已被注册, 已被注册返回1 ,未被注册返回0
* <p>Title: checkPhoneRegist</p>
* <p>Description: </p>
* @param phone
* @return
*/
@RequestMapping("/checkPhoneRegist")
@ResponseBody
public Integer checkPhoneRegist(String phone)
{
Integer result = 0;
log.info("判断号码是否已被注册:"+phone+",结果:"+result);
return result;
}
/**
* 发送验证码
* <p>Title: sendCodeMsg</p>
* <p>Description: </p>
* @param phone
*/
@RequestMapping("/sendCodeMsg")
@ResponseBody
public String sendCodeMsg(String phone)
{
String result = "123456";
log.info("发送验证码到:"+phone+",验证码:"+result);
return result;
}
@RequestMapping("/regist")
@ResponseBody
public String regist(WxUser user)
{
String url = "https://api.weixin.qq.com/sns/jscode2session?"
+"appid=" + appid
+"&secret=" + key
+"&js_code=" + user.getCode()
+"&grant_type=authorization_code";
JSONObject object = JSONObject.parseObject(HttpClientUtils.sendGet(url));
// 请求,获取openid或unionid
// 从数据库中查询是否存储
// 成功获取
String unionid = (String) object.get("unionid");
String openid = (String) object.get("openid");//用户唯一标识
String token = openid+"-" + unionid;
user.setToken(token);
log.info("注册用户:"+user.toString());
users.put(token, user);
return token;
}
}
遇到的问题:
验证用户所发送的code需要实时获取,不然会出现code been used的错误