Fel實現自定義計算引擎,平均計算速度每秒10w

Fel的介紹和簡單使用以及0.9版本的包在之前的博客寫過了,有需要的朋友移步:fel的簡單使用及介紹

項目地址:https://github.com/an-yusheng/CSDN-FelDemo

 

1. 基礎的應用

下面的代碼是引用原生類進行執行的,支持通用數學函數即加減乘除

package com.example.demo.util;

import com.greenpineyu.fel.FelEngine;
import com.greenpineyu.fel.FelEngineImpl;

/**
 * @author Anzepeng
 * @title: FelDemo
 * @projectName demo
 * @description: TODO
 * @date 2020/6/3 0003下午 13:42
 */
public class FelDemo {

    public static void main(String[] args){
        // 計算核心類
        FelEngine felEngine = new FelEngineImpl();
        // 執行計算的方法eval(公式)
        Object result = felEngine.eval("1+1");
        // 結果
        System.out.println("計算結果爲:"+result);
    }
}

執行結果

如果僅僅是普通運算那並不能滿足我們的需求,嘗試一下聚合函數

錯誤信息找不到函數,原生包並沒有支持這些函數,所以我們要重寫它的實現代碼

2. 查看源代碼

FelEngineImpl的源代碼
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.greenpineyu.fel;

import com.greenpineyu.fel.common.FelBuilder;
import com.greenpineyu.fel.compile.CompileService;
import com.greenpineyu.fel.context.ArrayCtxImpl;
import com.greenpineyu.fel.context.FelContext;
import com.greenpineyu.fel.context.Var;
import com.greenpineyu.fel.function.FunMgr;
import com.greenpineyu.fel.function.Function;
import com.greenpineyu.fel.optimizer.Optimizer;
import com.greenpineyu.fel.optimizer.VarVisitOpti;
import com.greenpineyu.fel.parser.AntlrParser;
import com.greenpineyu.fel.parser.FelNode;
import com.greenpineyu.fel.parser.Parser;
import com.greenpineyu.fel.security.SecurityMgr;

public class FelEngineImpl implements FelEngine {
    private FelContext context;
    private CompileService compiler;
    private Parser parser;
    private FunMgr funMgr;
    private SecurityMgr securityMgr;

    public SecurityMgr getSecurityMgr() {
        return this.securityMgr;
    }

    public void setSecurityMgr(SecurityMgr var1) {
        this.securityMgr = var1;
    }

    public FelEngineImpl(FelContext var1) {
        this.securityMgr = FelBuilder.newSecurityMgr();
        this.context = var1;
        this.compiler = new CompileService();
        this.parser = new AntlrParser(this);
        this.funMgr = new FunMgr();
    }

    public FelEngineImpl() {
        this(new ArrayCtxImpl());
    }

    public FelNode parse(String var1) {
        return this.parser.parse(var1);
    }

    public Object eval(String var1) {
        return this.eval(var1, this.context);
    }

    public Object eval(String var1, Var... var2) {
        FelNode var3 = this.parse(var1);
        VarVisitOpti var4 = new VarVisitOpti(var2);
        var3 = var4.call(this.context, var3);
        return var3.eval(this.context);
    }

    public Object eval(String var1, FelContext var2) {
        return this.parse(var1).eval(var2);
    }

    public Expression compile(String var1, Var... var2) {
        return this.compile(var1, (FelContext)null, new VarVisitOpti(var2));
    }

    public Expression compile(String var1, FelContext var2, Optimizer... var3) {
        if (var2 == null) {
            var2 = this.context;
        }

        FelNode var4 = this.parse(var1);
        if (var3 != null) {
            Optimizer[] var5 = var3;
            int var6 = var3.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                Optimizer var8 = var5[var7];
                if (var8 != null) {
                    var4 = var8.call(var2, var4);
                }
            }
        }

        return this.compiler.compile(var2, var4, var1);
    }

    public String toString() {
        return "FelEngine";
    }

    public void addFun(Function var1) {
        this.funMgr.add(var1);
    }

    public FelContext getContext() {
        return this.context;
    }

    public CompileService getCompiler() {
        return this.compiler;
    }

    public void setCompiler(CompileService var1) {
        this.compiler = var1;
    }

    public Parser getParser() {
        return this.parser;
    }

    public void setParser(Parser var1) {
        this.parser = var1;
    }

    public FunMgr getFunMgr() {
        return this.funMgr;
    }

    public void setFunMgr(FunMgr var1) {
        this.funMgr = var1;
    }

    public void setContext(FelContext var1) {
        this.context = var1;
    }
}

3. 重寫FelEngineImpl類並實現FelEngine

下面是我們創建可修改的實現類,跟源代碼不一致的地方是我將 this(new ArrayCtxImpl()); 註釋了

package com.example.demo.util;


import com.greenpineyu.fel.Expression;
import com.greenpineyu.fel.FelEngine;
import com.greenpineyu.fel.common.FelBuilder;
import com.greenpineyu.fel.compile.CompileService;
import com.greenpineyu.fel.context.ArrayCtxImpl;
import com.greenpineyu.fel.context.FelContext;
import com.greenpineyu.fel.context.Var;
import com.greenpineyu.fel.function.FunMgr;
import com.greenpineyu.fel.function.Function;
import com.greenpineyu.fel.optimizer.Optimizer;
import com.greenpineyu.fel.optimizer.VarVisitOpti;
import com.greenpineyu.fel.parser.AntlrParser;
import com.greenpineyu.fel.parser.FelNode;
import com.greenpineyu.fel.parser.Parser;
import com.greenpineyu.fel.security.SecurityMgr;

/**
 * @author Anzepeng
 * @title: FelEngineImpl
 * @projectName calculate
 * @description: TODO
 * @date 2020/5/20 0020下午 17:44
 */
public class FelEngineImpl implements FelEngine {
    private FelContext context;
    private CompileService compiler;
    private Parser parser;
    private FunMgr funMgr;
    private SecurityMgr securityMgr;

    public SecurityMgr getSecurityMgr() {
        return this.securityMgr;
    }

    public void setSecurityMgr(SecurityMgr var1) {
        this.securityMgr = var1;
    }

    public FelEngineImpl(FelContext var1) {
        this.securityMgr = FelBuilder.newSecurityMgr();
        this.context = var1;
        this.compiler = new CompileService();
        this.parser = new AntlrParser(this);
        this.funMgr = new FunMgr();
    }

    public FelEngineImpl() {
        //this(new ArrayCtxImpl());
    }

    public FelNode parse(String var1) {
        return this.parser.parse(var1);
    }

    public Object eval(String var1) {
        return this.eval(var1, this.context);
    }

    public Object eval(String var1, Var... var2) {
        FelNode var3 = this.parse(var1);
        VarVisitOpti var4 = new VarVisitOpti(var2);
        var3 = var4.call(this.context, var3);
        return var3.eval(this.context);
    }

    public Object eval(String var1, FelContext var2) {
        return this.parse(var1).eval(var2);
    }

    public Expression compile(String var1, Var... var2) {
        return this.compile(var1, (FelContext)null, new VarVisitOpti(var2));
    }

    public Expression compile(String var1, FelContext var2, Optimizer... var3) {
        if (var2 == null) {
            var2 = this.context;
        }

        FelNode var4 = this.parse(var1);
        if (var3 != null) {
            Optimizer[] var5 = var3;
            int var6 = var3.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                Optimizer var8 = var5[var7];
                if (var8 != null) {
                    var4 = var8.call(var2, var4);
                }
            }
        }

        return this.compiler.compile(var2, var4, var1);
    }

    public String toString() {
        return "FelEngine";
    }

    public void addFun(Function var1) {
        this.funMgr.add(var1);
    }

    public FelContext getContext() {
        return this.context;
    }

    public CompileService getCompiler() {
        return this.compiler;
    }

    public void setCompiler(CompileService var1) {
        this.compiler = var1;
    }

    public Parser getParser() {
        return this.parser;
    }

    public void setParser(Parser var1) {
        this.parser = var1;
    }

    public FunMgr getFunMgr() {
        return this.funMgr;
    }

    public void setFunMgr(FunMgr var1) {
        this.funMgr = var1;
    }

    public void setContext(FelContext var1) {
        this.context = var1;
    }
}

在demo類裏將引用的FelEngineImpl類改成我們創建的這個然後執行

出現錯誤java.lang.NullPointerException

解開註釋再次執行,恢復正常

這個方法裏寫的就是我們定義好的運算,函數或方法,Parser解析公式後執行的運算就是匹配這個方法裏的內容

4. 編寫自定義函數

在上面知道運算方法後我們就要加入自定義函數了,fel的簡單使用及介紹  裏面也講過自定義函數了,我們直接拿過來修改一下

我們只取第一部分代碼並修改一下,改成sum的函數,並添加函數引用,代碼如下

public FelEngineImpl() {
        this(new ArrayCtxImpl());
        // sum函數
        Function sumFun = new CommonFunction() {
            public String getName() {
                return "sum";
            }
            @Override
            public Object call(Object... values) {
                Double sum=0d;
                for(Object o:values){
                    if(o!=null){
                        Double d= NumberUtil.toDouble(o);
                        sum+=d;
                    }
                }
                return sum;
            }
        };
        // 添加sum函數
        this.addFun(sumFun);
    }

修改完成,執行一下sum函數

5. 重寫後的FelEngineImpl代碼執行示例

支持的運算 、函數、判斷

普通運算:加減乘除

聚合函數:MAX、MIN、AVG、SUM、、、等

關係:大於、小於、大於等於、小於等於、等於、不等於等

條件:?:

6. 百萬數據速度測試

因爲輸出所有結果會影響真實速度,我們先不輸出測試一下

百萬數據計算速度10161ms,平均每秒10w,我們再輸出一下公式結果,確定一下計算結果正確性

結果應該沒有錯誤,因爲有輸出所以多了8s的時間

7. FelEngineImpl代碼

package com.example.demo.util;


import com.greenpineyu.fel.Expression;
import com.greenpineyu.fel.FelEngine;
import com.greenpineyu.fel.common.FelBuilder;
import com.greenpineyu.fel.common.NumberUtil;
import com.greenpineyu.fel.common.ObjectUtils;
import com.greenpineyu.fel.compile.CompileService;
import com.greenpineyu.fel.context.ArrayCtxImpl;
import com.greenpineyu.fel.context.FelContext;
import com.greenpineyu.fel.context.Var;
import com.greenpineyu.fel.function.CommonFunction;
import com.greenpineyu.fel.function.FunMgr;
import com.greenpineyu.fel.function.Function;
import com.greenpineyu.fel.optimizer.Optimizer;
import com.greenpineyu.fel.optimizer.VarVisitOpti;
import com.greenpineyu.fel.parser.AntlrParser;
import com.greenpineyu.fel.parser.FelNode;
import com.greenpineyu.fel.parser.Parser;
import com.greenpineyu.fel.security.SecurityMgr;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;

/**
 * @author Anzepeng
 * @title: FelEngineImpl
 * @projectName calculate
 * @description: TODO
 * @date 2020/5/20 0020下午 17:44
 */
public class FelEngineImpl implements FelEngine {
    private FelContext context;
    private CompileService compiler;
    private Parser parser;
    private FunMgr funMgr;
    private SecurityMgr securityMgr;

    public SecurityMgr getSecurityMgr() {
        return this.securityMgr;
    }

    public void setSecurityMgr(SecurityMgr var1) {
        this.securityMgr = var1;
    }

    public FelEngineImpl(FelContext var1) {
        this.securityMgr = FelBuilder.newSecurityMgr();
        this.context = var1;
        this.compiler = new CompileService();
        this.parser = new AntlrParser(this);
        this.funMgr = new FunMgr();
    }

    public FelEngineImpl() {
        this(new ArrayCtxImpl());
        Function funmax= new CommonFunction() {
            public String getName() {
                return "MAX";
            }
            @Override
            public Object call(Object... values) {
                Double max=0-Double.MAX_VALUE;
                for(Object o:values){
                    Double d=NumberUtil.toDouble(o);
                    if(d.compareTo(max)>0){
                        max=d;
                    }
                }
                return max;
            }
        };
        this.addFun(funmax);
        Function funmin= new CommonFunction() {
            public String getName() {
                return "MIN";
            }
            @Override
            public Object call(Object... values) {
                Double min=Double.MAX_VALUE;
                for(Object o:values){
                    Double d=NumberUtil.toDouble(o);
                    if(d.compareTo(min)<0){
                        min=d;
                    }
                }
                return min;
            }
        };
        this.addFun(funmin);
        Function funavg= new CommonFunction() {
            public String getName() {
                return "AVG";
            }
            @Override
            public Object call(Object... values) {
                Double sum=0d;
                for(Object o:values){
                    Double d=NumberUtil.toDouble(o);
                    sum+=d;
                }
                return sum/values.length;
            }
        };
        this.addFun(funavg);

        Function avgeli= new CommonFunction() {
            public String getName() {
                return "AVGELI";
            }
            @Override
            public Object call(Object... values) {
                Double sum=0d;
                int count=0;
                for(Object o:values){
                    if(o!=null){
                        Double d=NumberUtil.toDouble(o);
                        sum+=d;
                        if(d!=0.0){
                            count++;
                        }
                    }
                }
                return sum/count;
            }
        };


        this.addFun(avgeli);
        Function funsum= new CommonFunction() {
            public String getName() {
                return "SUM";
            }
            @Override
            public Object call(Object... values) {
                Double sum=0d;
                for(Object o:values){
                    if(o!=null){
                        Double d=NumberUtil.toDouble(o);
                        sum+=d;
                    }
                }
                return sum;
            }
        };
        this.addFun(funsum);
        Function funabs= new CommonFunction() {
            public String getName() {
                return "ABS";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.abs(result);
            }
        };
        this.addFun(funabs);
        Function funbadmin= new CommonFunction() {
            public String getName() {
                return "BADMIN";
            }
            @Override
            public Object call(Object... values) {
                Double min=Double.MAX_VALUE;
                boolean a=true;
                for(Object o:values){
                    Double d=NumberUtil.toDouble(o);
                    Double b=Math.abs(d);
                    if(b.compareTo(min)<0){
                        min=b;
                        if(d>=0){
                            a=true;
                        }else{
                            a=false;
                        }
                    }
                }
                if(a){
                    return min;
                }else{
                    return 0-min;
                }
            }
        };
        this.addFun(funbadmin);

        Function funbadmax= new CommonFunction() {
            public String getName() {
                return "BADMAX";
            }
            @Override
            public Object call(Object... values) {
                Double max=0d;
                boolean a=true;
                for(Object o:values){
                    Double d=NumberUtil.toDouble(o);
                    if(d.equals(NumberUtil.toDouble(-100))){
                        continue;
                    }
                    Double b=Math.abs(d);
                    if(b.compareTo(max)>0){
                        if(d>=0){
                            a=true;
                        }else{
                            a=false;
                        }
                        max=b;
                    }
                }
                if(a){
                    return max;
                }else{
                    return 0-max;
                }
            }
        };
        this.addFun(funbadmax);
        Function funacos= new CommonFunction() {
            public String getName() {
                return "ACOS";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.acos(result);
            }
        };
        this.addFun(funacos);
        Function funasin= new CommonFunction() {
            public String getName() {
                return "ASIN";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.asin(result);
            }
        };
        this.addFun(funasin);
        Function funatan= new CommonFunction() {
            public String getName() {
                return "ATAN";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.atan(result);
            }
        };
        this.addFun(funatan);

        Function funcos= new CommonFunction() {
            public String getName() {
                return "COS";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.cos(result);
            }
        };
        this.addFun(funcos);
        Function funsin= new CommonFunction() {
            public String getName() {
                return "SIN";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.sin(result);
            }
        };
        this.addFun(funsin);
        Function funtan= new CommonFunction() {
            public String getName() {
                return "TAN";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.tan(result);
            }
        };
        this.addFun(funtan);

        Function funceil= new CommonFunction() {
            public String getName() {
                return "CEIL";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.ceil(result);
            }
        };
        this.addFun(funceil);

        Function funround= new CommonFunction() {
            public String getName() {
                return "ROUND";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.round(result*100)*0.01;
            }
        };
        this.addFun(funround);

        Function funfloor= new CommonFunction() {
            public String getName() {
                return "FLOOR";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.floor(result);
            }
        };
        this.addFun(funfloor);

        Function funsqrt= new CommonFunction() {
            public String getName() {
                return "SQRT";
            }
            @Override
            public Object call(Object... values) {
                Double result=NumberUtil.toDouble(values[0]);
                return Math.sqrt(result);
            }
        };
        this.addFun(funsqrt);

        Function funpow= new CommonFunction() {
            public String getName() {
                return "POW";
            }
            @Override
            public Object call(Object... values) {
                Double arg1=NumberUtil.toDouble(values[0]);
                return Math.pow(arg1,2);
            }
        };
        this.addFun(funpow);

        Function funpown= new CommonFunction() {
            public String getName() {
                return "POWN";
            }
            @Override
            public Object call(Object... values) {
                Double arg1=NumberUtil.toDouble(values[0]);
                Double arg2=NumberUtil.toDouble(values[1]);
                return Math.pow(arg1,arg2);
            }
        };
        this.addFun(funpown);

        Function funsqrtn= new CommonFunction() {
            public String getName() {
                return "SQRTN";
            }
            @Override
            public Object call(Object... values) {
                Double arg1=NumberUtil.toDouble(values[0]);
                Double arg2=NumberUtil.toDouble(values[1]);
                return Math.pow(arg1,1/arg2);
            }
        };
        this.addFun(funsqrtn);

        Function equals= new CommonFunction() {
            public String getName() {
                return "EQUALS";
            }
            @Override
            public Object call(Object... values) {
                return values[0].equals(values[1]);
            }
        };
        this.addFun(equals);

        Function isnull= new CommonFunction() {
            public String getName() {
                return "ISNULL";
            }
            @Override
            public Object call(Object... values) {
                if(values==null||values[0]==null||"".equals(values[0].toString().trim())){
                    return true;
                }
                return false;
            }
        };
        this.addFun(isnull);


        Function nulltozero= new CommonFunction() {
            public String getName() {
                return "NULLTOZERO";
            }
            @Override
            public Object call(Object... values) {
                if(values==null||values[0]==null||"".equals(values[0].toString().trim())){
                    return 0;
                }
                return values[0];
            }
        };
        this.addFun(nulltozero);

        Function concat= new CommonFunction() {
            public String getName() {
                return "CONCAT";
            }
            @Override
            public Object call(Object... values) {
                StringBuffer result=new StringBuffer();
                for(Object v:values){
                    if(v!=null)
                        result.append(v);
                }
                return result.toString();
            }
        };
        this.addFun(concat);

        Function date= new CommonFunction() {
            public String getName() {
                return "DATE";
            }
            @Override
            public Object call(Object... values) {
                SimpleDateFormat format=null;
                if(values==null){
                    format=new SimpleDateFormat("yyyy-MM-dd");
                } else if(values[0].toString().equals("1")){
                    format=new SimpleDateFormat("yyyy-MM-dd-");
                } else if(values[0].toString().equals("2")){
                    format=new SimpleDateFormat("yyyy-MM");
                } else if(values[0].toString().equals("3")){
                    format=new SimpleDateFormat("yyyy");
                }
                return format.format(new Date(System.currentTimeMillis()));
            }
        };
        this.addFun(date);


        Function random= new CommonFunction() {
            public String getName() {
                return "RANDOM";
            }
            @Override
            public Object call(Object... values) {
                Random random1 = new Random(System.currentTimeMillis());
                int a=random1.nextInt(2);
                Double c=Double.valueOf(values[0].toString());
                double b = random1.nextInt((int)(c*Double.valueOf(values[1].toString())*10000))/1000000.0;
                if(a==1){
                    return c+b;
                }else{
                    return c-b;
                }
            }
        };
        this.addFun(random);

        Function predate= new CommonFunction() {
            public String getName() {
                return "PREDATE";
            }
            @Override
            public Object call(Object... values) {
                SimpleDateFormat format=null;
                Calendar calendar=Calendar.getInstance();
                calendar.setFirstDayOfWeek(2);
                if(values==null){
                    calendar.add(Calendar.DAY_OF_YEAR, -1);
                    format=new SimpleDateFormat("yyyy-MM-dd");
                } else if(values[0].toString().equals("1")){
                    calendar.add(Calendar.DAY_OF_YEAR, -1);
                    format=new SimpleDateFormat("yyyy-MM-dd");
                } else if(values[0].toString().equals("2")){
                    calendar.add(Calendar.MONTH, -1);
                    format=new SimpleDateFormat("yyyy-MM");
                } else if(values[0].toString().equals("3")){
                    calendar.add(Calendar.YEAR, -1);
                    format=new SimpleDateFormat("yyyy");
                }
                return format.format(calendar.getTime());
            }
        };
        this.addFun(predate);

        Function pre15time= new CommonFunction() {
            public String getName() {
                return "PRE15TIME";
            }
            @Override
            public Object call(Object... values) {
                SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Calendar calendar=Calendar.getInstance();
                if(values!=null){
                    calendar.setTime(new Date((Long)values[0]));
                }
                calendar.setFirstDayOfWeek(2);
                int minu=calendar.get(Calendar.MINUTE);
                if(minu%15!=0){
                    minu=((int)(minu/15))*15;
                }
                calendar.set(Calendar.SECOND, 0);
                calendar.set(Calendar.MINUTE, minu);
                return format.format(calendar.getTime());
            }
        };
        this.addFun(pre15time);
    }

    public FelNode parse(String var1) {
        return this.parser.parse(var1);
    }

    public Object eval(String var1) {
        return this.eval(var1, this.context);
    }

    public Object eval(String var1, Var... var2) {
        FelNode var3 = this.parse(var1);
        VarVisitOpti var4 = new VarVisitOpti(var2);
        var3 = var4.call(this.context, var3);
        return var3.eval(this.context);
    }

    public Object eval(String var1, FelContext var2) {
        return this.parse(var1).eval(var2);
    }

    public Expression compile(String var1, Var... var2) {
        return this.compile(var1, (FelContext)null, new VarVisitOpti(var2));
    }

    public Expression compile(String var1, FelContext var2, Optimizer... var3) {
        if (var2 == null) {
            var2 = this.context;
        }

        FelNode var4 = this.parse(var1);
        if (var3 != null) {
            Optimizer[] var5 = var3;
            int var6 = var3.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                Optimizer var8 = var5[var7];
                if (var8 != null) {
                    var4 = var8.call(var2, var4);
                }
            }
        }

        return this.compiler.compile(var2, var4, var1);
    }

    public String toString() {
        return "FelEngine";
    }

    public void addFun(Function var1) {
        this.funMgr.add(var1);
    }

    public FelContext getContext() {
        return this.context;
    }

    public CompileService getCompiler() {
        return this.compiler;
    }

    public void setCompiler(CompileService var1) {
        this.compiler = var1;
    }

    public Parser getParser() {
        return this.parser;
    }

    public void setParser(Parser var1) {
        this.parser = var1;
    }

    public FunMgr getFunMgr() {
        return this.funMgr;
    }

    public void setFunMgr(FunMgr var1) {
        this.funMgr = var1;
    }

    public void setContext(FelContext var1) {
        this.context = var1;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章