😎 知识点概览
为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。
本章节为【学成在线】项目的 day06
的内容
-
使用
Spring boot
集成RabbitMQ
和GridFS
实现基于生产者和消费者模型的页面静态化发布的流程。在本章节的知识点中,再次复习了基于
GridFS
和RabbitMQ
的分布式静态页面发布的知识点,深化了记忆。 -
使用三级菜单实现课程计划的查询和添加
这里的技术点不是很多,用到了
Mysql
的表内自连接查询,以及在添加课程的时候,需要考虑一些意外情况的发生,例如再添加课程时,如果该课程的根节点(一级菜单)不存在,则需要为该课程添加一个根节点后再进行该二级节点的添加。
目录
内容会比较多,小伙伴们可以根据目录进行按需查阅。
一、页面发布
0x01 技术方案
本项目使用MQ实现页面发布的技术方案如下:
**技术方案说明 **
1、平台包括多个站点,页面归属不同的站点。
2、发布一个页面应将该页面发布到所属站点的服务器上。
3、每个站点服务部署 cms client
程序,并与交换机绑定,绑定时指定站点Id为routingKey。
指定站点id为 routingKey
就可以实现 cms client
只能接收到所属站点的页面发布消息。
4、页面发布程序向MQ发布消息时指定页面所属站点 Id
为 routingKey
,将该页面发布到它所在服务器上的cms client
。
路由模式分析如下
发布一个页面,需发布到该页面所属的每个站点服务器,其它站点服务器不发布。
比如 发布一个门户的页面,需要发布到每个门户服务器上,而用户中心服务器则不需要发布。
所以本项目采用 routing
模式,用站点 id
作为 routingKey
,这样就可以匹配页面只发布到所属的站点服务器上。
页面发布流程图如下
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
保存到所在服务器指定目录
0x02 页面发布消费方
需求分析
功能分析
创建 Cms Client
工程作为页面发布消费方,将 Cms Client
部署在多个服务器上,它负责接收到页面发布 的消息后从 GridFS
中下载文件在本地保存。
需求如下
1、将 cms Client
部署在服务器,配置队列名称和站点 ID
。
2、cms Client
连接 RabbitMQ
并监听各自的“页面发布队列”
3、cms Client
接收页面发布队列的消息
4、根据消息中的页面 id
从 mongodb
数据库下载页面到本地
创建Cms Client工程
1、POM配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>xc-framework-parent</artifactId>
<groupId>com.xuecheng</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../xc-framework-parent/pom.xml</relativePath>
</parent>
<artifactId>xc-service-manage-cms-client</artifactId>
<!--项目依赖-->
<dependencies>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-framework-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
</project>
2、配置文件
在resources
下配置 application.yml
。
server:
port: 31000
spring:
application:
name: xc-service-manage-cms-client
data:
mongodb:
uri: mongodb://root:123123@localhost:27017
database: xc_cms
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
freemarker:
cache: false #关闭模板缓存,方便测试
settings:
template_update_delay: 0
xuecheng:
mq:
#cms客户端监控的队列名称(不同的客户端监控的队列不能重复)
queue: queue_cms_postpage_01
routingKey: 5a751fab6abb5044e0d19ea1 #此routingKey为门户站点ID3
说明 在配置文件中配置队列的名称,每个 cms client在部署时注意队列名称不要重复
3、启动类
package com.xuecheng.manage_cms_client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@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);
}
}
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;
@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;
//站点id作为routing key
@Value("${xuecheng.mq.routingKey}")
public String routingKey;
/**
* 配置direct交换机
* @return
*/
@Bean(EX_ROUTING_CMS_POSTPAGE)
public Exchange EXCHANGE_DIRECT_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
* @param exchange
* @return
*/
@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();
}
}
配置 MongoConfig
后续的代码中需要将GridFSBucket注入成bean
package com.xuecheng.manage_cms_client.config;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MongoConfig {
@Value("${spring.data.mongodb.database}")
String db;
@Bean
public GridFSBucket getGridFSBucket(MongoClient mongoClient){
MongoDatabase database = mongoClient.getDatabase(db);
GridFSBucket gridFSBucket = GridFSBuckets.create(database);
return gridFSBucket;
}
}
定义消息格式
消息内容采用 json
格式存储数据,如下
页面id 发布页面的id
{
"pageId":""
}
PageDao
1、使用 CmsPageRepository
查询页面信息
public interface CmsPageRepository extends MongoRepository<CmsPage,String> {
}
2、使用 CmsSiteRepository
查询站点信息,主要获取站点物理路径
public interface CmsSiteRepository extends MongoRepository<CmsSite,String> {}
PageService
package com.xuecheng.manage_cms_client.service;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.framework.domain.cms.CmsSite;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CmsCode;
import com.xuecheng.manage_cms_client.dao.CmsPageRepository;
import com.xuecheng.manage_cms_client.dao.CmsSiteRepository;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
@Service
public class PageService {
@Autowired
CmsPageRepository cmsPageRepository;
@Autowired
CmsSiteRepository cmsSiteRepository;
@Autowired
GridFsTemplate gridFsTemplate;
@Autowired
GridFSBucket gridFSBucket;
/**
* 将页面html保存到页面物理路径
* @param pageId
*/
public void savePageToServerPath(String pageId){
Optional<CmsPage> optional = cmsPageRepository.findById(pageId);
//页面不存在则抛出异常
if(!optional.isPresent()){
ExceptionCast.cast(CmsCode.CMS_PAGE_NOT_EXISTS);
}
CmsPage cmsPage = optional.get();
CmsSite cmsSite = this.getCmsSiteById(cmsPage.getSiteId());
if(cmsSite == null){
ExceptionCast.cast(CmsCode.CMS_SIZE_NOT_EXISTS);
}
//原讲义和视频中使用的是sizePathysicaiPath,但是SizePage中没有这个字段,使用PageCms中的PathysicaiPath代替
String pagePath = cmsPage.getPagePhysicalPath() + cmsPage.getPageWebPath(