Browse Source

Merge remote-tracking branch 'origin/master'

chao 4 years ago
parent
commit
5cc30e0d50

+ 137 - 0
src/main/java/com/caimei365/user/components/NettyServer.java

@@ -0,0 +1,137 @@
+package com.caimei365.user.components;
+
+import com.caimei365.user.model.ResponseJson;
+import com.caimei365.user.service.LoginService;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import lombok.Value;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.time.LocalDateTime;
+
+/**
+ * @author Aslee
+ * @date 2021/3/10
+ */
+@Slf4j
+@Order(value = 100000000)
+@Component
+public class NettyServer implements ApplicationRunner {
+    private LoginService loginService;
+
+    @Autowired
+    public void setLoginService(LoginService loginService) throws Exception {
+        this.loginService = loginService;
+    }
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        // 创建2个线程组
+        EventLoopGroup boss=new NioEventLoopGroup(1);
+        // NettyRuntime.availableProcessors() * 2) 默认创建的线程个数
+        EventLoopGroup work=new NioEventLoopGroup();
+        ServerBootstrap serverBootstrap = new ServerBootstrap();
+        try {
+            serverBootstrap.group(boss, work);
+            serverBootstrap.channel(NioServerSocketChannel.class);
+            // Netty提供的日志handler
+            serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
+            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
+                @Override
+                protected void initChannel(SocketChannel ch) throws Exception {
+                    ChannelPipeline pipeline = ch.pipeline();
+                    //基于http协议,使用http的编码和解码器
+                    pipeline.addLast(new HttpServerCodec());
+                    //以块方式写,添加ChunkedWriteHandler处理器
+                    pipeline.addLast(new ChunkedWriteHandler());
+                    /*
+                    1. http数据在传输过程中是分段, HttpObjectAggregator ,就是可以将多个段聚合
+                    2. 这就就是为什么,当浏览器发送大量数据时,就会发出多次http请求
+                     */
+                    pipeline.addLast(new HttpObjectAggregator(7168));
+                    /*
+                    1. 对应websocket ,它的数据是以 帧(frame) 形式传递
+                    2. 可以看到WebSocketFrame 下面有六个子类
+                    3. 浏览器请求时 ws://localhost:9000/demo 表示请求的uri
+                    4. WebSocketServerProtocolHandler 核心功能是将 http协议升级为 ws协议 , 保持长连接
+                    5. 是通过一个 状态码 101
+                     */
+                    pipeline.addLast(new WebSocketServerProtocolHandler("/demo"));
+
+                    //自定义的handler ,处理业务逻辑
+                    pipeline.addLast(new MyTextWebSocketFrameHandler());
+                }
+            });
+
+            //启动服务器
+            ChannelFuture channelFuture = serverBootstrap.bind(9000).sync();
+            channelFuture.channel().closeFuture().sync();
+        }finally {
+            boss.shutdownGracefully();
+            work.shutdownGracefully();
+        }
+    }
+
+
+
+    public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
+        @Override
+        protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
+            log.info("收到登录请求,开始验证state:" + msg.text());
+            // 收到登录请求,根据安全认证state调用登录验证方法
+            ResponseJson responseJson = loginService.nettyAuthorization(msg.text());
+            Integer count = 0;
+            // 若验证返回码不为0且验证次数小于60次,重新调用验证
+            while (responseJson.getCode() != 0 && count<60) {
+                count++;
+                // 返回验证结果
+                ctx.channel().writeAndFlush(new TextWebSocketFrame("验证结果:" + responseJson.getCode() + "-" + responseJson.getMsg()));
+                Thread.sleep(1000);
+                // 重新调用登录验证方法
+                responseJson = loginService.nettyAuthorization(msg.text());
+            }
+            ctx.channel().writeAndFlush(new TextWebSocketFrame("验证结果:" + responseJson.getCode() + "-" + responseJson.getMsg()));
+            // 关闭连接
+            ctx.close();
+        }
+
+        // 当web客户端连接后,触发方法
+        @Override
+        public void handlerAdded(ChannelHandlerContext ctx){
+            //id 表示唯一的值,LongText 是唯一的 ShortText 不是唯一
+//            System.out.println("handlerAdded 被调用 id" + ctx.channel().id());
+//            System.out.println("handlerAdded 被调用" + ctx.channel().id().asLongText());
+//            System.out.println("handlerAdded 被调用" + ctx.channel().id().asShortText());
+        }
+
+
+        @Override
+        public void handlerRemoved(ChannelHandlerContext ctx){
+            log.info("连接关闭");
+        }
+
+        @Override
+        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
+            // 抛出异常
+            cause.printStackTrace();
+            // 关闭连接
+            ctx.close();
+        }
+    }
+}

+ 1 - 0
src/main/java/com/caimei365/user/config/SwaggerConfig.java

@@ -24,6 +24,7 @@ public class SwaggerConfig {
     public Docket createRestApi(){
         return new Docket(DocumentationType.SWAGGER_2)
                 .apiInfo(apiInfo())
+                // 是否开启swagger
                 .enable(swaggerEnabled)
                 .select()
                 // 扫描api

+ 12 - 2
src/main/java/com/caimei365/user/controller/LoginApi.java

@@ -40,8 +40,6 @@ public class LoginApi {
     /**
      * 微信授权登录(小程序)
      *
-     * spi旧接口:club/authorization
-     *
      * @param code          微信授权code
      * @param encryptedData 微信加密数据
      * @param iv            加密算法的初始向量
@@ -78,5 +76,17 @@ public class LoginApi {
         return loginService.websiteAuthorization(code, state, mode, serverWebExchange);
     }
 
+    /**
+     * 微信扫码登录
+     *
+     * spi旧接口:user/wechatLogin
+     *
+     * @param code      微信code
+     * @param state     安全认证
+     */
+    @GetMapping("/auth/qrCode")
+    public void qrCodeAuthorization(String code, String state, ServerWebExchange serverWebExchange) {
+        loginService.qrCodeAuthorization(code, state, serverWebExchange);
+    }
 
 }

+ 0 - 5
src/main/java/com/caimei365/user/controller/ShopApi.java

@@ -126,9 +126,4 @@ public class ShopApi {
         return shopService.appletsRegister(source, name, sName, bindMobile, email, smsCode, password, passWordConfirm, linkMan, provinceId, cityId, townId, address, socialCreditCode, businessLicenseImage, firstShopType, secondShopType, mainPro, isAgreed, whichStep, headers);
     }
 
-    @GetMapping("/hello")
-    @ResponseBody
-    public String hello(){
-        return "hello";
-    }
 }

+ 16 - 0
src/main/java/com/caimei365/user/service/LoginService.java

@@ -1,6 +1,7 @@
 package com.caimei365.user.service;
 
 import com.caimei365.user.model.ResponseJson;
+import com.caimei365.user.model.po.UserPo;
 import com.caimei365.user.model.vo.UserLoginVo;
 import org.springframework.web.server.ServerWebExchange;
 
@@ -50,4 +51,19 @@ public interface LoginService {
      * @param mode  1:静默授权,2:用户手动授权
      */
     ResponseJson<UserLoginVo> websiteAuthorization(String code, String state, Integer mode, ServerWebExchange serverWebExchange);
+
+    /**
+     * 微信扫码登录
+     *
+     * @param code      微信code
+     * @param state     安全认证
+     */
+    void qrCodeAuthorization(String code, String state, ServerWebExchange serverWebExchange);
+
+    /**
+     * netty长连接验证登录
+     * @param state     安全认证
+     * @return
+     */
+    ResponseJson nettyAuthorization(String state);
 }

+ 80 - 0
src/main/java/com/caimei365/user/service/impl/LoginServiceImpl.java

@@ -4,6 +4,8 @@ import com.alibaba.fastjson.JSONObject;
 import com.caimei365.user.components.WeChatService;
 import com.caimei365.user.mapper.LoginMapper;
 import com.caimei365.user.model.ResponseJson;
+import com.caimei365.user.model.po.OperationPo;
+import com.caimei365.user.model.po.UserPo;
 import com.caimei365.user.model.vo.UserLoginVo;
 import com.caimei365.user.service.LoginService;
 import com.caimei365.user.components.RedisService;
@@ -15,6 +17,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.web.server.ServerWebExchange;
 
 import javax.annotation.Resource;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
 
@@ -244,4 +247,81 @@ public class LoginServiceImpl implements LoginService {
         return ResponseJson.success("登录成功", baseUser);
     }
 
+    /**
+     * 微信扫码登录
+     *
+     * @param code      微信code
+     * @param state     安全认证
+     */
+    @Override
+    public void qrCodeAuthorization(String code, String state, ServerWebExchange serverWebExchange) {
+        Map<String, Object> map = new HashMap<>();
+        // 简单验证,防止csrf攻击(跨站请求伪造攻击)
+        String stateCache = (String) redisService.get("state:" + state);
+        if (stateCache == null) {
+            map.put("-1", "请从正确的途径打开链接");
+        }
+        if (StringUtils.isEmpty(code)) {
+            map.put("-1", "请重新进行授权登录");
+        }
+        Map<String, Object> tokenMap;
+        Map<String, Object> wxInfoMap = null;
+        try {
+            tokenMap = weChatService.getInfoMapByWeb(code, "pc");
+            String access_token = (String) tokenMap.get("access_token");
+            String openid = (String) tokenMap.get("openid");
+            log.info("wx回调openid>>>>>" + openid);
+            log.info("wx回调access_token>>>>>" + access_token);
+            wxInfoMap = weChatService.getUserInfo(access_token, openid);
+            log.info("wx回调openid>>>>>" + wxInfoMap.get("openid"));
+            log.info("wx回调unionid>>>>>" + wxInfoMap.get("unionid"));
+        } catch (Exception e) {
+            map.put("-1", "获取微信用户信息失败");
+        }
+        redisService.setMap("error:" + state, map);
+        redisService.setMap(state, wxInfoMap);
+    }
+
+    /**
+     * netty长连接验证登录
+     * @param state     安全认证
+     * @return
+     */
+    @Override
+    public ResponseJson nettyAuthorization(String state) {
+        if (state == null) {
+            return ResponseJson.error("参数异常");
+        }
+        Map<Object, Object> error = redisService.getEntries("error:" + state);
+        if (null != error && error.size() > 0) {
+            return ResponseJson.error(-1, "错误信息", error);
+        }
+        Map<Object, Object> wxInfoMap = redisService.getEntries(state);
+        if (null == wxInfoMap || wxInfoMap.size() == 0) {
+            return ResponseJson.error(-90, "redis缓存没有拿到");
+        }
+        redisService.remove("state");
+        /*HttpSession session = request.getSession();
+        session.setAttribute("wxInfoMap", wxInfoMap);*/
+        String unionId = (String) wxInfoMap.get("unionid");
+        log.info("pc商城unionId>>>>>>" + unionId);
+        String openid = (String) wxInfoMap.get("openid");
+        log.info("pc商城openid>>>>>>" + openid);
+        UserLoginVo operation = loginMapper.getOperationUserByUnionId(unionId, "www");
+        if (operation == null) {
+            operation = loginMapper.getOperationUserByOpenId(openid, "www");
+            if (operation == null) {
+                return ResponseJson.error(-4, "您的微信尚未绑定任何机构", wxInfoMap);
+            } else if (StringUtils.isBlank(operation.getUnionId())) {
+                // 如果openId存在, unionId不存在
+                loginMapper.updateOperationUnionId(operation.getUserId(), unionId);
+            }
+        // 如果unionId存在, openId不存在
+        } else if (operation.getOpenId() == null) {
+            loginMapper.updateOperationOpenId(operation.getUserId(), openid);
+        }
+        // 获取用户信息
+        UserLoginVo user = loginMapper.getLoginUserByUserId(operation.getUserId());
+        return logonVerify(user);
+    }
 }