| name | crud-generator |
| description | 为 mfish-nocode-pro 项目生成标准增删改查(CRUD)代码,包括 Entity、Req、Mapper、Service、ServiceImpl、Controller 六层结构。当用户说"帮我生成增删改查"、"新增一个模块"、"生成CRUD代码"时使用此 skill。 |
CRUD 代码生成器
项目架构概览
mf-api/mf-xxx-api/
└── src/main/java/cn/com/mfish/xxx/api/entity/ # API 层实体(跨服务共享)
mf-business/mf-xxx/
└── src/main/java/cn/com/mfish/xxx/
├── controller/ # Controller 层
├── entity/ # 业务实体
├── mapper/ # Mapper 接口
├── req/ # 请求参数类
└── service/
├── XxxService.java
└── impl/XxxServiceImpl.java
技术栈约定
- ORM: MyBatis-Plus(
BaseMapper<T>、ServiceImpl<M,T>、IService<T>)
- 分页: PageHelper(
PageHelper.startPage(pageNum, pageSize))
- 权限:
@RequiresPermissions("模块:功能:操作")(insert/update/delete/query/export)
- 日志:
@Log(title = "xxx-操作", operateType = OperateType.INSERT/UPDATE/DELETE)
- 返回值:
Result<T>、Result<PageResult<T>>、Result<Boolean>
- 文档: SpringDoc
@Tag、@Operation、@Parameter
- ID类型: String UUID →
@TableId(type = IdType.ASSIGN_UUID);数值自增 → @TableId(type = IdType.AUTO)
- 基类:
BaseEntity<T>(含 id、createBy、createTime、updateBy、updateTime),T 与 ID 类型一致
- 导出:
ExcelUtils.write(fileName, list)(来自 cn.com.mfish.common.core.utils.excel.ExcelUtils)
- 字符串判空:
StringUtils.isEmpty()(来自 cn.com.mfish.common.core.utils.StringUtils)
- Swagger 注解: 所有 Entity、Req 类必须添加
@Schema 注解(类级和字段级)
生成步骤
第一步:收集信息
询问用户(如未提供):
- 模块名(如:order、product)
- 中文名称(如:订单、商品)
- 数据表名(如:sys_order)
- 字段列表:字段名、类型、中文描述、是否必填
- 权限前缀(如:
sys:order)
- 包路径(如:
cn.com.mfish.sys)
- 数据权限(可选):是否需要按租户/用户/角色/组织过滤数据,表中需有对应字段
第二步:生成各层代码
按以下顺序生成,文件路径基于 mf-business/mf-xxx/src/main/java/ 目录。
1. Entity 实体类
package {包路径}.entity;
import cn.com.mfish.common.core.entity.BaseEntity;
import cn.idev.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.math.BigDecimal;
@Data
@TableName("{表名}")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "{表名}对象 {中文名称}")
public class {类名} extends BaseEntity<String> {
@ExcelProperty("唯一ID")
@Schema(description = "唯一ID")
@TableId(type = IdType.ASSIGN_UUID)
@Accessors(chain = true)
private String id;
@ExcelProperty("{字段注释}")
@Schema(description = "{字段注释}")
private {类型} {字段名};
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@ExcelProperty("{字段注释}")
@Schema(description = "{字段注释}")
private Date {字段名};
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty("{字段注释}")
@Schema(description = "{字段注释}")
private Date {字段名};
}
2. Req 请求参数类
package {包路径}.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
@Accessors(chain = true)
@Schema(description = "{中文名称}请求参数")
public class Req{类名} {
@Schema(description = "{字段注释}")
private {类型} {字段名};
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "{字段注释}")
private Date {字段名};
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "{字段注释}")
private Date {字段名};
}
3. Mapper 接口 + XML
package {包路径}.mapper;
import {包路径}.entity.{类名};
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface {类名}Mapper extends BaseMapper<{类名}> {
}
对应 XML 文件(resources/mapper/{类名}Mapper.xml):
<?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="{包路径}.mapper.{类名}Mapper">
</mapper>
4. Service 接口
package {包路径}.service;
import cn.com.mfish.common.core.web.PageResult;
import cn.com.mfish.common.core.web.ReqPage;
import cn.com.mfish.common.core.web.Result;
import {包路径}.entity.{类名};
import {包路径}.req.Req{类名};
import com.baomidou.mybatisplus.extension.service.IService;
import java.io.IOException;
public interface {类名}Service extends IService<{类名}> {
Result<PageResult<{类名}>> queryPageList(Req{类名} req{类名}, ReqPage reqPage);
Result<{类名}> add({类名} {变量名});
Result<{类名}> edit({类名} {变量名});
Result<Boolean> delete(String id);
Result<Boolean> deleteBatch(String ids);
Result<{类名}> queryById(String id);
void export(Req{类名} req{类名}, ReqPage reqPage) throws IOException;
}
若 ID 为数值型(如 Integer),将 String id 改为对应类型。
5. ServiceImpl 实现类
package {包路径}.service.impl;
import cn.com.mfish.common.core.utils.StringUtils;
import cn.com.mfish.common.core.utils.excel.ExcelUtils;
import cn.com.mfish.common.core.web.PageResult;
import cn.com.mfish.common.core.web.ReqPage;
import cn.com.mfish.common.core.web.Result;
import {包路径}.entity.{类名};
import {包路径}.mapper.{类名}Mapper;
import {包路径}.req.Req{类名};
import {包路径}.service.{类名}Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@Service
public class {类名}ServiceImpl extends ServiceImpl<{类名}Mapper, {类名}> implements {类名}Service {
@Override
public Result<PageResult<{类名}>> queryPageList(Req{类名} req{类名}, ReqPage reqPage) {
return Result.ok(new PageResult<>(queryList(req{类名}, reqPage)), "{中文名称}-查询成功!");
}
private List<{类名}> queryList(Req{类名} req{类名}, ReqPage reqPage) {
PageHelper.startPage(reqPage.getPageNum(), reqPage.getPageSize());
LambdaQueryWrapper<{类名}> lambdaQueryWrapper = new LambdaQueryWrapper<{类名}>()
.like(!StringUtils.isEmpty(req{类名}.get{字段}()), {类名}::get{字段}, req{类名}.get{字段}())
.eq(null != req{类名}.get{字段}(), {类名}::get{字段}, req{类名}.get{字段}());
return list(lambdaQueryWrapper);
}
@Override
public Result<{类名}> add({类名} {变量名}) {
if (save({变量名})) {
return Result.ok({变量名}, "{中文名称}-添加成功!");
}
return Result.fail({变量名}, "错误:{中文名称}-添加失败!");
}
@Override
public Result<{类名}> edit({类名} {变量名}) {
if (updateById({变量名})) {
return Result.ok({变量名}, "{中文名称}-编辑成功!");
}
return Result.fail({变量名}, "错误:{中文名称}-编辑失败!");
}
@Override
public Result<Boolean> delete(String id) {
if (removeById(id)) {
return Result.ok(true, "{中文名称}-删除成功!");
}
return Result.fail(false, "错误:{中文名称}-删除失败!");
}
@Override
public Result<Boolean> deleteBatch(String ids) {
if (removeByIds(Arrays.asList(ids.split(",")))) {
return Result.ok(true, "{中文名称}-批量删除成功!");
}
return Result.fail(false, "错误:{中文名称}-批量删除失败!");
}
@Override
public Result<{类名}> queryById(String id) {
{类名} {变量名} = getById(id);
return Result.ok({变量名}, "{中文名称}-查询成功!");
}
@Override
public void export(Req{类名} req{类名}, ReqPage reqPage) throws IOException {
ExcelUtils.write("{中文名称}_" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()), queryList(req{类名}, reqPage));
}
}
搜索条件 LambdaQueryWrapper 规则:
String 类型字段 → .like(!StringUtils.isEmpty(req.getXxx()), Entity::getXxx, req.getXxx())(模糊) 或 .eq(!StringUtils.isEmpty(req.getXxx()), Entity::getXxx, req.getXxx())(精确)
- 非
String 类型字段 → .eq(null != req.getXxx(), Entity::getXxx, req.getXxx())
6. Controller 控制器
package {包路径}.controller;
import cn.com.mfish.common.core.enums.OperateType;
import cn.com.mfish.common.core.web.PageResult;
import cn.com.mfish.common.core.web.ReqPage;
import cn.com.mfish.common.core.web.Result;
import cn.com.mfish.common.log.annotation.Log;
import cn.com.mfish.common.oauth.annotation.RequiresPermissions;
import {包路径}.entity.{类名};
import {包路径}.req.Req{类名};
import {包路径}.service.{类名}Service;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@Slf4j
@Tag(name = "{中文名称}")
@RestController
@RequestMapping("/{变量名}")
public class {类名}Controller {
@Resource
private {类名}Service {变量名}Service;
@Operation(summary = "{中文名称}-分页列表查询", description = "{中文名称}-分页列表查询")
@GetMapping
@RequiresPermissions("{权限前缀}:query")
public Result<PageResult<{类名}>> queryPageList(Req{类名} req{类名}, ReqPage reqPage) {
return {变量名}Service.queryPageList(req{类名}, reqPage);
}
@Log(title = "{中文名称}-添加", operateType = OperateType.INSERT)
@Operation(summary = "{中文名称}-添加")
@PostMapping
@RequiresPermissions("{权限前缀}:insert")
public Result<{类名}> add(@RequestBody {类名} {变量名}) {
return {变量名}Service.add({变量名});
}
@Log(title = "{中文名称}-编辑", operateType = OperateType.UPDATE)
@Operation(summary = "{中文名称}-编辑")
@PutMapping
@RequiresPermissions("{权限前缀}:update")
public Result<{类名}> edit(@RequestBody {类名} {变量名}) {
return {变量名}Service.edit({变量名});
}
@Log(title = "{中文名称}-通过id删除", operateType = OperateType.DELETE)
@Operation(summary = "{中文名称}-通过id删除")
@DeleteMapping("/{id}")
@RequiresPermissions("{权限前缀}:delete")
public Result<Boolean> delete(@Parameter(name = "id", description = "唯一性ID") @PathVariable String id) {
return {变量名}Service.delete(id);
}
@Log(title = "{中文名称}-批量删除", operateType = OperateType.DELETE)
@Operation(summary = "{中文名称}-批量删除")
@DeleteMapping("/batch/{ids}")
@RequiresPermissions("{权限前缀}:delete")
public Result<Boolean> deleteBatch(@Parameter(name = "ids", description = "唯一性ID") @PathVariable String ids) {
return {变量名}Service.deleteBatch(ids);
}
@Operation(summary = "{中文名称}-通过id查询")
@GetMapping("/{id}")
@RequiresPermissions("{权限前缀}:query")
public Result<{类名}> queryById(@Parameter(name = "id", description = "唯一性ID") @PathVariable String id) {
return {变量名}Service.queryById(id);
}
@Operation(summary = "导出{中文名称}", description = "导出{中文名称}")
@GetMapping("/export")
@RequiresPermissions("{权限前缀}:export")
public void export(Req{类名} req{类名}, ReqPage reqPage) throws IOException {
{变量名}Service.export(req{类名}, reqPage);
}
}
Controller 核心原则:Controller 只做路由转发,所有业务逻辑在 Service 中实现,Controller 方法体直接 return xxxService.方法()。
命名约定
| 占位符 | 说明 | 示例 |
|---|
{类名} | PascalCase 类名 | SysOrder |
{变量名} | camelCase 变量名(也用作 @RequestMapping 路径) | sysOrder |
{表名} | 数据库表名(下划线) | sys_order |
{权限前缀} | 权限标识符({apiPrefix}:{变量名}) | sys:sysOrder |
{包路径} | Java 包名 | cn.com.mfish.sys |
注意事项
- 业务逻辑分层:Controller 只做路由转发,
return xxxService.方法() 即可,业务实现全在 ServiceImpl
- 有业务校验逻辑时:在 ServiceImpl 中提取
verifyXxx() 私有方法,并在 add/edit 中调用
- 有关联删除时:在 ServiceImpl 中用
@Transactional 处理级联操作
- 复杂查询:在 Mapper 中声明方法,在
resources/mapper/ 下创建对应 XML 文件
- 模块位置:根据功能归属放到对应的
mf-business/mf-xxx 子模块
- API 层实体:如需跨服务调用,将实体放到
mf-api/mf-xxx-api 模块
- 软删除场景:实体增加
delFlag 字段,删除时 updateById(new Xxx().setId(id).setDelFlag(1)),查询时 .eq(Xxx::getDelFlag, 0)
- 租户隔离场景:实体增加
tenantId 字段,Controller 的写操作加 @DataScope(table="表名", type=DataScopeType.Tenant) 注解,Service 中用 AuthInfoUtils.getCurrentTenantId() 写入,修改时 setTenantId(null) 避免覆盖
- 数值型主键:
BaseEntity<Integer>,@TableId(type = IdType.AUTO),Service/Controller 中 id 参数类型相应改为 Integer
- Swagger 注解强制要求:所有 Entity、Req 类必须添加
@Schema 注解,包括:
- 类级别:
@Schema(description = "描述信息", name = "类名")
- 字段级别:每个字段都需添加
@Schema(description = "字段描述")
- 便于生成完整的 API 文档和 Swagger UI 展示
- 异常处理规范:人为抛出的业务异常统一采用
MyRuntimeException 处理
- 安全规范 - 异常信息不暴露给前端:
- 核心原则:返回给前端的错误消息必须是友好的、通用的提示,不能包含具体的异常堆栈或技术细节
- 错误示例:
return Result.fail(false, "错误:配置不正确," + e.getMessage()); ❌
- 正确示例:
return Result.fail(false, "错误:配置不正确,请检查配置是否完整且符合规范"); ✅
- 日志记录:详细的异常信息应通过
log.error() 记录到日志文件中,便于开发人员排查
- 实现模式:
try {
someOperation();
} catch (Exception e) {
log.error("操作失败:{}", e.getMessage(), e);
return Result.fail(false, "错误:操作失败,请检查配置是否正确");
}
- 适用范围:所有 catch 块中返回给前端的错误消息都必须遵循此规范
数据权限控制
数据权限通过 @DataScope / @DataScopes 注解在 Controller 查询方法 上声明,框架自动在 SQL 中追加过滤条件。
重要约束
- 注解只能用于查询方法,不能用于新增/修改/删除
- 注解加在 Controller 层,不在 Service 层
- 表中须有对应的权限字段:租户
tenant_id、用户 user_id、角色 role_id、组织 org_id
DataScopeType 权限类型
| 类型 | 说明 | 表中需要字段 |
|---|
DataScopeType.Tenant | 按当前租户过滤 | tenant_id |
DataScopeType.User | 按当前用户过滤 | user_id |
DataScopeType.Role | 按当前角色过滤 | role_id |
DataScopeType.Org | 按当前组织及下级过滤 | org_id |
DataScopeType.None | 不过滤(默认) | — |
常用场景示例
1. 单表租户过滤(最常用)
@GetMapping
@RequiresPermissions("{权限前缀}:query")
@DataScope(table = "{表名}", type = DataScopeType.Tenant)
public Result<PageResult<{类名}>> queryPageList(Req{类名} req, ReqPage reqPage) { ... }
2. 单表组织过滤
@DataScope(table = "{表名}", type = DataScopeType.Org)
3. 固定角色值过滤(指定具体角色编码)
@DataScope(table = "{表名}", type = DataScopeType.Role, values = {"manage", "superAdmin"})
4. 排除公开数据(满足排除条件的记录不被过滤,始终可查)
@DataScope(table = "{表名}", type = DataScopeType.Tenant, excludes = "is_public=1")
5. 忽略条件(优先级最高,满足时其他权限条件全部失效)
@DataScope(table = "{表名}", ignores = "share_token=#{_shareToken} and share_end_time>=now()")
6. 多表组合权限(使用 @DataScopes)
import cn.com.mfish.common.oauth.annotation.DataScopes;
@DataScopes({
@DataScope(table = "{主表名}", type = DataScopeType.Tenant),
@DataScope(table = "{关联表名}", type = DataScopeType.Tenant, excludes = "is_public=1")
})
7. 租户 + 角色组合过滤
@DataScopes({
@DataScope(table = "{表名}", type = DataScopeType.Tenant),
@DataScope(table = "{表名}", type = DataScopeType.Role, values = {"manage", "superAdmin"})
})
所需 Import
import cn.com.mfish.common.oauth.annotation.DataScope;
import cn.com.mfish.common.oauth.annotation.DataScopes;
import cn.com.mfish.common.oauth.common.DataScopeType;
参考实现
相关参考