Authored by Gino Zhang

Merge branch 'zf_dependency_opt' into test_newcore

package com.yoho.search.service.restapi;
import org.junit.Assert;
import org.yaml.snakeyaml.Yaml;
import java.io.FileInputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by ginozhang on 2016/11/17.
*/
public class ApiDefUtils {
private static Map<String, Map<String, Object>> apiDefs = new HashMap<>();
public static Map<String, Object> getApiDef(String api) {
if(apiDefs.containsKey(api))
{
return apiDefs.get(api);
}
synchronized (ApiDefUtils.class) {
if(apiDefs.containsKey(api))
{
return apiDefs.get(api);
}
String fileName = api.substring(1).replace("/", "_");
Yaml yaml = new Yaml();
URL ymlUrl = SearchNewControllerTest.class.getClassLoader().getResource("api_def/" + fileName + ".yml");
Map<String, Object> def = null;
try {
def = (Map<String, Object>) yaml.load(new FileInputStream(ymlUrl.getFile()));
} catch (Exception e) {
throw new RuntimeException("cannot find file " + fileName, e);
}
Assert.assertNotNull(fileName, def);
apiDefs.put(api, def);
return def;
}
}
public static List<String> getEnumItems(String api, String enumKey)
{
List<String> enums = ((Map<String, List<String>>)getApiDef(api).get("_enums")).get(enumKey);
return enums;
}
public static Map<String, Object> getRequestDef(String api)
{
return (Map<String, Object>)(getApiDef(api).get("_request"));
}
public static Map<String, Object> getResponseDef(String api)
{
return (Map<String, Object>)(getApiDef(api).get("_response"));
}
}
... ...
package com.yoho.search.service.restapi;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.junit.Assert;
import java.util.*;
/**
* Created by ginozhang on 2016/11/15.
*/
public class CheckStructureUtils {
private static final List<String> BASIC_TYPES = Arrays.asList("int", "long", "double", "string");
private static final List<String> COMPLEX_TYPES = Arrays.asList("array", "object", "enum_*", "connected_enum_*", "connected_<BASIC>", "ranged_int", "ranged_double", "optional(*), date(*)");
public static void checkRequest(String api, String url) {
// 1. 获取参数列表
Map<String, String> map = getParamMap(url);
// 2. 获取接口请求定义
Map<String, Object> requestDef = ApiDefUtils.getRequestDef(api);
// 3. 校验请求参数
for (Map.Entry<String, String> entry : map.entrySet()) {
checkSingleFiled(api, entry.getKey(), entry.getValue(), (Map<String, String>) requestDef.get(entry.getKey()));
}
}
private static Map<String, String> getParamMap(String api) {
Map<String, String> map = new LinkedHashMap<>();
String[] parts = api.split("\\?");
if (parts != null && parts.length == 2) {
String paramStr = parts[1];
String[] paramPairs = paramStr.split("\\&");
for (String paramItem : paramPairs) {
String[] keyValueArray = paramItem.split("=");
if (keyValueArray != null && keyValueArray.length == 2) {
map.put(keyValueArray[0], keyValueArray[1]);
}
}
}
System.out.println("[INFO]Get request url param map: " + map);
return map;
}
public static void main(String[] args) {
System.out.println(getParamMap("http://192.168.102.216:8080/yohosearch/productindex/productList?query=vans&brand=144&gender=2,3"));
}
public static void check(String api, JSONObject response) {
Assert.assertTrue("The response should contain data.", response.containsKey("data") && response.get("data") != null);
Map<String, Object> responseDef = ApiDefUtils.getResponseDef(api);
doCheck(api, responseDef, response);
}
private static void doCheck(String api, Map<String, Object> responseDef, JSONObject response) {
if (responseDef == null || response.isEmpty()) {
return;
}
System.out.println("Start to process. responseDef: " + responseDef + ", response: " + response);
for (Map.Entry<String, Object> entry : responseDef.entrySet()) {
String property = entry.getKey();
Map<String, Object> pDef = (Map<String, Object>) entry.getValue();
Object pResponse = response.get(property);
System.out.println("Begin to process. property: " + property + ", pDef: " + pDef + ", pResponse: " + pResponse);
Assert.assertNotNull("interface api_def for property " + property + " cannot be null", pDef);
boolean required = pDef.containsKey("_required") ? (Boolean) pDef.get("_required") : false;
String type = pDef.containsKey("_type") ? (String) pDef.get("_type") : "string";
if (pResponse == null || pResponse.toString() == null || pResponse.toString().isEmpty()) {
if (required) {
Assert.fail("The property " + property + " is required!");
} else {
continue;
}
}
if ("object".equalsIgnoreCase(type)) {
doCheck(api, (Map<String, Object>) pDef.get("_content"), (JSONObject) pResponse);
} else if ("array".equalsIgnoreCase(type)) {
JSONArray jsonArray = (JSONArray) pResponse;
for (int i = 0; i < jsonArray.size(); i++) {
doCheck(api, (Map<String, Object>) pDef.get("_content"), jsonArray.getJSONObject(i));
}
} else {
checkSingleFiled(api, property, pResponse.toString(), (Map<String, String>) entry.getValue());
}
}
// 检查是否有字段未定义
Set<String> defKeys = responseDef.keySet();
Set<String> resKeys = response.keySet();
if (!defKeys.containsAll(resKeys)) {
resKeys.removeAll(defKeys);
System.out.println("[WARN]NOT SAME KEYS. There has new propertys: " + resKeys);
} else if (!resKeys.containsAll(defKeys)) {
defKeys.removeAll(resKeys);
System.out.println("[WARN]NOT SAME KEYS. There has propertys not set value: " + defKeys);
}
}
private static void checkSingleFiled(String api, String fieldName, String sValue, Map<String, String> fieldDef) {
System.out.println("[INFO]Begin to check field " + fieldName);
Assert.assertNotNull("field not defined for " + fieldName, fieldDef);
String type = fieldDef.containsKey("_type") ? (String) fieldDef.get("_type") : "string";
if (BASIC_TYPES.contains(type)) {
checkBasicType(type, sValue);
} else if (type.startsWith("optional")) {
// skip to check
} else if (type.startsWith("date")) {
// skip to check
} else if ("ranged_int".equals(type)) {
String[] limits = sValue.split(",");
Assert.assertTrue("The value for " + fieldName + " should contains comma", limits != null && limits.length == 2);
Assert.assertTrue("The upper limit should larger than the lower limit", Integer.valueOf(limits[1]) >= Integer.valueOf(limits[0]));
} else if ("ranged_double".equals(type)) {
String[] limits = sValue.split(",");
Assert.assertTrue("The value for " + fieldName + " should contains comma", limits != null && limits.length == 2);
Assert.assertTrue("The upper limit should larger than the lower limit", Double.valueOf(limits[1]) >= Double.valueOf(limits[0]));
} else if (type.startsWith("enum_")) {
// 校验枚举类型
String enumKey = type.substring("enum_".length());
Assert.assertTrue("Invalid value " + sValue + " for enum " + enumKey, ApiDefUtils.getEnumItems(api, enumKey).contains(sValue));
} else if (type.startsWith("connected_enum_")) {
// 校验枚举类型
String enumKey = type.substring("connected_enum_".length());
Assert.assertTrue("Invalid value " + sValue + " for enum " + enumKey, ApiDefUtils.getEnumItems(api, enumKey).containsAll(Arrays.asList(sValue.split(","))));
} else if (type.startsWith("connected_")) {
String type4Connected = type.substring("connected_".length());
for (String item : sValue.split(",")) {
checkBasicType(type4Connected, item);
}
} else {
Assert.fail("Unknown property type " + type + " for property " + fieldName);
}
}
private static void checkBasicType(String type, String sValue) {
switch (type) {
case "int":
Integer.valueOf(sValue);
break;
case "long":
Long.valueOf(sValue);
break;
case "double":
Double.valueOf(sValue);
break;
default:
break;
}
}
}
... ...
package com.yoho.search.service.restapi;
import com.yoho.search.base.utils.FileUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* Created by ginozhang on 2016/11/17.
*/
public class MarkDownUtils {
private final String SEPARATOR = System.getProperty("line.separator");
private Map<String, String> typeDescptions = new HashMap<>();
private String api;
private MarkDownUtils(String api) {
this.api = api;
}
public static MarkDownUtils getInstance(String api) {
return new MarkDownUtils(api);
}
{
typeDescptions.put("connected_int", "int多值参数类型,多个值用逗号分隔。");
typeDescptions.put("ranged_double", "double范围类型,使用逗号分隔下限和上限");
typeDescptions.put("ranged_int", "int范围类型,使用逗号分隔下限和上限");
}
/**
* 根据接口描述文件生成接口描述的MD文件
*/
public void generateApiMd() {
Map<String, Object> apiDef = ApiDefUtils.getApiDef(api);
StringBuffer sb = new StringBuffer(500);
sb.append("# ").append(api).append(" 接口说明").append(SEPARATOR);
// 1. 生成header 秒杀接口的功能和使用场景
sb.append(apiDef.get("_name")).append(SEPARATOR);
// 2. 生成请求参数说明
sb.append(getRequestMdTable(apiDef));
// 3. 生成响应参数说明
sb.append(generateResponseMdTable(apiDef));
FileUtils.writeFile(api.replace("/", "_") + ".md", sb.toString());
}
private StringBuffer generateResponseMdTable(Map<String, Object> apiDef) {
return generateMdTable4SingleObject(api + " 响应参数说明", (Map<String, Object>) apiDef.get("_response"), 2);
}
private StringBuffer generateMdTable4SingleObject(String tableTitle, Map<String, Object> respnseDef, int level) {
StringBuffer sb = new StringBuffer(100);
sb.append(getLevelChars(level)).append(tableTitle).append(SEPARATOR);
sb.append("|参数名 |参数类型 |参数说明 |").append(SEPARATOR);
sb.append("|------|--------|---------|").append(SEPARATOR);
for (Map.Entry<String, Object> entry : respnseDef.entrySet()) {
String paramName = entry.getKey();
Map<String, Object> details = ((Map<String, Object>) entry.getValue());
String type = getTypeDescription((String) details.get("_type"));
String desc = (String) details.get("_desc");
sb.append("|").append(paramName).append("|").append(type).append("|").append(desc).append("|").append(SEPARATOR);
if ("object".equals(type) || "array".equals(type)) {
// 如果是数组或者对象类型 就新搞一个表格来说明里面的对象
sb.append(SEPARATOR).append(SEPARATOR);
sb.append(generateMdTable4SingleObject("参数" + paramName + "的属性说明", (Map<String, Object>) details.get("_content"), level + 1));
}
}
sb.append(SEPARATOR).append(SEPARATOR);
return sb;
}
private StringBuffer getRequestMdTable(Map<String, Object> apiDef) {
StringBuffer sb = new StringBuffer(100);
sb.append("## ").append(api).append(" 请求参数说明").append(SEPARATOR);
sb.append("|参数名 |参数类型 |参数说明 |").append(SEPARATOR);
sb.append("|------|--------|---------|").append(SEPARATOR);
Map<String, Object> requestDef = (Map<String, Object>) apiDef.get("_request");
for (Map.Entry<String, Object> entry : requestDef.entrySet()) {
String paramName = entry.getKey();
Map<String, String> details = ((Map<String, String>) entry.getValue());
String type = getTypeDescription(details.get("_type"));
String desc = details.get("_desc");
sb.append("|").append(paramName).append("|").append(type).append("|").append(desc).append("|").append(SEPARATOR);
}
sb.append(SEPARATOR).append(SEPARATOR);
return sb;
}
private StringBuffer getLevelChars(int level) {
StringBuffer sb = new StringBuffer(10);
level = level <= 3 ? level : 3;
for (int i = 1; i <= level; i++) {
sb.append("#");
}
sb.append(" ");
return sb;
}
private String getTypeDescription(String type) {
if (StringUtils.isEmpty(type)) {
type = "string";
}
if (typeDescptions.containsKey(type)) {
return typeDescptions.get(type);
}
if (type.startsWith("enum_")) {
String enumKey = type.substring("enum_".length());
return "枚举类型,可选范围:" + ApiDefUtils.getEnumItems(api, enumKey);
} else if (type.startsWith("connected_enum_")) {
String enumKey = type.substring("connected_enum_".length());
return "多值枚举类型,枚举可选范围:" + ApiDefUtils.getEnumItems(api, enumKey) + ", 多个值用逗号分隔。";
} else if (type.startsWith("optional")) {
String specialValue = type.substring("optional(".length(), "optional(".length() + 1);
return "特定值{" + specialValue + "}或不传值";
}
return type;
}
}
... ...
... ... @@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yoho.search.base.utils.HttpClientUtils;
import com.yoho.search.service.TestConstants;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.web.bind.annotation.RequestMapping;
... ... @@ -23,22 +24,22 @@ public class SearchNewControllerTest {
.append(TestConstants.SERVICE_PORT).append("/").append(TestConstants.SERVICE_CONTEXT)
.toString();
static Map<String,String> appendParams = new HashMap<>();
static Map<String, String> appendParams = new HashMap<>();
static final List<String> skippedApis = Arrays.asList("/count");
static
{
appendParams.put("/product_pool","filter_poolId=1");
appendParams.put("/new_product","brand=144");
appendParams.put("/group_brands","brand=144");
appendParams.put("/group_shops","shop=1");
appendParams.put("/suggest","query=vans");
appendParams.put("/shops","keyword=vans");
static {
appendParams.put("/product_pool", "filter_poolId=1");
appendParams.put("/new_product", "brand=144");
appendParams.put("/group_brands", "brand=144");
appendParams.put("/group_shops", "shop=1");
appendParams.put("/suggest", "query=vans");
appendParams.put("/shops", "keyword=vans");
appendParams.put("/productindex/productList", "query=vans&brand=144&gender=2,3&attribute_not=2&status=1");
}
@Test
public void testAPI() {
public void testAPIs() {
SearchNewController c = new SearchNewController();
Method[] methods = c.getClass().getMethods();
if (methods == null || methods.length == 0) {
... ... @@ -53,21 +54,66 @@ public class SearchNewControllerTest {
String methodName = m.getName();
String api = m.getAnnotation(RequestMapping.class).value()[0];
System.out.println("Begin to execute mothod " + methodName + " for api: " + api);
if(skippedApis.contains(api))
{
if (skippedApis.contains(api)) {
continue;
}
testSingle(api);
}
}
@Test
public void generateAPIMd() {
SearchNewController c = new SearchNewController();
Method[] methods = c.getClass().getMethods();
if (methods == null || methods.length == 0) {
return;
}
for (Method m : methods) {
if (!m.isAnnotationPresent(RequestMapping.class)) {
continue;
}
String api = m.getAnnotation(RequestMapping.class).value()[0];
if (skippedApis.contains(api)) {
continue;
}
// 生成markdown接口说明文件
MarkDownUtils.getInstance(api).generateApiMd();
}
}
@Test
public void testShops() {
testSingle("/shops");
}
@Test
public void testProductList() {
testSingle("/productindex/productList");
}
private void testSingle(String api) {
testSingle(api, appendParams.get(api));
}
private void testSingle(String api, String paramStr) {
String url = urlBase + api;
if(appendParams.containsKey(api))
{
url = url + "?" + appendParams.get(api);
if (StringUtils.isNotEmpty(paramStr)) {
url = url + "?" + paramStr;
}
CheckStructureUtils.checkRequest(api, url);
String jsonResult = HttpClientUtils.getMethod(url);
System.out.println("Request url is: " + url + ". jsonResult: \n" + jsonResult);
JSONObject jsonObject = JSON.parseObject(jsonResult);
// 检查响应是否OK
Assert.assertEquals(api, "200", jsonObject.getString("code"));
}
}
// 检查响应结果是否OK
CheckStructureUtils.check(api, jsonObject);
}
}
... ...
# _enums: 定义使用到的枚举类型
# _request: 请求
# _response: 响应
# _desc: 字段描述
# _required: 字段是否必须,默认为false
# _type: 字段类型,可选为int、long、string、double、object、array、enum_<ENUM_NAME>,默认为string
_name: 商品列表搜索接口。
_enums:
YesOrNo: ['Y','N']
Status: ['0','1']
Gender: ['1','2','3']
AgeLevel: ['1','2','3']
Outlet: ['1', '2']
VIPDiscountType: ['0', '1', '2']
Attribute: ['1', '2']
AppType: ['0', '1']
SellChannel: ['0', '1', '2']
_request:
query:
_desc: '搜索关键词'
_type: 'string'
viewNum:
_desc: '分页每页显示商品数量'
_type: 'int'
page:
_desc: '分页页数'
_type: 'int'
product_skn:
_desc: '指定的商品SKN列表'
_type: 'connected_int'
brand:
_desc: '指定的品牌列表'
_type: 'connected_int'
shop:
_desc: '指定的店铺列表'
_type: 'connected_int'
msort:
_desc: '指定的大分类列表'
_type: 'connected_int'
misort:
_desc: '指定的中分类列表'
_type: 'connected_int'
sort:
_desc: '指定的小分类列表'
_type: 'connected_int'
color:
_desc: '指定的颜色列表'
_type: 'connected_int'
style:
_desc: '指定的风格列表'
_type: 'connected_int'
size:
_desc: '指定的尺寸列表'
_type: 'connected_int'
gender:
_desc: '性别(1:男,2:女,3:通用)'
_type: 'connected_enum_Gender'
ageLevel:
_desc: '年龄段(1成人 2大童 3小童)'
_type: 'connected_enum_AgeLevel'
price:
_desc: '价格区间'
_type: 'ranged_int'
specialoffer:
_desc: '是否为促销品[5折以下]'
_type: 'enum_YesOrNo'
isdiscount:
_desc: '是否打折'
_type: 'enum_YesOrNo'
vdt:
_desc: 'VIP折扣类型'
_type: 'enum_VIPDiscountType'
p_d:
_desc: '折扣范围,浮点型,如【p_d_int=0.1,0.3】'
_type: 'ranged_double'
p_d_int:
_desc: '折扣范围,整形,如【p_d_int=1,3】'
_type: 'ranged_int'
isStudentPrice:
_desc: '是否有学生价优惠'
_type: 'enum_YesOrNo'
isStudentRebate:
_desc: '是否有学生返币'
_type: 'enum_YesOrNo'
isInstalment:
_desc: '是否分期'
_type: 'enum_Status'
sales:
_desc: '是否在售'
_type: 'enum_Status'
promotion:
_desc: '是否促销/推广商品 TODO'
_type: 'int'
attribute:
_desc: '包含指定的商品属性(1:正常商品,2:赠品)'
_type: 'enum_Attribute'
attribute_not:
_desc: '过滤指定的商品属性(1:正常商品,2:赠品)'
_type: 'enum_Attribute'
limited:
_desc: '是否限量商品'
_type: 'enum_YesOrNo'
new:
_desc: '是否新品'
_type: 'enum_YesOrNo'
outlets:
_desc: '是否奥莱商品'
_type: 'enum_Outlet'
status:
_desc: '是否上架'
_type: 'enum_Status'
breaking:
_desc: '传入1只查询断码商品,不传值或其他值该参数不起作用'
_type: 'optional(1)'
app_type:
_desc: 'APP类型'
_type: 'connected_enum_AppType'
sell_channels:
_desc: '销售平台 (网站、APP、现场 TODO)'
_type: 'enum_SellChannel'
stocknumber:
_desc: '商品库存,当传入0时查询库存为0的商品,当传入大于0的数值时,查询库存不小于改数值的商品'
_type: 'int'
folder_id:
_desc: '商品目录'
_type: 'int'
series_id:
_desc: '商品系列'
_type: 'int'
first_shelve_time:
_desc: '首次上架时间间隔'
_type: 'ranged_double'
shelve_time:
_desc: '最近上架日期间隔'
_type: 'ranged_double'
day:
_desc: '上架时间'
_type: 'date(yyyy-MM-dd)'
contain_global:
_desc: '传入Y查询结果包含全球购商品,不传值或其他值该参数不起作用'
_type: 'optional(Y)'
contain_seckill:
_desc: '传入Y查询结果包含秒杀商品,不传值或其他值该参数不起作用'
_type: 'optional(Y)'
act_temp:
_desc: '活动属性-模板id'
_type: 'int'
act_rec:
_desc: '活动属性-是否推荐'
_type: 'enum_Status'
act_status:
_desc: '活动属性-状态'
_type: 'enum_Status'
_response:
code:
_desc: '响应状态码,200为成功,其他为失败'
_required: true
_type: 'int'
message:
_desc: '响应描述信息'
data:
_desc: '响应具体数据'
_required: true
_type: 'object'
_content:
total:
_desc: '搜索出的商品总数'
_required: true
_type: 'int'
page:
_desc: '当前页数'
_required: true
_type: 'int'
page_size:
_desc: '每页显示商品数'
_required: true
_type: 'int'
page_total:
_desc: '总页数'
_required: true
_type: 'int'
product_list:
_desc: '当前页的商品列表'
_required: true
_type: 'array'
_content:
product_id:
_desc: '商品ID'
_type: 'int'
_required: true
product_skn:
_desc: '商品SKN'
_required: true
product_name:
_desc: '商品名称'
product_name_highlight:
_desc: '高亮的商品名称'
sales_phrase:
_desc: 'TODO'
cn_alphabet:
_desc: 'TODO'
brand_id:
_desc: '品牌ID'
_type: 'int'
brand_name:
_desc: '品牌名称'
brand_domain:
_desc: '品牌DOMAIN'
market_price:
_desc: '市场售价'
_type: 'double'
sales_price:
_desc: '售价'
_type: 'double'
student_price:
_desc: '学生售价'
_type: 'double'
vip_price:
_desc: 'VIP售价'
_type: 'double'
vip1_price:
_desc: 'VIP1售价'
_type: 'double'
vip2_price:
_desc: 'VIP2售价'
_type: 'double'
vip3_price:
_desc: 'VIP3售价'
_type: 'double'
vip_discount_type:
_desc: 'VIP折扣类型TODO'
_type: 'int'
is_new:
_desc: '是否新品'
_type: 'enum_YesOrNo'
is_advance:
_desc: 'TODO'
_type: 'enum_YesOrNo'
is_limited:
_desc: 'TODO'
_type: 'enum_YesOrNo'
is_special:
_desc: 'TODO'
_type: 'enum_YesOrNo'
is_discount:
_desc: '是否打折'
_type: 'enum_YesOrNo'
is_soon_sold_out:
_desc: '是否快要销售完'
_type: 'enum_YesOrNo'
status:
_desc: '商品状态'
_type: 'enum_Status'
is_promotion:
_desc: '优惠码'
_type: 'int'
is_student_price:
_desc: '是否有学生优惠'
_type: 'enum_YesOrNo'
is_student_rebate:
_desc: '是否有学生返币'
_type: 'enum_YesOrNo'
is_global:
_desc: '是否是全球购商品'
_type: 'enum_YesOrNo'
max_sort_id:
_desc: '商品大分类'
_type: 'int'
_required: true
middle_sort_id:
_desc: '商品中分类'
_type: 'int'
_required: true
small_sort_id:
_desc: '商品小分类'
_type: 'int'
_required: true
stock_number:
_desc: 'TODO'
_type: 'int'
storage_num:
_desc: '库存量'
_type: 'int'
sales_num:
_desc: '销量'
_type: 'int'
shelve_time:
_desc: '上架时间'
_type: 'long'
edit_time:
_desc: '编辑时间'
_type: 'long'
sales_num:
_desc: '销量'
_type: 'int'
is_outlets:
_desc: '是否是奥特莱斯商品'
_type: 'enum_Outlet'
gender:
_desc: '性别(1:男,2:女,3:通用)'
_type: 'enum_Gender'
age_level:
_desc: '年龄段(1成人 2大童 3小童)'
_type: 'connected_enum_AgeLevel'
default_images:
_desc: '默认的商品图片'
yohood_id:
_desc: 'TODO'
country_id:
_desc: 'TODO'
goods_list:
_desc: '商品的GOOD列表'
_type: 'array'
_content:
goods_id:
_desc: '商品的GOOD ID'
_type: 'int'
_required: true
color_id:
_desc: '商品的GOOD的颜色标识'
_type: 'int'
_required: true
product_skc:
_desc: '商品的GOOD的SKC'
_type: 'int'
_required: true
status:
_desc: '商品的GOOD状态'
_type: 'enum_Status'
_required: true
is_default:
_desc: '是否是默认GOOD'
_type: 'enum_YesOrNo'
color_name:
_desc: '商品的GOOD的颜色'
_type: 'string'
_required: true
color_code:
_desc: '商品的GOOD的颜色取值'
_type: 'string'
_required: true
color_value:
_desc: '商品的GOOD颜色值 TODO'
_type: 'string'
is_default:
_desc: '是否是默认GOOD'
_type: 'enum_YesOrNo'
images_url:
_desc: '商品的GOOD的图片链接'
_type: 'string'
cover_1:
_desc: '商品的GOOD的封面1'
_type: 'string'
cover_2:
_desc: '商品的GOOD的封面2'
_type: 'string'
\ No newline at end of file
... ...
_name: '根据关键词搜索品牌店铺接口。'
_request:
keyword:
_desc: '搜索店铺的关键词,不能为空'
_required: true
_type: 'string'
_response:
code:
_desc: '响应状态码,200为成功,其他为失败'
_required: true
_type: 'int'
message:
_desc: '响应描述信息'
_required: false
_type: 'string'
data:
_desc: '根据关键字搜索出的关联的品牌'
_required: false
_type: 'object'
_content:
id:
_desc: '品牌ID'
_required: true
_type: 'int'
brand_name:
_desc: '品牌名称'
_required: true
_type: 'string'
brand_ico:
_desc: '品牌ICO'
_required: true
_type: 'string'
brand_domain:
_desc: '品牌DOMAIN'
_required: true
_type: 'string'
\ No newline at end of file
... ...