Procházet zdrojové kódy

移动端登录互踢

deason před 6 roky
rodič
revize
927e58340f

+ 15 - 6
src/main/java/cn/com/qmth/examcloud/app/controller/version1/UserAuthRestController.java

@@ -7,6 +7,7 @@
 
 package cn.com.qmth.examcloud.app.controller.version1;
 
+import cn.com.qmth.examcloud.app.core.utils.StrUtils;
 import cn.com.qmth.examcloud.app.model.LoginInfo;
 import cn.com.qmth.examcloud.app.model.Result;
 import cn.com.qmth.examcloud.app.model.UserInfo;
@@ -38,13 +39,21 @@ public class UserAuthRestController {
 
     @ApiOperation(value = "登录接口", notes = "参数accountType值说明:学生身份证号类型=STUDENT_IDENTITY_NUMBER,学生学号类型=STUDENT_CODE,学生手机号类型=STUDENT_PHONE")
     @RequestMapping(value = "/user/login", method = {RequestMethod.GET, RequestMethod.POST})
-    public Result<UserInfo> login(@RequestParam String account, @RequestParam String password, @RequestParam String accountType,
-                                  @RequestParam(required = false) Long rootOrgId, @RequestParam(required = false) String domain) throws Exception {
-        LoginInfo loginInfo = new LoginInfo(account, password, accountType, rootOrgId, domain);
+    public Result<UserInfo> login(@RequestParam String account, @RequestParam String password, @RequestParam String accountType, @RequestParam(required = false) Long rootOrgId,
+                                  @RequestParam(required = false) String domain, @RequestHeader String deviceId) throws Exception {
+        LoginInfo loginInfo = new LoginInfo(account, password, accountType, rootOrgId, domain, deviceId);
         Result<UserInfo> result = userAuthService.login(loginInfo);
-        if (result.isSuccess()) {
-            //缓存登录信息
-            userAuthService.cacheLoginInfo(loginInfo, result.getData());
+        if (result.isSuccess() && result.getData() != null) {
+            //登录成功后缓存Token信息
+            UserInfo userInfo = result.getData();
+            loginInfo.setKey(userInfo.getKey());
+            loginInfo.setToken(userInfo.getToken());
+            String redisKey = StrUtils.md5Key(userInfo.getKey());
+            userAuthService.cacheLoginInfo(loginInfo, redisKey);
+            log.info(String.format("redisKey:%s key:%s token:%s", redisKey, userInfo.getKey(), userInfo.getToken()));
+
+            //替换login token为缓存的redisKey并返回
+            userInfo.setToken(redisKey);
         }
         return result;
     }

+ 64 - 19
src/main/java/cn/com/qmth/examcloud/app/core/config/TokenFilter.java

@@ -15,11 +15,14 @@ import cn.com.qmth.examcloud.app.service.UserAuthService;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
 
 import javax.servlet.*;
 import javax.servlet.FilterConfig;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.Date;
 
 import static cn.com.qmth.examcloud.app.model.Constants.PLATFORM_SESSION_EXPIRE_TIME;
@@ -47,7 +50,18 @@ public class TokenFilter implements Filter {
     @Override
     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
             throws IOException, ServletException {
-        CustomHttpServletRequest customRequest = this.initCustomRequest(servletRequest);
+        HttpServletRequest request = (HttpServletRequest) servletRequest;
+        HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+        //移动端设备请求必须带“设备编号”参数
+        String deviceId = request.getHeader(Constants.PARAM_DEVICE_ID);
+        if (StringUtils.isBlank(deviceId)) {
+            Result result = new Result().error("[APP] deviceId must be not empty.");
+            this.render(response, result.toString());
+            return;
+        }
+
+        CustomHttpServletRequest customRequest = this.initCustomRequest(request, deviceId);
         if (customRequest != null) {
             filterChain.doFilter(customRequest, servletResponse);
             return;
@@ -55,9 +69,8 @@ public class TokenFilter implements Filter {
         filterChain.doFilter(servletRequest, servletResponse);
     }
 
-    private CustomHttpServletRequest initCustomRequest(ServletRequest servletRequest) {
+    private CustomHttpServletRequest initCustomRequest(HttpServletRequest request, String deviceId) {
         //用户带的token实际为缓存的redisKey
-        HttpServletRequest request = (HttpServletRequest) servletRequest;
         String redisKey = request.getHeader(Constants.PARAM_TOKEN);
         if (StringUtils.isBlank(redisKey)) {
             //token为空,则不用过滤处理
@@ -71,25 +84,23 @@ public class TokenFilter implements Filter {
             return null;
         }
 
-        //处理登录过的信息
+        //处理登录信息
         CustomHttpServletRequest customRequest = new CustomHttpServletRequest(request);
         if (!this.filterAccessUrl(request.getServletPath())) {
-            //判断原始的login token是否在有效时间内
-            if (loginInfo.hasExpired(PLATFORM_SESSION_EXPIRE_TIME)) {
-                try {
-                    //已超过有效时间,则自动登录续期
-                    Result<UserInfo> result = userAuthService.login(loginInfo);
-                    UserInfo userInfo = result.getData();
-                    if (userInfo != null) {
-                        //登录成功后缓存新的login token
-                        log.info("redisKey:" + redisKey + " key:" + userInfo.getKey() + " newToken:" + userInfo.getToken());
-                        loginInfo.setToken(userInfo.getToken());
-                        loginInfo.setCreateTime(new Date());
-                        userAuthService.cacheLoginInfo(loginInfo, redisKey);
-                    }
-                } catch (Exception e) {
-                    log.error(e.getMessage());
+            /* 同一个账号不同移动端设备,支持互踢 */
+            if (deviceId.equals(loginInfo.getDeviceId())) {
+                //设备编号相同
+                if (loginInfo.hasExpired(PLATFORM_SESSION_EXPIRE_TIME)) {
+                    //判断原始的login token是否在有效时间内,若已超过有效时间,则自动登录续期
+                    this.reLogin(loginInfo, redisKey);
                 }
+            } else {
+                //设备编号不同且Token在有效时间内,则互踢
+                if (!loginInfo.hasExpired(PLATFORM_SESSION_EXPIRE_TIME)) {
+                    return null;
+                }
+                loginInfo.setDeviceId(deviceId);
+                this.reLogin(loginInfo, redisKey);
             }
         }
 
@@ -101,6 +112,23 @@ public class TokenFilter implements Filter {
         return customRequest;
     }
 
+    private void reLogin(LoginInfo loginInfo, String redisKey) {
+        try {
+            Result<UserInfo> result = userAuthService.login(loginInfo);
+            UserInfo userInfo = result.getData();
+            if (userInfo == null) {
+                return;
+            }
+            //登录成功后缓存新的Token信息
+            log.info(String.format("redisKey:%s key:%s newToken:%s", redisKey, userInfo.getKey(), userInfo.getToken()));
+            loginInfo.setToken(userInfo.getToken());
+            loginInfo.setCreateTime(new Date());
+            userAuthService.cacheLoginInfo(loginInfo, redisKey);
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        }
+    }
+
     private boolean filterAccessUrl(String url) {
         //跳过不用处理的访问地址
         if (url.contains("/user/logout")) {
@@ -109,6 +137,23 @@ public class TokenFilter implements Filter {
         return false;
     }
 
+    private void render(HttpServletResponse response, String json) {
+        PrintWriter out = null;
+        try {
+            response.setStatus(HttpStatus.OK.value());
+            response.setCharacterEncoding("UTF-8");
+            response.setContentType("application/json;charset=UTF-8");
+            out = response.getWriter();
+            out.print(json);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
     public void setUserAuthService(UserAuthService userAuthService) {
         this.userAuthService = userAuthService;
     }

+ 1 - 0
src/main/java/cn/com/qmth/examcloud/app/model/Constants.java

@@ -45,6 +45,7 @@ public interface Constants {
     /* 默认请求的Header参数 */
     String PARAM_KEY = "key";
     String PARAM_TOKEN = "token";
+    String PARAM_DEVICE_ID = "deviceId";
     String PARAM_TRACE_ID = "TRACE_ID";
     String PARAM_CLIENT = "$spring_cloud_client";
     String PARAM_CLIENT_VALUE = "-";

+ 13 - 2
src/main/java/cn/com/qmth/examcloud/app/model/LoginInfo.java

@@ -26,9 +26,10 @@ public class LoginInfo implements Serializable {
     private String domain;
     private String key;
     private String token;
+    private String deviceId;
     private Date createTime;
 
-    public LoginInfo(String account, String password, String accountType, Long rootOrgId, String domain, String key, String token) {
+    public LoginInfo(String account, String password, String accountType, Long rootOrgId, String domain, String key, String token, String deviceId) {
         this.account = account;
         this.password = password;
         this.accountType = accountType;
@@ -36,15 +37,17 @@ public class LoginInfo implements Serializable {
         this.domain = domain;
         this.key = key;
         this.token = token;
+        this.deviceId = deviceId;
         this.createTime = new Date();
     }
 
-    public LoginInfo(String account, String password, String accountType, Long rootOrgId, String domain) {
+    public LoginInfo(String account, String password, String accountType, Long rootOrgId, String domain, String deviceId) {
         this.account = account;
         this.password = password;
         this.accountType = accountType;
         this.rootOrgId = rootOrgId;
         this.domain = domain;
+        this.deviceId = deviceId;
         this.createTime = new Date();
     }
 
@@ -123,6 +126,14 @@ public class LoginInfo implements Serializable {
         this.token = token;
     }
 
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
     public Date getCreateTime() {
         return createTime;
     }

+ 0 - 8
src/main/java/cn/com/qmth/examcloud/app/service/UserAuthService.java

@@ -85,14 +85,6 @@ public interface UserAuthService {
      */
     Result userBindingPhone(String key, String token, String phone, String code) throws Exception;
 
-    /**
-     * 缓存用户登录信息
-     *
-     * @param loginInfo
-     * @param userInfo
-     */
-    void cacheLoginInfo(LoginInfo loginInfo, UserInfo userInfo);
-
     /**
      * 缓存用户登录信息
      *

+ 12 - 28
src/main/java/cn/com/qmth/examcloud/app/service/impl/UserAuthServiceImpl.java

@@ -44,7 +44,7 @@ public class UserAuthServiceImpl implements UserAuthService {
     public Result<UserInfo> login(LoginInfo loginInfo) throws Exception {
         Assert.notNull(loginInfo, "LoginInfo must be not null.");
         if (StringUtils.isBlank(loginInfo.getAccountType())) {
-            loginInfo.setAccountType(LoginType.COMMON_LOGIN_NAME.name());
+            loginInfo.setAccountType(LoginType.STUDENT_PHONE.name());
         }
         //封装请求参数
         final String requestUrl = String.format("%s/api/ecs_core/auth/login", propertyService.getUserAuthUrl());
@@ -137,53 +137,37 @@ public class UserAuthServiceImpl implements UserAuthService {
         return HttpUtils.doPost(requestUrl, formBody, key, token);
     }
 
-    @Override
-    public void cacheLoginInfo(LoginInfo loginInfo, UserInfo userInfo) {
-        if (loginInfo == null || userInfo == null) {
-            return;
-        }
-        //封装原始登录信息
-        loginInfo.setKey(userInfo.getKey());
-        loginInfo.setToken(userInfo.getToken());
-        //将原始登录信息存放到Redis中
-        String redisKey = StrUtils.md5Key(userInfo.getKey());
-        this.cacheLoginInfo(loginInfo, redisKey);
-        log.info("redisKey:" + redisKey + " key:" + userInfo.getKey() + " token:" + userInfo.getToken());
-        //替换login token为缓存的redisKey,并作为结果返回给接口
-        userInfo.setToken(redisKey);
-    }
-
     /**
      * 缓存用户登录信息
      */
     @Override
-    public void cacheLoginInfo(LoginInfo loginInfo, String key) {
-        if (StringUtils.isEmpty(key)) {
+    public void cacheLoginInfo(LoginInfo loginInfo, String redisKey) {
+        if (StringUtils.isEmpty(redisKey)) {
             throw new ApiException("Key must be not empty.");
         }
         String jsonStr = new JsonMapper().toJson(loginInfo);
-        redisService.set(APP_SESSION_USER_KEY_PREFIX + key, jsonStr, APP_SESSION_EXPIRE_TIME);
+        redisService.set(APP_SESSION_USER_KEY_PREFIX + redisKey, jsonStr, APP_SESSION_EXPIRE_TIME);
     }
 
     /**
      * 清除缓存用户登录信息
      */
-    private void removeCacheLoginInfo(String key) {
-        if (StringUtils.isEmpty(key)) {
-            throw new ApiException("Key must be not empty.");
+    private void removeCacheLoginInfo(String redisKey) {
+        if (StringUtils.isEmpty(redisKey)) {
+            throw new ApiException("redisKey must be not empty.");
         }
-        redisService.delete(APP_SESSION_USER_KEY_PREFIX + key);
+        redisService.delete(APP_SESSION_USER_KEY_PREFIX + redisKey);
     }
 
     /**
      * 获取缓存中的用户登录信息
      */
     @Override
-    public LoginInfo getLoginInfo(String key) {
-        if (StringUtils.isEmpty(key)) {
-            throw new ApiException("Key must be not empty.");
+    public LoginInfo getLoginInfo(String redisKey) {
+        if (StringUtils.isEmpty(redisKey)) {
+            throw new ApiException("redisKey must be not empty.");
         }
-        return redisService.fromJson(APP_SESSION_USER_KEY_PREFIX + key, LoginInfo.class);
+        return redisService.fromJson(APP_SESSION_USER_KEY_PREFIX + redisKey, LoginInfo.class);
     }
 
     /**