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

如何做网站发产品销售wordpress删除插件

如何做网站发产品销售,wordpress删除插件,网站如何进行备案,天猫出售零、前言 ​ 业务接口幂等问题是在开发中遇到的#xff0c;如果对业务接口代码不进行幂等控制#xff0c;并且在前端没有对请求进行限制的情况下#xff0c;可能会出现多次对接口调用#xff0c;导致错误异常的发生。就上述情况#xff0c;对PIGX自带的业务接口幂等实现进…零、前言 ​ 业务接口幂等问题是在开发中遇到的如果对业务接口代码不进行幂等控制并且在前端没有对请求进行限制的情况下可能会出现多次对接口调用导致错误异常的发生。就上述情况对PIGX自带的业务接口幂等实现进行了相关的学习相关具体内容可以参考官方文档本文章只是作为学习笔记。 一、接口幂等概念 幂等性原本是数学上的概念在数学中表示对同一操作的多次执行产生的结果与仅执行一次的结果相同。 用在接口上就可以理解为同一个接口多次发出同一个请求必须保证操作只执行一次。 调用接口发生异常并且重复尝试时总是会造成系统所无法承受的损失所以必须阻止这种现象的发生。 实现接口幂等性通常需要考虑以下几个方面 ①请求参数确保接口请求中的所有参数都是唯一的不能重复。比如在生成订单编号时可以使用时间戳或随机数等方式来保证唯一性 ②接口设计接口应该设计为“幂等性操作”即无论调用多少次结果都应该相同。例如修改密码的接口应该设计为只能修改一次多次修改的请求会被忽略。 ③数据库操作如果接口需要对数据库进行操作需要确保每次操作都是幂等的。可以通过添加唯一索引或使用乐观锁等方式来避免重复操作 ④错误处理当请求出现异常时接口应该处理异常并返回相应的错误信息。例如当订单已经存在时接口应该返回订单已存在的错误信息而不是尝试再次创建订单 二、PIGX中具体实现方式 1、依赖的引入 情况① 如果是其他项目可以从中央仓库中引入PIGX提供的实现接口幂等的依赖 dependencygroupIdcom.pig4cloud.plugin/groupIdartifactIdidempotent-spring-boot-starter/artifactIdversion0.4.0/version/dependency引入后的包内容如下所示 情况② 如果项目中原先使用的就是pigx框架那么在基础平台中的pigx-common-idempotent就已经自带 我们只需要引入pigx-common服务就可以将实现接口幂等的依赖引入 2、Redis的配置 在配置文件中进行Redis的配置设置redis存为我们的缓存中间件 通过如下配置Spring框架会自动启用Redis作为缓存并将Redis服务器的连接信息配置为指定的主机名和端口号 spring:cache:type: redisdata:redis:host: 127.0.0.1port: 6379①spring.cache 对应的配置类是CacheProperties这个配置项用来指定缓存的类型。在上述示例中我们将缓存类型设置为Redis表示使用Redis作为缓存。 ② spring.data.redis这个配置项用来指定连接Redis所需的参数。在上述示例中我们指定了Redis服务器的主机名为127.0.0.1端口号为6379 3、具体使用示例 pigx主要是通过AOP去实现业务接口幂等其最核心的就是其Idempotent注解其使用示例如下 Idempotent(key #demo.username, expireTime 3, info 请勿重复查询) GetMapping(/test) public String test(Demo demo) {return success; }通过使用Idempotent注解当多次请求进入test方法时框架会根据key生成的唯一标识符判断是否已经执行过相同的请求。如果已经执行过则直接返回之前的结果如果没有执行过则继续执行业务逻辑。 这样可以避免重复查询或操作带来的额外开销并确保系统在重复请求时的一致性和可靠性。 三、原理分析 1、目录结构说明 对于PIGX自带的幂等插件idempotent的目录结构如下所示 各目录内容说明如下 - annotation // 关于Idempotent注解的声明- Idempotent - aspect // 关于AOP的切面方法- IdempotentAspect - exception // 自定义异常类- IdempotentException - expression // sqel表达式的解析工具- ExpressionResolver- KeyResolver - IdempotentAutoConfiguration //幂等插件配置文件- org.springframework.boot.autoconfigure.AutoConfiguration.imports //springboot自动配置文件2、idempotent 注解 配置详细说明 2.1、idempotent注解源码 Inherited Target(ElementType.METHOD) Retention(value RetentionPolicy.RUNTIME) public interface Idempotent {/*** 幂等操作的唯一标识使用spring el表达式 用#来引用方法参数* return Spring-EL expression*/String key() default ;/*** 有效期 默认1 有效期要大于程序执行时间否则请求还是可能会进来* return expireTime*/int expireTime() default 1;/*** 时间单位 默认s* return TimeUnit*/TimeUnit timeUnit() default TimeUnit.SECONDS;/*** 提示信息可自定义* return String*/String info() default 重复请求请稍后重试;/*** 是否在业务完成后删除key true:删除 false:不删除* return boolean*/boolean delKey() default false;}①key key参数用于生成幂等性的唯一标识符用于识别不同的请求或操作.具体来说当多个请求或操作需要进行幂等性处理时每个请求或操作需要有一个唯一的标识符来区分。这个标识符可以是请求参数、请求头信息、用户ID等等根据实际场景而定。 在示例中我们使用了SpEL表达式#demo.username作为唯一标识符表示使用Demo对象的username属性作为标识符。也就是说当多个请求的demo对象的username属性相同时这些请求被认为是重复的并且只有第一个请求会被执行后续的请求会直接返回上次执行的结果。 ②expireTime 幂等性键过期时间单位为秒 ③timeUnit 用于设置幂等性键过期时间的单位默认是秒 ④info 用于描述幂等性操作的提示信息在某些情况下当出现重复请求时我们需要向客户端返回一些提示信息告知客户端该请求已经被处理过不能重复提交。因此通过设置info参数我们可以自定义提示信息以便更好地向客户端反馈信息。 ⑤delKey 是否在业务完成后删除key true:删除 false:不删除 默认为false。 3、IdempotentAspect 3.1、IdempotentAspect源码及相关解析 /*** The Idempotent Aspect** author ITyunqing*///Spring框架中用于声明切面Aspect的注解 //通过在类上添加Aspect注解将其标识为一个切面类。切面类中可以包含各种通知advice和切点pointcut用于定义切面的具体行为和拦截规则 Aspect public class IdempotentAspect {//通过slf4j的LoggerFactory日志工厂创建一个Logger对象用于在IdempotentAspect类中记录日志信息private static final Logger LOGGER LoggerFactory.getLogger(IdempotentAspect.class);//创建一个名为THREAD_CACHE的私有的静态常量ThreadLocal对象//指定了泛型类型为MapString, Object表示该ThreadLocal对象存储的值是一个键值对的映射//通过ThreadLocal类的withInitial方法传入一个Supplier接口实例该Supplier接口用于提供初始值。在这里使用HashMap::new作为Supplier接口的实现创建一个新的HashMap作为初始值。private static final ThreadLocalMapString, Object THREAD_CACHE ThreadLocal.withInitial(HashMap::new);//定义了一个常量字符串变量RMAPCACHE_KEY表示缓存中的键名用于在缓存中存储幂等性相关的数据private static final String RMAPCACHE_KEY idempotent;//定义了一个常量字符串变量KEY表示幂等性数据的键名用于在幂等性相关的数据中标识唯一的幂等性请求private static final String KEY key;//定义了一个常量字符串变量DELKEY表示删除缓存数据的键名用于在缓存中标识需要删除的幂等性数据private static final String DELKEY delKey;//通过spring的自动装配功能将Redisson对象注入到当前类中Autowiredprivate Redisson redisson;//通过spring的自动装配功能将KeyResolver对象注入到当前类中Autowiredprivate KeyResolver keyResolver;//通过spring的自动装配功能将一个OptionalKeyStrResolver对象注入到当前类中//KeyStrResolver的一个可选注入项它可以包含一个非空的值也可以为空Autowiredprivate OptionalKeyStrResolver keyStrResolverOptional;//该注解表示定义一个切入点使用annotation表达式来匹配被指定注解标记的方法。在这里切入点匹配的条件是被com.pig4cloud.pigx.common.idempotent.annotation.Idempotent注解标记的方法Pointcut(annotation(com.pig4cloud.pigx.common.idempotent.annotation.Idempotent))//这是一个空方法体作为切入点的定义。它没有任何实际的逻辑操作只是用来定义一个切入点的名称public void pointCut() {}//切面的前置通知Before它在切入点方法执行之前被调用Before(pointCut())public void beforePointCut(JoinPoint joinPoint) {//通过RequestContextHolder类可以获取当前线程的RequestAttributes对象//将获取到的RequestAttributes对象强制转换为ServletRequestAttributes类型。ServletRequestAttributes是RequestAttributes的子接口扩展了一些与Servlet请求相关的方法和属性ServletRequestAttributes requestAttributes (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();//获取当前请求的 HttpServletRequest 对象HttpServletRequest request requestAttributes.getRequest();//将 joinPoint 转换为 MethodSignature 对象MethodSignature signature (MethodSignature) joinPoint.getSignature();//通过 MethodSignature 对象获取到当前方法的 Method 对象Method method signature.getMethod();//如果当前方法上没有使用注解Idempotent则return返回if (!method.isAnnotationPresent(Idempotent.class)) {return;}//如果当前方法上有使用注解Idempotent获得该注解信息//通过调用 getAnnotation() 方法并传入 Idempotent.class 作为参数可以获取到该方法上的 Idempotent 注解的实例对象Idempotent idempotent method.getAnnotation(Idempotent.class);String key;// 若没有配置 幂等 标识编号则使用 url 参数列表作为区分作为生成幂等性的唯一标识符if (!StringUtils.hasLength(idempotent.key())) {String url request.getRequestURL().toString();String argString Arrays.asList(joinPoint.getArgs()).toString();key url argString;}else {// 使用jstl 规则区分具体会在expression中进行讲解key keyResolver.resolver(idempotent, joinPoint);}//StrUtil.COLON字符串常量冒号 {code :}//如果keyStrResolverOptional存在则设置key的值为KeyStrResolver调用extract(key, StrUtil.COLON)方法的结果值//即在原先key的基础的前面加上租户idif (keyStrResolverOptional.isPresent()) {key keyStrResolverOptional.get().extract(key, StrUtil.COLON);}//获取注解idempotent的有效期long expireTime idempotent.expireTime();//获取注解idempotent的提示信息String info idempotent.info();//获取注解idempotent的时间单位TimeUnit timeUnit idempotent.timeUnit();//获取注解idempotent的是否在业务完成后删除keyboolean delKey idempotent.delKey();// do not need check null//RMapCache 是 Redisson 提供的一个接口用于表示一个带有过期时间的分布式 Map 缓存RMapCache 继承自 RMap 接口提供了一些额外的方法可以设置和获取缓存的过期时间以及进行相关的缓存操作//通过 redisson 对象调用 getMapCache(RMAPCACHE_KEY) 方法传入参数 RMAPCACHE_KEY 来获取对应的缓存对象//RMAPCACHE_KEY 是表示缓存的键值可能是一个字符串或其他适当的类型RMapCacheString, Object rMapCache redisson.getMapCache(RMAPCACHE_KEY);//获取当前系统的时间并将其转化为字符串将其中的T替换为“ ”String value LocalDateTime.now().toString().replace(T, );Object v1;//如果当前对应的键key已经有缓存对象则抛出异常异常信息为[idempotent]: infoif (null ! rMapCache.get(key)) {// had storedthrow new IdempotentException([idempotent]: info);}//使用 synchronized 关键字来对代码块进行同步确保在多线程环境下的安全性//通过 synchronized(this) 来锁定当前对象以保证同一时间只有一个线程可以进入这个代码块synchronized (this) {//将键值对放入缓存中//该方法会返回之前关联到 key 上的值如果该值为 null则表示插入成功v1 rMapCache.putIfAbsent(key, value, expireTime, timeUnit);//如果 v1 不为 null即 putIfAbsent 返回了一个非空值说明之前已经存在相同的键值对此时抛出一个 IdempotentException 异常表示幂等性校验失败if (null ! v1) {throw new IdempotentException([idempotent]: info);}//如果 v1 为 null表示 putIfAbsent 成功插入了新的键值对记录一条日志信息并打印当前存储的 key、value、过期时间等信息else {LOGGER.info([idempotent]:has stored key{},value{},expireTime{}{},now{}, key, value, expireTime,timeUnit, LocalDateTime.now().toString());}}//在当前线程中使用 ThreadLocal 来创建一个线程本地的 Map 对象并将 key 和 delKey 存储到其中//通过 THREAD_CACHE.get() 方法获取当前线程绑定的 MapString, Object 对象存储到 map 变量中MapString, Object map THREAD_CACHE.get();//将键值对 KEY 和对应的变量 key 存储到 map 中map.put(KEY, key);//将键值对 DELKEY 和对应的变量 delKey 存储到 map 中map.put(DELKEY, delKey);}//切面的后置通知After它在切入点方法执行之后被调用After(pointCut())public void afterPointCut(JoinPoint joinPoint) {//从当前线程中获取之前存储的 map 对象并将其存储到 map 变量中MapString, Object map THREAD_CACHE.get();//判断 map 是否为空如果为空则直接返回不执行后续操作if (CollectionUtils.isEmpty(map)) {return;}//获取一个名为 RMAPCACHE_KEY 的 RMapCacheObject, Object 实例RMapCacheObject, Object mapCache redisson.getMapCache(RMAPCACHE_KEY);//判断该 RMapCache 是否为空。如果为空则直接返回不执行后续操作if (mapCache.size() 0) {return;}//从 map 中获取 key 并将 key 转换为字符串类型保存到 key 变量中String key map.get(KEY).toString();//从 map 中获取 delKey 将 delKey 强制转换为布尔型保存到 delKey 变量中boolean delKey (boolean) map.get(DELKEY);//delKey为真则调用 mapCache.fastRemove(key) 方法从 RMapCache 中删除指定的键值对并记录一条日志信息if (delKey) {mapCache.fastRemove(key);LOGGER.info([idempotent]:has removed key{}, key);}//从当前线程中删除 map 对象。这样做是为了避免在下次请求时出现数据混淆的情况THREAD_CACHE.remove();}} 3.2、相关内容补充 ① ThreadLocal ThreadLocal 是一个 Java 类它提供了线程局部变量的支持。每个线程都可以独立地访问和修改自己的线程局部变量而不会干扰其他线程的变量。 使用 ThreadLocal 可以解决多线程并发访问共享变量时可能出现的线程安全问题。通过将数据存储在 ThreadLocal 对象中每个线程都有自己独立的副本线程之间的数据互不干扰。 在使用 ThreadLocal 时通常的做法是创建一个 ThreadLocal 对象并使用 set() 方法来设置当前线程的变量值使用 get() 方法来获取当前线程的变量值。可以通过 remove() 方法来清除当前线程的变量值。 ② Redisson Redisson是一个Java Redis客户端提供了简单易用的API接口支持分布式锁、分布式集合、分布式对象等功能。它基于Redis客户端Jedis、Lettuce封装了一系列的分布式操作接口使得Java开发者可以方便地使用Redis实现分布式应用程序。 Redisson的主要特点如下 对Redis的高效封装Redisson对Redis客户端进行了高效的封装提供了简单易用的API接口使得Java开发者可以方便地实现复杂的分布式应用程序。分布式锁Redisson提供了分布式锁的实现支持可重入锁、公平锁、联锁、红锁等多种类型的锁并且保证了锁的正确性和高可用性。分布式集合Redisson支持分布式Set、List、Queue、Deque、Map、SortedSet等集合类型的操作可以方便地实现分布式计算、消息队列等功能。分布式对象Redisson支持分布式ExecutorService、ScheduledExecutorService、Semaphore、CountDownLatch、ReadWriteLock等对象类型的操作可以方便地实现分布式任务调度、并发控制等功能。 ③MethodSignature 在 Spring AOP 中joinPoint 是一个连接点即程序执行过程中能够插入额外代码的点它封装了当前方法的相关信息包括方法名、参数等。 MethodSignature 是 JoinPoint 的一个子类它用于表示方法签名即方法名和参数类型等信息。通过 MethodSignature 对象我们可以获取到当前方法的 Method 对象进而获取该方法的返回值类型、参数列表等信息。 ④KeyStrResolver 其是一个接口源码如下 public interface KeyStrResolver {/*** 字符串加工* param in 输入字符串* param split 分割符* return 输出字符串*/String extract(String in, String split);/*** 字符串获取* return 模块返回字符串*/String key(); } 其具体实现类TenantKeyStrResolver的源码如下所示 /*** author lengleng* date 2020/9/29* p* 租户字符串处理方便其他模块获取*/ public class TenantKeyStrResolver implements KeyStrResolver {/*** 传入字符串增加 租户编号:in* param in 输入字符串* param split 分割符* return*/Overridepublic String extract(String in, String split) {return TenantContextHolder.getTenantId() split in;}/*** 返回当前租户ID* return*/Overridepublic String key() {return String.valueOf(TenantContextHolder.getTenantId());}}4、IdempotentException自定义异常类 4.1、IdempotentException自定义异常类源码 //RuntimeException 运行时异常的实现类 public class IdempotentException extends RuntimeException {//无参构造器public IdempotentException() {super();}//有参构造器1public IdempotentException(String message) {super(message);}//有参构造器2public IdempotentException(String message, Throwable cause) {super(message, cause);}//有参构造器3public IdempotentException(Throwable cause) {super(cause);}//有参构造器4protected IdempotentException(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}} 5、唯一标志处理器KeyResolver 5.1、 KeyResolver源码及解析 其中定义了一个解析处理key的抽象方法 public interface KeyResolver {/*** 解析处理 key* param idempotent 接口注解标识* param point 接口切点信息* return 处理结果*/String resolver(Idempotent idempotent, JoinPoint point);} 5.2、ExpressionResolver源码及其解析 ExpressionResolver是KeyResolver的实现类其主要的作用就是进行key的抽取优先根据 spel 处理 /*** author lengleng* p* 默认key 抽取 优先根据 spel 处理* date 2020-09-25*/ public class ExpressionResolver implements KeyResolver {//创建一个 SpEL 表达式解析器实例SpelExpressionParserprivate static final SpelExpressionParser PARSER new SpelExpressionParser();//创建一个 LocalVariableTableParameterNameDiscoverer 对象用于获取方法参数的名称private static final LocalVariableTableParameterNameDiscoverer DISCOVERER new LocalVariableTableParameterNameDiscoverer();//重写父类keyResolver的resolver方法Overridepublic String resolver(Idempotent idempotent, JoinPoint point) {//通过 point.getArgs() 获取方法的参数数组Object[] arguments point.getArgs();// 获取方法参数的名称数组String[] params DISCOVERER.getParameterNames(getMethod(point));//创建一个 StandardEvaluationContext 对象用于提供表达式求值的上下文环境StandardEvaluationContext context new StandardEvaluationContext();//如果方法参数的名称数组不为空且长度大于 0则遍历参数名称数组将参数名和对应的参数值存储到 context 中if (params ! null params.length 0) {for (int len 0; len params.length; len) {context.setVariable(params[len], arguments[len]);}}//使用 PARSER.parseExpression(idempotent.key()) 解析 idempotent.key() 字符串为一个 SpEL 表达式并返回一个 Expression 对象Expression expression PARSER.parseExpression(idempotent.key());//调用 Expression 对象的 getValue() 方法传入 context 和期望的结果类型 String.class以获取表达式的求值结果return expression.getValue(context, String.class);}/*** 根据切点解析方法信息* param joinPoint 切点信息* return Method 原信息*/private Method getMethod(JoinPoint joinPoint) {//通过 joinPoint.getSignature() 获取切入点的方法签名然后将其转换为 MethodSignature 类型并赋值给 signature 变量MethodSignature signature (MethodSignature) joinPoint.getSignature();//从方法签名中获取切入点方法并赋值给 method 变量Method method signature.getMethod();//判断切入点方法所在的类是否是一个接口。如果是接口则执行以下代码块if (method.getDeclaringClass().isInterface()) {try {//通过 joinPoint.getTarget().getClass() 获取切入点目标对象的类并使用该类调用 getDeclaredMethod() 方法来获取具体实现类中与切入点方法相同名称和参数类型的方法并将得到的方法赋值给 method 变量method joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),method.getParameterTypes());}catch (SecurityException | NoSuchMethodException e) {//如果在获取具体实现类中的方法时抛出了 SecurityException 或 NoSuchMethodException 异常会捕获并抛出一个具有该异常的 RuntimeException 异常throw new RuntimeException(e);}}//返回获取到的方法对象return method;}}6、幂等插件配置文件IdempotentAutoConfiguration 6.1、配置文件源码 //标记该类为配置类 //proxyBeanMethods false表示Spring 将不会为配置类的方法生成代理对象这样可以避免额外的代理开销 Configuration(proxyBeanMethods false) //指定在特定自动配置类RedisAutoConfiguration之后进行自动配置 AutoConfigureAfter(RedisAutoConfiguration.class) public class IdempotentAutoConfiguration {/*** 生成IdempotentAspect切面类的bean实例* 切面 拦截处理所有 Idempotent* return Aspect*/Beanpublic IdempotentAspect idempotentAspect() {return new IdempotentAspect();}/*** 生成key 解析器的bean实例* key 解析器* return KeyResolver*/BeanConditionalOnMissingBean(KeyResolver.class)public KeyResolver keyResolver() {return new ExpressionResolver();}}7、AutoConfiguration.imports 7.1、源码 com.pig4cloud.pigx.common.idempotent.IdempotentAutoConfiguration7.2、作用 org.springframework.boot.autoconfigure.AutoConfiguration.imports 是一个 Spring Boot 的属性用于指定需要在自动配置类中导入的其他配置类。 在 Spring Boot 中自动配置是一种机制它根据应用程序的依赖和配置信息自动创建并配置相应的 Bean 实例。而 AutoConfiguration.imports 属性则提供了一种扩展机制允许开发人员在自动配置类中导入其他配置类以实现更为灵活和定制化的自动配置。 具体来说AutoConfiguration.imports 属性是一个字符串数组每个元素表示一个需要导入的配置类的全限定名。在自动配置过程中Spring Boot 会自动加载这些配置类并将它们合并到当前上下文中以实现更全面的自动配置。 8、补充 1.请求开始前根据key查询 查到结果报错 | 未查到结果存入key-value-expireTime 2.请求结束后直接删除key 不管key是否存在直接删除 是否删除可配置 3.expireTime过期时间防止一个请求卡死会一直阻塞超过过期时间自动删除 过期时间要大于业务执行时间需要大概评估下; 4.此方案直接切的是接口请求层面。 5.过期时间需要大于业务执行时间否则业务请求1进来还在执行中前端未做遮罩或者用户跳转页面后再回来做重复请求2在业务层面上看结果依旧是不符合预期的。 6.建议delKey false。即使业务执行完也不删除key强制锁expireTime的时间。预防5的情况发生。 7.实现思路同一个请求ip和接口相同参数的请求在expireTime内多次请求只允许成功一次。 8.页面做遮罩数据库层面的唯一索引先查询再添加等处理方式应该都处理下。 9.此注解只用于幂等不用于锁100个并发这种压测会出现问题在这种场景下也没有意义实际中用户也不会出现1s或者3s内手动发送了50个或者100个重复请求,或者弱网下有100个重复请求
http://www.zqtcl.cn/news/531886/

相关文章:

  • 我需要把网站做东莞营销外包公司
  • 平台型网站制作住房和城乡建设网站 上海
  • 个人网站可以如果做淘宝客WordPress用quic
  • 建设网站要什么广告设计专业有什么可从事的工作
  • 网站开发上传视频教程济南网站建站模板
  • 深圳市城乡和建设局网站有哪些ui的设计网站
  • vs2010网站开发源码音乐网站设计怎么做
  • 长沙县政务网站网站色彩的应用
  • 成都哪家公司做网站好百度关键词搜索热度
  • 单位写材料素材网站深圳建设外贸网站
  • 做网站如何赚钱景区网站建设的意义
  • 常用网站开发语言的优缺点口碑好的网站建设哪家好
  • 昆明找工作哪个网站好企业管理咨询包括哪些
  • 网站建设需要c语言吗ui设计培训大概多少钱
  • 门户网站开发语言响应式网站 图片尺寸奇数
  • 域外网站是山东城乡建设厅网站
  • 广州网站建设公司乐云seo598学创杯营销之道模板
  • 中国十大黑科技黑帽seo技巧
  • 阿里巴巴的网站建设与维护北京发布会直播回放
  • 深圳技术支持 骏域网站建设微信官方公众号
  • dns解析失败登录不了网站推广网站平台有哪些
  • 网站建设许可证网页设计找工作
  • 想通过网站卖自己做的东西网络公司如何建网站
  • 商务网站开发实训任务书网站建设验收合格确认书
  • 手机网站百度关键词排名查询wordpress 敏感词
  • 网站分页导航常州网约车哪个平台最好
  • 上海 网站开发设计方案参考网站
  • 网站一键备案外呼电销系统
  • 淘宝客购物网站源码网站项目开发的一般流程
  • 如何更改公司网站内容网站开发需要哪些文档