素材巴巴 > 程序开发 >

Web端 前后端分离 - 微信扫码登录方法的实现

程序开发 2023-09-03 23:50:50

前言:

      关于这个微信扫码登录,在做这个功能的时候主要是在开发物业系统的时候,要实现所有用户的统一性, 实现通行证理念的基础上开发实现的,关于前后端分离实现微信登录主要分为两个环节,下面我详细的介绍下前后端分离微信登录如何使用

 

一:实现网页二维码打开

        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 implements NettyCallBack {private static final Logger log = LoggerFactory.getLogger(NettyServerHandler.class);private ServletContext context;public NettyServerHandler() {}public ServletContext getContext() {return this.context;}public void setContext(ServletContext context) {this.context = context;}protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {try {this.read(ctx, msg, this.getContext());} catch (Exception var4) {throw var4;}}public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {try {this.readComplete(ctx, this.getContext());ctx.flush();} catch (Exception var3) {ctx.flush();throw var3;}}public void channelActive(ChannelHandlerContext ctx) throws Exception {try {this.active(ctx, this.getContext());} catch (Exception var3) {throw var3;}}public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {try {this.exc(ctx, cause, this.getContext());closeChannel(ctx.channel());} catch (Exception var4) {closeChannel(ctx.channel());}}public static void closeChannel(Channel channel) {final String addrRemote = parseChannelRemoteAddr(channel);channel.close().addListener(new ChannelFutureListener() {public void operationComplete(ChannelFuture future) throws Exception {NettyServerHandler.log.info("closeChannel: close the connection to remote address[{}] result: {}", addrRemote, future.isSuccess());}});}public static String parseChannelRemoteAddr(final Channel channel) {if (null == channel) {return "";} else {SocketAddress remote = channel.remoteAddress();String addr = remote != null ? remote.toString() : "";if (addr.length() > 0) {int index = addr.lastIndexOf("/");return index >= 0 ? addr.substring(index + 1) : addr;} else {return "";}}}public void read(ChannelHandlerContext ctx, Object msg, ServletContext context) throws Exception {}public void readComplete(ChannelHandlerContext ctx, ServletContext context) throws Exception {}public void active(ChannelHandlerContext ctx, ServletContext context) throws Exception {}public void exc(ChannelHandlerContext ctx, Throwable cause, ServletContext context) throws Exception {}
 } 

    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 实现

  1. 通过 @WebListener 实现 Netty 服务监听
    
     /*** netty服务监听器 . 
    * @author xbin*/ @WebListener public class NettyListener implements ServletContextListener {private Logger log = LoggerFactory.getLogger(getClass());private Map map = 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("启动服务失败");}}}}
  2. 是先 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 通行, 通讯的协议规则可以自己自定义,按照具体的需求来进行处理。


标签:

上一篇: IDEA 编辑配置运行 下一篇:
素材巴巴 Copyright © 2013-2021 http://www.sucaibaba.com/. Some Rights Reserved. 备案号:备案中。