验证token
对外API过滤器
public class APIFilter implements Filter {private static Logger logger = LogManager.getLogger(APIFilter.class);private ICachedTokenService tokenService;public APIFilter(ICachedTokenService tokenService) {super();this.tokenService = tokenService;}@Overridepublic void init(FilterConfig arg0) throws ServletException {}@Overridepublic void destroy() {}@Overridepublic void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {HttpServletRequest hreq = (HttpServletRequest) req;HttpServletResponse hres = (HttpServletResponse) resp;//设置一些Header其他信息hres.setHeader("Access-Control-Allow-Origin", hreq.getHeader("Origin"));hres.setHeader("Access-Control-Allow-Credentials", "true");hres.setHeader("Access-Control-Allow-Methods", hreq.getMethod());hres.setHeader("Access-Control-Max-Age", "86400");hres.setHeader("Access-Control-Allow-Headers", "*");//不校验权限if(hreq.getRequestURI().contains("/unauth") || hreq.getRequestURI().contains("/question")){chain.doFilter(req, resp);return;}// 如果是OPTIONS则结束请求if (HttpMethod.OPTIONS.toString().equals(hreq.getMethod())) {hres.setStatus(HttpStatus.NO_CONTENT.value());return;}try{//从请求cookie中获取access_tokenAccessTokenUser user = null;String access_token=hreq.getParameter("access_token");if(StringUtils.isBlank(access_token)){access_token= hreq.getHeader("access_token");}if(StringUtils.isBlank(access_token)){access_token=this.getCookieByCookieName("access_token", hreq);}//获取用户if(StringUtils.isNotBlank(access_token)){String token=access_token;user = ThreadLocalCache.fetchAPIData("AccessTokenUserByAccessToken:"+token+",true",() -> {ApiResultDTO<AccessTokenUser> tokenUserResp= tokenService.getAccessTokenUserByAccessToken(token, true);if(tokenUserResp.isSuccessed()) {return tokenUserResp;}else {String errorMsg = "访问令牌服务发生错误,错误码:"+tokenUserResp.getStatus()+",错误信息:"+tokenUserResp.getStatusMsg();logger.error(errorMsg);throw new RuntimeException(errorMsg);}});//判断是否存在这样的用户if(user==null) logger.error("请求地址{}根据access_token【{}】无法获取登录用户。",hreq.getRequestURI(),access_token);}if(user==null){throw new BusinessException("登录已过期,请重新登录!", ApiResultDTO.STATUS_UNAUTH);}LoginInitUtils.afterLogin(false,hreq,hres,user);chain.doFilter(req, resp);return;} catch (Exception e) {ApiResultDTO<String> apiResult=null;if(e instanceof BusinessException) {BusinessException businessException = (BusinessException) e;apiResult = ApiResultDTO.failed(businessException.getErrorCode(), businessException.getOriMsg());}else {apiResult = ApiResultDTO.failed(e.getMessage());}hres.setContentType("application/json;charset=UTF-8");hres.setHeader("Cache-Control", "no-store");hres.setHeader("Pragma", "no-cache");hres.setDateHeader("Expires", 0);hres.getWriter().write(new ObjectMapper().writeValueAsString(apiResult));hres.getWriter().flush();hres.getWriter().close();}}private String getCookieByCookieName(String cookieName,HttpServletRequest hreq) {Cookie[] cookies = hreq.getCookies();if(cookies!=null){for(Cookie cookie:cookies){if(cookieName.equalsIgnoreCase(cookie.getName())){return cookie.getValue();}}}return null;}
}
对内API过滤器
public class _APIFilter implements Filter {private Logger logger=LogManager.getLogger(this.getClass()); private ICachedTokenService tokenService;public _APIFilter(ICachedTokenService tokenService) {super();this.tokenService = tokenService;}@Overridepublic void init(FilterConfig arg0) throws ServletException {}@Overridepublic void destroy() {}@Overridepublic void doFilter(ServletRequest req, ServletResponse resp,FilterChain chain) throws IOException, ServletException {HttpServletRequest hreq = (HttpServletRequest) req;HttpServletResponse hres = (HttpServletResponse) resp;try{Utils.passOverByIp(hreq.getRemoteAddr());// logger.error("--------------------------_APIFilter["+hreq.getRequestURL()+"]开始--------------------------");AccessTokenUser user = null;String access_token=hreq.getParameter("access_token");if(StringUtils.isBlank(access_token)){access_token= hreq.getHeader("access_token");}//根据访问令牌获取用户if(StringUtils.isNotBlank(access_token)){try {String token=access_token;user = ThreadLocalCache.fetchAPIData("AccessTokenUser."+token+",true",() -> {ApiResultDTO<AccessTokenUser> tokenUserResp= tokenService.getAccessTokenUserByAccessToken(token, true);if(tokenUserResp.isSuccessed()) {return tokenUserResp;}else {String errorMsg = "访问令牌服务发生错误,错误码:"+tokenUserResp.getStatus()+",错误信息:"+tokenUserResp.getStatusMsg();logger.error(errorMsg);throw new RuntimeException(errorMsg);}});} catch (Exception e) {e.printStackTrace();user=null;}if(user==null) logger.error("--------------------------_APIFilter["+hreq.getRequestURL()+"]access_token["+access_token+"]获取用户结果"+user+"--------------------------");}//根据内部令牌获取用户if(user==null) {String inner_token=hreq.getParameter("inner_token");if(StringUtils.isBlank(inner_token)){inner_token = hreq.getHeader("inner_token");}if(StringUtils.isBlank(inner_token)) throw new RuntimeException("未找到访问令牌");String token=inner_token;user=ThreadLocalCache.fetchAPIData(null,()->{return tokenService.verifyTokenAndGetUser(token);},"验证访问令牌错误,");if(user==null) logger.error("--------------------------_APIFilter["+hreq.getRequestURL()+"]inner_token["+inner_token+"]获取用户结果"+user+"--------------------------");}LoginInitUtils.afterLogin(false,hreq,hres,user);chain.doFilter(req, resp);return;} catch (Exception e) {e.printStackTrace();ApiResultDTO<String> apiResult=ApiResultDTO.failed(e.getMessage());hres.setContentType("application/json;charset=UTF-8");hres.setHeader("Cache-Control", "no-store");hres.setHeader("Pragma", "no-cache");hres.setDateHeader("Expires", 0);hres.getWriter().write(new ObjectMapper().writeValueAsString(apiResult));hres.getWriter().flush();hres.getWriter().close();}}}
public ApiResultDTO<AccessTokenUser> verifyTokenAndGetUser(String inner_token) {Map<String,String> claims=JWTUtils.verifyTokenAndGetClaims(inner_token);AccessTokenUser user=null;if(claims.get("user")!=null) {user=JsonConverter.jsonStrToObject(claims.get("user"), AccessTokenUser.class);}return ApiResultDTO.success(user);}
//将inner_token 转化为用户信息
public static Map<String,String> verifyTokenAndGetClaims(String jwt_token) {JWT jwt=JWT.create();//jwt.setHeader(JWTHeader.TYPE, "JWT");//默认值jwt.setHeader(JWTHeader.ALGORITHM, "HS256");jwt.setSigner(JWTSignerUtil.createSigner("HS256", SECRET));try {jwt.parse(jwt_token);} catch (Exception e) {e.printStackTrace();throw new RuntimeException("校验jwt_token令牌失败");}if(!jwt.verify()) {throw new RuntimeException("无效的jwt_token");}Map<String,String> result = new HashMap<String,String>();JSONObject jo=jwt.getPayloads();if(jo==null||jo.size()==0) {return result;}for (Map.Entry<String,Object> d: jo.entrySet()) {if(d.getValue()==null) {continue;}result.put(d.getKey(), d.getValue().toString());}return result;}
配置拦截器
@Configuration
public class FiltersConfiguration {@Autowiredprivate ICachedTokenService tokenService;//前后端交互处理-拦截所有api访问请求@Beanpublic FilterRegistrationBean<APIFilter> apiFilterRegistrationBean() {FilterRegistrationBean<APIFilter> registrationBean = new FilterRegistrationBean<APIFilter>();registrationBean.setFilter(new APIFilter(tokenService));registrationBean.addUrlPatterns("/api/*");registrationBean.setOrder(5);return registrationBean;}//前后端交互处理-拦截所有_api访问请求@Beanpublic FilterRegistrationBean<_APIFilter> _apiFilterRegistrationBean() {FilterRegistrationBean<_APIFilter> registrationBean = new FilterRegistrationBean<_APIFilter>();registrationBean.setFilter(new _APIFilter(tokenService));registrationBean.addUrlPatterns("/_api/*");registrationBean.setOrder(6);return registrationBean;}
}
清除缓存
基础数据定义的一些资源变更,需要同步清除其他项目本地缓存
配置清除缓存过滤器
//处理基础数据变更后通知各系统清空本系统的基础数据的缓存@Beanpublic FilterRegistrationBean<FireSysDataChangedEventFilter> fireSysDataChangedEventFilterRegistrationBean() {FilterRegistrationBean<FireSysDataChangedEventFilter> registrationBean = new FilterRegistrationBean<FireSysDataChangedEventFilter>();registrationBean.setFilter(new FireSysDataChangedEventFilter(fireSysDataChangedEventService));registrationBean.addUrlPatterns("/api/*");registrationBean.setOrder(5);return registrationBean;}
定义过滤器
public class FireSysDataChangedEventFilter implements Filter {private Logger logger = LoggerFactory.getLogger(this.getClass());private IFireSysDataChangedEventService fireSysDataChangedEventService;public FireSysDataChangedEventFilter(IFireSysDataChangedEventService fireSysDataChangedEventService) {super();this.fireSysDataChangedEventService = fireSysDataChangedEventService;}@Overridepublic void init(FilterConfig arg0) throws ServletException {}@Overridepublic void destroy() {}@Overridepublic void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {try {chain.doFilter(req, resp);fireSysDataChangedEventIfNecessary(req);}finally {SysDataChanged.reset();}}private void fireSysDataChangedEventIfNecessary(ServletRequest req) {try {if(SysDataChanged.isChanged()) {HttpServletRequest hreq = (HttpServletRequest)req;String uri = hreq.getRequestURI();logger.info("完成请求{}后,基础数据有变更,需要向其他系统发出清除缓存的消息",uri);fireSysDataChangedEventService.sysDataChanged();}}catch(Exception e) {e.printStackTrace();logger.error("fireSysDataChangedEventIfNecessary错误{}",e.getMessage());} }
}
定义清除缓存服务
public interface IFireSysDataChangedEventService {/*** 基础数据发生变更*/void sysDataChanged();}
@Service
@ConfigurationProperties(prefix="docin-sys.config",ignoreUnknownFields=true)
public class FireSysDataChangedEventService implements IFireSysDataChangedEventService {private Logger logger = LoggerFactory.getLogger(this.getClass());@Autowiredprivate ISysDataVersionService sysDataVersionService;@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate AsyncTaskComponent asyncTaskComponent;//这里的url是再配置文件配置的//清理缓存urlsprivate Map<String, List<String>> clearcacheurls = new HashMap<>();public void setClearcacheurls(Map<String, List<String>> clearcacheurls) {this.clearcacheurls = clearcacheurls;}@SuppressWarnings("unchecked")@Overridepublic void sysDataChanged() {long lastTimestamp = System.currentTimeMillis();//更新系统版本时间戳sysDataVersionService.updateLastTimestampOfSysData(lastTimestamp);logger.info("成功更新系统版本时间戳为{}",lastTimestamp);if(MapUtils.isEmpty(clearcacheurls)) return;for (Map.Entry<String,List<String>> e: clearcacheurls.entrySet()) {asyncTaskComponent.runTaskInThreadPool((d)->{String name=(String)d.get("name");List<String> urls=(List<String>)(d.get("urls"));for (String url : urls) {try {restTemplate.getForObject(url, String.class);logger.info("向服务["+name+"].["+url+"]发送清理缓存请求成功");} catch (Exception e2) {logger.error("向服务["+name+"].["+url+"]发送清理缓存请求失败,",e2);}}return null;}, Utils.buildMap("name",e.getKey(),"urls",e.getValue()));}}}
docin-sys.config.clearcacheurls.docin-xfxt[0]=http://localhost:8088/docin-xfxt/syscache/clear
docin-sys.config.clearcacheurls.docin-xfxt[1]=http://localhost:8088/docin-xfxt/syscache/clear
docin-sys.config.clearcacheurls.docin-sys[0]=http://localhost:8088/docin-sys/syscache/clear
docin-sys.config.clearcacheurls.docin-sys[1]=http://localhost:8088/docin-sys/syscache/clear
docin-sys.config.clearcacheurls.docin-portal[0]=http://localhost:8088/docin-portal/syscache/clear
docin-sys.config.clearcacheurls.docin-stat[0]=http://localhost:8088/docin-portal/syscache/clear
docin-sys.config.clearcacheurls.hlw-xfxt[0]=http://localhost:8089/hlw-xfxt/syscache/clear
docin-sys.config.clearcacheurls.hlw-sys[0]=http://localhost:8089/hlw-sys/syscache/clear
docin-sys.config.clearcacheurls.docin-xf-exchange[0]=http://localhost:8080/docin-xf-exchange/syscache/clear
具体清除缓存请求接口
@Api(value="公共基础请求-基础数据缓存处理",tags="公共基础请求")
@RestController
@RequestMapping(value="/syscache")
public class SysDataCacheController {@Autowiredprivate CronClearSysDataCacheJob cronClearSysDataCacheJob;@ApiOperation(value="清理基础数据缓存", httpMethod="GET")@RequestMapping(value="/clear", method=RequestMethod.GET)public ApiResultDTO<RequestMsgTempVO> clearSyscache(HttpServletRequest request){return RestAPITemplate.restapi(()->{cronClearSysDataCacheJob.refreshCache();return null;});}}
清除本地缓存
@Autowiredprivate ISysDataVersionService sysDataVersionService;//本地所获得基础数据最后更新时间戳private volatile long lastSysTimestamp = -1L;private volatile boolean refreshing=false;@Async("taskExecutor")//使用线程池 启动public void refreshCache() {logger.info("定时刷新令牌任务启动");if(this.refreshing) {logger.error("之前刷新任务还在堵塞中....");return;}try {this.refreshing = true;long lastSysTimestampOfServer = sysDataVersionService.getLastTimestampOfSysData();if(this.lastSysTimestamp!=lastSysTimestampOfServer) {this.lastSysTimestamp = lastSysTimestampOfServer;SysDataCacheAspect.clearCache();logger.warn("本地基础数据缓存已经清理,清理后本地基础数据版本为{}",lastSysTimestamp);Thread.sleep(5000);//防止攻击}else {logger.info("本地基础数据缓存的版本与基础数据服务一致,无需清理,本地基础数据版本为{}",lastSysTimestamp);}}catch (InterruptedException e) {e.printStackTrace();}finally {this.refreshing=false;}}
缓存逻辑
@Aspect //定义一个切面
@Configuration
public class SysDataCacheAspect {private static Logger logger = LogManager.getLogger(SysDataCacheAspect.class);private static final Map<String,Boolean> cacheFileNames = new ConcurrentHashMap<String, Boolean>();private static LoadingCache<String,Object> cache = null;static {// CacheLoader 初始化CacheLoader<String, Object> cacheLoader = new CacheLoader<String, Object>() {@Override// load方法的作用是在通过get方法从LoadingCache获取不到值时去加载该值并放入缓存。public Object load(String key) throws Exception {return null;}};cache = CacheBuilder.newBuilder()// 设置容量大小.maximumSize(80000)//默认一天后过期.expireAfterWrite(10, TimeUnit.DAYS).removalListener(new RemovalListener<String, Object>() {@Overridepublic void onRemoval(RemovalNotification<String, Object> notification) {if(notification.getValue()!=null && notification.getValue() instanceof CacheFile) {CacheFile cacheFile = (CacheFile)notification.getValue();removeCacheFile(cacheFile.fileName);}}})// 加载器配置.build(cacheLoader);}private String normalizedArgsStr(Object[] args){if(args==null || args.length==0) {return "";}Object[] normalizedArgs = new Object[args.length];if(args!=null) {for(int i=0;i<args.length;i++) {Object arg = args[i];if(arg instanceof AccessTokenUser) {AccessTokenUser user = (AccessTokenUser)arg;normalizedArgs[i]= user.getUserId();}else {normalizedArgs[i]=arg;}}}return JsonConverter.toJsonStr(normalizedArgs);}@Around("execution(* (@com.xysd.bizbase.annotation.SysDataCache *).*(..)) || execution(@com.xysd.bizbase.annotation.SysDataCache * *(..))")public Object process(ProceedingJoinPoint point) throws Throwable {String className = point.getSignature().getDeclaringTypeName();String methodName = point.getSignature().getName();Object[] args = point.getArgs();String key = className+"_$_"+methodName+"_$_"+(normalizedArgsStr(args));Object cached = cache.getIfPresent(key);if(methodName.endsWith("_dontCache")){return point.proceed(args);}if(cached!=null) {if(cached instanceof CacheFile) {CacheFile cachedFile = (CacheFile)cached;Object cachedData = readCachedData(cachedFile);if(cachedData==null) {//读取缓存失败return point.proceed(args);}else {return cachedData;}}else {return cached;}}else {cached = point.proceed(args);if(cached instanceof ApiResultDTO){if(((ApiResultDTO<?>) cached).getData() == null) return cached;}if(cached!=null) {try {CacheFile cachedFile = cacheToDiskIfNecessary(cached);if(cachedFile!=null) {cache.put(key, cachedFile);}else {cache.put(key, cached);}}catch(Exception e) {logger.error("缓存失败,失败信息{}",e.getMessage());e.printStackTrace();}}return cached;}}private Object readCachedData(CacheFile cachedFile) {String fileName = cachedFile.getFileName();String absolutePath = getAbsoluteCacheFilePath(fileName);File f = new File(absolutePath);InputStream in = null;ObjectInputStream oin = null;try {in = new FileInputStream(f);oin = new ObjectInputStream(in);Object cachedData = oin.readObject();return cachedData;}catch(Exception e) {logger.error("读取缓存序列化文件失败,失败信息:{}",e.getMessage());e.printStackTrace();return null;}finally {Utils.clean(in,oin);}}/*** 当value序列化后占用字节大于50K时写入磁盘进行缓存* @param value* @return*/private CacheFile cacheToDiskIfNecessary(Object value) {int cachThreadshold = 50*1024;ByteArrayOutputStream bos = null ; ObjectOutputStream oos = null;try {bos = new ByteArrayOutputStream();oos = new ObjectOutputStream(bos);oos.writeObject(value);oos.flush();byte[] byteArray = bos.toByteArray();if(byteArray!=null && byteArray.length>cachThreadshold) {return buildCacheFile(byteArray);}else {return null;}}catch(Exception e) {throw new RuntimeException(e);}finally {Utils.clean(bos,oos);}}private CacheFile buildCacheFile(byte[] byteArray) {String fileName = "syscachefile_"+Utils.getUUID("");String absolutePath = getAbsoluteCacheFilePath(fileName);File f = new File(absolutePath);OutputStream out = null;try {if(!f.getParentFile().exists()) {f.getParentFile().mkdirs();}out = new FileOutputStream(f);out.write(byteArray);out.flush();cacheFileNames.put(fileName, true);return new CacheFile(fileName);}catch(Exception e) {throw new RuntimeException(e);}finally {Utils.clean(out);}}private static String getAbsoluteCacheFilePath(String fileName) {String sysCacheBaseDir = Utils.getTmpDirRoot()+"/sysDataCache";return sysCacheBaseDir+"/"+fileName;}public static void removeCacheFile(String fileName) {if(StringUtils.isNoneBlank(fileName)) {cacheFileNames.remove(fileName);String absolutePath = getAbsoluteCacheFilePath(fileName);File f = new File(absolutePath);try {if(f.exists() && f.isFile()) {f.delete();}}catch(Exception e) {//删除失败不做任何处理e.printStackTrace();}}}/*** 清空缓存*/public static final void clearCache() {for(String fileName:cacheFileNames.keySet()) {removeCacheFile(fileName);}cacheFileNames.clear();cache.invalidateAll();}public static class CacheFile implements Serializable {private static final long serialVersionUID = -6926387004863371705L;private String fileName;public CacheFile(String fileName) {super();this.fileName = fileName;}public String getFileName() {return fileName;}}
}
拦截器
Hibernate的拦截器 监听实体变化
public class SysEntityUpdateListener extends EmptyInterceptor {/*** */private static final long serialVersionUID = -7428554904158765594L;@Overridepublic void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {super.onDelete(entity, id, state, propertyNames, types);SysDataChanged.changed();}@Overridepublic boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,String[] propertyNames, Type[] types) {SysDataChanged.changed();return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);}@Overridepublic boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {SysDataChanged.changed();return super.onSave(entity, id, state, propertyNames, types);}@Overridepublic void postFlush(Iterator entities) {super.postFlush(entities);}@Overridepublic int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,String[] propertyNames, Type[] types) {return super.findDirty(entity, id, currentState, previousState, propertyNames, types);}@Overridepublic void onCollectionRemove(Object collection, Serializable key) throws CallbackException {SysDataChanged.changed();super.onCollectionRemove(collection, key);}@Overridepublic void onCollectionUpdate(Object collection, Serializable key) throws CallbackException {SysDataChanged.changed();super.onCollectionUpdate(collection, key);}}
记录是否要清理缓存
public class SysDataChanged {private static final ThreadLocal<Boolean> store = new ThreadLocal<Boolean>();/*** 当前线程已经更改过基础数据*/public static final void changed() {store.set(true);}/*** 重置*/public static final void reset() {store.set(false);}/*** 当前线程是否更改过基础数据* @return*/public static final boolean isChanged() {return Boolean.TRUE.equals(store.get());}}
通过SysEntityUpdateListener监听实体变化,更改SysDataChanged.store.set(true)。当用户请求后端接口时,通过FireSysDataChangedEventFilter过滤器判断SysDataChanged.store.get()==true,从而触发restTemplate.getForObject(url, String.class)请求,更新各个项目本地的基础数据缓存SysDataCacheAspect.clearCache()。