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