diff --git a/doc/模版.md b/doc/模版.md
new file mode 100644
index 0000000..95304a8
--- /dev/null
+++ b/doc/模版.md
@@ -0,0 +1,261 @@
+# stringtemplate4
+
+设置 $
+
+```
+STGroup group = new STGroupString("db_template",template, '$', '$');
+```
+
+
+
+## 模版的定义
+
+
+
+基础
+
+```
+classTemplate(item, module, operationList, className) ::= <<
+>>
+```
+
+> 注意 <<内容>> 定义的方法
+>
+> classTemplate 为 group.getInstanceOf("classTemplate"); 定义
+
+例子:
+
+```java
+classTemplate(item, module, operationList, className) ::= <<
+package $module.packageName$;
+/**
+ * @author xia
+ * @date 2026/1/10
+ * @version 0.0.1
+ */
+public class $className$ {
+
+}
+>>
+
+```
+
+
+
+映射字典
+
+```ts
+// 定义一个映射字典
+requestAnnotations ::= [
+ "POST": "@PostMapping",
+ "GET": "@GetMapping",
+ "PUT": "@PutMapping",
+ "DELETE": "@DeleteMapping",
+ default: "@RequestMapping"
+]
+```
+
+
+
+
+
+## Controller
+
+
+
+```java
+requestAnnotations ::= [
+ "POST": "@PostMapping",
+ "GET": "@GetMapping",
+ "PUT": "@PutMapping",
+ "DELETE": "@DeleteMapping",
+ default: "@RequestMapping"
+]
+
+classTemplate(module,item,operationList,basics) ::= <<
+package $module.packageName$.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * $item.itemName$ 控制器
+ * @author xia
+ */
+@Tag(name = "$item.itemName$")
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/$item.itemCode$")
+public class $basics.itemCodeUp$Controller {
+
+ private final $basics.itemCodeUp$Service $item.itemCode$Service;
+
+$operationList:{op |
+
+ @OperLog
+ @SecureAudit(id = $op.id$,$if(op.isGreenLight)$isGreenLight = true,$endif$ $if(!op.isTenant)$isTenant = false,$endif$
+ funType = FunTypeEnum.$op.funType$, funName = "$op.funName$", funCode = "$module.moduleCode$:$item.itemCode$:$op.operationCode$")
+ @Operation(summary = "$op.funName$", description = "$op.describe$")
+ $requestAnnotations.(op.requestType)$("$op.url$$op.pathParamsUrl$")
+ public XResult<$if(op.isPage)$Page<$basics.itemCodeUp$$op.methodNamePascalCase$Res>$else$$if(op.isResParams)$$basics.itemCodeUp$$op.methodNamePascalCase$Res$else$Void$endif$$endif$> $op.methodName$($if(op.isReqParams)$@RequestBody @Valid $basics.itemCodeUp$$op.methodNamePascalCase$Req req$endif$$op.pathParamsLong$) {
+ return $item.itemCode$Service.$op.methodName$($if(op.isPathParams)$$op.pathParams$$endif$$if(op.isReqParams)$req$endif$);
+ \}
+
+}$
+
+}
+>>
+```
+
+
+
+
+
+## 请求参数
+
+
+
+```java
+classTemplate(module,item,operationList,basics) ::= <<
+package $module.apiPackageName$.request.$item.itemCode$;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * $item.itemName$
+ * $basics.oper.funName$ 请求参数
+ * @author xia
+ */
+@Getter
+@Setter
+@Schema(description = "$basics.oper.funName$ 参数")
+public class $basics.itemCodeUp$Req $if(basics.oper.isPage)$extends XhPage$endif$ implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+
+
+}
+>>
+```
+
+
+
+## 响应参数
+
+
+
+```java
+classTemplate(module,item,operationList,basics) ::= <<
+package $module.apiPackageName$.response.$item.itemCode$;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * $item.itemName$
+ * $basics.oper.funName$ 响应
+ * @author xia
+ */
+@Getter
+@Setter
+@Schema(description = "$basics.oper.funName$ 响应")
+public class $basics.itemCodeUp$Res implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+
+
+}
+>>
+```
+
+
+
+## 服务接口
+
+
+
+```java
+classTemplate(module,item,operationList,basics) ::= <<
+package $module.packageName$.service;
+
+/**
+ * $item.itemName$ 服务层接口
+ * @author xia
+ */
+public interface $basics.itemCodeUp$Service {
+
+$operationList:{op |
+
+ /**
+ * $op.funName$
+ * $op.describe$
+ */
+ XResult<$if(op.isPage)$Page<$basics.itemCodeUp$$op.methodNamePascalCase$Res>$else$$if(op.isResParams)$$basics.itemCodeUp$$op.methodNamePascalCase$Res$else$Void$endif$$endif$> $op.methodName$($if(op.isReqParams)$$basics.itemCodeUp$$op.methodNamePascalCase$Req req$endif$$if(op.isPathParams)$$op.pathParamsLongReq$$endif$);
+
+}$
+
+}
+>>
+```
+
+
+
+
+
+## 服务接口实现
+
+
+
+```java
+classTemplate(module,item,operationList,basics) ::= <<
+package $module.packageName$.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import $module.packageName$.service.$basics.itemCodeUp$Service;
+
+/**
+ * $item.itemName$ 服务层接口
+ * @author xia
+ */
+@Service
+@RequiredArgsConstructor
+public class $basics.itemCodeUp$ServiceImpl implements $basics.itemCodeUp$Service{
+
+$operationList:{op |
+
+ /**
+ * $op.funName$
+ * $op.describe$
+ */
+ @Override
+ public XResult<$if(op.isPage)$Page<$basics.itemCodeUp$$op.methodNamePascalCase$Res>$else$$if(op.isResParams)$$basics.itemCodeUp$$op.methodNamePascalCase$Res$else$Void$endif$$endif$> $op.methodName$($if(op.isReqParams)$$basics.itemCodeUp$$op.methodNamePascalCase$Req req$endif$$if(op.isPathParams)$$op.pathParamsLongReq$$endif$){
+ // TODO $op.funName$ 实现
+ return null;
+ \}
+
+}$
+
+}
+>>
+```
+
+
+
+
+
diff --git a/generated_code.zip b/generated_code.zip
new file mode 100644
index 0000000..cff00ed
Binary files /dev/null and b/generated_code.zip differ
diff --git a/pom.xml b/pom.xml
index 1a57c57..067622b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,7 @@
9.1.0.Final
2.8.15
4.3.4
+ 2.15.2
@@ -102,8 +103,17 @@
org.antlr
ST4
${ST4.version}
+ compile
+
+
+ com.yomahub
+ liteflow-spring-boot-starter
+ ${liteflow.version}
+
+
+
org.springframework.boot
diff --git a/src/main/java/com/cczsa/xinghe/codegen/CodegenApplication.java b/src/main/java/com/cczsa/xinghe/codegen/CodegenApplication.java
index 52f0334..d6c5e67 100644
--- a/src/main/java/com/cczsa/xinghe/codegen/CodegenApplication.java
+++ b/src/main/java/com/cczsa/xinghe/codegen/CodegenApplication.java
@@ -3,6 +3,7 @@ package com.cczsa.xinghe.codegen;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+
@SpringBootApplication
public class CodegenApplication {
diff --git a/src/main/java/com/cczsa/xinghe/codegen/controller/CodeGenController.java b/src/main/java/com/cczsa/xinghe/codegen/controller/CodeGenController.java
index bb513f7..6d95522 100644
--- a/src/main/java/com/cczsa/xinghe/codegen/controller/CodeGenController.java
+++ b/src/main/java/com/cczsa/xinghe/codegen/controller/CodeGenController.java
@@ -1,11 +1,14 @@
package com.cczsa.xinghe.codegen.controller;
+import com.cczsa.xinghe.codegen.entity.domain.template.CodeGen;
import com.cczsa.xinghe.codegen.service.CodeGenService;
+import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -22,9 +25,9 @@ public class CodeGenController {
private final CodeGenService codeGenService;
@PostMapping("/download")
- public ResponseEntity downloadCode() {
+ public ResponseEntity downloadCode(@RequestBody @Valid CodeGen req) {
try {
- byte[] zipData = codeGenService.generateCodeZip();
+ byte[] zipData = codeGenService.generateCodeZip(req);
return ResponseEntity.ok()
// 设置下载文件名
diff --git a/src/main/java/com/cczsa/xinghe/codegen/entity/domain/package-info.java b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/package-info.java
new file mode 100644
index 0000000..0537981
--- /dev/null
+++ b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * @author xia
+ * @date 2026/1/17
+ * @version 0.0.1
+ */
+package com.cczsa.xinghe.codegen.entity.domain;
\ No newline at end of file
diff --git a/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/CodeGen.java b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/CodeGen.java
new file mode 100644
index 0000000..dc325b0
--- /dev/null
+++ b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/CodeGen.java
@@ -0,0 +1,42 @@
+package com.cczsa.xinghe.codegen.entity.domain.template;
+
+import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.AssertTrue;
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author xia
+ * @date 2026/1/17
+ * @version 0.0.1
+ */
+@Getter
+@Setter
+@Schema(description = "代码生成-参数")
+public class CodeGen implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @NotNull(message = "代码生成类型不能为空")
+ @Schema(description = "代码生成类型")
+ private CodeGenType codeGenType;
+
+ @Schema(description = "功能ID")
+ private Long itemId;
+
+
+ @Hidden
+ @AssertTrue(message = "当代码生成类型为 CONTROLLER 时,功能ID不能为空")
+ public boolean isItemIdValid() {
+ if (codeGenType == CodeGenType.CONTROLLER) {
+ return itemId != null;
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/CodeGenType.java b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/CodeGenType.java
new file mode 100644
index 0000000..98c0862
--- /dev/null
+++ b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/CodeGenType.java
@@ -0,0 +1,33 @@
+package com.cczsa.xinghe.codegen.entity.domain.template;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.mybatisflex.annotation.EnumValue;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+
+/**
+ * @author xia
+ * @date 2026/1/17
+ * @version 0.0.1
+ */
+@Schema(description = "代码生成类型")
+@Getter
+public enum CodeGenType {
+
+ CONTROLLER(0, "controller");
+
+ private final int code;
+ private final String desc;
+
+ CodeGenType(int code, String desc) {
+ this.code = code;
+ this.desc = desc;
+ }
+
+ @EnumValue
+ @JsonValue
+ public int getCode() {
+ return code;
+ }
+
+}
diff --git a/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/FunOperationTemp.java b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/FunOperationTemp.java
new file mode 100644
index 0000000..4603118
--- /dev/null
+++ b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/FunOperationTemp.java
@@ -0,0 +1,60 @@
+package com.cczsa.xinghe.codegen.entity.domain.template;
+
+import com.cczsa.xinghe.codegen.entity.FunOperationEntity;
+import com.cczsa.xinghe.codegen.util.StringUtils;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @author xia
+ * @date 2026/1/19
+ * @version 0.0.1
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class FunOperationTemp extends FunOperationEntity {
+
+ /**
+ * URL路径
+ */
+ private String url;
+ /**
+ * 方法名
+ */
+ private String methodName;
+ /**
+ * 方法名 驼峰
+ */
+ private String methodNamePascalCase;
+ /**
+ * 路径参数是否为空
+ */
+ private Boolean isPathParams;
+ /**
+ * 路径参数 Long入参
+ */
+ private String pathParamsLong;
+ /**
+ * 路径参数 Long入参
+ */
+ private String pathParamsLongReq;
+ /**
+ * 路径参数 url入参
+ */
+ private String pathParamsUrl;
+
+
+ public void info(){
+ url = StringUtils.toPath(this.getOperationCode());
+ methodName = StringUtils.toCamelCase(this.getOperationCode());
+ methodNamePascalCase = StringUtils.toPascalCase(this.getOperationCode());
+ isPathParams= StringUtils.isNotEmpty(this.getPathParams());
+ if (isPathParams){
+ pathParamsLongReq = "Long "+this.getPathParams();
+ pathParamsLong = "@PathVariable(\""+this.getPathParams()+"\") Long "+this.getPathParams();
+ pathParamsUrl = "/{"+this.getPathParams()+"}";
+ }
+ }
+
+
+}
diff --git a/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/FunctionCode.java b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/FunctionCode.java
new file mode 100644
index 0000000..311b7ec
--- /dev/null
+++ b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/FunctionCode.java
@@ -0,0 +1,28 @@
+package com.cczsa.xinghe.codegen.entity.domain.template;
+
+import com.cczsa.xinghe.codegen.entity.FunItemEntity;
+import com.cczsa.xinghe.codegen.entity.FunModuleEntity;
+import com.cczsa.xinghe.codegen.entity.FunOperationEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * @author xia
+ * @date 2026/1/17
+ * @version 0.0.1
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class FunctionCode extends TemplateObj{
+
+ // 模块信息
+ private FunModuleEntity moduleEntity;
+
+ // 功能信息
+ private FunItemEntity itemEntity;
+
+ // 操作信息
+ private List operationEntityList;
+}
diff --git a/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/TemplateObj.java b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/TemplateObj.java
new file mode 100644
index 0000000..7de74f0
--- /dev/null
+++ b/src/main/java/com/cczsa/xinghe/codegen/entity/domain/template/TemplateObj.java
@@ -0,0 +1,10 @@
+package com.cczsa.xinghe.codegen.entity.domain.template;
+
+/**
+ * @author xia
+ * @date 2026/1/17
+ * @version 0.0.1
+ */
+public class TemplateObj {
+
+}
diff --git a/src/main/java/com/cczsa/xinghe/codegen/entity/enums/TemplateTypeEnum.java b/src/main/java/com/cczsa/xinghe/codegen/entity/enums/TemplateTypeEnum.java
index ed3a389..a8a6d04 100644
--- a/src/main/java/com/cczsa/xinghe/codegen/entity/enums/TemplateTypeEnum.java
+++ b/src/main/java/com/cczsa/xinghe/codegen/entity/enums/TemplateTypeEnum.java
@@ -16,13 +16,15 @@ import java.util.stream.Collectors;
*
* @author My
*/
-@Schema(description = "从接口:/templateType 获取列表",
- example = "0",
- allowableValues = {"0: controller"})
+@Schema(description = "从接口:/templateType 获取列表")
@Getter
public enum TemplateTypeEnum {
- CONTROLLER(0, "controller");
+ CONTROLLER(0, "controller"),
+ REQUEST_PARAM(1, "请求参数"),
+ RESPONSE_PARAM(2, "响应参数"),
+ SERVICE(3, "服务接口"),
+ SERVICE_IMPL(4, "服务实现");
private final int code;
private final String desc;
diff --git a/src/main/java/com/cczsa/xinghe/codegen/entity/req/role/RoleBindFunReq.java b/src/main/java/com/cczsa/xinghe/codegen/entity/req/role/RoleBindFunReq.java
index 1739145..7824b1d 100644
--- a/src/main/java/com/cczsa/xinghe/codegen/entity/req/role/RoleBindFunReq.java
+++ b/src/main/java/com/cczsa/xinghe/codegen/entity/req/role/RoleBindFunReq.java
@@ -8,6 +8,7 @@ import lombok.Getter;
import lombok.Setter;
import java.io.Serial;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -41,7 +42,7 @@ public class RoleBindFunReq implements Serializable {
private List userDataScope;
@Schema(description = "排除的字段 根据 fieldConfig 进行设置")
- private List excludeField;
+ private List excludeField = new ArrayList<>();
@NotNull(message = "客户端类型不能为空")
@Schema(description = "客户端类型:0 PC端,1 小程序端,2 H5端")
diff --git a/src/main/java/com/cczsa/xinghe/codegen/entity/res/role/RoleQueryFunRes.java b/src/main/java/com/cczsa/xinghe/codegen/entity/res/role/RoleQueryFunRes.java
index b676010..712ffaf 100644
--- a/src/main/java/com/cczsa/xinghe/codegen/entity/res/role/RoleQueryFunRes.java
+++ b/src/main/java/com/cczsa/xinghe/codegen/entity/res/role/RoleQueryFunRes.java
@@ -1,6 +1,7 @@
package com.cczsa.xinghe.codegen.entity.res.role;
import com.cczsa.xinghe.codegen.entity.enums.ClientTypeEnum;
+import com.cczsa.xinghe.codegen.entity.enums.MenuTypeEnum;
import com.cczsa.xinghe.codegen.entity.enums.UsableConfigEnum;
import com.cczsa.xinghe.codegen.handler.PostgreSQLJsonTypeHandler;
import com.mybatisflex.annotation.Column;
@@ -38,6 +39,8 @@ public class RoleQueryFunRes implements Serializable {
@Schema(description = "父菜单ID")
private Long parentId = 0L;
+ private MenuTypeEnum menuType;
+
@Schema(description = "路由路径")
private String path;
diff --git a/src/main/java/com/cczsa/xinghe/codegen/service/CodeGenService.java b/src/main/java/com/cczsa/xinghe/codegen/service/CodeGenService.java
index 835d9ae..d34cb2b 100644
--- a/src/main/java/com/cczsa/xinghe/codegen/service/CodeGenService.java
+++ b/src/main/java/com/cczsa/xinghe/codegen/service/CodeGenService.java
@@ -1,10 +1,12 @@
package com.cczsa.xinghe.codegen.service;
+import com.cczsa.xinghe.codegen.entity.domain.template.CodeGen;
+
/**
* @author xia
* @date 2026/1/10
* @version 0.0.1
*/
public interface CodeGenService {
- byte[] generateCodeZip();
+ byte[] generateCodeZip(CodeGen req);
}
diff --git a/src/main/java/com/cczsa/xinghe/codegen/service/TemplateService.java b/src/main/java/com/cczsa/xinghe/codegen/service/TemplateService.java
index 159ae15..545a13f 100644
--- a/src/main/java/com/cczsa/xinghe/codegen/service/TemplateService.java
+++ b/src/main/java/com/cczsa/xinghe/codegen/service/TemplateService.java
@@ -1,5 +1,6 @@
package com.cczsa.xinghe.codegen.service;
+import com.cczsa.xinghe.codegen.entity.enums.TemplateTypeEnum;
import com.cczsa.xinghe.codegen.entity.req.template.TemplateAddReq;
import com.cczsa.xinghe.codegen.entity.req.template.TemplateEditReq;
import com.cczsa.xinghe.codegen.entity.req.template.TemplateQueryReq;
@@ -42,4 +43,11 @@ public interface TemplateService {
*/
XResult>> getTemplateTypeList();
+
+ /**
+ * 根据模板类型获取模板内容的方法
+ * @return 返回对应模板类型的字符串内容
+ */
+ String getTemplateTypeContent(TemplateTypeEnum templateType);
+
}
\ No newline at end of file
diff --git a/src/main/java/com/cczsa/xinghe/codegen/service/flow/package-info.java b/src/main/java/com/cczsa/xinghe/codegen/service/flow/package-info.java
new file mode 100644
index 0000000..0bc5894
--- /dev/null
+++ b/src/main/java/com/cczsa/xinghe/codegen/service/flow/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * @author xia
+ * @date 2026/1/17
+ * @version 0.0.1
+ */
+package com.cczsa.xinghe.codegen.service.flow;
\ No newline at end of file
diff --git a/src/main/java/com/cczsa/xinghe/codegen/service/flow/template/ControllerCodeCreate.java b/src/main/java/com/cczsa/xinghe/codegen/service/flow/template/ControllerCodeCreate.java
new file mode 100644
index 0000000..31fc680
--- /dev/null
+++ b/src/main/java/com/cczsa/xinghe/codegen/service/flow/template/ControllerCodeCreate.java
@@ -0,0 +1,208 @@
+package com.cczsa.xinghe.codegen.service.flow.template;
+
+import com.cczsa.xinghe.codegen.entity.FunItemEntity;
+import com.cczsa.xinghe.codegen.entity.FunModuleEntity;
+import com.cczsa.xinghe.codegen.entity.domain.template.FunOperationTemp;
+import com.cczsa.xinghe.codegen.entity.enums.TemplateTypeEnum;
+import com.cczsa.xinghe.codegen.mapper.FunItemMapper;
+import com.cczsa.xinghe.codegen.mapper.FunModuleMapper;
+import com.cczsa.xinghe.codegen.mapper.FunOperationMapper;
+import com.cczsa.xinghe.codegen.mapper.def.FunOperationDef;
+import com.cczsa.xinghe.codegen.service.TemplateService;
+import com.cczsa.xinghe.codegen.util.StringUtils;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.stringtemplate.v4.ST;
+import org.stringtemplate.v4.STGroup;
+import org.stringtemplate.v4.STGroupString;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @author xia
+ * @date 2026/1/17
+ * @version 0.0.1
+ */
+@Slf4j
+@RequiredArgsConstructor
+@LiteflowComponent(id = "controllerCodeCreate", name = "controller 代码生成")
+public class ControllerCodeCreate extends NodeComponent {
+
+ private final FunModuleMapper funModuleMapper;
+ private final FunItemMapper funItemMapper;
+ private final FunOperationMapper funOperationMapper;
+ private final TemplateService templateService;
+
+
+
+ // 模块信息
+ private FunModuleEntity moduleEntity;
+ // 功能信息
+ private FunItemEntity itemEntity;
+ // 操作信息
+ private List operationEntityList;
+ // 压缩流
+ private ZipOutputStream zos;
+
+
+ /**
+ * setResponseData()/getResponseData():用于整个流程的输入输出
+ * setInput()/getInput():用于节点间的输入数据
+ * setOutput()/getOutput():用于节点间的输出数据
+ * setChainReqData()/getChainReqData():用于链间数据传递
+ */
+
+ @Override
+ public void process() throws Exception {
+ Long itemId = this.getContextBean(Long.class);
+
+ itemEntity = funItemMapper.selectOneById(itemId);
+ if (itemEntity == null){
+ return;
+ }
+
+ moduleEntity = funModuleMapper.selectOneById(itemEntity.getModuleId());
+ if (itemEntity == null){
+ return;
+ }
+ FunOperationDef def = FunOperationDef.FUN_OPERATION_ENTITY;
+ QueryWrapper query = QueryWrapper.create()
+ .select(def.ALL_COLUMNS)
+ .from(def)
+ .where(def.MODULE_ID.eq(moduleEntity.getId()))
+ .and(def.ITEM_ID.eq(itemId))
+ .orderBy(def.ID,true);
+ operationEntityList = funOperationMapper.selectListByQueryAs(query,FunOperationTemp.class);
+ if (operationEntityList == null){
+ operationEntityList = List.of();
+ }
+ operationEntityList.forEach(FunOperationTemp::info);
+
+ // 流
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ zos = new ZipOutputStream(outputStream);
+ // 生成controller代码
+ generateControllerCode(TemplateTypeEnum.CONTROLLER);
+ // 生成请求参数代码
+ generateControllerCode(TemplateTypeEnum.REQUEST_PARAM);
+ // 生成响应参数代码
+ generateControllerCode(TemplateTypeEnum.RESPONSE_PARAM);
+ // 生成服务接口代码
+ generateControllerCode(TemplateTypeEnum.SERVICE);
+ // 生成服务接口实现代码
+ generateControllerCode(TemplateTypeEnum.SERVICE_IMPL);
+
+ // 确保 ZIP 流结束
+ zos.finish();
+ // 返回数据
+ this.getSlot().setResponseData(outputStream.toByteArray());
+ }
+
+ /**
+ * 生成Controller代码
+ */
+ public void generateControllerCode(TemplateTypeEnum templateType) throws IOException {
+ // 获取
+ String template = templateService.getTemplateTypeContent(templateType);
+ if (template == null){
+ return;
+ }
+ // 生成
+ STGroup group = new STGroupString("db_template",template, '$', '$');
+ ST classTemplate = group.getInstanceOf("classTemplate"); // 使用正确的模板名称
+
+ if (classTemplate == null) {
+ System.out.println(template);
+ throw new RuntimeException("Template 'classTemplate' not found");
+ }
+
+ // 设置模板数据
+ Map basics = new HashMap<>();
+ setTemplateData(classTemplate,basics);
+
+ if(templateType == TemplateTypeEnum.REQUEST_PARAM || templateType == TemplateTypeEnum.RESPONSE_PARAM){
+ for (FunOperationTemp item : operationEntityList){
+ if (!isValidItem(templateType, item)) {
+ continue;
+ }
+ // 生成代码并写入ZIP
+ String itemStr = StringUtils.capitalizeFirstLetter(itemEntity.getItemCode());
+ String operStr = StringUtils.toPascalCase(item.getOperationCode());
+ basics.put("itemCodeUp", itemStr+operStr);
+ basics.put("oper", item);
+ writeCodeToZip(classTemplate, templateType, itemStr+ operStr);
+ }
+ return;
+ }
+
+ // 生成代码并写入ZIP
+ writeCodeToZip(classTemplate, templateType,StringUtils.capitalizeFirstLetter(itemEntity.getItemCode()));
+ }
+
+ private boolean isValidItem(TemplateTypeEnum templateType, FunOperationTemp item) {
+ return switch (templateType) {
+ case REQUEST_PARAM -> item.getIsReqParams();
+ case RESPONSE_PARAM -> item.getIsResParams();
+ default -> false;
+ };
+ }
+
+ /**
+ * 根据 TemplateTypeEnum 获取文件后缀名
+ * @param templateType 模板类型
+ * @return 文件后缀名
+ */
+ private String getFileSuffix(TemplateTypeEnum templateType) {
+ return switch (templateType) {
+ case CONTROLLER -> "Controller.java";
+ case REQUEST_PARAM -> "Req.java";
+ case RESPONSE_PARAM -> "Res.java";
+ case SERVICE -> "Service.java";
+ case SERVICE_IMPL -> "ServiceImpl.java";
+ };
+ }
+
+ /**
+ * 设置模板数据
+ *
+ * @param classTemplate 模板对象
+ * @param basics
+ */
+ private void setTemplateData(ST classTemplate, Map basics) {
+ classTemplate.add("item", itemEntity);
+ classTemplate.add("module", moduleEntity);
+ classTemplate.add("operationList", operationEntityList);
+
+ // 基础信息
+
+ String itemCodeUp = StringUtils.capitalizeFirstLetter(itemEntity.getItemCode());
+ basics.put("itemCodeUp", itemCodeUp);
+ basics.put("path", StringUtils.toPath(itemEntity.getItemCode()));
+
+ classTemplate.add("basics", basics);
+ }
+
+ /**
+ * 生成代码并写入ZIP
+ */
+ private void writeCodeToZip(ST classTemplate, TemplateTypeEnum templateType,String fileName) throws IOException {
+ // 生成代码
+ String result = classTemplate.render();
+
+ ZipEntry entry = new ZipEntry(fileName + getFileSuffix(templateType));
+ zos.putNextEntry(entry);
+ zos.write(result.getBytes(StandardCharsets.UTF_8));
+ zos.closeEntry();
+ }
+
+}
diff --git a/src/main/java/com/cczsa/xinghe/codegen/service/impl/CodeGenServiceImpl.java b/src/main/java/com/cczsa/xinghe/codegen/service/impl/CodeGenServiceImpl.java
index 4a7f66f..5bead73 100644
--- a/src/main/java/com/cczsa/xinghe/codegen/service/impl/CodeGenServiceImpl.java
+++ b/src/main/java/com/cczsa/xinghe/codegen/service/impl/CodeGenServiceImpl.java
@@ -1,6 +1,15 @@
package com.cczsa.xinghe.codegen.service.impl;
+import com.cczsa.xinghe.codegen.entity.FunItemEntity;
+import com.cczsa.xinghe.codegen.entity.domain.template.CodeGen;
+import com.cczsa.xinghe.codegen.entity.domain.template.CodeGenType;
+import com.cczsa.xinghe.codegen.mapper.FunItemMapper;
+import com.cczsa.xinghe.codegen.mapper.FunModuleMapper;
+import com.cczsa.xinghe.codegen.mapper.FunOperationMapper;
import com.cczsa.xinghe.codegen.service.CodeGenService;
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -15,8 +24,26 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class CodeGenServiceImpl implements CodeGenService {
+ private final FunModuleMapper funModuleMapper;
+ private final FunItemMapper funItemMapper;
+ private final FunOperationMapper funOperationMapper;
+ @Resource
+ private FlowExecutor flowExecutor;
+
@Override
- public byte[] generateCodeZip() {
+ public byte[] generateCodeZip(CodeGen req) {
+
+
+ FunItemEntity funItemEntity = funItemMapper.selectOneById(req.getItemId());
+ if(req.getCodeGenType() == CodeGenType.CONTROLLER && funItemEntity != null){
+ LiteflowResponse response = flowExecutor.execute2Resp("controller",null,req.getItemId());
+ log.info("执行结果:{}",response);
+
+ if (response.isSuccess()) {
+ return (byte[]) response.getSlot().getResponseData();
+ }
+ }
+
return null;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/cczsa/xinghe/codegen/service/impl/RoleServiceImpl.java b/src/main/java/com/cczsa/xinghe/codegen/service/impl/RoleServiceImpl.java
index 5cf4ea2..9f61d7e 100644
--- a/src/main/java/com/cczsa/xinghe/codegen/service/impl/RoleServiceImpl.java
+++ b/src/main/java/com/cczsa/xinghe/codegen/service/impl/RoleServiceImpl.java
@@ -129,6 +129,14 @@ public class RoleServiceImpl implements RoleService {
*/
@Override
public XResult bindFun(RoleBindFunReq req) {
+ // ID = 操作id+ 角色ID + 客户端类型
+ String id = req.getFunId().toString() + req.getRoleId().toString()+req.getClientType().getCode();
+
+ FunOperationEntity funOperationEntity1 = funOperationMapper.selectOneById(Long.parseLong(id));
+ if (funOperationEntity1 != null) {
+ return XResult.ok();
+ }
+
FunOperationEntity funOperationEntity = funOperationMapper.selectOneById(req.getFunId());
if (funOperationEntity == null) {
return XResult.failed("权限不存在");
@@ -175,27 +183,14 @@ public class RoleServiceImpl implements RoleService {
roleFunEntity.setDataScope(req.getDataScope());
roleFunEntity.setExcludeField(req.getExcludeField());
roleFunEntity.setClientType(req.getClientType());
- if (req.getExcludeField() == null || !req.getExcludeField().isEmpty()) {
- roleFunEntity.setExcludeField(new ArrayList<>());
- }
// 删除旧权限
QueryWrapper deleteRoleFun = new QueryWrapper();
+ deleteRoleFun.eq(RoleFunEntity::getClientType, req.getClientType());
deleteRoleFun.eq(RoleFunEntity::getRoleId, req.getRoleId());
deleteRoleFun.eq(RoleFunEntity::getFunId, req.getFunId());
roleFunMapper.deleteByQuery(deleteRoleFun);
- // 获取最大ID
- RoleFunDef roleFunDef = RoleFunDef.ROLE_FUN_ENTITY;
- QueryWrapper queryRoleFun = new QueryWrapper();
- queryRoleFun.select(roleFunDef.ID)
- .from(roleFunDef)
- .orderBy(roleFunDef.ID, false);
- Long maxId = roleFunMapper.selectOneByQueryAs(queryRoleFun, Long.class);
- if (maxId == null) {
- maxId = 10000L;
- } else {
- maxId++;
- }
- roleFunEntity.setId(maxId);
+
+ roleFunEntity.setId(Long.parseLong(id));
// 新增权限
roleFunMapper.insertSelective(roleFunEntity);
return XResult.ok();
@@ -232,13 +227,15 @@ public class RoleServiceImpl implements RoleService {
)
.from(menuDef)
.leftJoin(funOperationDef).on(menuDef.FUN_ID.eq(funOperationDef.ID))
- .eq(MenuEntity::getClientType, req.getClientType());
+ .eq(MenuEntity::getClientType, req.getClientType())
+ .orderBy(MenuEntity::getSortOrder, false);
List roleQueryFunList = menuMapper.selectListByQueryAs(queryMenu, RoleQueryFunRes.class);
// 获取角色功能权限
RoleFunDef roleFunDef = RoleFunDef.ROLE_FUN_ENTITY;
QueryWrapper queryRoleFun = new QueryWrapper();
queryRoleFun.select(roleFunDef.ALL_COLUMNS)
.from(roleFunDef)
+ .eq(RoleFunEntity::getClientType,req.getClientType())
.eq(RoleFunEntity::getRoleId, req.getRoleId());
List roleFunEntities = roleFunMapper.selectListByQuery(queryRoleFun);
for (RoleQueryFunRes roleQueryFunRes : roleQueryFunList) {
diff --git a/src/main/java/com/cczsa/xinghe/codegen/service/impl/TemplateServiceImpl.java b/src/main/java/com/cczsa/xinghe/codegen/service/impl/TemplateServiceImpl.java
index 2d3c951..de45eef 100644
--- a/src/main/java/com/cczsa/xinghe/codegen/service/impl/TemplateServiceImpl.java
+++ b/src/main/java/com/cczsa/xinghe/codegen/service/impl/TemplateServiceImpl.java
@@ -63,9 +63,43 @@ public class TemplateServiceImpl implements TemplateService {
TemplateEntity templateEntity = new TemplateEntity();
BeanUtils.copyProperties(req, templateEntity);
templateMapper.insert(templateEntity);
+ // 其它的设置为未使用
+ if(req.getIsUse()){
+ setCurrentTemplate(templateEntity.getId());
+ }
return XResult.ok();
}
+ /**
+ * 设置模板为当前使用
+ * 同一类型下只能有一个模板被标记为使用中
+ * @param templateId 模板ID
+ */
+ public void setCurrentTemplate(Long templateId) {
+ // 获取模板
+ TemplateEntity templateEntity = templateMapper.selectOneById(templateId);
+ if (templateEntity == null) {
+ return;
+ }
+
+ // 获取模板类型
+ TemplateTypeEnum templateType = templateEntity.getTemplateType();
+
+ // 查询同一类型下的所有模板
+ QueryWrapper queryWrapper = new QueryWrapper();
+ queryWrapper.eq(TemplateEntity::getTemplateType, templateType);
+
+ // 将同一类型下除当前模板外的所有模板设置为未使用
+ TemplateEntity entity = new TemplateEntity();
+ entity.setIsUse(false);
+ templateMapper.updateByQuery(entity,true,queryWrapper);
+
+ // 将当前模板设置为使用中
+ templateEntity.setIsUse(true);
+ templateMapper.update(templateEntity);
+ }
+
+
/**
* 修改模板
*/
@@ -86,6 +120,10 @@ public class TemplateServiceImpl implements TemplateService {
}
BeanUtils.copyProperties(req, templateEntity);
templateMapper.update(templateEntity);
+ // 其它的设置为未使用
+ if(req.getIsUse()){
+ setCurrentTemplate(templateEntity.getId());
+ }
return XResult.ok();
}
@@ -106,4 +144,15 @@ public class TemplateServiceImpl implements TemplateService {
return XResult.ok(TemplateTypeEnum.getEnumList());
}
+ @Override
+ public String getTemplateTypeContent(TemplateTypeEnum templateType) {
+ TemplateDef def = TemplateDef.TEMPLATE_ENTITY;
+ QueryWrapper query = new QueryWrapper();
+
+ query.select(def.CONTENT)
+ .from(def)
+ .where(def.TEMPLATE_TYPE.eq(templateType))
+ .and(def.IS_USE.eq(true));
+ return templateMapper.selectOneByQueryAs(query, String.class);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/cczsa/xinghe/codegen/util/StringUtils.java b/src/main/java/com/cczsa/xinghe/codegen/util/StringUtils.java
new file mode 100644
index 0000000..94e118d
--- /dev/null
+++ b/src/main/java/com/cczsa/xinghe/codegen/util/StringUtils.java
@@ -0,0 +1,105 @@
+package com.cczsa.xinghe.codegen.util;
+
+/**
+ * 字符串工具类
+ *
+ * @author xia
+ * @date 2026/1/19
+ * @version 0.0.1
+ */
+public class StringUtils {
+
+ /**
+ * 将字符串首字母大写
+ * @param str 输入字符串
+ * @return 首字母大写的字符串
+ */
+ public static String capitalizeFirstLetter(String str) {
+ if (str == null || str.isEmpty()) {
+ return str;
+ }
+ return str.substring(0, 1).toUpperCase() + str.substring(1);
+ }
+
+ /**
+ * 将字符串转为路径
+ * 例子: user-add -> /user/add
+ * @param str 输入字符串
+ * @return 转换后的路径字符串
+ */
+ public static String toPath(String str) {
+ if (str == null || str.isEmpty()) {
+ return str;
+ }
+ // 将连字符替换为斜杠,并在开头添加斜杠
+ return "/" + str.replace("-", "/");
+ }
+
+ /**
+ * 将连字符分隔的字符串转为驼峰命名
+ * 例子:test-user -> testUser
+ * @param str 输入字符串
+ * @return 驼峰命名字符串
+ */
+ public static String toCamelCase(String str) {
+ if (str == null || str.isEmpty()) {
+ return str;
+ }
+ StringBuilder result = new StringBuilder();
+ String[] parts = str.split("-");
+ for (int i = 0; i < parts.length; i++) {
+ if (i == 0) {
+ // 第一部分保持原样(小写)
+ result.append(parts[i]);
+ } else {
+ // 后续部分首字母大写
+ if (!parts[i].isEmpty()) {
+ result.append(parts[i].substring(0, 1).toUpperCase())
+ .append(parts[i].substring(1));
+ }
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * 将连字符分隔的字符串转为大驼峰命名(PascalCase)
+ * 例子:test-user -> TestUser
+ * @param str 输入字符串
+ * @return 大驼峰命名字符串
+ */
+ public static String toPascalCase(String str) {
+ if (str == null || str.isEmpty()) {
+ return str;
+ }
+ StringBuilder result = new StringBuilder();
+ String[] parts = str.split("-");
+ for (String part : parts) {
+ if (!part.isEmpty()) {
+ result.append(part.substring(0, 1).toUpperCase())
+ .append(part.substring(1));
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * 判断字符串是否为空
+ * 包括 null、空字符串 ""、"null"(不区分大小写)
+ * @param str 输入字符串
+ * @return 如果为空返回 true,否则返回 false
+ */
+ public static boolean isEmpty(String str) {
+ return str == null || str.isEmpty() || str.trim().equalsIgnoreCase("null");
+ }
+
+ /**
+ * 判断字符串是否不为空
+ * @param str 输入字符串
+ * @return 如果不为空返回 true,否则返回 false
+ */
+ public static boolean isNotEmpty(String str) {
+ return !isEmpty(str);
+ }
+
+}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
new file mode 100644
index 0000000..484b676
--- /dev/null
+++ b/src/main/resources/application-dev.yml
@@ -0,0 +1,22 @@
+server:
+ port: 7011
+
+spring:
+ datasource:
+ url: jdbc:postgresql://192.168.1.26:5432/xinghe-codegen
+ username: postgres
+ password: root
+ driver-class-name: org.postgresql.Driver
+ type: com.zaxxer.hikari.HikariDataSource # 使用 HikariCP 作为连接池
+ hikari:
+ maximum-pool-size: 20
+ minimum-idle: 5
+ connection-timeout: 60000
+ idle-timeout: 300000
+ max-lifetime: 1200000
+ validation-timeout: 5000
+ leak-detection-threshold: 60000
+
+liteflow:
+ rule-source: liteflow/*.el.xml
+ enable: true
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 6051032..d6c9f73 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,18 +1,4 @@
-server:
- port: 7011
-
spring:
- datasource:
- url: jdbc:postgresql://192.168.1.26:5432/xinghe-codegen
- username: postgres
- password: root
- driver-class-name: org.postgresql.Driver
- type: com.zaxxer.hikari.HikariDataSource # 使用 HikariCP 作为连接池
- hikari:
- maximum-pool-size: 20
- minimum-idle: 5
- connection-timeout: 60000
- idle-timeout: 300000
- max-lifetime: 1200000
- validation-timeout: 5000
- leak-detection-threshold: 60000
\ No newline at end of file
+ # 使用不同模块的数据库配置
+ profiles:
+ active: dev
\ No newline at end of file
diff --git a/src/main/resources/liteflow/controller.el.xml b/src/main/resources/liteflow/controller.el.xml
new file mode 100644
index 0000000..6f599e5
--- /dev/null
+++ b/src/main/resources/liteflow/controller.el.xml
@@ -0,0 +1,7 @@
+
+
+
+
+ THEN(controllerCodeCreate);
+
+
\ No newline at end of file
diff --git a/src/test/java/com/cczsa/xinghe/codegen/CodeGenTest.java b/src/test/java/com/cczsa/xinghe/codegen/CodeGenTest.java
index 3ac646a..c72a126 100644
--- a/src/test/java/com/cczsa/xinghe/codegen/CodeGenTest.java
+++ b/src/test/java/com/cczsa/xinghe/codegen/CodeGenTest.java
@@ -22,15 +22,15 @@ public class CodeGenTest {
@Test
@DisplayName("直接运行代码生成逻辑并输出到本地文件")
void executeCodeGen() {
- // 1. 模拟 CodeGenServiceImpl.java 中的逻辑
+ // 创建字节输出流,用于在内存中构建 ZIP 文件
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
- // 定义模板语法
+ // 定义 StringTemplate 模板语法,用于生成 Java 类代码
String template = """
classTemplate(packageName, className, description, author) ::= <<
package ;
-
+
/**
*
* @author
@@ -41,31 +41,36 @@ public class CodeGenTest {
>>
""";
- // 初始化 StringTemplate 组
+ // 初始化 StringTemplate 组,加载模板定义
STGroup group = new STGroupString(template);
+ // 获取模板实例
ST st = group.getInstanceOf("classTemplate");
if (st == null) {
throw new RuntimeException("Template 'classTemplate' not found");
}
- // 填充数据
+ // 向模板实例填充数据
st.add("packageName", "com.example.generated");
st.add("className", "HelloWorld");
st.add("description", "这是一个通过测试类直接生成的示例");
st.add("author", "Gemini_Test");
+ // 渲染模板,生成最终的 Java 代码字符串
String result = st.render();
- // 写入 ZIP 流
+ // 创建 ZIP 条目,代表 ZIP 文件中的一个文件
ZipEntry entry = new ZipEntry("com/example/generated/HelloWorld.java");
+ // 将条目写入 ZIP 输出流
zos.putNextEntry(entry);
+ // 将生成的代码写入 ZIP 条目
zos.write(result.getBytes(StandardCharsets.UTF_8));
+ // 关闭当前 ZIP 条目
zos.closeEntry();
zos.finish(); // 确保 ZIP 流结束
- // 2. 将结果保存到本地磁盘(方便查看结果)
+ // 将内存中的 ZIP 内容写入本地文件,方便查看结果
try (FileOutputStream fos = new FileOutputStream("generated_code.zip")) {
baos.writeTo(fos);
System.out.println("代码生成成功!请检查项目根目录下的 generated_code.zip");
@@ -90,7 +95,7 @@ public class CodeGenTest {
String template = """
classTemplate(packageName, className, description, author) ::= <<
package ;
-
+
/**
*
* @author