Java各種規(guī)則引擎

一. Drools規(guī)則引擎

  1. 簡介:
Drools就是為了解決業(yè)務代碼和業(yè)務規(guī)則分離的引擎。
Drools 規(guī)則是在 Java 應用程序上運行的,其要執(zhí)行的步驟順序由代碼確定
,為了實現(xiàn)這一點,Drools 規(guī)則引擎將業(yè)務規(guī)則轉換成執(zhí)行樹。
  1. 特性:
優(yōu)點:
   1、簡化系統(tǒng)架構,優(yōu)化應用
   2、提高系統(tǒng)的可維護性和維護成本
   3、方便系統(tǒng)的整合
   4、減少編寫“硬代碼”業(yè)務規(guī)則的成本和風險

3.原理:


Drools.png
  1. 使用方式:

(1)Maven 依賴:

<dependencies>
    <dependency>
        <groupId>org.kie</groupId>
        <artifactId>kie-api</artifactId>
        <version>6.5.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-compiler</artifactId>
        <version>6.5.0.Final</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

(2)新建配置文件/src/resources/META-INF/kmodule.xml

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
    <kbase name="rules" packages="rules">
        <ksession name="myAgeSession"/>
    </kbase>
</kmodule>

(3)新建drools規(guī)則文件/src/resources/rules/age.drl

import com.lrq.wechatDemo.domain.User               // 導入類

dialect  "mvel"

rule "age"                                      // 規(guī)則名,唯一
    when
        $user : User(age<15 || age>60)     //規(guī)則的條件部分
    then
        System.out.println("年齡不符合要求!");
end

工程搭建完畢,效果如圖:

項目結構.png

測試用例:


/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/26
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:applicationContext.xml"})
public class TestUser {

    private static KieContainer container = null;
    private KieSession statefulKieSession = null;

    @Test
    public void test(){
        KieServices kieServices = KieServices.Factory.get();
        container = kieServices.getKieClasspathContainer();
        statefulKieSession = container.newKieSession("myAgeSession");
        User user = new User("duval yang",12);
        statefulKieSession.insert(user);
        statefulKieSession.fireAllRules();
        statefulKieSession.dispose();

    }



}


二.Aviator表達式求值引擎

  1. 簡介:
Aviator是一個高性能、輕量級的java語言實現(xiàn)的表達式求值引擎,主要用于各
種表達式的動態(tài)求值?,F(xiàn)在已經(jīng)有很多開源可用的java表達式求值引擎,為什
么還需要Avaitor呢?

Aviator的設計目標是輕量級和高性能 ,相比于Groovy、JRuby的笨重,Aviator
非常小,加上依賴包也才450K,不算依賴包的話只有70K;當然,Aviator的語法
是受限的,它不是一門完整的語言,而只是語言的一小部分集合。

其次,Aviator的實現(xiàn)思路與其他輕量級的求值器很不相同,其他求值器一般都
是通過解釋的方式運行,而Aviator則是直接將表達式編譯成Java字節(jié)碼,交給
JVM去執(zhí)行。簡單來說,Aviator的定位是介于Groovy這樣的重量級腳本語言和
IKExpression這樣的輕量級表達式引擎之間。
  1. 特性:
(1)支持大部分運算操作符,包括算術操作符、關系運算符、邏輯操作符、
正則匹配操作符(=~)、三元表達式?: ,并且支持操作符的優(yōu)先級和括號強制優(yōu)
先級,具體請看后面的操作符列表。
(2)支持函數(shù)調用和自定義函數(shù)。
(3)支持正則表達式匹配,類似Ruby、Perl的匹配語法,并且支持類Ruby的
$digit指向匹配分組。自動類型轉換,當執(zhí)行操作的時候,會自動判斷操作數(shù)類
型并做相應轉換,無法轉換即拋異常。
(4)支持傳入變量,支持類似a.b.c的嵌套變量訪問。
(5)性能優(yōu)秀。
(6)Aviator的限制,沒有if else、do while等語句,沒有賦值語句,僅支持邏
輯表達式、算術表達式、三元表達式和正則匹配。沒有位運算符

  1. 整體結構:


    整體結構.png
  2. maven依賴:

<dependency>
    <groupId>com.googlecode.aviator</groupId>
    <artifactId>aviator</artifactId>
    <version>${aviator.version}</version>
</dependency>
  1. 執(zhí)行方式
    執(zhí)行表達式的方法有兩個:execute()、exec();
    execute(),需要傳遞Map格式參數(shù)
    exec(),不需要傳遞Map
    示例:

/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/25
 */
public class Test {
    public static void main(String[] args) {
        // exec執(zhí)行方式,無需傳遞Map格式
        String age = "18";
        System.out.println(AviatorEvaluator.exec("'His age is '+ age +'!'", age));



        // execute執(zhí)行方式,需傳遞Map格式
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("age", "18");
        System.out.println(AviatorEvaluator.execute("'His age is '+ age +'!'", 
map));

    }
}
  1. 使用函數(shù)
    Aviator可以使用兩種函數(shù):內置函數(shù)、自定義函數(shù)
    (1)內置函數(shù)


    Aviator內置函數(shù).png
Aviator內置函數(shù).png

/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/25
 */
public class Test {
    public static void main(String[] args) {
        Map<String,Object> map = new HashMap<>();
        map.put("s1","123qwer");
        map.put("s2","123");

  System.out.println(AviatorEvaluator.execute("string.startsWith(s1,s2)",map));

    }
}


(2)自定義函數(shù)

自定義函數(shù)要繼承AbstractFunction類,重寫目標方法。


/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/25
 */
public class Test {
    public static void main(String[] args) {
        // 注冊自定義函數(shù)
        AviatorEvaluator.addFunction(new MultiplyFunction());
        // 方式1
        System.out.println(AviatorEvaluator.execute("multiply(12.23, -2.3)"));
        // 方式2
        Map<String, Object> params = new HashMap<>();
        params.put("a", 12.23);
        params.put("b", -2.3);
        System.out.println(AviatorEvaluator.execute("multiply(a, b)", params));
    }

}

class MultiplyFunction extends AbstractFunction{
    @Override
    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {

        double num1 = FunctionUtils.getNumberValue(arg1, env).doubleValue();
        double num2 = FunctionUtils.getNumberValue(arg2, env).doubleValue();
        return new AviatorDouble(num1 * num2);
    }

    @Override
    public String getName() {
        return "multiply";
    }

}
  1. 常用操作符的使用
    (1)操作符列表


    操作符列表.png

(2)常量和變量


常量和變量.png

(3)編譯表達式


/**
* CreateBy: haleyliu
* CreateDate: 2018/12/25
*/
public class Test {
   public static void main(String[] args) {
       String expression = "a+(b-c)>100";
       // 編譯表達式
       Expression compiledExp = AviatorEvaluator.compile(expression);

       Map<String, Object> env = new HashMap<>();
       env.put("a", 100.3);
       env.put("b", 45);
       env.put("c", -199.100);

       // 執(zhí)行表達式
       Boolean result = (Boolean) compiledExp.execute(env);
       System.out.println(result);

   }
}

(4) 訪問數(shù)組和集合
List和數(shù)組用list[0]和array[0],Map用map.date

/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/25
 */
public class Test {
    public static void main(String[] args) {

        final List<String> list = new ArrayList<>();
        list.add("hello");
        list.add(" world");

        final int[] array = new int[3];
        array[0] = 0;
        array[1] = 1;
        array[2] = 3;

        final Map<String, Date> map = new HashMap<>();
        map.put("date", new Date());

        Map<String, Object> env = new HashMap<>();
        env.put("list", list);
        env.put("array", array);
        env.put("map", map);

        System.out.println(AviatorEvaluator.execute(
                "list[0]+':'+array[0]+':'+'today is '+map.date", env));

    }

}

(5) 三元比較符

/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/25
 */
public class Test {
    public static void main(String[] args) {

        Map<String, Object> env = new HashMap<String, Object>();
        env.put("a", -5);
        String result = (String) AviatorEvaluator.execute("a>0? 'yes':'no'", env);
        System.out.println(result);
    }

}

(6) 正則表達式匹配


/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/25
 */
public class Test {
    public static void main(String[] args) {
        String email = "hello2018@gmail.com";
        Map<String, Object> env = new HashMap<String, Object>();
        env.put("email", email);
        String username = (String) AviatorEvaluator.execute("email=~/([\\w0-8]+)@\\w+[\\.\\w+]+/ ? $1 : 'unknow' ", env);
        System.out.println(username);
    }
}

(7) 變量的語法糖衣


/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/25
 */
public class Test {
    public static void main(String[] args) {
        User user = new User(1,"jack","18");
        Map<String, Object> env = new HashMap<>();
        env.put("user", user);

        String result = (String) AviatorEvaluator.execute(" '[user id='+ user.id + ',name='+user.name + ',age=' +user.age +']' ", env);
        System.out.println(result);
    }
}


/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/25
 */
public class User {

    private int id;

    private String name;

    private String age;

    public User() {
    }

    public User(int id, String name, String age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }

}

(8) nil對象[任何對象都比nil大除了nil本身]

nil是Aviator內置的常量,類似java中的null,表示空的值。nil跟null不同的在
于,在java中null只能使用在==、!=的比較運算符,而nil還可以使用>、>=、
<、<=等比較運算符。Aviator規(guī)定,[任何對象都比nil大除了nil本身]。用戶傳入
的變量如果為null,將自動以nil替代。

        AviatorEvaluator.execute("nil == nil");  //true 
        AviatorEvaluator.execute(" 3> nil");    //true 
        AviatorEvaluator.execute(" true!= nil");    //true 
        AviatorEvaluator.execute(" ' '>nil ");  //true 
        AviatorEvaluator.execute(" a==nil ");   //true,a is null
nil與String相加的時候,跟java一樣顯示為null

(9) 日期比較


/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/25
 */
public class Test {
    public static void main(String[] args) {
        Map<String, Object> env = new HashMap<String, Object>();
        final Date date = new Date();
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS").format(date);
        env.put("date", date);
        env.put("dateStr", dateStr);

        Boolean result = (Boolean) AviatorEvaluator.execute("date==dateStr",
 env);
        System.out.println(result);

        result = (Boolean) AviatorEvaluator.execute("date > '2009-12-20 
00:00:00:00' ", env);
        System.out.println(result);

        result = (Boolean) AviatorEvaluator.execute("date < '2200-12-20 
00:00:00:00' ", env);
        System.out.println(result);

        result = (Boolean) AviatorEvaluator.execute("date ==date ", env);
        System.out.println(result);


    }
}

(10) 語法手冊

數(shù)據(jù)類型
  • Number類型:數(shù)字類型,支持兩種類型,分別對應Java的Long和Double,也就是說任何整數(shù)都將被轉換為Long,而任何浮點數(shù)都將被轉換為Double,包括用戶傳入的數(shù)值也是如此轉換。不支持科學計數(shù)法,僅支持十進制。如-1、100、2.3等。

  • String類型: 字符串類型,單引號或者雙引號括起來的文本串,如'hello world',變量如果傳入的是String或者Character也將轉為String類型。

  • Bool類型: 常量true和false,表示真值和假值,與java的Boolean.TRUE和Boolean.False對應。

  • Pattern類型: 類似Ruby、perl的正則表達式,以//括起來的字符串,如//d+/,內部實現(xiàn)為java.util.Pattern。

  • 變量類型: 與Java的變量命名規(guī)則相同,變量的值由用戶傳入,如"a"、"b"等

  • nil類型: 常量nil,類似java中的null,但是nil比較特殊,nil不僅可以參與==、!=的比較,也可以參與>、>=、<、<=的比較,Aviator規(guī)定任何類型都n大于nil除了nil本身,nil==nil返回true。用戶傳入的變量值如果為null,那么也將作為nil處理,nil打印為null。

算術運算符

Aviator支持常見的算術運算符,包括+ - <tt></tt> / % 五個二元運算符,和一元運算符"-"。其中 - <tt></tt> / %和一元的"-"僅能作用于Number類型。

"+"不僅能用于Number類型,還可以用于String的相加,或者字符串與其他對象的相加。Aviator規(guī)定,任何類型與String相加,結果為String。

邏輯運算符

Avaitor的支持的邏輯運算符包括,一元否定運算符"!",以及邏輯與的"&&",邏輯或的"||"。邏輯運算符的操作數(shù)只能為Boolean。

關系運算符

Aviator支持的關系運算符包括"<" "<=" ">" ">=" 以及"=="和"!=" 。
&&和||都執(zhí)行短路規(guī)則。

關系運算符可以作用于Number之間、String之間、Pattern之間、Boolean之間、變量之間以及其他類型與nil之間的關系比較,不同類型除了nil之外不能相互比較。

Aviator規(guī)定任何對象都比nil大除了nil之外。

匹配運算符

匹配運算符"=~"用于String和Pattern的匹配,它的左操作數(shù)必須為String,右操作數(shù)必須為Pattern。匹配成功后,Pattern的分組將存于變量$num,num為分組索引。

三元運算符

Aviator沒有提供if else語句,但是提供了三元運算符 "?:",形式為 bool ? exp1: exp2。 其中bool必須為結果為Boolean類型的表達式,而exp1和exp2可以為任何合法的Aviator表達式,并且不要求exp1和exp2返回的結果類型一致。

  1. 兩種模式
    默認AviatorEvaluator以編譯速度優(yōu)先:
    AviatorEvaluator.setOptimize(AviatorEvaluator.COMPILE);
    你可以修改為運行速度優(yōu)先,這會做更多的編譯優(yōu)化:
    AviatorEvaluator.setOptimize(AviatorEvaluator.EVAL);

三.MVEL表達式解析器

1.簡介 :

MVEL在很大程度上受到Java語法的啟發(fā),作為一個表達式語言,也有一些根本
的區(qū)別,旨在更高的效率,例如:直接支持集合、數(shù)組和字符串匹配等操作以
及正則表達式。 MVEL用于執(zhí)行使用Java語法編寫的表達式。

2.特性:

MVEL是一個功能強大的基于Java應用程序的表達式語言。
目前最新的版本是2.0,具有以下特性:
(1). 動態(tài)JIT優(yōu)化器。當負載超過一個確保代碼產(chǎn)生的閾值時,選擇性地產(chǎn)生字
節(jié)代碼,這大大減少了內存的使用量。新的靜態(tài)類型檢查和屬性支持,允許集成
類型安全表達。
(2). 錯誤報告的改善。包括行和列的錯誤信息。
(3). 新的腳本語言特征。MVEL2.0 包含函數(shù)定義,如:閉包,lambda定義,
標準循環(huán)構造(for, while, do-while, do-until…),空值安全導航操作,內聯(lián)with
-context運營 ,易變的(isdef)的測試運營等等。
(4). 改進的集成功能。迎合主流的需求,MVEL2.0支持基礎類型的個性化屬性處理器,集成到JIT中。
(5). 更快的模板引擎,支持線性模板定義,宏定義和個性化標記定義。
(6). 新的交互式shell(MVELSH)。

(7). 缺少可選類型安全
(8). 集成不良,通常通過映射填入內容。沒有字節(jié)碼不能運作用字節(jié)碼生成編
譯時間慢,還增加了可擴展性問題;不用字節(jié)碼生成運行時執(zhí)行非常慢
(9). 內存消耗過大
(10). Jar巨大/依賴規(guī)模

3.原理:

與java不同,MVEL是動態(tài)類型(帶有可選分類),也就是說在源文件中是沒有
類型限制的。一條MVEL表達式,簡單的可以是單個標識符,復雜的則可能是
一個充滿了方法調用和內部集合創(chuàng)建的龐大的布爾表達式。

4.使用方式:
maven引入jar:

<dependency>
            <groupId>org.mvel</groupId>
            <artifactId>mvel2</artifactId>
            <version>2.3.1.Final</version>
        </dependency>

測試:

package com.lrq.wechatdemo.utils;

import com.google.common.collect.Maps;
import org.mvel2.MVEL;

import java.util.Map;

/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/26
 */
public class MvelUtils {

    public static void main(String[] args) {
        String expression = "a == null && b == nil ";
        Map<String,Object> map = Maps.newHashMap();
        map.put("a",null);
        map.put("b",null);

        Object object = MVEL.eval(expression,map);
        System.out.println(object);
    }

}

四.EasyRules規(guī)則引擎

1.簡介:

easy-rules首先集成了mvel表達式,后續(xù)可能集成SpEL的一款輕量
級規(guī)則引擎

2.特性:

easy rules是一個簡單而強大的java規(guī)則引擎,它有以下特性:

輕量級框架,學習成本低
基于POJO
為定義業(yè)務引擎提供有用的抽象和簡便的應用
從原始的規(guī)則組合成復雜的規(guī)則
它主要包括幾個主要的類或接口:Rule,RulesEngine,RuleListener,Facts 
還有幾個主要的注解:@Action,@Condition,@Fact,@Priority,@Rule

3.使用方式:

@Rule可以標注name和description屬性,每個rule的name要唯一,
如果沒有指定,則RuleProxy則默認取類名
@Condition是條件判斷,要求返回boolean值,表示是否滿足條件

@Action標注條件成立之后觸發(fā)的方法

@Priority標注該rule的優(yōu)先級,默認是Integer.MAX_VALUE - 1,值
越小越優(yōu)先

@Fact 我們要注意Facts的使用。Facts的用法很像Map,它是客戶
端和規(guī)則文件之間通信的橋梁。在客戶端使用put方法向Facts中添
加數(shù)據(jù),在規(guī)則文件中通過key來得到相應的數(shù)據(jù)。

有兩種使用方式:

  1. java方式
    首先先創(chuàng)建規(guī)則并標注屬性
package com.lrq.wechatdemo.rules;

import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
import org.jeasy.rules.support.UnitRuleGroup;

/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/26
 */
public class RuleClass {

    @Rule(priority = 1) //規(guī)則設定優(yōu)先級
    public static class FizzRule {
        @Condition
        public boolean isFizz(@Fact("number") Integer number) {
            return number % 5 == 0;
        }

        @Action
        public void printFizz() {
            System.out.print("fizz\n");
        }
    }

    @Rule(priority = 2)
    public static class BuzzRule {
        @Condition
        public boolean isBuzz(@Fact("number") Integer number) {
            return number % 7 == 0;
        }

        @Action
        public void printBuzz() {
            System.out.print("buzz\n");
        }
    }

    public static class FizzBuzzRule extends UnitRuleGroup {

        public FizzBuzzRule(Object... rules) {
            for (Object rule : rules) {
                addRule(rule);
            }
        }

        @Override
        public int getPriority() {
            return 0;
        }
    }

    @Rule(priority = 3)
    public static class NonFizzBuzzRule {

        @Condition
        public boolean isNotFizzNorBuzz(@Fact("number") Integer number) {
            // can return true, because this is the latest rule to trigger according to
            // assigned priorities
            // and in which case, the number is not fizz nor buzz
            return number % 5 != 0 || number % 7 != 0;
        }

        @Action
        public void printInput(@Fact("number") Integer number) {
            System.out.print(number+"\n");
        }
    }

}

然后客戶端調用

package com.lrq.wechatdemo.rules;

import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.core.RulesEngineParameters;

/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/26
 */
public class RuleJavaClient {
    public static void main(String[] args) {
        // 創(chuàng)建規(guī)則引擎
        RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
        RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);

        // 創(chuàng)建規(guī)則集并注冊規(guī)則
        Rules rules = new Rules();
        rules.register(new RuleClass.FizzRule());
        rules.register(new RuleClass.BuzzRule());
        rules.register(new RuleClass.FizzBuzzRule(new RuleClass.FizzRule(), new RuleClass.BuzzRule()));
        rules.register(new RuleClass.NonFizzBuzzRule());

        // 執(zhí)行規(guī)則
        Facts facts = new Facts();
        for (int i = 1; i <= 100; i++) {
            facts.put("number", i);
            fizzBuzzEngine.fire(rules, facts);
            System.out.println();
        }
    }

}

2.yml方式

resources目錄下新建fizzbuzz.yml

---
name: "fizz rule"
description: "print fizz if the number is multiple of 5"
priority: 1
condition: "number % 5 == 0"
actions:
- "System.out.println(\"fizz\")"

---
name: "buzz rule"
description: "print buzz if the number is multiple of 7"
priority: 2
condition: "number % 7 == 0"
actions:
- "System.out.println(\"buzz\")"

---
name: "fizzbuzz rule"
description: "print fizzbuzz if the number is multiple of 5 and 7"
priority: 0
condition: "number % 5 == 0 && number % 7 == 0"
actions:
- "System.out.println(\"fizzbuzz\")"

---
name: "non fizzbuzz rule"
description: "print the number itself otherwise"
priority: 3
condition: "number % 5 != 0 || number % 7 != 0"
actions:
- "System.out.println(number)"

客戶端調用:

package com.lrq.wechatdemo.rules;

import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.core.RulesEngineParameters;
import org.jeasy.rules.mvel.MVELRuleFactory;

import java.io.FileNotFoundException;
import java.io.FileReader;

/**
 * CreateBy: haleyliu
 * CreateDate: 2018/12/26
 */
public class RuleYmlClient {

    public static void main(String[] args) throws FileNotFoundException {
        // create a rules engine
        RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
        RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);

        // create rules
        Rules rules = MVELRuleFactory.createRulesFrom(new FileReader("fizzbuzz.yml"));

        // fire rules
        Facts facts = new Facts();
        for (int i = 1; i <= 100; i++) {
            facts.put("number", i);
            fizzBuzzEngine.fire(rules, facts);
            System.out.println();
        }
    }
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容