设计目标:尽量灵活的文案配置化
设计方案:
- 占位符支持更多的取值逻辑。
- 文案进行拆分,当取不到值时可以使用默认文案。
${}:占位符
英文",“:取值优先级。eg:${area,city,province} 先取区,区没有再取市,以此类推。
英文”.“:子属性。eg:${area.code} 取区编码
英文”:":设置默认值,可为空串 ${area:未知区域} 取区信息,没有展示“未知区域”
文案取值没有取到,就展示默认文案。
eg:
正常文案:尊敬的用户,您的服务即将到期,距离过期时间只有${remainingTime}天,请及时续费。默认文案:尊敬的用户,您的服务即将到期,距离过期时间不足10天,请及时续费。
表设计
type:类型(用于支持不同业务)
code :文案编码(同一文案使用同样的文案编码)
desc:文案描述(文案说明,编码可使用)
priority 优先级(若顺序相同,则根据优先级和替换占位符结果,决定对外展示文案)
order 顺序(子文案顺序)
subTemplate:子文案
核心代码块:
public static final Pattern PLACEHOLDER = Pattern.compile("\\$\\{.[^$]*}");
private static <T> String replace(List<Template> templates, T data) {
StringBuilder finalTemplate = new StringBuilder();
Map<Integer, List<Template>> classflyMap = ListUtils.classflyMap(templates, Template::getOrder);
Set<Integer> orders = classflyMap.keySet();
List<Integer> orderList = orders.stream().sorted().collect(Collectors.toList());
for (Integer order : orderList) {
List<Template> value = classflyMap.get(order);
value.sort(Comparator.comparing(Template::getPriority));
for (Template template : value) {
String subTemplate = replace(template.getSubTemplate(), data);
if (subTemplate == null) {
continue;
}
finalTemplate.append(subTemplate);
break;
}
}
return finalTemplate.toString();
}
private static <T> String replace(String template, T data) {
Matcher matcher = PLACEHOLDER.matcher(template);
while (matcher.find()) {
String holder = matcher.group();
String paramsStr = holder.substring(2, holder.length() - 1);
String[] params = paramsStr.split(",");
String value = null;
for (String param : params) {
value = getValue(data, param);
if (value != null) {
break;
}
}
if (value == null) {
return null;
}
template = template.replace(holder, value);
}
return template;
}
private static <T> String getValue(T data, String param) {
if (param.contains(":")) {
String defaultValue = "";
String[] splits = param.split(":", 2);
if (splits.length == 2) {
defaultValue = splits[1];
}
param = splits[0];
String value = getValue(param, data);
if (value == null) {
return defaultValue;
} else {
return value;
}
}
return getValue(param, data);
}
private static <T> String getValue(String fieldName, T data) {
if (data == null) {
return null;
}
Class<?> aClass = data.getClass();
try {
String[] splits = fieldName.split("\\.");
Field declaredField = null;
Object obj = data;
for (String split : splits) {
if (declaredField == null) {
declaredField = aClass.getDeclaredField(split);
} else {
declaredField = declaredField.getType().getDeclaredField(split);
}
declaredField.setAccessible(true);
obj = declaredField.get(obj);
if (obj == null) {
return null;
}
}
return String.valueOf(obj);
} catch (Exception e) {
System.out.println(e);
return null;
}
}
测试:
@Data
public static class Template {
private String type;
private String code;
private String desc;
private Integer order;
private Integer priority;
private String subTemplate;
public Template(String type, String code, String desc, Integer order, Integer priority, String subTemplate) {
this.type = type;
this.code = code;
this.desc = desc;
this.order = order;
this.priority = priority;
this.subTemplate = subTemplate;
}
}
@Data
public static class data {
private String weather;
private Area area;
private String direction;
private String power;
}
@Data
public static class Area {
private String city;
private String province;
public Area(String city, String province) {
this.city = city;
this.province = province;
}
}
public static List<Template> testData() {
ArrayList<Template> templates = new ArrayList<>();
templates.add(new Template("type", "code", "测试", 1, 1, "今日天气:${weather:-},气温最低${area.city,area.province:-},最高${area.city,area.province:-}。"));
templates.add(new Template("type", "code", "测试", 2, 1, "今日风向:${direction},风力${power}。请酌情增减衣物"));
templates.add(new Template("type", "code", "测试", 2, 2, "请酌情增减衣物"));
return templates;
}
public static void main(String[] args) {
data data = new data();
data.setWeather("晴");
data.setArea(new Area("4", "10"));
data.setPower("3");
data.setDirection("west");
System.out.println(replace(testData(), data));
}