一、需求描述
用户在PC端用微信扫描二维码实现后台登录
图示:
二、实现原理
此处采用socket实现,当然也可以通过轮询去监测微信扫码状态
三、实现步骤
1. 微信公众平台小程序后台申请跳转链接
开发管理->开发设置->扫普通链接二维码打开小程序
2. PC请求websocket连接获取二维码key
客户端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--登录界面-->
<img id="qrCode" src="" alt="扫码">
</body>
</html>
<script>
let socket;
let timer;
if (typeof WebSocket === "undefined") {
alert("您的浏览器不支持socket");
} else {
// 实例化socket
socket = new WebSocket(
"wss://ip:port/platform-system/socket/wx/qrLogin");
// 监听socket连接
socket.onopen = () => {
console.log('连接成功.......')
//模拟心跳
timer = setInterval(() => {
socket.send(1)
}, 30000)
};
// 监听socket错误信息
socket.onerror = () => {
console.log('连接发生错误.......')
};
// 监听socket消息
socket.onmessage = ({data}) => {
//判断状态
//此处0代表刚建立连接,返回来的是二维码key
if (data.code === 0) {
//请求服务端获取二维码
document.querySelector('#qrCode').src = `https://ip:port/platform-system/auth/wx/miniprogram/qrCode?pcKey=${data.data}`
} else if (data.code === 1) {
//关闭定时器
clearInterval(timer)
//关闭socket
socket.close()
//此处1代表登录成功
location.href = '后台主页'
} else {
//其他情况代表小程序登录出现问题
console.log(`登录出错:${data.msg}`)
//关闭定时器
clearInterval(timer)
//关闭socket
socket.close()
}
};
}
</script>
服务端:
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
public class JsonWebSocketEncoder implements Encoder.Text<Result> {
@Override
public String encode(Result object) throws EncodeException {
try {
return JSONUtil.toJsonStr(object);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void init(EndpointConfig endpointConfig) {
}
@Override
public void destroy() {
}
}
@Slf4j
@Component
@ServerEndpoint(value = "/socket/wx/qrLogin", encoders = JsonWebSocketEncoder.class)
public class WxQrLoginWebSocket {
public static final ConcurrentMap<String, Session> sockets = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {
log.info("客户端连接成功:{}", session.getId());
//将连接结果告诉客户端用于携带进入小程序(此处直接用的雪花算法,可使用其他方式生成key)
String key = IdUtil.getSnowflake().nextIdStr();
//存放session
sockets.put(key, session);
log.info("当前在线客户端:{}个", sockets.size());
//通知客户端
sendMessage(key, Result.result("0", null, key));
}
/**
* 链接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
try {
sockets.entrySet().removeIf(entry -> session.getId().equals(entry.getValue().getId()));
log.info("【websocket消息】连接断开,sessionId:" + session.getId());
log.info("当前在线客户端:{}个", sockets.size());
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message
* @param session
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("【websocket消息】收到客户端心跳:{},{}", message, session.getId());
log.info("当前在线客户端:{}个", sockets.size());
}
/**
* 发送错误时的处理
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
try {
//打印错误
log.error("用户错误,原因:" + error.getMessage());
error.printStackTrace();
//关闭链接
session.close();
sockets.entrySet().removeIf(entry -> session.getId().equals(entry.getValue().getId()));
log.info("当前在线客户端:{}个", sockets.size());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 消息
*
* @param key key
* @param data 数据
**/
public static void sendMessage(String key, Object data) {
sockets.get(key).getAsyncRemote().sendObject(data);
}
}
3. 小程序获取key并授权登录
客户端:
获取key
onLoad(option) { if(option.q){ //微信扫描扫描二维码进来的 let url = decodeURIComponent(option.q) let params = this.getUrlParam(url) console.log('从微信扫码过来params') //此处需要存储这个pcKey后面会用到 console.log(params.pcKey) } } getUrlParam(url){ let params = url.split("?")[1].split("&"); let obj = {}; params.map(v => (obj[v.split("=")[0]] = v.split("=")[1])); return obj }
请求服务端登录并通知PC端
wx.request({ url: 'https://ip:port/...', //仅为示例,并非真实的接口地址 method: 'POST', header: { 'content-type': 'application/x-www-form-urlencoded' }, data: { username: 'username', password: 'password' }, success: function (res) { //获取token var token = res.data.accessToken; //通知服务端告诉PC登录 wx.request({ url: 'https://ip:port/auth/notifyPcLogin', //仅为示例,并非真实的接口地址 method: 'GET', header: { 'Authorization': token }, data: { pcKey: '之前存储的key' }, success: function (res) { console.log(res.data) } }) } })
服务端:
@RestController @RequestMapping("/auth") @RequiredArgsConstructor public class LoginController { private final WxLoginService wxLoginService; @ApiOperation(value = "通知PC登录接口") @GetMapping("/notifyPcLogin") public Result<Boolean> notifyPcLogin( @ApiParam(name = "pcKey", value = "PC端后端给的唯一标识", required = true) @NotBlank(message = "唯一标识不能为空") @RequestParam(value = "pcKey") String pcKey ) { return Result.success(wxLoginService.notifyPcLogin(pcKey)); } }
@Service @RequiredArgsConstructor public class WxLoginService { private final ISysUserService sysUserService; /** * 唤醒PC登录 * * @param pcKey pc唯一key * @return java.lang.Boolean * @author Guo Shuai * @since 1.0 **/ public Boolean notifyPcLogin(String pcKey) { //获取socket session Session session = WxQrLoginWebSocket.sockets.get(pcKey); //判断是否存在 if (ObjectUtil.isNull(session)) { return Boolean.FALSE; } //获取当前登录用户手机号 String phone = sessionService.getLoginUser().getPhone(); //通过手机号查找用户 SysUser sysUser = sysUserService.lambdaQuery().eq(SysUser::getMobile, phone).one(); //获取用户pc端token LoginUserVO loginUserVO = sysUserService.getUserToken(sysUser, sysUser.getMobile(), sessionService.getTenantId(), "10"); //通知pc登录 WxQrLoginWebSocket.sendMessage(pcKey, Result.result("1", null, loginUserVO)); //返回 return Boolean.FALSE; } }
至此整个流程结束
四、注意事项
小程序申请的跳转链接只有正式版可以携带自定义的动态参数,否则只能用配置的测试地址,测试地址可以配置最多5个
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。