yuanchao 3 ماه پیش
کامیت
5884e331a0

+ 62 - 0
.gitignore

@@ -0,0 +1,62 @@
+### gradle ###
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+### STS ###
+.settings/
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+*.lock
+rebel.xml
+
+### NetBeans ###
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+### maven ###
+target/
+*.war
+*.ear
+*.zip
+*.tar
+*.tar.gz
+
+### logs ####
+/logs/
+*.log
+
+### temp ignore ###
+*.cache
+*.diff
+*.patch
+*.tmp
+*.java~
+*.properties~
+*.xml~
+
+### system ignore ###
+.DS_Store
+Thumbs.db
+Servers
+.metadata
+upload
+gen_code
+
+### node ###
+node_modules

+ 9 - 0
README.md

@@ -0,0 +1,9 @@
+# mall4g
+
+商城的代码生成器
+
+1. 修改数据库连接,修改 application.yml
+2. 代码生成器依赖生成模板,根据模板可以自己改造属于自己的代码生成器,模板位置 template文件夹
+3. 修改generator.properties 修改需要生成的模块
+4. 修改GenApplication里面的tables,更改需要生成的表,运行
+

+ 89 - 0
pom.xml

@@ -0,0 +1,89 @@
+<?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">
+
+    <groupId>com.yami.shop</groupId>
+    <version>1.0.0</version>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>yami-shop-generator</artifactId>
+
+    <properties>
+        <yami.shop.version>0.0.1-SNAPSHOT</yami.shop.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
+        <spring-boot.version>3.0.7</spring-boot.version>
+        <java.version>17</java.version>
+        <guava.version>31.1-jre</guava.version>
+        <hutool.version>5.8.15</hutool.version>
+        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
+        <mysql-connection.version>8.0.33</mysql-connection.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>${hutool.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>${mysql-connection.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+        <!--代码生成模板引擎-->
+        <dependency>
+            <artifactId>velocity</artifactId>
+            <groupId>org.apache.velocity</groupId>
+            <version>1.7</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-configuration</groupId>
+            <artifactId>commons-configuration</artifactId>
+            <version>1.10</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>${maven.compiler.plugin.version}</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 42 - 0
src/main/java/com/yami/shop/generator/GenApplication.java

@@ -0,0 +1,42 @@
+package com.yami.shop.generator;
+
+import com.yami.shop.generator.service.SysGeneratorService;
+import lombok.AllArgsConstructor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.BeansException;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.ComponentScan;
+
+/**
+ * @author LGH
+ */
+@SpringBootApplication
+@ComponentScan("com.yami.shop")
+@MapperScan({"com.yami.shop.generator.dao"})
+public class GenApplication implements ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext)
+            throws BeansException {
+        GenApplication.applicationContext = applicationContext;
+    }
+
+
+    public static void main(String[] args) {
+        SpringApplication.run(GenApplication.class, args);
+
+        SysGeneratorService sysGeneratorService = applicationContext.getBean(SysGeneratorService.class);
+
+        String[] tables = {"tz_seckill","tz_seckill_sku", "tz_seckill_order"};
+
+        for (String table : tables) {
+            sysGeneratorService.generatorCode(table);
+        }
+    }
+
+}

+ 39 - 0
src/main/java/com/yami/shop/generator/dao/SysGeneratorMapper.java

@@ -0,0 +1,39 @@
+package com.yami.shop.generator.dao;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 代码生成器
+ */
+public interface SysGeneratorMapper {
+
+    /**
+     * 分页查询表格
+     *
+     * @param page
+     * @param tableName
+     * @return
+     */
+    IPage<List<Map<String, Object>>> queryList(Page page, @Param("tableName") String tableName);
+
+    /**
+     * 查询表信息
+     *
+     * @param tableName 表名称
+     * @return
+     */
+    Map<String, String> queryTable(String tableName);
+
+    /**
+     * 查询表列信息
+     *
+     * @param tableName 表名称
+     * @return
+     */
+    List<Map<String, String>> queryColumns(String tableName);
+}

+ 39 - 0
src/main/java/com/yami/shop/generator/entity/ColumnEntity.java

@@ -0,0 +1,39 @@
+package com.yami.shop.generator.entity;
+
+import lombok.Data;
+
+/**
+ * 列属性: https://blog.csdn.net/lkforce/article/details/79557482
+ */
+@Data
+public class ColumnEntity {
+    /**
+     * 列表
+     */
+    private String columnName;
+    /**
+     * 数据类型
+     */
+    private String dataType;
+    /**
+     * 备注
+     */
+    private String comments;
+
+    /**
+     * 驼峰属性
+     */
+    private String caseAttrName;
+    /**
+     * 普通属性
+     */
+    private String lowerAttrName;
+    /**
+     * 属性类型
+     */
+    private String attrType;
+    /**
+     * 其他信息
+     */
+    private String extra;
+}

+ 22 - 0
src/main/java/com/yami/shop/generator/entity/CommonConstants.java

@@ -0,0 +1,22 @@
+package com.yami.shop.generator.entity;
+
+/**
+ */
+public interface CommonConstants {
+
+    /**
+     * 前端工程名
+     */
+    String FRONT_END_PROJECT = "yami-vue";
+
+    /**
+     * 后端工程名
+     */
+    String BACK_END_PROJECT = "yami-shop";
+
+    /**
+     * 后端工程名
+     */
+    String SQL_PROJECT = "yami-sql";
+
+}

+ 40 - 0
src/main/java/com/yami/shop/generator/entity/TableEntity.java

@@ -0,0 +1,40 @@
+package com.yami.shop.generator.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 表属性: https://blog.csdn.net/lkforce/article/details/79557482
+ */
+@Data
+public class TableEntity {
+    /**
+     * 名称
+     */
+    private String tableName;
+    /**
+     * 备注
+     */
+    private String comments;
+    /**
+     * 主键
+     */
+    private ColumnEntity pk;
+    /**
+     * 列名
+     */
+    private List<ColumnEntity> columns;
+    /**
+     * 驼峰类型
+     */
+    private String caseClassName;
+    /**
+     * 普通类型
+     */
+    private String lowerClassName;
+    /**
+     * 含有中划线的名称
+     */
+    private String midName;
+}

+ 11 - 0
src/main/java/com/yami/shop/generator/service/SysGeneratorService.java

@@ -0,0 +1,11 @@
+package com.yami.shop.generator.service;
+
+
+public interface SysGeneratorService {
+    /**
+     * 生成代码
+     *
+     * @param tableName 表名称
+     */
+    void generatorCode(String tableName);
+}

+ 49 - 0
src/main/java/com/yami/shop/generator/service/impl/SysGeneratorServiceImpl.java

@@ -0,0 +1,49 @@
+package com.yami.shop.generator.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import com.yami.shop.generator.dao.SysGeneratorMapper;
+import com.yami.shop.generator.service.SysGeneratorService;
+import com.yami.shop.generator.util.GenUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 代码生成器
+ */
+@Service
+@AllArgsConstructor
+public class SysGeneratorServiceImpl implements SysGeneratorService {
+    private final SysGeneratorMapper sysGeneratorMapper;
+
+    /**
+     * 生成代码
+     *
+     * @param tableName 表名称
+     */
+    @Override
+    public void generatorCode(String tableName) {
+
+        //查询表信息
+        Map<String, String> table = queryTable(tableName);
+        if (CollectionUtil.isEmpty(table)) {
+            throw new RuntimeException("在数据库中无法获取[" +tableName+"]表信息");
+        }
+        //查询列信息
+        List<Map<String, String>> columns = queryColumns(tableName);
+        //生成代码
+        GenUtils.generatorCode(table, columns);
+    }
+
+    private Map<String, String> queryTable(String tableName) {
+        return sysGeneratorMapper.queryTable(tableName);
+    }
+
+    private List<Map<String, String>> queryColumns(String tableName) {
+        return sysGeneratorMapper.queryColumns(tableName);
+    }
+}

+ 251 - 0
src/main/java/com/yami/shop/generator/util/GenUtils.java

@@ -0,0 +1,251 @@
+package com.yami.shop.generator.util;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.CharsetUtil;
+import com.yami.shop.generator.entity.ColumnEntity;
+import com.yami.shop.generator.entity.CommonConstants;
+import com.yami.shop.generator.entity.TableEntity;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.WordUtils;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.util.*;
+
+/**
+ * 代码生成器   工具类
+ */
+@Slf4j
+@UtilityClass
+public class GenUtils {
+
+    private final String ENTITY_JAVA_VM = "Model.java.vm";
+    private final String MAPPER_JAVA_VM = "Mapper.java.vm";
+    private final String SERVICE_JAVA_VM = "Service.java.vm";
+    private final String SERVICE_IMPL_JAVA_VM = "ServiceImpl.java.vm";
+    private final String CONTROLLER_JAVA_VM = "Controller.java.vm";
+    private final String MAPPER_XML_VM = "Mapper.xml.vm";
+    private final String MENU_SQL_VM = "menu.sql.vm";
+    private final String INDEX_VUE_VM = "index.vue.vm";
+    private final String ADD_OR_UPDATE_VUE_VM = "add-or-update.vue.vm";
+
+    private List<String> getTemplates() {
+        List<String> templates = new ArrayList<>();
+        templates.add("template/Model.java.vm");
+        templates.add("template/Mapper.java.vm");
+        templates.add("template/Mapper.xml.vm");
+        templates.add("template/Service.java.vm");
+        templates.add("template/ServiceImpl.java.vm");
+        templates.add("template/Controller.java.vm");
+        templates.add("template/menu.sql.vm");
+
+        templates.add("template/index.vue.vm");
+        templates.add("template/add-or-update.vue.vm");
+        return templates;
+    }
+
+    /**
+     * 生成代码
+     */
+    public void generatorCode(Map<String, String> table,
+                              List<Map<String, String>> columns) {
+        //配置信息
+        Configuration config = getConfig();
+        boolean hasBigDecimal = false;
+        //表信息
+        TableEntity tableEntity = new TableEntity();
+        tableEntity.setTableName(table.get("tableName"));
+
+
+        tableEntity.setComments(table.get("tableComment"));
+
+        String tablePrefix = config.getString("tablePrefix");
+
+        //表名转换成Java类名
+        String midName = tableToMid(tableEntity.getTableName(), tablePrefix);
+        tableEntity.setMidName(midName);
+        String className = tableToJava(tableEntity.getTableName(), tablePrefix);
+        tableEntity.setCaseClassName(className);
+        tableEntity.setLowerClassName(StringUtils.uncapitalize(className));
+
+        //列信息
+        List<ColumnEntity> columnList = new ArrayList<>();
+        for (Map<String, String> column : columns) {
+            ColumnEntity columnEntity = new ColumnEntity();
+            columnEntity.setColumnName(column.get("columnName"));
+            columnEntity.setDataType(column.get("dataType"));
+            columnEntity.setComments(column.get("columnComment"));
+            columnEntity.setExtra(column.get("extra"));
+
+            //列名转换成Java属性名
+            String attrName = columnToJava(columnEntity.getColumnName());
+            columnEntity.setCaseAttrName(attrName);
+            columnEntity.setLowerAttrName(StringUtils.uncapitalize(attrName));
+
+            //列的数据类型,转换成Java类型
+            String attrType = config.getString(columnEntity.getDataType(), "unknowType");
+            columnEntity.setAttrType(attrType);
+            if (!hasBigDecimal && "BigDecimal".equals(attrType)) {
+                hasBigDecimal = true;
+            }
+            //是否主键
+            if ("PRI".equalsIgnoreCase(column.get("columnKey")) && tableEntity.getPk() == null) {
+                tableEntity.setPk(columnEntity);
+            }
+
+            columnList.add(columnEntity);
+        }
+        tableEntity.setColumns(columnList);
+
+        //没主键,则第一个字段为主键
+        if (tableEntity.getPk() == null) {
+            tableEntity.setPk(tableEntity.getColumns().get(0));
+        }
+
+        //设置velocity资源加载器
+        Properties prop = new Properties();
+        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+        Velocity.init(prop);
+        //封装模板数据
+        Map<String, Object> map = new HashMap<>(16);
+        map.put("tableName", tableEntity.getTableName());
+        map.put("pk", tableEntity.getPk());
+        map.put("className", tableEntity.getCaseClassName());
+        map.put("classname", tableEntity.getLowerClassName());
+        map.put("midName", tableEntity.getMidName());
+        map.put("pathName", tableEntity.getLowerClassName().toLowerCase());
+        map.put("columns", tableEntity.getColumns());
+        map.put("hasBigDecimal", hasBigDecimal);
+        map.put("datetime", DateUtil.now());
+
+
+        map.put("comments", tableEntity.getComments());
+
+        map.put("author", config.getString("author"));
+
+
+        map.put("moduleName", config.getString("moduleName"));
+
+
+        map.put("package", config.getString("package"));
+        map.put("mainPath", config.getString("mainPath"));
+
+        VelocityContext context = new VelocityContext(map);
+
+        //获取模板列表
+        List<String> templates = getTemplates();
+        for (String template : templates) {
+            //渲染模板
+            StringWriter sw = new StringWriter();
+            Template tpl = Velocity.getTemplate(template, CharsetUtil.UTF_8);
+            tpl.merge(context, sw);
+
+            try {
+
+                File file = new File(config.getString("rootPath") + getFileName(template, tableEntity.getCaseClassName()
+                                                 , tableEntity.getMidName(), map.get("moduleName").toString()));
+                if (file.exists()) {
+                    file.delete();
+                }
+
+                FileUtil.writeString(sw.toString(),file,CharsetUtil.UTF_8);
+                System.out.println("成功创建文件:" + file.getPath());
+                IoUtil.close(sw);
+            } catch (Exception e) {
+                e.printStackTrace();
+                throw new RuntimeException("渲染模板失败,表名:" + tableEntity.getTableName());
+            }
+        }
+    }
+
+
+    /**
+     * 列名转换成Java属性名
+     */
+    private String columnToJava(String columnName) {
+        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
+    }
+
+    private String tableToMid(String tableName, String tablePrefix) {
+        if (StringUtils.isNotBlank(tablePrefix)) {
+            tableName = tableName.replaceFirst(tablePrefix, "");
+        }
+        return tableName.replace("_", "-");
+    }
+
+    /**
+     * 表名转换成Java类名
+     */
+    private String tableToJava(String tableName, String tablePrefix) {
+        if (StringUtils.isNotBlank(tablePrefix)) {
+            tableName = tableName.replaceFirst(tablePrefix, "");
+        }
+        return columnToJava(tableName);
+    }
+
+    /**
+     * 获取配置信息
+     */
+    private Configuration getConfig() {
+        try {
+            return new PropertiesConfiguration("generator.properties");
+        } catch (ConfigurationException e) {
+            throw new RuntimeException("获取配置文件失败,");
+        }
+    }
+
+    /**
+     * 获取文件名
+     */
+    private String getFileName(String template, String className, String midName, String moduleName) {
+        String packagePath = CommonConstants.BACK_END_PROJECT + File.separator;
+
+        if (template.contains(ENTITY_JAVA_VM)) {
+            return packagePath + "model" + File.separator + className + ".java";
+        }
+
+        if (template.contains(MAPPER_JAVA_VM)) {
+            return packagePath + "dao" + File.separator + className + "Mapper.java";
+        }
+
+        if (template.contains(SERVICE_JAVA_VM)) {
+            return packagePath + "service" + File.separator + className + "Service.java";
+        }
+
+        if (template.contains(SERVICE_IMPL_JAVA_VM)) {
+            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
+        }
+
+        if (template.contains(CONTROLLER_JAVA_VM)) {
+            return packagePath + "controller" + File.separator + className + "Controller.java";
+        }
+
+        if (template.contains(MAPPER_XML_VM)) {
+            return CommonConstants.BACK_END_PROJECT + File.separator + "xml" + File.separator + className + "Mapper.xml";
+        }
+
+        if (template.contains(MENU_SQL_VM)) {
+            return CommonConstants.SQL_PROJECT + File.separator + className.toLowerCase() + "_menu.sql";
+        }
+
+        if (template.contains(INDEX_VUE_VM)) {
+            return CommonConstants.FRONT_END_PROJECT + File.separator + "vue" + File.separator + StringUtils.uncapitalize(midName) + File.separator + "index.vue";
+        }
+
+        if (template.contains(ADD_OR_UPDATE_VUE_VM)) {
+            return CommonConstants.FRONT_END_PROJECT + File.separator + "vue" + File.separator + StringUtils.uncapitalize(midName) + File.separator + "add-or-update.vue";
+        }
+
+        return null;
+    }
+}

+ 28 - 0
src/main/resources/application.yml

@@ -0,0 +1,28 @@
+spring:
+  datasource:
+    url: jdbc:mysql://127.0.0.1:3306/yami_shops?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
+    username: root
+    password: root
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      minimum-idle: 0
+      maximum-pool-size: 20
+      idle-timeout: 10000
+      auto-commit: true
+      connection-test-query: SELECT 1
+
+# mybaits-plus配置
+mybatis-plus:
+  # MyBatis Mapper所对应的XML文件位置
+  mapper-locations: classpath*:/mapper/*Mapper.xml
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      # 主键类型 0:数据库ID自增 1.未定义 2.用户输入 3 id_worker 4.uuid 5.id_worker字符串表示
+      id-type: AUTO
+      #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
+      field-strategy: NOT_NULL
+      # 默认数据库表下划线命名
+      table-underline: true

+ 36 - 0
src/main/resources/generator.properties

@@ -0,0 +1,36 @@
+#\u4EE3\u7801\u751F\u6210\u5668\uFF0C\u914D\u7F6E\u4FE1\u606F
+rootPath=c:/generator/
+
+mainPath=com.yami.shop
+#\u5305\u540D
+package=com.yami.shop
+moduleName=seckill
+#\u4F5C\u8005
+author=LGH
+
+#\u8868\u524D\u7F00(\u7C7B\u540D\u4E0D\u4F1A\u5305\u542B\u8868\u524D\u7F00)
+tablePrefix=tz_
+
+#\u7C7B\u578B\u8F6C\u6362\uFF0C\u914D\u7F6E\u4FE1\u606F
+tinyint=Integer
+smallint=Integer
+mediumint=Integer
+int=Integer
+integer=Integer
+bigint=Long
+float=Float
+double=Double
+decimal=Double
+bit=Integer
+
+char=String
+varchar=String
+tinytext=String
+text=String
+mediumtext=String
+longtext=String
+json=String
+
+date=Date
+datetime=Date
+timestamp=Date

+ 23 - 0
src/main/resources/mapper/SysGeneratorMapper.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="com.yami.shop.generator.dao.SysGeneratorMapper">
+    <select id="queryList" resultType="map">
+        select table_name tableName, engine, table_comment tableComment, create_time createTime from information_schema.tables
+            where table_schema = (select database())
+        <if test="tableName != null and tableName.trim() != ''">
+            and table_name like concat('%', #{tableName}, '%')
+        </if>
+        order by create_time desc
+    </select>
+
+    <select id="queryTable" resultType="map">
+        select table_name tableName, engine, table_comment tableComment, create_time createTime from information_schema.tables
+            where table_schema = (select database()) and table_name = #{tableName}
+    </select>
+
+    <select id="queryColumns" resultType="map">
+        select column_name columnName, data_type dataType, column_comment columnComment, column_key columnKey, extra from information_schema.columns
+             where table_name = #{tableName} and table_schema = (select database()) order by ordinal_position
+    </select>
+</mapper>

+ 77 - 0
src/main/resources/template/Controller.java.vm

@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
+ *
+ * https://www.mall4j.com/
+ *
+ * 未经允许,不可做商业用途!
+ *
+ * 版权所有,侵权必究!
+ */
+package ${package}.${moduleName}.admin.controller;
+
+import ${package}.${moduleName}.common.model.${className};
+import ${package}.${moduleName}.common.service.${className}Service;
+
+import jakarta.validation.Valid;
+
+import com.yami.shop.common.response.ServerResponseEntity;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.yami.shop.common.util.PageParam;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+
+/**
+ * ${comments}
+ * @author ${author}
+ */
+@RestController
+@RequestMapping("/${moduleName}/${classname}")
+@Tag(name = "${comments}")
+@AllArgsConstructor
+public class ${className}Controller {
+
+    private final ${className}Service ${classname}Service;
+
+    @GetMapping("/page")
+    @Operation(summary = "获取${comments}列表", description = "分页获取${comments}列表")
+    public ServerResponseEntity<IPage<${className}>> get${className}Page(PageParam<${className}> page, ${className} ${classname}) {
+        return ServerResponseEntity.success(${classname}Service.page(page, new LambdaQueryWrapper<${className}>()));
+    }
+
+    @GetMapping("/info/{${pk.lowerAttrName}}")
+    @Operation(summary = "获取${comments}", description = "根据${pk.lowerAttrName}获取${comments}")
+    @Parameter(name = "${pk.lowerAttrName}", description = "${comments}", required = true)
+    public ServerResponseEntity<${className}> getById(@PathVariable("${pk.lowerAttrName}") ${pk.attrType} ${pk.lowerAttrName}) {
+        return ServerResponseEntity.success(${classname}Service.getById(${pk.lowerAttrName}));
+    }
+
+    @PostMapping
+    @PreAuthorize("@pms.hasPermission('${moduleName}:${classname}:save')")
+    @Operation(summary = "保存${comments}", description = "保存${comments}")
+    public ServerResponseEntity<Boolean> save(@RequestBody @Valid ${className} ${classname}) {
+        return ServerResponseEntity.success(${classname}Service.save(${classname}));
+    }
+
+    @PutMapping
+    @PreAuthorize("@pms.hasPermission('${moduleName}:${classname}:update')")
+    @Operation(summary = "更新${comments}", description = "更新${comments}")
+    public ServerResponseEntity<Boolean> updateById(@RequestBody @Valid ${className} ${classname}) {
+        return ServerResponseEntity.success(${classname}Service.updateById(${classname}));
+    }
+
+    @DeleteMapping("/{${pk.lowerAttrName}}")
+    @PreAuthorize("@pms.hasPermission('${moduleName}:${classname}:delete')")
+    @Operation(summary = "删除${comments}", description = "根据${comments}id删除${comments}")
+    @Parameter(name = "${pk.lowerAttrName}", description = "${comments}", required = true)
+    public ServerResponseEntity<Boolean> removeById(@PathVariable ${pk.attrType} ${pk.lowerAttrName}) {
+        return ServerResponseEntity.success(${classname}Service.removeById(${pk.lowerAttrName}));
+    }
+}

+ 21 - 0
src/main/resources/template/Mapper.java.vm

@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
+ *
+ * https://www.mall4j.com/
+ *
+ * 未经允许,不可做商业用途!
+ *
+ * 版权所有,侵权必究!
+ */
+package ${package}.${moduleName}.common.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import ${package}.${moduleName}.common.model.${className};
+
+/**
+ * ${comments}
+ * @author ${author}
+ */
+public interface ${className}Mapper extends BaseMapper<${className}> {
+
+}

+ 15 - 0
src/main/resources/template/Mapper.xml.vm

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="${package}.${moduleName}.common.dao.${className}Mapper">
+
+  <resultMap id="${classname}Map" type="${package}.${moduleName}.common.model.${className}">
+#foreach($column in $columns)
+#if($column.lowerAttrName==$pk.lowerAttrName)
+    <id column="${pk.columnName}" property="${pk.lowerAttrName}" />
+#else
+    <result column="${column.columnName}" property="${column.lowerAttrName}"/>
+#end
+#end
+  </resultMap>
+</mapper>

+ 40 - 0
src/main/resources/template/Model.java.vm

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
+ *
+ * https://www.mall4j.com/
+ *
+ * 未经允许,不可做商业用途!
+ *
+ * 版权所有,侵权必究!
+ */
+package ${package}.${moduleName}.common.model;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+#if(${hasBigDecimal})
+import java.math.BigDecimal;
+#end
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * ${comments}
+ * @author ${author}
+ */
+@Data
+@TableName("${tableName}")
+@Schema(description = "${comments}")
+public class ${className} implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+#foreach ($column in $columns)
+#if($column.columnName == $pk.columnName)
+    @TableId
+#else
+    @Schema(description = "$column.comments")
+#end
+    private $column.attrType $column.lowerAttrName;
+#end
+}

+ 21 - 0
src/main/resources/template/Service.java.vm

@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
+ *
+ * https://www.mall4j.com/
+ *
+ * 未经允许,不可做商业用途!
+ *
+ * 版权所有,侵权必究!
+ */
+package ${package}.${moduleName}.common.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import ${package}.${moduleName}.common.model.${className};
+
+/**
+ * ${comments}
+ * @author ${author}
+ */
+public interface ${className}Service extends IService<${className}> {
+
+}

+ 29 - 0
src/main/resources/template/ServiceImpl.java.vm

@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
+ *
+ * https://www.mall4j.com/
+ *
+ * 未经允许,不可做商业用途!
+ *
+ * 版权所有,侵权必究!
+ */
+package ${package}.${moduleName}.common.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import ${package}.${moduleName}.common.model.${className};
+import ${package}.${moduleName}.common.dao.${className}Mapper;
+import ${package}.${moduleName}.common.service.${className}Service;
+import org.springframework.stereotype.Service;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * ${comments}
+ * @author ${author}
+ */
+@Service
+@AllArgsConstructor
+public class ${className}ServiceImpl extends ServiceImpl<${className}Mapper, ${className}> implements ${className}Service {
+
+    private final ${className}Mapper ${classname}Mapper;
+}

+ 103 - 0
src/main/resources/template/add-or-update.vue.vm

@@ -0,0 +1,103 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    class="component-${midName}-add-or-update"
+    :title="!dataForm.${pk.lowerAttrName} ? $t('crud.addTitle') : $t('temp.modify')"
+    :close-on-click-modal="false"
+  >
+    <el-form
+      ref="dataFormRef"
+      :model="dataForm"
+      :rules="dataRule"
+      label-width="80px"
+      @keyup.enter="onSubmit()"
+    >
+#foreach ($column in $columns)
+      <el-form-item
+        label="$column.comments"
+        prop="$column.lowerAttrName"
+      >
+        <el-input
+          v-model="dataForm.$column.lowerAttrName"
+        />
+      </el-form-item>
+#end
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <div
+          class="default-btn"
+          @click="visible = false"
+        >
+          {{ $t("crud.filter.cancelBtn") }}
+        </div>
+        <div
+          class="default-btn primary-btn"
+          type="primary"
+          @click="onSubmit()"
+        >
+          {{ $t("crud.filter.submitBtn") }}
+        </div>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ElMessage } from 'element-plus'
+
+const emit = defineEmits(['refreshDataList'])
+const visible = ref(false)
+
+const dataFormRef = ref(null)
+const dataForm = ref({
+#foreach ($column in $columns)
+  $column.lowerAttrName: null,
+#end
+})
+
+const dataRule = reactive({})
+const init = (${pk.lowerAttrName}) => {
+  dataForm.value.${pk.lowerAttrName} = ${pk.lowerAttrName} || 0
+  visible.value = true
+  nextTick(() => {
+    dataFormRef.value?.resetFields()
+    if (dataForm.value.${pk.lowerAttrName}) {
+      http({
+        url: http.adornUrl('/${moduleName}/${classname}/info/' + dataForm.value.${pk.lowerAttrName}),
+        method: 'get',
+        params: http.adornParams()
+      }).then(({ data }) => {
+        dataForm.value = data
+      })
+    }
+  })
+}
+
+// 表单提交
+const onSubmit = () => {
+  dataFormRef.value.validate(valid => {
+    if (valid) {
+      http({
+        url: http.adornUrl('/${moduleName}/${classname}'),
+        method: dataForm.value.${pk.lowerAttrName} ? 'put' : 'post',
+        data: http.adornData(dataForm.value)
+      }).then(() => {
+        ElMessage({
+          message: $t('publics.operation'),
+          type: 'success',
+          duration: 1500,
+          onClose: () => {
+            visible.value = false
+            emit('refreshDataList')
+          }
+        })
+      })
+    }
+  })
+}
+
+defineExpose({
+  init
+})
+</script>

+ 215 - 0
src/main/resources/template/index.vue.vm

@@ -0,0 +1,215 @@
+<template>
+  <div class="page-${moduleName}-${midName}">
+    <!-- 搜索相关区域 -->
+    <div class="search-bar">
+      <el-form
+        ref="searchFormRef"
+        :inline="true"
+        class="search-form"
+        :model="searchForm"
+        label-width="auto"
+        @submit.prevent
+      >
+        <div class="input-row">
+          <el-form-item>
+            <div
+              class="default-btn primary-btn"
+              @click="onSearch(true)"
+            >
+              {{ $t('crud.searchBtn') }}
+            </div>
+            <div
+              class="default-btn"
+              @click="onResetSearch"
+            >
+              {{ $t('shop.resetMap') }}
+            </div>
+          </el-form-item>
+        </div>
+      </el-form>
+    </div>
+    <!-- 列表相关区域 -->
+    <div class="main-container">
+      <div class="operation-bar">
+        <div
+          v-if="isAuth('${moduleName}:${classname}:save')"
+          class="default-btn primary-btn"
+          @click="onAddOrUpdate()"
+        >
+          {{ $t("crud.addTitle") }}
+        </div>
+      </div>
+      <div class="table-con ${midName}-table">
+        <el-table
+          ref="${classname}Table"
+          :data="dataList"
+          header-cell-class-name="table-header"
+          row-class-name="table-row-low"
+          style="width: 100%"
+        >
+#foreach($column in $columns)#if(${column.columnName} != "create_time" && ${column.columnName} != "update_time" && $column.lowerAttrName!=$pk.lowerAttrName)
+          <!-- $column.comments -->
+          <el-table-column
+            :label="$t('${classname}.$column.lowerAttrName')"
+            prop="$column.lowerAttrName"
+            align="center"
+          >
+            <template #default="scope">
+              <span>{{ scope.row.$column.lowerAttrName }}</span>
+            </template>
+          </el-table-column>
+#end#end
+          <el-table-column
+            align="center"
+            fixed="right"
+            :label="$t('publics.operating')"
+            width="auto"
+          >
+            <template #default="scope">
+              <div class="text-btn-con">
+                <div
+                  v-if="isAuth('${moduleName}:${classname}:update')"
+                  class="default-btn text-btn"
+                  @click="onAddOrUpdate(scope.row.${pk.lowerAttrName})"
+                >
+                  {{ $t("crud.updateBtn") }}
+                </div>
+                <div
+                  v-if="isAuth('${moduleName}:${classname}:delete')"
+                  class="default-btn text-btn"
+                  @click.stop="onDelete(scope.row.${pk.lowerAttrName})"
+                >
+                  {{ $t("text.delBtn") }}
+                </div>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <el-pagination
+        v-if="dataList.length"
+        :current-page="page.currentPage"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="page.pageSize"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="page.total"
+        @update:page-size="onPageSizeChange"
+        @update:current-page="onPageChange"
+      />
+    </div>
+    <AddOrUpdate
+      v-if="addOrUpdateVisible"
+      ref="addOrUpdateRef"
+      @refresh-data-list="refreshChange"
+    />
+  </div>
+</template>
+
+<script setup>
+import AddOrUpdate from './add-or-update.vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { isAuth } from '@/utils/index'
+
+let tempSearchForm = null // 保存上次点击查询的请求条件
+
+const dataList = ref([])
+
+const page = reactive({
+  total: 0, // 总页数
+  currentPage: 1, // 当前页数
+  pageSize: 10 // 每页显示多少条
+})
+
+// 头部搜索表单
+const searchForm = reactive({})
+
+onMounted(() => {
+  getDataList()
+})
+
+const getDataList = (pageParam, newData = false) => {
+  if (newData || !tempSearchForm) {
+    tempSearchForm = JSON.parse(JSON.stringify(searchForm))
+  }
+  http({
+    url: http.adornUrl('/${moduleName}/${classname}/page'),
+    method: 'get',
+    params: http.adornParams(Object.assign(tempSearchForm, {
+      current: pageParam == null ? page.currentPage : pageParam.currentPage,
+      size: pageParam == null ? page.pageSize : pageParam.pageSize
+    }), false)
+  }).then(({ data }) => {
+    dataList.value = data.records
+    page.total = data.total
+    page.currentPage = data.current
+  })
+}
+
+// 新增 / 修改
+const addOrUpdateRef = ref(null)
+const addOrUpdateVisible = ref(false)
+const onAddOrUpdate = (id) => {
+  addOrUpdateVisible.value = true
+  nextTick(() => {
+    addOrUpdateRef.value?.init(id)
+  })
+}
+
+// 删除
+const onDelete = (id) => {
+  ElMessageBox.confirm($t('admin.isDeleOper'), $t('text.tips'), {
+    confirmButtonText: $t('crud.filter.submitBtn'),
+    cancelButtonText: $t('crud.filter.cancelBtn'),
+    type: 'warning'
+  }).then(() => {
+    http({
+      url: http.adornUrl('/${moduleName}/${classname}/' + id),
+      method: 'delete',
+      data: http.adornData({})
+    }).then(() => {
+      ElMessage({
+        message: $t('publics.operation'),
+        type: 'success',
+        duration: 1500,
+        onClose: () => {
+          refreshChange()
+        }
+      })
+    }).catch(() => {})
+  })
+}
+
+// 条件查询
+const onSearch = (newData = false) => {
+  page.currentPage = 1
+  getDataList(page, newData)
+}
+
+// 刷新回调
+const refreshChange = () => {
+  addOrUpdateVisible.value = false
+  getDataList(page)
+}
+
+// 每页数量变更
+const onPageSizeChange = val => {
+  page.pageSize = val
+  getDataList()
+}
+
+// 页数变更
+const onPageChange = val => {
+  page.currentPage = val
+  getDataList()
+}
+
+// 重置表单
+const searchFormRef = ref()
+const onResetSearch = () => {
+  searchFormRef.value.resetFields()
+}
+</script>
+<style lang="scss" scoped>
+.page-${moduleName}-${midName} {
+}
+</style>

+ 30 - 0
src/main/resources/template/menu.sql.vm

@@ -0,0 +1,30 @@
+-- 该脚本不要执行,请完善 ID 对应关系,注意层级关系 !!!!
+
+-- 菜单SQL
+INSERT INTO `tz_sys_menu` (`parent_id`,`url`,`perms`,`type`,`icon`,`order_num`)
+    VALUES (0, '${moduleName}/${classname}', '', '1', '', '0');
+
+SET @yamiSysMenuLastInsertId = LAST_INSERT_ID();
+INSERT INTO `tz_sys_menu_lang`(`menu_id`, `lang`, `name`)
+    VALUES (@yamiSysMenuLastInsertId, 0, '${comments}管理');
+-- 菜单对应按钮SQL
+
+INSERT INTO `tz_sys_menu` (`parent_id`,`url`,`perms`,`type`,`icon`,`order_num`)
+    VALUES (@yamiSysMenuLastInsertId, '', '${moduleName}:${classname}:save', '2', '', '0');
+SET @ChildInsertId = LAST_INSERT_ID();
+INSERT INTO `tz_sys_menu_lang`(`menu_id`, `lang`, `name`)
+    VALUES (@ChildInsertId, 0, '新增${comments}');
+
+INSERT INTO `tz_sys_menu` (`parent_id`,`url`,`perms`,`type`,`icon`,`order_num`)
+    VALUES (@yamiSysMenuLastInsertId,  '', '${moduleName}:${classname}:update', '2', '', '0');
+SET @ChildInsertId = LAST_INSERT_ID();
+INSERT INTO `tz_sys_menu_lang`(`menu_id`, `lang`, `name`)
+    VALUES (@ChildInsertId, 0, '修改${comments}');
+
+INSERT INTO `tz_sys_menu` (`parent_id`,`url`,`perms`,`type`,`icon`,`order_num`)
+    VALUES (@yamiSysMenuLastInsertId, '', '${moduleName}:${classname}:delete', '2', '', '0');
+SET @ChildInsertId = LAST_INSERT_ID();
+INSERT INTO `tz_sys_menu_lang`(`menu_id`, `lang`, `name`)
+    VALUES (@ChildInsertId, 0, '删除${comments}');
+
+SET @yamiSysMenuLastInsertId:=NULL;