mirror of
https://gitee.com/smallc/blade-tool.git
synced 2026-08-24 20:31:16 +08:00
✨ 代码生成器前端模板升级 TS + Composition API 产出,增强实体租户与主键序列化判定
This commit is contained in:
parent
e518473599
commit
3d93b3d587
|
|
@ -60,7 +60,7 @@ public class CodeGenerator {
|
||||||
/**
|
/**
|
||||||
* 基础业务字段
|
* 基础业务字段
|
||||||
*/
|
*/
|
||||||
public static String[] SUPER_ENTITY_COLUMNS = {"create_time", "create_user", "update_time", "update_user", "status", "is_deleted"};
|
public static String[] SUPER_ENTITY_COLUMNS = {"create_time", "create_user", "create_dept", "update_time", "update_user", "status", "is_deleted"};
|
||||||
/**
|
/**
|
||||||
* 是否包含包装器
|
* 是否包含包装器
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,13 @@
|
||||||
*/
|
*/
|
||||||
package org.springblade.develop.support;
|
package org.springblade.develop.support;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||||
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
|
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
|
||||||
import com.baomidou.mybatisplus.generator.config.OutputFile;
|
import com.baomidou.mybatisplus.generator.config.OutputFile;
|
||||||
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
|
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
|
||||||
|
import com.baomidou.mybatisplus.generator.config.builder.Entity;
|
||||||
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
||||||
import com.baomidou.mybatisplus.generator.config.rules.DateType;
|
import com.baomidou.mybatisplus.generator.config.rules.DateType;
|
||||||
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
|
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
|
||||||
|
|
@ -148,25 +150,33 @@ public class BladeCodeGenerator {
|
||||||
customFile.put("wrapper.java", "/templates/api/wrapper.java.vm");
|
customFile.put("wrapper.java", "/templates/api/wrapper.java.vm");
|
||||||
}
|
}
|
||||||
if (Func.isNotBlank(packageWebDir)) {
|
if (Func.isNotBlank(packageWebDir)) {
|
||||||
customFile.put("api.js", "/templates/saber/api.js.vm");
|
customFile.put("api.ts", "/templates/saber/api.ts.vm");
|
||||||
customFile.put("crud.vue", "/templates/saber/crud.vue.vm");
|
customFile.put("crud.vue", "/templates/saber/crud.vue.vm");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 无基础业务字段时实体不再继承 BaseEntity,服务层须同步降级为 MyBatis-Plus 原生接口(3.5.17 起位于 spring 包)
|
||||||
|
String superServiceClass = hasSuperEntity ? "org.springblade.core.mp.base.BaseService" : "com.baomidou.mybatisplus.spring.service.IService";
|
||||||
|
String superServiceImplClass = hasSuperEntity ? "org.springblade.core.mp.base.BaseServiceImpl" : "com.baomidou.mybatisplus.spring.service.impl.ServiceImpl";
|
||||||
|
|
||||||
FastAutoGenerator.create(url, username, password)
|
FastAutoGenerator.create(url, username, password)
|
||||||
.globalConfig(builder -> builder.author(props.getProperty("author")).dateType(DateType.TIME_PACK).enableSwagger().outputDir(getOutputDir()).disableOpenDir())
|
.globalConfig(builder -> builder.author(props.getProperty("author")).dateType(DateType.TIME_PACK).enableSwagger().outputDir(getOutputDir()).disableOpenDir())
|
||||||
.packageConfig(builder -> builder.parent(packageName).controller("controller").entity("pojo.entity").service("service").serviceImpl("service.impl").mapper("mapper").xml("mapper"))
|
.packageConfig(builder -> builder.parent(packageName).controller("controller").entity("pojo.entity").service("service").serviceImpl("service.impl").mapper("mapper").xml("mapper"))
|
||||||
.strategyConfig(builder -> builder.addTablePrefix(tablePrefix).addInclude(includeTables).addExclude(excludeTables)
|
.strategyConfig(builder -> {
|
||||||
.entityBuilder().naming(NamingStrategy.underline_to_camel).columnNaming(NamingStrategy.underline_to_camel).enableLombok().superClass("org.springblade.core.mp.base.BaseEntity").addSuperEntityColumns(superEntityColumns).enableFileOverride()
|
Entity.Builder entityBuilder = builder.addTablePrefix(tablePrefix).addInclude(includeTables).addExclude(excludeTables)
|
||||||
.javaTemplate("/templates/api/entity.java.vm")
|
.entityBuilder().naming(NamingStrategy.underline_to_camel).columnNaming(NamingStrategy.underline_to_camel).enableLombok().idType(IdType.ASSIGN_ID).enableFileOverride()
|
||||||
.serviceBuilder().superServiceClass("org.springblade.core.mp.base.BaseService").superServiceImplClass("org.springblade.core.mp.base.BaseServiceImpl").formatServiceFileName("I%sService").formatServiceImplFileName("%sServiceImpl").enableFileOverride()
|
.javaTemplate("/templates/api/entity.java.vm");
|
||||||
.serviceTemplate("/templates/api/service.java.vm")
|
if (hasSuperEntity) {
|
||||||
.serviceImplTemplate("/templates/api/serviceImpl.java.vm")
|
entityBuilder.superClass("org.springblade.core.mp.base.BaseEntity").addSuperEntityColumns(superEntityColumns);
|
||||||
.mapperBuilder().mapperAnnotation(Mapper.class).enableBaseResultMap().enableBaseColumnList().formatMapperFileName("%sMapper").formatXmlFileName("%sMapper").enableFileOverride()
|
}
|
||||||
.mapperTemplate("/templates/api/mapper.java.vm")
|
entityBuilder.serviceBuilder().superServiceClass(superServiceClass).superServiceImplClass(superServiceImplClass).formatServiceFileName("I%sService").formatServiceImplFileName("%sServiceImpl").enableFileOverride()
|
||||||
.mapperXmlTemplate("/templates/api/mapper.xml.vm")
|
.serviceTemplate("/templates/api/service.java.vm")
|
||||||
.controllerBuilder().superClass("org.springblade.core.boot.ctrl.BladeController").formatFileName("%sController").enableRestStyle().enableHyphenStyle().enableFileOverride()
|
.serviceImplTemplate("/templates/api/serviceImpl.java.vm")
|
||||||
.template("/templates/api/controller.java.vm")
|
.mapperBuilder().mapperAnnotation(Mapper.class).enableBaseResultMap().enableBaseColumnList().formatMapperFileName("%sMapper").formatXmlFileName("%sMapper").enableFileOverride()
|
||||||
)
|
.mapperTemplate("/templates/api/mapper.java.vm")
|
||||||
|
.mapperXmlTemplate("/templates/api/mapper.xml.vm")
|
||||||
|
.controllerBuilder().superClass("org.springblade.core.boot.ctrl.BladeController").formatFileName("%sController").enableRestStyle().enableHyphenStyle().enableFileOverride()
|
||||||
|
.template("/templates/api/controller.java.vm");
|
||||||
|
})
|
||||||
.injectionConfig(builder -> builder.beforeOutputFile(
|
.injectionConfig(builder -> builder.beforeOutputFile(
|
||||||
(tableInfo, objectMap) -> System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size())
|
(tableInfo, objectMap) -> System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size())
|
||||||
).customMap(customMap).customFile(customFile)
|
).customMap(customMap).customFile(customFile)
|
||||||
|
|
@ -202,8 +212,8 @@ public class BladeCodeGenerator {
|
||||||
outputPath = getOutputDir() + StringPool.SLASH + packageName.replace(StringPool.DOT, StringPool.SLASH) + StringPool.SLASH + "wrapper" + StringPool.SLASH + entityName + "Wrapper" + StringPool.DOT_JAVA;
|
outputPath = getOutputDir() + StringPool.SLASH + packageName.replace(StringPool.DOT, StringPool.SLASH) + StringPool.SLASH + "wrapper" + StringPool.SLASH + entityName + "Wrapper" + StringPool.DOT_JAVA;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtil.equals(key, "api.js")) {
|
if (StringUtil.equals(key, "api.ts")) {
|
||||||
outputPath = getOutputWebDir() + StringPool.SLASH + "api" + StringPool.SLASH + servicePackage.toLowerCase() + StringPool.SLASH + entityNameLower + ".js";
|
outputPath = getOutputWebDir() + StringPool.SLASH + "api" + StringPool.SLASH + servicePackage.toLowerCase() + StringPool.SLASH + entityNameLower + ".ts";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtil.equals(key, "crud.vue")) {
|
if (StringUtil.equals(key, "crud.vue")) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
|
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
|
||||||
* <p>
|
* <p>
|
||||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
* <p>
|
* <p>
|
||||||
* http://www.gnu.org/licenses/lgpl.html
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* <p>
|
* <p>
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|
@ -15,35 +15,30 @@
|
||||||
*/
|
*/
|
||||||
package $!{package.Controller};
|
package $!{package.Controller};
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import jakarta.validation.Valid;
|
#if($!{superControllerClassPackage})
|
||||||
|
import $!{superControllerClassPackage};
|
||||||
|
#end
|
||||||
import org.springblade.core.mp.support.Condition;
|
import org.springblade.core.mp.support.Condition;
|
||||||
import org.springblade.core.mp.support.Query;
|
import org.springblade.core.mp.support.Query;
|
||||||
import org.springblade.core.swagger.annotation.ApiOrder;
|
import org.springblade.core.swagger.annotation.ApiOrder;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
#if($!{superEntityClass})
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
#end
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
||||||
import $!{package.Entity}.$!{entity};
|
import $!{package.Entity}.$!{entity};
|
||||||
#set($voPackage=$package.Entity.replace("entity","vo"))
|
#set($voPackage=$package.Entity.replace("entity","vo"))
|
||||||
import $!{voPackage}.$!{entity}VO;
|
import $!{voPackage}.$!{entity}VO;
|
||||||
#set($wrapperPackage=$package.Entity.replace("pojo.entity","wrapper"))
|
|
||||||
#if($!{hasWrapper})
|
#if($!{hasWrapper})
|
||||||
|
#set($wrapperPackage=$package.Entity.replace("pojo.entity","wrapper"))
|
||||||
import $!{wrapperPackage}.$!{entity}Wrapper;
|
import $!{wrapperPackage}.$!{entity}Wrapper;
|
||||||
#end
|
#end
|
||||||
import $!{package.Service}.$!{table.serviceName};
|
import $!{package.Service}.$!{table.serviceName};
|
||||||
#if($!{superControllerClassPackage})
|
import org.springframework.web.bind.annotation.*;
|
||||||
import $!{superControllerClassPackage};
|
|
||||||
#end
|
import jakarta.validation.Valid;
|
||||||
#if(!$!{superEntityClass})
|
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* $!{table.comment} 控制器
|
* $!{table.comment} 控制器
|
||||||
|
|
@ -84,7 +79,6 @@ public class $!{table.controllerName} {
|
||||||
IPage<$!{entity}> pages = $!{table.entityPath}Service.page(Condition.getPage(query), Condition.getQueryWrapper($!{table.entityPath}));
|
IPage<$!{entity}> pages = $!{table.entityPath}Service.page(Condition.getPage(query), Condition.getQueryWrapper($!{table.entityPath}));
|
||||||
return R.data($!{entity}Wrapper.build().pageVO(pages));
|
return R.data($!{entity}Wrapper.build().pageVO(pages));
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
/**
|
/**
|
||||||
* 详情
|
* 详情
|
||||||
|
|
@ -144,8 +138,7 @@ public class $!{table.controllerName} {
|
||||||
return R.status($!{table.entityPath}Service.saveOrUpdate($!{table.entityPath}));
|
return R.status($!{table.entityPath}Service.saveOrUpdate($!{table.entityPath}));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if($!{superEntityClass})
|
#if($!{superEntityClass})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除 $!{table.comment}
|
* 删除 $!{table.comment}
|
||||||
*/
|
*/
|
||||||
|
|
@ -155,8 +148,7 @@ public class $!{table.controllerName} {
|
||||||
return R.status($!{table.entityPath}Service.deleteLogic(Func.toLongList(ids)));
|
return R.status($!{table.entityPath}Service.deleteLogic(Func.toLongList(ids)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除 $!{table.comment}
|
* 删除 $!{table.comment}
|
||||||
*/
|
*/
|
||||||
|
|
@ -166,6 +158,5 @@ public class $!{table.controllerName} {
|
||||||
return R.status($!{table.entityPath}Service.removeByIds(Func.toLongList(ids)));
|
return R.status($!{table.entityPath}Service.removeByIds(Func.toLongList(ids)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#end
|
#end
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,21 @@
|
||||||
|
#set($hasTenant = false)
|
||||||
|
#set($hasKeyField = false)
|
||||||
|
#set($hasLongKey = false)
|
||||||
|
#foreach($field in $!{table.fields})
|
||||||
|
#if($!{field.name} == $!{tenantColumn})
|
||||||
|
#set($hasTenant = true)
|
||||||
|
#end
|
||||||
|
#if($!{field.keyFlag})
|
||||||
|
#set($hasKeyField = true)
|
||||||
|
#end
|
||||||
|
#if($!{field.keyFlag} && $!{field.propertyType} == "Long")
|
||||||
|
#set($hasLongKey = true)
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
#set($useTenantEntity = false)
|
||||||
|
#if($hasTenant && "$!superEntityClass" == "BaseEntity")
|
||||||
|
#set($useTenantEntity = true)
|
||||||
|
#end
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
|
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -16,8 +34,20 @@
|
||||||
package $!{package.Entity};
|
package $!{package.Entity};
|
||||||
|
|
||||||
#foreach($pkg in $!{table.importPackages})
|
#foreach($pkg in $!{table.importPackages})
|
||||||
|
#if($useTenantEntity && $!{pkg} == "org.springblade.core.mp.base.BaseEntity")
|
||||||
|
import org.springblade.core.mp.base.TenantEntity;
|
||||||
|
#elseif($!{pkg} == "java.io.Serializable" && $!{superEntityClass} && !$!{activeRecord})
|
||||||
|
## 继承父类时不直接实现 Serializable,跳过强制注入的导入
|
||||||
|
#elseif(!$hasKeyField && ($!{pkg} == "com.baomidou.mybatisplus.annotation.IdType" || $!{pkg} == "com.baomidou.mybatisplus.annotation.TableId"))
|
||||||
|
## 主键列归入公共字段时实体内不产出主键注解,跳过全局 idType 注入的导入
|
||||||
|
#else
|
||||||
import $!{pkg};
|
import $!{pkg};
|
||||||
#end
|
#end
|
||||||
|
#end
|
||||||
|
#if($hasLongKey)
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
#end
|
||||||
#if($!{entityLombokModel})
|
#if($!{entityLombokModel})
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
@ -46,7 +76,7 @@ import java.io.Serial;
|
||||||
@Schema(description = #if ("$!{table.comment}"=="")"$!{entity}对象"#else"$!{table.comment}"#end)
|
@Schema(description = #if ("$!{table.comment}"=="")"$!{entity}对象"#else"$!{table.comment}"#end)
|
||||||
#end
|
#end
|
||||||
#if($!{superEntityClass})
|
#if($!{superEntityClass})
|
||||||
public class $!{entity} extends $!{superEntityClass}#if($!{activeRecord})<$!{entity}>#end {
|
public class $!{entity} extends #if($useTenantEntity)TenantEntity#{else}$!{superEntityClass}#end#if($!{activeRecord})<$!{entity}>#end {
|
||||||
#elseif($!{activeRecord})
|
#elseif($!{activeRecord})
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class $!{entity} extends Model<$!{entity}> {
|
public class $!{entity} extends Model<$!{entity}> {
|
||||||
|
|
@ -59,7 +89,7 @@ public class $!{entity} implements Serializable {
|
||||||
|
|
||||||
## ---------- BEGIN 字段循环遍历 ----------
|
## ---------- BEGIN 字段循环遍历 ----------
|
||||||
#foreach($field in $!{table.fields})
|
#foreach($field in $!{table.fields})
|
||||||
#if($!{field.name}!=$!{tenantColumn})
|
#if($!{field.name} != $!{tenantColumn} || !$useTenantEntity)
|
||||||
#if($!{field.keyFlag})
|
#if($!{field.keyFlag})
|
||||||
#set($keyPropertyName=$!{field.propertyName})
|
#set($keyPropertyName=$!{field.propertyName})
|
||||||
#end
|
#end
|
||||||
|
|
@ -80,6 +110,9 @@ public class $!{entity} implements Serializable {
|
||||||
#elseif($!{field.convert})
|
#elseif($!{field.convert})
|
||||||
@TableId("$!{field.name}")
|
@TableId("$!{field.name}")
|
||||||
#end
|
#end
|
||||||
|
#if($!{field.propertyType} == "Long")
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
#end
|
||||||
## 普通字段
|
## 普通字段
|
||||||
#elseif($!{field.fill})
|
#elseif($!{field.fill})
|
||||||
## ----- 存在字段填充设置 -----
|
## ----- 存在字段填充设置 -----
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package $!{package.Mapper};
|
package $!{package.Mapper};
|
||||||
|
|
||||||
|
import $!{superMapperClassPackage};
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import $!{package.Entity}.$!{entity};
|
import $!{package.Entity}.$!{entity};
|
||||||
#set($voPackage=$package.Entity.replace("entity","vo"))
|
#set($voPackage=$package.Entity.replace("entity","vo"))
|
||||||
import $!{voPackage}.$!{entity}VO;
|
import $!{voPackage}.$!{entity}VO;
|
||||||
import $!{superMapperClassPackage};
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,13 @@
|
||||||
|
#set($hasTenant = false)
|
||||||
|
#foreach($field in $!{table.fields})
|
||||||
|
#if($!{field.name} == $!{tenantColumn})
|
||||||
|
#set($hasTenant = true)
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
#set($useTenantEntity = false)
|
||||||
|
#if($hasTenant && "$!superEntityClass" == "BaseEntity")
|
||||||
|
#set($useTenantEntity = true)
|
||||||
|
#end
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="$!{package.Mapper}.$!{table.mapperName}">
|
<mapper namespace="$!{package.Mapper}.$!{table.mapperName}">
|
||||||
|
|
@ -19,7 +29,7 @@
|
||||||
<result column="$!{field.name}" property="$!{field.propertyName}"/>
|
<result column="$!{field.name}" property="$!{field.propertyName}"/>
|
||||||
#end
|
#end
|
||||||
#foreach($field in $!{table.fields})
|
#foreach($field in $!{table.fields})
|
||||||
#if(!$!{field.keyFlag} && $!{field.name} != $!{tenantColumn})##生成普通字段
|
#if(!$!{field.keyFlag} && ($!{field.name} != $!{tenantColumn} || !$useTenantEntity))##生成普通字段
|
||||||
<result column="$!{field.name}" property="$!{field.propertyName}"/>
|
<result column="$!{field.name}" property="$!{field.propertyName}"/>
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
|
@ -28,7 +38,11 @@
|
||||||
#end
|
#end
|
||||||
|
|
||||||
<select id="select$!{entity}Page" resultMap="$!{table.entityPath}ResultMap">
|
<select id="select$!{entity}Page" resultMap="$!{table.entityPath}ResultMap">
|
||||||
|
#if($!{superEntityClass})
|
||||||
select * from $!{table.name} where is_deleted = 0
|
select * from $!{table.name} where is_deleted = 0
|
||||||
|
#else
|
||||||
|
select * from $!{table.name}
|
||||||
|
#end
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package $!{package.Service};
|
package $!{package.Service};
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import $!{superServiceClassPackage};
|
||||||
import $!{package.Entity}.$!{entity};
|
import $!{package.Entity}.$!{entity};
|
||||||
#set($voPackage=$package.Entity.replace("entity","vo"))
|
#set($voPackage=$package.Entity.replace("entity","vo"))
|
||||||
import $!{voPackage}.$!{entity}VO;
|
import $!{voPackage}.$!{entity}VO;
|
||||||
import $!{superServiceClassPackage};
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* $!{table.comment} 服务类
|
* $!{table.comment} 服务类
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,14 @@
|
||||||
*/
|
*/
|
||||||
package $!{package.ServiceImpl};
|
package $!{package.ServiceImpl};
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import $!{superServiceImplClassPackage};
|
||||||
import $!{package.Entity}.$!{entity};
|
import $!{package.Entity}.$!{entity};
|
||||||
#set($voPackage=$package.Entity.replace("entity","vo"))
|
#set($voPackage=$package.Entity.replace("entity","vo"))
|
||||||
import $!{voPackage}.$!{entity}VO;
|
import $!{voPackage}.$!{entity}VO;
|
||||||
import $!{package.Mapper}.$!{table.mapperName};
|
import $!{package.Mapper}.$!{table.mapperName};
|
||||||
import $!{package.Service}.$!{table.serviceName};
|
import $!{package.Service}.$!{table.serviceName};
|
||||||
import $!{superServiceImplClassPackage};
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* $!{table.comment} 服务实现类
|
* $!{table.comment} 服务实现类
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
#set($wrapperPackage=$package.Entity.replace("pojo.entity","wrapper"))
|
#set($wrapperPackage=$package.Entity.replace("pojo.entity","wrapper"))
|
||||||
package $!{wrapperPackage};
|
package $!{wrapperPackage};
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.springblade.core.mp.support.BaseEntityWrapper;
|
import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||||
import org.springblade.core.tool.utils.BeanUtil;
|
import org.springblade.core.tool.utils.BeanUtil;
|
||||||
import $!{package.Entity}.$!{entity};
|
import $!{package.Entity}.$!{entity};
|
||||||
|
|
@ -29,11 +28,11 @@ import $!{voPackage}.$!{entity}VO;
|
||||||
* @author $!{author}
|
* @author $!{author}
|
||||||
* @since $!{date}
|
* @since $!{date}
|
||||||
*/
|
*/
|
||||||
public class $!{entity}Wrapper extends BaseEntityWrapper<$!{entity}, $!{entity}VO> {
|
public class $!{entity}Wrapper extends BaseEntityWrapper<$!{entity}, $!{entity}VO> {
|
||||||
|
|
||||||
public static $!{entity}Wrapper build() {
|
public static $!{entity}Wrapper build() {
|
||||||
return new $!{entity}Wrapper();
|
return new $!{entity}Wrapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public $!{entity}VO entityVO($!{entity} $!{table.entityPath}) {
|
public $!{entity}VO entityVO($!{entity} $!{table.entityPath}) {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,23 @@
|
||||||
|
#set($hasTenant = false)
|
||||||
|
#foreach($field in $!{table.fields})
|
||||||
|
#if($!{field.name} == $!{tenantColumn})
|
||||||
|
#set($hasTenant = true)
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
#set($useTenantEntity = false)
|
||||||
|
#if($hasTenant && "$!superEntityClass" == "BaseEntity")
|
||||||
|
#set($useTenantEntity = true)
|
||||||
|
#end
|
||||||
<template>
|
<template>
|
||||||
<basic-container>
|
<basic-container>
|
||||||
<avue-crud :option="option"
|
<avue-crud :option="option"
|
||||||
:table-loading="loading"
|
:table-loading="loading"
|
||||||
:data="data"
|
:data="data"
|
||||||
ref="crudRef"
|
:page="page"
|
||||||
v-model="form"
|
|
||||||
v-model:search="search"
|
|
||||||
v-model:page="page"
|
|
||||||
:permission="permissionList"
|
:permission="permissionList"
|
||||||
:before-open="beforeOpen"
|
:before-open="beforeOpen"
|
||||||
|
v-model="form"
|
||||||
|
ref="crudRef"
|
||||||
@row-update="rowUpdate"
|
@row-update="rowUpdate"
|
||||||
@row-save="rowSave"
|
@row-save="rowSave"
|
||||||
@row-del="rowDel"
|
@row-del="rowDel"
|
||||||
|
|
@ -17,6 +26,7 @@
|
||||||
@selection-change="selectionChange"
|
@selection-change="selectionChange"
|
||||||
@current-change="currentChange"
|
@current-change="currentChange"
|
||||||
@size-change="sizeChange"
|
@size-change="sizeChange"
|
||||||
|
@refresh-change="refreshChange"
|
||||||
@on-load="onLoad">
|
@on-load="onLoad">
|
||||||
<template #menu-left>
|
<template #menu-left>
|
||||||
<el-button type="danger"
|
<el-button type="danger"
|
||||||
|
|
@ -41,8 +51,8 @@ import { validData } from '@/utils/util';
|
||||||
interface $!{entity}Entity {
|
interface $!{entity}Entity {
|
||||||
id: string;
|
id: string;
|
||||||
#foreach($field in $!{table.fields})
|
#foreach($field in $!{table.fields})
|
||||||
#if($!{field.name}!=$!{tenantColumn})
|
#if($!{field.name} != $!{tenantColumn} || !$useTenantEntity)
|
||||||
#if($!{field.propertyName} != "id")
|
#if(!$!{field.keyFlag})
|
||||||
#if($!{field.propertyType} == "Integer" || $!{field.propertyType} == "Long" || $!{field.propertyType} == "Short" || $!{field.propertyType} == "Byte" || $!{field.propertyType} == "Double" || $!{field.propertyType} == "Float" || $!{field.propertyType} == "BigDecimal")
|
#if($!{field.propertyType} == "Integer" || $!{field.propertyType} == "Long" || $!{field.propertyType} == "Short" || $!{field.propertyType} == "Byte" || $!{field.propertyType} == "Double" || $!{field.propertyType} == "Float" || $!{field.propertyType} == "BigDecimal")
|
||||||
$!{field.propertyName}?: number;
|
$!{field.propertyName}?: number;
|
||||||
#elseif($!{field.propertyType} == "Boolean")
|
#elseif($!{field.propertyType} == "Boolean")
|
||||||
|
|
@ -65,14 +75,13 @@ const permission = computed(() => store.getters.permission);
|
||||||
// 表格实例与数据状态
|
// 表格实例与数据状态
|
||||||
const crudRef = ref();
|
const crudRef = ref();
|
||||||
const form = ref<$!{entity}Form>({});
|
const form = ref<$!{entity}Form>({});
|
||||||
const search = ref<Partial<$!{entity}Entity>>({});
|
|
||||||
const data = ref<$!{entity}Entity[]>([]);
|
const data = ref<$!{entity}Entity[]>([]);
|
||||||
const selectionList = ref<$!{entity}Entity[]>([]);
|
const selectionList = ref<$!{entity}Entity[]>([]);
|
||||||
const query = ref<Partial<$!{entity}Entity>>({});
|
const query = ref<Partial<$!{entity}Entity>>({});
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
// 分页参数(整体替换,需用 ref)
|
// 分页参数
|
||||||
const page = ref({
|
const page = reactive({
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
|
|
@ -85,16 +94,17 @@ const ids = computed(() => selectionList.value.map(ele => ele.id).join(','));
|
||||||
const option = reactive({
|
const option = reactive({
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
calcHeight: 210,
|
calcHeight: 210,
|
||||||
|
tip: false,
|
||||||
searchShow: true,
|
searchShow: true,
|
||||||
searchMenuSpan: 6,
|
searchMenuSpan: 6,
|
||||||
tip: false,
|
|
||||||
border: true,
|
border: true,
|
||||||
index: true,
|
index: true,
|
||||||
viewBtn: true,
|
viewBtn: true,
|
||||||
selection: true,
|
selection: true,
|
||||||
column: [
|
column: [
|
||||||
#foreach($field in $!{table.fields})
|
#foreach($field in $!{table.fields})
|
||||||
#if($!{field.name}!=$!{tenantColumn})
|
#if($!{field.name} != $!{tenantColumn} || !$useTenantEntity)
|
||||||
|
#if(!$!{field.keyFlag})
|
||||||
{
|
{
|
||||||
label: '$!{field.comment}',
|
label: '$!{field.comment}',
|
||||||
prop: '$!{field.propertyName}',
|
prop: '$!{field.propertyName}',
|
||||||
|
|
@ -105,6 +115,7 @@ const option = reactive({
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
#end
|
#end
|
||||||
|
#end
|
||||||
#end
|
#end
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
@ -121,9 +132,9 @@ const permissionList = computed(() => ({
|
||||||
const onLoad = (pageData: { currentPage: number; pageSize: number }, params: Partial<$!{entity}Entity> = {}) => {
|
const onLoad = (pageData: { currentPage: number; pageSize: number }, params: Partial<$!{entity}Entity> = {}) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
getList(pageData.currentPage, pageData.pageSize, Object.assign(params, query.value)).then(res => {
|
getList(pageData.currentPage, pageData.pageSize, Object.assign(params, query.value)).then(res => {
|
||||||
const list = res.data.data;
|
const listData = res.data.data;
|
||||||
page.value.total = list.total;
|
page.total = listData.total;
|
||||||
data.value = list.records;
|
data.value = listData.records;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
selectionClear();
|
selectionClear();
|
||||||
});
|
});
|
||||||
|
|
@ -132,25 +143,30 @@ const onLoad = (pageData: { currentPage: number; pageSize: number }, params: Par
|
||||||
// 条件检索
|
// 条件检索
|
||||||
const searchChange = (params: Partial<$!{entity}Entity>, done: () => void) => {
|
const searchChange = (params: Partial<$!{entity}Entity>, done: () => void) => {
|
||||||
query.value = params;
|
query.value = params;
|
||||||
page.value.currentPage = 1;
|
page.currentPage = 1;
|
||||||
onLoad(page.value, params);
|
onLoad(page, params);
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置检索条件
|
// 重置检索条件
|
||||||
const searchReset = () => {
|
const searchReset = () => {
|
||||||
query.value = {};
|
query.value = {};
|
||||||
onLoad(page.value);
|
onLoad(page);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换页码
|
// 切换页码
|
||||||
const currentChange = (currentPage: number) => {
|
const currentChange = (currentPage: number) => {
|
||||||
page.value.currentPage = currentPage;
|
page.currentPage = currentPage;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 调整每页条数
|
// 调整每页条数
|
||||||
const sizeChange = (pageSize: number) => {
|
const sizeChange = (pageSize: number) => {
|
||||||
page.value.pageSize = pageSize;
|
page.pageSize = pageSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 刷新当前列表
|
||||||
|
const refreshChange = () => {
|
||||||
|
onLoad(page, query.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 记录当前选中行
|
// 记录当前选中行
|
||||||
|
|
@ -168,7 +184,7 @@ const selectionClear = () => {
|
||||||
const rowSave = (row: $!{entity}Form, done: () => void, loading: () => void) => {
|
const rowSave = (row: $!{entity}Form, done: () => void, loading: () => void) => {
|
||||||
add(row).then(() => {
|
add(row).then(() => {
|
||||||
done();
|
done();
|
||||||
onLoad(page.value);
|
onLoad(page);
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: '操作成功!',
|
message: '操作成功!',
|
||||||
|
|
@ -183,7 +199,7 @@ const rowSave = (row: $!{entity}Form, done: () => void, loading: () => void) =>
|
||||||
const rowUpdate = (row: $!{entity}Form, index: number, done: () => void, loading: () => void) => {
|
const rowUpdate = (row: $!{entity}Form, index: number, done: () => void, loading: () => void) => {
|
||||||
update(row).then(() => {
|
update(row).then(() => {
|
||||||
done();
|
done();
|
||||||
onLoad(page.value);
|
onLoad(page);
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: '操作成功!',
|
message: '操作成功!',
|
||||||
|
|
@ -205,7 +221,7 @@ const rowDel = (row: $!{entity}Entity) => {
|
||||||
return remove(row.id);
|
return remove(row.id);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onLoad(page.value);
|
onLoad(page);
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: '操作成功!',
|
message: '操作成功!',
|
||||||
|
|
@ -228,7 +244,7 @@ const handleDelete = () => {
|
||||||
return remove(ids.value);
|
return remove(ids.value);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onLoad(page.value);
|
onLoad(page);
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: '操作成功!',
|
message: '操作成功!',
|
||||||
|
|
@ -237,7 +253,7 @@ const handleDelete = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开弹窗前置处理:编辑与查看回填详情
|
// 打开弹窗前加载编辑/查看详情
|
||||||
const beforeOpen = (done: () => void, type: string) => {
|
const beforeOpen = (done: () => void, type: string) => {
|
||||||
if (['edit', 'view'].includes(type)) {
|
if (['edit', 'view'].includes(type)) {
|
||||||
getDetail(form.value.id).then(res => {
|
getDetail(form.value.id).then(res => {
|
||||||
|
|
|
||||||
|
|
@ -652,7 +652,7 @@ import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl;
|
||||||
|
|
||||||
**⑥ 安全放行(springdoc 路径)**:springdoc UI 与规格端点需匿名可达。框架默认放行清单 `blade-core-secure` 的 `SecureRegistry.defaultExcludePatterns` 已含 `/v3/api-docs/**`、`/swagger-ui/**`、`/swagger-ui.html`(`/swagger-ui/**` 为 UI 静态资源,`/swagger-ui.html` 为跳转入口),下游 servlet 应用继承即可。若应用另有自建放行清单(Boot 单体的 `BladeConfiguration`、Cloud 网关的 `AuthProvider`),须同步补齐这三项,并去掉 `/doc.html`、`/swagger-resources/**` 等 knife4j / springfox 旧路径。
|
**⑥ 安全放行(springdoc 路径)**:springdoc UI 与规格端点需匿名可达。框架默认放行清单 `blade-core-secure` 的 `SecureRegistry.defaultExcludePatterns` 已含 `/v3/api-docs/**`、`/swagger-ui/**`、`/swagger-ui.html`(`/swagger-ui/**` 为 UI 静态资源,`/swagger-ui.html` 为跳转入口),下游 servlet 应用继承即可。若应用另有自建放行清单(Boot 单体的 `BladeConfiguration`、Cloud 网关的 `AuthProvider`),须同步补齐这三项,并去掉 `/doc.html`、`/swagger-resources/**` 等 knife4j / springfox 旧路径。
|
||||||
|
|
||||||
**⑦ 代码生成器模板**:`blade-starter-develop/src/main/resources/templates/controller.java.vm` 里的 `@ApiOperationSupport` import 与注解一并删除——它是 `.vm` 资源,下游按 `*.java` 批量清理的脚本覆盖不到,须单独处理,否则该生成器产出的 Controller 引用已移除的 knife4j 包、编译不过(排序改用 §9⑧ 的 `@ApiOrder`)。删干净后**还须在模板 `@Tag` 上方补一行无参 `@ApiOrder` + 其 import**(`org.springblade.core.swagger.annotation.ApiOrder`),让生成的 Controller 与手写控制器一致带排序(见下「落地约定」);下游 Boot(`src/{main,test}/resources/templates/`)、Cloud(`blade-ops/blade-develop/src/{main,test}/resources/templates/`)的同名模板副本一并补。
|
**⑦ 代码生成器模板**:`blade-starter-develop/src/main/resources/templates/controller.java.vm` 里的 `@ApiOperationSupport` import 与注解一并删除——它是 `.vm` 资源,下游按 `*.java` 批量清理的脚本覆盖不到,须单独处理,否则该生成器产出的 Controller 引用已移除的 knife4j 包、编译不过(排序改用 §9⑧ 的 `@ApiOrder`)。删干净后**还须在模板 `@Tag` 上方补一行无参 `@ApiOrder` + 其 import**(`org.springblade.core.swagger.annotation.ApiOrder`),让生成的 Controller 与手写控制器一致带排序(见下「落地约定」);下游 Boot(`src/main/resources/templates/`)、Cloud(`blade-ops/blade-develop/src/main/resources/templates/`)的同名模板副本一并补。`src/test/resources/templates/` 下的旧扁平路径 `.vm` 副本自模板重组为 `api/` 子目录后不再被加载,应直接删除,test 侧仅保留 `code.properties` 作为数据源配置覆盖点。
|
||||||
|
|
||||||
**⑧ 接口排序:`@ApiOrder`(替代 knife4j 的 `@ApiOperationSupport(order = n)`)**
|
**⑧ 接口排序:`@ApiOrder`(替代 knife4j 的 `@ApiOperationSupport(order = n)`)**
|
||||||
|
|
||||||
|
|
@ -664,7 +664,7 @@ springdoc / OpenAPI 3 无接口排序原生注解,blade-starter-swagger 新增 `
|
||||||
|
|
||||||
实现在 `SwaggerAutoConfiguration`:`GlobalOperationCustomizer` 读注解写入 `x-order` 扩展(类级用字节码行号取声明序,反射不保证方法顺序)并记录「tag → 类级序号」;`GlobalOpenApiCustomizer` 按 `x-order` 重排 `paths`、按类级序号重排顶层 `tags`。swagger-ui 仅在两个 sorter 未设时保留 spec 顺序(§9⑤),并须保持 `springdoc.writer-with-order-by-keys` 默认 `false`。
|
实现在 `SwaggerAutoConfiguration`:`GlobalOperationCustomizer` 读注解写入 `x-order` 扩展(类级用字节码行号取声明序,反射不保证方法顺序)并记录「tag → 类级序号」;`GlobalOpenApiCustomizer` 按 `x-order` 重排 `paths`、按类级序号重排顶层 `tags`。swagger-ui 仅在两个 sorter 未设时保留 spec 顺序(§9⑤),并须保持 `springdoc.writer-with-order-by-keys` 默认 `false`。
|
||||||
|
|
||||||
**落地约定(本次统一)**:三工程**每个带 `@Tag` 的控制器**均在 `@Tag` 上方补一行**无参 `@ApiOrder`**(值取默认 `Integer.MAX_VALUE`),方法体只保留 `@Operation`——效果是分组内接口按**源码声明顺序**展示、各 `@Tag` 分组之间因类级序号相等而回落 tag 名字母序;仅当个别接口要打破源码顺序时,才在该方法上补 `@ApiOrder(n)` 精确覆盖。删 knife4j 排序注解(§3.1 / §4.2)后**只删不补**会让接口在 springdoc UI 下按字母序、丢失原有顺序,故「删旧 + 补 `@ApiOrder`」是同一件事的两半。代码生成器模板 `controller.java.vm`(blade-tool `blade-starter-develop` 源模板 + 下游 Boot/Cloud `blade-develop` 的 main/test 副本,共 5 份)同步在 `@Tag` 上方生成 `@ApiOrder`,新生成的 Controller 开箱即带排序。
|
**落地约定(本次统一)**:三工程**每个带 `@Tag` 的控制器**均在 `@Tag` 上方补一行**无参 `@ApiOrder`**(值取默认 `Integer.MAX_VALUE`),方法体只保留 `@Operation`——效果是分组内接口按**源码声明顺序**展示、各 `@Tag` 分组之间因类级序号相等而回落 tag 名字母序;仅当个别接口要打破源码顺序时,才在该方法上补 `@ApiOrder(n)` 精确覆盖。删 knife4j 排序注解(§3.1 / §4.2)后**只删不补**会让接口在 springdoc UI 下按字母序、丢失原有顺序,故「删旧 + 补 `@ApiOrder`」是同一件事的两半。代码生成器模板 `controller.java.vm`(blade-tool `blade-starter-develop` 源模板 + 下游 Boot/Cloud `blade-develop` 的 main 副本,共 3 份)同步在 `@Tag` 上方生成 `@ApiOrder`,新生成的 Controller 开箱即带排序。
|
||||||
|
|
||||||
> 提示:类级 `@ApiOrder(n)` 的 `value` 用作 `@Tag` 分组之间的排序序号(`SwaggerAutoConfiguration#recordTagOrder` 写入、`GlobalOpenApiCustomizer` 据此重排顶层 `tags`);分组内各接口不受此值影响,按源码声明顺序排列。本次全量采用**无参**形式,`value` 恒为默认值、各分组落 tag 名字母序;如需固定分组顺序,给对应控制器标 `@ApiOrder(n)` 即可。
|
> 提示:类级 `@ApiOrder(n)` 的 `value` 用作 `@Tag` 分组之间的排序序号(`SwaggerAutoConfiguration#recordTagOrder` 写入、`GlobalOpenApiCustomizer` 据此重排顶层 `tags`);分组内各接口不受此值影响,按源码声明顺序排列。本次全量采用**无参**形式,`value` 恒为默认值、各分组落 tag 名字母序;如需固定分组顺序,给对应控制器标 `@ApiOrder(n)` 即可。
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user