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

襄阳网站建设价格低个性化网站建设报价

襄阳网站建设价格低,个性化网站建设报价,朝城做网站公司,58同城石家庄网站建设银行查询服务的设计和实现 项目地址github#xff1a;https://github.com/xl-echo/bankInquiryService项目地址gitee#xff1a;https://gitee.com/xl-echo/bank-inquiry-service 银行查询服务的设计初衷是#xff1a;为提供更加便利的查询服务#xff0c;我们在分布式系…银行查询服务的设计和实现 项目地址githubhttps://github.com/xl-echo/bankInquiryService项目地址giteehttps://gitee.com/xl-echo/bank-inquiry-service 银行查询服务的设计初衷是为提供更加便利的查询服务我们在分布式系统架构下独立开发了与各大银行对接的查询服务。该独立服务支持用户轻松查询账户余额和消费明细的信息同时保证用户消费的可见性。这种架构设计不仅提升了用户的查询体验、保证了用户的信息安全更为整个分布式系统的性能和可维护性提供了保障为用户和第三方支付机构的长期合作奠定了良好的基础。该服务设计以微服务为基础使用多种设计模式。如单例工厂模板策略等。其中对于IOC和AOP的应用是系统逻辑结构设计的最核心部分在高并发的服务下为了保证数据的安全性我们利用IOC和工厂构建的方式将每个请求流程独立保证每个请求之间数据独立。AOP的利用为整个系统提供了更好的全局处理方案并且对对接多家银行因为标准不一的问题提供了统一的解决平台解决了多家银行之间不能够有效统一返回。 The original intention of designing bank query services is to provide more convenient query services. We have independently developed query services that interface with major banks under a distributed system architecture. This independent service supports users to easily query account balance and consumption details, while ensuring visibility of user consumption. This architecture design not only improves users’ query experience and ensures their information security, but also provides guarantees for the performance and maintainability of the entire distributed system, laying a good foundation for long-term cooperation between users and third-party payment institutions. The service design is based on microservices and uses multiple design patterns. For example: single instance, factory, template, strategy, etc. The application of IOC and AOP is the most core part of system logic structure design. In high concurrency services, in order to ensure data security, we use IOC and factory construction methods to make each request process independent and ensure data independence between each request. The utilization of AOP provides a better global processing solution for the entire system, and provides a unified solution platform for connecting multiple banks due to different standards, solving the problem of not being able to effectively unify returns between multiple banks. 开发环境 工具版本描述IDEA2019.2JDK1.8MySQL5.75.7 技术框架 技术描述用途备注SpringBoot基础框架构建系统架构MyBatisORM框架构建数据层Maven项目管理项目管理ExecutorService线程池分批执行任务drools规则引擎简化if else这里仅简单实现用于入参校验tlog日志框架链路追踪保障长流程下日志能够快捷查询velocity模板引擎将bean转stringXml银行交互用xml格式的数据digester3XML解析工具包将stringXml转bean银行返回的数据格式为stringXml通过对应的解析模板将银行返回的stringXml类型的信息转成BeanfastjsonJSON解析工具将stringJson转bean bean转stringJsonhttpclienthttp工具银行通信 独立设计查询服务的前因后果 在较高服务请求下支付服务为了保障服务的高可用将查询整个链路都进行独立为支付提供更多的操作内存。同时简化支付服务的维护流程让支付服务代码结构更加的明了简洁。支付服务内部第二/三方基本都需要对接各大银行有些服务甚至接入的银行超过上千家对于服务的容错、统一各行标准有着高教的要求项目内部在不断的接入银行的情况各种特殊代码补全查漏层出不穷。导致项目出现横向臃肿以及可维护度大幅降低。为了支付服务更加的清晰结构更加有利于维护查询直接独立。重构查询服务。 系统整体技术结构如下 两大设计模式保障逻辑结构清晰代码标准一致 模板模式 查询服务接入各大银行在融合各银行请求和返回标准的时候代码风格不统一会导致项目混乱这里引入模板模式用一个接口定义具体执行方法所有请求都调用该接口接口被抽象类实现。使用抽象类定义具体模板方法所有银行的接入都需要重写对应的几个模板方法。同时在抽象类里面对前置如必传参数校验和公共补全部分进行补齐。具体实现如下 public interface ProcessHandler {ResultContext invoke(QueryContext queryContext); }public abstract class AbstractProcessHandler implements ProcessHandler {Overridepublic ResultContext invoke(QueryContext queryContext) {String queryFlag queryContext.getQueryFlag();log.info(开始执行查询查询类型为{}, queryFlag);// 参数校验checkParam(queryContext);// 公共参数补齐paramComplement(queryContext);// 负载均衡loadBalance(queryContext);// 执行查询if (QueryFlag.BALANCE.getDesc().equals(queryFlag)) {return this.queryBalance(queryContext);}if (QueryFlag.DETAIL.getDesc().equals(queryFlag)) {return this.queryDetail(queryContext);}return new ResultContext();}private void loadBalance(QueryContext queryContext) {SpringboardMachineAddrService springboardMachineAddrService SpringContextUtils.getBean(springboardMachineAddrService, SpringboardMachineAddrService.class);ListSpringboardMachineAddr springboardMachineAddrs springboardMachineAddrService.selectByBankCode(queryContext.getBankCode());if(CollectionUtils.isEmpty(springboardMachineAddrs)) {throw new BankException(未查询到对应的负载均衡地址信息!);}Random rand new Random();int randomIndex rand.nextInt(springboardMachineAddrs.size());SpringboardMachineAddr springboardMachineAddr springboardMachineAddrs.get(randomIndex);queryContext.setChannelId(springboardMachineAddr.getChannelId());queryContext.setMachineId(springboardMachineAddr.getMachineId());queryContext.setSignIp(springboardMachineAddr.getSignIp());queryContext.setSignPort(springboardMachineAddr.getSignPort());queryContext.setTradeIp(springboardMachineAddr.getTradeIp());queryContext.setTradePort(springboardMachineAddr.getTradePort());}private void paramComplement(QueryContext queryContext) {}private void checkParam(QueryContext queryContext) {log.info(开始执行规则引擎参数校验);KieSession kieSession SpringContextUtils.getBean(kieSession, KieSession.class);// 执行某个组的规则kieSession.getAgenda().getAgendaGroup(checkParam).setFocus();kieSession.insert(queryContext);// 执行所有规则kieSession.fireAllRules();// 执行指定规则 // kieSession.fireAllRules(new AgendaFilter() { // Override // public boolean accept(Match match) { // String ruleName match.getRule().getName(); // return Objects.equals(ruleName, checkQueryBalanceParamCurrencyCode); // } // });kieSession.dispose();log.info(规则引擎参数校验完成);}protected abstract ResultContext queryBalance(QueryContext queryContext);protected abstract ResultContext queryDetail(QueryContext queryContext);}public class ICBC10201 extends AbstractProcessHandler {Overrideprotected ResultContext queryBalance(QueryContext queryContext) {log.info(开始执行余额查询方法queryBalance被执行);// bean to string xmlString s javaBeanToStrXml(queryContext);log.info(bean to string xml success! xml content: {}, s);// to bank signString signMsg urj*^534faj;;String signStr sign(signMsg);log.info(sign success! signStr: {}, signStr);// send query content into bankString result sendQueryContentIntoBank(signStr JSON.toJSONString(queryContext));// string xml to beanBalanceResult balanceResult strXmlToJavaBean(bankDataModel(), queryContext);log.info(string xml to bean sccess! bean content: {}, JSON.toJSONString(balanceResult));ResultContext resultContext new ResultContext();resultContext.setStatus(0000);resultContext.setBalanceResult(balanceResult);return resultContext;}Overrideprotected ResultContext queryDetail(QueryContext queryContext) {log.info(开始执行明细查询方法queryDetail被执行);ResultContext resultContext new ResultContext();resultContext.setStatus(0000);return new ResultContext();}private String sendQueryContentIntoBank(String queryContent) {SocketConnector socketConnector new SocketConnector(127.0.0.1, 8081);return socketConnector.request(queryContent);}private String sign(String signMsg) {return HttpUtils.sendPostJson(signMsg, );}private String javaBeanToStrXml(QueryContext queryContext) {String ruleModeName templates/request- queryContext.getBankCode() - queryContext.getQueryFlag() .vm;ParserEngine parserEngine SpringContextUtils.getBean(parserEngine, ParserEngine.class);MapString, Object stringObjectMap JSON.parseObject(JSON.toJSONString(queryContext), new TypeReferenceMapString, Object() {});return parserEngine.beanToStringXml(ruleModeName, stringObjectMap);}private BalanceResult strXmlToJavaBean(String bankResultStrXml, QueryContext queryContext) {ParserEngine parserEngine SpringContextUtils.getBean(parserEngine, ParserEngine.class);return parserEngine.parseXml2Object(bankResultStrXml, response- queryContext.getBankCode() - queryContext.getQueryFlag());}private String bankDataModel() {return ;} }模板方法设计模式是行为型设计模式中的一种用在一个功能的完成需要经过一系列步骤这些步骤是固定的但是中间某些步骤具体行为是待定的在不同的场景中行为不同。这里定义了具体的整个流程需要执行的步骤但是对于具体的对接某个银行的方法还是留给了具体的银行来实现具体的UML图如下 策略模式 在查询服务中查询是我们最核心功能都是做了同一件事情但是接入的银行超过两个的时候查询的方式或者实现代码肯定有所不同在上面我们用模版方法模式定义了整个查询的流程基本构成但是具体的查询实现却没有办法对每一个银行进行统一。同时如果我们要走哪一个银行来查询仅有模版模式肯定会多出来很多的if else,这里我们用上策略模式就能完美的解决这些问题使用了一个接口就定义了整个查询让后我们的查询能够在各个银行之间有效的切换。具体实现代码如下 public interface ProcessHandler {ResultContext invoke(QueryContext queryContext); }public abstract class AbstractProcessHandler implements ProcessHandler {// 此处省略…… } Component(value 102) public class ICBC10201 extends AbstractProcessHandler {// 此处省略…… }// 策略体现 public class BankInquiryController {public ResultResultContext getDetail(RequestBody QueryContext queryContext) {ProcessHandler bean SpringContextUtils.getBean(queryContext.getBankCode(), ProcessHandler.class);ResultContext invoke bean.invoke(queryContext);} }策略模式在这里很有效的去除了if else同时对外暴露查询功能时提供了多银行之间自由切换的能力。具体实现UML图如下 日志的选型日志设计链路追踪 在高并发的情况下查询如果出现问题维护也是一个比较复杂的工作比如我们需要去排查某一次查询为什么出现了问题的时候这次出现问题的时间点内请求量很大那我们需要费一些时间来定位到我们当前日志是不是某一次请求在整个项目当中每次请求有对应很多日志的情况下那我们需要把每一行日志对应到这次出问题的请求上就需要很长的耗时。特别是公司内部如果都是微服务把系统拆分的很细的情况下那上下游的排查就会有很大的定位问题当然有的小伙伴就会想到使用SkyWalkingPinpoint等分布式追踪系统来解决并且这些系统通常都是无侵入性的同时也会提供相对友好的管理界面来进行链路Span的查询但是搭建分布式追踪系统还是需要一定的成本的所以本文要说的并不是这些分布式追踪系统而是一款简单、易用、几乎零侵入、适合中小型公司使用的日志追踪框架TLog。同时使用TLog也能有效的让我们内部的日志能够快速的定位到某一次请求的所有日志。TLog会自动的对日志进行打标签自动生成traceId贯穿你微服务的一整条链路在排查日志的时候可以根据traceId来快速定位请求处理的链路。在项目内的表现如下 TLog的优点 TLog不收集日志只在原来打印的日志上增强将请求链路信息traceId绑定到打印的日志上 操作日志的收集 对于一个查询来讲其实提到收集日志可能有人觉得这是多此一举当我们回顾这个服务的具体功能之后我们会发现与多家银行的交互这是对外统一提供服务的作为一个查询银行的服务我们需要有效的去保障对接的银行都能够有效的提供服务这样我们的操作日志就很有必要了。也能有效的去规避一些因为银行而导致的客户投诉。这里的操作日志收集还是用的老一套AOP注解来实现。SpringBoot中做日志的方法有很多比如用拦截器在拦截器中进行处理需要进行收集日志的方法同时也可以将日志存库但是这种方法可能会有一个弊端在拦截器中进行处理日志的话对于请求量过大的系统或者处理次数过多的以及并发过高的项目来说都是一个可怕的性能消耗。aop做日志处理的关键原因量级小简单同时利用线程池异步的方式有效的环节高并发的请求日志记录问题 定义的注解如下 Documented Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface OperationLog {/*** 执行方法功能描述*/String desc() default ;/*** 1:发送, 0:接收*/String type() default 0; }在银行查询服务中数据最关键的不是落库而是与银行交互然后处理数据最终统一返回所以我们不仅要记录被请求的日志还需要记录向外发送的请求日志已报账发送的请求都是符合规范的。这也是为什么需要记录操作日志的主要原因之一 AOP的实现如下 package com.echo.bank.framework.aspect;import com.alibaba.fastjson.JSONObject; import com.echo.bank.enums.StatusCode; import com.echo.bank.framework.ioc.SpringContextUtils; import com.echo.bank.framework.thread.ExecutorServiceUtil; import com.echo.bank.pojo.OperateLog; import com.echo.bank.service.OperateLogService; import com.echo.bank.utils.RequestUtils; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Date; import java.util.concurrent.ExecutorService;/*** author echo* version 1.0* Create by 2023/5/25 9:19*/ Slf4j Aspect Component public class OperationLogAspect {Autowiredprivate OperateLogService operateLogService;/*** 扫描使用这个注解的方法*/Pointcut(annotation(com.echo.bank.framework.aspect.OperationLog))public void logPointCut() {}Around(logPointCut())public Object around(ProceedingJoinPoint point) throws Throwable {Date beginTime new Date();String result null;String status null;try {Object obj point.proceed();if (obj ! null) {result JSONObject.toJSONString(obj);}status StatusCode.SUCCESS.getCode() ;return obj;} catch (Exception e) {//请求执行出错result e.getMessage();status StatusCode.ERROR.getCode() ;throw e;} finally {saveLog(point, beginTime, new Date(), result, status);}}/*** 保存日志*/private void saveLog(ProceedingJoinPoint joinPoint, Date beginTime, Date endTime, String result, String status) {OperateLog operateLog new OperateLog();try {MethodSignature signature (MethodSignature) joinPoint.getSignature();Method method signature.getMethod();OperationLog annotation method.getAnnotation(OperationLog.class);if (annotation ! null) {operateLog.setDesc(annotation.desc());operateLog.setType(annotation.type());}if (joinPoint.getArgs() ! null) {try {operateLog.setParamContext(JSONObject.toJSONString(joinPoint.getArgs()));} catch (Exception e) {log.info(解析并记录请求参数出现错误! msg: {}, e.getMessage());}}operateLog.setCreateUser(echo);operateLog.setCreateTime(beginTime);operateLog.setUpdateTime(endTime);operateLog.setStatus(status);operateLog.setResultContext(result);setRequestIp(operateLog);} catch (Exception e) {log.info(记录请求日志时初始化日志对象报错! msg: {}, e.getMessage());}ExecutorService instance ExecutorServiceUtil.getInstance();instance.execute(new OperationLogSaveThread(operateLogService, operateLog));}private void setRequestIp(OperateLog operateLog) {try {RequestUtils requestUtils SpringContextUtils.getBean(requestUtils, RequestUtils.class);HttpServletRequest httpServletRequest requestUtils.getHttpServletRequest();String requestIp requestUtils.getRequestIp(httpServletRequest);operateLog.setRequestIp(requestIp);} catch (Exception e) {// 向外请求的时候使用该IPoperateLog.setRequestIp(127.0.0.1);}}}这里仅仅使用AOP还不能有效的收集日志这个收集日志的操作还会让我们的系统性能下降导致查询缓慢。所以我们还需要异步的去执行这样就能有效的保障日志能被有效的记录同时不影响系统问题。线程池引入如下 Slf4j public class OperationLogSaveThread implements Runnable {private OperateLogService operateLogService;private OperateLog operateLog;public OperationLogSaveThread(OperateLogService operateLogService, OperateLog operateLog) {this.operateLogService operateLogService;this.operateLog operateLog;}Overridepublic void run() {log.info(开始异步执行记录请求日志!);try {operateLogService.insert(operateLog);} catch (Exception e) {log.info(异步记录日志出现报错msg: {}, e.getMessage());}} }public class ExecutorServiceUtil {private static final Logger logger LoggerFactory.getLogger(ExecutorServiceUtil.class);private static ThreadPoolExecutor executorService;private static final int INT_CORE_POOL_SIZE 50;private static final int MAXIMUM_POOL_SIZE 100;private static final int KEEP_ALIVE_TIME 60;private static final int WORK_QUEUE_SIZE 2000;static {executorService new ThreadPoolExecutor(INT_CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue(WORK_QUEUE_SIZE),new CommunityThreadFactory(),new DataMigrationRejectedExecutionHandler());}private ExecutorServiceUtil() {if (executorService ! null) {throw new BankException(Reflection call constructor terminates execution!!!);}}public static ExecutorService getInstance() {logger.info(queue size: {}, executorService.getQueue().size());logger.info(Number of active threads: {}, executorService.getActiveCount());logger.info(Number of execution completion threads: {}, executorService.getCompletedTaskCount());return executorService;} }public class DataMigrationRejectedExecutionHandler implements RejectedExecutionHandler {Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {try {// 核心改造点由blockingqueue的offer改成put阻塞方法executor.getQueue().put(r);} catch (InterruptedException e) {e.printStackTrace();}}}public class CommunityThreadFactory implements ThreadFactory {private static final AtomicInteger poolNumber new AtomicInteger(1);private final ThreadGroup group;private final AtomicInteger threadNumber new AtomicInteger(1);private final String namePrefix;CommunityThreadFactory() {SecurityManager s System.getSecurityManager();group (s ! null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();namePrefix dataMigration- poolNumber.getAndIncrement() -thread-;}/*** 创建线程** param r* return*/Overridepublic Thread newThread(Runnable r) {Thread t new Thread(group, r, namePrefix threadNumber.getAndIncrement(), 0);if (t.isDaemon())t.setDaemon(false);if (t.getPriority() ! Thread.NORM_PRIORITY)t.setPriority(Thread.NORM_PRIORITY);return t;} }这样的实现既保障了被调用的时候所有请求的日志都记录了同时也保障了发送出去的请求也能有效记录。具体图示 规则引擎 规则引擎的引入主要的原因就两个1、if else过多用以简化代码 2、小试牛刀 规则引擎最大的作用就是灵活的将规则和业务剥离,解决了灵活变更规则不影响具体业务的难点。选择Drools的理由Drools 是用 Java 语言编写的具有一个易于访问企业策略、易于调整以及易于管理的开源业务规则引擎其基于CHARLES FORGY’S的RETE算法符合业内标准速度快且效率高 引入的Drools版本如下 !--drools规则引擎-- dependencygroupIdorg.drools/groupIdartifactIddrools-core/artifactIdversion7.6.0.Final/version /dependency dependencygroupIdorg.drools/groupIdartifactIddrools-compiler/artifactIdversion7.6.0.Final/version /dependency dependencygroupIdorg.drools/groupIdartifactIddrools-templates/artifactIdversion7.6.0.Final/version /dependency dependencygroupIdorg.kie/groupIdartifactIdkie-api/artifactIdversion7.6.0.Final/version /dependency dependencygroupIdorg.kie/groupIdartifactIdkie-spring/artifactIdversion7.6.0.Final/version /dependencyDrools规则引擎具体在项目中做了件什么事情 在我们的代码中有很多的参数校验代码 if(checkParam1) throw new BankExcption(); if(checkParam2) throw new BankExcption(); if(checkParam3) throw new BankExcption(); if(checkParam4) throw new BankExcption(); if(checkParam5) throw new BankExcption(); if(checkParam6) throw new BankExcption(); if(checkParam7) throw new BankExcption();这类代码不仅在我们接口被调用的时候会出现也会出现在银行返回的时候对某些特定数据校验判断当前请求是否成功。如果业务发生变更或者银行内部变更我们这边就需要重新编码然后上线。但是drools的引入就完美的解决了这一问题我们可以重新编写规则文件替换规则文件之后重新加载即可不需要重新发布系统。同时简化了if else.使用了drools的具体代码如下 private void checkParam(QueryContext queryContext) {log.info(开始执行规则引擎参数校验);KieSession kieSession SpringContextUtils.getBean(kieSession, KieSession.class);// 执行某个组的规则kieSession.getAgenda().getAgendaGroup(checkParam).setFocus();kieSession.insert(queryContext);// 执行所有规则kieSession.fireAllRules();kieSession.dispose();log.info(规则引擎参数校验完成); }解决并发代码的时间安全问题 对接银行或者说支付行业的整体技术更新是比较缓慢的有可能有很多地方还在用ext等老古董框架。在这种前提下 很多项目的内部往往总是有老框架固定的架构规范导致内部代码结构难以真正的去修改。这里直接全部打破使用全新的技术来重构项目这样不仅解决了老框架的限制同时也为我们重构提供了更好的环境。当然新框架带来的问题也会随之而来比如并发的处理在老框架中很多static变量的兼容虽然拜读了很多而且也有很完美的处理方案去避免并发但是如果将这些东西搬到新框架就会带来很多的数据安全问题。所以这里采用了单次请求内数据全部独立的方式。 其核心思想就是每次请求当前请求的所有数据都不共享。实现的方式也是比较简单的直接使用了Spring Bean的作用域来解决这类问题。 核心代码如下 Scope(value prototype) Component(value 102) public class ICBC10201 extends AbstractProcessHandler {…… } ProcessHandler bean SpringContextUtils.getBean(queryContext.getBankCode(), ProcessHandler.class);每一次请求都使用一个独立的对象对象内无公共变量保障整个请求链上没有对其他的请求暴露出来的数据真正有效的规避数据安全风险。 总结 解决高并发痛点同时优化设计为对接多个银行提供统一标准。也许还有更多的优秀方案能够完美解决这些痛点当前项目也不一定完美对于技术的使用可能也会有些不尽人意。这里并不完美希望更多的大佬们斧正。
http://www.dnsts.com.cn/news/100057.html

相关文章:

  • 网站优化怎样提高网站用户体验wordpress页面转文章
  • 做网站设计怎么提升网站原型是以下哪层设计的结果
  • 海珠营销型网站建设wordpress 灯箱
  • 做一个门户网站多少钱建站平台与自己做网站
  • 想做网站找哪个公司好网站开发的实训周的实训过程
  • 做产品网站设计应该注意什么百度学术搜索
  • 桂林北站到两江机场有多远在线制作网站地图
  • 小学生做的网站上市公司集团网站建设
  • 西部数码网站管理助手 d盘免费个人网页制作网站
  • 旅游网站排名相关推荐网站制作公司广州
  • 焦作网站建设哪家便宜网站开发需求分析编写目的
  • 网站建设策划书结束语网站在线预约模板
  • 阿里云淘宝客网站建设教程李光辉:营销型企业网站建设的指导思想是什么?
  • 西安网站建设设计的好公司专业做网站公司24小时接单
  • 佛山 做网站公司有哪些汕尾市住房和城建设局网站
  • 山东省住房建设厅网站大数据网站开发工程师
  • wordpress接入微信支付宝网站设计 网站开发 优化
  • 做国外进口衣服的网站网页制作培训心得
  • 网站开发的概要设计模板wordpress rss 订阅
  • 建设网站前需考虑哪些问题oa连接到网站的链接怎么做
  • 著名建筑设计网站成都住建局官网电话查询
  • 免费做网站的平台wordpress自动推送工具代码
  • 网站建设的内容wordpress视差插件
  • 张家界网站建设企业三网合一营销型全网站
  • 织梦网站后台密码忘记现在建个企业网站要多少钱
  • 网站开发安装环境免费订单管理app
  • 做网站有哪些注意事项网上开店教程
  • 几何背景生成网站手机网站设计尺寸毫米
  • 广东在线网站建设p2p种子网站建设
  • 网站 代备案怀化网站建设有哪些