SAAS-HRM-day9(页面静态化实现)

  • 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 页面发布消费方
  • 3. 课程列表页
    • 3.1 需求
      • 3.1.1 入口
      • 3.1.2 需求分析
    • 3.2 实现
      • 3.2.1 主页跳转到列表页
      • 3.2.2 类型所独有面包屑实现
      • 3.2.3 分页列表+高级查询+排序展示功能

1. 搭建页面代理模块

1.1 步骤分析

  1. 创建项目
  2. 导包
  3. 配置application.yml
  4. 入口类
  5. 日志
  6. swagger
  7. 路由
  8. 测试
  9. 抽取交换机和队列常量
  10. 配置RabbitMQ
  11. 配置处理程序(队列的消费者)

1.2 步骤实现

  1. 创建项目
  • hrm_parent
    • hrm_page_agent_parent
      • hrm_page_agent_common
      • hrm_page_agent_client
      • hrm_page_agent_service
  1. 导包
  • 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
        
  1. 配置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
  1. 入口类

在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);
    }
}
  1. 日志

在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
        
    

    
    
    
    
    



    
    
        
        
    
 
  1. 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();
    }

}
  1. 路由

在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;
    }
}
  1. 测试

    启动项目,测试swagger是否成功

  2. 抽取交换机和队列常量

在公共模块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";
}
  1. 配置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;
    }
}
  1. 配置处理程序(队列的消费者)

改造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)]
技术方案说明:

  1. 平台包括多个站点,页面归属不同的站点。
  2. 发布一个页面应将该页面发布到所属站点的服务器上。
  3. 每个站点服务部署cms client程序,并与交换机绑定,绑定时指定站点Id为routingKey。 指定站点id为routingKey就可以实现cms client只能接收到所属站点的页面发布消息。
  4. 页面发布程序向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
        
  1. client
    /**
     * 静态化页面
     * @param map
     * @return
     */
    @PostMapping("/staticPage")
    AjaxResult staticPage(Map map);
  1. HystrixFallbackFactory暂时不做处理
            @Override
            public AjaxResult staticPage(Map map) {
                return null;
            }
  1. 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());
        }
    }
  1. IService
    void staticPage(String dataKey, String pageName);
  1. 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 入口

  1. 主页里面关键字搜索
  2. 主页里面通过类型导航过去.

3.1.2 需求分析

  1. 主页里面关键字搜索

    • 分页查询+排序 select name from t_user where name like xxx order by limit 0,10
  2. 主页里面通过类型导航过去.

    • 有面包屑
    • 有品牌
    • 分页查询+排序 select name from t_user where name like xxx order by limit 0,10

3.2 实现

3.2.1 主页跳转到列表页

分析:

  1. 主页携带参数跳转到列表页.
    location.href = list.html?keyword=xx
    location.href = list.html?productType=154
  2. 列表页,加载完毕后,获取传递过来keyword ,或者productType
    • keyword
      • ①回显搜索框
      • ②以keyword为搜索条件到es中查询分页数据,展示在界面
    • productType
      • ①发送获取面包屑请求,展示在界面
      • ②发送品牌请求获取品牌,展示在界面
      • ②以productType为搜索条件到es中查询分页数据,展示在界面

实现:


3.2.2 类型所独有面包屑实现

  1. 结构分析

根据当前类型获取层级关系,并且每一级都有自己及其兄弟. List>
每一级有一个Map表示,里面有两个值,一个是ProductType ower;一个是List otherProductTypes.

  1. 后台接口

配置跨域

        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> getCrumbs(Long courseTypeId){
        return courseTypeService.getCrumbs(courseTypeId);
    }

IService

    List> getCrumbs(Long courseTypeId);

ServiceImpl

    @Override
    public List> getCrumbs(Long courseTypeId) {
        List> result = new ArrayList<>();
        //1 获取path 1.2.3
        CourseType courseType = courseTypeMapper.selectById(courseTypeId);
        String path = courseType.getPath();

        //2 截取path中各个节点自己  1 2 3
        String[] paths = path.split("\\.");

        //3 获取自己节点兄弟封装Map,放入List中进行返回
        for (String ownerIdStr : paths) {
            Map map = new HashMap<>();

            Long ownerId = Long.valueOf(ownerIdStr);

            System.out.println(ownerId);
            //获取每个自己
            CourseType owner =  courseTypeMapper.selectById(ownerId);
            map.put("owner",owner);
            //查询兄弟
            //获取父亲所有儿子
            List allChildren = courseTypeMapper
                    .selectList(new EntityWrapper().eq("pid",owner.getPid()));

            //干掉自己-边遍历边操作(正删改),要用迭代器
            Iterator iterator = allChildren.iterator();
            while (iterator.hasNext()){
                CourseType currentType = iterator.next();
                if (currentType.getId().longValue()==owner.getId().longValue()){
                    iterator.remove();
                    continue; //跳出当前循环
                }
            }
            map.put("otherCourseTypes",allChildren);
            result.add(map);
        }
        return result;
    }

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);
    }

你可能感兴趣的:(SAAS-HRM-day9(页面静态化实现))