4.5 页面静态化
4.5.1 什么是页面静态化
根据课程发布的操作流程,执行课程发布后要将课程详情信息页面静态化,生成html页面上传至文件系统。
什么是页面静态化?
课程预览功能通过模板引擎技术在页面模板中填充数据,生成html页面,这个过程是当客户端请求服务器时服务器才开始渲染生成html页面,最后响应给浏览器,这个过程支持并发是有限的。
页面静态化则强调将生成html页面的过程提前,提前使用模板引擎技术生成html页面,当客户端请求时直接请求html页面,由于是静态页面可以使用nginx、apache等高性能的web服务器,并发性能高。
什么时候能用页面静态化技术?
当数据变化不频繁,一旦生成静态页面很长一段时间内很少变化,此时可以使用页面静态化。因为如果数据变化频繁,一旦改变就需要重新生成静态页面,导致维护静态页面的工作量很大。
根据课程发布的业务需求,虽然课程发布后仍可以修改课程信息,但需要经过课程审核,且修改频度不大,所以适合使用页面静态化。
4.5.2 静态化测试
下边使用freemarker技术对页面静态化生成html页面。
在内容管理service工程中添加freemarker依赖
XML org.springframework.boot spring-boot-starter-freemarker |
---|
编写测试方法
Javapackage com.xuecheng.content; import com.xuecheng.content.model.dto.CoursePreviewDto; import com.xuecheng.content.service.CoursePublishService; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; /** * @author Mr.M * @version 1.0 * @description freemarker测试 * @date 2022/9/20 18:42 */ @SpringBootTest public class FreemarkerTest { @Autowired CoursePublishService coursePublishService; //测试页面静态化 @Test public void testGenerateHtmlByTemplate() throws IOException, TemplateException { //配置freemarker Configuration configuration = new Configuration(Configuration.getVersion()); //加载模板 //选指定模板路径,classpath下templates下 //得到classpath路径 String classpath = this.getClass().getResource(“/”).getPath(); configuration.setDirectoryForTemplateLoading(new File(classpath + “/templates/”)); //设置字符编码 configuration.setDefaultEncoding(“utf-8”); //指定模板文件名称 Template template = configuration.getTemplate(“course_template.ftl”); //准备数据 CoursePreviewDto coursePreviewInfo = coursePublishService.getCoursePreviewInfo(2L); Map<String, Object> map = new HashMap<>(); map.put(“model”, coursePreviewInfo); //静态化 //参数1:模板,参数2:数据模型 String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map); System.out.println(content); //将静态化内容输出到文件中 InputStream inputStream = IOUtils.toInputStream(content); //输出流 FileOutputStream outputStream = new FileOutputStream(“D:\develop\test.html”); IOUtils.copy(inputStream, outputStream); } } |
---|
执行测试方法,观察D:\develop\test.html 是否成功生成。
4.5.3 上传文件测试
静态化生成文件后需要上传至分布式文件系统,根据微服务的职责划分,媒资管理服务负责维护文件系统中的文件,所以内容管理服务对页面静态化生成html文件需要调用媒资管理服务的上传文件接口。如下图:
微服务之间难免会存在远程调用,在Spring Cloud中可以使用Feign进行远程调用, Feign是什么?
Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
下边先准备Feign的开发环境,在内容管理content-service工程添加依赖:
XML com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-openfeign io.github.openfeign feign-httpclient io.github.openfeign.form feign-form 3.8.0 io.github.openfeign.form feign-form-spring 3.8.0 |
---|
在nacos配置feign-dev.yaml公用配置文件
YAMLfeign: client: config: default: # default全局的配置 loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息 httpclient: enabled: true # 开启feign对HttpClient的支持 max-connections: 200 # 最大的连接数 max-connections-per-route: 50 # 每个路径的最大连接数 |
---|
在内容管理service工程和内容管理api工程都引入此配置文件
YAMLshared-configs: - data-id: feign-${spring.profiles.active}.yaml group: xuecheng-plus-common refresh: true |
---|
在内容管理service工程配置feign支持Multipart,拷贝课程资料下的MultipartSupportConfig 到content-service工程下的config包下。
编写feign接口
Javapackage com.xuecheng.content.feignclient; import com.xuecheng.content.config.MultipartSupportConfig; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; /** * @description 媒资管理服务远程接口 * @author Mr.M * @date 2022/9/20 20:29 * @version 1.0 */ @FeignClient(value = “media-api”,configuration = MultipartSupportConfig.class) public interface MediaServiceClient { @RequestMapping(value = “/media/upload/coursefile”,consumes = MediaType.MULTIPART_FORM_DATA_VALUE) String uploadFile(@RequestPart(“filedata”) MultipartFile upload,@RequestParam(value = “folder”,required=false) String folder,@RequestParam(value = “objectName”,required=false) String objectName); } |
---|
在启动类添加@EnableFeignClients注解
Java@EnableFeignClients(basePackages={“com.xuecheng.content.feignclient”}) |
---|
编写测试方法
Javapackage com.xuecheng.content; import com.xuecheng.content.feignclient.MediaServiceClient; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.commons.CommonsMultipartFile; import java.io.File; import java.io.FileInputStream; import java.io.OutputStream; /** * @author Mr.M * @version 1.0 * @description 测试使用feign远程上传文件 * @date 2022/9/20 20:36 */ @SpringBootTest public class FeignUploadTest { @Autowired MediaServiceClient mediaServiceClient; //远程调用,上传文件 @Test public void test() { MultipartFile multipartFile = MultipartSupportConfig.getMultipartFile(new File(“D:\develop\test.html”)); mediaServiceClient.uploadFile(multipartFile,“course”,“test.html”); } } |
---|
执行测试方法,上传文件成功,进入minIO查看文件
访问:http://192.168.101.65:9000/mediafiles/course/74b386417bb9f3764009dc94068a5e44.html
查看是否可以正常访问。
4.5.4 课程静态化开发
课程页面静态化和静态页面远程上传测试通过,下一步开发课程静态化功能,最终使用消息处理SDK去调度执行。
4.5.4.1 添加消息
课程发布操作使用本地事务保存课程发布信息、添加消息表。
回到当初编写课程发布时的代码,如下:
Java@Transactional @Override public void publish(Long companyId, Long courseId) { //约束校验 //查询课程预发布表 CoursePublishPre coursePublishPre = coursePublishPreMapper.selectById(courseId); if(coursePublishPre == null){ XueChengPlusException.cast(“请先提交课程审核,审核通过才可以发布”); } //本机构只允许提交本机构的课程 if(!coursePublishPre.getCompanyId().equals(companyId)){ XueChengPlusException.cast(“不允许提交其它机构的课程。”); } //课程审核状态 String auditStatus = coursePublishPre.getStatus(); //审核通过方可发布 if(!“202004”.equals(auditStatus)){ XueChengPlusException.cast(“操作失败,课程审核通过方可发布。”); } //保存课程发布信息 saveCoursePublish(courseId); //保存消息表 saveCoursePublishMessage(courseId); //删除课程预发布表对应记录 coursePublishPreMapper.deleteById(courseId); } |
---|
我们要填充的saveCoursePublishMessage(courseId)方法,如下:
Java /** * @description 保存消息表记录 * @param courseId 课程id * @return void * @author Mr.M * @date 2022/9/20 16:32 */ private void saveCoursePublishMessage(Long courseId){ MqMessage mqMessage = mqMessageService.addMessage(CoursePublishTask.MESSAGE_TYPE, String.valueOf(courseId), null, null); if(mqMessage!=null){ XueChengPlusException.cast(CommonError.UNKOWN_ERROR); } } |
---|
下边进行测试:
发布一门课程,观察消息表是否正常添加消息。
需要手动修改课程审核状态为审核通过执行发布操作,发布后可以修改发布状态为下架重新发布测试。
4.5.4.2 课程静态化实现
课程静态化包括两部分工作:生成课程静态化页面,上传静态页面到文件系统。
在课程发布的service编写这两部分内容,最后通过消息去调度执行。
1、接口定义
Java/** * @description 课程静态化 * @param courseId 课程id * @return File 静态化文件 * @author Mr.M * @date 2022/9/23 16:59 / public File generateCourseHtml(Long courseId); /* * @description 上传课程静态化页面 * @param file 静态化文件 * @return void * @author Mr.M * @date 2022/9/23 16:59 */ public void uploadCourseHtml(Long courseId,File file); |
---|
2、接口实现
将之前编写的静态化测试代码以及上传静态文件测试代码拷贝过来使用
Java@Override public File generateCourseHtml(Long courseId) { //静态化文件 File htmlFile = null; try { //配置freemarker Configuration configuration = new Configuration(Configuration.getVersion()); //加载模板 //选指定模板路径,classpath下templates下 //得到classpath路径 String classpath = this.getClass().getResource(“/”).getPath(); configuration.setDirectoryForTemplateLoading(new File(classpath + “/templates/”)); //设置字符编码 configuration.setDefaultEncoding(“utf-8”); //指定模板文件名称 Template template = configuration.getTemplate(“course_template.ftl”); //准备数据 CoursePreviewDto coursePreviewInfo = this.getCoursePreviewInfo(courseId); Map<String, Object> map = new HashMap<>(); map.put(“model”, coursePreviewInfo); //静态化 //参数1:模板,参数2:数据模型 String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map); // System.out.println(content); //将静态化内容输出到文件中 InputStream inputStream = IOUtils.toInputStream(content); //创建静态化文件 htmlFile = File.createTempFile(“course”,“.html”); log.debug(“课程静态化,生成静态文件:{}”,htmlFile.getAbsolutePath()); //输出流 FileOutputStream outputStream = new FileOutputStream(htmlFile); IOUtils.copy(inputStream, outputStream); } catch (Exception e) { e.printStackTrace(); } return htmlFile; } @Override public void uploadCourseHtml(Long courseId, File file) { MultipartFile multipartFile = MultipartSupportConfig.getMultipartFile(file); String course = mediaServiceClient.uploadFile(multipartFile, “course”, courseId+“.html”); } |
---|
完善课程发布任务CoursePublishTask类的代码:
Java//生成课程静态化页面并上传至文件系统 public void generateCourseHtml(MqMessage mqMessage,long courseId){ log.debug(“开始进行课程静态化,课程id:{}”,courseId); //消息id Long id = mqMessage.getId(); //消息处理的service MqMessageService mqMessageService = this.getMqMessageService(); //消息幂等性处理 int stageOne = mqMessageService.getStageOne(id); if(stageOne == 1){ log.debug(“课程静态化已处理直接返回,课程id:{}”,courseId); return ; } //生成静态化页面 File file = coursePublishService.generateCourseHtml(courseId); //上传静态化页面 if(file!=null){ coursePublishService.uploadCourseHtml(courseId,file); } //保存第一阶段状态 mqMessageService.completedStageOne(id); } |
---|
4.5.4.3 测试
1、启动网关、媒资管理服务工程。
2、在内容管理api工程的启动类上配置FeignClient
Java@EnableFeignClients(basePackages={“com.xuecheng.content.feignclient”}) |
---|
启动内容管理接口工程。
在CoursePublishTask类的execute方法中打上断点。
3、发布一门课程,保存消息表存在未处理的处理。
4、启动xxl-job调度中心、启动课程发布任务,等待定时调度。
5、观察任务调度日志,观察任务是否可以正常处理。
6、处理完成进入文件系统,查询mediafiles桶内是否存在以课程id命名的html文件
如果不存在说明课程静态化存在问题,再仔细查看执行日志,排查问题。
如果存在,用浏览器访问html文件是否可以正常浏览,下图表示可以正常浏览。
页面还没有样式,需要在nginx配置虚拟目录,在www.xucheng-plus.com下配置:
Plain Text location /course/ { proxy_pass http://fileserver/mediafiles/course/; } |
---|