代码生成

This commit is contained in:
2026-01-19 21:30:09 +08:00
parent b3def6eb10
commit 06979818be
26 changed files with 933 additions and 51 deletions

261
doc/模版.md Normal file
View File

@@ -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;
\}
}$
}
>>
```

BIN
generated_code.zip Normal file

Binary file not shown.

10
pom.xml
View File

@@ -23,6 +23,7 @@
<hibernate-validator.version>9.1.0.Final</hibernate-validator.version>
<springdoc-openapi-starter-webmvc-ui.version>2.8.15</springdoc-openapi-starter-webmvc-ui.version>
<ST4.version>4.3.4</ST4.version>
<liteflow.version>2.15.2</liteflow.version>
</properties>
<dependencies>
@@ -102,8 +103,17 @@
<groupId>org.antlr</groupId>
<artifactId>ST4</artifactId>
<version>${ST4.version}</version>
<scope>compile</scope>
</dependency>
<!-- liteflow -->
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>${liteflow.version}</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -3,6 +3,7 @@ package com.cczsa.xinghe.codegen;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CodegenApplication {

View File

@@ -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<byte[]> downloadCode() {
public ResponseEntity<byte[]> downloadCode(@RequestBody @Valid CodeGen req) {
try {
byte[] zipData = codeGenService.generateCodeZip();
byte[] zipData = codeGenService.generateCodeZip(req);
return ResponseEntity.ok()
// 设置下载文件名

View File

@@ -0,0 +1,6 @@
/**
* @author xia
* &#064;date 2026/1/17
* @version 0.0.1
*/
package com.cczsa.xinghe.codegen.entity.domain;

View File

@@ -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
* &#064;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;
}
}

View File

@@ -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
* &#064;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;
}
}

View File

@@ -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
* &#064;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()+"}";
}
}
}

View File

@@ -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
* &#064;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<FunOperationEntity> operationEntityList;
}

View File

@@ -0,0 +1,10 @@
package com.cczsa.xinghe.codegen.entity.domain.template;
/**
* @author xia
* &#064;date 2026/1/17
* @version 0.0.1
*/
public class TemplateObj {
}

View File

@@ -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;

View File

@@ -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<Long> userDataScope;
@Schema(description = "排除的字段 根据 fieldConfig 进行设置")
private List<String> excludeField;
private List<String> excludeField = new ArrayList<>();
@NotNull(message = "客户端类型不能为空")
@Schema(description = "客户端类型0 PC端1 小程序端2 H5端")

View File

@@ -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;

View File

@@ -1,10 +1,12 @@
package com.cczsa.xinghe.codegen.service;
import com.cczsa.xinghe.codegen.entity.domain.template.CodeGen;
/**
* @author xia
* &#064;date 2026/1/10
* @version 0.0.1
*/
public interface CodeGenService {
byte[] generateCodeZip();
byte[] generateCodeZip(CodeGen req);
}

View File

@@ -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<List<Map<Integer, String>>> getTemplateTypeList();
/**
* 根据模板类型获取模板内容的方法
* @return 返回对应模板类型的字符串内容
*/
String getTemplateTypeContent(TemplateTypeEnum templateType);
}

View File

@@ -0,0 +1,6 @@
/**
* @author xia
* &#064;date 2026/1/17
* @version 0.0.1
*/
package com.cczsa.xinghe.codegen.service.flow;

View File

@@ -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
* &#064;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<FunOperationTemp> 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<String, Object> 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<String, Object> 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();
}
}

View File

@@ -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;
}
}

View File

@@ -129,6 +129,14 @@ public class RoleServiceImpl implements RoleService {
*/
@Override
public XResult<Void> 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<RoleQueryFunRes> 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<RoleFunEntity> roleFunEntities = roleFunMapper.selectListByQuery(queryRoleFun);
for (RoleQueryFunRes roleQueryFunRes : roleQueryFunList) {

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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
# 使用不同模块的数据库配置
profiles:
active: dev

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<!-- 主流程:用户下单 -->
<chain name="controller">
THEN(controllerCodeCreate);
</chain>
</flow>

View File

@@ -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 <packageName>;
/**
* <description>
* @author <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 <packageName>;
/**
* <description>
* @author <author>