Web端 前后端分离 - 微信扫码登录方法的实现
前言:
关于这个微信扫码登录,在做这个功能的时候主要是在开发物业系统的时候,要实现所有用户的统一性, 实现通行证理念的基础上开发实现的,关于前后端分离实现微信登录主要分为两个环节,下面我详细的介绍下前后端分离微信登录如何使用
一:实现网页二维码打开
A: 在实现Web端微信扫码登录,首先需要在微信开发者平台中获取对应的Web应用的 AppID 和 AppScert
微信开放平台 :https://open.weixin.qq.com/
B: 实现Url 展示微信二维码:(对应的三个参数 WebAppId WebAppSecret WebRedirectUri 分别是对应的微信AppId 和AppScect 和 回调对应的返回Url)
private static final String WebAppId= "微信AppId";private static final String WebAppSecret = "微信AppScret";//对应的回调路径(域名为 本地 hosts 修改 127.0.0.1 => zlwj.jiajgou.com)private static final String WebRedirectUri = "http://zlwj.jiajgou.com/wx/callback";/*** 获取 web端 微信登录二维码操作* @return*/public static String getWxWebLoginQrUrl(){String state = UUIDUtils.getUuid();String url = "https://open.weixin.qq.com/connect/qrconnect?appid=" + WebAppId + "&redirect_uri=" + WebRedirectUri + "&response_type=code&scope=snsapi_login&state=" + state;return url;}
回调之后获取对应的微信 OpenId 以及可以使用的 UnionId 以及其他信息
@RestController
@RequestMapping("/wx")
public class WxController {private static final String WebAppId= "微信对应的AppId";private static final String WebAppSecret = "微信对应的AppSerct";private static final String grantType = "authorization_code";private static final String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";private static final String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo";@RequestMapping(value = "/callback")public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception{String code = request.getParameter("code");String state = request.getParameter("state");if(code != null){StringBuffer url = new StringBuffer();url.append(requestUrl).append("?appid=").append(WebAppId).append("&secret=").append(WebAppSecret).append("&code=").append(code).append("&grant_type=").append(grantType);System.out.println("获取tokenUrl:" + url.toString());JSONObject jsonObject =JSON.parseObject(HttpClientUtil.doGet(url.toString()));System.out.println("token:" + jsonObject.toString());//拿到openid和请求微信api使用的tokenString openid =jsonObject.get("openid").toString();String token = jsonObject.get("access_token").toString();url = new StringBuffer();url.append(userInfoUrl).append("?access_token=").append(token).append("&openid=").append(openid);System.out.println("获取用户信息:" + url);JSONObject userInfoJSON =JSON.parseObject(HttpClientUtil.doGet(url.toString()));System.out.println(userInfoJSON.toJSONString());response.sendRedirect("https://www.jianshu.com/");}}}
二:在回调之后这个时候如何实现后端通知前端实现动态展示:
在这里我推荐使用 WebSockt 实现前后端通讯,通讯结束之后就结束对应的 长连接 已达到节省资源,当然,如果有存在需要这个长连接就任做其他的处理,接下来就说说 Netty WebSockt 实现
注: 在此 Netty 实现 WebSockt 时做了一个小封装 , 封装代码如下:
1: NettyServer
public class NettyServer
{protected static final int BIZGROUPSIZE = Runtime.getRuntime().availableProcessors() * 2;protected static final int BIZTHREADSIZE = 4;private Server server;private int port;public NettyServer(Server server, Integer port) {this.server = server;this.port = port;}@OperLog(operModul = "启动Netty服务器方法",operType = "run",operDesc = "启动Netty服务器方法")public void run() throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(BIZGROUPSIZE);NioEventLoopGroup workerGroup = new NioEventLoopGroup(4);try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup, workerGroup);serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);serverBootstrap.childHandler(this.server);ChannelFuture channelFuture = serverBootstrap.bind(this.port).sync();channelFuture.channel().closeFuture().sync();} catch (Exception var8) {var8.printStackTrace();throw var8;} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}} } 2:NettyServerHandler
public class NettyServerHandler
extends SimpleChannelInboundHandler 3:NettyServerInitializer
public class NettyServerInitializer
extends ChannelInitializer implements PipelineCallBack {private Handler handler;public NettyServerInitializer(Handler handler, ServletContext context) {handler.setContext(context);this.handler = handler;}@OperLog(operModul = "netty编码解码处理以及添加handler处理",operType = "handler",operDesc = "netty编码解码处理以及添加handler处理")public void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();this.channelPipeline(pipeline);pipeline.addLast(new ChannelHandler[]{this.handler});}public void channelPipeline(ChannelPipeline pipeline) {} } 4:NettyCallBack
public interface NettyCallBack {void read(ChannelHandlerContext ctx, Object msg, ServletContext context) throws Exception;void readComplete(ChannelHandlerContext ctx, ServletContext context) throws Exception;void active(ChannelHandlerContext ctx, ServletContext context) throws Exception;void exc(ChannelHandlerContext ctx, Throwable cause, ServletContext context) throws Exception; }
ThreadPoolUtil 开启多线程线程池:
public class ThreadPoolUtil {private static volatile ExecutorService executorService;private ThreadPoolUtil() {}public static ExecutorService getExecutorService() {if (executorService == null) {Class var0 = ThreadPoolUtil.class;synchronized(ThreadPoolUtil.class) {ThreadFactory namedThreadFactory = (new ThreadFactoryBuilder()).setNameFormat("netty-pool-%d").build();executorService = new ThreadPoolExecutor(10, 20, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(1024), namedThreadFactory, new AbortPolicy());}}return executorService;} }
Netty WebSockt 实现
- 通过 @WebListener 实现 Netty 服务监听
/*** netty服务监听器 .
* @author xbin*/ @WebListener public class NettyListener implements ServletContextListener {private Logger log = LoggerFactory.getLogger(getClass());private Mapmap = new HashMap<>();ServletContext context;@Overridepublic void contextInitialized(ServletContextEvent sce) {//获取上下文context = sce.getServletContext();ExecutorService executorService= ThreadPoolUtil.getExecutorService();executorService.execute(new WebServerThread());}@Overridepublic void contextDestroyed(ServletContextEvent sce) {}/*** 云+控制器 服务启动线程 C 版本 .
* @author hkb*/private class WebServerThread implements Runnable {@Overridepublic void run(){try {NettyServer nettyServer = new NettyServer(new WebServerInitializer(new WebHandler(), context), 4960);nettyServer.run();}catch (Exception e){log.info("启动服务失败");}}}} - 是先 Handler 通道逻辑处理
/*** @Sharable 表示它可以被多个channel安全地共享 C 版本* @author xiaobin*/ @ChannelHandler.Sharable public class WebHandler extends NettyServerHandler implements NettyCallBack {private WebSocketServerHandshaker handshaker;@Overridepublic void read(ChannelHandlerContext ctx, Object msg, ServletContext context) throws Exception {try {if (msg instanceof FullHttpRequest){//以http请求形式接入,但是走的是websockethandleHttpRequest(ctx, (FullHttpRequest) msg);}else if (msg instanceof WebSocketFrame){//处理websocket客户端的消息handlerWebSocketFrame(ctx, (WebSocketFrame) msg);}}catch (Exception e){e.printStackTrace();throw e;}}private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception{// 判断是否关闭链路的指令if (frame instanceof CloseWebSocketFrame) {handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());return;}// 判断是否ping消息if (frame instanceof PingWebSocketFrame) {ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));return;}// 本例程仅支持文本消息,不支持二进制消息if (!(frame instanceof TextWebSocketFrame)) {System.out.println("本例程仅支持文本消息,不支持二进制消息");throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass().getName()));}// 返回应答消息String request = ((TextWebSocketFrame) frame).text();JSONObject jsonObject = JSON.parseObject(request);WebUtil.cmdXlt(ctx, jsonObject);}/*** 唯一的一次http请求,用于创建websocket* */private void handleHttpRequest(ChannelHandlerContext ctx,FullHttpRequest req) {InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();String ip = insocket.getAddress().getHostAddress();int port = insocket.getPort();//要求Upgrade为websocket,过滤掉get/Postif (!req.decoderResult().isSuccess()|| (!"websocket".equals(req.headers().get("Upgrade")))) {//若不是websocket方式,则创建BAD_REQUEST的req,返回给客户端sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));return;}WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://"+ ip +":" + port + "/xbin/xlt", null, false);handshaker = wsFactory.newHandshaker(req);if (handshaker == null) {WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());} else {handshaker.handshake(ctx.channel(), req);}}/*** 拒绝不合法的请求,并返回错误信息* */private static void sendHttpResponse(ChannelHandlerContext ctx,FullHttpRequest req, DefaultFullHttpResponse res) {// 返回应答给客户端if (res.status().code() != 200) {ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(),CharsetUtil.UTF_8);res.content().writeBytes(buf);buf.release();}ChannelFuture f = ctx.channel().writeAndFlush(res);// 如果是非Keep-Alive,关闭连接 !isKeepAlive(req)if (res.status().code() != 200) {f.addListener(ChannelFutureListener.CLOSE);}}@Overridepublic void readComplete(ChannelHandlerContext ctx, ServletContext context) throws Exception {}@Overridepublic void active(ChannelHandlerContext ctx, ServletContext context) throws Exception {InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();String clientIp = insocket.getAddress().getHostAddress();ChannelSupervise.addChannel(ctx.channel());System.out.println("client -- [ip:" + clientIp + "]-- online");}@Overridepublic void exc(ChannelHandlerContext ctx, Throwable cause, ServletContext context) throws Exception {System.out.println("------------------error-----------------------" + cause + "---" + "ctx:" + ctx);ChannelSupervise.removeChannel(ctx.channel());} }
import javax.servlet.ServletContext;public class WebServerInitializer extends NettyServerInitializer implements PipelineCallBack {public WebServerInitializer(NettyServerHandler handler, ServletContext context) {super(handler, context);}@Overridepublic void channelPipeline(ChannelPipeline pipeline) {//设置log监听器,并且日志级别为debug,方便观察运行流程pipeline.addLast("logging",new LoggingHandler("DEBUG"));//设置解码器pipeline.addLast("http-codec",new HttpServerCodec());//聚合器,使用websocket会用到pipeline.addLast("aggregator",new HttpObjectAggregator(65536));//用于大数据的分区传输pipeline.addLast("http-chunked",new ChunkedWriteHandler());}}
public class WebUtil {public static void cmdXlt(ChannelHandlerContext ctx, JSONObject jsonObject) throws Exception{// 收到关于微信登录请求 { "cmd":"wx_login_qr_request" , "status":"1", "data":""}// 正常回复前端对应url 微信登录请求 { "cmd":"wx_login_qr_repose", "status":"1", "data":"" }String cmd = jsonObject.getString("cmd");switch (cmd){case "wx_login_qr_request":break;default:break;}}}
至此就可以实现 Netty WebSockt 通行, 通讯的协议规则可以自己自定义,按照具体的需求来进行处理。
标签:
相关文章
-
无相关信息