Loading... # 说明 利用Freemarker 与 SpringBoot 实现页面静态化,并在过滤器中,判断是否存在静态页、如果静态页面已经存在。直接使用静态页面、若不存在静态页使用动态页面渲染 使用@StaticPage 实现静态页,使用@ClearStaticPage 在页面数据更新时清理静态页面 # 1. 引入相关依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> ``` # 2. 基本的配置项 ```yaml spring: freemarker: # 关闭模板缓存,方便测试 cache: false settings: #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试 template_update_delay: 0 charset: UTF-8 content-type: text/html; charset=utf-8 suffix: .ftl template-loader-path: classpath:/templates check-template-location: true expose-request-attributes: true expose-session-attributes: true request-context-attribute: request ``` # 3. 自定义配置项 ```yml # 页面静态化配置 static-page: enabled: true localPath: D:/home/static ``` # 4. 自定义annotation ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface StaticPage { } ``` ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ClearStaticPage { public String value() default ""; } ``` # 5. 基于切面实现静态页面的切换与清理 ```java /** * 静态页处理 */ @Aspect @Component public class StaticPageAspect { private static final Logger log = LoggerFactory.getLogger(StaticPageAspect.class); @Value("${static-page.enabled:false}") private boolean enabled; @Value("${static-page.localPath:#{null}}") private String localPath; @Value("${spring.freemarker.suffix:#{null}}") private String templateSuffix; // 配置织入点 @Pointcut("@annotation(net.tonggeng.api.annotation.StaticPage)") public void logPointCut() { } /** * 处理完请求后执行 * * @param joinPoint 切点 */ @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult") public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) { handleLog(joinPoint, null, jsonResult); } /** * 拦截异常操作 * * @param joinPoint 切点 * @param e 异常 */ @AfterThrowing(value = "logPointCut()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Exception e) { handleLog(joinPoint, e, null); } protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) { try { // 获得注解 StaticPage annotation = getAnnotation(joinPoint); if (annotation == null || !enabled) { return; } if(jsonResult instanceof ModelAndView){ ModelAndView modelAndView = (ModelAndView) jsonResult; HttpServletRequest request = Objects.requireNonNull(getHttpServletRequest()); String requestUrl = request.getRequestURI(); String save = localPath + "/" + Context.getLocaleStr(getHttpServletRequest()) +requestUrl; String templateFile = modelAndView.getViewName()+templateSuffix; // 调用异步任务生成静态文件 // 这里是使用异步任务生产的静态页面、可直接调用generatingStaticFiles方法 AsyncManager.me().execute(generatingStaticFiles(save, modelAndView.getModel(), templateFile, Context.getRequestLocale(request))); } } catch (Exception exp) { // 记录本地异常日志 log.error("==前置通知异常=="); log.error("异常信息:{}", exp.getMessage()); exp.printStackTrace(); } } /** * 是否存在注解,如果存在就获取 */ private StaticPage getAnnotation(JoinPoint joinPoint) throws Exception { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { return method.getAnnotation(StaticPage.class); } return null; } /** * 生成静态文件 * @return 任务task */ public static TimerTask generatingStaticFiles(String save, Map<String, Object> datas, String templateName, Locale locale) { return new TimerTask() { @Override public void run() { //设置异步任务的本地化变量 LocaleContextHolder.setLocale(locale); //生成静态页面 FreemarkerUtils.geneFileStr(save, datas, templateName); } }; } private HttpServletRequest getHttpServletRequest() { try { // 这种方式获取的HttpServletRequest是线程安全的 return ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); } catch (Exception e) { return null; } } } ``` ```java /** * 清理静态页面 */ @Aspect @Component public class ClearStaticPageAspect { private static final Logger log = LoggerFactory.getLogger(ClearStaticPageAspect.class); @Value("${static-page.enabled:false}") private boolean enabled; @Value("${static-page.localPath:#{null}}") private String localPath; @Autowired private RemoteStaticPageService remoteStaticPageService; // 配置织入点 @Pointcut("@annotation(net.tonggeng.api.annotation.ClearStaticPage)") public void logPointCut() { } /** * 处理完请求后执行 * * @param joinPoint 切点 */ @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult") public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) { handleLog(joinPoint, null, jsonResult); } /** * 拦截异常操作 * * @param joinPoint 切点 * @param e 异常 */ @AfterThrowing(value = "logPointCut()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Exception e) { handleLog(joinPoint, e, null); } protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) { try { // 获得注解 ClearStaticPage annotation = getAnnotation(joinPoint); if (annotation == null || StrUtil.isEmpty(annotation.value())) { return; } // 这里是使用异步任务生产的静态页面、可直接调用clearStaticFile方法 AsyncManager.me().execute(clearStaticFile(remoteStaticPageService, joinPoint, annotation.value())); } catch (Exception exp) { // 记录本地异常日志 log.error("==前置通知异常=="); log.error("异常信息:{}", exp.getMessage()); exp.printStackTrace(); } } /** * 清理静态文件 * @return 任务task */ public static TimerTask clearStaticFile(RemoteStaticPageService remoteStaticPageService,JoinPoint joinPoint , String path) { //设置request对象线程共享 HttpServletRequest req = getHttpServletRequest(); return new TimerTask() { @Override public void run(){ AsyncManager.setAsyncLocalRequest(req); AjaxResult result = AjaxResult.error(); if ("*".equals(path)) { result = remoteStaticPageService.clearStaticPage(); } else if(path.contains("{")) { String tempPath = path; HttpServletRequest request = AsyncManager.getAsyncLocalRequest(); List<Annotation> annotations = getMethodAnnotations(joinPoint); if(annotations != null && annotations.size() > 0){ for(Annotation ann: annotations){ PathVariable pathVariable = (PathVariable) ann; assert request != null; tempPath = tempPath.replace("{"+pathVariable.value()+"}", request.getParameter(pathVariable.value())); } }else{ assert request != null; try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { char[] buff = new char[1024]; int length = 0; String x = null; while ((length = reader.read(buff)) != -1) { x = new String(buff, 0, length); } // 获取参数值 JSONObject json = JSONUtil.parseObj(x); String fieldName = tempPath.substring(tempPath.indexOf("{") + 1, tempPath.indexOf("}")); tempPath = tempPath.replace("{" + fieldName + "}", json.getStr(fieldName)); } catch (Exception e) { e.printStackTrace(); } } result = remoteStaticPageService.delStaticPage(tempPath); } else { result = remoteStaticPageService.delStaticPage(path); } log.info("清理["+path+"]缓存:"+ result.getMsg()); } }; } /** * 是否存在注解,如果存在就获取 */ private ClearStaticPage getAnnotation(JoinPoint joinPoint) throws Exception { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { return method.getAnnotation(ClearStaticPage.class); } return null; } private static List<Annotation> getMethodAnnotations(JoinPoint joinPoint){ Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { Annotation[][] annotations = method.getParameterAnnotations(); List<Annotation> annList = new ArrayList<>(); for(Annotation[] annArr: annotations){ for(Annotation ann: annArr){ if(ann instanceof PathVariable){ annList.add(ann); } } } return annList; } return null; } private static HttpServletRequest getHttpServletRequest() { try { // 这种方式获取的HttpServletRequest是线程安全的 return ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); } catch (Exception e) { return null; } } } ``` 生产静态页的方法 ```java public static void geneFileStr(String save, Map<String, Object> datas, String templateName){ Configuration cfg = SpringUtils.getBean(Configuration.class); Writer file = null; try { //创建路径 FileUtil.mkdir(new File(save).getParentFile()); //加载具体模板文件,获得模板对象 Template template = cfg.getTemplate(templateName); //编码格式 cfg.setDefaultEncoding("utf-8"); file = new FileWriter(new File(save)); //生成文件 template.process(datas, file); } catch (Exception e) { e.printStackTrace(); }finally { if (file != null) { try{ file.close(); }catch (Exception ignored){ } } } } ``` # 6. 把localPath加入SpringBoot 静态路径 ```java /** * 静态资源配置 */ @Configuration public class StaticPageConfig implements WebMvcConfigurer { @Value("${static-page.enabled:false}") private boolean enabled; @Value("${static-page.localPath:#{null}}") private String localPath; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if(enabled) { /** 本地生成的静态资源路径 */ registry.addResourceHandler( "/static-page/**").addResourceLocations("file:" + localPath + "/"); } } } ``` # 7. 配置拦截器动态判断静态页面是否存在 ```java @Component public class StaticPageFilter extends OncePerRequestFilter { @Value("${static-page.enabled:false}") private boolean enabled; @Value("${static-page.localPath:#{null}}") private String localPath; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { if(enabled){ String requestUri = request.getRequestURI().equals("/") ? "/index.html":request.getRequestURI(); String requestLocale = Context.getLocaleStr(request); String localFile = localPath + "/" + requestLocale + requestUri; if(FileUtil.exist(localFile)){ //如果本地静态资源存在则跳转本地静态资源 request.getRequestDispatcher("/static-page"+ "/" + requestLocale + requestUri) .forward(request,response); return; } } chain.doFilter(request, response); } } ``` # 8. 具体的使用方法 生成静态页面 ```java @StaticPage @RequestMapping("/news.html") public ModelAndView index(){ return new ModelAndView("notice/news") .addObject("list",remoteNewsService.page(new News())); } ``` 清理静态页面 ```java @DeleteMapping("{id}") // 指定清理的页面路径 @ClearStaticPage("/notice/news.html") public AjaxResult delete(@PathVariable("id") String ids){ return toAjax(newsService.removeByIds(Arrays.asList(ids.split(",")))); } @PutMapping() // 带有参数的清理路径 其中newsId为News属性 @ClearStaticPage("/notice/news/{newsId}.html") public AjaxResult update(@RequestBody News news){ return toAjax(newsService.updateById(news)); } ``` 最后修改:2020 年 11 月 19 日 © 禁止转载 赞 2 如果觉得我的文章对你有用,请随意赞赏