springboot综合项目练习十页面发布功能实现

一. 需求分析
springboot综合项目练习十页面发布功能实现_第1张图片
业务流程如下:
1、管理员进入管理界面点击“页面发布”,前端请求cms页面发布接口。
2、cms页面发布接口执行页面静态化,并将静态化页面(html文件)存储至GridFS中。
3、静态化成功后,向消息队列发送页面发布的消息。
4、消息队列负责将消息发送给各各服务器上部署的Cms Client(Cms客户端)。
在服务器上部署Cms Client(Cms客户端),客户端接收消息队列的通知。
5、每个接收到页面发布消息的Cms Client从GridFS获取Html页面文件,并将Html文件存储在本地服务器。

二. 分析
发布一个页面,需发布到该页面所属的每个站点服务器,其它站点服务器不发布。
比如:发布一个门户的页面,需要发布到每个门户服务器上,而用户中心服务器则不需要发布。
采用routing模式,用站点id作为routingKey,这样就可以匹配页面只发布到所属的站点服务器上。
CmsClient根据页面发布消息的内容请求GridFS获取页面文件,存储在本地服务器。
页面发布流程图如下:
springboot综合项目练习十页面发布功能实现_第2张图片
1、前端请求cms执行页面发布。
2、cms执行静态化程序生成html文件。
3、cms将html文件存储到GridFS中。
4、cms向MQ发送页面发布消息
5、MQ将页面发布消息通知给Cms Client
6、Cms Client从GridFS中下载html文件
7、Cms Client将html保存到所在服务器指定目录

三. 页面发布消费方
作为页面发布消费方,部署到多个服务器上,主要实现的功能是接收页面发布消息,从gridFS下载页面并保存到对应的服务器中.大体流程如下:
1、将cms Client部署在服务器,配置队列名称和站点ID。
2、cms Client连接RabbitMQ并监听各自的“页面发布队列”
3、cms Client接收页面发布队列的消息
4、根据消息中的页面id从mongodb数据库下载页面到本地
其中,消息体内容是页面id,通过页面id,调用 dao查询页面信息,获取到页面的物理路径,调用dao查询站点信息,得到站点的物理路径,从GridFS查询静态文件内容,将静态文件内容保存到页面物理路径下。
代码实现:
1.pom

		
            org.springframework.boot
            spring‐boot‐starter‐amqp
        
        
            org.springframework.boot
            spring‐boot‐starter‐data‐mongodb
        
        
            org.apache.commons
            commons‐io
        
		
            com.alibaba
 			fastjson
        

2.配置文件

server:
  port: 31000
spring:
  application:
    name: xc‐service‐manage‐cms‐client
  data:
    mongodb:
      uri:  mongodb://root:123@localhost:27017
      database: xc_cms
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtualHost: /
xuecheng:
  mq:
  #cms客户端监控的队列名称(不同的客户端监控的队列不能重复)
    queue: queue_cms_postpage_01
    routingKey: 5a751fab6abb5044e0d19ea1 #此routingKey为门户站点ID

注:在配置文件中配置队列的名称,每个 cms client在部署时注意队列名称不要重复

3、启动类

@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.cms")//扫描实体类
@ComponentScan(basePackages={"com.xuecheng.framework"})//扫描common下的所有类
@ComponentScan(basePackages={"com.xuecheng.manage_cms_client"})
public class ManageCmsClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(ManageCmsClientApplication.class, args);
    }
}

  1. RabbitmqConfig 配置类
    配置类消息队列设置如下:
    1)、创建“ex_cms_postpage”交换机
    2)、每个Cms Client创建一个队列与交换机绑定
    3)、每个Cms Client程序配置队列名称和routingKey,将站点ID作为routingKey。
 package com.xuecheng.manage_cms_client.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;
/**
 * @author Administrator
 * @version 1.0
 **/
@Configuration
public class RabbitmqConfig {
    //队列bean的名称
    public static final String QUEUE_CMS_POSTPAGE = "queue_cms_postpage";
    //交换机的名称
    public static final String EX_ROUTING_CMS_POSTPAGE="ex_routing_cms_postpage";
    //队列的名称
    @Value("${xuecheng.mq.queue}")
    public  String queue_cms_postpage_name;
    //routingKey 即站点Id
    @Value("${xuecheng.mq.routingKey}")
    public  String routingKey;
    /**
     * 交换机配置使用direct类型
     * @return the exchange
     */
    @Bean(EX_ROUTING_CMS_POSTPAGE)
    public Exchange EXCHANGE_TOPICS_INFORM() {
        return ExchangeBuilder.directExchange(EX_ROUTING_CMS_POSTPAGE).durable(true).build();
    }
    //声明队列
    @Bean(QUEUE_CMS_POSTPAGE)
    public Queue QUEUE_CMS_POSTPAGE() {
        Queue queue = new Queue(queue_cms_postpage_name);
        return queue;
    }
    /**
     * 绑定队列到交换机
     *
     * @param queue    the queue
     * @param exchange the exchange
     * @return the binding
     */
    @Bean
    public Binding BINDING_QUEUE_INFORM_SMS(@Qualifier(QUEUE_CMS_POSTPAGE) Queue queue,
@Qualifier(EX_ROUTING_CMS_POSTPAGE) Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(routingKey).noargs();
    }
}

5.service
在Service中定义保存页面静态文件到服务器物理路径方法

@Service
public class PageService {
    @Autowired
    CmsPageRepository cmsPageRepository;
    @Autowired
    CmsSiteRepository cmsSiteRepository;
    @Autowired
    GridFsTemplate gridFsTemplate;
    @Autowired
    GridFSBucket gridFSBucket;
    //将页面html保存到页面物理路径
    public void savePageToServerPath(String pageId){
        Optional optional = cmsPageRepository.findById(pageId);
        if(!optional.isPresent()){
            ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
        }
        //取出页面物理路径
        CmsPage cmsPage = optional.get();
        //页面所属站点
        CmsSite cmsSite = this.getCmsSiteById(cmsPage.getSiteId());
        //页面物理路径
        String pagePath = cmsSite.getSitePhysicalPath() + cmsPage.getPagePhysicalPath() +
cmsPage.getPageName();
        //查询页面静态文件
        String htmlFileId = cmsPage.getHtmlFileId();
        InputStream inputStream = this.getFileById(htmlFileId);
        if(inputStream == null){
            ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_HTMLISNULL);
        }
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(new File(pagePath));
            //将文件内容保存到服务物理路径
            IOUtils.copy(inputStream,fileOutputStream);
        } catch (Exception e) {
 e.printStackTrace();
        }finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //根据文件id获取文件内容
    public InputStream getFileById(String fileId){
        try {
            GridFSFile gridFSFile =
gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
            GridFSDownloadStream gridFSDownloadStream =
gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
            GridFsResource gridFsResource = new GridFsResource(gridFSFile,gridFSDownloadStream);
            return gridFsResource.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    //根据站点id得到站点
    public CmsSite getCmsSiteById(String siteId){
        Optional optional = cmsSiteRepository.findById(siteId);
        if(optional.isPresent()){
            CmsSite cmsSite = optional.get();
            return cmsSite;
        }
        return null;
    }
}
  1. 创建监听
@Component
public class ConsumerPostPage {
    private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerPostPage.class);
    @Autowired
    CmsPageRepository cmsPageRepository;
    @Autowired
    PageService pageService;
    @RabbitListener(queues={"${xuecheng.mq.queue}"})
    public void postPage(String msg){
        //解析消息
        Map map = JSON.parseObject(msg, Map.class);
        LOGGER.info("receive cms post page:{}",msg.toString());
        //取出页面id
        String pageId = (String) map.get("pageId");
        //查询页面信息
        Optional optional = cmsPageRepository.findById(pageId);
        if(!optional.isPresent()){
            LOGGER.error("receive cms post page,cmsPage is null:{}",msg.toString());
            return ;
        }
        //将页面保存到服务器物理路径
        pageService.savePageToServerPath(pageId);
    }
}

四. 页面发布生产方
作为页面发布生产方,需要实现的在用户点击页面发布请求后,根据页面id获得页面信息,查询模型数据和页面模板,通过freemaker实现页面静态化,并将静态化后的页面保存到mongodb,最后,将信息发布到rabbitmq.大致流程如下:
1)、管理员进入管理界面点击“页面发布”,前端请求cms页面发布接口。
2)、cms页面发布接口执行页面静态化,并将静态化页面存储至GridFS中。
3)、静态化成功后,向消息队列发送页面发布的消息,并将站点ID作为routingKey,消息内容为页面id。

代码实现

  1. pom.xml

    org.springframework.boot
    spring‐boot‐starter‐amqp

  1. application.yml
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtualHost: /
  1. RabbitMQConfig配置
    由于cms作为页面发布方要面对很多不同站点的服务器,面对很多页面发布队列,所以这里不再配置队列,只需要配置交换机即可。
package com.xuecheng.manage_cms_client.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 {
    //交换机的名称
    public static final String EX_ROUTING_CMS_POSTPAGE="ex_routing_cms_postpage";
    /**
     * 交换机配置使用direct类型
     * @return the exchange
     */
    @Bean(EX_ROUTING_CMS_POSTPAGE)
    public Exchange EXCHANGE_TOPICS_INFORM() {
        return ExchangeBuilder.directExchange(EX_ROUTING_CMS_POSTPAGE).durable(true).build();
    }
}
  1. service
    //页面发布
public ResponseResult postPage(String pageId){
    //执行静态化
    String pageHtml = this.getPageHtml(pageId);
    if(StringUtils.isEmpty(pageHtml)){
        ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_HTMLISNULL);
    }
    //保存静态化文件
    CmsPage cmsPage = saveHtml(pageId, pageHtml);
    //发送消息
    sendPostPage(pageId);
    return new ResponseResult(CommonCode.SUCCESS);
}
//发送页面发布消息
private void sendPostPage(String pageId){
CmsPage cmsPage = this.getById(pageId);
    if(cmsPage == null){
        ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
    }
    Map msgMap = new HashMap<>();
    msgMap.put("pageId",pageId);
    //消息内容
    String msg = JSON.toJSONString(msgMap);
    //获取站点id作为routingKey
    String siteId = cmsPage.getSiteId();
    //发布消息
    this.rabbitTemplate.convertAndSend(RabbitmqConfig.EX_ROUTING_CMS_POSTPAGE,siteId, msg);
}
//保存静态页面内容
private CmsPage saveHtml(String pageId,String content){
    //查询页面
    Optional optional = cmsPageRepository.findById(pageId);
    if(!optional.isPresent()){
        ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
    }
    CmsPage cmsPage = optional.get();
    //存储之前先删除
    String htmlFileId = cmsPage.getHtmlFileId();
    if(StringUtils.isNotEmpty(htmlFileId)){
        gridFsTemplate.delete(Query.query(Criteria.where("_id").is(htmlFileId)));
    }
    //保存html文件到GridFS
    InputStream inputStream = IOUtils.toInputStream(content);
    ObjectId objectId = gridFsTemplate.store(inputStream, cmsPage.getPageName());
    //文件id
    String fileId = objectId.toString();
    //将文件id存储到cmspage中
    cmsPage.setHtmlFileId(fileId);
    cmsPageRepository.save(cmsPage);
    return cmsPage;
}

最后,分别启动消费端和生产端,并在生产端发送页面发布请求,并在成功后查看对应的站点页面是否更新成功.

你可能感兴趣的:(技术栈)