大数据互联网架构阶段 前台系统架构 跨域请求

电商项目 前台系统的架构

零、目录

  • 前台架构设计
  • 前台分类树
  • 跨域请求技术
    • jsonp
    • httpClient

一 、前台架构设计

  1. 不能直接访问数据库 , 需要通过后台访问数据
  2. 架构: 单通道连接资源 , 数据资源不能交叉访问
  3. 安全: 前台 对外网挂接 , 不适合访问数据库 , 有安全隐患 , 前台需要跨系统访问数据
  4. 跨系统访问数据用到的的技术:
    1. jsonp
    2. httpClient
    3. RabbitMQ

二、 前台分类树

  1. 商品分类:
    1. 在后台系统中的商品分类请求的设计是分级请求的 , 先展示一级分类 ,然后点击时获取被点击的id , 获取他的子分类
    2. 但是在前台系统商品分类的设计中 , 只发起一次ajax异步请求 , 请求到所有的封装好的三层结构的商品分类数据(使用map+ list+ 三层循环嵌套实现)
  2. 商品分类数据的结构
  3. 数据的要求

    1. 前台需要一个json串 , ,需要构建一个对象ItemCatResult,内部只有一个data属性的集合
    2. 整体的返回结构都在data中完成
    3. 每个data中的list对象进行封装嵌套完成3层结构

      u, n, i;
      其中u和n都是字符串
      i是list集合,集合的元素类型又是itemCatData
      
    4. 按照以上要求完成pojo设计

      public class ItemCatResult {
          @JsonProperty("data")
          private List itemCats ;
      
          public List getItemCats() {
              return itemCats;
          }
      
          public void setItemCats(List itemCats) {
              this.itemCats = itemCats;
          }
      
      } 
      
      public class ItemCatData {
      
          @JsonProperty("u")//传递时以u传递 , 减少跨域传递数据的字节数 , 加快传递速度
          private String url;
          @JsonProperty("n")
          private String name;
          @JsonProperty("i")
          private List items;
          public String getUrl() {
              return url;
          }
          public void setUrl(String url) {
              this.url = url;
          }
          public String getName() {
              return name;
          }
          public void setName(String name) {
              this.name = name;
          }
          public List getItems() {
              return items;
          }
          public void setItems(List items) {
              this.items = items;
          }
      
      
      }
      完成三成结构数据的封装
      处理前台分类树 请求,返回具有三层结构偶的数据
          @RequestMapping("web/itemcat/all")
          @ResponseBody
          public ItemCatResult queryItemCat() {
              //整理三层结构的数据
              //先获取所有的数据
              List itemCats = itemCatService.queryAll();
              //创建一个返回对象
              ItemCatResult result = new ItemCatResult();
              //引入一个map + list + 三层for循环嵌套
              //map维护当前分类中的父子关系,  key保存一个id , value保存该id的所有子类(list形式)
              Map> map  = new HashMap>();
              //对map进行处理
              for(ItemCat itemCat : itemCats) {
                  //从当前的所有list中获取一个parentId
                  if(!map.containsKey(itemCat.getParentId())) {
                      //创建一个map的元素 ,key为整个parentId , value为该id的所有子类(list形式)
                      map.put(itemCat.getParentId(), new ArrayList());
                  }
                  //把当前对象放入map维护的父子关系中 。 
                  map.get(itemCat.getParentId()).add(itemCat);
              }
              //开始构建result对象
              //一级菜单内容 , 从map获取一级分类的list
              List itemCatList01 = map.get(new Long(0));
              //从一级菜单入手完成result的data
              List itemCatDataList1 = new ArrayList();
              //完成data里的数据
              for(ItemCat itemCat : itemCatList01) {
                  ItemCatData itemCatData1 = new ItemCatData();
                  itemCatData1.setUrl("/products/"+itemCat.getId()+".html");
                  itemCatData1.setName(""+itemCat.getName()+"");
                  //进行当前对象的二层菜单的数据封装个 , 使用第二层for
                  List itemCatDataList2 = new ArrayList();
                  List itemCatList02 = map.get(itemCat.getId());
                  for(ItemCat itemCats2 : itemCatList02) {
                      ItemCatData itemCatData2 = new ItemCatData();
                      itemCatData2.setUrl("/products/"+itemCats2.getId()+".html");
                      itemCatData2.setName(""+itemCats2.getName()+"");
      
                      List itemCatDataList3 = new ArrayList();
                      List itemCatList03 = map.get(itemCats2.getId());
                      for(ItemCat itemCat3 : itemCatList03) {
                          itemCatDataList3.add("/products/"+itemCat3.getId()+".html"+itemCat3.getName());
                      }
                      itemCatData2.setItems(itemCatDataList3);
                      itemCatDataList2.add(itemCatData2);
                  }
                  itemCatData1.setItems(itemCatDataList2);
                  itemCatDataList1.add(itemCatData1);
              }
      
              result.setItemCats(itemCatDataList1);
      
              return result;
          }
      

三 、 跨域请求问题

  1. 在电商项目中 , 为了提高数据库的可用性 , 引入了缓存技术 , 而像电商项目这样的大型项目一般都需需要进行横向拆分成多个特定功能的系统来同时开发 , 但是由于多个系统去访问数据库执行魂村逻辑时, 可能引起数据混乱 , 这就限制了数据只能单通道访问 , 前台系统在缓存中没有数据时不能直接访问数据库 , 而是通过后台系统去请求数据 。 , 这就引出了跨域访问的种种问题 。

四、 Jsonp跨域请求数据

  1. 前台系统的页面发出ajax请求 , 到后台系统请求商品分类数据
  2. 但是前台和后台是两个系统 , 从后台访问数据属于跨域访问 , 无法使用json格式数据 , 需要jsonp
  3. 前台调用的jquery代码

    $.getJSONP(url  , 参数){}
    
  4. json广泛流行的原因是 , json是js原声支持的格式, 在js中可以自动将json格式的数据转换为对象后调用其中的属性 。
  5. 但是由于js的同源策略导致前台系统获取到后台系统的数据后不能够解析 , 想要解决js同源策略引起的跨域请求问题 , 需要引入jsonp技术
  6. jsonp实际上就是在json字符串外包装一个方法 , 利用script标签可以将跨域的数据请求到 , 然后通过一个js方法即可解析 , 但是要注意js的方法名必须要与json外层包装的方法名一致
  7. 但是后台通过@ResponseBody返回的数据默认就是json格式 , 需要自己构建一个responseBody类似的对象将返回的数据转换成jsonp需要的格式 。
  8. 开发步骤:

    1. 前台发出请求 url?callback=xxxx
    2. 后台controller处理这个请求时不接受callback参数 , 正常返回数据即可
    3. 自定义responseBody转换器

          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 {
                          //将对象转换为json串,然后用回调方法包括起来
                          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;
              }
      
          }
      
    4. 将自定义的转换器配置在spring-mvc配置文件中

      
          
              
              
                  
                  
                      
                  
      
                  
                  
                      
                      
                      
                  
              
          
      
    5. 这样在返回就过时 , 就会在结果外层包装一个callback参数对应的方法 , 形成jsonp格式的数据 。

四、 httpClient跨域请求

  1. 自定义httpClientService

    @Service
    public class HttpClientService {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientService.class);
    
        @Autowired(required=false)
        private CloseableHttpClient httpClient;
    
        @Autowired(required=false)
        private RequestConfig requestConfig;
    
        /**
         * 执行get请求
         * 
         * @param url
         * @return
         * @throws Exception
         */
        public String doGet(String url,Map params,String encode) throws Exception {
            LOGGER.info("执行GET请求,URL = {}", url);
            if(null != params){
                URIBuilder builder = new URIBuilder(url);
                for (Map.Entry entry : params.entrySet()) {
                    builder.setParameter(entry.getKey(), entry.getValue());
                }
                url = builder.build().toString();
            }
            // 创建http GET请求
            HttpGet httpGet = new HttpGet(url);
            httpGet.setConfig(requestConfig);
            CloseableHttpResponse response = null;
            try {
                // 执行请求
                response = httpClient.execute(httpGet);
                // 判断返回状态是否为200
                if (response.getStatusLine().getStatusCode() == 200) {
                    if(encode == null){
                        encode = "UTF-8";
                    }
                    return EntityUtils.toString(response.getEntity(), encode);
                }
            } finally {
                if (response != null) {
                    response.close();
                }
                // 此处不能关闭httpClient,如果关闭httpClient,连接池也会销毁
            }
            return null;
        }
    
        public String doGet(String url, String encode) throws Exception{
            return this.doGet(url, null, encode);
        }
    
        public String doGet(String url) throws Exception{
            return this.doGet(url, null, null);
        }
    
        /**
         * 带参数的get请求
         * 
         * @param url
         * @param params
         * @return
         * @throws Exception
         */
        public String doGet(String url, Map params) throws Exception {
            return this.doGet(url, params, null);
        }
    
        /**
         * 执行POST请求
         * 
         * @param url
         * @param params
         * @return
         * @throws Exception
         */
        public String doPost(String url, Map params,String encode) throws Exception {
            // 创建http POST请求
            HttpPost httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
    
            if (null != params) {
                // 设置2个post参数,一个是scope、一个是q
                List parameters = new ArrayList(0);
                for (Map.Entry entry : params.entrySet()) {
                    parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
    
                // 构造一个form表单式的实体
                UrlEncodedFormEntity formEntity = null;
                if(encode!=null){
                    formEntity = new UrlEncodedFormEntity(parameters,encode);
                }else{
                    formEntity = new UrlEncodedFormEntity(parameters);
                }
                // 将请求实体设置到httpPost对象中
                httpPost.setEntity(formEntity);
            }
    
            CloseableHttpResponse response = null;
            try {
                // 执行请求
                response = httpClient.execute(httpPost);
                // 判断返回状态是否为200
                if (response.getStatusLine().getStatusCode() == 200) {
                    return EntityUtils.toString(response.getEntity(), "UTF-8");
                }
            } finally {
                if (response != null) {
                    response.close();
                }
            }
            return null;
        }
    
    
        /**
         * 执行POST请求
         * 
         * @param url
         * @param params
         * @return
         * @throws Exception
         */
        public String doPost(String url, Map params) throws Exception {
            // 创建http POST请求
            HttpPost httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
    
            if (null != params) {
                // 设置2个post参数,一个是scope、一个是q
                List parameters = new ArrayList(0);
                for (Map.Entry entry : params.entrySet()) {
                    parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
    
                // 构造一个form表单式的实体
                UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters);
                // 将请求实体设置到httpPost对象中
                httpPost.setEntity(formEntity);
            }
    
            CloseableHttpResponse response = null;
            try {
                // 执行请求
                response = httpClient.execute(httpPost);
                // 判断返回状态是否为200
                if (response.getStatusLine().getStatusCode() == 200) {
                    return EntityUtils.toString(response.getEntity(), "UTF-8");
                }
            } finally {
                if (response != null) {
                    response.close();
                }
            }
            return null;
        }
    
        public String doPostJson(String url, String json) throws Exception {
            // 创建http POST请求
            HttpPost httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
    
            if(null != json){
                //设置请求体为 字符串
                StringEntity stringEntity = new StringEntity(json,"UTF-8");
                httpPost.setEntity(stringEntity);
            }
    
            CloseableHttpResponse response = null;
            try {
                // 执行请求
                response = httpClient.execute(httpPost);
                // 判断返回状态是否为200
                if (response.getStatusLine().getStatusCode() == 200) {
                    return EntityUtils.toString(response.getEntity(), "UTF-8");
                }
            } finally {
                if (response != null) {
                    response.close();
                }
            }
            return null;
        }
    
    }
    
  2. 在spring配置文件中配置

    
    
        
        
        
        
    
    
    
    
        
    
    
    
    
    
    
    
        
        
        
    
    
    
    
        
        
        
        
        
        
        
        
     
    
    
    
    
  3. 配置文件所需要的参数信息 httpclient.properties

        #从连接池中获取到连接的最长时间
        http.request.connectionRequestTimeout=500
        #5000
        http.request.connectTimeout=5000
        #数据传输的最长时间
        http.request.socketTimeout=30000
        #提交请求前测试连接是否可用
        http.request.staleConnectionCheckEnabled=true
    
        #设置连接总数
        http.pool.maxTotal=200
        #设置每个地址的并发数
        http.pool.defaultMaxPerRoute=100
    

你可能感兴趣的:(WEB,大数据)