当前位置: 首页 > news >正文

合肥知名网站推广山东政务服务网

合肥知名网站推广,山东政务服务网,营销网站建设的规则,网站建设招标要求Open Feign 源码解析四 请求对象的构造#xff08;上#xff09; 源码前三篇文章写了这个图的过程 源码前三篇文章的内容归纳起来就是讲了这样的问题#xff1a; 如何把接口转换为具有发送http请求能力的feign client对象以及如何整合到Spring容器中#xff1f; 如何构造…Open Feign 源码解析四 请求对象的构造上 源码前三篇文章写了这个图的过程 源码前三篇文章的内容归纳起来就是讲了这样的问题 如何把接口转换为具有发送http请求能力的feign client对象以及如何整合到Spring容器中 如何构造请求对象? 思路分析 Http请求对象的分析目标 URL: http://127.0.0.1:9000/consumer/feign/order/{1}?namexxxage18 ​ 协议: http ​ IP端口: 127.0.0.1:9000 - 注册中心获取 ​ URI: /consumer/feign/order/{id} ​ 路径参: {1} (path variable) ​ 请求参namexxx, age18 (query) 请求头: headers 请求体 body 请求方法: Get/Post/Put/Delete … public final class Request {private final HttpMethod httpMethod;private final String url;private final MapString, CollectionString headers;private final Body body; }接口方法的分析数据源 方法本身的要素是否能表达所有Http请求的要素 方法的要素 ​ 方法名 × ​ 参数(名称与类型) √ ​ 返回值类型 × URI - 注解 或 Java对象URI对象表示 请求方法 - 注解 路径参、请求参、请求头、请求体 - 方法的入参 注解 问题一注解如何设计 1URI 和 请求方法可以合并在一个注解中 2对路径参、请求参、请求头、请求体分别设置对应的注解 feign RequestLine/Param/QueryMap/HeaderMap/Body open feign RequestMapping/PathVariable/RequestParam/SpringQueryMap/RequestHeader/RequestBody URI: 类的RequestMapping 方法的RequestMapping 请求方法 方法的RequestMapping 路径参参数的PathVariable 请求参参数的RequestParam SpringQueryMap 请求头: 类的RequestMapping(produce/consume/header) ​ 方法的RequestMapping(produce/consume/header) ​ 参数的RequestHeader 问题二为什么选择SpringMVC注解 SpringMVC http 请求 - Java 对象 open feignJava 对象 - http 请求 对于方法和注解信息可以封装在新的对象中 - 方法元数据 方法元数据的分析 1各种参数的位置索引 2参数名称类型 3参数类型转换器 4编码信息 public final class MethodMetadata implements Serializable {private static final long serialVersionUID 1L;private String configKey;private transient Type returnType;private Integer urlIndex;private Integer bodyIndex;private Integer headerMapIndex;private Integer queryMapIndex;private boolean queryMapEncoded;private transient Type bodyType;private RequestTemplate template new RequestTemplate();private ListString formParams new ArrayListString();private MapInteger, CollectionString indexToName new LinkedHashMapInteger, CollectionString();private MapInteger, Class? extends Expander indexToExpanderClass new LinkedHashMapInteger, Class? extends Expander();private MapInteger, Boolean indexToEncoded new LinkedHashMapInteger, Boolean();private transient MapInteger, Expander indexToExpander; }构造请求对象整体思路 构建请求对象分两步走 1解析方法和注解类、方法、参数并把信息封装到方法元数据中 - 应用启动 2结合方法元数据和实际参数构建请求对象 - 方法调用 实参的类型转换编码填充 问题三如何转换成方法元数据 1做成一个组件Contract public interface Contract {// 解析接口的注解信息并封装为方法元数据的集合ListMethodMetadata parseAndValidatateMetadata(Class? targetType); }模板方法的设计模式 ​ 接口 抽象实现 默认实现 ​ 接口提供扩展性 - Contract ​ 抽象实现 抽取公共逻辑 - BaseContract ​ 默认实现提供基本功能的使用 - Default, SpringMvcContract 2Contract组件从何获得 Springboot自动装配 从FeignContext获取 Configuration(proxyBeanMethods false) public class FeignClientsConfiguration {BeanConditionalOnMissingBeanpublic Contract feignContract(ConversionService feignConversionService) {return new SpringMvcContract(this.parameterProcessors, feignConversionService);} }源码解读 BaseContract 解析注解的顺序类 - 方法 - 参数 abstract class BaseContract implements Contract {/** 解析接口的注解信息并封装为方法元数据的集合 */Overridepublic ListMethodMetadata parseAndValidatateMetadata(Class? targetType) {// 接口不能带有泛型checkState(targetType.getTypeParameters().length 0, Parameterized types unsupported: %s,targetType.getSimpleName());// 接口最多只能有一个父接口checkState(targetType.getInterfaces().length 1, Only single inheritance supported: %s,targetType.getSimpleName());// 如果传入的接口有一个父接口 那么该父接口必须是顶级接口 if (targetType.getInterfaces().length 1) {checkState(targetType.getInterfaces()[0].getInterfaces().length 0,Only single-level inheritance supported: %s,targetType.getSimpleName());}// 新建一个结果集容器 MapString, MethodMetadata result new LinkedHashMapString, MethodMetadata();// 获取所有public方法包括从父接口继承而来的 for (Method method : targetType.getMethods()) {// 排除掉从Object继承的方法static方法接口中的default方法if (method.getDeclaringClass() Object.class ||(method.getModifiers() Modifier.STATIC) ! 0 ||Util.isDefault(method)) {continue;}// 把方法解析为方法元数据 【关键代码】 MethodMetadata metadata parseAndValidateMetadata(targetType, method);// 重写方法不支持 checkState(!result.containsKey(metadata.configKey()), Overrides unsupported: %s,metadata.configKey());result.put(metadata.configKey(), metadata);}return new ArrayList(result.values());}/** 解析方法的注解并封装为方法元数据对象 */protected MethodMetadata parseAndValidateMetadata(Class? targetType, Method method) {// 创建MethodMetadata对象MethodMetadata data new MethodMetadata();// 设置返回值data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));// 设置configKey,方法的唯一标识: 接口名#方法名(参数类型名称1,参数类型名称2)data.configKey(Feign.configKey(targetType, method));// 如果有父接口先处理父接口if (targetType.getInterfaces().length 1) {processAnnotationOnClass(data, targetType.getInterfaces()[0]);}// 再处理当前接口 【关键代码】processAnnotationOnClass(data, targetType);// 处理方法的注解 【关键代码】for (Annotation methodAnnotation : method.getAnnotations()) {processAnnotationOnMethod(data, methodAnnotation, method);}// 只支持GET POST等http方法checkState(data.template().method() ! null,Method %s not annotated with HTTP method type (ex. GET, POST),method.getName());// 获取参数原始类型Class?[] parameterTypes method.getParameterTypes();// 获取参数通用类型Type[] genericParameterTypes method.getGenericParameterTypes();// 获取参数注解 二维数组:因为可以有多个参数 每个参数有多个注解Annotation[][] parameterAnnotations method.getParameterAnnotations();int count parameterAnnotations.length;for (int i 0; i count; i) {boolean isHttpAnnotation false;if (parameterAnnotations[i] ! null) {// 处理每个参数的注解 如果其中有一个注解属于http注解 则isHttpAnnotation为true // 哪些属于http注解如SpringMVC的RequestHeader PathVariable RequestParam SpringQueryMap//【关键代码】isHttpAnnotation processAnnotationsOnParameter(data, parameterAnnotations[i], i);}if (parameterTypes[i] URI.class) {data.urlIndex(i);} else if (!isHttpAnnotation parameterTypes[i] ! Request.Options.class) {// 参数类型不是URI或Options 也没有加http注解 则该参数判定为body checkState(data.formParams().isEmpty(),Body parameters cannot be used with form parameters.);checkState(data.bodyIndex() null, Method has too many Body parameters: %s, method);// 设置body的位置和类型【关键代码】data.bodyIndex(i);data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));}}// ...return data;}/** 处理类上的注解 */protected abstract void processAnnotationOnClass(MethodMetadata data, Class? clz);/** 处理方法上的注解 */protected abstract void processAnnotationOnMethod(MethodMetadata data, Annotation annotation, Method method);/** 处理参数上的注解 */protected abstract boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex);}SpringMvcContract 类RequestMapping 方法RequestMapping 参数PathVariable SpringQueryMap RequestHeader RequestParam RequestMapping Target({ElementType.TYPE, ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) Documented Mapping public interface RequestMapping {AliasFor(path)String[] value() default {};AliasFor(value)String[] path() default {};/*** The HTTP request methods to map to, narrowing the primary mapping:* GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.*/RequestMethod[] method() default {};String[] params() default {};String[] headers() default {};/*** header的Content-Type*/String[] consumes() default {};/*** header的Accept*/String[] produces() default {};}public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {private static final String ACCEPT Accept;private static final String CONTENT_TYPE Content-Type;private static final TypeDescriptor STRING_TYPE_DESCRIPTOR TypeDescriptor.valueOf(String.class);private static final TypeDescriptor ITERABLE_TYPE_DESCRIPTOR TypeDescriptor.valueOf(Iterable.class);private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER new DefaultParameterNameDiscoverer();// 参数处理器 可以自动装配也可以使用默认的处理器private final MapClass? extends Annotation, AnnotatedParameterProcessor annotatedArgumentProcessors;private final MapString, Method processedMethods new HashMap();private final ConversionService conversionService;private final ConvertingExpanderFactory convertingExpanderFactory;private ResourceLoader resourceLoader new DefaultResourceLoader();public SpringMvcContract(ListAnnotatedParameterProcessor annotatedParameterProcessors,ConversionService conversionService) {Assert.notNull(annotatedParameterProcessors,Parameter processors can not be null.);Assert.notNull(conversionService, ConversionService can not be null.);// 初始化参数处理器ListAnnotatedParameterProcessor processors;if (!annotatedParameterProcessors.isEmpty()) {processors new ArrayList(annotatedParameterProcessors);}else {processors getDefaultAnnotatedArgumentsProcessors();}this.annotatedArgumentProcessors toAnnotatedArgumentProcessorMap(processors);// 创建参数转换器工厂 真正的转换功能来自conversionServicethis.conversionService conversionService;this.convertingExpanderFactory new ConvertingExpanderFactory(conversionService);}/** 获取默认处理器 */private ListAnnotatedParameterProcessor getDefaultAnnotatedArgumentsProcessors() {ListAnnotatedParameterProcessor annotatedArgumentResolvers new ArrayList();annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor()); // 处理MatrixVariableannotatedArgumentResolvers.add(new PathVariableParameterProcessor()); // 处理PathVavirableannotatedArgumentResolvers.add(new RequestParamParameterProcessor()); // 处理RequestParamannotatedArgumentResolvers.add(new RequestHeaderParameterProcessor()); // 处理RequestHeaderannotatedArgumentResolvers.add(new QueryMapParameterProcessor()); // 处理SpringQueryMapannotatedArgumentResolvers.add(new RequestPartParameterProcessor()); // 处理RequestPartreturn annotatedArgumentResolvers;}Overridepublic MethodMetadata parseAndValidateMetadata(Class? targetType, Method method) {// 方法先放入缓存中 表示已经处理this.processedMethods.put(Feign.configKey(targetType, method), method);// 调用父类的parseAndValidateMetadataMethodMetadata md super.parseAndValidateMetadata(targetType, method);// 处理类上的RequestMapping注解// 因为RequestMapping注解可以加在类上和方法上 两者中注解值有优先级问题RequestMapping classAnnotation findMergedAnnotation(targetType,RequestMapping.class);if (classAnnotation ! null) {// 解析header中的produces// 此时可能已经从方法的RequestMapping注解获得produces的值// 这样处理表示方法上的RequestMapping注解优先于类上的RequestMapping注解if (!md.template().headers().containsKey(ACCEPT)) {parseProduces(md, method, classAnnotation);}// 解析header中的consumes 原理同producesif (!md.template().headers().containsKey(CONTENT_TYPE)) {parseConsumes(md, method, classAnnotation);}// 解析headersparseHeaders(md, method, classAnnotation);}return md;}/** 处理类上的注解(RequestMapping) */Overrideprotected void processAnnotationOnClass(MethodMetadata data, Class? clz) {if (clz.getInterfaces().length 0) {RequestMapping classAnnotation findMergedAnnotation(clz,RequestMapping.class);// 这里只处理类上RequestMapping的path,// 其他produces, consumes, headers放在解析方法上的RequestMapping注解之后if (classAnnotation ! null) {// 如果类上的RequestMapping有value(path) 处理后放入uri中if (classAnnotation.value().length 0) {String pathValue emptyToNull(classAnnotation.value()[0]);// 解析path中的${} pathValue resolve(pathValue);// 保证uri以/开头if (!pathValue.startsWith(/)) {pathValue / pathValue;}// 放入uri中data.template().uri(pathValue);}}}}/** 处理方法上的注解(RequestMapping) */Overrideprotected void processAnnotationOnMethod(MethodMetadata data,Annotation methodAnnotation, Method method) {// 如果不是RequestMapping注解本身 也不带有RequestMapping注解的话就返回if (!RequestMapping.class.isInstance(methodAnnotation) !methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {return;}RequestMapping methodMapping findMergedAnnotation(method, RequestMapping.class);// 解析HTTP MethodRequestMethod[] methods methodMapping.method();if (methods.length 0) {methods new RequestMethod[] { RequestMethod.GET };}checkOne(method, methods, method);data.template().method(Request.HttpMethod.valueOf(methods[0].name()));// 解析pathcheckAtMostOne(method, methodMapping.value(), value);if (methodMapping.value().length 0) {String pathValue emptyToNull(methodMapping.value()[0]);if (pathValue ! null) {pathValue resolve(pathValue);if (!pathValue.startsWith(/) !data.template().path().endsWith(/)) {pathValue / pathValue;}data.template().uri(pathValue, true);}}// 解析header中的producesparseProduces(data, method, methodMapping);// 解析header中的consumesparseConsumes(data, method, methodMapping);// 解析headersparseHeaders(data, method, methodMapping);data.indexToExpander(new LinkedHashMapInteger, Param.Expander());}/** 处理参数上的注解 */Overrideprotected boolean processAnnotationsOnParameter(MethodMetadata data,Annotation[] annotations, int paramIndex) {boolean isHttpAnnotation false;AnnotatedParameterProcessor.AnnotatedParameterContext context new SimpleAnnotatedParameterContext(data, paramIndex);Method method this.processedMethods.get(data.configKey());for (Annotation parameterAnnotation : annotations) {// 根据参数注解类型获取对应的参数处理器AnnotatedParameterProcessor processor this.annotatedArgumentProcessors.get(parameterAnnotation.annotationType());if (processor ! null) {Annotation processParameterAnnotation;processParameterAnnotation synthesizeWithMethodParameterNameAsFallbackValue(parameterAnnotation, method, paramIndex);// 参数处理器处理【关键代码】isHttpAnnotation | processor.processArgument(context,processParameterAnnotation, method);}}// 如果是http注解并且没有对应的expander// 什么expander - 参数转换器if (isHttpAnnotation data.indexToExpander().get(paramIndex) null) {TypeDescriptor typeDescriptor createTypeDescriptor(method, paramIndex);if (this.conversionService.canConvert(typeDescriptor,STRING_TYPE_DESCRIPTOR)) {Param.Expander expander this.convertingExpanderFactory.getExpander(typeDescriptor);if (expander ! null) {data.indexToExpander().put(paramIndex, expander);}}}return isHttpAnnotation;}// ... }AnnotatedParameterProcessor PathVariableParameterProcessorPathVariable 解析路径参数 QueryMapParameterProcessor: SpringQueryMap 解析请求参数 RequestHeaderParameterProcessor: RequestHeader 解析请求头 RequestParamParameterProcessorRequestParam 解析请求参数 MatrixVariableParameterProcessor: MatrixVariable 解析矩阵参数 RequestPartParameterProcessor: RequestPart 解析form表单 File文件 QueryMapParameterProcessor 与 RequestParamParameterProcessor的区别 前者可以解析自定义实体对象Map和基本类型没有特别的限制 后者只能解析Map和基本类型不能解析自定义对象类型 QueryMapParameterProcessor public class QueryMapParameterProcessor implements AnnotatedParameterProcessor {private static final ClassSpringQueryMap ANNOTATION SpringQueryMap.class;Overridepublic Class? extends Annotation getAnnotationType() {return ANNOTATION;}Overridepublic boolean processArgument(AnnotatedParameterContext context,Annotation annotation, Method method) {int paramIndex context.getParameterIndex();MethodMetadata metadata context.getMethodMetadata();// 对SpringQueryMap注解所对应的参数的类型没有限制if (metadata.queryMapIndex() null) {metadata.queryMapIndex(paramIndex);metadata.queryMapEncoded(SpringQueryMap.class.cast(annotation).encoded());}return true;} }RequestParamParameterProcessor public class RequestParamParameterProcessor implements AnnotatedParameterProcessor {private static final ClassRequestParam ANNOTATION RequestParam.class;Overridepublic Class? extends Annotation getAnnotationType() {return ANNOTATION;}Overridepublic boolean processArgument(AnnotatedParameterContext context,Annotation annotation, Method method) {int parameterIndex context.getParameterIndex();Class? parameterType method.getParameterTypes()[parameterIndex];MethodMetadata data context.getMethodMetadata();// 参数必须是Map类型 否则不可以成为QueryMapif (Map.class.isAssignableFrom(parameterType)) {checkState(data.queryMapIndex() null,Query map can only be present once.);data.queryMapIndex(parameterIndex);return true;}RequestParam requestParam ANNOTATION.cast(annotation);String name requestParam.value();checkState(emptyToNull(name) ! null,RequestParam.value() was empty on parameter %s, parameterIndex);context.setParameterName(name);CollectionString query context.setTemplateParameter(name,data.template().queries().get(name));data.template().query(name, query);return true;} }实参类型转换和填充 interface Expander {/*** Expands the value into a string. Does not accept or return null.*/String expand(Object value); }public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {private static final TypeDescriptor STRING_TYPE_DESCRIPTOR TypeDescriptor.valueOf(String.class);private static class ConvertingExpanderFactory {private final ConversionService conversionService;ConvertingExpanderFactory(ConversionService conversionService) {this.conversionService conversionService;}Param.Expander getExpander(TypeDescriptor typeDescriptor) {return value - {Object converted this.conversionService.convert(value, typeDescriptor,STRING_TYPE_DESCRIPTOR);return (String) converted;};}} }Java 中的所有类型 raw type原始类型对应 Class 即我们通常说的引用类型包括普通的类例如 String.class、List.class 也包括数组(Array.class)、接口(Cloneable.class)、注解(Annotation.class)、枚举(Enum.class)等 primitive types基本类型对应 Class 包括 Built-in 内置类型例如 int.class、char.class、void.class 也包括 Wrappers 内置类型包装类型例如 Integer.class、Boolean.class、Void.class parameterized types参数化类型对应 ParameterizedType 带有类型参数的类型即常说的泛型例如 List、MapInteger, String、List? extends Number 实现类 sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl type variables类型变量类型对应 TypeVariable即参数化类型 ParameterizedType 中的 E、K 等类型变量表示泛指任何类实现类 sun.reflect.generics.reflectiveObjects.TypeVariableImpl array types泛型数组类型对应 GenericArrayType元素类型是参数化类型或者类型变量的泛型数组类型例如 T[]实现类 sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl Type 接口的另一个子接口 WildcardType 代表通配符表达式类型或泛型表达式类型比如?、? super T、? extends T他并不是 Java 类型中的一种。 private static class BuildTemplateByResolvingArgs implements RequestTemplate.Factory {private final QueryMapEncoder queryMapEncoder;protected final MethodMetadata metadata;private final MapInteger, Expander indexToExpander new LinkedHashMapInteger, Expander();/** 通过metadata信息和实参创建RequestTemplate */Overridepublic RequestTemplate create(Object[] argv) {// 把metadata中的半成品template拷贝一份 RequestTemplate mutable RequestTemplate.from(metadata.template());// 处理URI对象if (metadata.urlIndex() ! null) {int urlIndex metadata.urlIndex();checkArgument(argv[urlIndex] ! null, URI parameter %s was null, urlIndex);mutable.target(String.valueOf(argv[urlIndex]));}// MapString, Object varBuilder new LinkedHashMapString, Object();for (EntryInteger, CollectionString entry : metadata.indexToName().entrySet()) {int i entry.getKey();Object value argv[entry.getKey()];if (value ! null) { // Null values are skipped.if (indexToExpander.containsKey(i)) {value expandElements(indexToExpander.get(i), value);}for (String name : entry.getValue()) {varBuilder.put(name, value);}}}RequestTemplate template resolve(argv, mutable, varBuilder);// 处理queryMapif (metadata.queryMapIndex() ! null) {// add query map parameters after initial resolve so that they take// precedence over any predefined valuesObject value argv[metadata.queryMapIndex()];MapString, Object queryMap toQueryMap(value);template addQueryMapQueryParameters(queryMap, template);}// 处理headerMapif (metadata.headerMapIndex() ! null) {template addHeaderMapHeaders((MapString, Object) argv[metadata.headerMapIndex()], template);}return template;}SuppressWarnings(unchecked)private RequestTemplate addHeaderMapHeaders(MapString, Object headerMap,RequestTemplate mutable) {for (EntryString, Object currEntry : headerMap.entrySet()) {CollectionString values new ArrayListString();Object currValue currEntry.getValue();if (currValue instanceof Iterable?) {Iterator? iter ((Iterable?) currValue).iterator();while (iter.hasNext()) {Object nextObject iter.next();values.add(nextObject null ? null : nextObject.toString());}} else {values.add(currValue null ? null : currValue.toString());}mutable.header(currEntry.getKey(), values);}return mutable;}SuppressWarnings(unchecked)private RequestTemplate addQueryMapQueryParameters(MapString, Object queryMap,RequestTemplate mutable) {for (EntryString, Object currEntry : queryMap.entrySet()) {CollectionString values new ArrayListString();boolean encoded metadata.queryMapEncoded();Object currValue currEntry.getValue();if (currValue instanceof Iterable?) {Iterator? iter ((Iterable?) currValue).iterator();while (iter.hasNext()) {Object nextObject iter.next();values.add(nextObject null ? null: encoded ? nextObject.toString(): UriUtils.encode(nextObject.toString()));}} else {values.add(currValue null ? null: encoded ? currValue.toString() : UriUtils.encode(currValue.toString()));}mutable.query(encoded ? currEntry.getKey() : UriUtils.encode(currEntry.getKey()), values);}return mutable;}// ... }
http://www.zqtcl.cn/news/581930/

相关文章:

  • 黑龙江网站建站建设知名品牌形象设计公司
  • 网站建设去哪可接单怎么做微信小程序平台
  • 做外贸重新设计网站兰州网站建设慕枫
  • 服装销售 网站建设论文搭建企业网站需要什么
  • cnnic网站备案html网站建设代码
  • 金华网站建设明细报价表c苏宁网站开发
  • 在手机上怎么做微电影网站湖南人工智能建站系统软件
  • 网站做的一样算侵权吗站群建站系统
  • 骨干专业建设验收网站xueui wordpress
  • 宁波高质量品牌网站设计厂家世界上有php应用的网站
  • 网站平台建设的重要性响应式网站切图
  • 近期做网站需要什么软件网站制作常见的问题
  • vs做的本地网站甘肃省建设厅门户网站
  • 网站建设合同义务wordpress m1
  • 青海省建设局网站首页wordpress模板建站教程视频
  • 演示 又一个wordpress站点静态页面加wordpress
  • 企业做淘宝客网站有哪些有网站吗给一个
  • 深圳网站制作公司信息之梦与wordpress哪个好用
  • 免费搭建商城网站山西制作网站公司排名
  • 网站开发免费中建建设银行网站
  • 301重定向到新网站在线建站
  • 曰本做爰l网站沙朗做网站公司
  • 广州网站设计智能 乐云践新专家wordpress 头像设置
  • 电子商务学网站建设好吗佛山市手机网站建设企业
  • 为企业做网站电话开场白小说网站的里面的搜索是怎么做的
  • 深圳市设计网站公司自己做网站开网店
  • 智能建站cms管理系统修改wordpress时区
  • 站长怎么添加网站内容重庆网站推
  • 东莞网站建设属于什么专业网页设计代码书
  • 网站后台代码在哪修改wordpress添加搜索小工具