원클릭으로
crud-generator
// 为 mfish-nocode-pro 项目生成标准增删改查(CRUD)代码,包括 Entity、Req、Mapper、Service、ServiceImpl、Controller 六层结构。当用户说"帮我生成增删改查"、"新增一个模块"、"生成CRUD代码"时使用此 skill。
// 为 mfish-nocode-pro 项目生成标准增删改查(CRUD)代码,包括 Entity、Req、Mapper、Service、ServiceImpl、Controller 六层结构。当用户说"帮我生成增删改查"、"新增一个模块"、"生成CRUD代码"时使用此 skill。
为 mfish-nocode-pro 项目前端(Vue3 + TypeScript)生成标准增删改查页面代码,包括 Model、API、data、index.vue、Modal、ViewModal 六个文件。当用户说"帮我生成前端增删改查"、"新增前端页面"、"生成前端CRUD"时使用此 skill。
为 mfish-nocode-pro 项目集成 Flowable 工作流审批能力,包括注册 FlowKey、实体审批状态字段、Service 启动/撤回流程、Controller 审批回调接口、Feign 回调接口注册五个步骤的完整代码模板。当用户说"带工作流审批"、"发布审核流程"、"集成工作流"、"审批回调"时使用此 skill。
| name | crud-generator |
| description | 为 mfish-nocode-pro 项目生成标准增删改查(CRUD)代码,包括 Entity、Req、Mapper、Service、ServiceImpl、Controller 六层结构。当用户说"帮我生成增删改查"、"新增一个模块"、"生成CRUD代码"时使用此 skill。 |
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
BaseMapper<T>、ServiceImpl<M,T>、IService<T>)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>@Tag、@Operation、@Parameter@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)@Schema 注解(类级和字段级)询问用户(如未提供):
sys:order)cn.com.mfish.sys)按以下顺序生成,文件路径基于 mf-business/mf-xxx/src/main/java/ 目录。
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;
// 有 Date 类型字段时引入:
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
// 有 BigDecimal 类型字段时引入:
import java.math.BigDecimal;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.4.0
*/
@Data
@TableName("{表名}")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "{表名}对象 {中文名称}")
public class {类名} extends BaseEntity<String> {
// String UUID 主键:
@ExcelProperty("唯一ID")
@Schema(description = "唯一ID")
@TableId(type = IdType.ASSIGN_UUID)
@Accessors(chain = true)
private String id;
// 数值自增主键(替换上面的 id 声明):
// @TableId(type = IdType.AUTO)
// @Accessors(chain = true)
// private Integer id;
// 普通字段:
@ExcelProperty("{字段注释}")
@Schema(description = "{字段注释}")
private {类型} {字段名};
// 日期字段(DATE 类型):
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@ExcelProperty("{字段注释}")
@Schema(description = "{字段注释}")
private Date {字段名};
// 日期时间字段(DATETIME 类型):
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty("{字段注释}")
@Schema(description = "{字段注释}")
private Date {字段名};
}
package {包路径}.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
// 有 Date 类型搜索字段时引入:
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.4.0
*/
@Data
@Accessors(chain = true)
@Schema(description = "{中文名称}请求参数")
public class Req{类名} {
// 普通搜索字段:
@Schema(description = "{字段注释}")
private {类型} {字段名};
// DATE 类型搜索字段:
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "{字段注释}")
private Date {字段名};
// DATETIME 类型搜索字段:
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "{字段注释}")
private Date {字段名};
}
package {包路径}.mapper;
import {包路径}.entity.{类名};
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.4.0
*/
public interface {类名}Mapper extends BaseMapper<{类名}> {
// 如有复杂查询,声明自定义方法,并对应 XML
}
对应 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>
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;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.4.0
*/
public interface {类名}Service extends IService<{类名}> {
/** 分页列表查询 */
Result<PageResult<{类名}>> queryPageList(Req{类名} req{类名}, ReqPage reqPage);
/** 添加 */
Result<{类名}> add({类名} {变量名});
/** 编辑 */
Result<{类名}> edit({类名} {变量名});
/** 通过id删除 */
Result<Boolean> delete(String id);
/** 批量删除(ids 逗号分隔) */
Result<Boolean> deleteBatch(String ids);
/** 通过id查询 */
Result<{类名}> queryById(String id);
/** 导出 */
void export(Req{类名} req{类名}, ReqPage reqPage) throws IOException;
}
若 ID 为数值型(如
Integer),将String id改为对应类型。
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;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.4.0
*/
@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<{类名}>()
// String 类型字段用 StringUtils.isEmpty 判断:
.like(!StringUtils.isEmpty(req{类名}.get{字段}()), {类名}::get{字段}, req{类名}.get{字段}())
// 非 String 类型字段用 null != xxx 判断:
.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 {
// swagger 调用有问题,使用 postman 测试
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())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;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.4.0
*/
@Slf4j
@Tag(name = "{中文名称}")
@RestController
@RequestMapping("/{变量名}")
public class {类名}Controller {
@Resource
private {类名}Service {变量名}Service;
/**
* 分页列表查询
*/
@Operation(summary = "{中文名称}-分页列表查询", description = "{中文名称}-分页列表查询")
@GetMapping
@RequiresPermissions("{权限前缀}:query")
// 需要数据权限时加:@DataScope(table = "{表名}", type = DataScopeType.Tenant)
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({变量名});
}
/**
* 通过id删除
*/
@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);
}
/**
* 通过id查询
*/
@Operation(summary = "{中文名称}-通过id查询")
@GetMapping("/{id}")
@RequiresPermissions("{权限前缀}:query")
// 需要数据权限时加:@DataScope(table = "{表名}", type = DataScopeType.Tenant)
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 |
return xxxService.方法() 即可,业务实现全在 ServiceImplverifyXxx() 私有方法,并在 add/edit 中调用@Transactional 处理级联操作resources/mapper/ 下创建对应 XML 文件mf-business/mf-xxx 子模块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@Schema 注解,包括:
@Schema(description = "描述信息", name = "类名")@Schema(description = "字段描述")MyRuntimeException 处理
import cn.com.mfish.common.core.exception.MyRuntimeException;// 数据不存在校验
if (entity == null) {
throw new MyRuntimeException("错误:记录不存在!");
}
// 重复性校验
if (baseMapper.exists(new LambdaQueryWrapper<Entity>()
.eq(Entity::getField, value))) {
throw new MyRuntimeException("错误:已存在,不能重复提交!");
}
// 状态校验
if (!"active".equals(entity.getStatus())) {
throw new MyRuntimeException("错误:记录状态不正确!");
}
"错误:" 开头,便于前端统一处理和识别RuntimeException 或其他自定义异常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, "错误:操作失败,请检查配置是否正确");
}
数据权限通过 @DataScope / @DataScopes 注解在 Controller 查询方法 上声明,框架自动在 SQL 中追加过滤条件。
重要约束
- 注解只能用于查询方法,不能用于新增/修改/删除
- 注解加在 Controller 层,不在 Service 层
- 表中须有对应的权限字段:租户
tenant_id、用户user_id、角色role_id、组织org_id
| 类型 | 说明 | 表中需要字段 |
|---|---|---|
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. 忽略条件(优先级最高,满足时其他权限条件全部失效)
// 变量值从 ServletRequest.getParameter 中取,为空时不使用忽略条件
@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 cn.com.mfish.common.oauth.annotation.DataScope;
import cn.com.mfish.common.oauth.annotation.DataScopes; // 多条件时引入
import cn.com.mfish.common.oauth.common.DataScopeType;