如何编写Iveely搜索引擎插件

     如果一个搜索引擎仅仅是网页搜索,那么将会是非常枯燥的,也不能根据业务需求扩展,还好Iveely在设计之初,就考虑了扩展性,预留插件功能,在不关闭服务或者停用服务的情况下,可以随时启用新插件或者禁用。

     首先先介绍下Iveely加载插件的流程,再举例一步一步写插件。

     原理

     在Iveely.Service下面,存在一个plugin.json文件,Iveely.Service将会每六个小时,更新配置信息,如果plugin.json有更新,将会更新到系统中。Iveey.Service只是一个服务中转站,它将用户的请求,根据请求的命令或者匹配的规则,将请求数据转发给对应插件进行处理,最终将插件处理后的结果返回给用户。plugin.json详细信息介绍如下:

     plugin.json文件示例

{
    "emailServer":"smtp.126.com",
    "emailPort":"25",
    "emailName":"Iveely后台服务",
    "email": "2@126.com",
    "password": "2",
    "notify": "liufanping@iveely.com,founder@iveely.com",
    "plugins": [
        {
            "name": "天气预报",
            "enable": "1",
            "pattern": ".*天气.*",
            "command": "weather",
            "executeType":"1",
            "owner": "1@qq.com",
            "ip": "1.iveely.com,5001",
            "backup": "1.iveely.com,5001"
        },
        {
            "name": "计算器",
            "enable": "1",
            "pattern": ".*等于.*",
            "command": "calculate",
            "executeType":"2",
            "owner": "2@qq.com",
            "ip": "2.iveely.com,5002",
            "backup": "2.iveely.com,5002"
        }]
}

    

     下面将以搜索引擎的计算功能为例,为Iveely新增计算插件,首先效果图如下:     

     示例

     第一步:新建纯Java应用程序工程。

     

     第二步:添加相应的库。

     由于插件是以网络方式存在的,因此添加必要的网络库是必然的,其次是基于Iveely.Framework存在。

     

      这些jar文件,均可以在Iveely.Framework工程中找到。

      第三步:编写计算器计算类:Calculate类。     

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.iveely.plugins.calculator;

import java.util.Collections;
import java.util.Stack;

/**
 *
 * @author X1 Carbon
 */
public class Calculate {

    /**
     * Algorithm helper.
     *
     * @author liufanping@iveely.com
     * @date 2014-11-16 10:38:07
     */
    public static class ArithHelper {

        /**
         * The default precision division.
         */
        private static final int DEF_DIV_SCALE = 16;

        private ArithHelper() {
        }

        /**
         * Provide accurate addition.
         *
         * @param v1
         * @param v2
         * @return
         */
        public static double add(double v1, double v2) {
            java.math.BigDecimal b1 = new java.math.BigDecimal(Double.toString(v1));
            java.math.BigDecimal b2 = new java.math.BigDecimal(Double.toString(v2));
            return b1.add(b2).doubleValue();
        }

        /**
         * Provide accurate addition.
         *
         * @param v1
         * @param v2
         * @return
         */
        public static double add(String v1, String v2) {
            java.math.BigDecimal b1 = new java.math.BigDecimal(v1);
            java.math.BigDecimal b2 = new java.math.BigDecimal(v2);
            return b1.add(b2).doubleValue();
        }

        /**
         * Provide accurate subtraction.
         *
         * @param v1
         * @param v2
         * @return
         */
        public static double sub(double v1, double v2) {
            java.math.BigDecimal b1 = new java.math.BigDecimal(Double.toString(v1));
            java.math.BigDecimal b2 = new java.math.BigDecimal(Double.toString(v2));
            return b1.subtract(b2).doubleValue();
        }

        /**
         * Provide accurate subtraction.
         *
         * @param v1
         * @param v2
         * @return
         */
        public static double sub(String v1, String v2) {
            java.math.BigDecimal b1 = new java.math.BigDecimal(v1);
            java.math.BigDecimal b2 = new java.math.BigDecimal(v2);
            return b1.subtract(b2).doubleValue();
        }

        /**
         * Provides accurate multiplication.
         *
         * @param v1
         * @param v2
         * @return
         */
        public static double mul(double v1, double v2) {
            java.math.BigDecimal b1 = new java.math.BigDecimal(Double.toString(v1));
            java.math.BigDecimal b2 = new java.math.BigDecimal(Double.toString(v2));
            return b1.multiply(b2).doubleValue();
        }

        /**
         * Provides accurate multiplication.
         *
         * @param v1
         * @param v2
         * @return
         */
        public static double mul(String v1, String v2) {
            java.math.BigDecimal b1 = new java.math.BigDecimal(v1);
            java.math.BigDecimal b2 = new java.math.BigDecimal(v2);
            return b1.multiply(b2).doubleValue();
        }

        /**
         * Provide (relatively) precise division, except when the situation
         * occurs when the endless, accurate to 10 decimal point, after the
         * figures are rounded.
         *
         * @param v1
         * @param v2
         * @return
         */
        public static double div(double v1, double v2) {
            return div(v1, v2, DEF_DIV_SCALE);
        }

        /**
         * Provide (relatively) precise division, except when the situation
         * occurs when the endless, accurate to 10 decimal point, after the
         * figures are rounded.
         *
         * @param v1
         * @param v2
         * @return
         */
        public static double div(String v1, String v2) {
            java.math.BigDecimal b1 = new java.math.BigDecimal(v1);
            java.math.BigDecimal b2 = new java.math.BigDecimal(v2);
            return b1.divide(b2, DEF_DIV_SCALE, java.math.BigDecimal.ROUND_HALF_UP).doubleValue();
        }

        /**
         * Providing (relatively) accurate division. When occurrence except
         * endless, specify the scale parameter accuracy, after rounding
         * numbers.
         *
         * @param v1
         * @param v2
         * @param scale。
         * @return
         */
        public static double div(double v1, double v2, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("The   scale   must   be   a   positive   integer   or   zero");
            }
            java.math.BigDecimal b1 = new java.math.BigDecimal(Double.toString(v1));
            java.math.BigDecimal b2 = new java.math.BigDecimal(Double.toString(v2));
            return b1.divide(b2, scale, java.math.BigDecimal.ROUND_HALF_UP).doubleValue();
        }

        /**
         * Provides precise decimals rounded handle.
         *
         * @param v
         * @param scale
         * @return
         */
        public static double round(double v, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("The   scale   must   be   a   positive   integer   or   zero");
            }
            java.math.BigDecimal b = new java.math.BigDecimal(Double.toString(v));
            java.math.BigDecimal one = new java.math.BigDecimal("1");
            return b.divide(one, scale, java.math.BigDecimal.ROUND_HALF_UP).doubleValue();
        }

        /**
         * Provides precise decimals rounded handle.
         *
         * @param v
         * @param scale
         * @return
         */
        public static double round(String v, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("The   scale   must   be   a   positive   integer   or   zero");
            }
            java.math.BigDecimal b = new java.math.BigDecimal(v);
            java.math.BigDecimal one = new java.math.BigDecimal("1");
            return b.divide(one, scale, java.math.BigDecimal.ROUND_HALF_UP).doubleValue();
        }
    }

    /**
     * Postfix stack.
     */
    private final Stack<String> postfixStack;

    /**
     * Operator Stack.
     */
    private final Stack<Character> opStack;

    /**
     * Operators use the ASCII -40 indexing of operator precedence.
     */
    private final int[] operatPriority;

    public Calculate() {
        postfixStack = new Stack<>();
        opStack = new Stack<>();
        operatPriority = new int[]{0, 3, 2, 1, -1, 1, 0, 2};
    }

    /**
     * According to the given expression evaluates.
     *
     * @param expression
     * @return
     */
    public String calculate(String expression) {
        try {
            Stack<String> resultStack = new Stack<>();
            prepare(expression);
            Collections.reverse(postfixStack);
            String firstValue, secondValue, currentValue;
            while (!postfixStack.isEmpty()) {
                currentValue = postfixStack.pop();
                if (!isOperator(currentValue.charAt(0))) {
                    resultStack.push(currentValue);
                } else {
                    secondValue = resultStack.pop();
                    firstValue = resultStack.pop();
                    String tempResult = calculate(firstValue, secondValue, currentValue.charAt(0));
                    resultStack.push(tempResult);
                }
            }
            return expression + "=" + Double.valueOf(resultStack.pop());
        } catch (NumberFormatException e) {
        }
        return "";
    }

    /**
     * Be converted into postfix expression stack.
     *
     * @param expression
     */
    private void prepare(String expression) {
        opStack.push(',');
        char[] arr = expression.toCharArray();
        int currentIndex = 0;
        int count = 0;
        char currentOp, peekOp;
        for (int i = 0; i < arr.length; i++) {
            currentOp = arr[i];
            if (isOperator(currentOp)) {
                if (count > 0) {
                    postfixStack.push(new String(arr, currentIndex, count));
                }
                peekOp = opStack.peek();
                if (currentOp == ')') {
                    while (opStack.peek() != '(') {
                        postfixStack.push(String.valueOf(opStack.pop()));
                    }
                    opStack.pop();
                } else {
                    while (currentOp != '(' && peekOp != ',' && compare(currentOp, peekOp)) {
                        postfixStack.push(String.valueOf(opStack.pop()));
                        peekOp = opStack.peek();
                    }
                    opStack.push(currentOp);
                }
                count = 0;
                currentIndex = i + 1;
            } else {
                count++;
            }
        }
        if (count > 1 || (count == 1 && !isOperator(arr[currentIndex]))) {
            postfixStack.push(new String(arr, currentIndex, count));
        }
        while (opStack.peek() != ',') {
            postfixStack.push(String.valueOf(opStack.pop()));
        }
    }

    /**
     * Determine whether the arithmetic sign.
     *
     * @param c
     * @return
     */
    private boolean isOperator(char c) {
        return c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')';
    }

    /**
     * Use ASCII code -40 subscript to do arithmetic signs priority.
     *
     * @param cur
     * @param peek
     * @return
     */
    public boolean compare(char cur, char peek) {
        boolean result = false;
        if (operatPriority[(peek) - 40] >= operatPriority[(cur) - 40]) {
            result = true;
        }
        return result;
    }

    /**
     * According to the given arithmetic operators to do the calculation.
     *
     * @param firstValue
     * @param secondValue
     * @param currentOp
     * @return
     */
    private String calculate(String firstValue, String secondValue, char currentOp) {
        String result = "";
        switch (currentOp) {
            case '+':
                result = String.valueOf(ArithHelper.add(firstValue, secondValue));
                break;
            case '-':
                result = String.valueOf(ArithHelper.sub(firstValue, secondValue));
                break;
            case '*':
                result = String.valueOf(ArithHelper.mul(firstValue, secondValue));
                break;
            case '/':
                result = String.valueOf(ArithHelper.div(firstValue, secondValue));
                break;
            default:
                result = "error format.";
        }
        return result;
    }
}
View Code

      Calculate类是用于服务的,那么它是怎么提供对外服务呢?

      第四步:新建EventHandler类:用于消息处理。

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.iveely.plugins.calculator;

import com.iveely.framework.net.ICallback;
import com.iveely.framework.net.InternetPacket;
import java.io.UnsupportedEncodingException;

/**
 *
 * @author 凡平
 */
public class EventHandler implements ICallback {

    /**
     * Weather forecast.
     */
    private Calculate calculate;

    public EventHandler() {
        this.calculate = new Calculate();
    }

    @Override
    public InternetPacket invoke(InternetPacket packet) {
        InternetPacket respPacket = new InternetPacket();
        respPacket.setMimeType(0);
        respPacket.setExecutType(packet.getExecutType() * -1);
        if (packet.getExecutType() == 2) {
            try {
                String query = getString(packet.getData());
                System.out.println("计算表达式:" + query);
                String result =  this.calculate.calculate(query);
                if (result.isEmpty()) {
                    respPacket.setExecutType(Integer.MIN_VALUE);
                    respPacket.setData(getBytes("Expression error."));
                }else{
                    respPacket.setData(getBytes(result));
                }

            } catch (Exception e) {
                respPacket.setExecutType(Integer.MIN_VALUE);
                respPacket.setData(getBytes(e.getMessage()));
            }
            return respPacket;
        } else {
            return InternetPacket.getUnknowPacket();
        }
    }

    /**
     * Convert string to byte[].
     *
     * @param content
     * @return
     */
    private byte[] getBytes(String content) {
        byte[] bytes;
        try {
            bytes = content.getBytes("UTF-8");
        } catch (UnsupportedEncodingException ex) {
            bytes = content.getBytes();
        }
        return bytes;
    }

    /**
     * Convert byte[] to string.
     *
     * @param bytes
     * @return
     */
    private String getString(byte[] bytes) {
        try {
            return new String(bytes, "UTF-8").trim();
        } catch (UnsupportedEncodingException ex) {
            return new String(bytes).trim();
        }
    }
}
View Code

      这里面有几个注意的事项:

      1. 一定要继承 ICallback。

      2. packet.getExecutType() == 2 表示它的执行类型,需要在plugin.json 中配置。

      第五步:启动插件服务。

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.iveely.plugins.calculator;

import com.iveely.framework.net.Server;

/**
 *
 * @author 凡平
 */
public class Program {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        int port = 5002;
        System.out.println("Server started, port = " + port);
        EventHandler handler = new EventHandler();
        Server server = new Server(handler, port);
        server.start();
    }

}
View Code

      按照上面,五个步骤,你就完成了一个最基本的插件编写,生成jar包,找一台机器,将它运行起来,要让搜索引擎能够正常使用,还需要让搜索引擎知道这个插件,那么需要配置plugin.json文件。

 

posted @ 2015-05-12 08:30  Iveely Liu  阅读(903)  评论(0编辑  收藏  举报