MybatisPlus3.5.12自定义代码生成器模板
版本说明:
技术 | 版本 |
---|---|
mybatis-plus | 3.5.12 |
mybatis-plus-generator | 3.5.7 |
注意:此方案mybatis-plus-generator3.5.6以上版本的项目可用,如果你的项目中版本低于此版本,请先升级MybatisPlus版本
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.12</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>3.5.12</version>
</dependency>
一 创建模板
模板代码都放在/resources/templates 目录下。例如/resources/templates/controller.java.ftl
controller.java.ftl
package ${package.Controller};
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import ${package.Entity}.${entity};
import ${package.Service}.${table.serviceName};
import com.pj.request.dto.LResponse;
import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
<#list table.fields as field>
<#if field.keyFlag>
<#assign keyPropertyType="${field.propertyType}"/>
</#if>
</#list>
/**
* <p>
* ${table.comment} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
@RestController
@AllArgsConstructor
public class ${table.controllerName} {
private final ${table.serviceName} service;
@PostMapping("/${table.name}")
public ResponseEntity<?> add(@RequestBody ${entity} entity) {
service.save(entity);
return ResponseEntity.ok(new LResponse(List.of(entity)));
}
@DeleteMapping("/${table.name}/{id}")
public ResponseEntity<?> del(@PathVariable <#if table.havePrimaryKey>${keyPropertyType}<#else>Long</#if> id) {
return ResponseEntity.ok(new LResponse(service.removeById(id)));
}
@PutMapping("/${table.name}")
public ResponseEntity<?> update(@RequestBody ${entity} entity) {
return ResponseEntity.ok(new LResponse(service.updateById(entity)));
}
@GetMapping("/${table.name}")
public ResponseEntity<?> get(${entity} entity) {
QueryWrapper<${entity}> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("created_at");
return ResponseEntity.ok(new LResponse(service.list(wrapper)));
}
}
entity.java.ftl
package ${package.Entity};
<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#if springdoc>
import io.swagger.v3.oas.annotations.media.Schema;
<#elseif swagger>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
<#if chainModel>
import lombok.experimental.Accessors;
</#if>
</#if>
/**
* <p>
* ${table.comment!}
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
@Data
@AllArgsConstructor
@NoArgsConstructor
<#if chainModel>
@Accessors(chain = true)
</#if>
</#if>
<#if table.convert>
@TableName("${schemaName}${table.name}")
</#if>
<#if springdoc>
@Schema(name = "${entity}", description = "${table.comment!}")
<#elseif swagger>
@ApiModel(value = "${entity}对象", description = "${table.comment!}")
</#if>
<#if superEntityClass??>
public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#elseif activeRecord>
public class ${entity} extends Model<${entity}> {
<#elseif entitySerialVersionUID>
public class ${entity} implements Serializable {
<#else>
public class ${entity} {
</#if>
<#if entitySerialVersionUID>
private static final long serialVersionUID = 1L;
</#if>
<#-- ---------- BEGIN 字段循环遍历 ---------->
<#list table.fields as field>
<#if field.keyFlag>
<#assign keyPropertyName="${field.propertyName}"/>
</#if>
<#if field.comment!?length gt 0>
<#if springdoc>
@Schema(description = "${field.comment}")
<#elseif swagger>
@ApiModelProperty("${field.comment}")
<#else>
/**
* ${field.comment}
*/
</#if>
</#if>
<#if field.keyFlag>
<#-- 主键 -->
<#if field.keyIdentityFlag>
@TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
<#elseif idType??>
@TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
<#elseif field.convert>
@TableId("${field.annotationColumnName}")
</#if>
<#-- 普通字段 -->
<#elseif field.fill??>
<#-- ----- 存在字段填充设置 ----->
<#if field.convert>
@TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
<#else>
@TableField(fill = FieldFill.${field.fill})
</#if>
<#elseif field.convert>
@TableField("${field.annotationColumnName}")
</#if>
<#-- 乐观锁注解 -->
<#if field.versionField>
@Version
</#if>
<#-- 逻辑删除注解 -->
<#if field.logicDeleteField>
@TableLogic
</#if>
private ${field.propertyType} ${field.propertyName};
</#list>
<#------------ END 字段循环遍历 ---------->
<#if !entityLombokModel>
<#list table.fields as field>
<#if field.propertyType == "boolean">
<#assign getprefix="is"/>
<#else>
<#assign getprefix="get"/>
</#if>
public ${field.propertyType} ${getprefix}${field.capitalName}() {
return ${field.propertyName};
}
<#if chainModel>
public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
<#else>
public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
</#if>
this.${field.propertyName} = ${field.propertyName};
<#if chainModel>
return this;
</#if>
}
</#list>
</#if>
<#if entityColumnConstant>
<#list table.fields as field>
public static final String ${field.name?upper_case} = "${field.name}";
</#list>
</#if>
<#if activeRecord>
@Override
public Serializable pkVal() {
<#if keyPropertyName??>
return this.${keyPropertyName};
<#else>
return null;
</#if>
}
</#if>
<#if !entityLombokModel>
@Override
public String toString() {
return "${entity}{" +
<#list table.fields as field>
<#if field_index==0>
"${field.propertyName} = " + ${field.propertyName} +
<#else>
", ${field.propertyName} = " + ${field.propertyName} +
</#if>
</#list>
"}";
}
</#if>
}
二 配置生成策略
数据库配置信息
# application.yml
db:
ip: localhost
port: 3306
username: root
password: 123456
server: life_space
对应的实体类
package com.pj.ls.code.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author pj
* @date 2025/4/1 21:11
**/
@Component
@ConfigurationProperties(prefix = "db")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class DatabasePropertity {
private String ip;
private String port;
private String username;
private String password;
private String server;
}
代码生成器服务类
接口
package com.pj.ls.code.service;
import com.pj.ls.code.entity.CodeGeneratorParam;
public interface ICodeGenerator {
boolean generate(CodeGeneratorParam param);
}
impl
package com.pj.ls.code.service.impl;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.pj.ls.code.entity.CodeGeneratorParam;
import com.pj.ls.code.entity.DatabasePropertity;
import com.pj.ls.code.service.ICodeGenerator;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.sql.Types;
import java.util.Collections;
/**
* @author pj
* @date 2025/4/1 21:18
**/
@Service
@AllArgsConstructor
public class CodeGeneratorImpl implements ICodeGenerator {
private static final Logger logger = LoggerFactory.getLogger(CodeGeneratorImpl.class);
private DatabasePropertity dbInfo;
@Override
public boolean generate(CodeGeneratorParam param) {
try {
String url =
"jdbc:mysql://" + dbInfo.getIp() + ":" + dbInfo.getPort() + "/" + dbInfo.getServer() +
"?useSSL=false&serverTimezone=UTC&remarks=true&useInformationSchema=true";
FastAutoGenerator.create(url, dbInfo.getUsername(), dbInfo.getPassword())
.globalConfig(builder -> {
builder.author(param.getAuth()) // 设置作者
// .enableSwagger() // 开启 swagger 模式
.outputDir(param.getOutPutDir()); // 指定输出目录
})
.dataSourceConfig(builder ->
builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
int typeCode = metaInfo.getJdbcType().TYPE_CODE;
if (typeCode == Types.SMALLINT) {
// 自定义类型转换
return DbColumnType.INTEGER;
}
return typeRegistry.getColumnType(metaInfo);
})
)
.packageConfig(builder ->
builder.parent(param.getBasePath()) // 设置父包名
.moduleName(param.getPackageName()) // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, param.getMapperPath())) // 设置mapperXml生成路径
)
.strategyConfig(builder ->
builder.addInclude(param.getTableName()) // 设置需要生成的表名
.controllerBuilder().template("/templates/controller.java").enableFileOverride()
.entityBuilder().javaTemplate("templates/entity.java").enableLombok().enableFileOverride()
.serviceBuilder().enableFileOverride()
.mapperBuilder().enableFileOverride()
// .addTablePrefix("t_", "c_") // 设置过滤表前缀
)
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
return true;
} catch (RuntimeException e) {
logger.error("代码生成出错", e);
return false;
}
}
}
三 调用代码生成器
controller层
package com.pj.ls.code.controller;
import cn.hutool.core.util.StrUtil;
import com.pj.ls.code.entity.CodeGeneratorParam;
import com.pj.ls.code.service.ICodeGenerator;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author pj
* @date 2025/4/1 21:07
**/
@RestController
@AllArgsConstructor
public class GeneratorController {
private ICodeGenerator service;
@PostMapping("/generate")
public boolean generate(@RequestBody CodeGeneratorParam params) {
if (StrUtil.isEmpty(params.getAuth())
|| StrUtil.isEmpty(params.getBasePath())
|| StrUtil.isEmpty(params.getMapperPath())
|| StrUtil.isEmpty(params.getOutPutDir())
|| StrUtil.isEmpty(params.getPackageName())
|| StrUtil.isEmpty(params.getTableName())
){
return false;
}
return service.generate(params);
}
}
前端 vue 页面
<template>
<div
style="
height: 100%;
display: flex;
justify-content: center;
align-items: center;
"
>
<div class="tech-background">
<!-- <div style="width: 100%;position: absolute;right: -20px;top: 20px;" >-->
<!-- <i class="el-icon-s-home"></i>-->
<!-- <el-button type="text" size="default" @click="back">首页</el-button>-->
<!-- </div>-->
<div class="title">
<div>代码生成器</div>
</div>
<form @submit.prevent="generateCode(form)" class="tech-form">
<div class="form-row">
<div class="form-group">
<label for="auth">作者</label>
<input
required
type="text"
id="auth"
placeholder="请输入代码作者"
v-model="form.auth"
/>
</div>
<div class="form-group">
<label for="outPutDir">文件生成所在目录(项目根目录)</label>
<input
required
type="text"
id="outPutDir"
v-model="form.outPutDir"
placeholder="例:F:\project\java\demo_20\module_01\src\main\java"
/>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="basePath">父包路径</label>
<input
required
type="text"
id="basePath"
placeholder="例:com.pj.ls"
v-model="form.basePath"
/>
</div>
<div class="form-group">
<label for="packageName">包名</label>
<input
required
type="text"
id="packageName"
placeholder="例:bill"
v-model="form.packageName"
/>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="mapperPath">mapper文件路径</label>
<input
required
type="text"
id="mapperPath"
v-model="form.mapperPath"
placeholder="例:F:\project\java\demo_20\module_01\src\main\resources\com\pj\bill\mapper"
/>
</div>
<div class="form-group">
<label for="tableName">表名</label>
<input
required
type="text"
id="tableName"
v-model="form.tableName"
placeholder="例:sys_user"
/>
</div>
</div>
<button type="submit" class="btn-submit">一键生成</button>
<div
style="
color: #ffadad;
border: none;
background-color: rgba(138, 43, 226, 0.82);
width: fit-content;
border-radius: 5px;
padding: 5px;
float: right;
"
>
注意:在同一路径下对同一张表重复生成代码,原代码会被新生成的代码覆盖!
</div>
</form>
</div>
</div>
</template>
<script>
import { generate } from '@/api/codeRequest'
import { mapMutations, mapState } from 'vuex'
export default {
data() {
return {
form: {
auth: '',
outPutDir: '',
basePath: '',
packageName: '',
mapperPath: '',
tableName: '',
},
}
},
methods: {
...mapMutations('app', ['UPDATE_SHOW_FLAG']),
generateCode(params) {
generate(params)
.then((res) => {
if (res) {
this.$message({ message: '生成成功', type: 'success' })
} else {
this.$message({ message: '生成失败', type: 'error' })
}
})
.catch((err) =>
this.$message({ message: '生成失败:' + err, type: 'error' })
)
},
back() {
this.UPDATE_SHOW_FLAG(true)
this.$router.push('/')
},
},
computed: {
...mapState('app', ['showIndexPanel']),
},
mounted() {
// this.UPDATE_SHOW_FLAG(false);
},
beforeRouteEnter(to, from, next) {
next()
},
}
</script>
<style scoped>
.tech-background {
position: relative;
width: 95%;
height: 95%;
background: linear-gradient(135deg, #00ffcc, #0066ff);
border-radius: 20px;
box-shadow: 0 0 50px rgba(0, 255, 204, 0.5), 0 0 20px rgba(0, 102, 255, 0.5);
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
}
.tech-background::before,
.tech-background::after {
content: '';
position: absolute;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(0, 255, 204, 0.8), transparent 70%);
border-radius: 50%;
animation: move 10s linear infinite;
}
.tech-background::before {
top: -50px;
left: -50px;
animation-delay: 0s;
}
.tech-background::after {
bottom: -50px;
right: -50px;
animation-delay: 5s;
}
@keyframes move {
0% {
transform: translate(0, 0);
}
50% {
transform: translate(100px, 100px);
}
100% {
transform: translate(0, 0);
}
}
.tech-background .glow {
position: absolute;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(0, 255, 204, 0.3), transparent 70%);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation: glow 3s ease-in-out infinite alternate;
}
@keyframes glow {
0% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
.title {
display: flex;
margin-top: -50px;
margin-bottom: 100px;
justify-content: center;
align-items: normal;
font-size: 300%;
font-weight: bold;
opacity: 70%;
color: #0056d4;
width: 600px;
box-shadow: 2px 5px 10px 2px rgba(142, 21, 168, 0.81);
}
.tech-form {
width: 80%;
display: flex;
flex-direction: column;
gap: 20px;
z-index: 1;
}
.form-row {
display: flex;
gap: 20px;
}
.form-group {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.form-group label {
font-size: 16px;
font-weight: bold;
color: rgba(165, 3, 234, 0.62);
}
.form-group input {
padding: 10px;
border: 2px solid #00ffcc;
border-radius: 10px;
background: rgba(109, 213, 126, 0.3);
color: #ffffff;
font-size: 14px;
outline: none;
transition: all 0.3s ease;
}
.form-group input:focus {
border-color: #0066ff;
box-shadow: 0 0 10px rgba(0, 102, 255, 0.5);
}
.btn-submit {
padding: 12px;
border: none;
border-radius: 10px;
background: linear-gradient(135deg, #00ffcc, rgba(200, 0, 255, 0.52));
color: white;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-submit:hover {
box-shadow: 0 0 15px rgba(233, 238, 238, 0.5);
}
</style>
请求接口
import axios from 'axios'
export function generate(data) {
return axios.post('generate',data)
}