Dubbo源碼分析(二):Dubbo之消費端(Consumer)


 

    通觀全部Dubbo代碼,有兩個很重要的對象就是Invoker和Exporter,Dubbo會根據用戶配置的協議調用不同協議的Invoker,再通過ReferenceFonfig將Invoker的引用關聯到Reference的ref屬性上提供給消費端調用。當用戶調用一個Service接口的一個方法后由於Dubbo使用javassist動態代理,會調用Invoker的Invoke方法從而初始化一個RPC調用訪問請求訪問服務端的Service返回結果。下面我們就從Comsumer端開始逐步解析這個框架。

    Dubbo首先使用com.alibaba.dubbo.config.spring.schema.NamespaceHandler注冊解析器,當spring解析xml配置文件時就會調用這些解析器生成對應的BeanDefinition交給spring管理:

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public class DubboNamespaceHandler extends NamespaceHandlerSupport {  
  2.   
  3.     static {  
  4.         Version.checkDuplicate(DubboNamespaceHandler.class);  
  5.     }  
  6.   
  7.     public void init() {  
  8.         //配置<dubbo:application>標簽解析器  
  9.         registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));  
  10.         //配置<dubbo:module>標簽解析器  
  11.         registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));  
  12.         //配置<dubbo:registry>標簽解析器  
  13.         registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));  
  14.         //配置<dubbo:monitor>標簽解析器  
  15.         registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));  
  16.         //配置<dubbo:provider>標簽解析器  
  17.         registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));  
  18.         //配置<dubbo:consumer>標簽解析器  
  19.         registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));  
  20.         //配置<dubbo:protocol>標簽解析器  
  21.         registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));  
  22.         //配置<dubbo:service>標簽解析器  
  23.         registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));  
  24.         //配置<dubbo:refenrence>標簽解析器  
  25.         registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));  
  26.         //配置<dubbo:annotation>標簽解析器  
  27.         registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));  
  28.     }  
  29. }  

 

 

 

 

    Spring在初始化IOC容器時會利用這里注冊的BeanDefinitionParser的parse方法獲取對應的ReferenceBean的BeanDefinition實例,由於ReferenceBean實現了InitializingBean接口,在設置了bean的所有屬性后會調用afterPropertiesSet方法:

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1.     public void afterPropertiesSet() throws Exception {  
  2.         //如果Consumer還未注冊  
  3.         if (getConsumer() == null) {  
  4.             //獲取applicationContext這個IOC容器實例中的所有ConsumerConfig  
  5.             Map<String, ConsumerConfig> consumerConfigMap = applicationContext == null ? null  : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ConsumerConfig.class, false, false);  
  6.             //如果IOC容器中存在這樣的ConsumerConfig  
  7.             if (consumerConfigMap != null && consumerConfigMap.size() > 0) {  
  8.                 ConsumerConfig consumerConfig = null;  
  9.                 //遍歷這些ConsumerConfig  
  10.                 for (ConsumerConfig config : consumerConfigMap.values()) {  
  11.                     //如果用戶沒配置Consumer系統會生成一個默認Consumer,且它的isDefault返回ture  
  12.                     //這里是說要么是Consumer是默認的要么是用戶配置的Consumer並且沒設置isDefault屬性  
  13.                     if (config.isDefault() == null || config.isDefault().booleanValue()) {  
  14.                         //防止存在兩個默認Consumer  
  15.                         if (consumerConfig != null) {  
  16.                             throw new IllegalStateException("Duplicate consumer configs: " + consumerConfig + " and " + config);  
  17.                         }  
  18.                         //獲取默認Consumer  
  19.                         consumerConfig = config;  
  20.                     }  
  21.                 }  
  22.                 if (consumerConfig != null) {  
  23.                     //設置默認Consumer  
  24.                     setConsumer(consumerConfig);  
  25.                 }  
  26.             }  
  27.         }  
  28.         //如果reference未綁定application且(reference未綁定consumer或referenc綁定的consumer沒綁定application  
  29.         if (getApplication() == null  
  30.                 && (getConsumer() == null || getConsumer().getApplication() == null)) {  
  31.             //獲取IOC中所有application的實例  
  32.             Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);  
  33.             if (applicationConfigMap != null && applicationConfigMap.size() > 0) {  
  34.                 //如果IOC中存在application  
  35.                 ApplicationConfig applicationConfig = null;  
  36.                 //遍歷這些application  
  37.                 for (ApplicationConfig config : applicationConfigMap.values()) {  
  38.                     //如果application是默認創建或者被指定成默認  
  39.                     if (config.isDefault() == null || config.isDefault().booleanValue()) {  
  40.                         if (applicationConfig != null) {  
  41.                             throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);  
  42.                         }  
  43.                         //獲取application  
  44.                         applicationConfig = config;  
  45.                     }  
  46.                 }  
  47.                 if (applicationConfig != null) {  
  48.                     //關聯到reference  
  49.                     setApplication(applicationConfig);  
  50.                 }  
  51.             }  
  52.         }  
  53.         //如果reference未綁定module且(reference未綁定consumer或referenc綁定的consumer沒綁定module  
  54.         if (getModule() == null  
  55.                 && (getConsumer() == null || getConsumer().getModule() == null)) {  
  56.             //獲取IOC中所有module的實例  
  57.             Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);  
  58.             if (moduleConfigMap != null && moduleConfigMap.size() > 0) {  
  59.                 ModuleConfig moduleConfig = null;  
  60.               //遍歷這些module  
  61.                 for (ModuleConfig config : moduleConfigMap.values()) {  
  62.                     //如果module是默認創建或者被指定成默認  
  63.                     if (config.isDefault() == null || config.isDefault().booleanValue()) {  
  64.                         if (moduleConfig != null) {  
  65.                             throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);  
  66.                         }  
  67.                       //獲取module  
  68.                         moduleConfig = config;  
  69.                     }  
  70.                 }  
  71.                 if (moduleConfig != null) {  
  72.                     //關聯到reference  
  73.                     setModule(moduleConfig);  
  74.                 }  
  75.             }  
  76.         }  
  77.         //如果reference未綁定注冊中心(Register)且(reference未綁定consumer或referenc綁定的consumer沒綁定注冊中心(Register)  
  78.         if ((getRegistries() == null || getRegistries().size() == 0)  
  79.                 && (getConsumer() == null || getConsumer().getRegistries() == null || getConsumer().getRegistries().size() == 0)  
  80.                 && (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().size() == 0)) {  
  81.             //獲取IOC中所有的注冊中心(Register)實例  
  82.             Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);  
  83.             if (registryConfigMap != null && registryConfigMap.size() > 0) {  
  84.                 List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();  
  85.                 //遍歷這些registry  
  86.                 for (RegistryConfig config : registryConfigMap.values()) {  
  87.                     //如果registry是默認創建或者被指定成默認  
  88.                     if (config.isDefault() == null || config.isDefault().booleanValue()) {  
  89.                         registryConfigs.add(config);  
  90.                     }  
  91.                 }  
  92.                   
  93.                 if (registryConfigs != null && registryConfigs.size() > 0) {  
  94.                     //關聯到reference,此處可以看出一個consumer可以綁定多個registry(注冊中心)  
  95.                     super.setRegistries(registryConfigs);  
  96.                 }  
  97.             }  
  98.         }  
  99.       //如果reference未綁定監控中心(Monitor)且(reference未綁定consumer或reference綁定的consumer沒綁定監控中心(Monitor)  
  100.         if (getMonitor() == null  
  101.                 && (getConsumer() == null || getConsumer().getMonitor() == null)  
  102.                 && (getApplication() == null || getApplication().getMonitor() == null)) {  
  103.             //獲取IOC中所有的監控中心(Monitor)實例  
  104.             Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);  
  105.             if (monitorConfigMap != null && monitorConfigMap.size() > 0) {  
  106.                 MonitorConfig monitorConfig = null;  
  107.                 //遍歷這些監控中心(Monitor)  
  108.                 for (MonitorConfig config : monitorConfigMap.values()) {  
  109.                     //如果monitor是默認創建或者被指定成默認  
  110.                     if (config.isDefault() == null || config.isDefault().booleanValue()) {  
  111.                         if (monitorConfig != null) {  
  112.                             throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);  
  113.                         }  
  114.                         monitorConfig = config;  
  115.                     }  
  116.                 }  
  117.                 if (monitorConfig != null) {  
  118.                     //關聯到reference,一個consumer綁定到一個監控中心(monitor)  
  119.                     setMonitor(monitorConfig);  
  120.                 }  
  121.             }  
  122.         }  
  123.         Boolean b = isInit();  
  124.         if (b == null && getConsumer() != null) {  
  125.             b = getConsumer().isInit();  
  126.         }  
  127.         if (b != null && b.booleanValue()) {  
  128.             //如果consumer已經被關聯則組裝Reference  
  129.             getObject();  
  130.         }  
  131.     }  
  132. }  

      這步其實是Reference確認生成Invoker所需要的組件是否已經准備好,都准備好后我們進入生成Invoker的部分。這里的getObject會調用父類ReferenceConfig的init方法完成組裝:

     

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. private void init() {  
  2.         //避免重復初始化  
  3.         if (initialized) {  
  4.             return;  
  5.         }  
  6.         //置為已經初始化  
  7.         initialized = true;  
  8.         //如果interfaceName不存在  
  9.         if (interfaceName == null || interfaceName.length() == 0) {  
  10.             throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");  
  11.         }  
  12.         // 獲取消費者  
  13.         checkDefault();  
  14.         appendProperties(this);  
  15.         //如果未使用泛接口並且consumer已經准備好的情況下,reference使用和consumer一樣的泛接口  
  16.         if (getGeneric() == null && getConsumer() != null) {  
  17.             setGeneric(getConsumer().getGeneric());  
  18.         }  
  19.         //如果是泛接口那么interface的類型是GenericService  
  20.         if (ProtocolUtils.isGeneric(getGeneric())) {  
  21.             interfaceClass = GenericService.class;  
  22.         } else {  
  23.             //如果不是泛接口使用interfaceName指定的泛接口  
  24.             try {  
  25.                 interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()  
  26.                         .getContextClassLoader());  
  27.             } catch (ClassNotFoundException e) {  
  28.                 throw new IllegalStateException(e.getMessage(), e);  
  29.             }  
  30.             //檢查接口以及接口中的方法都是否配置齊全  
  31.             checkInterfaceAndMethods(interfaceClass, methods);  
  32.         }  
  33.         //如果服務比較多可以指定dubbo-resolve.properties文件配置service(service集中配置文件)  
  34.         String resolve = System.getProperty(interfaceName);  
  35.         String resolveFile = null;  
  36.         if (resolve == null || resolve.length() == 0) {  
  37.             resolveFile = System.getProperty("dubbo.resolve.file");  
  38.             if (resolveFile == null || resolveFile.length() == 0) {  
  39.                 File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");  
  40.                 if (userResolveFile.exists()) {  
  41.                     resolveFile = userResolveFile.getAbsolutePath();  
  42.                 }  
  43.             }  
  44.             if (resolveFile != null && resolveFile.length() > 0) {  
  45.                 Properties properties = new Properties();  
  46.                 FileInputStream fis = null;  
  47.                 try {  
  48.                     fis = new FileInputStream(new File(resolveFile));  
  49.                     properties.load(fis);  
  50.                 } catch (IOException e) {  
  51.                     throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);  
  52.                 } finally {  
  53.                     try {  
  54.                         if(null != fis) fis.close();  
  55.                     } catch (IOException e) {  
  56.                         logger.warn(e.getMessage(), e);  
  57.                     }  
  58.                 }  
  59.                 resolve = properties.getProperty(interfaceName);  
  60.             }  
  61.         }  
  62.         if (resolve != null && resolve.length() > 0) {  
  63.             url = resolve;  
  64.             if (logger.isWarnEnabled()) {  
  65.                 if (resolveFile != null && resolveFile.length() > 0) {  
  66.                     logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");  
  67.                 } else {  
  68.                     logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");  
  69.                 }  
  70.             }  
  71.         }  
  72.         //如果application、module、registries、monitor未配置則使用consumer的  
  73.         if (consumer != null) {  
  74.             if (application == null) {  
  75.                 application = consumer.getApplication();  
  76.             }  
  77.             if (module == null) {  
  78.                 module = consumer.getModule();  
  79.             }  
  80.             if (registries == null) {  
  81.                 registries = consumer.getRegistries();  
  82.             }  
  83.             if (monitor == null) {  
  84.                 monitor = consumer.getMonitor();  
  85.             }  
  86.         }  
  87.         //如果module已關聯則關聯module的registries和monitor  
  88.         if (module != null) {  
  89.             if (registries == null) {  
  90.                 registries = module.getRegistries();  
  91.             }  
  92.             if (monitor == null) {  
  93.                 monitor = module.getMonitor();  
  94.             }  
  95.         }  
  96.       //如果application已關聯則關聯application的registries和monitor  
  97.         if (application != null) {  
  98.             if (registries == null) {  
  99.                 registries = application.getRegistries();  
  100.             }  
  101.             if (monitor == null) {  
  102.                 monitor = application.getMonitor();  
  103.             }  
  104.         }  
  105.         //檢查application  
  106.         checkApplication();  
  107.         //檢查遠端和本地服務接口真實存在(是否可load)  
  108.         checkStubAndMock(interfaceClass);  
  109.         Map<String, String> map = new HashMap<String, String>();  
  110.         //配置dubbo的端屬性(是consumer還是provider)、版本屬性、創建時間、進程號  
  111.         Map<Object, Object> attributes = new HashMap<Object, Object>();  
  112.         map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);  
  113.         map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());  
  114.         map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));  
  115.         if (ConfigUtils.getPid() > 0) {  
  116.             map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));  
  117.         }  
  118.         if (! isGeneric()) {  
  119.             String revision = Version.getVersion(interfaceClass, version);  
  120.             if (revision != null && revision.length() > 0) {  
  121.                 map.put("revision", revision);  
  122.             }  
  123.   
  124.             String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();  
  125.             if(methods.length == 0) {  
  126.                 logger.warn("NO method found in service interface " + interfaceClass.getName());  
  127.                 map.put("methods", Constants.ANY_VALUE);  
  128.             }  
  129.             else {  
  130.                 map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));  
  131.             }  
  132.         }  
  133.         map.put(Constants.INTERFACE_KEY, interfaceName);  
  134.         //調用application、module、consumer的get方法將屬性設置到map中  
  135.         appendParameters(map, application);  
  136.         appendParameters(map, module);  
  137.         appendParameters(map, consumer, Constants.DEFAULT_KEY);  
  138.         appendParameters(map, this);  
  139.         String prifix = StringUtils.getServiceKey(map);  
  140.         if (methods != null && methods.size() > 0) {  
  141.             for (MethodConfig method : methods) {  
  142.                 appendParameters(map, method, method.getName());  
  143.                 String retryKey = method.getName() + ".retry";  
  144.                 if (map.containsKey(retryKey)) {  
  145.                     String retryValue = map.remove(retryKey);  
  146.                     if ("false".equals(retryValue)) {  
  147.                         map.put(method.getName() + ".retries", "0");  
  148.                     }  
  149.                 }  
  150.                 appendAttributes(attributes, method, prifix + "." + method.getName());  
  151.                 checkAndConvertImplicitConfig(method, map, attributes);  
  152.             }  
  153.         }  
  154.         //attributes通過系統context進行存儲.  
  155.         StaticContext.getSystemContext().putAll(attributes);  
  156.         //在map裝載了application、module、consumer、reference的所有屬性信息后創建代理  
  157.         ref = createProxy(map);  
  158.     }  

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. private T createProxy(Map<String, String> map) {  
  2.         URL tmpUrl = new URL("temp", "localhost", 0, map);  
  3.         final boolean isJvmRefer;  
  4.         if (isInjvm() == null) {  
  5.             if (url != null && url.length() > 0) { //指定URL的情況下,不做本地引用  
  6.                 isJvmRefer = false;  
  7.             } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {  
  8.                 //默認情況下如果本地有服務暴露,則引用本地服務.  
  9.                 isJvmRefer = true;  
  10.             } else {  
  11.                 isJvmRefer = false;  
  12.             }  
  13.         } else {  
  14.             isJvmRefer = isInjvm().booleanValue();  
  15.         }  
  16.           
  17.         if (isJvmRefer) {  
  18.             URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);  
  19.             invoker = refprotocol.refer(interfaceClass, url);  
  20.             if (logger.isInfoEnabled()) {  
  21.                 logger.info("Using injvm service " + interfaceClass.getName());  
  22.             }  
  23.         } else {  
  24.             if (url != null && url.length() > 0) { // 用戶指定URL,指定的URL可能是對點對直連地址,也可能是注冊中心URL  
  25.                 String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);  
  26.                 if (us != null && us.length > 0) {  
  27.                     for (String u : us) {  
  28.                         URL url = URL.valueOf(u);  
  29.                         if (url.getPath() == null || url.getPath().length() == 0) {  
  30.                             url = url.setPath(interfaceName);  
  31.                         }  
  32.                         if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {  
  33.                             urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));  
  34.                         } else {  
  35.                             urls.add(ClusterUtils.mergeUrl(url, map));  
  36.                         }  
  37.                     }  
  38.                 }  
  39.             } else { // 通過注冊中心配置拼裝URL  
  40.                 List<URL> us = loadRegistries(false);  
  41.                 if (us != null && us.size() > 0) {  
  42.                     for (URL u : us) {  
  43.                         URL monitorUrl = loadMonitor(u);  
  44.                         if (monitorUrl != null) {  
  45.                             map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));  
  46.                         }  
  47.                         urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));  
  48.                     }  
  49.                 }  
  50.                 if (urls == null || urls.size() == 0) {  
  51.                     throw new IllegalStateException("No such any registry to reference " + interfaceName  + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");  
  52.                 }  
  53.             }  
  54.   
  55.             if (urls.size() == 1) {  
  56.                 //此處舉例說明如果是Dubbo協議則調用DubboProtocol的refer方法生成invoker,當用戶調用service接口實際調用的是invoker的invoke方法  
  57.                 invoker = refprotocol.refer(interfaceClass, urls.get(0));  
  58.             } else {  
  59.                 //多個service生成多個invoker  
  60.                 List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();  
  61.                 URL registryURL = null;  
  62.                 for (URL url : urls) {  
  63.                     invokers.add(refprotocol.refer(interfaceClass, url));  
  64.                     if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {  
  65.                         registryURL = url; // 用了最后一個registry url  
  66.                     }  
  67.                 }  
  68.                 if (registryURL != null) { // 有 注冊中心協議的URL  
  69.                     // 對有注冊中心的Cluster 只用 AvailableCluster  
  70.                     URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);   
  71.                     invoker = cluster.join(new StaticDirectory(u, invokers));  
  72.                 }  else { // 不是 注冊中心的URL  
  73.                     invoker = cluster.join(new StaticDirectory(invokers));  
  74.                 }  
  75.             }  
  76.         }  
  77.   
  78.         Boolean c = check;  
  79.         if (c == null && consumer != null) {  
  80.             c = consumer.isCheck();  
  81.         }  
  82.         if (c == null) {  
  83.             c = true; // default true  
  84.         }  
  85.         if (c && ! invoker.isAvailable()) {  
  86.             throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());  
  87.         }  
  88.         if (logger.isInfoEnabled()) {  
  89.             logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());  
  90.         }  
  91.         // 創建服務代理  
  92.         return (T) proxyFactory.getProxy(invoker);  
  93.     }  


      至此Reference在關聯了所有application、module、consumer、registry、monitor、service、protocol后調用對應Protocol類的refer方法生成InvokerProxy。當用戶調用service時dubbo會通過InvokerProxy調用Invoker的invoke的方法向服務端發起請求。客戶端就這樣完成了自己的初始化。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM