Enjoy指令扩展管理常用文本模板

个人站:http://www.wenhaofan.com/article/20190304102258

平时在项目中使用短信模板 邮件模板以及 站内消息通知等文本模板一般都是通过手动的字符串拼接来完成,例如:"欢迎"+user.getName()+"加入俱乐部。"

然而这种方法不仅代码难看而且还不方便管理,因此为了更方便的在项目中管理使用这类文本模板,参考JFinal源码中的activerecord管理sql的代码来扩展了Enjoy的指令。


1.扩展代码

package com.autu.common.keys;
 
 
import com.jfinal.kit.StrKit;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;

/**
 * KeysDirective
 */
public class KeysDirective extends Directive {
	
	static final String KEYS_DIRECTIVE="_KEYS_DIRECTIVE";
	
	private String id;
	
	public void setExprList(ExprList exprList) {
		if (exprList.length() == 0) {
			throw new ParseException("The parameter of #keys directive can not be blank", location);
		}
		if (exprList.length() > 1) {
			throw new ParseException("Only one parameter allowed for #keys directive", location);
		}
		Expr expr = exprList.getExpr(0);
		if (expr instanceof Const && ((Const)expr).isStr()) {
		} else {
			throw new ParseException("The parameter of #keys directive must be String", location);
		}
		
		this.id = ((Const)expr).getStr();
	}
	
	
	public void exec(Env env, Scope scope, Writer writer) {
		String beforeKey=(String)scope.get(KeysDirective.KEYS_DIRECTIVE);
 
		String key = StrKit.isBlank(beforeKey) ? id : beforeKey + "." + id;
		scope.set(KEYS_DIRECTIVE, key);
	
		stat.exec(env, scope, writer);
	
	}
	
	public boolean hasEnd() {
		return true;
	}
}
package com.autu.common.keys;
 
 
import java.util.Map;

import com.jfinal.kit.StrKit;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.Template;
import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;

/**
 * KeyDirective
 */
public class KeyDirective extends Directive {
	
	static final String KEY_DIRECTIVE="_KEY_DIRECTIVE";
	
	private String id;
	
	public void setExprList(ExprList exprList) {
		if (exprList.length() == 0) {
			throw new ParseException("The parameter of #key directive can not be blank", location);
		}
		if (exprList.length() > 1) {
			throw new ParseException("Only one parameter allowed for #key directive", location);
		}
		Expr expr = exprList.getExpr(0);
		if (expr instanceof Const && ((Const)expr).isStr()) {
		} else {
			throw new ParseException("The parameter of #key directive must be String", location);
		}
		
		this.id = ((Const)expr).getStr();
	}
	
	
	@SuppressWarnings("unchecked")
	public void exec(Env env, Scope scope, Writer writer) {
		String nameSpace = (String)scope.get(KeysDirective.KEYS_DIRECTIVE);
		String key = StrKit.isBlank(nameSpace) ? id : nameSpace + "." + id;
		Map<String, Template> keyTemplateMap = (Map<String, Template>)scope.get(KeyKit.KEY_TEMPLATE_MAP_KEY);
		if (keyTemplateMap.containsKey(key)) {
			throw new ParseException("Key already exists with key : " + key, location);
		}
		
		keyTemplateMap.put(key, new Template(env, stat));
	}
	
	public boolean hasEnd() {
		return true;
	}
}
package com.autu.common.keys;

 
import com.jfinal.template.source.ISource;

/**
 * 封装 key 模板源
 */
class KeySource {
	
	String file;
	ISource source;
	
	KeySource(String file) {
		this.file = file;
		this.source = null;
	}
	
	KeySource(ISource source) {
		this.file = null;
		this.source = source;
	}
	
	boolean isFile() {
		return file != null;
	}
}
package com.autu.common.keys;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jfinal.kit.Kv;
import com.jfinal.kit.StrKit;
import com.jfinal.template.Engine;
import com.jfinal.template.Template;
import com.jfinal.template.source.ISource;
import com.jfinal.template.stat.ParseException;

/**
 * Email Kit
 */
public class KeyKit {

	static final String KEY_TEMPLATE_MAP_KEY = "_KEY_TEMPLATE_MAP_";
	static final boolean DEFAULT_DEV_MODE=false;
	public static final String MAIN_CONFIG_NAME = "keys";
	public String configName;
	private  boolean devMode=false;
	private Engine engine;
	private List<KeySource> keySourceList = new ArrayList<KeySource>();
	public Map<String, Template> keyTemplateMap;

	private static final Map<String,KeyKit> keyKitMap=new HashMap<>();
	
	public static KeyKit use(String configName) {
		return keyKitMap.get(configName);
	}
	
	public static KeyKit use() {
		return use(MAIN_CONFIG_NAME);
	}
	
	private KeyKit(boolean devMode) {
		this(MAIN_CONFIG_NAME, devMode);
	}

	public static KeyKit load(String configName) {
		
		if(keyKitMap.containsKey(configName)) {
			return keyKitMap.get(configName);
		}
		
		return new KeyKit(configName,DEFAULT_DEV_MODE);
	}
	
	public static KeyKit load(boolean devMode) {
		if(keyKitMap.containsKey(MAIN_CONFIG_NAME)) {
			return keyKitMap.get(MAIN_CONFIG_NAME);
		}
		return new KeyKit(devMode);
	}

	public static KeyKit load(String configName, boolean devMode) {
		if(keyKitMap.containsKey(configName)) {
			return keyKitMap.get(configName);
		}
		return new KeyKit(configName, devMode);
	}

	private KeyKit(String configName, boolean devMode) {
		this.configName = configName;
		this.devMode = devMode;

		if(keyKitMap.containsKey(configName)) {
			throw new ParseException("Key already exists", null );
		}
		
		engine = new Engine(configName);
		engine.setDevMode(devMode);
		engine.addDirective("key", KeyDirective.class);
		engine.addDirective("keys", KeysDirective.class);
		engine.addSharedMethod(new StrKit());
		
		
		keyKitMap.put(configName, this);
	}

	public KeyKit(String configName) {
		this(configName, false);
	}

	public Engine getEngine() {
		return engine;
	}

	public void setDevMode(boolean devMode) {
		this.devMode = devMode;
		engine.setDevMode(devMode);
	}

	public KeyKit setBaseKeyTemplatePath(String baseKeyTemplatePath) {
		engine.setBaseTemplatePath(baseKeyTemplatePath);
		return this;
	}

	public KeyKit addTemplate(String KeyTemplate) {
		if (StrKit.isBlank(KeyTemplate)) {
			throw new IllegalArgumentException("keyTemplate can not be blank");
		}
		keySourceList.add(new KeySource(KeyTemplate));
		return this;
	}

	public void addTemplate(ISource keyTemplate) {
		if (keyTemplate == null) {
			throw new IllegalArgumentException("keyTemplate can not be null");
		}
		keySourceList.add(new KeySource(keyTemplate));
	}

	public synchronized KeyKit parseKeysTemplate() {
		Map<String, Template> keyTemplateMap = new HashMap<String, Template>(512, 0.5F);
		for (KeySource ss : keySourceList) {
			Template template = ss.isFile() ? engine.getTemplate(ss.file) : engine.getTemplate(ss.source);
			Map<Object, Object> data = new HashMap<Object, Object>();
			data.put(KEY_TEMPLATE_MAP_KEY, keyTemplateMap);
			template.renderToString(data);
		}
		this.keyTemplateMap = keyTemplateMap;
		return this;
	}

	private void reloadModifiedKeyTemplate() {
		engine.removeAllTemplateCache(); // 去除 Engine 中的缓存,以免 get 出来后重新判断 isModified
		parseKeysTemplate();
	}

	private boolean isKeyTemplateModified() {
		for (Template template : keyTemplateMap.values()) {
			if (template.isModified()) {
				return true;
			}
		}
		return false;
	}

	private Template getKeyTemplate(String key) {
		Template template = keyTemplateMap.get(key);
		if (template == null) { // 此 if 分支,处理起初没有定义,但后续不断追加 key 的情况
			if (!devMode) {
				return null;
			}
			if (isKeyTemplateModified()) {
				synchronized (this) {
					if (isKeyTemplateModified()) {
						reloadModifiedKeyTemplate();
						template = keyTemplateMap.get(key);
					}
				}
			}
			return template;
		}

		if (devMode && template.isModified()) {
			synchronized (this) {
				template = keyTemplateMap.get(key);
				if (template.isModified()) {
					reloadModifiedKeyTemplate();
					template = keyTemplateMap.get(key);
				}
			}
		}
		return template;
	}
 
	
	/**
	 * 示例: 1:模板 定义 #key("key")
	 * 
	 * #end
	 *
	 * 2:java 代码 getContent("key", Kv);
	 */
	public String getContent(String key, Kv kv) {
		Template template = getKeyTemplate(key);
		if (template == null) {
			return null;
		}
		return template.renderToString(kv);
	}

	
	public java.util.Set<java.util.Map.Entry<String, Template>> getKeyMapEntrySet() {
		return keyTemplateMap.entrySet();
	}

	public String toString() {
		return "KeyTplKit for config : " + configName;
	}
}

2.模板文件

image.png

    3.1 all_emails.tpl

#keys("comment")
	#include("innerKeys.tpl")
#end

    3.2 comment.tpl

#key("comment_title")
[#(config.title)评论通知] Re:#(title)
#end

    3.3 inner.tpl

#keys("inner")
   #include("comment.tpl")
#end

3.创建管理工具

KeyKit.load(p.getBoolean("devMode", false))
    .setBaseKeyTemplatePath(PathKit.getRootClassPath() + "/email")
    .addTemplate("all_emails.tpl")
    .parseKeysTemplate();

4.使用

   String keyContent=	KeyKit.use().getContent("comment.inner.comment_title", 
	    		Kv.by("config",new Config().setTitle("test")).set("title", "title1"));
		System.out.println(keyContent);

输出结果

  [test评论通知] Re:title1

5.说明

    此处演示的为多层嵌套keys的使用,单层和jfinal的sql管理一样使用

评论区

JFinal

2019-03-04 12:56

对于 enjoy 的扩展十分深入,赞一个

对于一般的需求还有更简单的使用方式,例如,先将所有模板通过 #define 事先全部定义好,假定文件名为 template_define.txt:

#define comment()
[#(config.title)评论] Re:#(title)
#end

#define commnetReply()
[#(config.title)评论回复] Re:#(reply)
#end

然后通过 engine.addSharedFunction("template_define.txt") 将其添加为共享函数,然后就可以在任意地方使用了:
Engine engine = Engine.use();
Kv kv = kv.by("config", config).set(...);
String ret = engine.getTemplateByString("#@comment()").renderToString(kv);
System.out.print(ret);

好多好玩、简单、方便的用法呢

JFinal

2019-03-04 13:01

如果觉得下面这行代码的代码量比较大:
String ret = engine.getTemplateByString("#@comment()").renderToString(kv);

可以做个工具类:
public class TemplateKit {
public static String renderToString(String functionName, Kv kv) {
String fn = "#@" + functionName + "()";
return Engine.use().getTemplateByString(fn).renderToString(kv);
}

上面的 functionName 参数只需要传一个在 template_define.txt 中通过 #define 定义的函数名就可以了,如: "comment"

那么使用的时候就变成了:
Kv kv = kv.by("config", config).set(...);
String ret = TemplateKit.renderToString("comment", kv);
System.out.print(ret);

是不是超级爽?

正负余

2019-03-04 13:04

@JFinal 可以的,单层用这种方法很方便,扩展的指令主要是为了方便xx.xx.xx以及sql管理这种多层级的需求

JFinal

2019-03-04 13:13

@正负余 多层需求扩展一下为好

maxwade

2019-03-04 22:11

之前一直在想jfinal有什么好用的邮件模板,总算出来了。收藏!

正负余

2019-03-05 09:43

@maxwade 哈哈哈 有用就很odk

laofa

2020-04-19 20:35

engine.addSharedFunction("template_define.txt") template_define.txt这个文件要放在哪里呢?

热门分享

扫码入社