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使用的token
            String 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<Server extends NettyServerInitializer> {
    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<T extends NettyServerHandler> extends SimpleChannelInboundHandler<Object> 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<Handler extends NettyServerHandler> extends ChannelInitializer<SocketChannel> 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服务监听器 . <br>
     * @author xbin
     */
    @WebListener
    public class NettyListener implements ServletContextListener {
    
        private Logger log = LoggerFactory.getLogger(getClass());
    
        private Map<String, ChannelHandlerContext> map = new HashMap<>();
    
        ServletContext context;
    
        @Override
        public void contextInitialized(ServletContextEvent sce) {
    
            //获取上下文
            context = sce.getServletContext();
    
            ExecutorService executorService= ThreadPoolUtil.getExecutorService();
            executorService.execute(new WebServerThread());
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
    
        }
    
        /**
         *  云+控制器 服务启动线程 C 版本 . <br>
         * @author hkb
         */
        private class WebServerThread implements Runnable {
    
            @Override
            public void run(){
                try {
                    NettyServer nettyServer = new NettyServer<WebServerInitializer>(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;
    
        @Override
        public void read(ChannelHandlerContext ctx, Object msg, ServletContext context) throws Exception {
    
            try {
                if (msg instanceof FullHttpRequest){
                    //以http请求形式接入,但是走的是websocket
                    handleHttpRequest(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/Post
            if (!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);
            }
        }
    
        @Override
        public void readComplete(ChannelHandlerContext ctx, ServletContext context) throws Exception {
    
        }
    
        @Override
        public 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");
        }
    
        @Override
        public 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);
        }
    
        @Override
        public 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 通行, 通讯的协议规则可以自己自定义,按照具体的需求来进行处理。

实现Java前后不分离的微信扫码登录,需要以下步骤: 1. 配置微信开放平台:首先,在微信开放平台上创建开发者账号,并注册一个应用。获取到微信开放平台的AppID和AppSecret。 2. 后接口:创建一个后接口,用于提供微信OAuth认证的相关功能。可以使用Java的框架,如Spring MVC来实现该接口。 3. 前端页面:创建一个前端页面,用于展示微信登录的按钮和扫码窗口。可以使用HTML、CSS和JavaScript来编写该页面。 4. 后代码实现:在后接口中,需要处理前端页面发送的请求。具体的步骤如下: - 前端页面向后发送登录请求,包含微信OAuth认证的URL地址。 - 后接口收到请求后,生成一个唯一的state,将其存储到数据库或者服务器中,并将state和微信OAuth认证的URL地址返回给前端页面。 - 前端页面将state和微信OAuth认证的URL地址展示给用户,在用户点击把扫码登录按钮后,前端页面将用户重定向至微信开放平台的认证页面,并携带state参数。 - 用户在微信认证页面进行登录操作,微信服务器会将认证结果返回给前端页面,并在返回结果中携带之前传递的state参数。 - 前端页面将返回结果和state参数发送给后接口。 - 后接口接收到结果后,对比传递的state参数与之前存储的state是否一致,以确保请求的合法性。 - 合法请求则获取到微信开放平台返回的用户授权信息,进行相应的业务操作。否则返回错误信息给前端。 以上就是实现Java前后不分离的微信扫码登录的大致步骤。可以根据具体需求进行相应的优化和完善。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值