package com.ckm.ball.utils;
import java.util.Base64;
import java.util.Date;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
public class JwtUtilChat {
private static final String SECRET_KEY = "secret"; // 秘钥
private static final long VALID_TIME = 30 * 60 * 1000; // 有效时间30分钟
/**
* 生成Token
* @param openId 用户唯一标识
* @return Token
*/
public static String generateToken(String openId) {
Date now = new Date();
Date expireTime = new Date(now.getTime() + VALID_TIME);
byte[] secretBytes = Base64.getEncoder().encode(SECRET_KEY.getBytes());
JwtBuilder builder = Jwts.builder()
.setId(openId)
.setIssuedAt(now)
.setExpiration(expireTime)
.setSubject("jwt")
.signWith(SignatureAlgorithm.HS256, secretBytes);
return builder.compact();
}
/**
* 更新Token
* @param token 原始Token
* @return 新的Token
* @throws SignatureException 无效Token异常
*/
public static String refreshToken(String token) throws SignatureException {
Claims claims = Jwts.parser()
.setSigningKey(Base64.getEncoder().encode(SECRET_KEY.getBytes()))
.parseClaimsJws(token)
.getBody();
Date now = new Date();
Date expireTime = new Date(now.getTime() + VALID_TIME);
JwtBuilder builder = Jwts.builder()
.setId(claims.getId())
.setIssuedAt(now)
.setExpiration(expireTime)
.setSubject("jwt")
.signWith(SignatureAlgorithm.HS256, Base64.getEncoder().encode(SECRET_KEY.getBytes()));
return builder.compact();
}
/**
* 验证Token是否过期 如果返回false则代表token没有过期,如果返回true则代表token已经过期
* @param token 要验证的Token
* @return 是否过期
* @throws SignatureException 无效Token异常
*/
public static boolean isTokenExpired(String token) throws SignatureException {
Claims claims = Jwts.parser()
.setSigningKey(Base64.getEncoder().encode(SECRET_KEY.getBytes()))
.parseClaimsJws(token)
.getBody();
return claims.getExpiration().before(new Date());
}
}
微信小程序js代码
微信小程序点击登录按钮调用该方法
login() {
let that = this
//调用wx登录接口获取openId
wx.login({
success: (res) => {
//调用getUserCode获取openId
getUserCode(res.code).then((data) => {
//生成Token
userLogin(data.openid).then((result) => {
that.setData({
userInfo: result
})
//保存到缓存中,后续做判断
wx.setStorageSync('userInfo', result)
wx.showLoading()
setTimeout(function () {
wx.hideLoading()
wx.navigateBack({
delta: 0,
})
}, 500)
})
})
},
})
},
java后端代码
@ApiOperation(value = "获取用户openId",tags = "用户接口")
@GetMapping(value="/getUserCode")
public JSONObject getUserCode(@RequestParam("code") String code){
System.out.println(code);
JSONObject jsonObject = new JSONObject();
try{
if(code == null || code.equals("")){
jsonObject.put("errcode","10001");
jsonObject.put("errmsg","数据传输错误,code为空");
return jsonObject;
}
Map<String, String> data = new HashMap<String, String>();
data.put("appid", "wx4ab3xxxxxxxx52b");
data.put("secret", "33ae24be19xxxxxxxxxx77b990");
data.put("js_code", code);
data.put("grant_type", "authorization_code");
String response = HttpRequest.get("https://api.weixin.qq.com/sns/jscode2session").form(data).body();
jsonObject = JSON.parseObject(response);
jsonObject.remove("session_key"); //删除session_key 避免泄露用户信息
}catch (Exception ex){
jsonObject.put("errcode","10004");
jsonObject.put("errmsg","获取失败,发生未知错误");
}
System.out.println(jsonObject);
return jsonObject;
}
@GetMapping("/userLogin")
@ApiOperation(value = "用户登录",tags = "用户接口")
public BallUserVo userLogin(@RequestParam("openId") String openId){
return ballUserService.userLogin(openId);
}
userLogin实现类方法
@Override
public BallUserVo userLogin(String openId) {
//根据openId生成Token
String token = JwtUtilChat.generateToken(openId);
BallUser ballUser = this.getOne(new LambdaQueryWrapper<BallUser>().eq(BallUser::getOpenId, openId));
//没有记录,首次授权登录
if (ObjectUtils.isEmpty(ballUser)){
BallUser newBallUser = new BallUser();
newBallUser.setCreateTime(new Date());
newBallUser.setAvatarUrl(avatarUrl);
newBallUser.setNickName("微信用户"+RandomStringGenerator.generateRandomString());
newBallUser.setOpenId(openId);
this.save(newBallUser);
BallUserVo ballUserVo = new BallUserVo();
//token赋值
ballUserVo.setToken(token);
BeanUtils.copyProperties(newBallUser,ballUserVo);
return ballUserVo;
}
BallUserVo ballUserVo = new BallUserVo();
//token赋值
ballUserVo.setToken(token);
BeanUtils.copyProperties(ballUser,ballUserVo);
return ballUserVo;
}
封装了wx的request请求,每次发起请求的时候都走一遍更新Token的接口/user/updateTokenTime,如果接口返回offline则代表已经过期,则跳转到登录页面,否则就是没有过期,则更新缓存userInfo中的token。
微信小程序js代码
//经过token验证
function request(options) {
let userInfo = wx.getStorageSync('userInfo');
if (userInfo == null || userInfo == undefined || userInfo == '') {
const userInfoNologin = {
avatarUrl: "/images/nologin.png",
nickName: "授权登录"
};
wx.setStorageSync('userInfo', userInfoNologin);
console.log("请登录!");
wx.navigateTo({
url: '/pages/login/login',
});
return Promise.reject("用户未登录");
} else {
const API_ROOT = "http://192.168.1.2:8088";
return new Promise((resolve, reject) => {
refreshToken(API_ROOT, userInfo)
.then(() => {
wx.request({
...options,
url: API_ROOT + options.url,
success: function (res) {
if (res.statusCode === 200) {
resolve(res.data);
} else {
reject(res.errMsg);
}
},
fail: function (err) {
reject(err.errMsg);
},
});
})
.catch(error => {
reject(error);
});
});
}
}
// 更新 Token
function refreshToken(url, userInfo) {
return new Promise((resolve, reject) => {
wx.request({
url: url + "/user/updateTokenTime",
method: 'GET',
data: { 'token': userInfo.token },
success: res => {
if (res.data == "offline") {
const userInfoNologin = {
avatarUrl: "/images/nologin.png",
nickName: "授权登录"
};
wx.setStorageSync('userInfo', userInfoNologin);
wx.navigateTo({
url: '/pages/login/login',
});
reject("用户已离线");
} else {
userInfo.token = res.data;
wx.setStorageSync('userInfo', userInfo);
resolve();
}
},
fail: error => {
console.log(error);
reject(error);
}
});
});
}
export default request;
java后端代码
@GetMapping("/updateTokenTime")
@ApiOperation(value = "更新token",tags = "用户接口")
public String updateTokenTime(@RequestParam("token") String token){
return ballUserService.updateTokenTime(token);
}
updateTokenTime实现类方法
@Override
public String updateTokenTime(String token) {
// 判断token是否为空
if (StringUtils.isEmpty(token)) {
// Token为空,则返回未认证错误
return "offline";
}
try {
//判断token是否过期,返回false则代表没有过期,反正true则代表过期
boolean flag = JwtUtilChat.isTokenExpired(token);
if (!flag){
// 更新token
return JwtUtilChat.refreshToken(token);
}else{
return "offline";
}
}catch (Exception e){
//异常代表token已经过期
return "offline";
}
}
如果你想有一些接口不要经过这个request封装,就比如用户没有登录是可以进入到首页等页面的,开放一些允许用户没有登录状态可以访问的接口。我们可以再封装一个request,专门开放接口不经过Token验证。
//不经过token验证
function onlineRequest(options) {
const API_ROOT = "http://192.168.1.2:8088";
return new Promise((resolve, reject) => {
wx.request({
...options,
url: API_ROOT + options.url, // 在请求的 URL 前加上 API_ROOT
success: function (res) {
if (res.statusCode === 200) {
resolve(res.data);
} else {
reject(res.errMsg);
}
},
fail: function (err) {
reject(err.errMsg);
},
});
});
}
export default onlineRequest;
API怎么编写?
import request from "../../utils/request"; //经过token的封装
import onlineRequest from "../../utils/onlineRequest"; //不经过token的封装
//用户没有登录,但是可以访问这个接口
export function getBallCourtInfoByPoint(lat,lng) {
return onlineRequest({
url: '/court/getBallCourtInfoByPoint',
method: 'get',
data:{
lat,
lng
}
});
}
//用户必须登录才能访问这个接口
export function getBallCourtInfoByPointById(id) {
return request({
url: '/court/getBallCourtInfoByPointById',
method: 'get',
data:{id}
});
}
接口怎么调用?
import {
getBallCourtInfoByPoint,
getBallCourtInfoByPointById
} from "../../../api/home/home.js";
getBallCourtInfoByPoint(res){
let that = this
//该方法不经过Token验证
getBallCourtInfoByPoint(res.latitude, res.longitude).then((res) => {
res.forEach(element => {
element['id'] = Number(element.id)
element['markerId'] = Number(element.id)
element['title'] = element.ballCourtName
element['width'] = 45
element['height'] = 45
element['latitude'] = Number(element.lat)
element['longitude'] = Number(element.lng)
element['lat'] = Number(element.lat)
element['lng'] = Number(element.lng)
element['iconPath'] = "/images/ball.png"
});
that.setData({
markers: res
})
});
},
onMarkerTap(e) {
let that = this
console.log(e);
//该方法经过Token验证
getBallCourtInfoByPointById(e.detail.markerId).then((res) => {
that.setData({
showPopup: true,
ballCourtInfo: res
})
})
}