如何编写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; } }
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(); } } }
这里面有几个注意的事项:
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(); } }
按照上面,五个步骤,你就完成了一个最基本的插件编写,生成jar包,找一台机器,将它运行起来,要让搜索引擎能够正常使用,还需要让搜索引擎知道这个插件,那么需要配置plugin.json文件。