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

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

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

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


1.扩展代码

  1. package com.autu.common.keys;
  2.  
  3.  
  4. import com.jfinal.kit.StrKit;
  5. import com.jfinal.template.Directive;
  6. import com.jfinal.template.Env;
  7. import com.jfinal.template.expr.ast.Const;
  8. import com.jfinal.template.expr.ast.Expr;
  9. import com.jfinal.template.expr.ast.ExprList;
  10. import com.jfinal.template.io.Writer;
  11. import com.jfinal.template.stat.ParseException;
  12. import com.jfinal.template.stat.Scope;
  13.  
  14. /**
  15.  * KeysDirective
  16.  */
  17. public class KeysDirective extends Directive {
  18. static final String KEYS_DIRECTIVE="_KEYS_DIRECTIVE";
  19. private String id;
  20. public void setExprList(ExprList exprList) {
  21. if (exprList.length() == 0) {
  22. throw new ParseException("The parameter of #keys directive can not be blank", location);
  23. }
  24. if (exprList.length() > 1) {
  25. throw new ParseException("Only one parameter allowed for #keys directive", location);
  26. }
  27. Expr expr = exprList.getExpr(0);
  28. if (expr instanceof Const && ((Const)expr).isStr()) {
  29. } else {
  30. throw new ParseException("The parameter of #keys directive must be String", location);
  31. }
  32. this.id = ((Const)expr).getStr();
  33. }
  34. public void exec(Env env, Scope scope, Writer writer) {
  35. String beforeKey=(String)scope.get(KeysDirective.KEYS_DIRECTIVE);
  36.  
  37. String key = StrKit.isBlank(beforeKey) ? id : beforeKey + "." + id;
  38. scope.set(KEYS_DIRECTIVE, key);
  39. stat.exec(env, scope, writer);
  40. }
  41. public boolean hasEnd() {
  42. return true;
  43. }
  44. }
  1. package com.autu.common.keys;
  2.  
  3.  
  4. import java.util.Map;
  5.  
  6. import com.jfinal.kit.StrKit;
  7. import com.jfinal.template.Directive;
  8. import com.jfinal.template.Env;
  9. import com.jfinal.template.Template;
  10. import com.jfinal.template.expr.ast.Const;
  11. import com.jfinal.template.expr.ast.Expr;
  12. import com.jfinal.template.expr.ast.ExprList;
  13. import com.jfinal.template.io.Writer;
  14. import com.jfinal.template.stat.ParseException;
  15. import com.jfinal.template.stat.Scope;
  16.  
  17. /**
  18.  * KeyDirective
  19.  */
  20. public class KeyDirective extends Directive {
  21. static final String KEY_DIRECTIVE="_KEY_DIRECTIVE";
  22. private String id;
  23. public void setExprList(ExprList exprList) {
  24. if (exprList.length() == 0) {
  25. throw new ParseException("The parameter of #key directive can not be blank", location);
  26. }
  27. if (exprList.length() > 1) {
  28. throw new ParseException("Only one parameter allowed for #key directive", location);
  29. }
  30. Expr expr = exprList.getExpr(0);
  31. if (expr instanceof Const && ((Const)expr).isStr()) {
  32. } else {
  33. throw new ParseException("The parameter of #key directive must be String", location);
  34. }
  35. this.id = ((Const)expr).getStr();
  36. }
  37. @SuppressWarnings("unchecked")
  38. public void exec(Env env, Scope scope, Writer writer) {
  39. String nameSpace = (String)scope.get(KeysDirective.KEYS_DIRECTIVE);
  40. String key = StrKit.isBlank(nameSpace) ? id : nameSpace + "." + id;
  41. Map<String, Template> keyTemplateMap = (Map<String, Template>)scope.get(KeyKit.KEY_TEMPLATE_MAP_KEY);
  42. if (keyTemplateMap.containsKey(key)) {
  43. throw new ParseException("Key already exists with key : " + key, location);
  44. }
  45. keyTemplateMap.put(key, new Template(env, stat));
  46. }
  47. public boolean hasEnd() {
  48. return true;
  49. }
  50. }
  1. package com.autu.common.keys;
  2.  
  3.  
  4. import com.jfinal.template.source.ISource;
  5.  
  6. /**
  7.  * 封装 key 模板源
  8.  */
  9. class KeySource {
  10. String file;
  11. ISource source;
  12. KeySource(String file) {
  13. this.file = file;
  14. this.source = null;
  15. }
  16. KeySource(ISource source) {
  17. this.file = null;
  18. this.source = source;
  19. }
  20. boolean isFile() {
  21. return file != null;
  22. }
  23. }
  1. package com.autu.common.keys;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.HashMap;
  5. import java.util.List;
  6. import java.util.Map;
  7.  
  8. import com.jfinal.kit.Kv;
  9. import com.jfinal.kit.StrKit;
  10. import com.jfinal.template.Engine;
  11. import com.jfinal.template.Template;
  12. import com.jfinal.template.source.ISource;
  13. import com.jfinal.template.stat.ParseException;
  14.  
  15. /**
  16.  * Email Kit
  17.  */
  18. public class KeyKit {
  19.  
  20. static final String KEY_TEMPLATE_MAP_KEY = "_KEY_TEMPLATE_MAP_";
  21. static final boolean DEFAULT_DEV_MODE=false;
  22. public static final String MAIN_CONFIG_NAME = "keys";
  23. public String configName;
  24. private  boolean devMode=false;
  25. private Engine engine;
  26. private List<KeySource> keySourceList = new ArrayList<KeySource>();
  27. public Map<String, Template> keyTemplateMap;
  28.  
  29. private static final Map<String,KeyKit> keyKitMap=new HashMap<>();
  30. public static KeyKit use(String configName) {
  31. return keyKitMap.get(configName);
  32. }
  33. public static KeyKit use() {
  34. return use(MAIN_CONFIG_NAME);
  35. }
  36. private KeyKit(boolean devMode) {
  37. this(MAIN_CONFIG_NAME, devMode);
  38. }
  39.  
  40. public static KeyKit load(String configName) {
  41. if(keyKitMap.containsKey(configName)) {
  42. return keyKitMap.get(configName);
  43. }
  44. return new KeyKit(configName,DEFAULT_DEV_MODE);
  45. }
  46. public static KeyKit load(boolean devMode) {
  47. if(keyKitMap.containsKey(MAIN_CONFIG_NAME)) {
  48. return keyKitMap.get(MAIN_CONFIG_NAME);
  49. }
  50. return new KeyKit(devMode);
  51. }
  52.  
  53. public static KeyKit load(String configName, boolean devMode) {
  54. if(keyKitMap.containsKey(configName)) {
  55. return keyKitMap.get(configName);
  56. }
  57. return new KeyKit(configName, devMode);
  58. }
  59.  
  60. private KeyKit(String configName, boolean devMode) {
  61. this.configName = configName;
  62. this.devMode = devMode;
  63.  
  64. if(keyKitMap.containsKey(configName)) {
  65. throw new ParseException("Key already exists", null );
  66. }
  67. engine = new Engine(configName);
  68. engine.setDevMode(devMode);
  69. engine.addDirective("key", KeyDirective.class);
  70. engine.addDirective("keys", KeysDirective.class);
  71. engine.addSharedMethod(new StrKit());
  72. keyKitMap.put(configName, this);
  73. }
  74.  
  75. public KeyKit(String configName) {
  76. this(configName, false);
  77. }
  78.  
  79. public Engine getEngine() {
  80. return engine;
  81. }
  82.  
  83. public void setDevMode(boolean devMode) {
  84. this.devMode = devMode;
  85. engine.setDevMode(devMode);
  86. }
  87.  
  88. public KeyKit setBaseKeyTemplatePath(String baseKeyTemplatePath) {
  89. engine.setBaseTemplatePath(baseKeyTemplatePath);
  90. return this;
  91. }
  92.  
  93. public KeyKit addTemplate(String KeyTemplate) {
  94. if (StrKit.isBlank(KeyTemplate)) {
  95. throw new IllegalArgumentException("keyTemplate can not be blank");
  96. }
  97. keySourceList.add(new KeySource(KeyTemplate));
  98. return this;
  99. }
  100.  
  101. public void addTemplate(ISource keyTemplate) {
  102. if (keyTemplate == null) {
  103. throw new IllegalArgumentException("keyTemplate can not be null");
  104. }
  105. keySourceList.add(new KeySource(keyTemplate));
  106. }
  107.  
  108. public synchronized KeyKit parseKeysTemplate() {
  109. Map<String, Template> keyTemplateMap = new HashMap<String, Template>(512, 0.5F);
  110. for (KeySource ss : keySourceList) {
  111. Template template = ss.isFile() ? engine.getTemplate(ss.file) : engine.getTemplate(ss.source);
  112. Map<Object, Object> data = new HashMap<Object, Object>();
  113. data.put(KEY_TEMPLATE_MAP_KEY, keyTemplateMap);
  114. template.renderToString(data);
  115. }
  116. this.keyTemplateMap = keyTemplateMap;
  117. return this;
  118. }
  119.  
  120. private void reloadModifiedKeyTemplate() {
  121. engine.removeAllTemplateCache(); // 去除 Engine 中的缓存,以免 get 出来后重新判断 isModified
  122. parseKeysTemplate();
  123. }
  124.  
  125. private boolean isKeyTemplateModified() {
  126. for (Template template : keyTemplateMap.values()) {
  127. if (template.isModified()) {
  128. return true;
  129. }
  130. }
  131. return false;
  132. }
  133.  
  134. private Template getKeyTemplate(String key) {
  135. Template template = keyTemplateMap.get(key);
  136. if (template == null) { // 此 if 分支,处理起初没有定义,但后续不断追加 key 的情况
  137. if (!devMode) {
  138. return null;
  139. }
  140. if (isKeyTemplateModified()) {
  141. synchronized (this) {
  142. if (isKeyTemplateModified()) {
  143. reloadModifiedKeyTemplate();
  144. template = keyTemplateMap.get(key);
  145. }
  146. }
  147. }
  148. return template;
  149. }
  150.  
  151. if (devMode && template.isModified()) {
  152. synchronized (this) {
  153. template = keyTemplateMap.get(key);
  154. if (template.isModified()) {
  155. reloadModifiedKeyTemplate();
  156. template = keyTemplateMap.get(key);
  157. }
  158. }
  159. }
  160. return template;
  161. }
  162.  
  163. /**
  164.  * 示例: 1:模板 定义 #key("key")
  165.  * 
  166.  * #end
  167.  *
  168.  * 2:java 代码 getContent("key", Kv);
  169.  */
  170. public String getContent(String key, Kv kv) {
  171. Template template = getKeyTemplate(key);
  172. if (template == null) {
  173. return null;
  174. }
  175. return template.renderToString(kv);
  176. }
  177.  
  178. public java.util.Set<java.util.Map.Entry<String, Template>> getKeyMapEntrySet() {
  179. return keyTemplateMap.entrySet();
  180. }
  181.  
  182. public String toString() {
  183. return "KeyTplKit for config : " + configName;
  184. }
  185. }

2.模板文件

image.png

    3.1 all_emails.tpl

  1. #keys("comment")
  2. #include("innerKeys.tpl")
  3. #end

    3.2 comment.tpl

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

    3.3 inner.tpl

  1. #keys("inner")
  2.    #include("comment.tpl")
  3. #end

3.创建管理工具

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

4.使用

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

输出结果

  1.   [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这个文件要放在哪里呢?

热门分享

扫码入社