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

网站制作xiu021网站生成app工具

网站制作xiu021,网站生成app工具,程序源代码下载网站,做网站用母版页不好么文章目录 OGNL 介绍OGNL 使用场景- ognl- 主要功能- 注意事项- Ognl类的主要方法- 设置值- 获取值- 使用示例 - MybatisJava原生表达式的使用 - Fastjson- JSONPath类的主要方法- 主要功能- JSONPath的优势- 使用示例 Spring不选择OGNL的原因 OGNL 介绍 OGNL(Objec…

文章目录

  • OGNL 介绍
  • OGNL 使用场景
    • - ognl
      • - 主要功能
      • - 注意事项
      • - Ognl类的主要方法
      • - 设置值
      • - 获取值
      • - 使用示例
    • - Mybatis
      • Java原生表达式的使用
    • - Fastjson
      • - JSONPath类的主要方法
      • - 主要功能
      • - JSONPath的优势
      • - 使用示例
  • Spring不选择OGNL的原因


OGNL 介绍

OGNL(Object Graph Navigation Language)表达式语言是一种用于Java语言的表达式语言,专门用于在对象图中进行导航和操作。

在Java中,OGNL可以让开发人员以简洁的方式访问和操作Java对象的属性、调用对象的方法,执行算术和逻辑运算,以及处理集合和数组等操作。OGNL的语法简洁明了,可以方便地用于在Java开发中进行动态属性存取、列表和Map操作、函数和方法调用等,为开发人员提供了便利的数据操作手段。

OGNL 使用场景

常见使用场景:

  • ognl
  • Fastjson

- ognl

使用ognl包需要引入依赖

maven依赖:

<dependency><groupId>ognl</groupId><artifactId>ognl</artifactId><version>3.3.4</version>
</dependency>

- 主要功能

  1. 访问对象属性:

    使用点号(.)来访问对象的属性(也可设置对象的属性)。例如:person.name 表示访问 person 对象的 name 属性。

  2. 调用对象方法

    使用at符号(@)来调用对象的方法。例如:@java.lang.Math@random() 表示调用 Math 类的 random 方法。

  3. 访问集合和数组:

    使用方括号([])来访问集合或数组中的元素。例如:myList[0] 表示访问 myList 集合中的第一个元素。

  4. 赋值和表达式计算:

    支持变量赋值和基本的算术、逻辑运算符。例如:age > 18 && age < 60 表示对 age 变量进行逻辑判断。

  5. 条件表达式:

    支持条件运算符,如三元运算符 condition ? true : false。

  6. 对象引用:

    使用(#)符号来引用对象。例如:#person.name 表示引用 person 对象的 name 属性。

  7. 内置对象:

    OGNL中有一些内置对象,如 #context(上下文对象)、#root(根对象)、#this(当前对象)等,可以方便地用于表达式中的引用和操作。

- 注意事项

  • 当表达式expression中的属性不存在时,获取或设置值会报错

- Ognl类的主要方法

  • applyExpressionMaxLength:设置Ognl 表达式的最大允许长度限制
    • void applyExpressionMaxLength(Integer expressionMaxLength)
      • expressionMaxLength:表达式的最大允许长度
  • freezeExpressionMaxLength:冻结 Ognl 表达式的最大允许长度的限制
    • void freezeExpressionMaxLength()
  • thawExpressionMaxLength:解除 Ognl 表达式的最大允许长度的限制
    • void thawExpressionMaxLength()
  • createDefaultContext:创建一个默认的 Ognl 上下文对象
    • Map createDefaultContext(Object root)
      • root:根对象
    • Map createDefaultContext(Object root, ClassResolver classResolver)
      • root:根对象
      • classResolver:类解析器
    • Map createDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter)
      • root:根对象
      • classResolver:类解析器
      • converter:类型转换器
    • Map createDefaultContext(Object root, MemberAccess memberAccess)
      • root:根对象
      • memberAccess:成员访问对象
    • Map createDefaultContext(Object root, MemberAccess memberAccess, ClassResolver classResolver, TypeConverter converter)
      • root:根对象
      • memberAccess:成员访问对象
      • classResolver:类解析器
      • converter:类型转换器
  • addDefaultContext:向默认 Ognl 上下文中添加一个自定义的上下文对象
    • Map addDefaultContext(Object root, Map context)
      • root:根对象
      • context:上下文对象
    • Map addDefaultContext(Object root, ClassResolver classResolver, Map context)
      • root:根对象
      • classResolver:类解析器
      • context:上下文对象
    • Map addDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter, Map context)
      • root:根对象
      • classResolver:类解析器
      • converter:类型转换器
      • context:上下文对象
    • Map addDefaultContext(Object root, MemberAccess memberAccess, ClassResolver classResolver, TypeConverter converter, Map context)
      • root:根对象
      • memberAccess:成员访问对象
      • classResolver:类解析器
      • converter:类型转换器
      • context:上下文对象
  • compileExpression:将字符串表达式编译为 Ognl 表达式对象
    • Node compileExpression(OgnlContext context, Object root, String expression)
      • context:上下文对象
      • expression:字符串表达式
  • parseExpression:将字符串表达式解析为 Ognl 表达式对象
    • Object parseExpression(String expression)
      • expression:字符串表达式
  • getLastEvaluation:从上下文中获取最后一个评估(Evaluation)对象
    • Evaluation getLastEvaluation(Map context)
      • context:上下文对象
  • setRoot:设置 Ognl 表达式的根对象
    • void setRoot(Map context, Object root)
      • context:上下文对象
      • root:根对象
  • getRoot:获取 Ognl 表达式的根对象
    • Object getRoot(Map context)
      • context:上下文对象
  • isConstant:判断给定的表达式是否是常量
    • isConstant(Object tree)
      • tree:Ognl 表达式对象
    • isConstant(Object tree, Map context)
      • tree:Ognl 表达式对象
      • context:上下文对象
    • isConstant(String expression)
      • expression:字符串表达式
    • isConstant(String expression, Map context)
      • expression:字符串表达式
      • context:上下文对象
  • isSimpleProperty:判断给定的表达式是否是简单的属性
    • isSimpleProperty(String expression)
      • expression:字符串表达式
    • isSimpleProperty(String expression, Map context)
      • expression:字符串表达式
      • context:上下文对象
    • isSimpleProperty(Object tree)
      • tree:Ognl 表达式对象
    • isSimpleProperty(Object tree, Map context)
      • tree:Ognl 表达式对象
      • context:上下文对象
  • isSimpleNavigationChain:判断给定的表达式是否是简单的导航链
    • isSimpleNavigationChain(String expression)
      • expression:字符串表达式
    • isSimpleNavigationChain(String expression, Map context)
      • expression:字符串表达式
      • context:上下文对象
    • isSimpleNavigationChain(Object tree)
      • tree:Ognl 表达式对象
    • isSimpleNavigationChain(Object tree, Map context)
      • tree:Ognl 表达式对象
      • context:上下文对象
  • setValue:将给定的值设置到根对象的属性中
    • void setValue(String expression, Object root, Object value)
      根据表达式定位到根对象的属性,并将给定的值设置为该属性的值

      • expression:字符串表达式
      • root:根对象
      • value:要设置的值
    • void setValue(String expression, Map context, Object root, Object value)
      使用给定的上下文对象和根对象,根据表达式定位到根对象的属性,并将给定的值设置为该属性的值

      • expression:字符串表达式
      • context:上下文对象
      • root:根对象
      • value:要设置的值
    • void setValue(Object tree, Object root, Object value)
      使用表达式对象定位到根对象的属性,并将给定的值设置为该属性的值

      • tree:Ognl 表达式对象
      • root:根对象
      • value:要设置的值
    • void setValue(Object tree, Map context, Object root, Object value)
      使用给定的上下文对象和根对象,根据表达式对象定位到根对象的属性,并将给定的值设置为该属性的值

      • tree:Ognl 表达式对象
      • context:上下文对象
      • root:根对象
      • value:要设置的值
    • void setValue(ExpressionAccessor expression, OgnlContext context, Object root, Object value)
      使用给定的表达式访问器、上下文对象和根对象,定位到根对象的属性,并将给定的值设置为该属性的值

      • expression:Ognl 表达式访问器
      • context:Ognl 表达式访问器
      • root:根对象
      • value:要设置的值
  • getValue:通过给定的表达式从根对象中获取属性值
    • Object getValue(String expression, Object root)
      根据表达式定位到根对象的属性,并返回该属性的值
      • expression:字符串表达式
      • root:根对象
    • Object getValue(String expression, Object root, Class resultType)
      根据表达式定位到根对象的属性,并将返回的值转换为指定的结果类型
      • expression:字符串表达式
      • root:根对象
      • resultType:结果类型
    • Object getValue(String expression, Map context, Object root)
      使用给定的上下文对象和根对象,根据表达式定位到根对象的属性,并返回该属性的值
      • expression:字符串表达式
      • context:上下文对象
      • root:根对象
    • Object getValue(String expression, Map context, Object root, Class resultType)
      使用给定的上下文对象和根对象,根据表达式定位到根对象的属性,并将返回的值转换为指定的结果类型
      • expression:字符串表达式
      • context:上下文对象
      • root:根对象
      • resultType:结果类型
    • Object getValue(Object tree, Object root)
      使用表达式对象定位到根对象的属性,并返回该属性的值
      • tree:Ognl 表达式对象
      • root:根对象
    • Object getValue(Object tree, Object root, Class resultType)
      使用表达式对象定位到根对象的属性,并将返回的值转换为指定的结果类型
      • tree:Ognl 表达式对象
      • root:根对象
      • resultType:结果类型
    • Object getValue(Object tree, Map context, Object root)
      使用给定的上下文对象和根对象,根据表达式对象定位到根对象的属性,并返回该属性的值
      • tree:Ognl 表达式对象
      • context:上下文对象
      • root:根对象
    • Object getValue(Object tree, Map context, Object root, Class resultType)
      使用给定的上下文对象和根对象,根据表达式对象定位到根对象的属性,并将返回的值转换为指定的结果类型
      • tree:Ognl 表达式对象
      • context:上下文对象
      • root:根对象
      • resultType:结果类型
    • Object getValue(ExpressionAccessor expression, OgnlContext context, Object root)
      使用给定的表达式访问器、上下文对象和根对象,定位到根对象的属性,并返回该属性的值
      • expression:Ognl 表达式访问器
      • context:Ognl 上下文对象
      • root:根对象
    • Object getValue(ExpressionAccessor expression, OgnlContext context, Object root, Class resultType)
      使用给定的表达式访问器、上下文对象和根对象,定位到根对象的属性,并将返回的值转换为指定的结果类型
      • expression:Ognl 表达式访问器
      • context:Ognl 上下文对象
      • root:根对象
      • resultType:结果类型

- 设置值

  1. 使用字符串表达式方式设置值

    public static void main(String[] args) throws Exception {// 创建一个根对象Person person = new Person("John", 25);// 创建一个 Ognl 上下文OgnlContext context = new OgnlContext();context.setRoot(person);// 使用字符串表达式方式设置值Ognl.setValue("name", context, person, "Alice");Ognl.setValue("age", context, person, 30);// 输出更新后的属性值System.out.println(person.getName());  // 输出:AliceSystem.out.println(person.getAge());   // 输出:30}
    
  2. 使用 Ognl 表达式对象方式设置值

        public static void main(String[] args) throws Exception {// 创建一个根对象Person person = new Person("John", 25);// 创建一个 Ognl 表达式对象Object tree = Ognl.parseExpression("name");// 使用表达式对象方式设置值Ognl.setValue(tree, person, "Alice");// 输出更新后的属性值System.out.println(person.getName());  // 输出:Alice}
    
  3. 使用 Ognl 表达式访问器和 Ognl 上下文对象方式设置根值

        public static void main(String[] args) throws OgnlException {// 创建一个根对象Person person = new Person("John", 25);// 创建一个 Ognl 上下文OgnlContext context = new OgnlContext();context.setRoot(person);// 创建一个 Ognl 表达式访问器ExpressionAccessor expression = OgnlRuntime.getPropertyAccessor(Person.class);// 使用表达式访问器和上下文对象设置值Object tree = Ognl.parseExpression("age");Ognl.setValue(expression, context, person, tree, 30);// 输出更新后的属性值System.out.println(person.getAge());   // 输出:30}
    

- 获取值

  1. 使用字符串表达式方式获取值

        public static void main(String[] args) throws OgnlException {// 创建一个根对象Person person = new Person("Alice", 30);// 使用字符串表达式方式获取值Object nameValue = Ognl.getValue("name", person);System.out.println(nameValue);  // 输出:Alice// 使用字符串表达式方式获取值Object ageValue = Ognl.getValue("age", person);System.out.println(ageValue);  // 输出:30}
    
  2. 使用字符串表达式及Ognl 上下文对象方式获取值

        public static void main(String[] args) throws OgnlException {// 创建一个根对象Person person = new Person("Alice", 30);// 创建一个 Ognl 上下文OgnlContext context = new OgnlContext();context.setRoot(person);// 使用带上下文参数的字符串表达式方式获取值Object nameValue = Ognl.getValue("name", context, person);System.out.println(nameValue);  // 输出:Alice// 使用带上下文参数的字符串表达式方式获取值Object ageValue = Ognl.getValue("age", context, person);System.out.println(ageValue);  // 输出:30}
    
  3. 使用 Ognl 表达式对象方式获取值

        public static void main(String[] args) throws OgnlException {// 创建一个根对象Person person = new Person("Alice", 30);// 创建一个 Ognl 表达式对象Object nameTree = Ognl.parseExpression("name");// 使用表达式对象方式获取值Object nameValue = Ognl.getValue(nameTree, person);System.out.println(nameValue);  // 输出:Alice}
    
  4. 使用 Ognl 表达式对象及Ognl 上下文对象方式获取值

        public static void main(String[] args) throws OgnlException {// 创建一个根对象Person person = new Person("Alice", 30);// 创建一个 Ognl 上下文OgnlContext context = new OgnlContext();context.setRoot(person);// 创建一个 Ognl 表达式对象Object nameTree = Ognl.parseExpression("name");// 使用表达式对象方式获取值Object nameValue = Ognl.getValue(nameTree, context, person);System.out.println(nameValue);  // 输出:Alice}
    
  5. 使用自定义的表达式访问器和上下文对象方式获取值

        public static void main(String[] args) throws OgnlException {// 创建一个根对象Person person = new Person("Alice", 30);// 创建一个 Ognl 上下文OgnlContext context = new OgnlContext();context.setRoot(person);// 创建一个自定义的表达式访问器ExpressionAccessor expression = OgnlRuntime.getPropertyAccessor(Person.class);// 使用自定义的表达式访问器和上下文对象获取值Object ageValue = Ognl.getValue(expression, context, person, "age");System.out.println(ageValue);  // 输出:30}
    

- 使用示例

maven依赖

<dependency><groupId>ognl</groupId><artifactId>ognl</artifactId><version>3.3.4</version>
</dependency>

实现源码

public class OGNLTest {@Data@NoArgsConstructor@AllArgsConstructorpublic static class User {public Integer userId;public String userName;public Sex sex;}@Data@NoArgsConstructor@AllArgsConstructorpublic static class Sex {public Integer sexCode;public String sexName;}public static void main(String[] args) throws OgnlException {// 访问对象属性User user = new User(111111, "哈哈", new Sex(1, "男"));Integer userId = (Integer) Ognl.getValue("userId", user);System.out.println(userId);// 111111Integer sexCode = (Integer) Ognl.getValue("sex.sexCode", user);System.out.println(sexCode);// 1// 调用对象方法Double random = (Double) Ognl.getValue("@java.lang.Math@random()", null);System.out.println(random);// 0.9562367273068916// 访问集合和数组List<User> list = Stream.of(user).collect(Collectors.toList());String sexName = (String) Ognl.getValue("[0].sex.sexName", list);System.out.println(sexName);// 男// 赋值和表达式计算boolean isMan = (Boolean) Ognl.getValue("sex.sexCode == 1", user);System.out.println(isMan);// true// 条件表达式String sex = (String) Ognl.getValue("sex.sexCode == 1 ? \"男\" : \"女\"", user);System.out.println(sex);// 男// 对象引用Map<String,Object> context = new HashMap<>();context.put("aaa", new Sex(2, "女"));Integer sexCode1 = (Integer) Ognl.getValue("#aaa.sexCode", context, user);System.out.println(sexCode1);// 2// 内置对象Integer sexCode2 = (Integer) Ognl.getValue("#root.sex.sexCode", user);System.out.println(sexCode2);// 1Integer sexCode3 = (Integer) Ognl.getValue("#this.sex.sexCode", user);System.out.println(sexCode3);// 1}
}

- Mybatis

MyBatis 早期版本确实使用过 OGNL 作为表达式语言,特别是在动态 SQL 处理中。在 MyBatis 的 XML 配置文件中,OGNL 被用来处理一些复杂的表达式,例如在 、 等标签中进行条件判断。

然而,由于 OGNL 存在一些性能和安全问题,MyBatis 在后续版本中逐步淘汰了 OGNL,转而使用更为简洁和安全的 OGNL 变种 或 Java 原生表达式。目前,OGNL 仍然可以通过自定义插件使用,但它已不再是 MyBatis 的核心功能。

Java原生表达式更简单、更安全,并且能够直接利用 Java 语言的语法和功能进行条件判断、循环等操作。

Java原生表达式的使用

在 MyBatis 中,Java原生表达式主要用于动态 SQL 的编写,特别是在 、、 等标签中,用于控制 SQL 的生成逻辑。

基本语法:
在 MyBatis 中,Java原生表达式通常出现在如下标签的属性中:

  • :用于条件判断
  • :类似于 switch-case 的控制结构
  • :用于循环生成 SQL 部分
  • :用于动态 SQL 中的空白字符去除

常用的Java 原生表达式:

  • 条件表达式
  • 三元运算符
  • 逻辑运算符
  • 逻辑运算(&&, ||, !)
  • 数学运算(+, -, *, /)
  • 类型转换(通过 (type) 强制转换)
  • 方法调用(如 string.equals())

使用示例:

(1) 标签中的 Java 原生表达式:
标签用于根据某个条件动态地生成 SQL 片段。我们可以在 标签中直接使用 Java 表达式进行条件判断。

<select id="selectUser" resultType="User">SELECT * FROM users<where><if test="username != null">AND username = #{username}</if><if test="age != null">AND age = #{age}</if></where>
</select>
  • test=“username != null” 中的 username != null 就是一个 Java 原生表达式。
  • 在这里,test 属性值就是 Java 表达式的条件判断语句,如果表达式结果为 true,那么 中的 SQL 片段才会被加入到最终生成的 SQL 中。

(2) 标签中的 Java 原生表达式:
标签类似于 Java 中的 switch-case 语法,用于根据多个条件选择生成不同的 SQL 片段。

<select id="selectUser" resultType="User">SELECT * FROM users<where><choose><when test="age != null">AND age = #{age}</when><when test="username != null">AND username = #{username}</when><otherwise>AND status = 'active'</otherwise></choose></where>
</select>
  • 允许在多个 中根据不同条件选择合适的 SQL 片段。
  • 每个 中的 test 属性也可以使用 Java 表达式。

(3) 标签中的 Java 原生表达式:
标签用于循环生成 SQL 片段,通常用于处理数组、集合或列表类型的参数。

<select id="selectUsersByIds" resultType="User">SELECT * FROM users<where><foreach collection="ids" item="id" open="AND id IN (" separator="," close=")">#{id}</foreach></where>
</select>
  • collection=“ids”:表示传入的参数集合或数组。
  • 在 标签中,我们可以使用 Java 表达式来处理循环逻辑和参数拼接。

(4) 使用 Java 原生三元运算符:
在 MyBatis 中,Java原生表达式也支持使用 Java 的三元运算符来简化条件判断。

<select id="selectUser" resultType="User">SELECT * FROM usersWHERE status = <if test="status != null">#{status}</if><if test="status == null">'active'</if>
</select>

使用三元运算符来替代多重条件判断,改为:

<select id="selectUser" resultType="User">SELECT * FROM usersWHERE status = #{status != null ? status : 'active'}
</select>

(5) 标签中的 Java 原生表达式:
标签用于对生成的 SQL 语句进行前后字符的去除(如去掉 AND 或 OR 等)。

<select id="selectUser" resultType="User">SELECT * FROM users<trim prefix="WHERE" prefixOverrides="AND |OR "><if test="status != null">AND status = #{status}</if><if test="age != null">AND age = #{age}</if></trim>
</select>

- Fastjson

- JSONPath类的主要方法

  1. Object eval(String json, String path)
    通过给定的 JSON 数据和 JSONPath 表达式,返回匹配该表达式的结果对象

  2. JSONArray extract(Object json, String path)
    根据指定的 JSONPath 表达式,从 JSON 对象中提取匹配的 JSON 数组。返回一个 JSONArray 对象

  3. boolean contains(Object json, String path)
    判断 JSON 数据中是否包含匹配指定 JSONPath 表达式的内容

  4. boolean containsValue(Object json, String path, Object value)
    判断 JSON 对象中匹配 JSONPath 表达式的内容是否包含指定的值

  5. int size(Object json, String path)
    计算 JSONPath 表达式匹配的内容的大小,并返回匹配结果的个数

  6. Set<?> keySet(Object json, String path)
    返回 JSONPath 表达式匹配的所有键值的 Set 集合

  7. void arrayAdd(Object rootObject, String path, Object… values)
    在 JSON 对象中的指定位置添加一个或多个元素

  8. void remove(Object json, String path)
    根据指定的 JSONPath 表达式,从 JSON 对象中移除匹配路径的内容

  9. void set(Object json, String path, Object value)
    根据指定的 JSONPath 表达式,在 JSON 对象中设置匹配路径的值为指定的 value

  10. JSONPath compile(String path)
    编译 JSONPath 表达式,返回一个对应的 JSONPath 对象

    这个方法可以提高后续对同一个 JSONPath 表达式的操作性能。

  11. Object read(String json, String path)
    读取 JSON 数据中指定 JSONPath 表达式的值

  12. List paths(Object json, String path)
    获取 JSON 对象中匹配指定 JSONPath 表达式的所有路径,并返回一个 JSONPath 列表

  13. void setArrayItem(Object json, String path, Object value)
    根据指定的 JSONPath 表达式,在 JSON 数组中设置匹配路径的元素为指定的 value

  14. void removeArrayItem(Object json, String path, int index)
    根据指定的 JSONPath 表达式,从 JSON 数组中移除指定索引位置的元素

- 主要功能

  1. 设置JSON对象中的属性值

  2. 获取JSON对象中的属性值

- JSONPath的优势

  1. 设置JSON对象中的属性值时,如果属性不存在,也会自动创建
  2. 获取JSON对象中的属性值是,如果属性不存在,不会报错空指针,会返回null

- 使用示例

public class OGNLTest {public static void main(String[] args) {// 设置JSON对象中的属性值JSONObject jsonObject = new JSONObject();JSONPath.set(jsonObject, "$.role.roleName", "管理员");JSONPath.set(jsonObject, "$.user.userName", "Joker");System.out.println(JSON.toJSONString(jsonObject));// {"role":{"roleName":"管理员"},"user":{"userName":"Joker"}}JSONObject jsonObject1 = JSON.parseObject("{\"role\":{\"roleName\":\"管理员\"},\"user\":{\"userName\":\"Joker\"}}");JSONPath.set(jsonObject1, "$.user.userId", 18);System.out.println(JSON.toJSONString(jsonObject1));// {"role":{"roleName":"管理员"},"user":{"userName":"Joker","userId":18}}// 获取JSON对象中的属性值System.out.println(JSONPath.eval(jsonObject, "$.role.roleName"));// 管理员System.out.println(JSONPath.eval(jsonObject, "$.user.userName"));// JokerSystem.out.println(JSONPath.eval(jsonObject, "$.user.age"));// nullSystem.out.println(JSONPath.eval(jsonObject, "$.user.sex.setName"));// null}
}

Spring不选择OGNL的原因

Spring 官方推荐的表达式语言是Spring Expression Language (SpEL) ,它提供了更强大的功能和更多的特性。

为什么Spring使用SpEL替代了OGNL了呢:

  • OGNL 在早期版本中被发现存在一些安全漏洞,尤其是当其用于动态求值时,可能会导致恶意代码执行。因此,在使用 OGNL 时要特别小心,尽量避免在不信任的输入中执行 OGNL 表达式,或使用一些安全措施来过滤不安全的表达式。

可参考文章:
Spring SpEL表达式的使用

http://www.shuangfujiaoyu.com/news/44446.html

相关文章:

  • 上海虹口网站建设公司销售渠道及方式
  • 土巴兔装修公司靠谱吗网站优化及推广方案
  • 迈肯奇迹做网站网站seo推广优化
  • wordpress 限制标题字数百度优化
  • 武汉市建设网外贸seo优化公司
  • 免费邮箱注册入口黑帽seo365t技术
  • wordpress触屏主题网站优化包括
  • 毕业设计做旅游网站百度大数据分析工具
  • 广州网站建设studstu百度竞价排名叫什么
  • 开发网站性能监控线上营销培训
  • 广州网站推广自助seo技术培训岳阳
  • 做网站推广排名搜索引擎营销的主要方法包括
  • 个人网站内容怎么写50个市场营销经典案例
  • 外国做挂的网站是多少廊坊百度关键词排名平台
  • 开源的网站系统手机百度一下
  • 金华网站建设360网址大全
  • 保山市城市建设网站网络营销有什么岗位
  • 施工企业会计核算办法2021百度seo排名优化是什么
  • 宁波做网站价格河南今日头条最新消息
  • 外贸网站管理系统营销的三个基本概念是什么
  • 宣传片制作公司业务宁波关键词优化企业网站建设
  • 甜品店网站建设山西太原百度公司
  • 人社局网站建设方案建立网站费用大概需要多少钱
  • 如何在自己的电脑上做网站关键词排名客服
  • 有没有专门做桑拿的网站呀最牛餐饮营销手段
  • 做网站优惠百度提交网站收录入口
  • 糖果网站是李笑来做的吗百度大数据官网
  • 商丘网站建设查数据的网站有哪些
  • 网站建设靠谱的seo网站优化专员
  • google play成都最好的seo外包