refactor: 抽取代码生成器插件模块(后续会改造为独立插件)

This commit is contained in:
2024-03-05 22:54:21 +08:00
parent 8026f660c7
commit 87829d3ce8
30 changed files with 81 additions and 89 deletions

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-admin</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-admin-generator</artifactId>
<description>代码生成器插件</description>
<dependencies>
<!-- 公共模块(存放公共工具类,公共配置等) -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-admin-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.config.properties;
import java.util.Map;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import cn.hutool.core.map.MapUtil;
/**
* 代码生成器配置属性
*
* @author Charles7c
* @since 2023/8/5 11:08
*/
@Data
@Component
@ConfigurationProperties(prefix = "generator")
public class GeneratorProperties {
/**
* 排除数据表(哪些数据表不展示在代码生成中)
*/
private String[] excludeTables;
/**
* 模板配置
*/
private Map<String, TemplateConfig> templateConfigs = MapUtil.newHashMap(true);
/**
* 模板配置
*/
@Data
public static class TemplateConfig {
/**
* 模板路径
*/
private String templatePath;
/**
* 包名称
*/
private String packageName;
/**
* 排除字段
*/
private String[] excludeFields;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.charles7c.continew.starter.data.mybatis.plus.base.IBaseEnum;
/**
* 表单类型枚举
*
* @author Charles7c
* @since 2023/8/6 10:49
*/
@Getter
@RequiredArgsConstructor
public enum FormTypeEnum implements IBaseEnum<Integer> {
/**
* 文本框
*/
TEXT(1, "文本框"),
/**
* 文本域
*/
TEXT_AREA(2, "文本域"),
/**
* 下拉框
*/
SELECT(3, "下拉框"),
/**
* 单选框
*/
RADIO(4, "单选框"),
/**
* 日期框
*/
DATE(5, "日期框"),
/**
* 日期时间框
*/
DATE_TIME(6, "日期时间框"),;
private final Integer value;
private final String description;
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.charles7c.continew.starter.data.mybatis.plus.base.IBaseEnum;
/**
* 查询类型枚举
*
* @author Charles7c
* @since 2023/8/6 10:49
*/
@Getter
@RequiredArgsConstructor
public enum QueryTypeEnum implements IBaseEnum<Integer> {
/**
* 等于 =例如WHERE age = 18
*/
EQ(1, "="),
/**
* 不等于 !=例如WHERE age != 18
*/
NE(2, "!="),
/**
* 大于 >例如WHERE age > 18
*/
GT(3, ">"),
/**
* 大于等于 >= 例如WHERE age >= 18
*/
GE(4, ">="),
/**
* 小于 <例如WHERE age < 18
*/
LT(5, "<"),
/**
* 小于等于 <=例如WHERE age <= 18
*/
LE(6, "<="),
/**
* 范围查询例如WHERE age BETWEEN 10 AND 18
*/
BETWEEN(7, "BETWEEN"),
/**
* LIKE '%值%'例如WHERE nickname LIKE '%s%'
*/
LIKE(8, "LIKE '%s%'"),
/**
* LIKE '%值'例如WHERE nickname LIKE '%s'
*/
LIKE_LEFT(9, "LIKE '%s'"),
/**
* LIKE '值%'例如WHERE nickname LIKE 's%'
*/
LIKE_RIGHT(10, "LIKE 's%'"),
/**
* 包含查询例如WHERE age IN (10, 20, 30)
*/
IN(11, "IN"),
/**
* 不包含查询例如WHERE age NOT IN (20, 30)
*/
NOT_IN(12, "NOT IN"),
/**
* 空查询例如WHERE email IS NULL
*/
IS_NULL(13, "IS NULL"),
/**
* 非空查询例如WHERE email IS NOT NULL
*/
IS_NOT_NULL(14, "IS NOT NULL"),;
private final Integer value;
private final String description;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import top.charles7c.continew.admin.generator.model.entity.FieldConfigDO;
import top.charles7c.continew.starter.data.mybatis.plus.base.BaseMapper;
import java.util.List;
/**
* 字段配置 Mapper
*
* @author Charles7c
* @since 2023/4/12 23:56
*/
public interface FieldConfigMapper extends BaseMapper<FieldConfigDO> {
/**
* 根据表名称查询
*
* @param tableName 表名称
* @return 字段配置信息
*/
@Select("SELECT * FROM gen_field_config WHERE table_name = #{tableName}")
List<FieldConfigDO> selectListByTableName(@Param("tableName") String tableName);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.mapper;
import top.charles7c.continew.admin.generator.model.entity.GenConfigDO;
import top.charles7c.continew.starter.data.mybatis.plus.base.BaseMapper;
/**
* 生成配置 Mapper
*
* @author Charles7c
* @since 2023/4/12 23:56
*/
public interface GenConfigMapper extends BaseMapper<GenConfigDO> {}

View File

@@ -0,0 +1,170 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.model.entity;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.meta.Column;
import cn.hutool.setting.dialect.Props;
import cn.hutool.setting.dialect.PropsUtil;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import top.charles7c.continew.admin.generator.enums.FormTypeEnum;
import top.charles7c.continew.admin.generator.enums.QueryTypeEnum;
import top.charles7c.continew.starter.core.constant.StringConstants;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 字段配置实体
*
* @author Charles7c
* @since 2023/4/12 20:21
*/
@Data
@NoArgsConstructor
@TableName("gen_field_config")
@Schema(description = "字段配置信息")
public class FieldConfigDO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 表名称
*/
@Schema(description = "表名称", example = "sys_user")
@NotBlank(message = "表名称不能为空")
private String tableName;
/**
* 列名称
*/
@Schema(description = "列名称", example = "nickname")
@NotBlank(message = "列名称不能为空")
private String columnName;
/**
* 列类型
*/
@Schema(description = "列类型", example = "varchar")
@NotBlank(message = "列类型不能为空")
private String columnType;
/**
* 列大小
*/
@Schema(description = "列大小", example = "255")
private String columnSize;
/**
* 字段名称
*/
@Schema(description = "字段名称", example = "nickname")
@NotBlank(message = "字段名称不能为空")
private String fieldName;
/**
* 字段类型
*/
@Schema(description = "字段类型", example = "String")
@NotBlank(message = "字段类型不能为空")
private String fieldType;
/**
* 注释
*/
@Schema(description = "注释", example = "昵称")
private String comment;
/**
* 是否必填
*/
@Schema(description = "是否必填", example = "true")
private Boolean isRequired;
/**
* 是否在列表中显示
*/
@Schema(description = "是否在列表中显示", example = "true")
private Boolean showInList;
/**
* 是否在表单中显示
*/
@Schema(description = "是否在表单中显示", example = "true")
private Boolean showInForm;
/**
* 是否在查询中显示
*/
@Schema(description = "是否在查询中显示", example = "true")
private Boolean showInQuery;
/**
* 表单类型
*/
@Schema(description = "表单类型", type = "Integer", allowableValues = {"1", "2", "3", "4", "5", "6"}, example = "1")
private FormTypeEnum formType;
/**
* 查询方式
*/
@Schema(description = "查询方式", type = "Integer", allowableValues = {"1", "2", "3", "4", "5", "6", "7", "8", "9",
"10", "11", "12", "13", "14"}, example = "1")
private QueryTypeEnum queryType;
/**
* 创建时间
*/
@Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
public FieldConfigDO(@NonNull Column column) {
this.setTableName(column.getTableName());
this.setColumnName(column.getName());
this.setColumnType(StrUtil.splitToArray(column.getTypeName(), StringConstants.SPACE)[0].toLowerCase());
this.setColumnSize(Convert.toStr(column.getSize()));
this.setComment(column.getComment());
this.setIsRequired(!column.isPk() && !column.isNullable());
this.setShowInList(true);
this.setShowInForm(this.getIsRequired());
this.setShowInQuery(this.getIsRequired());
this.setFormType(FormTypeEnum.TEXT);
this.setQueryType("String".equals(this.getFieldType()) ? QueryTypeEnum.LIKE : QueryTypeEnum.EQ);
}
public void setColumnName(String columnName) {
this.columnName = columnName;
this.fieldName = StrUtil.toCamelCase(this.columnName);
}
public void setColumnType(String columnType) {
this.columnType = columnType;
Props generatorProp = PropsUtil.get("generator");
this.fieldType = generatorProp.getStr(columnType);
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.model.entity;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import cn.hutool.core.util.StrUtil;
import top.charles7c.continew.admin.common.constant.RegexConstants;
/**
* 生成配置实体
*
* @author Charles7c
* @since 2023/4/12 20:21
*/
@Data
@NoArgsConstructor
@TableName("gen_config")
@Schema(description = "生成配置信息")
public class GenConfigDO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 表名称
*/
@Schema(description = "表名称", example = "sys_user")
@TableId(type = IdType.INPUT)
@NotBlank(message = "表名称不能为空")
private String tableName;
/**
* 模块名称
*/
@Schema(description = "模块名称", example = "continew-admin-system")
@NotBlank(message = "模块名称不能为空")
@Length(max = 60, message = "模块名称不能超过 {max} 个字符")
private String moduleName;
/**
* 包名称
*/
@Schema(description = "包名称", example = "top.charles7c.continew.admin.system")
@NotBlank(message = "包名称不能为空")
@Pattern(regexp = RegexConstants.PACKAGE_NAME, message = "包名称格式错误")
@Length(max = 60, message = "包名称不能超过 {max} 个字符")
private String packageName;
/**
* 前端路径
*/
@Schema(description = "前端路径", example = "D:/continew-admin-ui/src/views/system/user")
@Length(max = 255, message = "前端路径不能超过 {max} 个字符")
private String frontendPath;
/**
* 业务名称
*/
@Schema(description = "业务名称", example = "用户")
@NotBlank(message = "业务名称不能为空")
@Length(max = 50, message = "业务名称不能超过 {max} 个字符")
private String businessName;
/**
* 作者
*/
@Schema(description = "作者", example = "Charles7c")
@NotBlank(message = "作者名称不能为空")
@Length(max = 100, message = "作者名称不能超过 {max} 个字符")
private String author;
/**
* 表前缀
*/
@Schema(description = "表前缀", example = "sys_")
private String tablePrefix;
/**
* 是否覆盖
*/
@Schema(description = "是否覆盖", example = "false")
@NotNull(message = "是否覆盖不能为空")
private Boolean isOverride;
/**
* 创建时间
*/
@Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 修改时间
*/
@Schema(description = "修改时间", example = "2023-08-08 08:08:08", type = "string")
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updateTime;
/**
* 类名前缀
*/
@Setter(AccessLevel.NONE)
@JsonIgnore
@TableField(exist = false)
private String classNamePrefix;
public GenConfigDO(String tableName) {
this.tableName = tableName;
}
public String getClassNamePrefix() {
String rawClassName = StrUtil.isNotBlank(this.tablePrefix)
? StrUtil.removePrefix(this.tableName, this.tablePrefix)
: this.tableName;
return StrUtil.upperFirst(StrUtil.toCamelCase(rawClassName));
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.model.query;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 表信息查询条件
*
* @author Charles7c
* @since 2023/4/12 20:21
*/
@Data
@Schema(description = "表信息查询条件")
public class TableQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 表名称
*/
@Schema(description = "表名称", example = "sys_user")
private String tableName;
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.model.req;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.charles7c.continew.admin.generator.model.entity.FieldConfigDO;
import top.charles7c.continew.admin.generator.model.entity.GenConfigDO;
/**
* 代码生成配置信息
*
* @author Charles7c
* @since 2023/8/8 20:40
*/
@Data
@Schema(description = "代码生成配置信息")
public class GenConfigReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 字段配置信息
*/
@Valid
@Schema(description = "字段配置信息")
@NotEmpty(message = "字段配置不能为空")
private List<FieldConfigDO> fieldConfigs = new ArrayList<>();
/**
* 生成配置信息
*/
@Valid
@Schema(description = "生成配置信息")
@NotNull(message = "生成配置不能为空")
private GenConfigDO genConfig;
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.model.resp;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* 生成预览信息
*
* @author Charles7c
* @since 2023/12/19 21:34
*/
@Data
@Schema(description = "生成预览信息")
public class GeneratePreviewResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 文件名
*/
@Schema(description = "文件名", example = "UserController.java")
private String fileName;
/**
* 内容
*/
@Schema(description = "内容", example = "public class UserController {...}")
private String content;
/**
* 是否为后端代码
*/
@Schema(hidden = true)
@JsonIgnore
private boolean backend;
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.model.resp;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 表信息
*
* @author Charles7c
* @since 2023/4/12 20:21
*/
@Data
@Schema(description = "表信息")
public class TableResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 表名称
*/
@Schema(description = "表名称", example = "sys_user")
private String tableName;
/**
* 描述
*/
@Schema(description = "描述", example = "用户表")
private String comment;
/**
* 存储引擎
*/
@Schema(description = "存储引擎", example = "InnoDB")
private String engine;
/**
* 字符集
*/
@Schema(description = "字符集", example = "utf8mb4_general_ci")
private String charset;
/**
* 创建时间
*/
@Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string")
private LocalDateTime createTime;
/**
* 是否已配置
*/
@Schema(description = "是否已配置", example = "true")
private Boolean isConfiged;
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.service;
import java.sql.SQLException;
import java.util.List;
import top.charles7c.continew.admin.generator.model.entity.FieldConfigDO;
import top.charles7c.continew.admin.generator.model.entity.GenConfigDO;
import top.charles7c.continew.admin.generator.model.query.TableQuery;
import top.charles7c.continew.admin.generator.model.req.GenConfigReq;
import top.charles7c.continew.admin.generator.model.resp.GeneratePreviewResp;
import top.charles7c.continew.admin.generator.model.resp.TableResp;
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
import top.charles7c.continew.starter.extension.crud.model.resp.PageResp;
/**
* 代码生成业务接口
*
* @author Charles7c
* @since 2023/4/12 23:57
*/
public interface GeneratorService {
/**
* 分页查询表信息列表
*
* @param query 查询条件
* @param pageQuery 分页查询条件
* @return 表信息分页列表
* @throws SQLException /
*/
PageResp<TableResp> pageTable(TableQuery query, PageQuery pageQuery) throws SQLException;
/**
* 查询生成配置信息
*
* @param tableName 表名称
* @return 生成配置信息
* @throws SQLException /
*/
GenConfigDO getGenConfig(String tableName) throws SQLException;
/**
* 查询字段配置列表
*
* @param tableName 表名称
* @param requireSync 是否需要同步
* @return 字段配置列表
*/
List<FieldConfigDO> listFieldConfig(String tableName, Boolean requireSync);
/**
* 保存代码生成配置信息
*
* @param req 代码生成配置信息
* @param tableName 表名称
*/
void saveConfig(GenConfigReq req, String tableName);
/**
* 生成预览
*
* @param tableName 表名称
* @return 预览信息
*/
List<GeneratePreviewResp> preview(String tableName);
/**
* 生成代码
*
* @param tableName 表名称
*/
void generate(String tableName);
}

View File

@@ -0,0 +1,374 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.generator.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.meta.Column;
import cn.hutool.system.SystemUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.charles7c.continew.admin.generator.config.properties.GeneratorProperties;
import top.charles7c.continew.admin.generator.config.properties.GeneratorProperties.TemplateConfig;
import top.charles7c.continew.admin.generator.enums.QueryTypeEnum;
import top.charles7c.continew.admin.generator.mapper.FieldConfigMapper;
import top.charles7c.continew.admin.generator.mapper.GenConfigMapper;
import top.charles7c.continew.admin.generator.model.entity.FieldConfigDO;
import top.charles7c.continew.admin.generator.model.entity.GenConfigDO;
import top.charles7c.continew.admin.generator.model.query.TableQuery;
import top.charles7c.continew.admin.generator.model.req.GenConfigReq;
import top.charles7c.continew.admin.generator.model.resp.GeneratePreviewResp;
import top.charles7c.continew.admin.generator.model.resp.TableResp;
import top.charles7c.continew.admin.generator.service.GeneratorService;
import top.charles7c.continew.starter.core.constant.StringConstants;
import top.charles7c.continew.starter.core.exception.BusinessException;
import top.charles7c.continew.starter.core.util.TemplateUtils;
import top.charles7c.continew.starter.data.core.util.MetaUtils;
import top.charles7c.continew.starter.data.core.util.Table;
import top.charles7c.continew.starter.core.util.validate.CheckUtils;
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
import top.charles7c.continew.starter.extension.crud.model.resp.PageResp;
import javax.sql.DataSource;
import java.io.File;
import java.sql.SQLException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 代码生成业务实现
*
* @author Charles7c
* @since 2023/4/12 23:58
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class GeneratorServiceImpl implements GeneratorService {
private final DataSource dataSource;
private final GeneratorProperties generatorProperties;
private final FieldConfigMapper fieldConfigMapper;
private final GenConfigMapper genConfigMapper;
@Override
public PageResp<TableResp> pageTable(TableQuery query, PageQuery pageQuery) throws SQLException {
List<Table> tableList = MetaUtils.getTables(dataSource);
String tableName = query.getTableName();
if (StrUtil.isNotBlank(tableName)) {
tableList.removeIf(table -> !StrUtil.containsAny(table.getTableName(), tableName));
}
tableList.removeIf(table -> StrUtil.equalsAnyIgnoreCase(table.getTableName(), generatorProperties
.getExcludeTables()));
CollUtil.sort(tableList, Comparator.comparing(Table::getCreateTime)
.thenComparing(table -> Optional.ofNullable(table.getUpdateTime()).orElse(table.getCreateTime()))
.reversed());
List<TableResp> tableRespList = BeanUtil.copyToList(tableList, TableResp.class);
PageResp<TableResp> pageResp = PageResp.build(pageQuery.getPage(), pageQuery.getSize(), tableRespList);
for (TableResp tableResp : pageResp.getList()) {
long count = genConfigMapper.selectCount(Wrappers.lambdaQuery(GenConfigDO.class)
.eq(GenConfigDO::getTableName, tableResp.getTableName()));
tableResp.setIsConfiged(count > 0);
}
return pageResp;
}
@Override
public GenConfigDO getGenConfig(String tableName) throws SQLException {
GenConfigDO genConfig = genConfigMapper.selectById(tableName);
if (null == genConfig) {
genConfig = new GenConfigDO(tableName);
// 默认包名(当前包名)
String packageName = ClassUtil.getPackage(GeneratorService.class);
genConfig.setPackageName(StrUtil.subBefore(packageName, StringConstants.DOT, true));
// 默认业务名(表注释)
List<Table> tableList = MetaUtils.getTables(dataSource, tableName);
if (CollUtil.isNotEmpty(tableList)) {
Table table = tableList.get(0);
genConfig.setBusinessName(StrUtil.replace(table.getComment(), "", StringConstants.EMPTY));
}
// 默认作者名称(上次保存使用的作者名称)
GenConfigDO lastGenConfig = genConfigMapper.selectOne(Wrappers.lambdaQuery(GenConfigDO.class)
.orderByDesc(GenConfigDO::getCreateTime)
.last("LIMIT 1"));
if (null != lastGenConfig) {
genConfig.setAuthor(lastGenConfig.getAuthor());
}
// 默认表前缀sys_user -> sys_
int underLineIndex = StrUtil.indexOf(tableName, StringConstants.C_UNDERLINE);
if (-1 != underLineIndex) {
genConfig.setTablePrefix(StrUtil.subPre(tableName, underLineIndex + 1));
}
}
return genConfig;
}
@Override
public List<FieldConfigDO> listFieldConfig(String tableName, Boolean requireSync) {
List<FieldConfigDO> fieldConfigList = fieldConfigMapper.selectListByTableName(tableName);
if (CollUtil.isEmpty(fieldConfigList)) {
Collection<Column> columnList = MetaUtils.getColumns(dataSource, tableName);
return columnList.stream().map(FieldConfigDO::new).toList();
}
// 同步最新数据表列信息
if (Boolean.TRUE.equals(requireSync)) {
Collection<Column> columnList = MetaUtils.getColumns(dataSource, tableName);
// 移除已不存在的字段配置
List<String> columnNameList = columnList.stream().map(Column::getName).toList();
fieldConfigList.removeIf(column -> !columnNameList.contains(column.getColumnName()));
// 新增或更新字段配置
Map<String, FieldConfigDO> fieldConfigMap = fieldConfigList.stream()
.collect(Collectors.toMap(FieldConfigDO::getColumnName, Function.identity(), (key1, key2) -> key2));
for (Column column : columnList) {
FieldConfigDO fieldConfig = fieldConfigMap.get(column.getName());
if (null != fieldConfig) {
// 更新已有字段配置
String columnType = StrUtil.splitToArray(column.getTypeName(), StringConstants.SPACE)[0]
.toLowerCase();
fieldConfig.setColumnType(columnType);
fieldConfig.setColumnSize(Convert.toStr(column.getSize()));
fieldConfig.setComment(column.getComment());
} else {
// 新增字段配置
fieldConfig = new FieldConfigDO(column);
fieldConfigList.add(fieldConfig);
}
}
}
return fieldConfigList;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveConfig(GenConfigReq req, String tableName) {
// 保存字段配置
fieldConfigMapper.delete(Wrappers.lambdaQuery(FieldConfigDO.class).eq(FieldConfigDO::getTableName, tableName));
List<FieldConfigDO> fieldConfigList = req.getFieldConfigs();
for (FieldConfigDO fieldConfig : fieldConfigList) {
if (Boolean.TRUE.equals(fieldConfig.getShowInForm())) {
CheckUtils.throwIfNull(fieldConfig.getFormType(), "字段 [{}] 的表单类型不能为空", fieldConfig.getFieldName());
} else {
// 在表单中不显示,不需要设置必填
fieldConfig.setIsRequired(false);
}
if (Boolean.TRUE.equals(fieldConfig.getShowInQuery())) {
CheckUtils.throwIfNull(fieldConfig.getFormType(), "字段 [{}] 的表单类型不能为空", fieldConfig.getFieldName());
CheckUtils.throwIfNull(fieldConfig.getQueryType(), "字段 [{}] 的查询方式不能为空", fieldConfig.getFieldName());
} else {
// 在查询中不显示,不需要设置查询方式
fieldConfig.setQueryType(null);
}
// 既不在表单也不在查询中显示,不需要设置表单类型
if (Boolean.FALSE.equals(fieldConfig.getShowInForm()) && Boolean.FALSE.equals(fieldConfig
.getShowInQuery())) {
fieldConfig.setFormType(null);
}
fieldConfig.setTableName(tableName);
}
fieldConfigMapper.insertBatch(fieldConfigList);
// 保存或更新生成配置信息
GenConfigDO newGenConfig = req.getGenConfig();
String frontendPath = newGenConfig.getFrontendPath();
if (StrUtil.isNotBlank(frontendPath)) {
CheckUtils.throwIf(!FileUtil.exist(frontendPath), "前端路径不存在");
CheckUtils.throwIf(!StrUtil.containsAll(frontendPath, "src", "views"), "前端路径配置错误");
}
GenConfigDO oldGenConfig = genConfigMapper.selectById(tableName);
if (null != oldGenConfig) {
BeanUtil.copyProperties(newGenConfig, oldGenConfig);
genConfigMapper.updateById(oldGenConfig);
} else {
genConfigMapper.insert(newGenConfig);
}
}
@Override
public List<GeneratePreviewResp> preview(String tableName) {
List<GeneratePreviewResp> generatePreviewList = new ArrayList<>();
// 初始化配置
GenConfigDO genConfig = genConfigMapper.selectById(tableName);
CheckUtils.throwIfNull(genConfig, "请先进行数据表 [{}] 生成配置", tableName);
List<FieldConfigDO> fieldConfigList = fieldConfigMapper.selectListByTableName(tableName);
CheckUtils.throwIfEmpty(fieldConfigList, "请先进行数据表 [{}] 字段配置", tableName);
Map<String, Object> genConfigMap = BeanUtil.beanToMap(genConfig);
genConfigMap.put("date", DateUtil.date().toString("yyyy/MM/dd HH:mm"));
String packageName = genConfig.getPackageName();
String apiModuleName = StrUtil.subSuf(packageName, StrUtil
.lastIndexOfIgnoreCase(packageName, StringConstants.DOT) + 1);
genConfigMap.put("apiModuleName", apiModuleName);
genConfigMap.put("apiName", StrUtil.lowerFirst(genConfig.getClassNamePrefix()));
// 渲染后端代码
String classNamePrefix = genConfig.getClassNamePrefix();
Map<String, TemplateConfig> templateConfigMap = generatorProperties.getTemplateConfigs();
for (Map.Entry<String, TemplateConfig> templateConfigEntry : templateConfigMap.entrySet()) {
this.pretreatment(genConfigMap, fieldConfigList, templateConfigEntry);
String className = classNamePrefix + StrUtil.nullToEmpty(templateConfigEntry.getKey());
genConfigMap.put("className", className);
TemplateConfig templateConfig = templateConfigEntry.getValue();
String content = TemplateUtils.render(templateConfig.getTemplatePath(), genConfigMap);
GeneratePreviewResp codePreview = new GeneratePreviewResp();
codePreview.setFileName(className + FileNameUtil.EXT_JAVA);
codePreview.setContent(content);
codePreview.setBackend(true);
generatePreviewList.add(codePreview);
}
// 渲染前端代码
// api 代码
genConfigMap.put("fieldConfigs", fieldConfigList);
String apiContent = TemplateUtils.render("generator/api.ftl", genConfigMap);
GeneratePreviewResp apiCodePreview = new GeneratePreviewResp();
apiCodePreview.setFileName(classNamePrefix.toLowerCase() + ".ts");
apiCodePreview.setContent(apiContent);
generatePreviewList.add(apiCodePreview);
// view 代码
String viewContent = TemplateUtils.render("generator/index.ftl", genConfigMap);
GeneratePreviewResp viewCodePreview = new GeneratePreviewResp();
viewCodePreview.setFileName("index.vue");
viewCodePreview.setContent(viewContent);
generatePreviewList.add(viewCodePreview);
return generatePreviewList;
}
@Override
public void generate(String tableName) {
// 初始化配置及数据
List<GeneratePreviewResp> generatePreviewList = this.preview(tableName);
GenConfigDO genConfig = genConfigMapper.selectById(tableName);
Boolean isOverride = genConfig.getIsOverride();
// 生成代码
String packageName = genConfig.getPackageName();
try {
// 生成后端代码
String classNamePrefix = genConfig.getClassNamePrefix();
// 1.确定后端代码基础路径
// 例如D:/continew-admin
String projectPath = SystemUtil.getUserInfo().getCurrentDir();
// 例如D:/continew-admin/continew-admin-tool
File backendModuleFile = new File(projectPath, genConfig.getModuleName());
// 例如D:/continew-admin/continew-admin-tool/src/main/java/top/charles7c/continew/admin/tool
List<String> backendModuleChildPathList = CollUtil.newArrayList("src", "main", "java");
backendModuleChildPathList.addAll(StrUtil.split(packageName, StringConstants.DOT));
File backendParentFile = FileUtil.file(backendModuleFile, backendModuleChildPathList
.toArray(new String[0]));
// 2.生成代码
List<GeneratePreviewResp> backendCodePreviewList = generatePreviewList.stream()
.filter(GeneratePreviewResp::isBackend)
.toList();
Map<String, TemplateConfig> templateConfigMap = generatorProperties.getTemplateConfigs();
for (GeneratePreviewResp codePreview : backendCodePreviewList) {
// 例如D:/continew-admin/continew-admin-tool/src/main/java/top/charles7c/continew/admin/tool/service/impl/XxxServiceImpl.java
TemplateConfig templateConfig = templateConfigMap.get(codePreview.getFileName()
.replace(classNamePrefix, StringConstants.EMPTY)
.replace(FileNameUtil.EXT_JAVA, StringConstants.EMPTY));
File classParentFile = FileUtil.file(backendParentFile, StrUtil.splitToArray(templateConfig
.getPackageName(), StringConstants.DOT));
File classFile = new File(classParentFile, codePreview.getFileName());
// 如果已经存在,且不允许覆盖,则跳过
if (classFile.exists() && Boolean.FALSE.equals(isOverride)) {
continue;
}
FileUtil.writeUtf8String(codePreview.getContent(), classFile);
}
// 生成前端代码
String frontendPath = genConfig.getFrontendPath();
if (StrUtil.isBlank(frontendPath)) {
return;
}
List<GeneratePreviewResp> frontendCodePreviewList = generatePreviewList.stream()
.filter(p -> !p.isBackend())
.toList();
// 1.生成 api 代码
String apiModuleName = StrUtil.subSuf(packageName, StrUtil
.lastIndexOfIgnoreCase(packageName, StringConstants.DOT) + 1);
GeneratePreviewResp apiCodePreview = frontendCodePreviewList.get(0);
// 例如D:/continew-admin-ui
List<String> frontendSubPathList = StrUtil.split(frontendPath, "src");
String frontendModulePath = frontendSubPathList.get(0);
// 例如D:/continew-admin-ui/src/api/tool/xxx.ts
File apiParentFile = FileUtil.file(frontendModulePath, "src", "api", apiModuleName);
File apiFile = new File(apiParentFile, apiCodePreview.getFileName());
if (apiFile.exists() && Boolean.FALSE.equals(isOverride)) {
return;
}
FileUtil.writeUtf8String(apiCodePreview.getContent(), apiFile);
// 2.生成 view 代码
GeneratePreviewResp viewCodePreview = frontendCodePreviewList.get(1);
// 例如D:/continew-admin-ui/src/views/tool/xxx/index.vue
File indexFile = FileUtil.file(frontendPath, apiModuleName, StrUtil
.lowerFirst(classNamePrefix), "index.vue");
if (indexFile.exists() && Boolean.FALSE.equals(isOverride)) {
return;
}
FileUtil.writeUtf8String(viewCodePreview.getContent(), indexFile);
} catch (Exception e) {
log.error("Generate code occurred an error: {}. tableName: {}.", e.getMessage(), tableName, e);
throw new BusinessException("代码生成失败,请手动清理生成文件");
}
}
/**
* 预处理生成配置
*
* @param genConfigMap 生成配置
* @param originFieldConfigList 原始字段配置列表
* @param templateConfigEntry 模板配置
*/
private void pretreatment(Map<String, Object> genConfigMap,
List<FieldConfigDO> originFieldConfigList,
Map.Entry<String, TemplateConfig> templateConfigEntry) {
TemplateConfig templateConfig = templateConfigEntry.getValue();
// 移除需要忽略的字段
List<FieldConfigDO> fieldConfigList = originFieldConfigList.stream()
.filter(fieldConfig -> !StrUtil.equalsAny(fieldConfig.getFieldName(), templateConfig.getExcludeFields()))
.toList();
genConfigMap.put("fieldConfigs", fieldConfigList);
// 统计部分特殊字段特征
genConfigMap.put("hasLocalDateTime", false);
genConfigMap.put("hasBigDecimal", false);
genConfigMap.put("hasRequiredField", false);
genConfigMap.put("hasListQueryField", false);
for (FieldConfigDO fieldConfig : fieldConfigList) {
String fieldType = fieldConfig.getFieldType();
if ("LocalDateTime".equals(fieldType)) {
genConfigMap.put("hasLocalDateTime", true);
}
if ("BigDecimal".equals(fieldType)) {
genConfigMap.put("hasBigDecimal", true);
}
if (Boolean.TRUE.equals(fieldConfig.getIsRequired())) {
genConfigMap.put("hasRequiredField", true);
}
QueryTypeEnum queryType = fieldConfig.getQueryType();
if (null != queryType && StrUtil.equalsAny(queryType.name(), QueryTypeEnum.IN.name(), QueryTypeEnum.NOT_IN
.name(), QueryTypeEnum.BETWEEN.name())) {
genConfigMap.put("hasListQueryField", true);
}
}
String subPackageName = templateConfig.getPackageName();
genConfigMap.put("subPackageName", subPackageName);
}
}

View File

@@ -0,0 +1,26 @@
tinyint=Integer
smallint=Integer
mediumint=Integer
int=Integer
integer=Integer
bigint=Long
float=Float
double=Double
decimal=BigDecimal
bit=Boolean
char=String
varchar=String
tinytext=String
text=String
mediumtext=String
longtext=String
date=LocalDate
datetime=LocalDateTime
timestamp=LocalDateTime

View File

@@ -0,0 +1,26 @@
package ${packageName}.${subPackageName};
import top.charles7c.continew.starter.extension.crud.enums.Api;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import top.charles7c.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.charles7c.continew.starter.extension.crud.controller.BaseController;
import ${packageName}.model.query.${classNamePrefix}Query;
import ${packageName}.model.req.${classNamePrefix}Req;
import ${packageName}.model.resp.${classNamePrefix}DetailResp;
import ${packageName}.model.resp.${classNamePrefix}Resp;
import ${packageName}.service.${classNamePrefix}Service;
/**
* ${businessName}管理 API
*
* @author ${author}
* @since ${date}
*/
@Tag(name = "${businessName}管理 API")
@RestController
@CrudRequestMapping(value = "/${apiModuleName}/${apiName}", api = {Api.PAGE, Api.GET, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT})
public class ${className} extends BaseController<${classNamePrefix}Service, ${classNamePrefix}Resp, ${classNamePrefix}DetailResp, ${classNamePrefix}Query, ${classNamePrefix}Req> {}

View File

@@ -0,0 +1,44 @@
package ${packageName}.${subPackageName};
import java.io.Serial;
<#if hasLocalDateTime>
import java.time.LocalDateTime;
</#if>
<#if hasBigDecimal>
import java.math.BigDecimal;
</#if>
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import top.charles7c.continew.starter.extension.crud.model.resp.BaseDetailResp;
/**
* ${businessName}详情信息
*
* @author ${author}
* @since ${date}
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "${businessName}详情信息")
public class ${className} extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
/**
* ${fieldConfig.comment}
*/
@Schema(description = "${fieldConfig.comment}")
@ExcelProperty(value = "${fieldConfig.comment}")
private ${fieldConfig.fieldType} ${fieldConfig.fieldName};
</#list>
</#if>
}

View File

@@ -0,0 +1,38 @@
package ${packageName}.${subPackageName};
import java.io.Serial;
<#if hasLocalDateTime>
import java.time.LocalDateTime;
</#if>
<#if hasBigDecimal>
import java.math.BigDecimal;
</#if>
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.charles7c.continew.starter.extension.crud.model.entity.BaseDO;
/**
* ${businessName}实体
*
* @author ${author}
* @since ${date}
*/
@Data
@TableName("${tableName}")
public class ${className} extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
/**
* ${fieldConfig.comment}
*/
private ${fieldConfig.fieldType} ${fieldConfig.fieldName};
</#list>
</#if>
}

View File

@@ -0,0 +1,12 @@
package ${packageName}.${subPackageName};
import top.charles7c.continew.starter.data.mybatis.plus.base.BaseMapper;
import ${packageName}.model.entity.${classNamePrefix}DO;
/**
* ${businessName} Mapper
*
* @author ${author}
* @since ${date}
*/
public interface ${className} extends BaseMapper<${classNamePrefix}DO> {}

View File

@@ -0,0 +1,51 @@
package ${packageName}.${subPackageName};
import java.io.Serial;
import java.io.Serializable;
<#if hasLocalDateTime>
import java.time.LocalDateTime;
</#if>
<#if hasBigDecimal>
import java.math.BigDecimal;
</#if>
<#if hasListQueryField>
import java.util.List;
</#if>
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.charles7c.continew.starter.data.core.annotation.Query;
import top.charles7c.continew.starter.data.core.enums.QueryType;
/**
* ${businessName}查询条件
*
* @author ${author}
* @since ${date}
*/
@Data
@Schema(description = "${businessName}查询条件")
public class ${className} implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
/**
* ${fieldConfig.comment}
*/
@Schema(description = "${fieldConfig.comment}")
@Query(type = QueryType.${fieldConfig.queryType})
<#if fieldConfig.queryType = 'IN' || fieldConfig.queryType = 'NOT_IN' || fieldConfig.queryType = 'BETWEEN'>
private List<${fieldConfig.fieldType}> ${fieldConfig.fieldName};
<#else>
private ${fieldConfig.fieldType} ${fieldConfig.fieldName};
</#if>
</#if>
</#list>
</#if>
}

View File

@@ -0,0 +1,57 @@
package ${packageName}.${subPackageName};
import java.io.Serial;
<#if hasLocalDateTime>
import java.time.LocalDateTime;
</#if>
<#if hasBigDecimal>
import java.math.BigDecimal;
</#if>
<#if hasRequiredField>
import jakarta.validation.constraints.*;
</#if>
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import top.charles7c.continew.starter.extension.crud.model.req.BaseReq;
/**
* 创建或修改${businessName}信息
*
* @author ${author}
* @since ${date}
*/
@Data
@Schema(description = "创建或修改${businessName}信息")
public class ${className} extends BaseReq {
@Serial
private static final long serialVersionUID = 1L;
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInForm>
/**
* ${fieldConfig.comment}
*/
@Schema(description = "${fieldConfig.comment}")
<#if fieldConfig.isRequired>
<#if fieldConfig.fieldType = 'String'>
@NotBlank(message = "${fieldConfig.comment}不能为空")
<#else>
@NotNull(message = "${fieldConfig.comment}不能为空")
</#if>
</#if>
<#if fieldConfig.fieldType = 'String' && fieldConfig.columnSize??>
@Length(max = ${fieldConfig.columnSize}, message = "${fieldConfig.comment}长度不能超过 {max} 个字符")
</#if>
private ${fieldConfig.fieldType} ${fieldConfig.fieldName};
</#if>
</#list>
</#if>
}

View File

@@ -0,0 +1,41 @@
package ${packageName}.${subPackageName};
import java.io.Serial;
<#if hasLocalDateTime>
import java.time.LocalDateTime;
</#if>
<#if hasBigDecimal>
import java.math.BigDecimal;
</#if>
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.charles7c.continew.starter.extension.crud.model.resp.BaseResp;
/**
* ${businessName}信息
*
* @author ${author}
* @since ${date}
*/
@Data
@Schema(description = "${businessName}信息")
public class ${className} extends BaseResp {
@Serial
private static final long serialVersionUID = 1L;
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInList>
/**
* ${fieldConfig.comment}
*/
@Schema(description = "${fieldConfig.comment}")
private ${fieldConfig.fieldType} ${fieldConfig.fieldName};
</#if>
</#list>
</#if>
}

View File

@@ -0,0 +1,15 @@
package ${packageName}.${subPackageName};
import top.charles7c.continew.starter.extension.crud.service.BaseService;
import ${packageName}.model.query.${classNamePrefix}Query;
import ${packageName}.model.req.${classNamePrefix}Req;
import ${packageName}.model.resp.${classNamePrefix}DetailResp;
import ${packageName}.model.resp.${classNamePrefix}Resp;
/**
* ${businessName}业务接口
*
* @author ${author}
* @since ${date}
*/
public interface ${className} extends BaseService<${classNamePrefix}Resp, ${classNamePrefix}DetailResp, ${classNamePrefix}Query, ${classNamePrefix}Req> {}

View File

@@ -0,0 +1,24 @@
package ${packageName}.${subPackageName};
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.charles7c.continew.starter.extension.crud.service.impl.BaseServiceImpl;
import ${packageName}.mapper.${classNamePrefix}Mapper;
import ${packageName}.model.entity.${classNamePrefix}DO;
import ${packageName}.model.query.${classNamePrefix}Query;
import ${packageName}.model.req.${classNamePrefix}Req;
import ${packageName}.model.resp.${classNamePrefix}DetailResp;
import ${packageName}.model.resp.${classNamePrefix}Resp;
import ${packageName}.service.${classNamePrefix}Service;
/**
* ${businessName}业务实现
*
* @author ${author}
* @since ${date}
*/
@Service
@RequiredArgsConstructor
public class ${className} extends BaseServiceImpl<${classNamePrefix}Mapper, ${classNamePrefix}DO, ${classNamePrefix}Resp, ${classNamePrefix}DetailResp, ${classNamePrefix}Query, ${classNamePrefix}Req> implements ${classNamePrefix}Service {}

View File

@@ -0,0 +1,57 @@
import axios from 'axios';
import qs from 'query-string';
const BASE_URL = '/${apiModuleName}/${apiName}';
export interface DataRecord {
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
${fieldConfig.fieldName}?: string;
</#list>
createUserString?: string;
updateUserString?: string;
</#if>
}
export interface ListParam {
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
${fieldConfig.fieldName}?: string;
</#if>
</#list>
</#if>
page?: number;
size?: number;
sort?: Array<string>;
}
export interface ListRes {
list: DataRecord[];
total: number;
}
export function list(params: ListParam) {
return axios.get<ListRes>(`${'$'}{BASE_URL}`, {
params,
paramsSerializer: (obj) => {
return qs.stringify(obj);
},
});
}
export function get(id: string) {
return axios.get<DataRecord>(`${'$'}{BASE_URL}/${'$'}{id}`);
}
export function add(req: DataRecord) {
return axios.post(BASE_URL, req);
}
export function update(req: DataRecord, id: string) {
return axios.put(`${'$'}{BASE_URL}/${'$'}{id}`, req);
}
export function del(ids: string | Array<string>) {
return axios.delete(`${'$'}{BASE_URL}/${'$'}{ids}`);
}

View File

@@ -0,0 +1,510 @@
<script lang="ts" setup>
import {
DataRecord,
ListParam,
list,
get,
add,
update,
del,
} from '@/api/${apiModuleName}/${apiName}';
import checkPermission from '@/utils/permission';
const { proxy } = getCurrentInstance() as any;
// const { dis_enable_status_enum } = proxy.useDict('dis_enable_status_enum');
const dataList = ref<DataRecord[]>([]);
const dataDetail = ref<DataRecord>({
// TODO 待补充详情字段默认值
});
const total = ref(0);
const ids = ref<Array<string>>([]);
const title = ref('');
const single = ref(true);
const multiple = ref(true);
const showQuery = ref(true);
const loading = ref(false);
const detailLoading = ref(false);
const exportLoading = ref(false);
const visible = ref(false);
const detailVisible = ref(false);
const data = reactive({
// 查询参数
queryParams: {
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
${fieldConfig.fieldName}: undefined,
</#if>
</#list>
page: 1,
size: 10,
sort: ['createTime,desc'],
},
// 表单数据
form: {} as DataRecord,
// 表单验证规则
rules: {
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInForm && fieldConfig.isRequired>
${fieldConfig.fieldName}: [{ required: true, message: '${fieldConfig.comment}不能为空' }],
</#if>
</#list>
},
});
const { queryParams, form, rules } = toRefs(data);
/**
* 查询列表
*
* @param params 查询参数
*/
const getList = (params: ListParam = { ...queryParams.value }) => {
loading.value = true;
list(params)
.then((res) => {
dataList.value = res.data.list;
total.value = res.data.total;
})
.finally(() => {
loading.value = false;
});
};
getList();
/**
* 打开新增对话框
*/
const toAdd = () => {
reset();
title.value = '新增${businessName}';
visible.value = true;
};
/**
* 打开修改对话框
*
* @param id ID
*/
const toUpdate = (id: string) => {
reset();
get(id).then((res) => {
form.value = res.data;
title.value = '修改${businessName}';
visible.value = true;
});
};
/**
* 重置表单
*/
const reset = () => {
form.value = {
// TODO 待补充需要重置的字段默认值,详情请参考其他模块 index.vue
};
proxy.$refs.formRef?.resetFields();
};
/**
* 取消
*/
const handleCancel = () => {
visible.value = false;
proxy.$refs.formRef.resetFields();
};
/**
* 确定
*/
const handleOk = () => {
proxy.$refs.formRef.validate((valid: any) => {
if (!valid) {
if (form.value.id !== undefined) {
update(form.value, form.value.id).then((res) => {
handleCancel();
getList();
proxy.$message.success(res.msg);
});
} else {
add(form.value).then((res) => {
handleCancel();
getList();
proxy.$message.success(res.msg);
});
}
}
});
};
/**
* 查看详情
*
* @param id ID
*/
const toDetail = async (id: string) => {
if (detailLoading.value) return;
detailLoading.value = true;
detailVisible.value = true;
get(id)
.then((res) => {
dataDetail.value = res.data;
})
.finally(() => {
detailLoading.value = false;
});
};
/**
* 关闭详情
*/
const handleDetailCancel = () => {
detailVisible.value = false;
};
/**
* 批量删除
*/
const handleBatchDelete = () => {
if (ids.value.length === 0) {
proxy.$message.info('请选择要删除的数据');
} else {
proxy.$modal.warning({
title: '警告',
titleAlign: 'start',
content: `是否确定删除所选的${r'${ids.value.length}'}条数据?`,
hideCancel: false,
onOk: () => {
handleDelete(ids.value);
},
});
}
};
/**
* 删除
*
* @param ids ID 列表
*/
const handleDelete = (ids: Array<string>) => {
del(ids).then((res) => {
proxy.$message.success(res.msg);
getList();
});
};
/**
* 已选择的数据行发生改变时触发
*
* @param rowKeys ID 列表
*/
const handleSelectionChange = (rowKeys: Array<any>) => {
ids.value = rowKeys;
single.value = rowKeys.length !== 1;
multiple.value = !rowKeys.length;
};
/**
* 导出
*/
const handleExport = () => {
if (exportLoading.value) return;
exportLoading.value = true;
proxy
.download('/${apiModuleName}/${apiName}/export', { ...queryParams.value }, '${businessName}数据')
.finally(() => {
exportLoading.value = false;
});
};
/**
* 查询
*/
const handleQuery = () => {
getList();
};
/**
* 重置
*/
const resetQuery = () => {
proxy.$refs.queryRef.resetFields();
handleQuery();
};
/**
* 切换页码
*
* @param current 页码
*/
const handlePageChange = (current: number) => {
queryParams.value.page = current;
getList();
};
/**
* 切换每页条数
*
* @param pageSize 每页条数
*/
const handlePageSizeChange = (pageSize: number) => {
queryParams.value.size = pageSize;
getList();
};
</script>
<script lang="ts">
export default {
name: '${classNamePrefix}',
};
</script>
<template>
<div class="app-container">
<Breadcrumb :items="['menu.${apiModuleName}', 'menu.${apiModuleName}.${apiName}.list']" />
<a-card class="general-card" :title="$t('menu.${apiModuleName}.${apiName}.list')">
<!-- 头部区域 -->
<div class="header">
<!-- 搜索栏 -->
<div v-if="showQuery" class="header-query">
<a-form ref="queryRef" :model="queryParams" layout="inline">
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
<a-form-item field="${fieldConfig.fieldName}" hide-label>
<a-input
v-model="queryParams.${fieldConfig.fieldName}"
placeholder="输入${fieldConfig.comment}搜索"
allow-clear
style="width: 150px"
@press-enter="handleQuery"
/>
</a-form-item>
</#if>
</#list>
<a-form-item hide-label>
<a-space>
<a-button type="primary" @click="handleQuery">
<template #icon><icon-search /></template>查询
</a-button>
<a-button @click="resetQuery">
<template #icon><icon-refresh /></template>重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</div>
<!-- 操作栏 -->
<div class="header-operation">
<a-row>
<a-col :span="12">
<a-space>
<a-button
v-permission="['${apiModuleName}:${apiName}:add']"
type="primary"
@click="toAdd"
>
<template #icon><icon-plus /></template>新增
</a-button>
<a-button
v-permission="['${apiModuleName}:${apiName}:update']"
type="primary"
status="success"
:disabled="single"
:title="single ? '请选择一条要修改的数据' : ''"
@click="toUpdate(ids[0])"
>
<template #icon><icon-edit /></template>修改
</a-button>
<a-button
v-permission="['${apiModuleName}:${apiName}:delete']"
type="primary"
status="danger"
:disabled="multiple"
:title="multiple ? '请选择要删除的数据' : ''"
@click="handleBatchDelete"
>
<template #icon><icon-delete /></template>删除
</a-button>
<a-button
v-permission="['${apiModuleName}:${apiName}:export']"
:loading="exportLoading"
type="primary"
status="warning"
@click="handleExport"
>
<template #icon><icon-download /></template>导出
</a-button>
</a-space>
</a-col>
<a-col :span="12">
<right-toolbar
v-model:show-query="showQuery"
@refresh="getList"
/>
</a-col>
</a-row>
</div>
</div>
<!-- 列表区域 -->
<a-table
ref="tableRef"
row-key="id"
:data="dataList"
:loading="loading"
:row-selection="{
type: 'checkbox',
showCheckedAll: true,
onlyCurrent: false,
}"
:pagination="{
showTotal: true,
showPageSize: true,
total: total,
current: queryParams.page,
}"
:bordered="false"
column-resizable
stripe
size="large"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
@selection-change="handleSelectionChange"
>
<template #columns>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig_index = 0>
<a-table-column title="${fieldConfig.comment}" data-index="${fieldConfig.fieldName}">
<template #cell="{ record }">
<a-link @click="toDetail(record.id)">{{ record.${fieldConfig.fieldName} }}</a-link>
</template>
</a-table-column>
<#else>
<#if fieldConfig.showInList>
<a-table-column title="${fieldConfig.comment}" data-index="${fieldConfig.fieldName}" />
</#if>
</#if>
</#list>
<a-table-column
v-if="checkPermission(['${apiModuleName}:${apiName}:update', '${apiModuleName}:${apiName}:delete'])"
title="操作"
align="center"
>
<template #cell="{ record }">
<a-button
v-permission="['${apiModuleName}:${apiName}:update']"
type="text"
size="small"
title="修改"
@click="toUpdate(record.id)"
>
<template #icon><icon-edit /></template>修改
</a-button>
<a-popconfirm
content="是否确定删除该数据?"
type="warning"
@ok="handleDelete([record.id])"
>
<a-button
v-permission="['${apiModuleName}:${apiName}:delete']"
type="text"
size="small"
title="删除"
:disabled="record.disabled"
>
<template #icon><icon-delete /></template>删除
</a-button>
</a-popconfirm>
</template>
</a-table-column>
</template>
</a-table>
<!-- 表单区域 -->
<a-modal
:title="title"
:visible="visible"
:mask-closable="false"
:esc-to-close="false"
unmount-on-close
render-to-body
@ok="handleOk"
@cancel="handleCancel"
>
<a-form ref="formRef" :model="form" :rules="rules" size="large">
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInForm>
<a-form-item label="${fieldConfig.comment}" field="${fieldConfig.fieldName}">
<#if fieldConfig.formType = 'TEXT'>
<a-input v-model="form.${fieldConfig.fieldName}" placeholder="请输入${fieldConfig.comment}" />
<#elseif fieldConfig.formType = 'TEXT_AREA'>
<a-textarea
v-model="form.${fieldConfig.fieldName}"
:max-length="200"
placeholder="请输入${fieldConfig.comment}"
:auto-size="{
minRows: 3,
}"
show-word-limit
/>
<#elseif fieldConfig.formType = 'SELECT'>
<#--<a-select
v-model="form.${fieldConfig.fieldName}"
:options="${apiName}Options"
placeholder="请选择${fieldConfig.comment}"
:loading="${apiName}Loading"
multiple
allow-clear
:allow-search="{ retainInputValue: true }"
style="width: 416px"
/>-->
<#elseif fieldConfig.formType = 'RADIO'>
<#--<a-radio-group v-model="form.${fieldConfig.fieldName}" type="button">
</a-radio-group>-->
<#elseif fieldConfig.formType = 'DATE'>
<a-date-picker v-model="form.${fieldConfig.fieldName}" placeholder="请选择${fieldConfig.comment}"/>
<#elseif fieldConfig.formType = 'DATE_TIME'>
<a-date-picker
v-model="form.${fieldConfig.fieldName}"
placeholder="请选择${fieldConfig.comment}"
show-time
format="YYYY-MM-DD HH:mm:ss"
/>
</#if>
</a-form-item>
</#if>
</#list>
</a-form>
</a-modal>
<!-- 详情区域 -->
<a-drawer
title="${businessName}详情"
:visible="detailVisible"
:width="580"
:footer="false"
unmount-on-close
render-to-body
@cancel="handleDetailCancel"
>
<a-descriptions :column="2" bordered size="large">
<#list fieldConfigs as fieldConfig>
<a-descriptions-item label="${fieldConfig.comment}">
<a-skeleton v-if="detailLoading" :animation="true">
<a-skeleton-line :rows="1" />
</a-skeleton>
<#if fieldConfig.fieldName = 'createUser'>
<span v-else>{{ dataDetail.createUserString }}</span>
<#elseif fieldConfig.fieldName = 'updateUser'>
<span v-else>{{ dataDetail.updateUserString }}</span>
<#else>
<span v-else>{{ dataDetail.${fieldConfig.fieldName} }}</span>
</#if>
</a-descriptions-item>
</#list>
</a-descriptions>
</a-drawer>
</a-card>
</div>
</template>
<style scoped lang="less"></style>