引言
最近点外卖发现起手接单越来越慢了,主要原因是因为这个时间点骑手少了。
用户增长部门反馈接单时间变长,用户体验变差,用户会流失。需要解决,需要缩短接单时长。
解决方式就是给骑手补贴。
- 降低抽佣比例,但是这种公司的营收大盘会降低,这种一般只有国家出政策才会使用。
- 建活动,完成10单奖励10元,吸引骑手参与活动。
所以场景就是外卖订单和活动匹配。
规则引擎
- 动态表达式执行:支持类似 Java 语法的表达式,可以在运行时解析和执行。
- 轻量级:不依赖复杂框架,适合嵌入到各类 Java 应用中。
- 高性能:通过缓存、编译优化等机制提升执行效率。
- 安全性控制:可以限制可调用的方法、类、操作符等,防止恶意脚本执行。
- 扩展性强:支持自定义函数、操作符、宏等。
- 中文友好:支持中文变量名、中文操作符(如"如果"、"那么"),便于非程序员编写规则。
企业级项目落地
从开发系统的角度出发,我们要建设底层的能力,能业务是对能力的编排。规则引擎也是。
规则引擎首先定义脚本函数,然后定义表达式(能力),最后定义表达式组(业务)
小试牛刀:城市活动
- 脚本函数(能力)
bash
public class FunctionConfig {
public static String[][] customFunctions = {
{
"xInList", //判断x是否在list中
"function xInList(Object x,Object list){\n" +
" if (null == list || list.size() == 0) {\n" +
" return true;\n" +
" } else {\n" +
" return (x in list);\n" +
" }\n" +
"}"
}
};
}
- 表达式
bash
xInList(city,cityList)
- 表达式组(组)
bash
//城市活动-订单和活动匹配
public static List<ProcessNode> CITY_ACTIVITY_ORDER_MATCH = Arrays.asList(
new ProcessNode("城市匹配" ,"city", "cityList", "xInList(city,cityList)")
);
跑一个案例
bash
public class App {
public static void main(String[] args) throws Exception {
//创建一个城市活动
Activity activity = new Activity();
activity.setName("情人节晚上冲单奖");
activity.setCityList(Arrays.asList("上海", "北京"));
JSONObject activityJSON = JSONUtil.parseObj(activity);
//创建一个天津订单
Order order = new Order();
order.setCity("天津");
JSONObject orderJSON = JSONUtil.parseObj(order);
//初始化运行器
ExpressRunner runner = new ExpressRunner(true, true);
String[][] customFunctions = FunctionConfig.customFunctions;
for (String[] customFunction : customFunctions) {
runner.loadMutilExpress(null, customFunction[1]);
}
//执行匹配逻辑
Boolean matchStatus = true;
String matchFailReason = "";
//订单和活动匹配
List<ProcessNode> orderMatchActivity = ProcessConfig.CITY_ACTIVITY_ORDER_MATCH;
for (ProcessNode processNode : orderMatchActivity) {
String orderKey = processNode.getOrderKey();
String activityKey = processNode.getActivityKey();
String expression = processNode.getExpression();
//创建上下文,参数赋值
DefaultContext<String, Object> ctx = new DefaultContext<>();
ctx.put(activityKey, activityJSON.get(activityKey));
ctx.put(orderKey, orderJSON.get(orderKey));
//执行业务逻辑
matchStatus = matchStatus && (Boolean) runner.execute(expression, ctx, null, true, false);
if (!matchStatus){
matchFailReason = processNode.getName();
}
}
System.out.println("订单和活动匹配结果:" + matchStatus + ",匹配失败原因:" + matchFailReason);
}
}
输出结果
bash
订单和活动匹配结果:false,匹配失败原因:城市匹配
大刀阔斧:时间段活动
业务新增规则,要求只在晚上8点到9点才算
- 脚本函数(能力):逻辑比较复杂,通过Java好实现,
bash
public class TimeSegmentMatch {
public static boolean match(Date orderDate, JSONArray timeSegmentArray) {
if (timeSegmentArray == null || timeSegmentArray.size() == 0){
return true;
}
List<TimeSegment> timeSegmentList = JSONUtil.toList(JSONUtil.toJsonStr(timeSegmentArray), TimeSegment.class);
String orderDateFormat = new SimpleDateFormat("HH:mm:ss").format(orderDate);
for (TimeSegment timeSegment : timeSegmentList) {
if (orderDateFormat.compareTo(timeSegment.getSegmentStart()) >= 0 && orderDateFormat.compareTo(timeSegment.getSegmentEnd()) <= 0) {
return true;
}
}
return false;
}
}
- 表达式
bash
timeSegmentMatch(orderDate,timeSegmentList)
- 表达式组(组)
bash
//城市活动-订单和活动匹配
public static List<ProcessNode> TIME_SEGMENT_ACTIVITY_ORDER_MATCH = Arrays.asList(
new ProcessNode("时间段匹配" ,"orderDate", "timeSegmentList", "timeSegmentMatch(orderDate,timeSegmentList)")
);
跑一个案例:限制活动参与时间是20~21点,而订单时间是7点,不匹配
bash
public class App {
public static void main(String[] args) throws Exception {
//创建一个城市活动
Activity activity = new Activity();
activity.setName("情人节晚上冲单奖");
//限制参与活动的时间点
TimeSegment timeSegment8 = new TimeSegment();
timeSegment8.setSegmentStart("20:00:00");
timeSegment8.setSegmentEnd("21:00:00");
TimeSegment timeSegment9 = new TimeSegment();
timeSegment9.setSegmentStart("21:00:00");
timeSegment9.setSegmentEnd("22:00:00");
activity.setTimeSegmentList(Arrays.asList(timeSegment8, timeSegment9));
JSONObject activityJSON = JSONUtil.parseObj(activity);
//创建一个订单
Order order = new Order();
order.setOrderDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2026-02-14 07:00:00"));
JSONObject orderJSON = JSONUtil.parseObj(order);
//初始化运行器
ExpressRunner runner = new ExpressRunner(true, true);
//加载自定义函数
runner.addFunctionOfClassMethod("timeSegmentMatch", TimeSegmentMatch.class.getName(), "match", new String[]{"java.util.Date", "cn.hutool.json.JSONArray"}, null);
//执行匹配逻辑
Boolean matchStatus = true;
String matchFailReason = "";
//订单和活动匹配
List<ProcessNode> orderMatchActivity = ProcessConfig.TIME_SEGMENT_ACTIVITY_ORDER_MATCH;
for (ProcessNode processNode : orderMatchActivity) {
String orderKey = processNode.getOrderKey();
String activityKey = processNode.getActivityKey();
String expression = processNode.getExpression();
//创建上下文,参数赋值
DefaultContext<String, Object> ctx = new DefaultContext<>();
System.out.println(activityJSON.get(activityKey).getClass());
System.out.println(orderJSON.get(orderKey).getClass());
ctx.put(activityKey, activityJSON.get(activityKey));
ctx.put(orderKey, orderJSON.get(orderKey));
//执行业务逻辑
matchStatus = matchStatus && (Boolean) runner.execute(expression, ctx, null, true, false);
if (!matchStatus){
matchFailReason = processNode.getName();
}
}
System.out.println("订单和活动匹配结果:" + matchStatus + ",匹配失败原因:" + matchFailReason);
}
}
输出结果
bash
订单和活动匹配结果:false,匹配失败原因:时间段匹配
性能优化
优化性能的方式无非就是加缓存,少执行,用单例。本次也是一样。
- 函数只注册一次
- execute() 是线程安全的(QLExpress 官方保证)
- 高性能、低内存
bash
public class QLExpressEngine {
private static final ExpressRunner RUNNER = new ExpressRunner(true, true);
;
static {
// 应用启动时注册所有函数(只执行一次!)
registerBuiltInFunctions();
}
private static void registerBuiltInFunctions() {
try {
// 注册 Java 静态方法
RUNNER.addFunctionOfClassMethod("timeSegmentMatch", TimeSegmentMatch.class.getName(), "match", new String[]{"java.util.Date", "cn.hutool.json.JSONArray"}, null);
// 注册脚本函数 todo
} catch (Exception e) {
e.printStackTrace();
}
}
public static Boolean execute(String script, DefaultContext<String, Object> context)
throws Exception {
// ExpressRunner.execute() 是线程安全的!
return (Boolean) RUNNER.execute(script, context, null, true, false);
}
}
- 😃作者简介:大厂高级 Java 开发工程师
- 💻称 号:CSDN 博客专家✨、阿里云博客专家🌟
- 🔍公众号:云服务小管家。免费💡的阿里云服务器☁ 和云环境直接使用
- 💪生活:专注于后端技术分享🎓迷茫时可来瞅瞅码农轨迹🚶♂️
- 📋服务:提供模拟面试和简历辅导,提供生产项目。📩内推可私信✉
- 💬卷卷群:可以和大家一起学习,一起进步👀
- 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦