REQ-2135: 提供更加强大的模版内容解析

This commit is contained in:
yanglin 2024-04-01 14:04:35 +08:00
parent c3042796a5
commit 8bfdc54d20
6 changed files with 71 additions and 44 deletions

View File

@ -93,6 +93,7 @@ import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -785,6 +786,9 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
.orElse(null);
// 解析并替换掉路由地址中的动态参数变量
JSONObject routerParam = JSONObjectUtil.parseObject(adapter.getRouterParams());
routerParam.put(MessageRouterUtil.CTX, ImmutableMap.of(
"business", adapter.getTodoBusiness(),
"todo", adapter.getTodo()));
JSONObject authParam = new JSONObject();
if (!routerParam.containsKey("ouId")) {
authParam.put("ouId", adapter.getOuId());

View File

@ -13,12 +13,14 @@ import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import cn.axzo.msg.center.service.enums.TodoType;
import cn.axzo.msg.center.service.enums.YesOrNo;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import java.util.Date;
/**
* @author yanglin
*/
@Getter
public class TodoRecordAdapter implements PendingRecordAdapter {
private final TodoBusiness business;
@ -226,4 +228,9 @@ public class TodoRecordAdapter implements PendingRecordAdapter {
public Boolean isRead() {
return getState() == PendingMessageStateEnum.READ;
}
@Override
public TodoBusiness getTodoBusiness() {
return business;
}
}

View File

@ -47,6 +47,7 @@ import java.util.stream.Collectors;
public final class MessageRouterUtil {
public static final String DETAIL_ROUTER_DESC = "详情";
public static final String CTX = "ctx";
/**
* 获取业务详情路由策略
@ -227,11 +228,13 @@ public final class MessageRouterUtil {
}
private static String concatRouterParam(String originalUrl, JSONObject routerParam) {
if (routerParam.isEmpty()) {
JSONObject copy = new JSONObject(routerParam);
copy.remove(CTX);
if (copy.isEmpty()) {
return originalUrl;
}
StringBuilder paramBuilder = new StringBuilder();
for (Map.Entry<String, Object> entry : routerParam.entrySet()) {
for (Map.Entry<String, Object> entry : copy.entrySet()) {
if (originalUrl.contains(entry.getKey() + "=")) {
continue;
}

View File

@ -19,6 +19,10 @@
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-auth-spring-boot-starter</artifactId>

View File

@ -1,11 +1,18 @@
package cn.axzo.msg.center.common.utils;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.ognl.NullHandler;
import org.apache.ibatis.ognl.OgnlRuntime;
import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.scripting.xmltags.OgnlCache;
import cn.axzo.basics.common.exception.ServiceException;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.BuilderException;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;
import java.util.Map;
@ -14,59 +21,53 @@ import java.util.Map;
*
* @author yanglin
*/
@Slf4j
public class TemplateParser {
static {
OgnlRuntime.setNullHandler(Map.class, new MapPropertyHandler());
}
public static String parseNumberSign(String expression, Object root) {
if (expression == null || root == null)
return expression;
GenericTokenParser parser = new GenericTokenParser("#{", "}", new OgnlTokenHandler(root));
return parser.parse(expression);
return parseImpl("#{", "}", expression, root);
}
public static String parseDollarSign(String expression, Object root) {
return parseImpl("${", "}", expression, root);
}
@SuppressWarnings("SameParameterValue")
private static String parseImpl(String openToken, String closeToken, String expression, Object root) {
if (expression == null || root == null)
return expression;
GenericTokenParser parser = new GenericTokenParser("${", "}", new OgnlTokenHandler(root));
return parser.parse(expression);
try {
StandardEvaluationContext evalContext = new StandardEvaluationContext(root);
evalContext.addPropertyAccessor(new NullableMapAccessor());
evalContext.addPropertyAccessor(new ReflectivePropertyAccessor());
TemplateParserContext templateContext = new TemplateParserContext(openToken, closeToken);
Expression exp = new SpelExpressionParser().parseExpression(expression, templateContext);
return exp.getValue(evalContext, String.class);
} catch (BuilderException e) {
log.warn("无效的模版内容, 请检查格式是否正确. expression={}, value={}",
expression, JSON.toJSONString(root), e);
throw new ServiceException(String.format("无效的模版内容, 请检查格式是否正确. expression=%s, value=%s",
expression, JSON.toJSONString(root)));
}
}
@RequiredArgsConstructor
private static class OgnlTokenHandler implements TokenHandler {
private final Object root;
private static class NullableMapAccessor extends MapAccessor {
@Override
public String handleToken(String content) {
Object value = OgnlCache.getValue(content, root);
return value == null ? "" : String.valueOf(value);
public boolean canRead(@NonNull EvaluationContext context, Object target, @NonNull String name) {
return target instanceof Map;
}
}
private static class MapPropertyHandler implements NullHandler {
@Override
public Object nullMethodResult(Map context, Object target, String methodName, Object[] args) {
return null;
@NonNull
public TypedValue read(@NonNull EvaluationContext context, Object target, @NonNull String name) {
Map<?, ?> map = (Map<?, ?>) target;
Object value = map.get(name);
if (value == null)
return TypedValue.NULL;
return new TypedValue(value);
}
/**
* 支持流程的 [_PENDING_VARIABLES] 的写法. 只支持顶层的这种写法, 不然词法解析不了
*/
@Override
public Object nullPropertyValue(Map context, Object target, Object property) {
if (target instanceof Map && property instanceof String) {
String key = (String) property;
String bracketsKey = String.format("[%s]", key.trim());
if (((Map) target).containsKey(bracketsKey))
return bracketsKey;
}
return null;
}
}
}

View File

@ -102,4 +102,12 @@ public interface PendingRecordAdapter {
default Long getPromoterOuId() {
return 0L;
}
default Todo getTodo() {
return null;
}
default TodoBusiness getTodoBusiness() {
return null;
}
}