后台框架采用SpringMVC,不同的框架可根据逻辑更改即可:
【思路】- PC端生成二维码,二维码包含uuid(全局唯一标识符),且打通websocket通道,等待服务器返回登录成功信息;APP扫描二维码,获取uuid及登录信息,推送给服务端,处理后的登录信息通过websocket返回给PC端,PC端得到登录信息后保存即登录成功。APP扫描确认登录的信息可以采用ActiveMQ进行推送。
生成二维码部分引入依赖文件
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.1.0</version>
</dependency>
二维码登录后台控制层Controller
/**
* 项目名称:dream_user
* 项目包名:org.fore.user.controller
* 创建时间:2017年8月8日下午5:29:41
* 创建者:Administrator-宋发元
* 创建地点:杭州
*/
package org.fore.user.controller;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.fore.model.user.UserAccount;
import org.fore.model.user.UserModel;
import org.fore.user.qrcode.websocket.WebSocketHandler;
import org.fore.user.service.UserAccountService;
import org.fore.user.service.UserService;
import org.fore.utils.jms.JmsSender;
import org.fore.utils.mvc.TokenUtil;
import org.fore.utils.mvc.annotation.LimitLess;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONObject;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
/**
* 描述:控制层
* @author songfayuan
* 2017年8月8日下午5:29:41
*/
@Controller
@RequestMapping("/qrcodelogin")
public class QrCodeLoginController {
private Logger logger = LoggerFactory.getLogger(QrCodeLoginController.class);
public static int defaultWidthAndHeight=260;
@Autowired
private WebSocketHandler webSocketHandler;
@Autowired
private UserService userService;
@Autowired
private UserAccountService userAccountService;
@Autowired
@Qualifier(value = "qrCodeLoginSender")
private JmsSender jmsSender;
/**
* 描述:PC获取二维码
* @param uuid
* @param request
* @param response
* @throws ServletException
* @throws IOException
* @author songfayuan
* 2017年8月11日上午9:04:43
*/
@RequestMapping("/getLoginQrCode")
@ResponseBody
@LimitLess
public void getLoginQrCode(String uuid, HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
//生成参数
//String uuid = generateUUID();
String host = request.getHeader("Host");
JSONObject data = new JSONObject();
data.put("code", 200);
data.put("msg", "获取二维码成功");
data.put("uuid", uuid);
data.put("host", host);
logger.info("【二维码内容】:{}",data);
//生成二维码
Map<EncodeHintType, Object> hints=new HashMap<EncodeHintType, Object>();
// 指定纠错等级
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
// 指定编码格式
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.MARGIN, 1);
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(data.toString(),BarcodeFormat.QR_CODE, defaultWidthAndHeight, defaultWidthAndHeight, hints);
OutputStream out = response.getOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, "png", out);//输出二维码
out.flush();
out.close();
} catch (WriterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 描述:app确认请求处理
* @param uuid
* @param host
* @param userid
* @author songfayuan
* 2017年8月11日上午9:05:56
*/
@RequestMapping("/sendCodeLoginInfo")
@ResponseBody
@LimitLess
public void sendCodeLoginInfo(String uuid, String host, Integer userid) {
// 注册成功后 或 登录,需要同步账户信息,获取用户基本信息
UserAccount account = userAccountService.findCurrentUserAccount(userid);
userAccountService.syncAccount(account);
UserModel userModel = userService.findUserById(userid);
userModel = changeUserForShow(userModel);
JSONObject token = TokenUtil.generateTokenByQrCodeLogin(userid, host);
JSONObject object = new JSONObject();
object.put("code", 10086);
object.put("uuid", uuid);
object.put("userinfo", userModel);
object.put("token", token);
object.put("msg", "登录成功");
//this.webSocketHandler.forwardQrCode(object.toString());
jmsSender.sendMessage(object.toString()); //采用ActiveMQ进行推送,也可以直接注入websocket进行发送
}
//处理用户登录信息
private UserModel changeUserForShow(UserModel userModel) {
UserModel user = new UserModel();
user.setId(userModel.getId());
user.setUserName(userModel.getUserName());
user.setUserSex(userModel.getUserSex());
user.setUserPortrait(userModel.getUserPortrait());
return user;
}
/**
* 描述:唯一标识符
* @return
* @author songfayuan
* 2017年8月11日上午9:06:12
*/
public static String generateUUID() {
String uuid = UUID.randomUUID().toString();
uuid = uuid.replace("-", "");
Long currentTime = System.currentTimeMillis();
String currentDate = String.valueOf(currentTime);
return uuid + currentDate;
}
}
websocket实现(本案例采用Spring自带的websocket)
package org.fore.sms.qrcode.websocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
@Configuration
@EnableWebMvc
@EnableWebSocket
public class QrCodeLoginWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
@Autowired
private QrCodeLoginWebSocketEndPoint endPoint;
@Autowired
private QrCodeLoginHandshakeInterceptor interceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(endPoint, "/qrcodelogin.do").addInterceptors(interceptor).setAllowedOrigins("*");
// registry.addHandler(endPoint,
// "/sockjs.do").addInterceptors(interceptor).setAllowedOrigins("*")
// .withSockJS();
}
/**
* Each underlying WebSocket engine exposes configuration properties that
* control runtime characteristics such as the size of message buffer sizes,
* idle timeout, and others.
*/
/**
* For Tomcat, WildFly, and GlassFish add a
* ServletServerContainerFactoryBean to your WebSocket Java config:
*/
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
/**
* For Jetty, you’ll need to supply a pre-configured Jetty
* WebSocketServerFactory and plug that into Spring’s
* DefaultHandshakeHandler through your WebSocket Java config:
*/
// @Bean
// public DefaultHandshakeHandler handshakeHandler() {
//
// WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
// policy.setInputBufferSize(8192); /* 设置消息缓冲大小 */
// policy.setIdleTimeout(600000); /* 10分钟read不到数据的话,则断开该客户端 */
//
// return new DefaultHandshakeHandler(new JettyRequestUpgradeStrategy(new
// WebSocketServerFactory(policy)));
// }
}
package org.fore.sms.qrcode.websocket;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
@Component
public class QrCodeLoginHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
private Logger logger = LoggerFactory.getLogger(QrCodeLoginHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
super.afterHandshake(request, response, wsHandler, ex);
}
}
package org.fore.sms.qrcode.websocket;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.fore.model.quota.tcp.ReqCode;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
@Component
public class QrCodeLoginWebSocketEndPoint extends TextWebSocketHandler {
private Logger logger = LoggerFactory.getLogger(QrCodeLoginWebSocketEndPoint.class);
private static Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();
private static Map<WebSocketSession,String > sessionMap2 = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("WebSocketHandler:客户端{}上线", session.getRemoteAddress());
String uuid = generateUUID();
sessionMap.put(uuid,session);
sessionMap2.put(session,uuid);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String msg = message.getPayload();
String ipAddress = session.getRemoteAddress().toString();
JSONObject requestData = JSON.parseObject(msg);
Integer code = requestData.getInteger("code");
JSONObject result = new JSONObject();
String uuid = sessionMap2.get(session);
result.put("code", 200);
result.put("uuid", uuid);
switch (code) {
case ReqCode.REQ_QR_CODE:
logger.info("WebSocketHandler:客户端{}发送消息{}...", ipAddress, msg);
if(session.isOpen())
session.sendMessage(new TextMessage(result.toString()));
logger.info("WebSocketHandler:客户端{}发送消息{}完成", ipAddress, msg);
break;
default:
break;
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String ipAddress = session.getRemoteAddress().toString();
logger.info("WebSocketHandler:客户端{}下线", ipAddress);
logger.info("WebSocketHandler:删除客户端{}的session...", ipAddress);
logger.info("WebSocketHandler:删除sessionMap的客户端{}连接...", ipAddress);
String uuid = sessionMap2.get(session);
sessionMap.remove(uuid);
sessionMap2.remove(session);
logger.info("WebSocketHandler:删除sessionMap的客户端{}连接完成", ipAddress);
logger.info("WebSocketHandler:删除WebSocket客户端{}连接...", ipAddress);
// logger.info("{}", sessionMap);
sessionMap.remove(session);
// logger.info("{}", sessionMap);
logger.info("WebSocketHandler:删除WebSocket客户端{}连接完成", ipAddress);
logger.info("WebSocketHandler:删除客户端{}的session完成", ipAddress);
if(session.isOpen())
session.close();
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
logger.info("WebSocketHandler:客户端{}异常", session.getRemoteAddress(), exception);
}
//发送消息
public void sendMessage(String userInfo) throws Exception {
JSONObject json = JSONObject.parseObject(userInfo);
String uuid = json.getString("uuid");
WebSocketSession session = sessionMap.get(uuid);
if (session == null) {
logger.info("app发送给PC的登录信息:{}参数不正确!",userInfo);
}else {
logger.info("app发送给PC的登录信息:{}",userInfo);
session.sendMessage(new TextMessage(userInfo));
}
}
//唯一标识符
public static String generateUUID() {
String uuid = UUID.randomUUID().toString();
uuid = uuid.replace("-", "");
Long currentTime = System.currentTimeMillis();
String currentDate = String.valueOf(currentTime);
return uuid + currentDate;
}
}
JMS实现
package org.fore.sms.qrcode.jms;
import org.fore.utils.jms.Listener;
import org.fore.sms.qrcode.websocket.QrCodeLoginWebSocketEndPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSONObject;
@Component
public class QrCodeLoginListener implements Listener {
private Logger logger = LoggerFactory.getLogger(QrCodeLoginListener.class);
@Autowired
private QrCodeLoginWebSocketEndPoint qrCodeLoginWebSocketEndPoint;
@Override
public void onMessage(String message) {
logger.info("app确认登录信息:接收app推送的确定PC登录消息{}", message);
JSONObject object = JSONObject.parseObject(message);
try {
qrCodeLoginWebSocketEndPoint.sendMessage(object.toJSONString());
} catch (Exception e) {
logger.info("app确认登录信息:接收app推送的确定PC登录消息异常", e);
}
}
}
核心代码就酱......