1.静态页面模块枚举
public enum ModuleEnum
{
index, // 首页模块
locale, // 现场课程模块
video, // 视频模块
announcement, // 资讯模块
bwHelp, // 软件助手
;
}
2.新建页面处理器
import java.io.File;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.ResourceUtils;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
/**
* 页面处理器
*/
public abstract class PageProcess implements InitializingBean
{
private static final Logger log = LoggerFactory.getLogger(PageProcess.class);
/**
* 页面处理集合
* -key moduleName
*/
private static final Map<String, PageProcess> PROCESSES = new HashMap<>();
/**
* 指向static目录
*/
protected static File staticFile;
static
{
try
{
staticFile = FileUtil.file(ResourceUtils.getURL("classpath:").getPath(), "static");
} catch (FileNotFoundException e)
{
log.error("指向static目录失败", e);
}
}
/**
* 初始化完毕之后注册到页面处理容器中
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception
{
ModuleEnum module = this.getModule();
if (module != null)
{
PageProcess.PROCESSES.put(module.toString(), this);
}
}
/**
* 设置静态页面的基础数据
*/
protected Map<String, Object> baseData()
{
Map<String, Object> data = new HashMap<>();
return data;
}
/*-------------对外方法---------------*/
/**
* 刷新所有静态页面
*/
public static void refreshAll()
{
refresh(null);
}
/**
* 刷新页面
* -模块为空刷新所有页面
* -ids为空刷新某个模块所有页面
* @param id
*/
public static void refresh(ModuleEnum module, Integer... ids)
{
if (module == null)
{
File htmlFile = FileUtil.file(staticFile, "html");
FileUtil.clean(htmlFile);
} else
{
String moduleName = module.toString();
if (ids == null || ids.length == 0)
{
// 删除整个模块页面
File moduleDir = FileUtil.file(staticFile, String.format("/html/%s", moduleName));
FileUtil.clean(moduleDir);
} else
{
// 精确删除某个页面
for (Integer id : ids)
{
File pageFile = FileUtil.file(staticFile, String.format("/html/%s/%s.html", moduleName, id));
FileUtil.del(pageFile);
}
}
}
}
/**
* 访问页面
* @param module 模块
* @param pageName 页面名称 例如1.html
* @throws Exception
*/
public static String view(String moduleName, String pageName) throws Exception
{
// 参数校验
Assert.notBlank(moduleName);
Assert.notBlank(pageName);
Assert.isTrue(staticFile != null && staticFile.exists(), "static目录不存在");
// xhtml处理成html
pageName = pageName.replace("xhtml", "html");
// 获取对应页面处理器
PageProcess pageProcess = PROCESSES.get(moduleName);
Assert.notNull(pageProcess, "没有找到对应模块的页面处理器");
// 对应静态页面url
String pageUrl = "/html/" + moduleName + "/" + pageName;
File pageFile = FileUtil.file(staticFile, pageUrl);
if (!pageFile.exists())
{
// 生成静态化页面
String pageHtml = pageProcess.createPage(pageName);
FileUtil.writeString(pageHtml, pageFile, StandardCharsets.UTF_8);
}
// 为了处理资讯访问量类似问题增加访问钩子
pageProcess.viewHooks(pageName);
return "forward:" + pageUrl;
}
/*-------------子类实现---------------*/
/**
* 要处理的页面所属模块
* @return
*/
protected abstract ModuleEnum getModule();
/**
* 生成html页面
* @param pageName
* @return
*/
protected abstract String createPage(String pageName) throws Exception;
/**
* 访问钩子,默认不做任何事,可以用来追加访问量
* @param pageName
*/
protected void viewHooks(String pageName)
{
}
}
3.新建子类继承页面处理器1
@Component
public class IndexPageProcess extends PageProcess
{
@Autowired
private CourseService courseService;
@Override
public ModuleEnum getModule()
{
return ModuleEnum.index;
}
@Override
protected String createPage(String pageName) throws Exception
{
// 构建页面数据
Map<String, Object> pageData = new HashedMap<>();
this.courseService.indexDetail(pageData);
// 模板加数据渲染成html页面
String templateName = this.getModule().toString() + "/" + pageName;
String html = FreemarkerUtil.parseTpl(templateName, pageData);
return html;
}
}
4.新建子类继承页面处理器2
@Component
public class VideoPageProcess extends PageProcess
{
@Autowired
private CourseVideoService courseVideoService;
@Override
public ModuleEnum getModule()
{
return ModuleEnum.video;
}
@Override
protected String createPage(String pageName) throws Exception
{
// 课程id
Integer courseId = Integer.parseInt(StringUtils.substringBeforeLast(pageName, "."));
// 构建页面数据
Map<String, Object> params = this.baseData();
CourseVideoPortalInfoVO courseInfo = this.courseVideoService.getCourseVideoPortalInfo(courseId);
params.put("courseInfo", courseInfo);
// 模板加数据渲染成html页面
String templateName = this.getModule().toString() + "/videodetail.html";
String html = FreemarkerUtil.parseTpl(templateName, params);
return html;
}
}
5.新建自定义静态化页面注解
/**
* 标注在控器器方法上,默认刷新首页模块,也可以指定刷新模块,刷新指定页面默认取方法的第一个参数的id属性
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RefreshPage
{
/**
* 刷新的模块
* @return
*/
ModuleEnum module() default ModuleEnum.index;
/**
* 是否刷新首页
* @return
*/
boolean refreshIndex() default true;
/**
* 标识所在参数下标
* @return
*/
int argIndex() default 0;
/**
* 标识取值表达式
* @return
*/
String idExpress() default "id";
}
6. 切面
@Aspect
@Component
@Slf4j
public class RefreshPageAspect
{
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
private void pointcut()
{
throw new UnsupportedOperationException();
}
@Around(value = "@annotation(refreshPage)", argNames = "refreshPage")
public Object refreshPage(ProceedingJoinPoint pjp, RefreshPage refreshPage) throws Throwable
{
// 执行方法
Object result = pjp.proceed();
// 刷新页面
ModuleEnum module = refreshPage.module();
try
{
if (module.equals(ModuleEnum.index))
{
PageProcess.refresh(ModuleEnum.index);
log.info("刷新静态首页成功");
} else
{
// 获取页面标识
int argIndex = refreshPage.argIndex();
Object arg = pjp.getArgs()[argIndex];
String idExpress = StrUtil.emptyIfNull(refreshPage.idExpress());
BeanPath resolver = new BeanPath(idExpress);
Integer id = Integer.parseInt(resolver.get(arg).toString());
// 清理页面
PageProcess.refresh(module, id);
// 根据标识判断是否级联刷新首页
if (refreshPage.refreshIndex())
{
PageProcess.refresh(ModuleEnum.index);
log.info("刷新静态首页成功");
}
log.info("刷新模块[{}]的静态页面成功,页面标识[{}]", module.toString(), id);
}
} catch (Exception e)
{
log.error("刷新模块[{}]的静态页面异常:", module.toString(), e);
}
return result;
}
}
7.FreemarkerUtil
@Slf4j
public class FreemarkerUtil
{
private static final Configuration CONFIG = SpringContextUtil.getBean(Configuration.class);
/**
* 根据模板和数据生成html页面
* @param templateUrl 模板路径
* @param params 页面参数
* @return
* @throws IOException
* @throws ParseException
* @throws MalformedTemplateNameException
* @throws TemplateNotFoundException
*/
public static String parseTpl(String templateUrl, Object params) throws Exception
{
log.info("渲染模板[{}],数据[{}]", templateUrl, JSON.toJSONString(params));
Template template = CONFIG.getTemplate(templateUrl);
return FreeMarkerTemplateUtils.processTemplateIntoString(template, params);
}
}
8.CourseBannerDO
public class CourseBannerDO extends BaseDO<CourseBannerDO>
{
private static final long serialVersionUID = 1L;
/**
* id主键
*/
@TableId(type = IdType.AUTO)
@ApiModelProperty("id主键")
private Integer id;
/**
* 图片名称
*/
@ApiModelProperty("图片名称")
private String name;
/**
* 图片路径
*/
@ApiModelProperty("图片路径")
private String imgUrl;
/**
* 跳转目标地址
*/
@ApiModelProperty("跳转目标地址")
private String link;
}
9.CourseVideoForm
@Data
@ToString
@ApiModel("视频课程添加表单模型")
public class CourseVideoForm implements Serializable
{
private static final long serialVersionUID = -9024302026705244796L;
@ApiModelProperty("视频课程id")
private Integer id;
@ApiModelProperty("课程id")
private Integer courseId;
@ApiModelProperty("课程名称")
@NotBlank
@Size(min = 1, max = 100, message = "最小长度不低于1位,最大长度不超过100位")
private String courseName;
}
10.添加注解刷新静态化页面
@PostMapping("/save")
@ApiOperation(value = "新建")
@RefreshPage
public void save(@RequestBody CourseBannerDO courseBanner)
{
log.info("{}新建宣传图courseBanner={}:", getUid(), courseBanner.toString());
courseBannerService.save(courseBanner);
}
@PostMapping("/update")
@ApiOperation(value = "更新课程")
@RefreshPage(module = ModuleEnum.video, argIndex = 0, refreshIndex = true, idExpress = "courseId")
public R update(@Valid @RequestBody CourseVideoForm courseVideoForm)
{
log.info("更新课程courseId:" + courseVideoForm.getCourseId());
courseVideoService.update(courseVideoForm);
return R.success();
}
- @RefreshPage
后面不跟参数,默认刷新某个模块所有页面 - @RefreshPage(module = ModuleEnum.index, argIndex = 0, refreshIndex = true, idExpress = “courseId”)
refreshIndex :代表是否刷新首页
idExpress :代表即将更新的静态化页面的名称
11.路由跳转到静态页面
@Controller
@RequestMapping("/ke")
@Api(tags = "静态页面控制器")
public class StaticController
{
private static final Logger log = LoggerFactory.getLogger(StaticController.class);
@GetMapping("/{moduleName}/{pageName}")
@ApiOperation(value = "跳转到对应静态页面")
public String toStaticPage(@PathVariable String moduleName, @PathVariable String pageName)
{
try
{
return PageProcess.view(moduleName, pageName);
} catch (Exception e)
{
log.error("跳转到静态页面[{}/{}]异常:", moduleName, pageName, e);
// 对于静态页面出现业务错误跳转到404页面
return CommonConstant.REDIRECT_PREFIX + CommonConstant.URL_404;
}
}
@ResponseBody
@GetMapping("/refresh.html")
@ApiOperation(value = "刷新所有页面(供调试)")
public R refresh()
{
PageProcess.refreshAll();
return R.success();
}
}