静态化优化方案设计
课程主页的访问人数非常多, 以不发请求静态页面代替要发请求静态页面或者动态页面.没有对后台数据获取.
课程详情页:只要课程信息不改,详情页就不会改变.
官网主页:一定的时间段是不可变
招聘主页:一定的时间段是不可变
职位详情:只要职位信息不改,详情页就不会改变.
有的页面访问人数很多,但是在一定时间段内不会改变(数据没变化).页面静态化.
静态化好处
①降低数据库或缓存压力
②提高响应速度,增强用户体验.
基本分析
静态页面=模板(结构)+数据(内容)
静态页面生成时机:
①当部署并启动,需要在后台管理里面触发一个按钮,初始化静态页面. 初始化
②当数据(类型,广告等)发生改变后(后台做了增删改),要覆盖原来静态页面. 替换
方案:页面静态化,通过模板技术来实现.
模板技术:freemaker,velocity,thymeleaf等
模板(velocity)+数据=静态页面(初始化,数据改变)
Dfs技术-fastdfs
Client
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>2.1.0</version>
</dependency>
@FeignClient(value = "HRM-FASTDFS",configuration = FeignMultipartSupportConfig.class,
//@FeignClient(value = "HRM-FASTDFS",configuration = FeignClientsConfiguration.class,
fallbackFactory = FastDfsClientHystrixFallbackFactory.class
)
@RequestMapping("/fastdfs")
public interface FastDfsClient {
// @RequestMapping(value="/upload",method= RequestMethod.POST)
@PostMapping(value="/upload", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}
, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String upload(@RequestPart("file") MultipartFile file);
/**
* 删除对象信息
* @return
*/
@RequestMapping(value="/delete",method=RequestMethod.DELETE)
AjaxResult delete(@RequestParam("path") String path);
//获取用户
@RequestMapping(value = "/download",method = RequestMethod.GET,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
Response download(@RequestParam("path")String path); //直接把流写到response
}
Service
package org.hhw.hrm.controller;
import org.hhw.hrm.client.FastDfsClient;
import org.hhw.hrm.util.AjaxResult;
import org.hhw.hrm.util.FastDfsApiOpr;
import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@RestController
@RequestMapping("/fastdfs")
public class FastDfsController {
Logger logger = LoggerFactory.getLogger(FastDfsController.class);
// @PostMapping("/upload")
// public String upload(@RequestParam("file") MultipartFile file) {
@PostMapping(value="/upload", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}
, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String upload(@RequestPart("file") MultipartFile file) {
try {
String fileName = file.getOriginalFilename(); // 1.png
String extName = fileName.substring(fileName.lastIndexOf(".")+1);
System.out.println(extName);
return FastDfsApiOpr.upload(file.getBytes(),extName);
}catch (Exception e){
e.printStackTrace();
logger.error("error...."+e.getMessage());
}
return null;
}
@DeleteMapping("/delete")
public AjaxResult delete(@RequestParam("path") String path) {
///group1/xxxx
try {
String pathTmp = path.substring(1); // goup1/xxxxx/yyyy
String groupName = pathTmp.substring(0, pathTmp.indexOf("/")); //goup1
String remotePath = pathTmp.substring(pathTmp.indexOf("/")+1);// /xxxxx/yyyy
System.out.println(groupName);
System.out.println(remotePath);
FastDfsApiOpr.delete(groupName,remotePath);
return AjaxResult.me();
}catch (Exception e){
e.printStackTrace();
logger.error("error...."+e.getMessage());
return AjaxResult.me().setSuccess(false).setMessage(e.getMessage());
}
}
@GetMapping(value = "/download",consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void download(@RequestParam("path")String path, HttpServletResponse response) {
String pathTmp = path.substring(1); // goup1/xxxxx/yyyy
String groupName = pathTmp.substring(0, pathTmp.indexOf("/")); //goup1
String remotePath = pathTmp.substring(pathTmp.indexOf("/")+1);// xxxxx/yyyy
System.out.println(groupName);
System.out.println(remotePath);
OutputStream os = null;
InputStream is = null;
try {
byte[] datas = FastDfsApiOpr.download(groupName, remotePath);
os = response.getOutputStream(); //直接给以流方式进行返回
is = new ByteInputStream(datas,datas.length);
IOUtils.copy(is,os);
}catch (Exception e){
e.printStackTrace();
logger.error("error...."+e.getMessage());
}
finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
课程主页静态化实现
测试方法初始化
数据改变重新生成
技术方案说明:
1、平台包括多个站点,页面归属不同的站点。
2、发布一个页面应将该页面发布到所属站点的服务器上。
3、每个站点服务部署cms client程序,并与交换机绑定,绑定时指定站点Id为routingKey。 指定站点id为routingKey就可以实现cms client只能接收到所属站点的页面发布消息。
4、页面发布程序向MQ发布消息时指定页面所属站点Id为routingKey,将该页面发布到它所在服务器上的cms client。
路由模式分析如下:
发布一个页面,需发布到该页面所属的每个站点服务器,其它站点服务器不发布。
比如:发布一个门户的页面,需要发布到每个门户服务器上,而用户中心服务器则不需要发布。 所以本项目采用routing模式,用站点id作为routingKey,这样就可以匹配页面只发布到所属的站点服务器上。
课程模块
//初始化课程管理首页
@Test
public void testInitCourseSiteIndex()throws Exception{
courseTypeService.InitCourseSiteIndex();
}
@Autowired
private PageConfigClient pageConfigClient;
@Override
public void InitCourseSiteIndex() {
//1 准备模板,并且上传fastdfs
//2存放数据到redis
List<CourseType> courseTypes = queryTypeTree(0L);
String dataKey = "courseTypes";
redisClient.set(dataKey, JSONArray.toJSONString(courseTypes));
//3调用静态化接口产生静态页面,并且放入fastdfs
String pageName = "CourseIndex";
//本来应该通过PageName获取page后设置pageconfig传递,由于数据在查询端,还不如直接传入pageName到那边查询.
Map<String,String> map = new HashMap<>();
map.put("dataKey",dataKey);
map.put("pageName",pageName);
pageConfigClient.staticPage(map);
//4往消息队列放一个消息,让pageAgent来下载静态页面
}
@Override
public boolean insert(CourseType entity) {
courseTypeMapper.insert(entity);
List<CourseType> courseTypes = queryTypeTree(0L);
courseTypeCache.setCourseTypes(courseTypes);
//同步页面静态化
this.InitCourseSiteIndex();
return true;
}
@Override
public boolean deleteById(Serializable id) {
courseTypeMapper.deleteById(id);
List<CourseType> courseTypes = queryTypeTree(0L);
courseTypeCache.setCourseTypes(courseTypes);
//同步页面静态化
this.InitCourseSiteIndex();
return true;
}
@Override
public boolean updateById(CourseType entity) {
courseTypeMapper.updateById(entity);
List<CourseType> courseTypes = queryTypeTree(0L);
courseTypeCache.setCourseTypes(courseTypes);
//同步页面静态化
this.InitCourseSiteIndex();
return true;
}
页面生成者-page
PageConfigController
//页面静态化接口
@PostMapping("/staticPage")
AjaxResult staticPage(@RequestBody Map<String, String> map){
String dataKey = map.get("dataKey");
String pageName = map.get("pageName");
try {
pageConfigService.staticPage(dataKey,pageName);
return AjaxResult.me();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("静态化失败!"+e.getMessage());
}
}
@Override
public void staticPage(String dataKey, String pageName) {
FileOutputStream os = null;
InputStream is = null;
try {
//一 页面静态化
Pager pager = pagerMapper
.selectList(new EntityWrapper<Pager>().eq("name", pageName)).get(0);
String templateUrl = pager.getTemplateUrl(); //fastdfs地址 zip包的
String templateName = pager.getTemplateName(); //要执行模板文件
//1.1 下载fastdfs上面压缩包
Response response =
fastDfsClient.download(templateUrl); //通过fastdfs下载压缩包
is = response.body().asInputStream();
//1.2 所有静态化中间数据都写入临时目录
//1)跨操作系统
//2 操作系统会自动维护,不用删除
String tmpdir=System.getProperty("java.io.tmpdir");
System.out.println(tmpdir+"jjjjj.........");
String zipPath = tmpdir+"/temp.zip"; //要下载zip包路径
String unZipPath = tmpdir + "/temp/"; //解压到路径
os = new FileOutputStream(zipPath);
IOUtils.copy(is , os); //保存到本地
ZipUtil.unzip(zipPath,unZipPath); // 解压缩
//1.3 获取到模板
String templatePath = unZipPath+"/"+templateName; //模板路径 temp/home.vm
System.out.println(templatePath+"zz.........");
//2 生成静态页面的路劲
String templatePagePath = templatePath+".html"; //本地静态页面地址
System.out.println(templatePagePath+"xxx.........");
//3 生成数据
String courseTypes =redisClient.get("courseTypes"); //redis数据
List<CourseTypeDto> courseTypeDtos = JSONArray.parseArray(courseTypes,CourseTypeDto.class);
Map<String, Object> modelMap = new HashMap<>(); //封装两个参数作为一个对象传入进去
modelMap.put("staticRoot", unZipPath);
modelMap.put("courseTypes", courseTypeDtos);
//4 做页面静态化
VelocityUtils.staticByTemplate(modelMap,templatePath,templatePagePath); //进行页面静态化
// 5 传递到fastdfs
String pageUrl = fastDfsClient.upload(
new CommonsMultipartFile(createFileItem(new File(templatePagePath),"file")));
//二 PageConfig 并且进行保存
PageConfig config = new PageConfig();
config.setTemplateUrl(templateUrl);
config.setTemplateName(templateName);
config.setDataKey(dataKey);
config.setPhysicalPath(pager.getPhysicalPath());
config.setDfsType(0L); //0表示fastDfs
config.setPageUrl(pageUrl);
config.setPageId(pager.getId());
pageConfigMapper.insert(config);
//三 往消息队列放入消息
String routingKey = siteMapper
.selectList(new EntityWrapper<Site>().eq("id", pager.getSiteId())).get(0).getSn();
System.out.println(routingKey+"ddh......");
JSONObject jsonObject = new JSONObject();
jsonObject.put("fileSysType",0);
jsonObject.put("staticPageUrl",pageUrl);
jsonObject.put("physicalPath",pager.getPhysicalPath());
System.out.println(jsonObject.toJSONString()+"dbl.....");
rabbitTemplate.convertAndSend(
RabbitmqConstants.EXCHANGE_DIRECT_INFORM,routingKey,jsonObject.toJSONString());
}catch (Exception e){
e.printStackTrace();
}finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
创建FileItem
*/
private FileItem createFileItem(File file,String filedName) {
FileItemFactory factory = new DiskFileItemFactory(16, null);
FileItem item = factory.createItem(filedName, "text/plain", true, file.getName());
int bytesRead = 0;
byte[] buffer = new byte[8192];
try {
FileInputStream fis = new FileInputStream(file);
OutputStream os = item.getOutputStream();
while ((bytesRead = fis.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
return item;
}
页面发布消费方 -PageAgent
package org.hhw.hrm.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 主站
* 课程管理
* 职位管理
*/
@Configuration
public class RabbitmqConfig {
@Value("${rabbitmq.queues.routingKey}")
public String routingKey;
public static final String EXCHANGE_DIRECT_INFORM = RabbitmqConstants.EXCHANGE_DIRECT_INFORM;
public static final String QUEUE_INFORM_PAGESTATIC = RabbitmqConstants.QUEUE_INFORM_PAGESTATIC;
/**
* 交换机配置
* ExchangeBuilder提供了fanout、direct、topic、header交换机类型的配置
*
* @return the exchange
*/
@Bean(EXCHANGE_DIRECT_INFORM)
public Exchange exchange_direct_inform() {
//durable(true)持久化,消息队列重启后交换机仍然存在
return ExchangeBuilder.directExchange(EXCHANGE_DIRECT_INFORM).durable(true).build();
}
//声明队列
@Bean(QUEUE_INFORM_PAGESTATIC) //交给spring管理的bean的名字可以随便的
public Queue pageStaticQueue() {
Queue queue = new Queue(QUEUE_INFORM_PAGESTATIC); //队列名
return queue;
}
// Qualifier//获取特定名称bean
@Bean
public Binding BINDING_QUEUE_INFORM_HRMJOBSITE(@Qualifier(QUEUE_INFORM_PAGESTATIC) Queue queue,
@Qualifier(EXCHANGE_DIRECT_INFORM) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(routingKey).noargs();
}
public String getRoutingKey() {
return routingKey;
}
public void setRoutingKey(String routingKey) {
this.routingKey = routingKey;
}
}
@Component
public class StaticPageDownloadHandler {
@Autowired
private FastDfsClient fastDfsClient;
@RabbitListener(queues = RabbitmqConstants.QUEUE_INFORM_PAGESTATIC)
public void receiveHomeSite(String msg, Message message, Channel channel) {
//msg -fileSysType,staticPageUrl,physicalPath
JSONObject jsonObject = JSONObject.parseObject(msg);
Integer fileSysType = jsonObject.getInteger("fileSysType");
String staticPageUrl = jsonObject.getString("staticPageUrl");
String physicalPath = jsonObject.getString("physicalPath");
System.out.println(staticPageUrl);
System.out.println(physicalPath);
switch (fileSysType) {
case 0: //fastdfs
fastDfsDownloadAndCopy(staticPageUrl, physicalPath);
break;
case 1: //hdfs
hdfsDownloadAndCopy(staticPageUrl, physicalPath);
break;
default:
break;
}
}
/**
* 通过fastdfs下载文件,并且拷贝到特定的目录
* @param staticPageUrl
* @param physicalPath
*/
private void hdfsDownloadAndCopy(String staticPageUrl, String physicalPath) {
//@TODO
}
/**
* 通过fastdfs下载文件,并且拷贝到特定的目录
* @param staticPageUrl
* @param physicalPath
*/
private void fastDfsDownloadAndCopy(String staticPageUrl, String physicalPath) {
Response response = fastDfsClient.download(staticPageUrl);
InputStream is = null;
OutputStream os = null;
try {
is = response.body().asInputStream();
os = new FileOutputStream(physicalPath);
IOUtils.copy(is,os);
} catch (Exception e) {
e.printStackTrace();
}
finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}