Authored by Gino Zhang

Merge branch 'zf_dependency_opt' into test_newcore

  1 +package com.yoho.search.service.restapi;
  2 +
  3 +import org.junit.Assert;
  4 +import org.yaml.snakeyaml.Yaml;
  5 +
  6 +import java.io.FileInputStream;
  7 +import java.net.URL;
  8 +import java.util.HashMap;
  9 +import java.util.List;
  10 +import java.util.Map;
  11 +
  12 +/**
  13 + * Created by ginozhang on 2016/11/17.
  14 + */
  15 +public class ApiDefUtils {
  16 +
  17 + private static Map<String, Map<String, Object>> apiDefs = new HashMap<>();
  18 +
  19 + public static Map<String, Object> getApiDef(String api) {
  20 +
  21 + if(apiDefs.containsKey(api))
  22 + {
  23 + return apiDefs.get(api);
  24 + }
  25 +
  26 + synchronized (ApiDefUtils.class) {
  27 + if(apiDefs.containsKey(api))
  28 + {
  29 + return apiDefs.get(api);
  30 + }
  31 +
  32 + String fileName = api.substring(1).replace("/", "_");
  33 + Yaml yaml = new Yaml();
  34 + URL ymlUrl = SearchNewControllerTest.class.getClassLoader().getResource("api_def/" + fileName + ".yml");
  35 + Map<String, Object> def = null;
  36 + try {
  37 + def = (Map<String, Object>) yaml.load(new FileInputStream(ymlUrl.getFile()));
  38 + } catch (Exception e) {
  39 + throw new RuntimeException("cannot find file " + fileName, e);
  40 + }
  41 +
  42 + Assert.assertNotNull(fileName, def);
  43 + apiDefs.put(api, def);
  44 + return def;
  45 + }
  46 + }
  47 +
  48 + public static List<String> getEnumItems(String api, String enumKey)
  49 + {
  50 + List<String> enums = ((Map<String, List<String>>)getApiDef(api).get("_enums")).get(enumKey);
  51 + return enums;
  52 + }
  53 +
  54 + public static Map<String, Object> getRequestDef(String api)
  55 + {
  56 + return (Map<String, Object>)(getApiDef(api).get("_request"));
  57 + }
  58 +
  59 + public static Map<String, Object> getResponseDef(String api)
  60 + {
  61 + return (Map<String, Object>)(getApiDef(api).get("_response"));
  62 + }
  63 +}
  1 +package com.yoho.search.service.restapi;
  2 +
  3 +import com.alibaba.fastjson.JSONArray;
  4 +import com.alibaba.fastjson.JSONObject;
  5 +import org.junit.Assert;
  6 +
  7 +import java.util.*;
  8 +
  9 +/**
  10 + * Created by ginozhang on 2016/11/15.
  11 + */
  12 +public class CheckStructureUtils {
  13 +
  14 + private static final List<String> BASIC_TYPES = Arrays.asList("int", "long", "double", "string");
  15 +
  16 + private static final List<String> COMPLEX_TYPES = Arrays.asList("array", "object", "enum_*", "connected_enum_*", "connected_<BASIC>", "ranged_int", "ranged_double", "optional(*), date(*)");
  17 +
  18 + public static void checkRequest(String api, String url) {
  19 + // 1. 获取参数列表
  20 + Map<String, String> map = getParamMap(url);
  21 +
  22 + // 2. 获取接口请求定义
  23 + Map<String, Object> requestDef = ApiDefUtils.getRequestDef(api);
  24 +
  25 + // 3. 校验请求参数
  26 + for (Map.Entry<String, String> entry : map.entrySet()) {
  27 + checkSingleFiled(api, entry.getKey(), entry.getValue(), (Map<String, String>) requestDef.get(entry.getKey()));
  28 + }
  29 + }
  30 +
  31 + private static Map<String, String> getParamMap(String api) {
  32 + Map<String, String> map = new LinkedHashMap<>();
  33 + String[] parts = api.split("\\?");
  34 + if (parts != null && parts.length == 2) {
  35 + String paramStr = parts[1];
  36 + String[] paramPairs = paramStr.split("\\&");
  37 + for (String paramItem : paramPairs) {
  38 + String[] keyValueArray = paramItem.split("=");
  39 + if (keyValueArray != null && keyValueArray.length == 2) {
  40 + map.put(keyValueArray[0], keyValueArray[1]);
  41 + }
  42 + }
  43 + }
  44 +
  45 + System.out.println("[INFO]Get request url param map: " + map);
  46 + return map;
  47 + }
  48 +
  49 + public static void main(String[] args) {
  50 + System.out.println(getParamMap("http://192.168.102.216:8080/yohosearch/productindex/productList?query=vans&brand=144&gender=2,3"));
  51 + }
  52 +
  53 + public static void check(String api, JSONObject response) {
  54 + Assert.assertTrue("The response should contain data.", response.containsKey("data") && response.get("data") != null);
  55 + Map<String, Object> responseDef = ApiDefUtils.getResponseDef(api);
  56 + doCheck(api, responseDef, response);
  57 + }
  58 +
  59 + private static void doCheck(String api, Map<String, Object> responseDef, JSONObject response) {
  60 + if (responseDef == null || response.isEmpty()) {
  61 + return;
  62 + }
  63 +
  64 + System.out.println("Start to process. responseDef: " + responseDef + ", response: " + response);
  65 + for (Map.Entry<String, Object> entry : responseDef.entrySet()) {
  66 + String property = entry.getKey();
  67 + Map<String, Object> pDef = (Map<String, Object>) entry.getValue();
  68 + Object pResponse = response.get(property);
  69 + System.out.println("Begin to process. property: " + property + ", pDef: " + pDef + ", pResponse: " + pResponse);
  70 + Assert.assertNotNull("interface api_def for property " + property + " cannot be null", pDef);
  71 + boolean required = pDef.containsKey("_required") ? (Boolean) pDef.get("_required") : false;
  72 + String type = pDef.containsKey("_type") ? (String) pDef.get("_type") : "string";
  73 +
  74 + if (pResponse == null || pResponse.toString() == null || pResponse.toString().isEmpty()) {
  75 + if (required) {
  76 + Assert.fail("The property " + property + " is required!");
  77 + } else {
  78 + continue;
  79 + }
  80 + }
  81 +
  82 + if ("object".equalsIgnoreCase(type)) {
  83 + doCheck(api, (Map<String, Object>) pDef.get("_content"), (JSONObject) pResponse);
  84 + } else if ("array".equalsIgnoreCase(type)) {
  85 + JSONArray jsonArray = (JSONArray) pResponse;
  86 + for (int i = 0; i < jsonArray.size(); i++) {
  87 + doCheck(api, (Map<String, Object>) pDef.get("_content"), jsonArray.getJSONObject(i));
  88 + }
  89 + } else {
  90 + checkSingleFiled(api, property, pResponse.toString(), (Map<String, String>) entry.getValue());
  91 + }
  92 + }
  93 +
  94 + // 检查是否有字段未定义
  95 + Set<String> defKeys = responseDef.keySet();
  96 + Set<String> resKeys = response.keySet();
  97 + if (!defKeys.containsAll(resKeys)) {
  98 + resKeys.removeAll(defKeys);
  99 + System.out.println("[WARN]NOT SAME KEYS. There has new propertys: " + resKeys);
  100 + } else if (!resKeys.containsAll(defKeys)) {
  101 + defKeys.removeAll(resKeys);
  102 + System.out.println("[WARN]NOT SAME KEYS. There has propertys not set value: " + defKeys);
  103 + }
  104 + }
  105 +
  106 + private static void checkSingleFiled(String api, String fieldName, String sValue, Map<String, String> fieldDef) {
  107 + System.out.println("[INFO]Begin to check field " + fieldName);
  108 + Assert.assertNotNull("field not defined for " + fieldName, fieldDef);
  109 + String type = fieldDef.containsKey("_type") ? (String) fieldDef.get("_type") : "string";
  110 + if (BASIC_TYPES.contains(type)) {
  111 + checkBasicType(type, sValue);
  112 + } else if (type.startsWith("optional")) {
  113 + // skip to check
  114 + } else if (type.startsWith("date")) {
  115 + // skip to check
  116 + } else if ("ranged_int".equals(type)) {
  117 + String[] limits = sValue.split(",");
  118 + Assert.assertTrue("The value for " + fieldName + " should contains comma", limits != null && limits.length == 2);
  119 + Assert.assertTrue("The upper limit should larger than the lower limit", Integer.valueOf(limits[1]) >= Integer.valueOf(limits[0]));
  120 + } else if ("ranged_double".equals(type)) {
  121 + String[] limits = sValue.split(",");
  122 + Assert.assertTrue("The value for " + fieldName + " should contains comma", limits != null && limits.length == 2);
  123 + Assert.assertTrue("The upper limit should larger than the lower limit", Double.valueOf(limits[1]) >= Double.valueOf(limits[0]));
  124 + } else if (type.startsWith("enum_")) {
  125 + // 校验枚举类型
  126 + String enumKey = type.substring("enum_".length());
  127 + Assert.assertTrue("Invalid value " + sValue + " for enum " + enumKey, ApiDefUtils.getEnumItems(api, enumKey).contains(sValue));
  128 + } else if (type.startsWith("connected_enum_")) {
  129 + // 校验枚举类型
  130 + String enumKey = type.substring("connected_enum_".length());
  131 + Assert.assertTrue("Invalid value " + sValue + " for enum " + enumKey, ApiDefUtils.getEnumItems(api, enumKey).containsAll(Arrays.asList(sValue.split(","))));
  132 +
  133 + } else if (type.startsWith("connected_")) {
  134 + String type4Connected = type.substring("connected_".length());
  135 + for (String item : sValue.split(",")) {
  136 + checkBasicType(type4Connected, item);
  137 + }
  138 + } else {
  139 + Assert.fail("Unknown property type " + type + " for property " + fieldName);
  140 + }
  141 + }
  142 +
  143 + private static void checkBasicType(String type, String sValue) {
  144 + switch (type) {
  145 + case "int":
  146 + Integer.valueOf(sValue);
  147 + break;
  148 + case "long":
  149 + Long.valueOf(sValue);
  150 + break;
  151 + case "double":
  152 + Double.valueOf(sValue);
  153 + break;
  154 + default:
  155 + break;
  156 + }
  157 + }
  158 +
  159 +}
  1 +package com.yoho.search.service.restapi;
  2 +
  3 +import com.yoho.search.base.utils.FileUtils;
  4 +import org.apache.commons.lang3.StringUtils;
  5 +
  6 +import java.util.HashMap;
  7 +import java.util.Map;
  8 +
  9 +/**
  10 + * Created by ginozhang on 2016/11/17.
  11 + */
  12 +public class MarkDownUtils {
  13 +
  14 + private final String SEPARATOR = System.getProperty("line.separator");
  15 +
  16 + private Map<String, String> typeDescptions = new HashMap<>();
  17 +
  18 + private String api;
  19 +
  20 + private MarkDownUtils(String api) {
  21 + this.api = api;
  22 + }
  23 +
  24 + public static MarkDownUtils getInstance(String api) {
  25 + return new MarkDownUtils(api);
  26 + }
  27 +
  28 + {
  29 + typeDescptions.put("connected_int", "int多值参数类型,多个值用逗号分隔。");
  30 + typeDescptions.put("ranged_double", "double范围类型,使用逗号分隔下限和上限");
  31 + typeDescptions.put("ranged_int", "int范围类型,使用逗号分隔下限和上限");
  32 + }
  33 +
  34 +
  35 + /**
  36 + * 根据接口描述文件生成接口描述的MD文件
  37 + */
  38 + public void generateApiMd() {
  39 + Map<String, Object> apiDef = ApiDefUtils.getApiDef(api);
  40 + StringBuffer sb = new StringBuffer(500);
  41 + sb.append("# ").append(api).append(" 接口说明").append(SEPARATOR);
  42 +
  43 + // 1. 生成header 秒杀接口的功能和使用场景
  44 + sb.append(apiDef.get("_name")).append(SEPARATOR);
  45 +
  46 + // 2. 生成请求参数说明
  47 + sb.append(getRequestMdTable(apiDef));
  48 +
  49 + // 3. 生成响应参数说明
  50 + sb.append(generateResponseMdTable(apiDef));
  51 +
  52 + FileUtils.writeFile(api.replace("/", "_") + ".md", sb.toString());
  53 + }
  54 +
  55 + private StringBuffer generateResponseMdTable(Map<String, Object> apiDef) {
  56 + return generateMdTable4SingleObject(api + " 响应参数说明", (Map<String, Object>) apiDef.get("_response"), 2);
  57 + }
  58 +
  59 + private StringBuffer generateMdTable4SingleObject(String tableTitle, Map<String, Object> respnseDef, int level) {
  60 + StringBuffer sb = new StringBuffer(100);
  61 + sb.append(getLevelChars(level)).append(tableTitle).append(SEPARATOR);
  62 + sb.append("|参数名 |参数类型 |参数说明 |").append(SEPARATOR);
  63 + sb.append("|------|--------|---------|").append(SEPARATOR);
  64 + for (Map.Entry<String, Object> entry : respnseDef.entrySet()) {
  65 + String paramName = entry.getKey();
  66 + Map<String, Object> details = ((Map<String, Object>) entry.getValue());
  67 + String type = getTypeDescription((String) details.get("_type"));
  68 + String desc = (String) details.get("_desc");
  69 + sb.append("|").append(paramName).append("|").append(type).append("|").append(desc).append("|").append(SEPARATOR);
  70 +
  71 + if ("object".equals(type) || "array".equals(type)) {
  72 + // 如果是数组或者对象类型 就新搞一个表格来说明里面的对象
  73 + sb.append(SEPARATOR).append(SEPARATOR);
  74 + sb.append(generateMdTable4SingleObject("参数" + paramName + "的属性说明", (Map<String, Object>) details.get("_content"), level + 1));
  75 + }
  76 +
  77 + }
  78 + sb.append(SEPARATOR).append(SEPARATOR);
  79 + return sb;
  80 + }
  81 +
  82 + private StringBuffer getRequestMdTable(Map<String, Object> apiDef) {
  83 + StringBuffer sb = new StringBuffer(100);
  84 + sb.append("## ").append(api).append(" 请求参数说明").append(SEPARATOR);
  85 + sb.append("|参数名 |参数类型 |参数说明 |").append(SEPARATOR);
  86 + sb.append("|------|--------|---------|").append(SEPARATOR);
  87 +
  88 + Map<String, Object> requestDef = (Map<String, Object>) apiDef.get("_request");
  89 + for (Map.Entry<String, Object> entry : requestDef.entrySet()) {
  90 + String paramName = entry.getKey();
  91 + Map<String, String> details = ((Map<String, String>) entry.getValue());
  92 + String type = getTypeDescription(details.get("_type"));
  93 + String desc = details.get("_desc");
  94 + sb.append("|").append(paramName).append("|").append(type).append("|").append(desc).append("|").append(SEPARATOR);
  95 + }
  96 + sb.append(SEPARATOR).append(SEPARATOR);
  97 + return sb;
  98 + }
  99 +
  100 + private StringBuffer getLevelChars(int level) {
  101 + StringBuffer sb = new StringBuffer(10);
  102 + level = level <= 3 ? level : 3;
  103 + for (int i = 1; i <= level; i++) {
  104 + sb.append("#");
  105 + }
  106 +
  107 + sb.append(" ");
  108 + return sb;
  109 + }
  110 +
  111 + private String getTypeDescription(String type) {
  112 +
  113 + if (StringUtils.isEmpty(type)) {
  114 + type = "string";
  115 + }
  116 +
  117 + if (typeDescptions.containsKey(type)) {
  118 + return typeDescptions.get(type);
  119 + }
  120 +
  121 + if (type.startsWith("enum_")) {
  122 + String enumKey = type.substring("enum_".length());
  123 + return "枚举类型,可选范围:" + ApiDefUtils.getEnumItems(api, enumKey);
  124 + } else if (type.startsWith("connected_enum_")) {
  125 + String enumKey = type.substring("connected_enum_".length());
  126 + return "多值枚举类型,枚举可选范围:" + ApiDefUtils.getEnumItems(api, enumKey) + ", 多个值用逗号分隔。";
  127 + } else if (type.startsWith("optional")) {
  128 + String specialValue = type.substring("optional(".length(), "optional(".length() + 1);
  129 + return "特定值{" + specialValue + "}或不传值";
  130 + }
  131 +
  132 + return type;
  133 + }
  134 +
  135 +}
@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON; @@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
4 import com.alibaba.fastjson.JSONObject; 4 import com.alibaba.fastjson.JSONObject;
5 import com.yoho.search.base.utils.HttpClientUtils; 5 import com.yoho.search.base.utils.HttpClientUtils;
6 import com.yoho.search.service.TestConstants; 6 import com.yoho.search.service.TestConstants;
  7 +import org.apache.commons.lang3.StringUtils;
7 import org.junit.Assert; 8 import org.junit.Assert;
8 import org.junit.Test; 9 import org.junit.Test;
9 import org.springframework.web.bind.annotation.RequestMapping; 10 import org.springframework.web.bind.annotation.RequestMapping;
@@ -23,22 +24,22 @@ public class SearchNewControllerTest { @@ -23,22 +24,22 @@ public class SearchNewControllerTest {
23 .append(TestConstants.SERVICE_PORT).append("/").append(TestConstants.SERVICE_CONTEXT) 24 .append(TestConstants.SERVICE_PORT).append("/").append(TestConstants.SERVICE_CONTEXT)
24 .toString(); 25 .toString();
25 26
26 - static Map<String,String> appendParams = new HashMap<>(); 27 + static Map<String, String> appendParams = new HashMap<>();
27 28
28 static final List<String> skippedApis = Arrays.asList("/count"); 29 static final List<String> skippedApis = Arrays.asList("/count");
29 30
30 - static  
31 - {  
32 - appendParams.put("/product_pool","filter_poolId=1");  
33 - appendParams.put("/new_product","brand=144");  
34 - appendParams.put("/group_brands","brand=144");  
35 - appendParams.put("/group_shops","shop=1");  
36 - appendParams.put("/suggest","query=vans");  
37 - appendParams.put("/shops","keyword=vans"); 31 + static {
  32 + appendParams.put("/product_pool", "filter_poolId=1");
  33 + appendParams.put("/new_product", "brand=144");
  34 + appendParams.put("/group_brands", "brand=144");
  35 + appendParams.put("/group_shops", "shop=1");
  36 + appendParams.put("/suggest", "query=vans");
  37 + appendParams.put("/shops", "keyword=vans");
  38 + appendParams.put("/productindex/productList", "query=vans&brand=144&gender=2,3&attribute_not=2&status=1");
38 } 39 }
39 40
40 @Test 41 @Test
41 - public void testAPI() { 42 + public void testAPIs() {
42 SearchNewController c = new SearchNewController(); 43 SearchNewController c = new SearchNewController();
43 Method[] methods = c.getClass().getMethods(); 44 Method[] methods = c.getClass().getMethods();
44 if (methods == null || methods.length == 0) { 45 if (methods == null || methods.length == 0) {
@@ -53,21 +54,66 @@ public class SearchNewControllerTest { @@ -53,21 +54,66 @@ public class SearchNewControllerTest {
53 String methodName = m.getName(); 54 String methodName = m.getName();
54 String api = m.getAnnotation(RequestMapping.class).value()[0]; 55 String api = m.getAnnotation(RequestMapping.class).value()[0];
55 System.out.println("Begin to execute mothod " + methodName + " for api: " + api); 56 System.out.println("Begin to execute mothod " + methodName + " for api: " + api);
56 - if(skippedApis.contains(api))  
57 - { 57 + if (skippedApis.contains(api)) {
58 continue; 58 continue;
59 } 59 }
60 60
61 - String url = urlBase + api;  
62 - if(appendParams.containsKey(api))  
63 - {  
64 - url = url + "?" + appendParams.get(api); 61 + testSingle(api);
  62 + }
  63 + }
  64 +
  65 + @Test
  66 + public void generateAPIMd() {
  67 + SearchNewController c = new SearchNewController();
  68 + Method[] methods = c.getClass().getMethods();
  69 + if (methods == null || methods.length == 0) {
  70 + return;
  71 + }
  72 +
  73 + for (Method m : methods) {
  74 + if (!m.isAnnotationPresent(RequestMapping.class)) {
  75 + continue;
65 } 76 }
66 - String jsonResult = HttpClientUtils.getMethod(url);  
67 - System.out.println("Request url is: " + url + ". jsonResult: \n" + jsonResult);  
68 - JSONObject jsonObject = JSON.parseObject(jsonResult);  
69 - Assert.assertEquals(api, "200", jsonObject.getString("code")); 77 +
  78 + String api = m.getAnnotation(RequestMapping.class).value()[0];
  79 + if (skippedApis.contains(api)) {
  80 + continue;
  81 + }
  82 +
  83 + // 生成markdown接口说明文件
  84 + MarkDownUtils.getInstance(api).generateApiMd();
70 } 85 }
71 } 86 }
72 87
  88 + @Test
  89 + public void testShops() {
  90 + testSingle("/shops");
  91 + }
  92 +
  93 + @Test
  94 + public void testProductList() {
  95 + testSingle("/productindex/productList");
  96 + }
  97 +
  98 + private void testSingle(String api) {
  99 + testSingle(api, appendParams.get(api));
  100 + }
  101 +
  102 + private void testSingle(String api, String paramStr) {
  103 + String url = urlBase + api;
  104 + if (StringUtils.isNotEmpty(paramStr)) {
  105 + url = url + "?" + paramStr;
  106 + }
  107 +
  108 + CheckStructureUtils.checkRequest(api, url);
  109 + String jsonResult = HttpClientUtils.getMethod(url);
  110 + System.out.println("Request url is: " + url + ". jsonResult: \n" + jsonResult);
  111 + JSONObject jsonObject = JSON.parseObject(jsonResult);
  112 +
  113 + // 检查响应是否OK
  114 + Assert.assertEquals(api, "200", jsonObject.getString("code"));
  115 +
  116 + // 检查响应结果是否OK
  117 + CheckStructureUtils.check(api, jsonObject);
  118 + }
73 } 119 }
  1 +# _enums: 定义使用到的枚举类型
  2 +# _request: 请求
  3 +# _response: 响应
  4 +# _desc: 字段描述
  5 +# _required: 字段是否必须,默认为false
  6 +# _type: 字段类型,可选为int、long、string、double、object、array、enum_<ENUM_NAME>,默认为string
  7 +_name: 商品列表搜索接口。
  8 +_enums:
  9 + YesOrNo: ['Y','N']
  10 + Status: ['0','1']
  11 + Gender: ['1','2','3']
  12 + AgeLevel: ['1','2','3']
  13 + Outlet: ['1', '2']
  14 + VIPDiscountType: ['0', '1', '2']
  15 + Attribute: ['1', '2']
  16 + AppType: ['0', '1']
  17 + SellChannel: ['0', '1', '2']
  18 +_request:
  19 + query:
  20 + _desc: '搜索关键词'
  21 + _type: 'string'
  22 + viewNum:
  23 + _desc: '分页每页显示商品数量'
  24 + _type: 'int'
  25 + page:
  26 + _desc: '分页页数'
  27 + _type: 'int'
  28 + product_skn:
  29 + _desc: '指定的商品SKN列表'
  30 + _type: 'connected_int'
  31 + brand:
  32 + _desc: '指定的品牌列表'
  33 + _type: 'connected_int'
  34 + shop:
  35 + _desc: '指定的店铺列表'
  36 + _type: 'connected_int'
  37 + msort:
  38 + _desc: '指定的大分类列表'
  39 + _type: 'connected_int'
  40 + misort:
  41 + _desc: '指定的中分类列表'
  42 + _type: 'connected_int'
  43 + sort:
  44 + _desc: '指定的小分类列表'
  45 + _type: 'connected_int'
  46 + color:
  47 + _desc: '指定的颜色列表'
  48 + _type: 'connected_int'
  49 + style:
  50 + _desc: '指定的风格列表'
  51 + _type: 'connected_int'
  52 + size:
  53 + _desc: '指定的尺寸列表'
  54 + _type: 'connected_int'
  55 + gender:
  56 + _desc: '性别(1:男,2:女,3:通用)'
  57 + _type: 'connected_enum_Gender'
  58 + ageLevel:
  59 + _desc: '年龄段(1成人 2大童 3小童)'
  60 + _type: 'connected_enum_AgeLevel'
  61 + price:
  62 + _desc: '价格区间'
  63 + _type: 'ranged_int'
  64 + specialoffer:
  65 + _desc: '是否为促销品[5折以下]'
  66 + _type: 'enum_YesOrNo'
  67 + isdiscount:
  68 + _desc: '是否打折'
  69 + _type: 'enum_YesOrNo'
  70 + vdt:
  71 + _desc: 'VIP折扣类型'
  72 + _type: 'enum_VIPDiscountType'
  73 + p_d:
  74 + _desc: '折扣范围,浮点型,如【p_d_int=0.1,0.3】'
  75 + _type: 'ranged_double'
  76 + p_d_int:
  77 + _desc: '折扣范围,整形,如【p_d_int=1,3】'
  78 + _type: 'ranged_int'
  79 + isStudentPrice:
  80 + _desc: '是否有学生价优惠'
  81 + _type: 'enum_YesOrNo'
  82 + isStudentRebate:
  83 + _desc: '是否有学生返币'
  84 + _type: 'enum_YesOrNo'
  85 + isInstalment:
  86 + _desc: '是否分期'
  87 + _type: 'enum_Status'
  88 + sales:
  89 + _desc: '是否在售'
  90 + _type: 'enum_Status'
  91 + promotion:
  92 + _desc: '是否促销/推广商品 TODO'
  93 + _type: 'int'
  94 + attribute:
  95 + _desc: '包含指定的商品属性(1:正常商品,2:赠品)'
  96 + _type: 'enum_Attribute'
  97 + attribute_not:
  98 + _desc: '过滤指定的商品属性(1:正常商品,2:赠品)'
  99 + _type: 'enum_Attribute'
  100 + limited:
  101 + _desc: '是否限量商品'
  102 + _type: 'enum_YesOrNo'
  103 + new:
  104 + _desc: '是否新品'
  105 + _type: 'enum_YesOrNo'
  106 + outlets:
  107 + _desc: '是否奥莱商品'
  108 + _type: 'enum_Outlet'
  109 + status:
  110 + _desc: '是否上架'
  111 + _type: 'enum_Status'
  112 + breaking:
  113 + _desc: '传入1只查询断码商品,不传值或其他值该参数不起作用'
  114 + _type: 'optional(1)'
  115 + app_type:
  116 + _desc: 'APP类型'
  117 + _type: 'connected_enum_AppType'
  118 + sell_channels:
  119 + _desc: '销售平台 (网站、APP、现场 TODO)'
  120 + _type: 'enum_SellChannel'
  121 + stocknumber:
  122 + _desc: '商品库存,当传入0时查询库存为0的商品,当传入大于0的数值时,查询库存不小于改数值的商品'
  123 + _type: 'int'
  124 + folder_id:
  125 + _desc: '商品目录'
  126 + _type: 'int'
  127 + series_id:
  128 + _desc: '商品系列'
  129 + _type: 'int'
  130 + first_shelve_time:
  131 + _desc: '首次上架时间间隔'
  132 + _type: 'ranged_double'
  133 + shelve_time:
  134 + _desc: '最近上架日期间隔'
  135 + _type: 'ranged_double'
  136 + day:
  137 + _desc: '上架时间'
  138 + _type: 'date(yyyy-MM-dd)'
  139 + contain_global:
  140 + _desc: '传入Y查询结果包含全球购商品,不传值或其他值该参数不起作用'
  141 + _type: 'optional(Y)'
  142 + contain_seckill:
  143 + _desc: '传入Y查询结果包含秒杀商品,不传值或其他值该参数不起作用'
  144 + _type: 'optional(Y)'
  145 + act_temp:
  146 + _desc: '活动属性-模板id'
  147 + _type: 'int'
  148 + act_rec:
  149 + _desc: '活动属性-是否推荐'
  150 + _type: 'enum_Status'
  151 + act_status:
  152 + _desc: '活动属性-状态'
  153 + _type: 'enum_Status'
  154 +_response:
  155 + code:
  156 + _desc: '响应状态码,200为成功,其他为失败'
  157 + _required: true
  158 + _type: 'int'
  159 + message:
  160 + _desc: '响应描述信息'
  161 + data:
  162 + _desc: '响应具体数据'
  163 + _required: true
  164 + _type: 'object'
  165 + _content:
  166 + total:
  167 + _desc: '搜索出的商品总数'
  168 + _required: true
  169 + _type: 'int'
  170 + page:
  171 + _desc: '当前页数'
  172 + _required: true
  173 + _type: 'int'
  174 + page_size:
  175 + _desc: '每页显示商品数'
  176 + _required: true
  177 + _type: 'int'
  178 + page_total:
  179 + _desc: '总页数'
  180 + _required: true
  181 + _type: 'int'
  182 + product_list:
  183 + _desc: '当前页的商品列表'
  184 + _required: true
  185 + _type: 'array'
  186 + _content:
  187 + product_id:
  188 + _desc: '商品ID'
  189 + _type: 'int'
  190 + _required: true
  191 + product_skn:
  192 + _desc: '商品SKN'
  193 + _required: true
  194 + product_name:
  195 + _desc: '商品名称'
  196 + product_name_highlight:
  197 + _desc: '高亮的商品名称'
  198 + sales_phrase:
  199 + _desc: 'TODO'
  200 + cn_alphabet:
  201 + _desc: 'TODO'
  202 + brand_id:
  203 + _desc: '品牌ID'
  204 + _type: 'int'
  205 + brand_name:
  206 + _desc: '品牌名称'
  207 + brand_domain:
  208 + _desc: '品牌DOMAIN'
  209 + market_price:
  210 + _desc: '市场售价'
  211 + _type: 'double'
  212 + sales_price:
  213 + _desc: '售价'
  214 + _type: 'double'
  215 + student_price:
  216 + _desc: '学生售价'
  217 + _type: 'double'
  218 + vip_price:
  219 + _desc: 'VIP售价'
  220 + _type: 'double'
  221 + vip1_price:
  222 + _desc: 'VIP1售价'
  223 + _type: 'double'
  224 + vip2_price:
  225 + _desc: 'VIP2售价'
  226 + _type: 'double'
  227 + vip3_price:
  228 + _desc: 'VIP3售价'
  229 + _type: 'double'
  230 + vip_discount_type:
  231 + _desc: 'VIP折扣类型TODO'
  232 + _type: 'int'
  233 + is_new:
  234 + _desc: '是否新品'
  235 + _type: 'enum_YesOrNo'
  236 + is_advance:
  237 + _desc: 'TODO'
  238 + _type: 'enum_YesOrNo'
  239 + is_limited:
  240 + _desc: 'TODO'
  241 + _type: 'enum_YesOrNo'
  242 + is_special:
  243 + _desc: 'TODO'
  244 + _type: 'enum_YesOrNo'
  245 + is_discount:
  246 + _desc: '是否打折'
  247 + _type: 'enum_YesOrNo'
  248 + is_soon_sold_out:
  249 + _desc: '是否快要销售完'
  250 + _type: 'enum_YesOrNo'
  251 + status:
  252 + _desc: '商品状态'
  253 + _type: 'enum_Status'
  254 + is_promotion:
  255 + _desc: '优惠码'
  256 + _type: 'int'
  257 + is_student_price:
  258 + _desc: '是否有学生优惠'
  259 + _type: 'enum_YesOrNo'
  260 + is_student_rebate:
  261 + _desc: '是否有学生返币'
  262 + _type: 'enum_YesOrNo'
  263 + is_global:
  264 + _desc: '是否是全球购商品'
  265 + _type: 'enum_YesOrNo'
  266 + max_sort_id:
  267 + _desc: '商品大分类'
  268 + _type: 'int'
  269 + _required: true
  270 + middle_sort_id:
  271 + _desc: '商品中分类'
  272 + _type: 'int'
  273 + _required: true
  274 + small_sort_id:
  275 + _desc: '商品小分类'
  276 + _type: 'int'
  277 + _required: true
  278 + stock_number:
  279 + _desc: 'TODO'
  280 + _type: 'int'
  281 + storage_num:
  282 + _desc: '库存量'
  283 + _type: 'int'
  284 + sales_num:
  285 + _desc: '销量'
  286 + _type: 'int'
  287 + shelve_time:
  288 + _desc: '上架时间'
  289 + _type: 'long'
  290 + edit_time:
  291 + _desc: '编辑时间'
  292 + _type: 'long'
  293 + sales_num:
  294 + _desc: '销量'
  295 + _type: 'int'
  296 + is_outlets:
  297 + _desc: '是否是奥特莱斯商品'
  298 + _type: 'enum_Outlet'
  299 + gender:
  300 + _desc: '性别(1:男,2:女,3:通用)'
  301 + _type: 'enum_Gender'
  302 + age_level:
  303 + _desc: '年龄段(1成人 2大童 3小童)'
  304 + _type: 'connected_enum_AgeLevel'
  305 + default_images:
  306 + _desc: '默认的商品图片'
  307 + yohood_id:
  308 + _desc: 'TODO'
  309 + country_id:
  310 + _desc: 'TODO'
  311 + goods_list:
  312 + _desc: '商品的GOOD列表'
  313 + _type: 'array'
  314 + _content:
  315 + goods_id:
  316 + _desc: '商品的GOOD ID'
  317 + _type: 'int'
  318 + _required: true
  319 + color_id:
  320 + _desc: '商品的GOOD的颜色标识'
  321 + _type: 'int'
  322 + _required: true
  323 + product_skc:
  324 + _desc: '商品的GOOD的SKC'
  325 + _type: 'int'
  326 + _required: true
  327 + status:
  328 + _desc: '商品的GOOD状态'
  329 + _type: 'enum_Status'
  330 + _required: true
  331 + is_default:
  332 + _desc: '是否是默认GOOD'
  333 + _type: 'enum_YesOrNo'
  334 + color_name:
  335 + _desc: '商品的GOOD的颜色'
  336 + _type: 'string'
  337 + _required: true
  338 + color_code:
  339 + _desc: '商品的GOOD的颜色取值'
  340 + _type: 'string'
  341 + _required: true
  342 + color_value:
  343 + _desc: '商品的GOOD颜色值 TODO'
  344 + _type: 'string'
  345 + is_default:
  346 + _desc: '是否是默认GOOD'
  347 + _type: 'enum_YesOrNo'
  348 + images_url:
  349 + _desc: '商品的GOOD的图片链接'
  350 + _type: 'string'
  351 + cover_1:
  352 + _desc: '商品的GOOD的封面1'
  353 + _type: 'string'
  354 + cover_2:
  355 + _desc: '商品的GOOD的封面2'
  356 + _type: 'string'
  1 +_name: '根据关键词搜索品牌店铺接口。'
  2 +_request:
  3 + keyword:
  4 + _desc: '搜索店铺的关键词,不能为空'
  5 + _required: true
  6 + _type: 'string'
  7 +_response:
  8 + code:
  9 + _desc: '响应状态码,200为成功,其他为失败'
  10 + _required: true
  11 + _type: 'int'
  12 + message:
  13 + _desc: '响应描述信息'
  14 + _required: false
  15 + _type: 'string'
  16 + data:
  17 + _desc: '根据关键字搜索出的关联的品牌'
  18 + _required: false
  19 + _type: 'object'
  20 + _content:
  21 + id:
  22 + _desc: '品牌ID'
  23 + _required: true
  24 + _type: 'int'
  25 + brand_name:
  26 + _desc: '品牌名称'
  27 + _required: true
  28 + _type: 'string'
  29 + brand_ico:
  30 + _desc: '品牌ICO'
  31 + _required: true
  32 + _type: 'string'
  33 + brand_domain:
  34 + _desc: '品牌DOMAIN'
  35 + _required: true
  36 + _type: 'string'