From 39ab15287edd4ad3a08f90bfe6d2e4bf951e7c48 Mon Sep 17 00:00:00 2001 From: CodeCaster Date: Sat, 8 Nov 2025 20:12:37 +0800 Subject: [PATCH 1/4] =?UTF-8?q?[fit-launcher]=20=E4=BD=BF=E7=94=A8=20Node.?= =?UTF-8?q?js=20=E9=87=8D=E5=86=99=E5=90=AF=E5=8A=A8=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=EF=BC=8C=E5=AE=9E=E7=8E=B0=E7=9C=9F=E6=AD=A3=E7=9A=84=E8=B7=A8?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改进: 1. 启动脚本重构 - 使用 Node.js 重写核心启动逻辑 (fit.js) - Unix/Linux/macOS 使用 fit 脚本(Node.js 实现) - Windows 使用 fit.cmd 批处理文件 - 移除旧的 fit.bat 文件 2. 跨平台兼容性 - 解决 Bash 脚本在不同系统上的兼容性问题 - 避免 macOS 上 readlink -f 不可用的问题 - 统一 Windows/Linux/macOS 的使用体验 3. 功能增强 - 改进错误处理和错误信息输出 - 正确处理进程信号 (SIGINT/SIGTERM) - 更清晰的参数解析逻辑 - 更好的代码可维护性 4. 文档更新 - 重构模块 README,从模块级别进行说明 - 添加架构设计、启动流程等技术文档 - 完善使用说明和故障排除指南 技术栈:Node.js 12+ 命令接口保持完全兼容,无需修改现有文档和脚本 --- framework/fit/java/fit-launcher/README.md | 296 +++++++++++++++++- .../fit-launcher/src/main/resources/bin/fit | 77 +---- .../src/main/resources/bin/fit.bat | 2 - .../src/main/resources/bin/fit.cmd | 2 + .../src/main/resources/bin/fit.js | 164 ++++++++++ 5 files changed, 456 insertions(+), 85 deletions(-) mode change 100644 => 100755 framework/fit/java/fit-launcher/src/main/resources/bin/fit delete mode 100644 framework/fit/java/fit-launcher/src/main/resources/bin/fit.bat create mode 100644 framework/fit/java/fit-launcher/src/main/resources/bin/fit.cmd create mode 100755 framework/fit/java/fit-launcher/src/main/resources/bin/fit.js diff --git a/framework/fit/java/fit-launcher/README.md b/framework/fit/java/fit-launcher/README.md index e1e4bfdae..cdb33b886 100644 --- a/framework/fit/java/fit-launcher/README.md +++ b/framework/fit/java/fit-launcher/README.md @@ -1,18 +1,296 @@ -
常规FIT应用启动程序
+# FIT Discrete Launcher(FIT 离散启动器) -[TOC] +## 模块概述 -# 说明 +`fit-discrete-launcher` 是 FIT 框架的应用启动入口模块,提供了完整的应用程序启动、类加载和进程管理能力。该模块支持 FIT 应用以离散(独立进程)模式运行,是 FIT 框架"聚散部署"能力的核心实现之一。 -在常规FIT应用中,以目录展开形式被安装在OS中,通过在环境变量中配置`FIT_HOME`来指定应用程序所在根目录。 +**主要职责:** +- 应用程序的启动引导 +- 自定义类加载器体系的初始化 +- 跨平台启动脚本的提供 +- 应用程序的生命周期管理 -应用程序可通过FIT提供的启动脚本,通过相应命令启动应用程序。 +## 核心特性 -# 运行时目录结构 +### 1. 离散启动能力 -![avatar](https://cloudmodelingapi.tools.huawei.com/cloudmodelingdrawiosvr/d/2Fay) +支持 FIT 应用以独立进程的方式启动和运行: +- 每个应用拥有独立的 JVM 进程 +- 完全隔离的运行环境 +- 支持多应用并行部署 -# ClassLoader模型 +### 2. 自定义类加载体系 -![avatar](https://cloudmodelingapi.tools.huawei.com/cloudmodelingdrawiosvr/d/2FfL) +实现了分层的类加载器架构: +- **SharedClassLoader**: 加载共享类库 +- **FrameworkClassLoader**: 加载框架核心类 +- 支持插件化的类隔离机制 +### 3. 跨平台启动脚本 + +提供基于 Node.js 的跨平台启动脚本: +- Unix/Linux/macOS: `fit` 脚本 +- Windows: `fit.cmd` 批处理文件 +- 统一的命令行接口 + +## 构建和安装 + +### 编译模块 + +在 `framework/fit/java` 目录下执行: + +```bash +mvn clean install +``` + +编译完成后,会生成: +- `fit-discrete-launcher-{version}.jar`: 启动器 JAR 文件 +- `build/bin/`: 启动脚本目录 + +### 构建产物 + +``` +build/ +├── fit-discrete-launcher-3.5.5-SNAPSHOT.jar +└── bin/ + ├── fit # Unix/Linux/macOS 启动脚本 + ├── fit.js # Node.js 核心脚本 + └── fit.cmd # Windows 启动脚本 +``` + +## 启动脚本使用说明 + +### 前置要求 + +- **Node.js**: 12.0+ (推荐 16.0+) +- **Java**: 17+ + +### Unix/Linux/macOS 使用方法 + +#### 启动应用 + +```bash +./fit start [Java参数] [程序参数] +``` + +**示例:** +```bash +# 基本启动 +./fit start + +# 指定 JVM 内存参数 +./fit start -Xmx1g -Xms512m + +# 传递应用参数 +./fit start -Xmx1g myapp config.yaml +``` + +#### Debug 模式启动 + +```bash +./fit debug [Java参数] [程序参数] +``` + +Debug 模式会自动添加以下 JVM 参数: +``` +-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 +``` + +启动后可以使用 IDE(如 IntelliJ IDEA)连接到端口 5005 进行远程调试。 + +#### 查看版本 + +```bash +./fit version +``` + +#### 查看帮助 + +```bash +./fit help +``` + +### Windows 使用方法 + +使用 `fit.cmd` 命令: + +```cmd +# 启动应用 +fit.cmd start + +# Debug 模式启动 +fit.cmd debug + +# 查看版本 +fit.cmd version + +# 查看帮助 +fit.cmd help + +# 传递参数 +fit.cmd start -Xmx1g -Xms512m myapp config.yaml +``` + +### 参数传递机制 + +启动脚本会自动区分 Java 参数和程序参数: + +- **Java 参数**: 以 `-` 开头的参数(如 `-Xmx512m`、`-Dproperty=value`) +- **程序参数**: 不以 `-` 开头的参数 + +**完整示例:** +```bash +./fit start -Xmx1g -Dplugin.path=/custom/path myapp config.yaml +``` + +实际执行的 Java 命令: +```bash +java -Xmx1g -Dplugin.path=/custom/path \ + -Dsun.io.useCanonCaches=true \ + -Djdk.tls.client.enableSessionTicketExtension=false \ + -Dplugin.fit.dynamic.plugin.directory=/current/working/directory \ + -jar fit-discrete-launcher-3.5.5-SNAPSHOT.jar \ + myapp config.yaml +``` + +### 环境变量和系统属性 + +启动脚本会自动设置以下 Java 系统属性: + +| 系统属性 | 默认值 | 说明 | +|---------|--------|------| +| `sun.io.useCanonCaches` | `true` | 启用文件路径缓存,提升性能 | +| `jdk.tls.client.enableSessionTicketExtension` | `false` | 禁用 TLS 会话票证扩展 | +| `plugin.fit.dynamic.plugin.directory` | 当前工作目录 | 动态插件加载目录 | + +## 技术实现 + +### 启动脚本实现(Node.js) + +#### 为什么使用 Node.js? + +相比传统的 Bash 脚本,Node.js 实现具有以下优势: + +1. **跨平台兼容性**: 可以在 Windows、Linux、macOS 上无缝运行 +2. **无需特殊处理**: 避免了 Shell 脚本在不同系统上的兼容性问题(如 macOS 上的 `readlink -f`) +3. **更好的可维护性**: JavaScript 语法更容易理解和维护 +4. **统一的开发体验**: 与现代开发工具保持一致 + +#### 脚本工作原理 + +1. 解析命令行参数 +2. 区分 Java 参数和程序参数 +3. 在脚本所在目录的上级目录查找 `fit-discrete-launcher-*.jar` 文件 +4. 构造完整的 Java 命令 +5. 使用 `child_process.spawn` 启动 Java 进程 +6. 继承标准输入/输出/错误流 +7. 正确处理进程信号(SIGINT、SIGTERM) + +### 类加载器实现 + +#### SharedClassLoader + +加载共享类库,位于 `lib/shared/` 目录: +- 基础工具类 +- 日志框架 +- 其他共享依赖 + +#### FrameworkClassLoader + +加载 FIT 框架核心类,位于 `lib/framework/` 目录: +- FIT IoC 容器 +- FIT 插件系统 +- FIT 运行时 + +## 故障排除 + +### 找不到 JAR 文件 + +**错误信息:** +``` +No fit-discrete-launcher-[version].jar file found. +``` + +**解决方法:** +1. 确保已经编译了项目:`mvn clean install` +2. 检查 `bin/..` 目录下是否存在 `fit-discrete-launcher-*.jar` 文件 +3. 确保脚本在正确的位置执行 + +### Node.js 未安装 + +**错误信息:** +``` +bash: node: command not found +``` +或 +``` +'node' 不是内部或外部命令... +``` + +**解决方法:** +从 [Node.js 官网](https://nodejs.org/) 下载并安装 Node.js。 + +### Java 未安装或版本不正确 + +**错误信息:** +``` +Failed to start Java process: spawn java ENOENT +``` + +**解决方法:** +1. 安装 Java 17 或更高版本 +2. 确保 `java` 命令在系统 PATH 中 + +验证 Java 安装: +```bash +java -version +``` + +### 类加载错误 + +**错误信息:** +``` +ClassNotFoundException: ... +``` + +**可能原因:** +1. 缺少必要的依赖库 +2. 类加载器配置错误 +3. JAR 文件损坏 + +**解决方法:** +1. 检查 `lib/` 目录下的依赖是否完整 +2. 重新编译和打包应用 +3. 查看详细的错误日志 + +## 与其他启动方式的对比 + +FIT 框架支持多种启动方式: + +| 启动方式 | 适用场景 | 类加载方式 | 进程模型 | +|---------|---------|-----------|---------| +| **Discrete Launcher** | 生产环境、独立部署 | 自定义类加载器 | 独立进程 | +| IDE 直接启动 | 开发调试 | IDE 类加载器 | IDE 进程 | +| Spring Boot 集成 | Spring Boot 应用 | Spring Boot 类加载器 | Spring Boot 进程 | +| Aggregated Launcher | 聚合部署 | 共享类加载器 | 多应用单进程 | + +## 相关文档 + +- [FIT 快速入门指南](../../docs/framework/fit/java/quick-start-guide/) +- [FIT 用户指导手册](../../docs/framework/fit/java/user-guide-book/) +- [启动程序设计](./设计文档链接) +- [类加载架构设计](./设计文档链接) + +## 兼容性 + +- **Node.js**: 12.0.0+ (推荐 16.0.0+) +- **操作系统**: Windows 7+, macOS 10.12+, Linux (any modern distribution) +- **Java**: 17+ + +## 贡献 + +如果你发现任何问题或有改进建议,请提交 Issue 或 Pull Request。 + +## 许可证 + +与 FIT 框架保持一致。 diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/fit b/framework/fit/java/fit-launcher/src/main/resources/bin/fit old mode 100644 new mode 100755 index 094fbc271..1be1b52e4 --- a/framework/fit/java/fit-launcher/src/main/resources/bin/fit +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/fit @@ -1,74 +1,3 @@ -#!/bin/bash - -# 获取脚本所在的路径 -CURRENT_DIR=$(pwd) -CMD_PATH=$(dirname $(readlink -f $0)) - -# 定义版本号 -VERSION="3.5.5-SNAPSHOT" - -function start { - local DEBUG_ARGS="" - if [ "$1" == "debug" ]; then - DEBUG_ARGS=" -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" - fi - - # 初始化变量 - JAVA_ARGS="" - PROGRAM_ARGS="" - - # 迭代所有的参数,跳过第一个参数("start"或"debug") - shift - for arg in "$@"; do - # 检查参数是否以-开头 - if [[ $arg == -* ]]; then - JAVA_ARGS="$JAVA_ARGS $arg" - else - PROGRAM_ARGS="$PROGRAM_ARGS $arg" - fi - done - - cd $CMD_PATH/.. - # 查找符合 fit-discrete-launcher-[version].jar 格式的文件 - JAR_FILE=$(ls | grep "fit-discrete-launcher-.*.jar" | head -n 1) - if [ -z "$JAR_FILE" ]; then - echo "No fit-discrete-launcher-[version].jar file found." - else - # 构造并运行 Java 命令 - echo "Running command: java${DEBUG_ARGS}${JAVA_ARGS} -D\"sun.io.useCanonCaches=true\" -D\"plugin.fit.dynamic.plugin.directory=${CURRENT_DIR}\" -jar ${JAR_FILE}${PROGRAM_ARGS}" - java${DEBUG_ARGS}${JAVA_ARGS} -D"sun.io.useCanonCaches=true" -D"jdk.tls.client.enableSessionTicketExtension=false" -D"plugin.fit.dynamic.plugin.directory=${CURRENT_DIR}" -jar ${JAR_FILE}${PROGRAM_ARGS} - fi -} - -function version { - echo "Version: $VERSION" -} - -function help { - echo "Usage: fit [arguments]" - echo "Commands:" - echo " start Start the application" - echo " debug Start the application in debug mode" - echo " version Display the version number" - echo " help Display this help message" -} - -# 根据输入的参数执行相应的函数 -case "$1" in - start) - start "$@" - ;; - debug) - start "$@" - ;; - version) - version - ;; - help) - help - ;; - *) - echo "Unknown command: $1" - echo "Run 'fit help' for usage." - exit 1 -esac +#!/usr/bin/env node +// 这个文件会直接执行 fit.js +require('./fit.js'); diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/fit.bat b/framework/fit/java/fit-launcher/src/main/resources/bin/fit.bat deleted file mode 100644 index 4f0648189..000000000 --- a/framework/fit/java/fit-launcher/src/main/resources/bin/fit.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -bash %~dp0fit %* diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/fit.cmd b/framework/fit/java/fit-launcher/src/main/resources/bin/fit.cmd new file mode 100644 index 000000000..3af017509 --- /dev/null +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/fit.cmd @@ -0,0 +1,2 @@ +@echo off +node "%~dp0fit.js" %* diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js b/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js new file mode 100755 index 000000000..8f603d5ff --- /dev/null +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js @@ -0,0 +1,164 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const { spawn } = require('child_process'); + +// 定义版本号 +const VERSION = '3.5.5-SNAPSHOT'; + +// 获取脚本所在的路径 +const scriptDir = __dirname; +const currentDir = process.cwd(); + +/** + * 查找符合 fit-discrete-launcher-[version].jar 格式的文件 + */ +function findJarFile(directory) { + try { + const files = fs.readdirSync(directory); + const jarFile = files.find(file => + file.startsWith('fit-discrete-launcher-') && file.endsWith('.jar') + ); + return jarFile || null; + } catch (err) { + console.error(`Error reading directory ${directory}:`, err.message); + return null; + } +} + +/** + * 启动应用 + */ +function start(args) { + const isDebugMode = args.length > 0 && args[0] === 'debug'; + + // 初始化参数数组 + const javaArgs = []; + const programArgs = []; + + // 如果是 debug 模式,添加 debug 参数 + if (isDebugMode) { + javaArgs.push('-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005'); + } + + // 跳过第一个参数(start 或 debug) + const remainingArgs = args.slice(1); + + // 分离 Java 参数和程序参数 + for (const arg of remainingArgs) { + if (arg.startsWith('-')) { + javaArgs.push(arg); + } else { + programArgs.push(arg); + } + } + + // 切换到脚本所在目录的上级目录 + const jarDir = path.join(scriptDir, '..'); + + // 查找 JAR 文件 + const jarFile = findJarFile(jarDir); + + if (!jarFile) { + console.error('No fit-discrete-launcher-[version].jar file found.'); + process.exit(1); + } + + // 构造 Java 命令参数 + const javaCommand = 'java'; + const commandArgs = [ + ...javaArgs, + `-Dsun.io.useCanonCaches=true`, + `-Djdk.tls.client.enableSessionTicketExtension=false`, + `-Dplugin.fit.dynamic.plugin.directory=${currentDir}`, + '-jar', + path.join(jarDir, jarFile), + ...programArgs + ]; + + // 打印运行命令 + console.log(`Running command: ${javaCommand} ${commandArgs.join(' ')}`); + + // 执行 Java 命令 + const javaProcess = spawn(javaCommand, commandArgs, { + stdio: 'inherit', + cwd: jarDir + }); + + // 处理进程退出 + javaProcess.on('exit', (code) => { + process.exit(code || 0); + }); + + // 处理错误 + javaProcess.on('error', (err) => { + console.error('Failed to start Java process:', err.message); + process.exit(1); + }); + + // 处理 SIGINT 和 SIGTERM 信号 + process.on('SIGINT', () => { + javaProcess.kill('SIGINT'); + }); + + process.on('SIGTERM', () => { + javaProcess.kill('SIGTERM'); + }); +} + +/** + * 显示版本号 + */ +function version() { + console.log(`Version: ${VERSION}`); +} + +/** + * 显示帮助信息 + */ +function help() { + console.log('Usage: fit [arguments]'); + console.log('Commands:'); + console.log(' start Start the application'); + console.log(' debug Start the application in debug mode'); + console.log(' version Display the version number'); + console.log(' help Display this help message'); +} + +/** + * 主函数 + */ +function main() { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error('Unknown command: (no command provided)'); + console.error("Run 'fit help' for usage."); + process.exit(1); + } + + const command = args[0]; + + switch (command) { + case 'start': + start(args); + break; + case 'debug': + start(args); + break; + case 'version': + version(); + break; + case 'help': + help(); + break; + default: + console.error(`Unknown command: ${command}`); + console.error("Run 'fit help' for usage."); + process.exit(1); + } +} + +// 执行主函数 +main(); From d968a2b4a038b086347b7458c6a247b03d86a893 Mon Sep 17 00:00:00 2001 From: CodeCaster Date: Sat, 8 Nov 2025 23:20:07 +0800 Subject: [PATCH 2/4] =?UTF-8?q?[project-config]=20=E6=B7=BB=E5=8A=A0=20Cla?= =?UTF-8?q?ude=20Code=20=E9=A1=B9=E7=9B=AE=E8=A7=84=E5=88=99=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加项目级别的开发规范和自动化规则: 1. 文件权限管理规则 - 动态获取文件所有者信息,避免硬编码用户名 - 从 pom.xml 等参考文件自动获取权限设置 - 支持跨协作者使用,提高团队协作便利性 2. Pull Request 提交规范 - 自动读取 .github/PULL_REQUEST_TEMPLATE.md - 确保 PR 描述符合项目规范 - 简化 PR 创建流程 配置文件位置:.claude/project-rules.md 适用范围:所有使用 Claude Code 的项目协作者 --- .claude/project-rules.md | 184 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 .claude/project-rules.md diff --git a/.claude/project-rules.md b/.claude/project-rules.md new file mode 100644 index 000000000..c0b800085 --- /dev/null +++ b/.claude/project-rules.md @@ -0,0 +1,184 @@ +# FIT Framework 项目规则 + +## 文件权限管理规则 + +### 规则 1: 新增和修改文件的所有权 + +**重要:** 所有新增或修改的文件必须设置正确的所有者权限,确保用户可以自主修改。 + +**原则:** 不要硬编码用户名和用户组,而是从项目中已有文件动态获取权限信息。 + +**动态获取权限的方法:** + +```bash +# 方法1: 从项目根目录的 pom.xml 获取所有者 +REF_FILE="pom.xml" # 或其他稳定存在的文件 +OWNER=$(ls -l $REF_FILE | awk '{print $3}') +GROUP=$(ls -l $REF_FILE | awk '{print $4}') + +# 方法2: 获取当前工作目录的所有者 +OWNER=$(ls -ld . | awk '{print $3}') +GROUP=$(ls -ld . | awk '{print $4}') + +# 方法3: 使用 stat 命令(更可靠) +OWNER=$(stat -f "%Su" $REF_FILE) # macOS +GROUP=$(stat -f "%Sg" $REF_FILE) # macOS +# 或 +OWNER=$(stat -c "%U" $REF_FILE) # Linux +GROUP=$(stat -c "%G" $REF_FILE) # Linux +``` + +**实施方法:** + +```bash +# 1. 先获取参考文件的所有者 +OWNER_GROUP=$(ls -l pom.xml | awk '{print $3":"$4}') + +# 2. 应用到新文件 +sudo chown $OWNER_GROUP + +# 或者分步执行 +OWNER=$(ls -l pom.xml | awk '{print $3}') +GROUP=$(ls -l pom.xml | awk '{print $4}') +sudo chown $OWNER:$GROUP +``` + +**检查清单:** +- [ ] 使用 Write 工具创建新文件后,立即从已有文件获取权限并设置 +- [ ] 使用 Edit 工具修改文件时,确认文件所有者与项目其他文件一致 +- [ ] 批量创建文件后,统一修改所有权 +- [ ] 创建目录后,递归修改目录及其内容的所有权 + +**完整示例:** + +```bash +# ❌ 错误做法:硬编码用户信息 +Write(file_path, content) +Bash("sudo chown jiyujie:staff " + file_path) # 在其他人电脑上会失败 + +# ✅ 正确做法:动态获取权限 +Write(file_path, content) +# 从项目根目录的 pom.xml 获取所有者信息 +Bash("OWNER_GROUP=$(ls -l pom.xml | awk '{print $3\":\"$4}') && sudo chown $OWNER_GROUP " + file_path) + +# ✅ 更简洁的做法:批量处理 +Write(file1, content1) +Write(file2, content2) +Write(file3, content3) +Bash("OWNER_GROUP=$(ls -l pom.xml | awk '{print $3\":\"$4}') && sudo chown $OWNER_GROUP file1 file2 file3") +``` + +## Pull Request 提交规范 + +### 规则 2: PR 提交必须遵循项目规范 + +本项目的 PR 规范定义在 `.github/PULL_REQUEST_TEMPLATE.md` 文件中。 + +**强制要求:** +1. 创建 PR 前,必须先阅读 `.github/PULL_REQUEST_TEMPLATE.md` +2. PR 描述必须完整填写模板中的所有必填项 +3. 不需要每次让用户提醒查看 PR 模板 + +**PR 模板位置:** +``` +.github/PULL_REQUEST_TEMPLATE.md +``` + +**必填项清单:** + +1. **相关问题 / Related Issue** + - [ ] Issue 链接或说明这是微小修改 + +2. **变更类型 / Type of Change** + - [ ] 选择适当的变更类型(Bug修复/新功能/破坏性变更/文档/重构/性能优化/依赖升级/功能增强/代码清理) + +3. **变更目的 / Purpose of the Change** + - [ ] 详细描述变更的目的和必要性 + +4. **主要变更 / Brief Changelog** + - [ ] 列出主要的变更内容 + +5. **验证变更 / Verifying this Change** + - [ ] 测试步骤 + - [ ] 测试覆盖情况 + +6. **贡献者检查清单 / Contributor Checklist** + - [ ] 基本要求 + - [ ] 代码质量 + - [ ] 测试要求 + - [ ] 文档和兼容性 + +**自动化流程:** + +当用户要求创建 PR 时: +1. 自动读取 `.github/PULL_REQUEST_TEMPLATE.md` +2. 根据当前变更内容填写 PR 模板 +3. 生成完整的 PR 描述 +4. 提供推送和创建 PR 的命令 + +**示例工作流:** + +```bash +# 1. 自动读取 PR 模板 +Read(".github/PULL_REQUEST_TEMPLATE.md") + +# 2. 分析当前变更 +Bash("git diff --stat") +Bash("git log -1") + +# 3. 生成 PR 描述(根据模板) +# ... 填写各个部分 + +# 4. 推送并创建 PR +Bash("git push -u origin ") +Bash("gh pr create --base --title --body <description>") +``` + +## 通用最佳实践 + +### 文件操作 +- 创建文件后立即检查并修改权限 +- 使用 `ls -l` 验证文件所有者 +- 批量操作后统一修改权限 + +### Git 操作 +- 提交前检查 `.github/` 目录中的规范 +- 遵循项目的 commit message 格式 +- PR 描述要完整、清晰 + +### 文档更新 +- 修改代码时同步更新相关文档 +- 确保 README 的准确性 +- 添加必要的使用示例 + +## 项目特定信息 + +**项目名称**: FIT Framework +**主分支**: main +**开发分支**: 3.5.x +**仓库**: https://github.com/ModelEngine-Group/fit-framework + +**常用命令:** + +```bash +# 编译项目 +mvn clean install + +# 运行测试 +mvn test + +# 启动应用 +./fit start + +# 检查文件权限 +ls -l <file> + +# 动态修改文件权限(从 pom.xml 获取所有者) +OWNER_GROUP=$(ls -l pom.xml | awk '{print $3":"$4}') +sudo chown $OWNER_GROUP <file> +``` + +--- + +**最后更新**: 2025-11-08 +**Claude Code 版本**: 最新版 From 21764908eefbef21e309ab4c1126ffe0df70cdad Mon Sep 17 00:00:00 2001 From: CodeCaster <codecaster365@outlook.com> Date: Sat, 8 Nov 2025 23:45:55 +0800 Subject: [PATCH 3/4] =?UTF-8?q?[fit-launcher]=20=E6=B7=BB=E5=8A=A0=20fit?= =?UTF-8?q?=20init=20=E8=84=9A=E6=89=8B=E6=9E=B6=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: 1. 脚手架命令 (fit init) - 支持快速创建 FIT 项目结构 - 交互式输入项目信息 - 命令行参数模式支持 - 自动生成标准项目结构 2. 项目模板生成 - pom.xml(包含 FIT 依赖配置) - Application.java(启动类) - HelloController.java(示例控制器) - Message.java(示例领域模型) - README.md(项目文档) - .gitignore(版本控制忽略文件) 3. 命令参数 - --group-id: 指定 Maven Group ID - --artifact-id: 指定 Maven Artifact ID - --package: 指定 Java 包名 - 支持交互式和非交互式两种模式 4. 文档更新 - README 中添加 init 命令使用说明 - 包含详细的使用示例和参数说明 使用示例: ./fit init my-app ./fit init my-app --group-id=com.mycompany --artifact-id=my-app 脚手架功能降低了 FIT 项目的上手难度,提高开发效率 --- framework/fit/java/fit-launcher/README.md | 45 ++ .../src/main/resources/bin/fit.js | 402 +++++++++++++++++- 2 files changed, 442 insertions(+), 5 deletions(-) diff --git a/framework/fit/java/fit-launcher/README.md b/framework/fit/java/fit-launcher/README.md index cdb33b886..a3bd5ba5a 100644 --- a/framework/fit/java/fit-launcher/README.md +++ b/framework/fit/java/fit-launcher/README.md @@ -67,6 +67,47 @@ build/ ### Unix/Linux/macOS 使用方法 +#### 初始化新项目 + +使用 `init` 命令可以快速创建一个新的 FIT 项目(脚手架功能): + +```bash +./fit init <project-name> [options] +``` + +**选项:** +- `--group-id=<id>`: Maven Group ID(默认:com.example) +- `--artifact-id=<id>`: Maven Artifact ID(默认:项目名称) +- `--package=<name>`: Java 包名(默认:groupId.artifactId) + +**示例:** + +```bash +# 交互式创建项目(会提示输入信息) +./fit init my-app + +# 使用命令行参数创建项目 +./fit init my-app --group-id=com.mycompany --artifact-id=my-app --package=com.mycompany.myapp +``` + +创建的项目包含: +- 标准的 Maven 项目结构 +- FIT 框架依赖配置 +- 示例启动类(Application.java) +- 示例控制器(HelloController.java) +- 示例领域模型(Message.java) +- README.md 和 .gitignore 文件 + +创建后即可进入项目目录,编译并运行: + +```bash +cd my-app +mvn clean install +./fit start +``` + +访问 http://localhost:8080/hello 测试应用。 + #### 启动应用 ```bash @@ -115,6 +156,10 @@ Debug 模式会自动添加以下 JVM 参数: 使用 `fit.cmd` 命令: ```cmd +# 初始化新项目 +fit.cmd init my-app +fit.cmd init my-app --group-id=com.mycompany --artifact-id=my-app + # 启动应用 fit.cmd start diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js b/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js index 8f603d5ff..1d7ca713c 100755 --- a/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js @@ -3,6 +3,7 @@ const fs = require('fs'); const path = require('path'); const { spawn } = require('child_process'); +const readline = require('readline'); // 定义版本号 const VERSION = '3.5.5-SNAPSHOT'; @@ -114,22 +115,410 @@ function version() { console.log(`Version: ${VERSION}`); } +/** + * 初始化项目 + */ +async function init(args) { + const projectName = args.length > 1 ? args[1] : null; + + if (!projectName) { + console.error('Error: Project name is required'); + console.error('Usage: fit init <project-name> [--group-id=<id>] [--artifact-id=<id>] [--package=<name>]'); + process.exit(1); + } + + // 检查目录是否已存在 + const projectDir = path.join(currentDir, projectName); + if (fs.existsSync(projectDir)) { + console.error(`Error: Directory '${projectName}' already exists`); + process.exit(1); + } + + console.log(`Creating FIT project: ${projectName}\n`); + + // 解析命令行参数 + let groupId = 'com.example'; + let artifactId = projectName; + let packageName = null; + + for (let i = 2; i < args.length; i++) { + const arg = args[i]; + if (arg.startsWith('--group-id=')) { + groupId = arg.substring('--group-id='.length); + } else if (arg.startsWith('--artifact-id=')) { + artifactId = arg.substring('--artifact-id='.length); + } else if (arg.startsWith('--package=')) { + packageName = arg.substring('--package='.length); + } + } + + // 如果没有指定 package,使用默认值 + if (!packageName) { + packageName = `${groupId}.${artifactId.replace(/-/g, '.')}`; + } + + // 如果是交互模式(没有提供参数) + const isInteractive = args.length === 2; + + if (isInteractive && process.stdin.isTTY) { + // 交互式获取项目信息 + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + const question = (prompt) => new Promise((resolve) => { + rl.question(prompt, resolve); + }); + + try { + const inputGroupId = await question(`Group ID (default: ${groupId}): `); + groupId = inputGroupId || groupId; + + const inputArtifactId = await question(`Artifact ID (default: ${artifactId}): `); + artifactId = inputArtifactId || artifactId; + + const defaultPackage = `${groupId}.${artifactId.replace(/-/g, '.')}`; + const inputPackage = await question(`Package name (default: ${defaultPackage}): `); + packageName = inputPackage || defaultPackage; + + rl.close(); + } catch (err) { + rl.close(); + throw err; + } + } + + try { + + console.log('\nGenerating project structure...'); + + // 创建项目目录结构 + const srcDir = path.join(projectDir, 'src', 'main', 'java'); + const packageDir = path.join(srcDir, ...packageName.split('.')); + const controllerDir = path.join(packageDir, 'controller'); + const domainDir = path.join(packageDir, 'domain'); + + fs.mkdirSync(controllerDir, { recursive: true }); + fs.mkdirSync(domainDir, { recursive: true }); + + // 生成 pom.xml + const pomContent = generatePomXml(groupId, artifactId); + fs.writeFileSync(path.join(projectDir, 'pom.xml'), pomContent); + console.log(' ✓ Created pom.xml'); + + // 生成启动类 + const applicationClass = generateApplicationClass(packageName); + fs.writeFileSync(path.join(packageDir, 'Application.java'), applicationClass); + console.log(' ✓ Created Application.java'); + + // 生成示例 Controller + const controllerClass = generateControllerClass(packageName); + fs.writeFileSync(path.join(controllerDir, 'HelloController.java'), controllerClass); + console.log(' ✓ Created HelloController.java'); + + // 生成示例 Domain 类 + const domainClass = generateDomainClass(packageName); + fs.writeFileSync(path.join(domainDir, 'Message.java'), domainClass); + console.log(' ✓ Created Message.java'); + + // 生成 README.md + const readmeContent = generateReadme(projectName, groupId, artifactId); + fs.writeFileSync(path.join(projectDir, 'README.md'), readmeContent); + console.log(' ✓ Created README.md'); + + // 生成 .gitignore + const gitignoreContent = generateGitignore(); + fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignoreContent); + console.log(' ✓ Created .gitignore'); + + console.log('\n✨ Project created successfully!\n'); + console.log('Next steps:'); + console.log(` 1. cd ${projectName}`); + console.log(' 2. mvn clean install'); + console.log(' 3. ./fit start (or java -jar target/*.jar)'); + console.log('\nVisit http://localhost:8080/hello after starting the application.'); + + } catch (err) { + console.error('Error during initialization:', err.message); + process.exit(1); + } +} + +/** + * 生成 pom.xml 文件 + */ +function generatePomXml(groupId, artifactId) { + return `<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>${groupId}</groupId> + <artifactId>${artifactId}</artifactId> + <version>1.0-SNAPSHOT</version> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <java.version>17</java.version> + + <!-- FIT version --> + <fit.version>${VERSION}</fit.version> + + <!-- Maven plugin versions --> + <maven.compiler.version>3.14.0</maven.compiler.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.fitframework</groupId> + <artifactId>fit-starter</artifactId> + <version>\${fit.version}</version> + </dependency> + <dependency> + <groupId>org.fitframework</groupId> + <artifactId>fit-plugins-starter-web</artifactId> + <version>\${fit.version}</version> + </dependency> + <dependency> + <groupId>org.fitframework</groupId> + <artifactId>fit-api</artifactId> + <version>\${fit.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>\${maven.compiler.version}</version> + <configuration> + <source>\${java.version}</source> + <target>\${java.version}</target> + <encoding>\${project.build.sourceEncoding}</encoding> + <compilerArgs> + <arg>-parameters</arg> + </compilerArgs> + </configuration> + </plugin> + <plugin> + <groupId>org.fitframework</groupId> + <artifactId>fit-build-maven-plugin</artifactId> + <version>\${fit.version}</version> + <executions> + <execution> + <id>package-app</id> + <goals> + <goal>package-app</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> +`; +} + +/** + * 生成启动类 + */ +function generateApplicationClass(packageName) { + const year = new Date().getFullYear(); + return `/*--------------------------------------------------------------------------------------------- + * Copyright (c) ${year} FIT Framework Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package ${packageName}; + +import modelengine.fitframework.runtime.FitStarter; + +/** + * 应用程序启动类 + */ +public class Application { + public static void main(String[] args) { + FitStarter.start(Application.class, args); + } +} +`; +} + +/** + * 生成示例 Controller + */ +function generateControllerClass(packageName) { + const year = new Date().getFullYear(); + return `/*--------------------------------------------------------------------------------------------- + * Copyright (c) ${year} FIT Framework Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package ${packageName}.controller; + +import ${packageName}.domain.Message; +import modelengine.fit.http.annotation.GetMapping; +import modelengine.fit.http.annotation.RequestParam; +import modelengine.fitframework.annotation.Component; + +/** + * Hello World 控制器 + */ +@Component +public class HelloController { + + @GetMapping(path = "/hello") + public Message hello(@RequestParam(value = "name", required = false) String name) { + String greeting = name != null ? "Hello, " + name + "!" : "Hello, World!"; + return new Message(greeting, System.currentTimeMillis()); + } +} +`; +} + +/** + * 生成示例 Domain 类 + */ +function generateDomainClass(packageName) { + const year = new Date().getFullYear(); + return `/*--------------------------------------------------------------------------------------------- + * Copyright (c) ${year} FIT Framework Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package ${packageName}.domain; + +/** + * 消息领域模型 + */ +public class Message { + private final String content; + private final long timestamp; + + public Message(String content, long timestamp) { + this.content = content; + this.timestamp = timestamp; + } + + public String getContent() { + return content; + } + + public long getTimestamp() { + return timestamp; + } +} +`; +} + +/** + * 生成 README.md + */ +function generateReadme(projectName, groupId, artifactId) { + return `# ${projectName} + +A FIT Framework application. + +## Requirements + +- Java 17+ +- Maven 3.8.8+ +- Node.js 12+ (for fit command) + +## Build + +\`\`\`bash +mvn clean install +\`\`\` + +## Run + +Using fit command: +\`\`\`bash +./fit start +\`\`\` + +Or using Java: +\`\`\`bash +java -jar target/${artifactId}-1.0-SNAPSHOT.jar +\`\`\` + +## Test + +Visit: http://localhost:8080/hello + +With name parameter: http://localhost:8080/hello?name=YourName + +## Project Information + +- **Group ID**: ${groupId} +- **Artifact ID**: ${artifactId} +- **FIT Version**: ${VERSION} + +## Documentation + +For more information about FIT Framework, visit: +- [FIT Quick Start Guide](https://github.com/ModelEngine-Group/fit-framework/tree/main/docs) +- [FIT User Guide](https://github.com/ModelEngine-Group/fit-framework/tree/main/docs) +`; +} + +/** + * 生成 .gitignore + */ +function generateGitignore() { + return `# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# IDE +.idea/ +*.iml +.vscode/ +.eclipse/ +.settings/ +.classpath +.project + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Build +build/ +dist/ +`; +} + /** * 显示帮助信息 */ function help() { console.log('Usage: fit <command> [arguments]'); console.log('Commands:'); - console.log(' start Start the application'); - console.log(' debug Start the application in debug mode'); - console.log(' version Display the version number'); - console.log(' help Display this help message'); + console.log(' init <name> Initialize a new FIT project'); + console.log(' start Start the application'); + console.log(' debug Start the application in debug mode'); + console.log(' version Display the version number'); + console.log(' help Display this help message'); } /** * 主函数 */ -function main() { +async function main() { const args = process.argv.slice(2); if (args.length === 0) { @@ -141,6 +530,9 @@ function main() { const command = args[0]; switch (command) { + case 'init': + await init(args); + break; case 'start': start(args); break; From a75c27491f63197a1b772fdd8705f7c2d7c382e8 Mon Sep 17 00:00:00 2001 From: CodeCaster <codecaster365@outlook.com> Date: Sun, 9 Nov 2025 10:44:06 +0800 Subject: [PATCH 4/4] =?UTF-8?q?[fit-launcher]=20=E9=87=8D=E6=9E=84=20init?= =?UTF-8?q?=20=E5=91=BD=E4=BB=A4=E6=94=AF=E6=8C=81=20Service/Plugin=20?= =?UTF-8?q?=E5=88=86=E7=A6=BB=E6=9E=B6=E6=9E=84=E5=92=8C=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E5=BC=8F=E5=BC=95=E5=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支持创建独立的 Service (SPI) 和 Plugin 项目,符合 FIT 框架的插件架构规范 - 将模板代码从 fit.js 中分离到独立的 .tpl 文件,提高可维护性 - 新增交互式引导模式,所有参数均有合理默认值,用户可直接回车使用 - 保持向后兼容,支持命令行参数方式用于自动化场景 - Service 项目使用 build-service goal,Plugin 项目使用 build-plugin 和 package-plugin goals - Plugin 项目自动依赖对应的 Service 项目 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --- .../src/main/resources/bin/fit.js | 663 +++++++++--------- .../bin/templates/plugin/.gitignore.tpl | 30 + .../bin/templates/plugin/README.md.tpl | 73 ++ .../bin/templates/plugin/ServiceImpl.java.tpl | 22 + .../bin/templates/plugin/application.yml.tpl | 4 + .../bin/templates/plugin/pom.xml.tpl | 75 ++ .../bin/templates/service/README.md.tpl | 72 ++ .../bin/templates/service/Service.java.tpl | 21 + .../bin/templates/service/pom.xml.tpl | 64 ++ 9 files changed, 684 insertions(+), 340 deletions(-) create mode 100644 framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/.gitignore.tpl create mode 100644 framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/README.md.tpl create mode 100644 framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/ServiceImpl.java.tpl create mode 100644 framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/application.yml.tpl create mode 100644 framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/pom.xml.tpl create mode 100644 framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/README.md.tpl create mode 100644 framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/Service.java.tpl create mode 100644 framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/pom.xml.tpl diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js b/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js index 1d7ca713c..96484df37 100755 --- a/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/fit.js @@ -11,6 +11,7 @@ const VERSION = '3.5.5-SNAPSHOT'; // 获取脚本所在的路径 const scriptDir = __dirname; const currentDir = process.cwd(); +const templatesDir = path.join(scriptDir, 'templates'); /** * 查找符合 fit-discrete-launcher-[version].jar 格式的文件 @@ -28,6 +29,31 @@ function findJarFile(directory) { } } +/** + * 读取模板文件 + */ +function readTemplate(templateType, fileName) { + const templatePath = path.join(templatesDir, templateType, fileName); + try { + return fs.readFileSync(templatePath, 'utf-8'); + } catch (err) { + console.error(`Error reading template ${templatePath}:`, err.message); + throw err; + } +} + +/** + * 替换模板变量 + */ +function replaceTemplateVars(template, vars) { + let result = template; + for (const [key, value] of Object.entries(vars)) { + const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g'); + result = result.replace(regex, value); + } + return result; +} + /** * 启动应用 */ @@ -119,49 +145,63 @@ function version() { * 初始化项目 */ async function init(args) { - const projectName = args.length > 1 ? args[1] : null; - - if (!projectName) { - console.error('Error: Project name is required'); - console.error('Usage: fit init <project-name> [--group-id=<id>] [--artifact-id=<id>] [--package=<name>]'); - process.exit(1); - } - - // 检查目录是否已存在 - const projectDir = path.join(currentDir, projectName); - if (fs.existsSync(projectDir)) { - console.error(`Error: Directory '${projectName}' already exists`); - process.exit(1); - } - - console.log(`Creating FIT project: ${projectName}\n`); + let projectName = args.length > 1 ? args[1] : null; // 解析命令行参数 - let groupId = 'com.example'; - let artifactId = projectName; + let projectType = null; + let groupId = null; + let artifactId = null; let packageName = null; + let serviceName = null; + let serviceGroupId = null; + let serviceArtifactId = null; + let serviceVersion = null; + let servicePackage = null; for (let i = 2; i < args.length; i++) { const arg = args[i]; - if (arg.startsWith('--group-id=')) { + if (arg.startsWith('--type=')) { + projectType = arg.substring('--type='.length); + } else if (arg.startsWith('--group-id=')) { groupId = arg.substring('--group-id='.length); } else if (arg.startsWith('--artifact-id=')) { artifactId = arg.substring('--artifact-id='.length); } else if (arg.startsWith('--package=')) { packageName = arg.substring('--package='.length); + } else if (arg.startsWith('--service=')) { + serviceName = arg.substring('--service='.length); + } else if (arg.startsWith('--service-group-id=')) { + serviceGroupId = arg.substring('--service-group-id='.length); + } else if (arg.startsWith('--service-artifact-id=')) { + serviceArtifactId = arg.substring('--service-artifact-id='.length); + } else if (arg.startsWith('--service-version=')) { + serviceVersion = arg.substring('--service-version='.length); + } else if (arg.startsWith('--service-package=')) { + servicePackage = arg.substring('--service-package='.length); } } - // 如果没有指定 package,使用默认值 - if (!packageName) { - packageName = `${groupId}.${artifactId.replace(/-/g, '.')}`; - } + // 如果在非交互式环境(如 CI/CD)且缺少必需参数,则报错 + const isInteractive = process.stdin.isTTY; - // 如果是交互模式(没有提供参数) - const isInteractive = args.length === 2; + if (!isInteractive) { + if (!projectName) { + console.error('Error: Project name is required'); + console.error('Usage: fit init <project-name> --type=<service|plugin> [options]'); + process.exit(1); + } + if (!projectType || (projectType !== 'service' && projectType !== 'plugin')) { + console.error('Error: --type must be either "service" or "plugin"'); + process.exit(1); + } + if (projectType === 'plugin' && (!serviceGroupId || !serviceArtifactId)) { + console.error('Error: Plugin type requires --service-group-id and --service-artifact-id'); + process.exit(1); + } + } - if (isInteractive && process.stdin.isTTY) { - // 交互式获取项目信息 + // 交互式引导 + if (isInteractive) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout @@ -172,347 +212,290 @@ async function init(args) { }); try { - const inputGroupId = await question(`Group ID (default: ${groupId}): `); - groupId = inputGroupId || groupId; - - const inputArtifactId = await question(`Artifact ID (default: ${artifactId}): `); - artifactId = inputArtifactId || artifactId; + console.log('Welcome to FIT Project Generator!\n'); + + // 项目名称 + if (!projectName) { + projectName = await question('Project name: '); + if (!projectName) { + console.error('Error: Project name is required'); + rl.close(); + process.exit(1); + } + } + + // 检查目录是否已存在 + const projectDir = path.join(currentDir, projectName); + if (fs.existsSync(projectDir)) { + console.error(`Error: Directory '${projectName}' already exists`); + rl.close(); + process.exit(1); + } + + // 项目类型 + if (!projectType) { + console.log('\nProject type:'); + console.log(' 1) service - Service interface (SPI)'); + console.log(' 2) plugin - Plugin implementation'); + const typeChoice = await question('Select type (1 or 2, default: 1): '); + projectType = typeChoice === '2' ? 'plugin' : 'service'; + } + + // 验证项目类型 + if (projectType !== 'service' && projectType !== 'plugin') { + console.error('Error: type must be either "service" or "plugin"'); + rl.close(); + process.exit(1); + } + + console.log(`\nCreating FIT ${projectType === 'service' ? 'Service' : 'Plugin'}: ${projectName}\n`); + + // 通用参数 + const defaultGroupId = 'com.example'; + const inputGroupId = await question(`Group ID (default: ${defaultGroupId}): `); + groupId = inputGroupId.trim() || defaultGroupId; + + const defaultArtifactId = projectName; + const inputArtifactId = await question(`Artifact ID (default: ${defaultArtifactId}): `); + artifactId = inputArtifactId.trim() || defaultArtifactId; const defaultPackage = `${groupId}.${artifactId.replace(/-/g, '.')}`; const inputPackage = await question(`Package name (default: ${defaultPackage}): `); - packageName = inputPackage || defaultPackage; + packageName = inputPackage.trim() || defaultPackage; + + const defaultServiceName = 'Demo'; + const inputServiceName = await question(`Service name (default: ${defaultServiceName}): `); + serviceName = inputServiceName.trim() || defaultServiceName; + + // Plugin 特有参数 + if (projectType === 'plugin') { + console.log('\nPlugin depends on a service interface:'); + + if (!serviceGroupId) { + const inputServiceGroupId = await question('Service Group ID: '); + serviceGroupId = inputServiceGroupId.trim(); + if (!serviceGroupId) { + console.error('Error: Service Group ID is required for plugin'); + rl.close(); + process.exit(1); + } + } + + if (!serviceArtifactId) { + const inputServiceArtifactId = await question('Service Artifact ID: '); + serviceArtifactId = inputServiceArtifactId.trim(); + if (!serviceArtifactId) { + console.error('Error: Service Artifact ID is required for plugin'); + rl.close(); + process.exit(1); + } + } + + const defaultServiceVersion = '1.0-SNAPSHOT'; + const inputServiceVersion = await question(`Service Version (default: ${defaultServiceVersion}): `); + serviceVersion = inputServiceVersion.trim() || defaultServiceVersion; + + const defaultServicePackage = `${serviceGroupId}.${serviceArtifactId.replace(/-/g, '.')}`; + const inputServicePackage = await question(`Service Package (default: ${defaultServicePackage}): `); + servicePackage = inputServicePackage.trim() || defaultServicePackage; + } rl.close(); } catch (err) { rl.close(); - throw err; + console.error('Error during interactive input:', err.message); + process.exit(1); } + } else { + // 非交互式模式,检查目录 + const projectDir = path.join(currentDir, projectName); + if (fs.existsSync(projectDir)) { + console.error(`Error: Directory '${projectName}' already exists`); + process.exit(1); + } + console.log(`Creating FIT ${projectType === 'service' ? 'Service' : 'Plugin'}: ${projectName}\n`); } - try { - - console.log('\nGenerating project structure...'); - - // 创建项目目录结构 - const srcDir = path.join(projectDir, 'src', 'main', 'java'); - const packageDir = path.join(srcDir, ...packageName.split('.')); - const controllerDir = path.join(packageDir, 'controller'); - const domainDir = path.join(packageDir, 'domain'); - - fs.mkdirSync(controllerDir, { recursive: true }); - fs.mkdirSync(domainDir, { recursive: true }); - - // 生成 pom.xml - const pomContent = generatePomXml(groupId, artifactId); - fs.writeFileSync(path.join(projectDir, 'pom.xml'), pomContent); - console.log(' ✓ Created pom.xml'); - - // 生成启动类 - const applicationClass = generateApplicationClass(packageName); - fs.writeFileSync(path.join(packageDir, 'Application.java'), applicationClass); - console.log(' ✓ Created Application.java'); - - // 生成示例 Controller - const controllerClass = generateControllerClass(packageName); - fs.writeFileSync(path.join(controllerDir, 'HelloController.java'), controllerClass); - console.log(' ✓ Created HelloController.java'); - - // 生成示例 Domain 类 - const domainClass = generateDomainClass(packageName); - fs.writeFileSync(path.join(domainDir, 'Message.java'), domainClass); - console.log(' ✓ Created Message.java'); - - // 生成 README.md - const readmeContent = generateReadme(projectName, groupId, artifactId); - fs.writeFileSync(path.join(projectDir, 'README.md'), readmeContent); - console.log(' ✓ Created README.md'); - - // 生成 .gitignore - const gitignoreContent = generateGitignore(); - fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignoreContent); - console.log(' ✓ Created .gitignore'); - - console.log('\n✨ Project created successfully!\n'); - console.log('Next steps:'); - console.log(` 1. cd ${projectName}`); - console.log(' 2. mvn clean install'); - console.log(' 3. ./fit start (or java -jar target/*.jar)'); - console.log('\nVisit http://localhost:8080/hello after starting the application.'); - - } catch (err) { - console.error('Error during initialization:', err.message); - process.exit(1); - } -} - -/** - * 生成 pom.xml 文件 - */ -function generatePomXml(groupId, artifactId) { - return `<?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <groupId>${groupId}</groupId> - <artifactId>${artifactId}</artifactId> - <version>1.0-SNAPSHOT</version> - - <properties> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <java.version>17</java.version> - - <!-- FIT version --> - <fit.version>${VERSION}</fit.version> - - <!-- Maven plugin versions --> - <maven.compiler.version>3.14.0</maven.compiler.version> - </properties> - - <dependencies> - <dependency> - <groupId>org.fitframework</groupId> - <artifactId>fit-starter</artifactId> - <version>\${fit.version}</version> - </dependency> - <dependency> - <groupId>org.fitframework</groupId> - <artifactId>fit-plugins-starter-web</artifactId> - <version>\${fit.version}</version> - </dependency> - <dependency> - <groupId>org.fitframework</groupId> - <artifactId>fit-api</artifactId> - <version>\${fit.version}</version> - </dependency> - </dependencies> - - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-compiler-plugin</artifactId> - <version>\${maven.compiler.version}</version> - <configuration> - <source>\${java.version}</source> - <target>\${java.version}</target> - <encoding>\${project.build.sourceEncoding}</encoding> - <compilerArgs> - <arg>-parameters</arg> - </compilerArgs> - </configuration> - </plugin> - <plugin> - <groupId>org.fitframework</groupId> - <artifactId>fit-build-maven-plugin</artifactId> - <version>\${fit.version}</version> - <executions> - <execution> - <id>package-app</id> - <goals> - <goal>package-app</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </build> -</project> -`; -} - -/** - * 生成启动类 - */ -function generateApplicationClass(packageName) { - const year = new Date().getFullYear(); - return `/*--------------------------------------------------------------------------------------------- - * Copyright (c) ${year} FIT Framework Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package ${packageName}; - -import modelengine.fitframework.runtime.FitStarter; - -/** - * 应用程序启动类 - */ -public class Application { - public static void main(String[] args) { - FitStarter.start(Application.class, args); - } -} -`; -} - -/** - * 生成示例 Controller - */ -function generateControllerClass(packageName) { - const year = new Date().getFullYear(); - return `/*--------------------------------------------------------------------------------------------- - * Copyright (c) ${year} FIT Framework Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package ${packageName}.controller; - -import ${packageName}.domain.Message; -import modelengine.fit.http.annotation.GetMapping; -import modelengine.fit.http.annotation.RequestParam; -import modelengine.fitframework.annotation.Component; + // 设置默认值(如果用户没有提供) + groupId = groupId || 'com.example'; + artifactId = artifactId || projectName; + packageName = packageName || `${groupId}.${artifactId.replace(/-/g, '.')}`; + serviceName = serviceName || 'Demo'; + serviceVersion = serviceVersion || '1.0-SNAPSHOT'; -/** - * Hello World 控制器 - */ -@Component -public class HelloController { - - @GetMapping(path = "/hello") - public Message hello(@RequestParam(value = "name", required = false) String name) { - String greeting = name != null ? "Hello, " + name + "!" : "Hello, World!"; - return new Message(greeting, System.currentTimeMillis()); + // Plugin 的 service package 默认值 + if (projectType === 'plugin' && !servicePackage) { + servicePackage = `${serviceGroupId}.${serviceArtifactId.replace(/-/g, '.')}`; } -} -`; -} - -/** - * 生成示例 Domain 类 - */ -function generateDomainClass(packageName) { - const year = new Date().getFullYear(); - return `/*--------------------------------------------------------------------------------------------- - * Copyright (c) ${year} FIT Framework Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -package ${packageName}.domain; - -/** - * 消息领域模型 - */ -public class Message { - private final String content; - private final long timestamp; + const projectDir = path.join(currentDir, projectName); - public Message(String content, long timestamp) { - this.content = content; - this.timestamp = timestamp; - } + try { + console.log(`\nGenerating ${projectType} project structure...`); + + // 准备模板变量 + const serviceId = serviceName.toLowerCase(); + const packagePath = packageName.replace(/\./g, '/'); + const year = new Date().getFullYear(); + + const templateVars = { + GROUP_ID: groupId, + ARTIFACT_ID: artifactId, + PACKAGE: packageName, + PACKAGE_PATH: packagePath, + SERVICE_NAME: serviceName, + SERVICE_ID: serviceId, + FIT_VERSION: VERSION, + YEAR: year.toString(), + PROJECT_NAME: projectName, + SERVICE_GROUP_ID: serviceGroupId || '', + SERVICE_ARTIFACT_ID: serviceArtifactId || '', + SERVICE_VERSION: serviceVersion || '', + SERVICE_PACKAGE: servicePackage || '' + }; + + if (projectType === 'service') { + // 生成 Service 项目 + const srcDir = path.join(projectDir, 'src', 'main', 'java'); + const packageDir = path.join(srcDir, ...packageName.split('.')); + + fs.mkdirSync(packageDir, { recursive: true }); + + // 生成 pom.xml + const pomTemplate = readTemplate('service', 'pom.xml.tpl'); + const pomContent = replaceTemplateVars(pomTemplate, templateVars); + fs.writeFileSync(path.join(projectDir, 'pom.xml'), pomContent); + console.log(' ✓ Created pom.xml'); + + // 生成服务接口 + const serviceTemplate = readTemplate('service', 'Service.java.tpl'); + const serviceContent = replaceTemplateVars(serviceTemplate, templateVars); + fs.writeFileSync(path.join(packageDir, `${serviceName}.java`), serviceContent); + console.log(` ✓ Created ${serviceName}.java`); + + // 生成 README.md + const readmeTemplate = readTemplate('service', 'README.md.tpl'); + const readmeContent = replaceTemplateVars(readmeTemplate, templateVars); + fs.writeFileSync(path.join(projectDir, 'README.md'), readmeContent); + console.log(' ✓ Created README.md'); + + // 生成 .gitignore + const gitignoreTemplate = readTemplate('plugin', '.gitignore.tpl'); + const gitignoreContent = replaceTemplateVars(gitignoreTemplate, templateVars); + fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignoreContent); + console.log(' ✓ Created .gitignore'); + + } else if (projectType === 'plugin') { + // 生成 Plugin 项目 + const srcDir = path.join(projectDir, 'src', 'main', 'java'); + const resourcesDir = path.join(projectDir, 'src', 'main', 'resources'); + const packageDir = path.join(srcDir, ...packageName.split('.')); + + fs.mkdirSync(packageDir, { recursive: true }); + fs.mkdirSync(resourcesDir, { recursive: true }); + + // 生成 pom.xml + const pomTemplate = readTemplate('plugin', 'pom.xml.tpl'); + const pomContent = replaceTemplateVars(pomTemplate, templateVars); + fs.writeFileSync(path.join(projectDir, 'pom.xml'), pomContent); + console.log(' ✓ Created pom.xml'); + + // 生成服务实现 + const serviceImplTemplate = readTemplate('plugin', 'ServiceImpl.java.tpl'); + const serviceImplContent = replaceTemplateVars(serviceImplTemplate, templateVars); + fs.writeFileSync(path.join(packageDir, `Default${serviceName}.java`), serviceImplContent); + console.log(` ✓ Created Default${serviceName}.java`); + + // 生成 application.yml + const ymlTemplate = readTemplate('plugin', 'application.yml.tpl'); + const ymlContent = replaceTemplateVars(ymlTemplate, templateVars); + fs.writeFileSync(path.join(resourcesDir, 'application.yml'), ymlContent); + console.log(' ✓ Created application.yml'); + + // 生成 README.md + const readmeTemplate = readTemplate('plugin', 'README.md.tpl'); + const readmeContent = replaceTemplateVars(readmeTemplate, templateVars); + fs.writeFileSync(path.join(projectDir, 'README.md'), readmeContent); + console.log(' ✓ Created README.md'); + + // 生成 .gitignore + const gitignoreTemplate = readTemplate('plugin', '.gitignore.tpl'); + const gitignoreContent = replaceTemplateVars(gitignoreTemplate, templateVars); + fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignoreContent); + console.log(' ✓ Created .gitignore'); + } - public String getContent() { - return content; - } + // 显示成功信息 + console.log(`\n✨ FIT ${projectType === 'service' ? 'Service' : 'Plugin'} created successfully!\n`); + + if (projectType === 'service') { + console.log('Service Structure:'); + console.log(` ${projectName}/`); + console.log(` ├── pom.xml # Maven 配置`); + console.log(` ├── README.md # 服务文档`); + console.log(` └── src/main/java/${packagePath}/`); + console.log(` └── ${serviceName}.java # 服务接口(SPI)`); + console.log('\nNext steps:'); + console.log(` 1. cd ${projectName}`); + console.log(' 2. mvn clean install'); + console.log(' 3. Create plugin implementations that depend on this service'); + } else { + console.log('Plugin Structure:'); + console.log(` ${projectName}/`); + console.log(` ├── pom.xml # Maven 配置`); + console.log(` ├── README.md # 插件文档`); + console.log(` └── src/main/`); + console.log(` ├── java/${packagePath}/`); + console.log(` │ └── Default${serviceName}.java # 服务实现`); + console.log(` └── resources/`); + console.log(` └── application.yml # FIT 配置`); + console.log('\nNext steps:'); + console.log(` 1. cd ${projectName}`); + console.log(' 2. mvn clean install'); + console.log(' 3. Deploy the plugin JAR to your FIT application'); + } - public long getTimestamp() { - return timestamp; + } catch (err) { + console.error('Error during initialization:', err.message); + process.exit(1); } } -`; -} - -/** - * 生成 README.md - */ -function generateReadme(projectName, groupId, artifactId) { - return `# ${projectName} - -A FIT Framework application. - -## Requirements - -- Java 17+ -- Maven 3.8.8+ -- Node.js 12+ (for fit command) - -## Build - -\`\`\`bash -mvn clean install -\`\`\` - -## Run - -Using fit command: -\`\`\`bash -./fit start -\`\`\` - -Or using Java: -\`\`\`bash -java -jar target/${artifactId}-1.0-SNAPSHOT.jar -\`\`\` - -## Test - -Visit: http://localhost:8080/hello - -With name parameter: http://localhost:8080/hello?name=YourName - -## Project Information - -- **Group ID**: ${groupId} -- **Artifact ID**: ${artifactId} -- **FIT Version**: ${VERSION} - -## Documentation - -For more information about FIT Framework, visit: -- [FIT Quick Start Guide](https://github.com/ModelEngine-Group/fit-framework/tree/main/docs) -- [FIT User Guide](https://github.com/ModelEngine-Group/fit-framework/tree/main/docs) -`; -} - -/** - * 生成 .gitignore - */ -function generateGitignore() { - return `# Maven -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties - -# IDE -.idea/ -*.iml -.vscode/ -.eclipse/ -.settings/ -.classpath -.project - -# OS -.DS_Store -Thumbs.db - -# Logs -*.log - -# Build -build/ -dist/ -`; -} /** * 显示帮助信息 */ function help() { console.log('Usage: fit <command> [arguments]'); - console.log('Commands:'); - console.log(' init <name> Initialize a new FIT project'); + console.log('\nCommands:'); + console.log(' init [name] Initialize a new FIT service or plugin project'); console.log(' start Start the application'); console.log(' debug Start the application in debug mode'); console.log(' version Display the version number'); console.log(' help Display this help message'); + console.log('\nInit Usage:'); + console.log(' fit init # Interactive mode (recommended)'); + console.log(' fit init <name> # Interactive mode with project name'); + console.log(' fit init <name> [options] # Non-interactive mode with options'); + console.log('\nInit Options:'); + console.log(' --type=<service|plugin> Project type'); + console.log(' --group-id=<id> Maven Group ID'); + console.log(' --artifact-id=<id> Maven Artifact ID'); + console.log(' --package=<name> Java package name'); + console.log(' --service=<name> Service name'); + console.log('\nPlugin-specific options:'); + console.log(' --service-group-id=<id> Service Group ID'); + console.log(' --service-artifact-id=<id> Service Artifact ID'); + console.log(' --service-version=<version> Service Version'); + console.log(' --service-package=<package> Service Package'); + console.log('\nExamples:'); + console.log(' # Interactive mode (easiest)'); + console.log(' fit init'); + console.log(' fit init my-project'); + console.log('\n # Non-interactive mode - Create a service (SPI)'); + console.log(' fit init weather-service --type=service --service=Weather'); + console.log('\n # Non-interactive mode - Create a plugin implementation'); + console.log(' fit init default-weather --type=plugin --service=Weather \\'); + console.log(' --service-group-id=com.example --service-artifact-id=weather-service'); } /** diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/.gitignore.tpl b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/.gitignore.tpl new file mode 100644 index 000000000..d4fea541c --- /dev/null +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/.gitignore.tpl @@ -0,0 +1,30 @@ +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# IDE +.idea/ +*.iml +.vscode/ +.eclipse/ +.settings/ +.classpath +.project + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Build +build/ +dist/ diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/README.md.tpl b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/README.md.tpl new file mode 100644 index 000000000..54fa2d0b3 --- /dev/null +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/README.md.tpl @@ -0,0 +1,73 @@ +# {{PROJECT_NAME}} + +A FIT Framework Plugin (Service Implementation). + +## Project Information + +- **Group ID**: {{GROUP_ID}} +- **Artifact ID**: {{ARTIFACT_ID}} +- **Package**: {{PACKAGE}} +- **Service Dependency**: {{SERVICE_GROUP_ID}}:{{SERVICE_ARTIFACT_ID}} +- **FIT Version**: {{FIT_VERSION}} + +## Build + +```bash +mvn clean install +``` + +This will: +1. Build the plugin JAR file +2. Package the plugin with all dependencies + +## Plugin Structure + +``` +{{PROJECT_NAME}}/ +├── src/main/java/ +│ └── {{PACKAGE_PATH}}/ +│ └── Default{{SERVICE_NAME}}.java # 服务实现(使用 @Component 和 @Fitable) +└── src/main/resources/ + └── application.yml # FIT 配置文件 +``` + +## Key Annotations + +- `@Component` - 标记为 FIT 组件,自动注册到 IoC 容器 +- `@Fitable(id = "default-{{SERVICE_ID}}")` - 标记为可远程调用的实现 + +## Usage + +### 1. 部署插件 + +将构建好的插件 JAR 包部署到 FIT 应用的插件目录: + +```bash +cp target/{{ARTIFACT_ID}}-1.0-SNAPSHOT.jar /path/to/fit-app/plugins/ +``` + +### 2. 在应用中使用 + +首先确保 service 项目已安装到本地仓库或远程仓库。 + +然后在应用中注入并使用服务: + +```java +@Component +public class MyController { + + @FitBean + private {{SERVICE_NAME}} {{SERVICE_ID}}; + + public void usePlugin() { + String result = {{SERVICE_ID}}.execute(); + System.out.println(result); + } +} +``` + +## Documentation + +For more information about FIT Framework plugins, visit: +- [FIT Plugin Development Guide](https://github.com/ModelEngine-Group/fit-framework/tree/main/docs) +- [FIT Quick Start Guide](https://github.com/ModelEngine-Group/fit-framework/tree/main/docs) diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/ServiceImpl.java.tpl b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/ServiceImpl.java.tpl new file mode 100644 index 000000000..d10cd051e --- /dev/null +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/ServiceImpl.java.tpl @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) {{YEAR}} FIT Framework Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package {{PACKAGE}}; + +import {{SERVICE_PACKAGE}}.{{SERVICE_NAME}}; +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.annotation.Fitable; + +/** + * {{SERVICE_NAME}} 的默认实现 + */ +@Component +public class Default{{SERVICE_NAME}} implements {{SERVICE_NAME}} { + @Override + @Fitable(id = "default-{{SERVICE_ID}}") + public String execute() { + return "{{SERVICE_NAME}} plugin is working successfully!"; + } +} diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/application.yml.tpl b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/application.yml.tpl new file mode 100644 index 000000000..496250e97 --- /dev/null +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/application.yml.tpl @@ -0,0 +1,4 @@ +fit: + beans: + packages: + - '{{PACKAGE}}' diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/pom.xml.tpl b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/pom.xml.tpl new file mode 100644 index 000000000..fc18f57de --- /dev/null +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/plugin/pom.xml.tpl @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>{{GROUP_ID}}</groupId> + <artifactId>{{ARTIFACT_ID}}</artifactId> + <version>1.0-SNAPSHOT</version> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <java.version>17</java.version> + + <!-- FIT version --> + <fit.version>{{FIT_VERSION}}</fit.version> + + <!-- Maven plugin versions --> + <maven.compiler.version>3.14.0</maven.compiler.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.fitframework</groupId> + <artifactId>fit-api</artifactId> + <version>${fit.version}</version> + </dependency> + <dependency> + <groupId>org.fitframework</groupId> + <artifactId>fit-util</artifactId> + <version>${fit.version}</version> + </dependency> + <dependency> + <groupId>{{SERVICE_GROUP_ID}}</groupId> + <artifactId>{{SERVICE_ARTIFACT_ID}}</artifactId> + <version>{{SERVICE_VERSION}}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>${maven.compiler.version}</version> + <configuration> + <source>${java.version}</source> + <target>${java.version}</target> + <encoding>${project.build.sourceEncoding}</encoding> + <compilerArgs> + <arg>-parameters</arg> + </compilerArgs> + </configuration> + </plugin> + <plugin> + <groupId>org.fitframework</groupId> + <artifactId>fit-build-maven-plugin</artifactId> + <version>${fit.version}</version> + <executions> + <execution> + <id>build-plugin</id> + <goals> + <goal>build-plugin</goal> + </goals> + </execution> + <execution> + <id>package-plugin</id> + <goals> + <goal>package-plugin</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/README.md.tpl b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/README.md.tpl new file mode 100644 index 000000000..7047ca0a0 --- /dev/null +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/README.md.tpl @@ -0,0 +1,72 @@ +# {{PROJECT_NAME}} + +A FIT Framework Service (SPI). + +## Project Information + +- **Group ID**: {{GROUP_ID}} +- **Artifact ID**: {{ARTIFACT_ID}} +- **Package**: {{PACKAGE}} +- **FIT Version**: {{FIT_VERSION}} + +## Build + +```bash +mvn clean install +``` + +This will: +1. Build the service JAR file +2. Make it available for plugin implementations + +## Service Structure + +``` +{{PROJECT_NAME}}/ +└── src/main/java/ + └── {{PACKAGE_PATH}}/ + └── {{SERVICE_NAME}}.java # 服务接口(使用 @Genericable) +``` + +## Key Annotations + +- `@Genericable(id = "{{SERVICE_NAME}}")` - 标记服务接口,支持远程和本地调用 + +## Usage + +### 1. 安装到本地仓库 + +```bash +mvn clean install +``` + +### 2. 创建插件实现 + +其他项目可以依赖此服务并创建插件实现: + +```xml +<dependency> + <groupId>{{GROUP_ID}}</groupId> + <artifactId>{{ARTIFACT_ID}}</artifactId> + <version>1.0-SNAPSHOT</version> +</dependency> +``` + +然后实现 {{SERVICE_NAME}} 接口: + +```java +@Component +public class Default{{SERVICE_NAME}} implements {{SERVICE_NAME}} { + @Override + @Fitable(id = "default-{{SERVICE_ID}}") + public String execute() { + return "Implementation result"; + } +} +``` + +## Documentation + +For more information about FIT Framework services, visit: +- [FIT Service Development Guide](https://github.com/ModelEngine-Group/fit-framework/tree/main/docs) +- [FIT Quick Start Guide](https://github.com/ModelEngine-Group/fit-framework/tree/main/docs) diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/Service.java.tpl b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/Service.java.tpl new file mode 100644 index 000000000..d87efb9c4 --- /dev/null +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/Service.java.tpl @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) {{YEAR}} FIT Framework Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package {{PACKAGE}}; + +import modelengine.fitframework.annotation.Genericable; + +/** + * {{SERVICE_NAME}} 服务接口(SPI) + */ +public interface {{SERVICE_NAME}} { + /** + * 执行服务操作 + * + * @return 服务执行结果 + */ + @Genericable(id = "{{SERVICE_NAME}}") + String execute(); +} diff --git a/framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/pom.xml.tpl b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/pom.xml.tpl new file mode 100644 index 000000000..f3bdea388 --- /dev/null +++ b/framework/fit/java/fit-launcher/src/main/resources/bin/templates/service/pom.xml.tpl @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>{{GROUP_ID}}</groupId> + <artifactId>{{ARTIFACT_ID}}</artifactId> + <version>1.0-SNAPSHOT</version> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <java.version>17</java.version> + + <!-- FIT version --> + <fit.version>{{FIT_VERSION}}</fit.version> + + <!-- Maven plugin versions --> + <maven.compiler.version>3.14.0</maven.compiler.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.fitframework</groupId> + <artifactId>fit-api</artifactId> + <version>${fit.version}</version> + </dependency> + <dependency> + <groupId>org.fitframework</groupId> + <artifactId>fit-util</artifactId> + <version>${fit.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>${maven.compiler.version}</version> + <configuration> + <source>${java.version}</source> + <target>${java.version}</target> + <encoding>${project.build.sourceEncoding}</encoding> + <compilerArgs> + <arg>-parameters</arg> + </compilerArgs> + </configuration> + </plugin> + <plugin> + <groupId>org.fitframework</groupId> + <artifactId>fit-build-maven-plugin</artifactId> + <version>${fit.version}</version> + <executions> + <execution> + <id>build-service</id> + <goals> + <goal>build-service</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project>