前台系统开发

来源:互联网 发布:淘宝发货地址怎么改 编辑:程序博客网 时间:2024/05/16 04:14

商城前台页面系统

1、技术选型

后台技术: Spring+SpringMVC
前台技术:html+CSS+JS
商品数据的获取途径:从后台系统的接口获取数据。

2、建立Maven工程

  • pom.xml文件内容如下:
<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>        <groupId>com.enjoyshop.parent</groupId>        <artifactId>enjoyshop-parent</artifactId>        <version>0.0.1-SNAPSHOT</version>    </parent>    <groupId>com.enjoyshop.web</groupId>    <artifactId>enjoyshop-web</artifactId>    <version>1.0.0-SNAPSHOT</version>    <packaging>war</packaging>    <dependencies>        <dependency>            <groupId>com.enjoyshop.common</groupId>            <artifactId>enjoyshop-common</artifactId>            <version>1.0.0-SNAPSHOT</version>        </dependency>        <dependency>            <groupId>com.enjoyshop.manage</groupId>            <artifactId>enjoyshop-manage-pojo</artifactId>            <version>1.0.0-SNAPSHOT</version>        </dependency>        <!-- 单元测试 -->        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <scope>test</scope>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-webmvc</artifactId>        </dependency>        <dependency>            <groupId>org.slf4j</groupId>            <artifactId>slf4j-log4j12</artifactId>        </dependency>        <!-- Jackson Json处理工具包 -->        <dependency>            <groupId>com.fasterxml.jackson.core</groupId>            <artifactId>jackson-databind</artifactId>        </dependency>        <!-- JSP相关 -->        <dependency>            <groupId>jstl</groupId>            <artifactId>jstl</artifactId>        </dependency>        <dependency>            <groupId>javax.servlet</groupId>            <artifactId>servlet-api</artifactId>            <scope>provided</scope>        </dependency>        <dependency>            <groupId>javax.servlet</groupId>            <artifactId>jsp-api</artifactId>            <scope>provided</scope>        </dependency>        <dependency>            <groupId>taglibs</groupId>            <artifactId>standard</artifactId>            <version>1.1.2</version>        </dependency>        <!-- Apache工具组件 -->        <dependency>            <groupId>org.apache.commons</groupId>            <artifactId>commons-lang3</artifactId>        </dependency>        <dependency>            <groupId>org.apache.commons</groupId>            <artifactId>commons-io</artifactId>        </dependency>        <!-- httpclient -->        <dependency>            <groupId>org.apache.httpcomponents</groupId>            <artifactId>httpclient</artifactId>        </dependency>        <dependency>            <groupId>redis.clients</groupId>            <artifactId>jedis</artifactId>            <version>2.6.0</version>        </dependency>        <!-- 时间操作组件 -->        <dependency>            <groupId>joda-time</groupId>            <artifactId>joda-time</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.amqp</groupId>            <artifactId>spring-rabbit</artifactId>            <version>1.4.0.RELEASE</version>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.apache.tomcat.maven</groupId>                <artifactId>tomcat7-maven-plugin</artifactId>                <configuration>                    <port>8082</port>                    <path>/</path>                </configuration>            </plugin>        </plugins>    </build></project>
  • web.xml内容如下
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns="http://java.sun.com/xml/ns/javaee"    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"    id="WebApp_ID" version="2.5">    <display-name>enjoyshop-web</display-name>    <context-param>        <param-name>contextConfigLocation</param-name>        <param-value>classpath:spring/applicationContext*.xml</param-value>    </context-param>    <!--Spring的ApplicationContext 载入 -->    <listener>        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>    </listener>    <!-- 编码过滤器,以UTF8编码 -->    <filter>        <filter-name>encodingFilter</filter-name>        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>        <init-param>            <param-name>encoding</param-name>            <param-value>UTF8</param-value>        </init-param>    </filter>    <filter-mapping>        <filter-name>encodingFilter</filter-name>        <url-pattern>/*</url-pattern>    </filter-mapping>    <!-- 配置SpringMVC框架入口 -->    <servlet>        <servlet-name>enjoyshop-web</servlet-name>        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        <init-param>            <param-name>contextConfigLocation</param-name>            <param-value>classpath:spring/enjoyshop-web-servlet.xml</param-value>        </init-param>        <load-on-startup>1</load-on-startup>    </servlet>    <servlet-mapping>        <servlet-name>enjoyshop-web</servlet-name>        <!-- 伪静态,好处:有利于SEO(搜索引擎优化) -->        <url-pattern>*.html</url-pattern>    </servlet-mapping>    <servlet-mapping>        <servlet-name>enjoyshop-web</servlet-name>        <url-pattern>/service/*</url-pattern>    </servlet-mapping>    <welcome-file-list>        <welcome-file>index.html</welcome-file>    </welcome-file-list></web-app>
  • Spring和SpringMVC配置文件

这部分配置和后台系统配置类似,不做具体描述。

3、编写通用控制类

package com.enjoyshop.web.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.servlet.ModelAndView;import com.enjoyshop.web.service.IndexService;@Controllerpublic class IndexController {    @Autowired    private IndexService indexService;    @RequestMapping(value = "index", method = { RequestMethod.GET, RequestMethod.POST })    public ModelAndView index() {        ModelAndView mv = new ModelAndView("index");        // 获取大广告位数据        String indexAD1 = this.indexService.queryIndexAD1();        //将数据放入request请求中        mv.addObject("indexAD1", indexAD1);        // 右上角广告位数据        String indexAD2 = this.indexService.queryIndexAD2();        mv.addObject("indexAD2", indexAD2);        return mv;    }}
  • 封装HttpClient的接口服务
@Servicepublic class ApiService implements BeanFactoryAware {    private BeanFactory beanFactory;    @Autowired(required=false)    private RequestConfig requestConfig;    /**     * 执行GET请求,响应200返回内容,404返回null     *      * @param url     * @return     * @throws ClientProtocolException     * @throws IOException     */    public String doGet(String url) throws ClientProtocolException, IOException {        // 创建http GET请求        HttpGet httpGet = new HttpGet(url);        httpGet.setConfig(requestConfig);        CloseableHttpResponse response = null;        try {            // 执行请求            response = getHttpClient().execute(httpGet);            // 判断返回状态是否为200            if (response.getStatusLine().getStatusCode() == 200) {                return EntityUtils.toString(response.getEntity(), "UTF-8");            }        } finally {            if (response != null) {                response.close();            }        }        return null;    }    /**     * 带有参数的GET请求,响应200返回内容,404返回null     *      * @param url     * @param params     * @return     * @throws ClientProtocolException     * @throws IOException     * @throws URISyntaxException     */    public String doGet(String url, Map<String, String> params)            throws ClientProtocolException, IOException, URISyntaxException {        URIBuilder builder = new URIBuilder(url);        for (Map.Entry<String, String> entry : params.entrySet()) {            builder.setParameter(entry.getKey(), entry.getValue());        }        return doGet(builder.build().toString());    }    /**     * 执行post请求     *      * @param url     * @param params     * @return     * @throws IOException     */    public HttpResult doPost(String url, Map<String, String> params) throws IOException {        // 创建http POST请求        HttpPost httpPost = new HttpPost(url);        httpPost.setConfig(requestConfig);        if (null != params) {            List<NameValuePair> parameters = new ArrayList<NameValuePair>(0);            for (Map.Entry<String, String> entry : params.entrySet()) {                parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));            }            // 构造一个form表单式的实体            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");            // 将请求实体设置到httpPost对象中            httpPost.setEntity(formEntity);        }        CloseableHttpResponse response = null;        try {            // 执行请求            response = getHttpClient().execute(httpPost);            return new HttpResult(response.getStatusLine().getStatusCode(),                    EntityUtils.toString(response.getEntity(), "UTF-8"));        } finally {            if (response != null) {                response.close();            }        }    }    public HttpResult doPostJson(String url, String json) throws IOException {        // 创建http POST请求        HttpPost httpPost = new HttpPost(url);        httpPost.setConfig(requestConfig);        if (null != json) {            // 构造一个字符串式的实体            StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);            // 将请求实体设置到httpPost对象中            httpPost.setEntity(stringEntity);        }        CloseableHttpResponse response = null;        try {            // 执行请求            response = getHttpClient().execute(httpPost);            return new HttpResult(response.getStatusLine().getStatusCode(),                    EntityUtils.toString(response.getEntity(), "UTF-8"));        } finally {            if (response != null) {                response.close();            }        }    }    //每次都从工厂中获取一个bean,实现httpclient的多例    private CloseableHttpClient getHttpClient() {        return this.beanFactory.getBean(CloseableHttpClient.class);    }    //用于spring初始化时设置BeanFactory,否则spring无法初始化这个service    @Override    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {        this.beanFactory = beanFactory;    }}
  • 获取首页大广告位的数据

通过访问后台系统接口获取数据

    @Autowired    private ApiService apiService;    private static final ObjectMapper MAPPER = new ObjectMapper();    @Value("${ENJOYSHOP_MANAGE_URL}")    private String ENJOYSHOP_MANAGE_URL;    @Value("${INDEX_AD1_URL}")    private String INDEX_AD1_URL;    @Value("${INDEX_AD2_URL}")    private String INDEX_AD2_URL;    public String queryIndexAD1() {        try {            String url = ENJOYSHOP_MANAGE_URL + INDEX_AD1_URL;            String jsonData = this.apiService.doGet(url);            if (StringUtils.isEmpty(jsonData)) {                return null;            }            // 解析json,生成相应的json数据            JsonNode jsonNode = MAPPER.readTree(jsonData);            ArrayNode rows = (ArrayNode) jsonNode.get("rows");            List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();            for (JsonNode row : rows) {                Map<String, Object> map = new LinkedHashMap<String, Object>();                map.put("srcB", row.get("pic").asText());                map.put("height", 240);                map.put("alt", row.get("title").asText());                map.put("width", 670);                map.put("src", row.get("pic").asText());                map.put("widthB", 550);                map.put("href", row.get("url").asText());                map.put("heightB", 240);                result.add(map);            }            return MAPPER.writeValueAsString(result);        } catch (Exception e) {            e.printStackTrace();        }        return null;    }
  • 获取右侧小广告位的数据
public String queryIndexAD2() {        try {            String url = ENJOYSHOP_MANAGE_URL + INDEX_AD2_URL;            String jsonData = this.apiService.doGet(url);            if (StringUtils.isEmpty(jsonData)) {                return null;            }            // 解析json,生成前端所需要的json数据            JsonNode jsonNode = MAPPER.readTree(jsonData);            ArrayNode rows = (ArrayNode) jsonNode.get("rows");            List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();            for (JsonNode row : rows) {                Map<String, Object> map = new LinkedHashMap<String, Object>();                map.put("width", 310);                map.put("height", 70);                map.put("src", row.get("pic").asText());                map.put("href", row.get("url").asText());                map.put("alt", row.get("title").asText());                map.put("widthB", 210);                map.put("heightB", 70);                map.put("srcB", row.get("pic").asText());                result.add(map);            }            return MAPPER.writeValueAsString(result);        } catch (Exception e) {            e.printStackTrace();        }        return null;    }

4、首页左侧商品类目的显示

  • 跨域请求问题

因为商品类目的数据接口在后台系统中,所以前台系统可以直接通过接口服务获取数据。但是后台系统的URL为manage.enjoyshop.com,而前台系统的URL为www.enjoyshop.com。这里涉及到了跨域请求的问题。浏览器对ajax请求的限制,不允许跨域请求资源。
http://www.a.com请求http://www.b.com下的资源是跨域。
http://www.a.com请求http://www.a.com:8080下的资源是跨域。
http://a.a.com请求http://b.a.com下的资源是跨域。
http://www.a.com  http://www.a.com/api下的资源不是跨域。
不同的域名或不同的端口都是跨域请求。

  • 跨域请求解决方案

可以使用jsonp技术来解决这个问题。具体关于jsonp的介绍请参考这里。点我

  • 前端页面请求后台接口
var category = {OBJ: $("#_JD_ALLSORT"),        URL_Serv: "http://manage.enjoyshop.com/rest/api/item/cat?callback=category.getDataService",    ·······    }
  • 乱码问题解决
    虽然jsonp解决了跨域请求的问题,成功的请求到了数据。但是展示出来时却是乱码。这是SpringMVC消息转化器引起的。如果使用默认的消息转化器,其对于字符串的编码采用的是ISO-8859-1。如下所示:
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {    public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");    private final Charset defaultCharset;    ······}

这里来自定义一个消息转化器来处理json

package com.enjoyshop.common.spring.exetend.converter.json;import java.io.IOException;import javax.servlet.http.HttpServletRequest;import org.apache.commons.io.IOUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.http.HttpOutputMessage;import org.springframework.http.converter.HttpMessageNotWritableException;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import com.fasterxml.jackson.core.JsonEncoding;import com.fasterxml.jackson.core.JsonProcessingException;//继承自MappingJackson2HttpMessageConverter,自定义消息转化器public class CallbackMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {    // 做jsonp的支持的标识,在请求参数中加该参数    private String callbackName;    @Override    protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {        // 从threadLocal中获取当前的Request对象        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();        String callbackParam = request.getParameter(callbackName);        if(StringUtils.isEmpty(callbackParam)){            // 没有找到callback参数,直接返回json数据            super.writeInternal(object, outputMessage);        }else{            JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());            try {                String result =callbackParam+"("+super.getObjectMapper().writeValueAsString(object)+");";                IOUtils.write(result, outputMessage.getBody(),encoding.getJavaName());            }            catch (JsonProcessingException ex) {                throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);            }        }    }    public String getCallbackName() {        return callbackName;    }    public void setCallbackName(String callbackName) {        this.callbackName = callbackName;    }}

需要在SpringMVC的配置文件中来加载该消息转化器。

    <!-- 定义注解驱动 -->    <mvc:annotation-driven>      <mvc:message-converters register-defaults="true">        <!-- 配置一个消息转化器解决中文乱码(将对象序列化为json时默认使用iso-8859-1)         <bean class="org.springframework.http.converter.StringHttpMessageConverter">          <constructor-arg index="0" value="UTF-8"></constructor-arg>        </bean> -->         <!-- 设置自定义的消息转化器处理json -->        <bean class="com.enjoyshop.common.spring.exetend.converter.json.CallbackMappingJackson2HttpMessageConverter">           <property name="callbackName" value="callback"></property>        </bean>      </mvc:message-converters>    </mvc:annotation-driven>

5、商品详情信息的显示

商品详情页面的URL形式为http://www.enjoyshop.com/item/{itemId}.html。

  • 相应的controller层代码如下:
@RequestMapping("item")@Controllerpublic class ItemController {    @Autowired    private ItemService itemService;    @RequestMapping(value="{itemId}",method=RequestMethod.GET)    public ModelAndView showDetail(@PathVariable("itemId") Long itemId){        ModelAndView mv=new ModelAndView("item");        //添加商品基本数据        Item item=this.itemService.queryItemById(itemId);        mv.addObject("item",item);        //添加商品描述数据        ItemDesc itemDesc=this.itemService.queryItemDescByItemId(itemId);        mv.addObject("itemDesc", itemDesc);        //添加商品规格参数        String itemParam=this.itemService.queryItemParamByItemId(itemId);        mv.addObject("itemParam", itemParam);        return mv;    }}
  • 对应的service层关键代码如下:这里通过apiService向后台系统请求数据,并且为商品信息加入了缓存功能
@Servicepublic class ItemService {    @Autowired    private ApiService apiService;    @Value("${ENJOYSHOP_MANAGE_URL}")    private String ENJOYSHOP_MANAGE_URL;    private static final ObjectMapper MAPPER = new ObjectMapper();    public static final String REDIS_KEY="ENJOYSHOP_WEB_ITEM_DETAIL_";    private static final Integer REDIS_TIME=60*60*24;    @Autowired    private RedisService redisService;    //获取商品基本信息    public Item queryItemById(Long itemId) {        String key=REDIS_KEY+itemId;        try {            //先从缓存中命中            String cacheData=this.redisService.get(key);            if(StringUtils.isNotEmpty(cacheData)){                return MAPPER.readValue(cacheData, Item.class);            }        } catch (Exception e1) {            e1.printStackTrace();        }        try {            String url = ENJOYSHOP_MANAGE_URL + "/rest/item/" + itemId;            String jsonData = this.apiService.doGet(url);            if (StringUtils.isEmpty(jsonData)) {                return null;            }            try {//需要加try catch防止影响原逻辑                //写入缓存                this.redisService.set(key, jsonData, REDIS_TIME);            } catch (Exception e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            return MAPPER.readValue(jsonData, Item.class);        } catch (Exception e) {            e.printStackTrace();        }        return null;    }    //获取商品描述信息    public ItemDesc queryItemDescByItemId(Long itemId) {        try {            String url = ENJOYSHOP_MANAGE_URL + "/rest/item/desc/" + itemId;            String jsonData = this.apiService.doGet(url);            if (StringUtils.isEmpty(jsonData)) {                return null;            }            return MAPPER.readValue(jsonData, ItemDesc.class);        } catch (Exception e) {            e.printStackTrace();        }        return null;    }    //获取商品规格参数信息    public String queryItemParamByItemId(Long itemId) {        try {            String url = ENJOYSHOP_MANAGE_URL + "/rest/item/param/item/" + itemId;            String jsonData = this.apiService.doGet(url);            // 解析JSON            JsonNode jsonNode = MAPPER.readTree(jsonData);            ArrayNode paramData = (ArrayNode) MAPPER.readTree(jsonNode.get("paramData").asText());            StringBuilder sb = new StringBuilder();            sb.append(                    "<table cellpadding=\"0\" cellspacing=\"1\" width=\"100%\" border=\"0\" class=\"Ptable\"><tbody>");            for (JsonNode param : paramData) {                sb.append("<tr><th class=\"tdTitle\" colspan=\"2\">" + param.get("group").asText() + "</th></tr>");                ArrayNode params = (ArrayNode) param.get("params");                for (JsonNode p : params) {                    sb.append("<tr><td class=\"tdTitle\">" + p.get("k").asText() + "</td><td>" + p.get("v").asText()                            + "</td></tr>");                }            }            sb.append("</tbody></table>");            return sb.toString();        } catch (Exception e) {            e.printStackTrace();        }        return null;    }}
  • 后台系统提供的接口如下:
    查询商品基本信息
@RequestMapping(value = "{itemId}", method = RequestMethod.GET)    public ResponseEntity<Item> queryById(@PathVariable("itemId") Long itemId) {        try {            Item item = this.itemService.queryById(itemId);            if (null == item) {                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);            }            return ResponseEntity.ok(item);        } catch (Exception e) {            e.printStackTrace();        }        // 出错 500        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);    }

查询商品描述信息

@RequestMapping(value = "{itemId}", method = RequestMethod.GET)    public ResponseEntity<ItemDesc> queryByItemId(@PathVariable("itemId") Long itemId) {        try {            ItemDesc itemDesc = this.itemDescService.queryById(itemId);            if (null == itemDesc) {                // 资源不存在,404                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);            }            // 200            return ResponseEntity.ok(itemDesc);        } catch (Exception e) {            e.printStackTrace();        }        // 错误,500        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);    }

查询商品规格参数信息

@RequestMapping(method = RequestMethod.GET)    public ResponseEntity<EasyUIResult> queryItemParamList(@RequestParam(value = "page", defaultValue = "1") Integer page,            @RequestParam(value = "rows", defaultValue = "30") Integer rows) {        try {            PageInfo<ItemParam> pageInfo = this.itemParamService.queryPageList(page, rows);            EasyUIResult easyUIResult = new EasyUIResult(pageInfo.getTotal(), pageInfo.getList());            return ResponseEntity.ok(easyUIResult);        } catch (Exception e) {            e.printStackTrace();        }        // 出错 500        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);    }

6、解决系统间的数据同步问题

  • 面临的问题:

后台系统中如果将商品修改,前台系统没有进行数据的同步,仍然从Redis的缓存中获取数据,导致前端系统不能够实时显示最新的数据。

  • 解决方案一:手动实现系统间的更新通知

基本思路:如果在后台系统中商品修改后向其他系统发送通知,其他系统做出对应的处理(清理Redis中的缓存),就可以实现同步。
首先在前台系统中开发一个接口,用于后台系统调用该接口来删除缓存。

@RequestMapping("item/cache")@Controllerpublic class ItemCacheController {    @Autowired    private RedisService redisService;    //删除缓存数据    @RequestMapping(value="{itemId}",method=RequestMethod.POST)    public ResponseEntity<Void> deleteCache(@PathVariable("itemId") Long itemId){        try {            String key=ItemService.REDIS_KEY+itemId;            this.redisService.del(key);            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();        } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();    }}

在后台系统更新商品数据的逻辑中,加入调用前台接口的方法。

public void updateItem(Item item, String desc, String itemParams) {        // 强制设置不能修改的字段为null        item.setStatus(null);        item.setCreated(null);        super.updateSelective(item);        // 修改商品描述数据        ItemDesc itemDesc = new ItemDesc();        itemDesc.setItemId(item.getId());        itemDesc.setItemDesc(desc);        this.itemDescService.updateSelective(itemDesc);        // 修改商品规格参数        this.itemParamItemService.updateItemParamItem(item.getId(), itemParams);        try {        // 需要通知其他系统进行数据更新        String url = ENJOYSHOP_WEB_URL + "/item/cache/" + item.getId() +".html";        this.apiService.doPost(url, null);        } catch (Exception e) {        e.printStackTrace();      }    }
  • 解决方案二:使用消息队列RabbitMQ

RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
关于RabbitMQ的更多介绍请参考这里。点我

7、使用RabbitMQ解决据同步问题

  • 选用队列模式

这里采用通配符模式,这种模式更具有扩展性。

  • 队列和交换机的绑定方法

有两种方法:
1、 在配置文件中将队列和交换机完成绑定。
2、 在RabbitMQ的管理界面中手动完成绑定。
这里采用手动绑定的方式,优点如下:
1、 若使用配置文件绑定的方式,如果绑定关系发生变化,需要修改配置文件,并且服务需要重启。
2、 采用手动绑定,管理更加灵活,更容易对绑定关系进行权限管理,流程管理。

  • 发送的消息内容

1、 将Item对象做json序列化发送
a) 数据大
b) 有些数据其他人是可能用不到的
2、 发送商品的id、操作类型。
这里采用第二种方式。

  • 在后台系统中添加Spring和RabbitMQ的整合文件
<beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"    xsi:schemaLocation="http://www.springframework.org/schema/rabbit    http://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd    http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">    <!-- 定义RabbitMQ的连接工厂 -->    <rabbit:connection-factory id="connectionFactory"        host="${rabbitmq.host}" port="${rabbitmq.port}" username="${rabbitmq.username}" password="${rabbitmq.password}"        virtual-host="${rabbitmq.vhost}" />    <!-- 定义管理 -->    <rabbit:admin connection-factory="connectionFactory"/>    <!-- 定义交换机 -->    <rabbit:topic-exchange name="enjoyshop-item-exchange" durable="true" auto-declare="true"/>    <!-- 定义模板 -->    <rabbit:template id="template" connection-factory="connectionFactory" exchange="enjoyshop-item-exchange"/></beans>
  • 后台系统中在新增和修改商品时发送消息
    @Autowired    private RabbitTemplate rabbitTemplate;    private static final ObjectMapper MAPPER = new ObjectMapper();    public void saveItem(Item item, String desc, String itemParams) {        // 默认为上架状态        item.setStatus(1);        // 防止前端恶意添加id值        item.setId(null);        //保存商品信息        super.save(item);        //构建ItemDesc数据并保存        ItemDesc itemDesc = new ItemDesc();        itemDesc.setItemId(item.getId());        itemDesc.setItemDesc(desc);        this.itemDescService.save(itemDesc);                //构建ItemParamItem数据并保存        ItemParamItem itemParamItem = new ItemParamItem();        itemParamItem.setItemId(item.getId());        itemParamItem.setParamData(itemParams);        this.itemParamItemService.save(itemParamItem);        //发送mq消息        sendMsg(item.getId(), "insert");    }    public void updateItem(Item item, String desc, String itemParams) {        // 强制设置不能修改的字段为null        item.setStatus(null);        item.setCreated(null);        super.updateSelective(item);        // 修改商品描述数据        ItemDesc itemDesc = new ItemDesc();        itemDesc.setItemId(item.getId());        itemDesc.setItemDesc(desc);        this.itemDescService.updateSelective(itemDesc);        // 修改商品规格参数        this.itemParamItemService.updateItemParamItem(item.getId(), itemParams);        // try {        // // 需要通知其他系统进行数据更新        // String url = ENJOYSHOP_WEB_URL + "/item/cache/" + item.getId() +        // ".html";        // this.apiService.doPost(url, null);        // } catch (Exception e) {        // e.printStackTrace();        // }        //发送mq消息        sendMsg(item.getId(), "update");    }    private void sendMsg(long itemId,String type){        try {            //发送mq详细通知其他系统进行数据更新            Map<String, Object> msg = new HashMap<String, Object>();            msg.put("itemId", itemId);            msg.put("type", type);            msg.put("date", System.currentTimeMillis());            this.rabbitTemplate.convertAndSend("item."+type, MAPPER.writeValueAsString(msg));        } catch (Exception e) {            e.printStackTrace();        }    }
  • 前台系统接收消息

Spring和RabbitMQ整合文件

<beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"    xsi:schemaLocation="http://www.springframework.org/schema/rabbit    http://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd    http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">    <!-- 定义RabbitMQ的连接工厂 -->    <rabbit:connection-factory id="connectionFactory"        host="${rabbitmq.host}" port="${rabbitmq.port}" username="${rabbitmq.username}" password="${rabbitmq.password}"        virtual-host="${rabbitmq.vhost}" />    <!-- 定义管理 -->    <rabbit:admin connection-factory="connectionFactory"/>    <!-- 定义队列 -->    <rabbit:queue name="enjoyshop-web-item-queue" durable="true" auto-declare="true"/>    <!-- 定义消费者 -->    <bean id="itemMQHandler" class="com.enjoyshop.web.mq.handler.ItemMQHandler"/>    <!-- 消费者监听队列 -->    <rabbit:listener-container connection-factory="connectionFactory">        <rabbit:listener ref="itemMQHandler" method="execute" queue-names="enjoyshop-web-item-queue"/>    </rabbit:listener-container></beans>

接收消息,清理缓存

package com.enjoyshop.web.mq.handler;import org.springframework.beans.factory.annotation.Autowired;import com.enjoyshop.common.service.RedisService;import com.enjoyshop.web.service.ItemService;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;public class ItemMQHandler {    private static final ObjectMapper MAPPER = new ObjectMapper();    @Autowired    private RedisService redisService;    //拿到商品id,删除redis缓存    public void execute(String msg) {        try {            JsonNode jsonNode = MAPPER.readTree(msg);            Long itemId = jsonNode.get("itemId").asLong();            String key = ItemService.REDIS_KEY + itemId;            this.redisService.del(key);        } catch (Exception e) {            e.printStackTrace();        }    }}
原创粉丝点击