diff --git a/.gitignore b/.gitignore index 36b13f1..92f5439 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,4 @@ cython_debug/ # PyPI configuration file .pypirc +.DS_Store diff --git a/README.md b/README.md index 2694e6b..86e5456 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,42 @@ # py-enjoy -jfinal-enjoy 5.2.2 的python 3.9.0 实现,基于jpype1实现。 mrzhou@miw.cn \ No newline at end of file +jfinal-enjoy 5.2.2 的python 3.9.0 实现,基于jpype1实现。 +python port by mrzhou@miw.cn + +### 使用样例 +``` + # 1. 配置参数(必须修改为你的实际路径) + JAR_PATH = "enjoy-5.2.2.jar" # enjoy-5.2.2.jar的绝对/相对路径 + TEMPLATE_BASE_PATH = "./templates" # 模板文件所在目录(如index.html放在这个目录下) + + # 2. 创建Enjoy实例(初始化Engine) + try: + enjoy = Enjoy( + jar_path=JAR_PATH, + template_base_path=TEMPLATE_BASE_PATH + ) + + # 3. 准备模板参数 + render_data = { + "name": "JFinal Enjoy 5.2.2测试", + "hobbies": ["Python调用Java", "模板渲染", "跨语言开发"], + "user": {"age": 25, "gender": "男"} + } + + # 4. 渲染模板(tempStr是模板文件名,相对于TEMPLATE_BASE_PATH) + result = enjoy.render( + tempStr="index.html", # 模板文件:./templates/index.html + data=render_data # 渲染数据(Python字典) + ) + + # 5. 输出结果 + print("\n=== 模板渲染结果 ===") + print(result) + + except RuntimeError as e: + print(f"\n执行失败: {e}") + finally: + # 6. 关闭资源 + if 'enjoy' in locals(): + enjoy.close() +``` \ No newline at end of file diff --git a/enjoy-5.2.2.jar b/enjoy-5.2.2.jar new file mode 100644 index 0000000..ea9b0e3 Binary files /dev/null and b/enjoy-5.2.2.jar differ diff --git a/enjoy.py b/enjoy.py new file mode 100644 index 0000000..7f6cbee --- /dev/null +++ b/enjoy.py @@ -0,0 +1,159 @@ +import jpype +import os +from typing import Dict, Any, Optional + +class Enjoy: + """ + 适配JFinal Enjoy 5.2.2模板引擎的Python调用类 + 核心类:com.jfinal.template.Engine + 实现:mrzhou@miw.cn + """ + _instance: Optional['Enjoy'] = None + _jvm_started: bool = False + _engine: Optional[Any] = None # 保存JFinal Engine实例 + + def __new__(cls, jar_path: str, template_base_path: str = "./"): + """单例模式:全局唯一Engine实例""" + if cls._instance is None: + cls._instance = super().__new__(cls) + cls._instance.jar_path = os.path.abspath(jar_path) + cls._instance.template_base_path = os.path.abspath(template_base_path) + cls._instance._init_jvm() + cls._instance._init_engine() # 初始化JFinal Engine + return cls._instance + + def _init_jvm(self): + """启动JVM并加载enjoy-5.2.2.jar""" + if not self._jvm_started: + try: + jvm_path = jpype.getDefaultJVMPath() + # 修复:所有位置参数在前,关键字参数在后 + jpype.startJVM( + jvm_path, # 位置参数1 + "-ea", # 位置参数2 + f"-Djava.class.path={self.jar_path}", # 位置参数3 + "-Dfile.encoding=UTF-8", # 位置参数4(移到关键字参数前) + convertStrings=False # 关键字参数(必须在最后) + ) + self._jvm_started = True + print(f"JVM启动成功 | 工作目录: {jpype.java.lang.System.getProperty('user.dir')}") + except Exception as e: + raise RuntimeError(f"JVM启动失败: {e}") from e + + def _init_engine(self): + """初始化JFinal Engine实例(核心)""" + try: + # 加载JFinal Engine类 + Engine = jpype.JClass("com.jfinal.template.Engine") + # 获取默认Engine实例(JFinal推荐单例使用) + self._engine = Engine.use() + # 配置模板基础路径(关键:指定模板文件所在目录) + self._engine.setBaseTemplatePath(self.template_base_path) + # 可选配置:设置编码、开启热加载等 + self._engine.setEncoding("UTF-8") + self._engine.setDevMode(True) # 开发模式,便于调试 + print(f"Engine初始化成功 | 模板基础路径: {self.template_base_path}") + except jpype.JException as e: + e.printStackTrace() + raise RuntimeError(f"Engine初始化失败: {e.getMessage()}") from e + + def _convert_python_to_java(self, obj: Any) -> Any: + """转换Python数据为Java Map(适配Enjoy模板数据格式)""" + if isinstance(obj, dict): + # 优先使用JFinal的Map(也可用java.util.HashMap) + HashMap = jpype.JClass("java.util.HashMap") + java_map = HashMap() + for key, value in obj.items(): + # 递归转换嵌套数据 + if isinstance(value, (dict, list)): + java_map.put(key, self._convert_python_to_java(value)) + else: + java_map.put(key, value) + return java_map + elif isinstance(obj, list): + return jpype.JArray(jpype.JObject)([self._convert_python_to_java(x) for x in obj]) + else: + return obj + + def render(self, tempStr: str, data: Dict[str, Any]) -> str: + """ + 渲染Enjoy模板(核心方法) + :param tempStr: 模板文件名(相对于baseTemplatePath的路径,如index.html) + :param data: 模板渲染数据(Python字典) + :return: 渲染后的字符串 + """ + if not self._engine: + raise RuntimeError("Engine未初始化,请检查Jar包路径") + + try: + # 1. 转换Python字典为Java Map + java_data = self._convert_python_to_java(data) + # 2. 获取Template实例(Engine.getTemplate) + template = self._engine.getTemplate(tempStr) + # 3. 渲染模板:使用Writer接收结果 + StringWriter = jpype.JClass("java.io.StringWriter") + writer = StringWriter() + template.render(java_data, writer) + # 4. 转换为Python字符串 + result = writer.toString() + writer.close() + return result + + except jpype.JException as e: + print("\n=== JFinal Enjoy异常详情 ===") + e.printStackTrace() + raise RuntimeError(f"模板渲染失败: {e.getMessage()}") from e + except Exception as e: + raise RuntimeError(f"调用失败: {e}") from e + + def close(self): + """关闭JVM,释放资源""" + if self._jvm_started: + jpype.shutdownJVM() + self._jvm_started = False + self._engine = None + self.__class__._instance = None + print("JVM已关闭") + + def __del__(self): + """析构函数:自动关闭""" + self.close() + +# ------------------------------ +# 标准调用示例(适配enjoy-5.2.2.jar) +# ------------------------------ +if __name__ == "__main__": + # 1. 配置参数(必须修改为你的实际路径) + JAR_PATH = "enjoy-5.2.2.jar" # enjoy-5.2.2.jar的绝对/相对路径 + TEMPLATE_BASE_PATH = "./templates" # 模板文件所在目录(如index.html放在这个目录下) + + # 2. 创建Enjoy实例(初始化Engine) + try: + enjoy = Enjoy( + jar_path=JAR_PATH, + template_base_path=TEMPLATE_BASE_PATH + ) + + # 3. 准备模板参数 + render_data = { + "name": "JFinal Enjoy 5.2.2测试", + "hobbies": ["Python调用Java", "模板渲染", "跨语言开发"], + "user": {"age": 25, "gender": "男"} + } + + # 4. 渲染模板(tempStr是模板文件名,相对于TEMPLATE_BASE_PATH) + result = enjoy.render( + tempStr="index.html", # 模板文件:./templates/index.html + data=render_data # 渲染数据(Python字典) + ) + + # 5. 输出结果 + print("\n=== 模板渲染结果 ===") + print(result) + + except RuntimeError as e: + print(f"\n执行失败: {e}") + finally: + # 6. 关闭资源 + if 'enjoy' in locals(): + enjoy.close() \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..72fb283 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,29 @@ + + + + + + Document + + + + +#(name) + +
+#for(i : hobbies) + #(i) +#end + +
+#for(i : scores) + #(i) +#end + +
+#for(i : user) + #(i.key) #(i.value) +#end + + + \ No newline at end of file