为什么要使用页面静态化
课程主页的访问人数非常多, 以不发请求静态页面代替要发请求静态页面或者动态页面.没有对后台数据获取.
课程详情页:只要课程信息不改,详情页就不会改变.
官网主页:一定的时间段是不可变
招聘主页:一定的时间段是不可变
职位详情:只要职位信息不改,详情页就不会改变.
有的页面访问人数很多,但是在一定时间段内不会改变(数据没变化).页面静态化
静态化好处
①降低数据库或缓存压力
②提高响应速度,增强用户体验.
方案分析
页面静态化是这一种方案,而模板技术是实现这种方案的技术。
静态页面=模板(结构)+数据(内容)
静态页面生成时机:
①当部署并启动,需要在后台管理里面触发一个按钮,初始化静态页面. 初始化
②当数据(类型,广告等)发生改变后(后台做了增删改),要覆盖原来静态页面. 替换
方案:页面静态化,通过模板技术来实现.
模板技术:freemaker,velocity,thymeleaf等
模板(velocity)+数据=静态页面(初始化,数据改变)
单体项目方案分析
集群项目架构分析
页面中心功能
新建项目模块
1)页面结构
hrm_page_parent
hrm_page_common
hrm_page_client
hrm_page_service_2030
2)基本结构搭建-拷贝course模块
common Pom
<dependencies>
<!--不能直接依赖starter,有自动配置,而消费者是不需要额。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.zhanglin</groupId>
<artifactId>hrm_basic_util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--不能全部引入mybatis-plus,这是要做数据库操作,这里是不需要的,只需引入核心包解决错误而已-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies>
client pom
<dependencies>
<dependency>
<groupId>com.zhanglin</groupId>
<artifactId>hrm_page_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--客户端feign支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
service pom
<dependencies>
<!--所有provider公共依賴-->
<dependency>
<groupId>com.zhanglin</groupId>
<artifactId>hrm_page_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Eureka 客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--配置中心支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--mybatis-plus支持-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--数据库支持-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 引入feign fastdfs调用-->
<!--所有provider公共依賴-->
<dependency>
<groupId>com.zhanglin</groupId>
<artifactId>hrm_common_client</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
</dependencies>
配置
service
bootstrap.yml
spring:
profiles:
active: dev
cloud:
config:
name: application-page #github上面名称
profile: ${spring.profiles.active} #环境 java -jar -D xxx jar
label: master #分支
discovery:
enabled: true #从eureka上面找配置服务
service-id: hrm-config-server #指定服务名
#uri: http://127.0.0.1:1299 #配置服务器 单机配置
eureka: #eureka不能放到远程配置中
client:
service-url:
defaultZone: http://localhost:1010/eureka #告诉服务提供者要把服务注册到哪儿 #单机环境
instance:
prefer-ip-address: true #显示客户端真实ip
feign:
hystrix:
enabled: true #开启熔断支持
client:
config:
remote-service: #服务名,填写default为所有服务
connectTimeout: 3000
readTimeout: 3000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
application.yml
spring:
application:
name: hrm-page
入口类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class PageServiceApplication2030 {
public static void main(String[] args) {
SpringApplication.run(PageServiceApplication2030.class,args);
}
}
路由(配置中心)
application-page-dev.yml
server:
port: 2030
spring:
application:
name: hrm-page
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/hrm-page
username: root
password: 123456
mybatis-plus:
mapper-locations: classpath:com/zhanglin/mapper/*Mapper.xml
type-aliases-package: com.zhanglin.domain,com.zhanglin.query
Swagger
测试
项目domain表设计
t_site:前端站点 主站 课程 职位 要做页面静态化的前端网站
t_page 页面, 在站点下面有多个页面要做静态化。 定义了模板
t_page_config 每做一次页面静态化,都要添加一个对象。一个页面一次持久化所需要数据保存。
微服务实现-代码生成并测试
前端实现
模板技术-velocity
Velocity是一个基于java的模板引擎(template engine),它允许任何人仅仅简单的使用模板语言(template language)来引用由java代码定义的对象。作为一个比较完善的模板引擎,Velocity的功能是比较强大的,但强大的同时也增加了应用复杂性。
课程主页静态化实现
pom
<!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
VelocityUtils
package com.zhanglin.utils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import java.io.*;
import java.util.Properties;
public class VelocityUtils {
private static Properties p = new Properties();
static {
p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, "");
p.setProperty(Velocity.ENCODING_DEFAULT, "UTF-8");
p.setProperty(Velocity.INPUT_ENCODING, "UTF-8");
p.setProperty(Velocity.OUTPUT_ENCODING, "UTF-8");
}
/**
* 返回通过模板,将model中的数据替换后的内容
* @param model
* @param templateFilePathAndName
* @return
*/
public static String getContentByTemplate(Object model, String templateFilePathAndName){
try {
Velocity.init(p);
Template template = Velocity.getTemplate(templateFilePathAndName);
VelocityContext context = new VelocityContext();
context.put("model", model);
StringWriter writer = new StringWriter();
template.merge(context, writer);
String retContent = writer.toString();
writer.close();
return retContent;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 根据模板,静态化model到指定的文件 模板文件中通过访问model来访问设置的内容
*
* @param model
* 数据对象
* @param templateFilePathAndName
* 模板文件的物理路径
* @param targetFilePathAndName
* 目标输出文件的物理路径
*/
public static void staticByTemplate(Object model, String templateFilePathAndName, String targetFilePathAndName) {
try {
Velocity.init(p);
Template template = Velocity.getTemplate(templateFilePathAndName);
VelocityContext context = new VelocityContext();
context.put("model", model);
FileOutputStream fos = new FileOutputStream(targetFilePathAndName);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8"));// 设置写入的文件编码,解决中文问题
template.merge(context, writer);
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 静态化内容content到指定的文件
*
* @param content
* @param targetFilePathAndName
*/
public static void staticBySimple(Object content, String targetFilePathAndName) {
VelocityEngine ve = new VelocityEngine();
ve.init(p);
String template = "${content}";
VelocityContext context = new VelocityContext();
context.put("content", content);
StringWriter writer = new StringWriter();
ve.evaluate(context, writer, "", template);
try {
FileWriter fileWriter = new FileWriter(new File(targetFilePathAndName));
fileWriter.write(writer.toString());
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
生成课程页面
测试方法初始化
数据改变重新生成
技术方案说明:
- 平台包括多个站点,页面归属不同的站点。
- 发布一个页面应将该页面发布到所属站点的服务器上。
- 每个站点服务部署cms client程序,并与交换机绑定,绑定时指定站点Id为routingKey。 指定站点id为routingKey就可以实现cms client只能接收到所属站点的页面发布消息。
- 页面发布程序向MQ发布消息时指定页面所属站点Id为routingKey,将该页面发布到它所在服务器上的cms client。
路由模式分析如下:
发布一个页面,需发布到该页面所属的每个站点服务器,其它站点服务器不发布。
比如:发布一个门户的页面,需要发布到每个门户服务器上,而用户中心服务器则不需要发布。 所以本项目采用routing模式,用站点id作为routingKey,这样就可以匹配页面只发布到所属的站点服务器上。
页面发布流程图如下:
feign文件上传与下载
下载
Feigin
//获取用户
@RequestMapping(value = "/download",method = RequestMethod.GET,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
Response download(@RequestParam("path")String path); //直接把流写到response
Service
@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);
调用:
Response response =
fastDfsClient.download(templateUrl); //通过fastdfs下载压缩包
String tmpdir=System.getProperty("java.io.tmpdir");
System.out.println(tmpdir);
String zipName = tmpdir+"/temp.zip";
os = new FileOutputStream(zipName);
is = response.body().asInputStream();
IOUtils.copy(is , os); //保存到本地
上传:
Feign
<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>
配置类 config
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
@Configuration
public class FeignMultipartSupportConfig {
@Bean
@Primary
@Scope("prototype")
public Encoder multipartFormEncoder() {
return new SpringFormEncoder();
}
@Bean
public feign.Logger.Level multipartLoggerLevel() {
return feign.Logger.Level.FULL;
}
}
fastDfsClient
//fallbackFactory = CourseTypeClientHystrixFallbackFactory.class
@FeignClient(value = "HRM-FASTDFS",configuration = FeignMultipartSupportConfig.class,
//@FeignClient(value = "HRM-FASTDFS",configuration = FeignClientsConfiguration.class,
fallbackFactory = FastDfsClientHystrixFallbackFactory.class
)
@RequestMapping("/fastdfs")
public interface FastDfsClient {
@PostMapping(value= "/upload", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}
, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String upload(@RequestPart(value = "file") MultipartFile file);
Service:
@PostMapping(value = "/upload", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}
, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String upload(@RequestPart(value = "file") MultipartFile file) {
@Autowired
private FastDfsClient fastDfsClient;
@Test
public void test1() throws Exception{
FileItem fileItem = createFileItem(new File("C:\\Users\\Administrator\\Desktop\\问题.txt"));
MultipartFile mfile = new CommonsMultipartFile(fileItem);
fastDfsClient.upload(mfile);
}
/*
创建FileItem
*/
private FileItem createFileItem(File file) {
FileItemFactory factory = new DiskFileItemFactory(16, null);
String textFieldName = "textField";
FileItem item = factory.createItem("file", "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;
}