- 1. 搭建页面代理模块
- 1.1 步骤分析
- 1.2 步骤实现
- 2. 发布页面架构图
- 2.1 页面发布生产方
- 2.1.1 页面静态化服务
- 2.1.2 页面静态化调用
- 2.1.2.1 初始化
- 2.1.2.2 feign文件上传与下载
- 2.2 页面发布消费方
- 2.1 页面发布生产方
- 3. 课程列表页
- 3.1 需求
- 3.1.1 入口
- 3.1.2 需求分析
- 3.2 实现
- 3.2.1 主页跳转到列表页
- 3.2.2 类型所独有面包屑实现
- 3.2.3 分页列表+高级查询+排序展示功能
- 3.1 需求
1. 搭建页面代理模块
1.1 步骤分析
- 创建项目
- 导包
- 配置application.yml
- 入口类
- 日志
- swagger
- 路由
- 测试
- 抽取交换机和队列常量
- 配置RabbitMQ
- 配置处理程序(队列的消费者)
1.2 步骤实现
- 创建项目
- hrm_parent
- hrm_page_agent_parent
- hrm_page_agent_common
- hrm_page_agent_client
- hrm_page_agent_service
- hrm_page_agent_parent
- 导包
- hrm_page_agent_common
cn.wangningbo.hrm
hrm_basic_util
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
- hrm_page_agent_client
cn.wangningbo.hrm
hrm_page_agent_common
1.0-SNAPSHOT
org.springframework.cloud
spring-cloud-starter-openfeign
- hrm_page_agent_service
cn.wangningbo.hrm
hrm_page_agent_common
1.0-SNAPSHOT
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
org.springframework.boot
spring-boot-starter-amqp
com.alibaba
fastjson
1.2.58
cn.wangningbo.hrm
hrm_basic_fastdfs_interface
1.0-SNAPSHOT
commons-io
commons-io
2.4
- 配置application.yml
在hrm_page_agent_service配置
server:
port: 9007
spring:
application:
name: hrm-page-agent
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtualHost: /
queues:
routingKey: hrmCourseSite
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
prefer-ip-address: true
- 入口类
在hrm_page_agent_service配置
package cn.wangningbo.hrm;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class PageAgent9007Application {
public static void main(String[] args) {
SpringApplication.run(PageAgent9007Application.class, args);
}
}
- 日志
在hrm_page_agent_service配置
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
${LOG_HOME}/${appName}/${appName}.log
${LOG_HOME}/${appName}/${appName}-%d{yyyy-MM-dd}-%i.log
365
100MB
%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n
- swagger
在hrm_page_agent_service配置
package cn.wangningbo.hrm.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//对外暴露服务的包,以controller的方式暴露,所以就是controller的包.
.apis(RequestHandlerSelectors.basePackage("cn.wangningbo.hrm.web.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("页面代理api")
.description("页面代理接口文档说明")
.contact(new Contact("wangningbo", "", "[email protected]"))
.version("1.0")
.build();
}
}
- 路由
在zuul的配置文件里配置一下
zuul:
routes:
pageAgent.serviceId: hrm-page-agent # 服务名
pageAgent.path: /pageAgent/** # 把pageAgent打头的所有请求都转发给hrm-page-agent
在zuul的config包下配置一下
resources.add(swaggerResource("页面代理系统", "/services/pageAgent/v2/api-docs", "2.0"));
最终结果
package cn.wangningbo.hrm.config;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
@Component
@Primary
public class DocumentationConfig implements SwaggerResourcesProvider {
@Override
public List get() {
List resources = new ArrayList<>();
//通过网关访问服务地址
resources.add(swaggerResource("系统管理", "/services/sysmanage/v2/api-docs", "2.0"));
resources.add(swaggerResource("课程中心", "/services/course/v2/api-docs", "2.0"));
resources.add(swaggerResource("分布式文件系统", "/services/fastdfs/v2/api-docs", "2.0"));
resources.add(swaggerResource("分布式全文检索", "/services/es/v2/api-docs", "2.0"));
resources.add(swaggerResource("redis中央缓存", "/services/redis/v2/api-docs", "2.0"));
resources.add(swaggerResource("页面管理系统", "/services/page/v2/api-docs", "2.0"));
resources.add(swaggerResource("页面代理系统", "/services/pageAgent/v2/api-docs", "2.0"));
return resources;
}
private SwaggerResource swaggerResource(String name, String location, String version) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion(version);
return swaggerResource;
}
}
-
测试
启动项目,测试swagger是否成功
抽取交换机和队列常量
在公共模块hrm_basic_parent下新建子模块hrm_basic_rabbitmq
配置常量
package cn.wangningbo.hrm.config;
/**
* 抽取常量
*/
public class RabbitmqConstants {
// 交换机常量
public static final String EXCHANGE_DIRECT_INFORM = "exchange_direct_inform";
// 队列常量
public static final String QUEUE_INFORM_PAGESTATIC = "queue_inform_pageStatic";
}
- 配置RabbitMQ
在hrm_page_agent_service的pom里面依赖抽取的常量模块
cn.wangningbo.hrm
hrm_basic_rabbitmq
1.0-SNAPSHOT
在hrm_page_agent_service配置RabbitMQ
package cn.wangningbo.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;
}
}
- 配置处理程序(队列的消费者)
改造fastdfs的下载方法
FastDFSClient
/**
* 下载
*
* @param path
*/
@GetMapping(value = "/download", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
//直接把流写到Response // 注意:这个Response是feign的
Response download(@RequestParam("path") String path);
FastDFSClientHystrixFallbackFactory
package cn.wangningbo.hrm.client;
import cn.wangningbo.hrm.util.AjaxResult;
import feign.Response;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Component
public class FastDFSClientHystrixFallbackFactory implements FallbackFactory {
@Override
public FastDFSClient create(Throwable throwable) {
return new FastDFSClient() {
@Override
public String upload(MultipartFile file) {
return null;
}
@Override
public AjaxResult delete(String path) {
return null;
}
@Override
public Response download(String path) {
return null;
}
};
}
}
在hrm_page_agent_service配置处理程序
package cn.wangningbo.hrm.handler;
import cn.wangningbo.hrm.client.FastDFSClient;
import cn.wangningbo.hrm.config.RabbitmqConstants;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import feign.Response;
import org.apache.commons.io.IOUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@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
System.out.println("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[");
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) {
System.out.println(staticPageUrl+"=====================================staticPageUrl");
System.out.println(physicalPath+"-----------------------------------------physicalPath");
Response response = fastDFSClient.download(staticPageUrl);
System.out.println("pppppppppppppppppp||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
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();
}
}
}
}
}
2. 发布页面架构图
[图片上传失败...(image-ceee53-1569803685569)]
技术方案说明:
- 平台包括多个站点,页面归属不同的站点。
- 发布一个页面应将该页面发布到所属站点的服务器上。
- 每个站点服务部署cms client程序,并与交换机绑定,绑定时指定站点Id为routingKey。 指定站点id为routingKey就可以实现cms client只能接收到所属站点的页面发布消息。
- 页面发布程序向MQ发布消息时指定页面所属站点Id为routingKey,将该页面发布到它所在服务器上的cms client。
路由模式分析如下:
发布一个页面,需发布到该页面所属的每个站点服务器,其它站点服务器不发布。
比如:发布一个门户的页面,需要发布到每个门户服务器上,而用户中心服务器则不需要发布。 所以本项目采用routing模式,用站点id作为routingKey,这样就可以匹配页面只发布到所属的站点服务器上。
页面发布流程图如下:
[图片上传失败...(image-9ea392-1569803685569)]
2.1 页面发布生产方
2.1.1 页面静态化服务
hrm_page_service的最终导包情况
cn.wangningbo.hrm
hrm_page_common
1.0-SNAPSHOT
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
com.baomidou
mybatis-plus-boot-starter
2.2.0
mysql
mysql-connector-java
org.springframework.cloud
spring-cloud-starter-config
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
cn.wangningbo.hrm
hrm_basic_redis_client
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
cn.wangningbo.hrm
hrm_basic_fastdfs_interface
1.0-SNAPSHOT
commons-io
commons-io
2.4
org.apache.velocity
velocity
1.7
commons-fileupload
commons-fileupload
1.3.1
cn.wangningbo.hrm
hrm_basic_rabbitmq
1.0-SNAPSHOT
- client
/**
* 静态化页面
* @param map
* @return
*/
@PostMapping("/staticPage")
AjaxResult staticPage(Map map);
- HystrixFallbackFactory暂时不做处理
@Override
public AjaxResult staticPage(Map map) {
return null;
}
- controller
/**
* 静态化页面
* @param map
* @return
*/
@PostMapping("/staticPage")
AjaxResult staticPage(@RequestBody Map 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());
}
}
- IService
void staticPage(String dataKey, String pageName);
- ServiceImpl
修改fastdfs的上传:client
@PostMapping(value="/upload", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}
, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String upload(@RequestPart("file") MultipartFile file);
修改fastdfs的上传:controller
/**
* 上传
*
* @param file
* @return
*/
@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;
}
压缩包工具ZipUtil
package cn.wangningbo.hrm.util;
import java.io.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
*
* @version 1.0
* @since 2015-9-14
* 文件解压缩工具类
*
*/
public final class ZipUtil
{
/**
* 缓冲大小
*/
private static int BUFFERSIZE = 2 << 10;
/**
* 压缩
* @param paths
* @param fileName
*/
public static void zip(String[] paths, String fileName)
{
ZipOutputStream zos = null;
try
{
zos = new ZipOutputStream(new FileOutputStream(fileName));
for(String filePath : paths)
{
//递归压缩文件
File file = new File(filePath);
String relativePath = file.getName();
if(file.isDirectory())
{
relativePath += File.separator;
}
zipFile(file, relativePath, zos);
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(zos != null)
{
zos.close();
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
public static void zipFile(File file, String relativePath, ZipOutputStream zos)
{
InputStream is = null;
try
{
if(!file.isDirectory())
{
ZipEntry zp = new ZipEntry(relativePath);
zos.putNextEntry(zp);
is = new FileInputStream(file);
byte[] buffer = new byte[BUFFERSIZE];
int length = 0;
while ((length = is.read(buffer)) >= 0)
{
zos.write(buffer, 0, length);
}
zos.flush();
zos.closeEntry();
}
else
{
for(File f: file.listFiles())
{
zipFile(f, relativePath + f.getName() + File.separator, zos);
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(is != null)
{
is.close();
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
/**
* 解压缩
* @param fileName
* @param path
*/
public static void unzip(String fileName, String path)
{
FileOutputStream fos = null;
InputStream is = null;
try
{
ZipFile zf = new ZipFile(new File(fileName));
Enumeration en = zf.entries();
while (en.hasMoreElements())
{
ZipEntry zn = (ZipEntry) en.nextElement();
if (!zn.isDirectory())
{
is = zf.getInputStream(zn);
File f = new File(path + zn.getName());
File file = f.getParentFile();
file.mkdirs();
fos = new FileOutputStream(path + zn.getName());
int len = 0;
byte bufer[] = new byte[BUFFERSIZE];
while (-1 != (len = is.read(bufer)))
{
fos.write(bufer, 0, len);
}
fos.close();
}
}
}
catch (ZipException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(null != is)
{
is.close();
}
if(null != fos)
{
fos.close();
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
/**
* @param args
*/
public static void main(String[] args)
{
//zip(new String[] {"D:/tmp/20150418/logs/file/2015-08-28/debug-log.log","D:/tmp/20150418/logs/file/2015-08-28/error-log.log"}, "D:/tmp/20150418/logs/file/2015-08-28/test.zip");
//unzip("D://tmep.zip", "D:/temp/");
Map modelMap = new HashMap<>(); //封装两个参数作为一个对象传入进去
String templatePagePath = "D://temp/home.vm.html"; //本地静态页面地址
String staticRoot = "D://temp/";
modelMap.put("staticRoot", staticRoot);
modelMap.put("courseTypes", null);
VelocityUtils.staticByTemplate(modelMap,"D://temp/home.vm",templatePagePath); //进行页面静态化
}
}
页面静态化工具
package cn.wangningbo.hrm.util;
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();
}
}
}
ServiceImpl
package cn.wangningbo.hrm.service.impl;
import cn.wangningbo.hrm.client.FastDFSClient;
import cn.wangningbo.hrm.client.RedisClient;
import cn.wangningbo.hrm.config.RabbitmqConstants;
import cn.wangningbo.hrm.domain.PageConfig;
import cn.wangningbo.hrm.domain.Pager;
import cn.wangningbo.hrm.domain.Site;
import cn.wangningbo.hrm.dto.CourseTypeDto;
import cn.wangningbo.hrm.mapper.PageConfigMapper;
import cn.wangningbo.hrm.mapper.PagerMapper;
import cn.wangningbo.hrm.mapper.SiteMapper;
import cn.wangningbo.hrm.service.IPageConfigService;
import cn.wangningbo.hrm.util.VelocityUtils;
import cn.wangningbo.hrm.util.ZipUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import feign.Response;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.IOUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* 服务实现类
*
*
* @author wangningbo
* @since 2019-09-09
*/
@Service
public class PageConfigServiceImpl extends ServiceImpl implements IPageConfigService {
@Autowired
private PagerMapper pagerMapper;
@Autowired
private FastDFSClient fastDFSClient;
@Autowired
private RedisClient redisClient;
@Autowired
private PageConfigMapper pageConfigMapper;
@Autowired
private SiteMapper siteMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void staticPage(String dataKey, String pageName) {
//一、页面静态化
// 根据name去数据库中查询name记录,获取到第一个
Pager pager = pagerMapper.selectList(new EntityWrapper().eq("name", pageName)).get(0);
// 获取到fastdfs中模板压缩包的路径
String templateUrl = pager.getTemplateUrl();
// 获取要执行的模板文件
String templateName = pager.getTemplateName();
// 下载fastdfs上面的模板压缩包
Response response = fastDFSClient.download(templateUrl);
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
try {
//获得输入流资源
inputStream = response.body().asInputStream();
// 所有静态化中数据都写入操作系统的临时目录(这个可以跨操作系统,而且临时目录的东西会自动维护,无需手动删除)
String tempdir = System.getProperty("java.io.tmpdir");
System.out.println(tempdir + "-------------------------------临时目录");
// 要下载压缩包的路径
String zipPath = tempdir + "/temp.zip";
// 要解压到的路径
String unZipPath = tempdir + "/temp/";
// 获得输出流资源
fileOutputStream = new FileOutputStream(zipPath);
// 保存到本地 //import org.apache.commons.io.IOUtils;
IOUtils.copy(inputStream, fileOutputStream); //保存到本地
// 解压缩
ZipUtil.unzip(zipPath, unZipPath);
// 获取到模板 //模板路径 temp/home.vm
String templatePath = unZipPath + "/" + templateName;
System.out.println(templatePath + "-----------------------------------模板路径");
// 获取到生成生成静态页面的路径 //本地静态页面的地址
String templatePagePath = templatePath + ".html";
System.out.println(templatePagePath + "----------------------------------本地静态页面的地址");
// 生成页面需要的数据 //从redis中获取
String courseTypes = redisClient.get("courseTypes");
// json字符串转list
List courseTypeDtos = JSONArray.parseArray(courseTypes, CourseTypeDto.class);
// 封装两个参数成一个对象传入进去
HashMap map = new HashMap<>();
map.put("staticRoot", unZipPath);
map.put("courseTypes", courseTypeDtos);
// 进行页面静态化
VelocityUtils.staticByTemplate(map, templatePath, templatePagePath);
// 传递到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().eq("id", pager.getSiteId())).get(0).getSn();
System.out.println(routingKey + "----------------------------routingKey");
JSONObject jsonObject = new JSONObject();
jsonObject.put("fileSysType", 0);
jsonObject.put("staticPageUrl", pageUrl);
jsonObject.put("physicalPath", pager.getPhysicalPath());
System.out.println(jsonObject.toJSONString() + "---------------------------------------------jsonObject.toJSONString()");
rabbitTemplate.convertAndSend(
RabbitmqConstants.EXCHANGE_DIRECT_INFORM, routingKey, jsonObject.toJSONString());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 创建FileItem
*
* @param file
* @param filedName
* @return
*/
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;
}
}
2.1.2 页面静态化调用
2.1.2.1 初始化
IService
/**
*初始化课程站点主页
*/
void InitCourseSiteIndex();
ServiceImpl
@Override
public void InitCourseSiteIndex() {
//1 准备模板,并且上传fastdfs
//2存放数据到redis
List courseTypes = queryTypeTree(0L);
String dataKey = "courseTypes";
redisClient.set(dataKey, JSONArray.toJSONString(courseTypes));
//3调用静态化接口产生静态页面,并且放入fastdfs
String pageName = "CourseIndex";
//本来应该通过PageName获取page后设置pageconfig传递,由于数据在查询端,还不如直接传入pageName到那边查询.
Map map = new HashMap<>();
map.put("dataKey",dataKey);
map.put("pageName",pageName);
pageConfigClient.staticPage(map);
//4往消息队列放一个消息,让pageAgent来下载静态页面
}
2.1.2.2 feign文件上传与下载
参考文档:Feign文件上传与下载.md
链接:http://note.youdao.com/noteshare?id=b3a62850f8b259a0fbc14b40b46288b7&sub=08937A6337FD44DBBB3BF9C4C1839436
2.2 页面发布消费方
新建公共模块抽取常量
导包
org.springframework.boot
spring-boot-starter-amqp
抽取常量
package cn.wangningbo.hrm.config;
/**
* 抽取常量
*/
public class RabbitmqConstants {
// 交换机常量
public static final String EXCHANGE_DIRECT_INFORM = "exchange_direct_inform";
// 队列常量
public static final String QUEUE_INFORM_PAGESTATIC = "queue_inform_pageStatic";
}
3. 课程列表页
3.1 需求
3.1.1 入口
- 主页里面关键字搜索
- 主页里面通过类型导航过去.
3.1.2 需求分析
-
主页里面关键字搜索
- 分页查询+排序 select name from t_user where name like xxx order by limit 0,10
-
主页里面通过类型导航过去.
- 有面包屑
- 有品牌
- 分页查询+排序 select name from t_user where name like xxx order by limit 0,10
3.2 实现
3.2.1 主页跳转到列表页
分析:
- 主页携带参数跳转到列表页.
location.href = list.html?keyword=xx
location.href = list.html?productType=154 - 列表页,加载完毕后,获取传递过来keyword ,或者productType
- keyword
- ①回显搜索框
- ②以keyword为搜索条件到es中查询分页数据,展示在界面
- productType
- ①发送获取面包屑请求,展示在界面
- ②发送品牌请求获取品牌,展示在界面
- ②以productType为搜索条件到es中查询分页数据,展示在界面
- keyword
实现:
3.2.2 类型所独有面包屑实现
- 结构分析
根据当前类型获取层级关系,并且每一级都有自己及其兄弟. List
- 后台接口
配置跨域
config.addAllowedOrigin("http://127.0.0.1:6002");
config.addAllowedOrigin("http://localhost:6002");
controller
/**
* 通过类型查询面包屑数据
* 有层次(Node): path
* Node: 自己和兄弟 path里面就是自己 通过自己查询父亲,再通过父亲找到儿子,删除自己就ok
* @return
*/
@RequestMapping(value = "/crumbs",method = RequestMethod.GET)
public List
IService
List
ServiceImpl
@Override
public List
3.2.3 分页列表+高级查询+排序展示功能
es的client
/**
* 从es中查询
* @param params
* @return
*/
@PostMapping("/query")
PageList> query(@RequestBody Map params);
HystrixFallbackFactory先不做处理
@Override
public PageList> query(Map params) {
return null;
}
es的controller
@PostMapping("/query")
PageList> query(@RequestBody Map params) {
return esCourseService.query(params);
}
es的IService
PageList> query(Map params);
es的ServiceImpl
@Override
public PageList> query(Map params) {
// keyword CourseyType brandId priceMin priceMax sortField sortType page rows
String keyword = (String) params.get("keyword"); //查询
String sortField = (String) params.get("sortField"); //排序
String sortType = (String) params.get("sortType");//排序
Long courseType = params.get("CourseType") != null ? Long.valueOf(params.get("CourseType").toString()) : null;//过滤
Long priceMin = params.get("priceMin") != null ? Long.valueOf(params.get("priceMin").toString()) * 100 : null;//过滤
Long priceMax = params.get("priceMax") != null ? Long.valueOf(params.get("priceMax").toString()) * 100 : null;//过滤
Long page = params.get("page") != null ? Long.valueOf(params.get("page").toString()) : null; //分页
Long rows = params.get("rows") != null ? Long.valueOf(params.get("rows").toString()) : null;//分页
//构建器
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
//设置查询条件=查询+过滤
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
if (StringUtils.isNotBlank(keyword)) {
boolQuery.must(QueryBuilders.matchQuery("all", keyword));
}
List filter = boolQuery.filter();
if (courseType != null) { //类型
filter.add(QueryBuilders.termQuery("courseTypeId", courseType));
}
//最大价格 最小价格
//minPrice <= priceMax && maxPrice>=priceMin
if (priceMax != null && priceMin != null) {
filter.add(QueryBuilders.rangeQuery("price").gte(priceMin).lte(priceMax));
}
builder.withQuery(boolQuery);
//排序
SortOrder defaultSortOrder = SortOrder.DESC;
if (StringUtils.isNotBlank(sortField)) {//销量 新品 价格 人气 评论
//如果传入的不是降序改为升序
if (StringUtils.isNotBlank(sortType) && !sortType.equals(SortOrder.DESC)) {
defaultSortOrder = SortOrder.ASC;
}
// 价格 索引库有两个字段 最大,最小
//如果用户按照升序就像买便宜的,就用最小价格,如果用户按照降序想买贵的,用最大价格
if (sortField.equals("jg")) {
builder.withSort(SortBuilders.fieldSort("price").order(defaultSortOrder));
}
}
//分页
Long pageTmp = page - 1; //从0开始
builder.withPageable(PageRequest.of(pageTmp.intValue(), rows.intValue()));
//截取字段 @TODO
//封装数据
Page CourseDocs = courseRepository.search(builder.build());
List> datas = esCourses2ListMap(CourseDocs.getContent());
return new PageList<>(CourseDocs.getTotalElements(), datas);
}
/**
* 数据转换
*
* @param content
* @return
*/
private List> esCourses2ListMap(List content) {
List> result = new ArrayList<>();
for (ESCourse esCourse : content) {
result.add(esCourse2Map(esCourse));
}
return result;
}
private Map esCourse2Map(ESCourse esCourse) {
Map result = new HashMap<>();
result.put("id", esCourse.getId());
result.put("name", esCourse.getName());
result.put("users", esCourse.getUsers());
result.put("courseTypeId", esCourse.getCourseTypeId());
result.put("courseTypeName", esCourse.getCourseTypeName());
result.put("gradeId", esCourse.getGradeId());
result.put("gradeName", esCourse.getGradeName());
result.put("status", esCourse.getStatus());
result.put("tenantId", esCourse.getTenantId());
result.put("tenantName", esCourse.getTenantName());
result.put("userId", esCourse.getUserId());
result.put("userName", esCourse.getUserName());
result.put("startTime", esCourse.getStartTime());
result.put("endTime", esCourse.getEndTime());
result.put("expires", esCourse.getExpires());
result.put("priceOld", esCourse.getPriceOld());
result.put("price", esCourse.getPrice());
result.put("intro", esCourse.getIntro());
result.put("qq", esCourse.getQq());
result.put("resources", esCourse.getResources());
return result;
}
高级搜索的controller
@PostMapping("/queryCourses")
public PageList> queryCourses(@RequestBody Map query){
return courseService.queryCourses(query);
}
IService
PageList> queryCourses(Map query);
ServiceImpl
/**
* 高级搜索
* @param query
* @return
*/
@Override
public PageList> queryCourses(Map query) {
return esCourseClient.query(query);
}