package com.vci.web.service.impl; import com.vci.client.mw.ClientSessionUtility; import com.vci.constant.CacheNameConstant; import com.vci.corba.common.PLException; import com.vci.corba.common.data.InvocationInfo; import com.vci.corba.omd.data.AttributeValue; import com.vci.starter.web.annotation.log.VciUnLog; import com.vci.starter.web.constant.TokenKeyConstant; import com.vci.starter.web.exception.VciBaseException; import com.vci.starter.web.interceptor.VciSessionForLoginI; import com.vci.starter.web.pagemodel.SessionInfo; import com.vci.starter.web.pagemodel.TokenVO; import com.vci.starter.web.redis.RedisService; import com.vci.starter.web.util.*; import com.vci.web.properties.WebProperties; import com.vci.web.service.WebBoServiceI; import com.vci.web.util.PlatformClientUtil; import com.vci.web.util.WebUtil; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; /** * 会话存储的服务 * @author weidy * @date 2021/2/18 */ @Service @VciUnLog public class SmSessionForLoginImpl implements VciSessionForLoginI { /** * 日志 */ private Logger logger = LoggerFactory.getLogger(getClass()); /** * 配置信息 */ @Autowired private WebProperties webProperties; /** * 业务类型服务 */ @Autowired private WebBoServiceI boService; /** * 平台的调用类 */ @Autowired private PlatformClientUtil platformClientUtil; /** * redis服务 */ @Resource private RedisService redisService; /** * 检查用户是否登录 * * @param userId 用户名 * @return 为空表示没有登录 */ @Override public String checkIsLogined(String userId) { WebUtil.alertNotNull(userId,"用户名"); List dataList = boService.queryBySqlForMap("select JSONSTRING as JSONSTRING from VCI_SESSIONINFO where userid ='" + userId.trim() + "'", new HashMap<>()); if(!CollectionUtils.isEmpty(dataList)) { Map data = dataList.get(0); SessionInfo sessionInfo = WebUtil.jsonString2JavaBean((String)data.getOrDefault("JSONSTRING",""),SessionInfo.class); return "在ip为" + sessionInfo.getIp() + "的地方已经登录"; } return ""; } /** * 强制用户下线 * * @param userId 用户名 */ @Override public void popUser(String userId) { WebUtil.alertNotNull(userId,"用户名"); try{ platformClientUtil.getBOFactoryService().executeUpdateSql("delete from VCI_SESSIONINFO where userid ='" + userId.trim() + "'"); }catch (PLException e){ throw WebUtil.getVciBaseException(e); } } /** * 将会话信息存储到数据库中------ * * @param sessionInfo 会话信息 */ @Override @Deprecated public void saveSessionInfo(SessionInfo sessionInfo) { WebUtil.alertNotNull(sessionInfo,"会话信息"); try { AttributeValue[] attrs = new AttributeValue[3]; attrs[0] = new AttributeValue("0", sessionInfo.getUserId()); attrs[1] = new AttributeValue("1", sessionInfo.getToken()); attrs[2] = new AttributeValue("2", WebUtil.getJSONStringWithDateFormat(sessionInfo)); platformClientUtil.getBOFactoryService().executeUpdateSqlByParams(" insert into VCI_SESSIONINFO (USERID, TOKEN, JSONSTRING,lastRequestTime\n" + " )\n" + " VALUES (?,?,?," + System.currentTimeMillis() + " )", attrs); }catch (PLException e){ throw WebUtil.getVciBaseException(e); } } /** * 校验请求是否符合权限验证 * 包含1,系统是否可以访问当前服务或者接口 * 2, 用户是否有权限访问当前服务 * 3, 用户是否有权限访问当前数据 * * @param request 请求对象 * @param systemPrivateToken 系统的许可码 * @param sessionInfo 当前用户会话对象 * @param handler 执行对象 * @return true表示有权限,false表示没权限 * @throws VciBaseException 没有权限的时候会抛出异常 */ @Override public boolean checkRequestRights(HttpServletRequest request, String systemPrivateToken, SessionInfo sessionInfo, Object handler) throws VciBaseException { return true; } /** * 更新请求时间 * * @param userToken 用户token */ @Override public void updateRequestTime(String userToken) { WebUtil.alertNotNull(userToken,"会话许可码"); //说明是jwt的token String jwtToken = getSessionTokenKeyInRedis(userToken); if(StringUtils.isBlank(jwtToken)){ jwtToken = userToken; } SessionInfo sessionInfo = redisService.getCacheObject(jwtToken); sessionInfo.setLastLoginTime(VciDateUtil.getNowTime()); redisService.setCacheObject(jwtToken, sessionInfo, webProperties.getClientSessionAliveMax()!=0?webProperties.getClientSessionAliveMax(): TokenKeyConstant.EXPIRATION, TimeUnit.MINUTES); } /** * 根据token获取用户的对象 * * @param userToken 用户token * @return 用户会话对象 */ @Override public SessionInfo getSessionInfoByToken(String userToken) { WebUtil.alertNotNull(userToken,"许可的信息"); SessionInfo sessionInfo = null; if(StringUtils.isNotBlank(userToken)){ if(userToken.startsWith(TokenKeyConstant.TOKEN_KEY_PREFIX_IN_REDIS)){ sessionInfo = redisService.getCacheObject(userToken); if(sessionInfo == null){ throw new VciBaseException("token已过期!"); } }else{ //说明是jwt的token String jwtToken = getSessionTokenKeyInRedis(userToken); if(StringUtils.isBlank(jwtToken)){ jwtToken = userToken; } sessionInfo = redisService.getCacheObject(jwtToken); if(sessionInfo == null){ throw new VciBaseException("token已过期!"); } } } return sessionInfo; } /** * 退出登录 * 清除用户session * @param userToken 用户的会话许可 */ @Override public void logout(String userToken,String userId) throws PLException { //TODO 根据情况,单独处理 redisService.deleteObject(CacheNameConstant.USERID_TOKEN_KEY + userId); redisService.deleteObject(TokenKeyConstant.TOKEN_KEY_PREFIX_IN_REDIS + userToken); platformClientUtil.getFrameworkService().logoff(userToken); } /** * 根据token获取用户在系统中还可以存在的时间 * @param userToken userToken 用户的会话许可 * @return 用户在系统中还可以存在的时间(毫秒) */ @Override public long getCanAliveTime(String userToken) { VciBaseUtil.alertNotNull(userToken,"用户会话许可(令牌)"); List dataList = boService.queryBySqlForMap("select lastRequestTime as LASTREQUESTTIME from VCI_SESSIONINFO where TOKEN ='" + userToken.trim() + "'", new HashMap<>()); if(!CollectionUtils.isEmpty(dataList)) { Map data = dataList.get(0); long lastTime = WebUtil.getLong(data.get("LASTREQUESTTIME").toString()); long currentTime = System.currentTimeMillis(); long canAliveTime = lastTime + (webProperties.getClientSessionAliveMax()*60*1000) - currentTime; return canAliveTime; } return 0; } /** * 删除超时的会话的信息 */ @Scheduled(fixedDelay=60000) public void deleteTimeoutSession(){ long now = System.currentTimeMillis(); if(webProperties.getClientSessionAliveMax() > 0) { long lastValidTime = now - webProperties.getClientSessionAliveMax() * 60000; if (logger.isDebugEnabled()) { logger.debug("开始执行扫描超时的会话信息,其中当前时间为{},会话最后访问的有效时间应该为{}", now, lastValidTime); } List invalidSessionList = boService.queryBySqlForMap("select token as TOKEN from VCI_SESSIONINFO where lastRequestTime<= " + lastValidTime, null); if (!CollectionUtils.isEmpty(invalidSessionList)) { List tokenList = new ArrayList<>(); invalidSessionList.stream().forEach(map -> { tokenList.add(map.get("TOKEN").toString()); }); WebUtil.switchCollectionForOracleIn(tokenList).stream().forEach(tokens -> { try { platformClientUtil.getBOFactoryService().executeUpdateSql("delete from VCI_SESSIONINFO where TOKEN in (" + WebUtil.toInSql(tokens.toArray(new String[0])) + ")"); } catch (PLException e) { if (logger.isErrorEnabled()) { logger.error("删除会话信息", e); } } }); } if (logger.isDebugEnabled()) { logger.debug("开始执行扫描超时的会话信息完成,删除了{}条数据", invalidSessionList == null ? 0 : invalidSessionList.size()); } } } /** * 创建许可的信息,并存储到缓存中 * * @param sessionInfo session的信息 * @return 许可信息 */ @Override public TokenVO createToken(SessionInfo sessionInfo) { return createToken(TokenKeyConstant.TOKEN_KEY_PREFIX_IN_REDIS, sessionInfo); } /** * 创建许可的信息,并存储到缓存中 * @param key token在redis中的key * @param sessionInfo session的信息 * @return 许可信息 */ @Override public TokenVO createToken(String key, SessionInfo sessionInfo) { if(StringUtils.isBlank(sessionInfo.getToken())) { String token = Md5.md5(VciBaseUtil.getPk() + "_" + sessionInfo.getUserId()); sessionInfo.setToken(token); } if(StringUtils.isBlank(key)){ key = TokenKeyConstant.TOKEN_KEY_PREFIX_IN_REDIS; } refreshToken(key, sessionInfo); Map claimsMap = new HashMap<>(); claimsMap.put(TokenKeyConstant.JWT_TOKEN_KEY,sessionInfo.getToken()); claimsMap.put(TokenKeyConstant.JWT_USER_KEY,sessionInfo.getUserOid()); claimsMap.put(TokenKeyConstant.JWT_USER_NAME_KEY,sessionInfo.getUserName()); claimsMap.put(TokenKeyConstant.JWT_USER_CODE_KEY,sessionInfo.getUserId()); TokenVO tokenVO = new TokenVO(); tokenVO.setAccessToken(JwtUtils.createToken(claimsMap)); tokenVO.setExpireTime(TokenKeyConstant.EXPIRATION); return tokenVO; } /** * 刷新缓存中的token * @param sessionInfo session的信息 */ @Override public void refreshToken(SessionInfo sessionInfo) { refreshToken(TokenKeyConstant.TOKEN_KEY_PREFIX_IN_REDIS, sessionInfo); } /** * 刷新缓存中的token * @param key token在redis中的key * @param sessionInfo session的信息 */ @Override public void refreshToken(String key, SessionInfo sessionInfo) { if(sessionInfo!=null && StringUtils.isNotBlank(sessionInfo.getToken())){ if(StringUtils.isBlank(key)){ key = TokenKeyConstant.TOKEN_KEY_PREFIX_IN_REDIS; } String redisKey = key + sessionInfo.getToken(); redisService.setCacheObject(redisKey, sessionInfo, webProperties.getClientSessionAliveMax()!=0?webProperties.getClientSessionAliveMax(): TokenKeyConstant.EXPIRATION, TimeUnit.MINUTES); //因为可能需要退出登录,或同一用户只能在线一个,故需要存储,用户和jwtToken的关系 String tokenKey = CacheNameConstant.cacheKey(CacheNameConstant.USERID_TOKEN_KEY,sessionInfo.getUserId()); redisService.setCacheObject(tokenKey, redisKey, webProperties.getClientSessionAliveMax()!=0?webProperties.getClientSessionAliveMax(): TokenKeyConstant.EXPIRATION, TimeUnit.MINUTES); } } /** * jwt的token改成 * @param jwtToken jwt的许可 * @return 在redis里的信息 */ private String getSessionTokenKeyInRedis(String jwtToken) { if(StringUtils.isBlank(jwtToken)){ return ""; } String key = TokenKeyConstant.TOKEN_KEY_PREFIX_IN_REDIS; if(ControllerUtil.urlDecode(jwtToken).startsWith(key)){ return ControllerUtil.urlDecode(jwtToken); } String userToken = JwtUtils.getUserToken(jwtToken); return TokenKeyConstant.TOKEN_KEY_PREFIX_IN_REDIS + userToken; } /** * 将token放入平台中 * * @param sessionInfo */ @Override public void initInvocationInfo(SessionInfo sessionInfo) { InvocationInfo vcii = new InvocationInfo(); vcii.setToken(sessionInfo.getToken()); ClientSessionUtility.setInvocationInThread(vcii); } }