具體需求:
後臺添加商品後,需要執行兩個操作:
- 同步索引庫(商品搜索使用了Solr實現)
- 生成靜態頁面(使用freemarker)
實現構思:
使用消息隊列。MQ作爲消息中間件,傳遞的消息內容爲新增商品的ID。
準備工作:
在需要的地方添加相應的依賴(基礎依賴就不再說了)
商品服務需要發送商品添加消息,所以需要添加三個依賴,分別是整合Spring需要的兩個Jar和ActiveMQ的Jar:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId></dependency><dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId></dependency>
索引服務需要接收消息,然後同步索引庫,所以需要添加四個依賴,分別是整合Spring需要的兩個Jar和ActiveMQ的Jar和Solr客戶端的Jar:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId></dependency><dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId></dependency><dependency> <groupId>org.apache.solr</groupId> <artifactId>solr-solrj</artifactId></dependency>
商品詳情工程需要就收消息,然後生成靜態頁面,所以需要添加四個依賴,分別是整合Spring需要的兩個Jar和ActiveMQ的Jar和Solr客戶端的Jar:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId></dependency><dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId></dependency><dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId></dependency>
具體實現:
1.商品服務添加商品信息後,發佈商品添加消息
spring-activemq.xml
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供 --><bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://192.168.25.131:61616" /><!-- activemq地址 --></bean> <!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory --><bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory --> <property name="targetConnectionFactory" ref="targetConnectionFactory" /></bean> <!-- 配置生產者 --><!-- Spring提供的JMS工具類,它可以進行消息發送、接收等 --><bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 --> <property name="connectionFactory" ref="connectionFactory" /></bean> <!--配置主題目的地,一對多的,以爲我們有多個接收方,所以這裏使用topic而不是queue --><bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic"> <constructor-arg value="itemAddTopic" /></bean>
ItemServiceImpl.java
@Autowiredprivate JmsTemplate jmsTemplate;@Resource // 默認通過id注入,找不到再通過類型注入private Destination topicDestination; @Overridepublic E3Result addItem(TbItem item, String desc, String itemParams) { // 生成商品ID final long itemId = IDUtils.genItemId(); /** * 將商品信息插入數據庫中 */ // 發送一個商品添加消息 jmsTemplate.send(topicDestination, new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { TextMessage textMessage = session.createTextMessage(itemId + ""); return textMessage; } }); // 返回成功 return E3Result.ok();}
2.索引服務配置監聽器,接收到商品添加消息後,同步索引庫
ItemAddMessageListener.java
public class ItemAddMessageListener implements MessageListener { @Autowired private ItemMapper itemMapper; @Autowired private SolrServer solrServer; @Override public void onMessage(Message message) { try { // 從消息中取商品ID TextMessage textMessage = (TextMessage) message; String text = textMessage.getText(); Long itemId = new Long(text); // 等待事務提交,不等待的話有可能下面會查不到商品信息 Thread.sleep(1000); // 根據商品ID查詢商品信息 SearchItem searchItem = itemMapper.getItemById(itemId); // 創建一個文檔對象 SolrInputDocument document = new SolrInputDocument(); // 向文檔對象中添加域 document.addField("id", searchItem.getId()); document.addField("item_title", searchItem.getTitle()); document.addField("item_sell_point", searchItem.getSell_point()); document.addField("item_price", searchItem.getPrice()); document.addField("item_image", searchItem.getImage()); document.addField("item_category_name", searchItem.getCategory_name()); // 把文檔對象寫入索引庫 solrServer.add(document); // 提交 solrServer.commit(); } catch (Exception e) { e.printStackTrace(); } }}
spring-activemq.xml
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供 --><bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://192.168.25.131:61616" /><!-- activemq地址 --></bean> <!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory --><bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory --> <property name="targetConnectionFactory" ref="targetConnectionFactory" /></bean> <!--配置主題目的地,一對多的 --><bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic"> <constructor-arg value="itemAddTopic" /></bean> <!-- 接收消息 --><!-- 配置消息監聽器,監聽商品添加消息,同步索引庫 --><bean id="itemAddMessageListener" class="cn.e3mall.search.message.ItemAddMessageListener"/><!-- 消息監聽容器 --><bean class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory" /> <property name="destination" ref="topicDestination" /> <property name="messageListener" ref="itemAddMessageListener" /></bean>
3.商品詳情工程配置監聽器,接收到商品添加消息後,生成商品詳情靜態頁面
首先需要配置用於生成靜態頁面的模板,具體的配置方法請參看FreeMarker教程,在這裏就不詳細敘述了,我這裏配置好的模板如下:
item.ftl是商品詳情頁面,其他的幾個ftl是include進去的,都需要配置。另外,關注Java知音公衆號,回覆“後端面試”,送你一份面試題寶典!
FreeMarker的配置:
<!-- 配置Freemaker --><bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/ftl/" /><!-- 指定模板所在目錄 --> <property name="defaultEncoding" value="UTF-8" /><!-- 指定默認編碼方式 --></bean>
監聽器:
public class HtmlGenListener implements MessageListener { @Autowired private ItemService itemService; @Autowired private FreeMarkerConfigurer freeMarkerConfigurer; @Value("${HTML_GEN_PATH}") private String HTML_GEN_PATH; // 生成的靜態頁面保存的位置 @Override public void onMessage(Message message) { try { // 從商品中取商品ID TextMessage textMessage = (TextMessage) message; String text = textMessage.getText(); Long itemId = new Long(text); // 等待事務提交(防止還未插入數據庫就查詢) Thread.sleep(1000); // 根據商品id查詢商品信息,商品基本信息和商品描述信息 TbItem tbItem= itemService.getItemById(itemId); Item item = new Item(tbItem); item.setPrice(item.getPrice() / 100); // 轉換價格 // 取商品描述 TbItemDesc itemDesc = itemService.getItemDesc(itemId); // 創建一個數據集,將模板所需的數據全部放進去 Map<String, Object> data = new HashMap<>(); data.put("item", item); data.put("itemDesc", itemDesc); // 加載模板對象 Configuration configuration = freeMarkerConfigurer.getConfiguration(); Template template = configuration.getTemplate("item.ftl"); // 創建一個輸出流,指定輸出的目錄以及文件名 Writer out = new FileWriter(HTML_GEN_PATH + itemId + ".html"); // 生成靜態頁面 template.process(data, out); // 關閉流 out.close(); } catch (Exception e) { e.printStackTrace(); } }}
引用外部文件:
監聽器配置:
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供 --><bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://192.168.25.131:61616" /></bean> <!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory --><bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory --> <property name="targetConnectionFactory" ref="targetConnectionFactory" /></bean> <!--配置主題目的地,一對多的 --><bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic"> <constructor-arg value="itemAddTopic" /></bean> <!-- 接收消息 --><!-- 監聽商品添加消息,同步生成靜態頁面 --><bean id="htmlGenListener" class="cn.e3mall.item.message.HtmlGenListener"/><bean class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory" /> <property name="destination" ref="topicDestination" /> <property name="messageListener" ref="htmlGenListener" /></bean>
至此,功能就實現了。
功能測試
首先查看一下索引庫,可以看到目前有943條商品數據
再次查看索引庫,可以看到新增加了一條數據,現在有944條數據:
搜索新添加的商品:,可以看到,已經能夠查到了:
查看商品詳情,與本地生成的靜態頁面進行對比:
本地生成靜態文件:
訪問本地靜態文件(這裏使用了Nginx服務器訪問靜態文件)
可以對比一下,兩個絕對是一模一樣的,這樣以後用戶訪問商品詳情的時候就可以返回給其一個靜態頁面,大大減小了服務器壓力,訪問速度加快,也提升了用戶體驗。