Skip to content

数据库字段与 CRUD 代码生成器实战

本文结合当前仓库的真实结构,说明两类常见开发任务:

  1. 在已有分页接口 crm/customer-limit-config/page 中增加“合同结束时间”字段。
  2. 新建一张业务表,并完成数据库、后端、前端、权限菜单的增删改查。

本文默认使用:

  • 后端:JDK 8、Spring Boot 2.7、MyBatis Plus
  • 数据库:MySQL 8
  • 后端模块:yudao-module-crm
  • 前端:yudao-ui/yudao-ui-admin-vue3,即 Vue3 + Element Plus
  • 后端端口:48080
  • 管理端接口前缀:/admin-api

注意:Controller 中写的是 /crm/customer-limit-config/page,浏览器实际请求通常是 /admin-api/crm/customer-limit-config/page/admin-api 由前端 Axios 配置和后端 Web 配置统一添加。

一、先理解这个项目的 CRUD 调用链

以客户限制配置分页为例:

text
前端页面 CustomerLimitConfigList.vue
  -> 前端 API src/api/crm/customer/limitConfig/index.ts
  -> CrmCustomerLimitConfigController
  -> CrmCustomerLimitConfigServiceImpl
  -> CrmCustomerLimitConfigMapper
  -> crm_customer_limit_config 表

当前相关文件如下:

这个项目大量使用同名属性自动转换:

java
BeanUtils.toBean(source, TargetClass.class)

因此数据库字段 contract_end_time、Java 字段 contractEndTime、前端字段 contractEndTime 必须保持同一语义和稳定命名。

二、已有分页接口增加“合同结束时间”

2.1 先确定需求范围

“增加一个字段”至少有三种不同范围:

需求必改位置
只让分页接口返回并在列表展示数据库、DO、RespVO、前端类型、前端列表
还要支持新增和编辑再改 SaveReqVO、前端表单
还要支持按结束时间筛选再改 PageReqVO、Mapper、前端查询区

下面给出完整方案,即支持返回、新增、编辑和时间范围筛选。只需要其中一部分时,可以按表格裁剪。

业务提醒:crm_customer_limit_config 是“客户数量限制配置”表,“合同结束时间”从业务语义上并不自然。 如果该字段实际属于合同,应优先放到 crm_contract.end_time。本文仍按你的示例演示技术操作。

2.2 编写数据库增量 SQL

不要只在 Navicat 中点一下“新增字段”就结束。数据库变化必须保存成 SQL 文件并提交到 Git,否则其他环境无法同步。

建议新增一个版本化脚本,例如:

text
sql/mysql/schema-change-2026-07-11-crm.sql

脚本内容:

sql
-- CRM 客户限制配置增加合同结束时间
ALTER TABLE `crm_customer_limit_config`
    ADD COLUMN `contract_end_time` datetime NULL DEFAULT NULL
        COMMENT '合同结束时间'
        AFTER `deal_count_enabled`;

先设计为可空字段,原因是旧数据没有该值。若业务要求必填,建议分三步上线:

sql
-- 1. 先增加可空字段
ALTER TABLE `crm_customer_limit_config`
    ADD COLUMN `contract_end_time` datetime NULL DEFAULT NULL COMMENT '合同结束时间';

-- 2. 根据真实业务补历史数据,不要直接照抄示例日期
UPDATE `crm_customer_limit_config`
SET `contract_end_time` = '2026-12-31 23:59:59'
WHERE `contract_end_time` IS NULL;

-- 3. 确认没有 NULL 后,再调整为非空
ALTER TABLE `crm_customer_limit_config`
    MODIFY COLUMN `contract_end_time` datetime NOT NULL COMMENT '合同结束时间';

执行增量脚本:

bash
mysql -h127.0.0.1 -P3306 -uroot -p ruoyi-vue-pro \
  < sql/mysql/schema-change-2026-07-11-crm.sql

执行后验证:

sql
SHOW COLUMNS FROM `crm_customer_limit_config` LIKE 'contract_end_time';

SELECT `id`, `type`, `contract_end_time`
FROM `crm_customer_limit_config`
ORDER BY `id` DESC
LIMIT 10;

如果项目仍使用以下初始化快照创建新环境,也要把字段补进快照中的建表语句:

  • sql/mysql/crm-2024-09-30.sql
  • sql/mysql/ruoyi-vue-pro.sql 中对应表结构

不要在已有数据库上重新执行整个初始化快照,只执行增量脚本。

回滚 SQL 为:

sql
ALTER TABLE `crm_customer_limit_config`
    DROP COLUMN `contract_end_time`;

回滚会丢失该列数据,只能在确认无需保留数据时执行。

2.3 修改 DO,让 MyBatis Plus 读取字段

修改 CrmCustomerLimitConfigDO.java,增加导入:

java
import java.time.LocalDateTime;

dealCountEnabled 后增加:

java
/**
 * 合同结束时间
 */
private LocalDateTime contractEndTime;

本项目开启了下划线转驼峰,Java 的 contractEndTime 会自动映射数据库的 contract_end_time,不需要额外写 @TableField("contract_end_time")

完成这一步后,Mapper 的普通查询已经可以从数据库读取该字段。

2.4 修改 RespVO,让分页接口返回字段

修改 CrmCustomerLimitConfigRespVO.java

java
@Schema(description = "合同结束时间")
private LocalDateTime contractEndTime;

Controller 当前使用:

java
BeanUtils.toBean(pageResult, CrmCustomerLimitConfigRespVO.class, ...)

DO 与 RespVO 字段同名、类型一致,因此 Controller 不需要增加手工赋值。

如果只要求分页接口返回字段,到这里后端核心改动已经完成。

2.5 支持新增和编辑

修改 CrmCustomerLimitConfigSaveReqVO.java,增加:

java
import java.time.LocalDateTime;
java
@Schema(description = "合同结束时间")
@DiffLogField(name = "合同结束时间")
private LocalDateTime contractEndTime;

如果必须填写,再增加:

java
@NotNull(message = "合同结束时间不能为空")

当前 Service 创建和更新都使用同名属性转换:

java
BeanUtils.toBean(createReqVO, CrmCustomerLimitConfigDO.class)
BeanUtils.toBean(updateReqVO, CrmCustomerLimitConfigDO.class)

所以 SaveReqVO 和 DO 增加同名字段后,Service 一般不需要改动。

2.6 支持分页时间范围筛选

时间字段通常不适合按某一秒精确相等查询,建议使用开始时间和结束时间组成的范围。

修改 CrmCustomerLimitConfigPageReqVO.java

java
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDateTime;

import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;

增加字段:

java
@Schema(description = "合同结束时间范围")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] contractEndTime;

修改 CrmCustomerLimitConfigMapper.java 的分页条件:

java
default PageResult<CrmCustomerLimitConfigDO> selectPage(CrmCustomerLimitConfigPageReqVO reqVO) {
    return selectPage(reqVO, new LambdaQueryWrapperX<CrmCustomerLimitConfigDO>()
            .eqIfPresent(CrmCustomerLimitConfigDO::getType, reqVO.getType())
            .betweenIfPresent(CrmCustomerLimitConfigDO::getContractEndTime,
                    reqVO.getContractEndTime())
            .orderByDesc(CrmCustomerLimitConfigDO::getId));
}

请求示例:

text
GET /admin-api/crm/customer-limit-config/page
    ?pageNo=1
    &pageSize=10
    &type=1
    &contractEndTime[0]=2026-07-01 00:00:00
    &contractEndTime[1]=2026-07-31 23:59:59

正常使用前端 Axios 提交数组即可,不建议手写拼接这个 URL。

2.7 修改前端 TypeScript 类型

修改前端 API 文件中的 CustomerLimitConfigVO

ts
export interface CustomerLimitConfigVO {
  id?: number
  type?: number
  userIds?: number[]
  deptIds?: number[]
  maxCount?: number
  dealCountEnabled?: boolean
  contractEndTime?: string
}

当前文件把 userIdsdeptIds 写成了 string,但页面实际按数组使用。修改本次字段时可以顺便将它们纠正为 number[],避免后续类型错误。

2.8 在列表展示字段

修改 CustomerLimitConfigList.vue,在“创建时间”列之前增加:

vue
<el-table-column
  label="合同结束时间"
  align="center"
  prop="contractEndTime"
  :formatter="dateFormatter"
  width="180px"
/>

该页面已经导入了 dateFormatter,无需重复导入。

如果只想显示日期,不显示时分秒,可以使用项目中的日期格式工具,或者自定义 formatter;数据库仍建议保存完整 datetime

2.9 在新增/编辑弹窗增加日期控件

修改 CustomerLimitConfigForm.vue 模板,在合适位置加入:

vue
<el-form-item label="合同结束时间" prop="contractEndTime">
  <el-date-picker
    v-model="formData.contractEndTime"
    type="datetime"
    value-format="YYYY-MM-DD HH:mm:ss"
    placeholder="请选择合同结束时间"
    class="!w-100%"
  />
</el-form-item>

在初始化对象中增加:

ts
contractEndTime: undefined

需要在以下两个位置都增加:

  1. formData 的首次定义。
  2. resetForm() 重置对象。

如果后端增加了 @NotNull,前端规则也应增加:

ts
contractEndTime: [
  { required: true, message: '合同结束时间不能为空', trigger: 'change' }
]

2.10 在列表增加时间范围查询

当前 CustomerLimitConfigList.vue 只有刷新按钮,没有查询表单。可在表格之前增加:

vue
<el-date-picker
  v-model="queryParams.contractEndTime"
  type="datetimerange"
  value-format="YYYY-MM-DD HH:mm:ss"
  start-placeholder="合同结束开始时间"
  end-placeholder="合同结束截止时间"
/>

并在 queryParams 中增加:

ts
contractEndTime: []

点击查询时继续调用现有的 handleQuery() 即可。

2.11 分页响应示例

改完后,分页接口中的单条数据应类似:

json
{
  "id": 1,
  "type": 1,
  "userIds": [1, 103],
  "deptIds": [100],
  "maxCount": 999,
  "dealCountEnabled": false,
  "contractEndTime": "2026-12-31 23:59:59",
  "createTime": "2023-11-18 22:04:11"
}

2.12 编译和验证

后端编译:

bash
mvn -pl yudao-module-crm -am -DskipTests compile

前端类型检查:

bash
cd yudao-ui/yudao-ui-admin-vue3
pnpm ts:check

启动后端:

bash
mvn -f yudao-server/pom.xml \
  org.springframework.boot:spring-boot-maven-plugin:2.7.18:run

启动前端:

bash
cd yudao-ui/yudao-ui-admin-vue3
pnpm install
pnpm dev

验证顺序:

  1. 数据库 SHOW COLUMNS 能看到字段。
  2. Swagger 中分页接口能看到 contractEndTime
  3. 新增一条数据,数据库字段值正确。
  4. 编辑该记录,字段能回显并更新。
  5. 分页列表能显示日期。
  6. 时间范围筛选只返回范围内记录。

三、新建一张表并生成增删改查

下面以“合同档案”表 crm_contract_archive 为例。实际开发时替换表名和业务字段即可。

3.1 设计表结构

本项目普通业务表通常包含:

  • 业务主键 id
  • 创建人、创建时间
  • 更新人、更新时间
  • 逻辑删除字段 deleted
  • 多租户字段 tenant_id

示例 DDL:

sql
CREATE TABLE `crm_contract_archive` (
    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '合同档案编号',
    `customer_id` bigint NOT NULL COMMENT '客户编号',
    `contract_no` varchar(64) NOT NULL COMMENT '合同编号',
    `contract_name` varchar(128) NOT NULL COMMENT '合同名称',
    `start_time` datetime NULL DEFAULT NULL COMMENT '合同开始时间',
    `end_time` datetime NULL DEFAULT NULL COMMENT '合同结束时间',
    `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态',
    `remark` varchar(512) NULL DEFAULT NULL COMMENT '备注',
    `creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater` varchar(64) NULL DEFAULT '' COMMENT '更新者',
    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
        ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uk_tenant_contract_no` (`tenant_id`, `contract_no`),
    KEY `idx_customer_id` (`customer_id`),
    KEY `idx_end_time` (`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='CRM 合同档案表';

设计注意事项:

  1. 表名使用模块前缀。CRM 表使用 crm_,系统表使用 system_
  2. 表和字段必须写 COMMENT,代码生成器会把 COMMENT 用作类名、表单标签和 Swagger 描述。
  3. 金额不要使用 double。通常使用整数保存“分”,或者使用 decimal
  4. 状态字段建议配套字典,例如 crm_contract_archive_status
  5. 多租户业务表必须考虑 tenant_id,唯一索引通常也要带上 tenant_id
  6. 代码生成器只读取已经存在的表,不会替你先创建业务表。

上面的唯一索引表示合同编号在同一租户下永久唯一,即使记录被逻辑删除也不能复用编号。 如果业务要求删除后可以复用编号,需要单独设计删除时间或历史归档方案;不要简单把只有 0/1 两个值的 deleted 加进唯一索引,否则第二条同编号的已删除记录仍会冲突。

将 DDL 保存到版本化 SQL 文件并执行:

bash
mysql -h127.0.0.1 -P3306 -uroot -p ruoyi-vue-pro \
  < sql/mysql/schema-change-2026-07-11-crm.sql

验证:

sql
SHOW CREATE TABLE `crm_contract_archive`;

3.2 确认代码生成器可用

项目有代码生成器,后端实现位于:

Vue3 管理页面位于:

系统菜单为:

text
基础设施 -> 代码生成

相关权限包括:

text
infra:codegen:query
infra:codegen:create
infra:codegen:update
infra:codegen:preview
infra:codegen:download
infra:codegen:delete

看不到菜单或按钮时,给当前角色分配代码生成权限并重新登录。

3.3 检查生成器配置

默认配置在 yudao-server/src/main/resources/application.yaml

yaml
yudao:
  codegen:
    base-package: ${yudao.info.base-package}
    db-schemas: ${spring.datasource.dynamic.datasource.master.name}
    front-type: 20
    vo-type: 10
    delete-batch-enable: true
    unit-test-enable: false
    import-enable: false

关键含义:

  • front-type: 20:Vue3 Element Plus 模板。
  • vo-type: 10:生成 SaveReqVO、RespVO 等独立 VO。
  • delete-batch-enable: true:生成批量删除接口。
  • unit-test-enable: false:当前默认不生成单元测试。
  • import-enable: false:当前默认不生成 Excel 导入功能。

代码生成器读取的是系统中的“数据源配置”。主数据源默认来自 application-local.yamlapplication-dev.yaml

3.4 在页面导入数据库表

操作步骤:

  1. 启动后端和 Vue3 前端。
  2. 登录管理后台。
  3. 进入“基础设施 -> 代码生成”。
  4. 点击“导入”。
  5. 选择主数据源。
  6. 按表名搜索 crm_contract_archive
  7. 勾选表,点击“导入”。

导入动作只是把数据库表结构复制到:

text
infra_codegen_table
infra_codegen_column

它不会立刻把 Java、Vue 文件写进工作区。

3.5 编辑基本信息

导入成功后点击该表的“编辑”,先检查“基本信息”:

配置推荐值
表名称crm_contract_archive
表描述CRM 合同档案表
实体类名称CrmContractArchive
作者团队或开发者名称

实体类推荐使用 CrmContractArchive,而不只是 ContractArchive。这样能避免不同模块存在同名实体时触发 MyBatis TypeAlias 冲突,也与当前 CRM 类命名风格一致。

3.6 编辑字段信息

在“字段信息”页逐列确认:

字段Java 类型插入编辑列表查询查询方式显示类型
idLong=文本框
customer_idLong=文本框/自定义客户选择器
contract_noStringLIKE文本框
contract_nameStringLIKE文本框
start_timeLocalDateTimeBETWEEN日期控件
end_timeLocalDateTimeBETWEEN日期控件
statusInteger=下拉框/单选框
remarkString否或是LIKE文本域

基础字段通常不需要作为业务表单字段:

text
creator
create_time
updater
update_time
deleted
tenant_id

生成器会识别 BaseDO 字段,并默认排除大多数插入、编辑和查询配置。

状态字段如果要显示中文:

  1. 先在“系统管理 -> 字典管理”创建字典类型,例如 crm_contract_archive_status
  2. 回到字段信息。
  3. status 选择该字典类型。
  4. 显示类型选择“下拉框”或“单选框”。

3.7 编辑生成信息

推荐配置:

配置
生成模板单表(增删改查)
前端类型Vue3 Element Plus 标准模版
生成场景管理后台
上级菜单CRM 下合适的目录,例如合同管理
模块名crm
业务名contractarchive
类名称CrmContractArchive
类描述合同档案

说明:

  • 模块名 决定后端模块、接口第一段和前端一级目录。
  • 业务名 会用于 Java package 和前端目录,Java package 推荐全小写。
  • 接口和权限前缀会根据模块名及类名自动生成,预期为:
text
/crm/contract-archive/page
crm:contract-archive:query
crm:contract-archive:create
crm:contract-archive:update
crm:contract-archive:delete
crm:contract-archive:export

设置“上级菜单”是必需的,否则保存时会提示上级菜单不能为空。

3.8 保存并预览

点击“保存”,返回代码生成列表,然后点击“预览”。重点检查:

  1. Controller 的 @RequestMapping 是否正确。
  2. Java package 是否落在 cn.iocoder.yudao.module.crm
  3. DO 的 @TableName 是否为 crm_contract_archive
  4. 时间字段是否生成 LocalDateTime
  5. Mapper 是否生成 betweenIfPresent
  6. 前端 API 地址是否为 /crm/contract-archive/...
  7. 菜单组件路径是否与生成的 Vue 文件一致。
  8. 权限标识是否统一。

预览不正确时,回到“编辑”调整配置,不要先下载再大范围手工改名。

3.9 下载并合并生成代码

点击“生成代码”会下载 ZIP。这个项目的生成器是“下载代码”,不会直接修改工作区。

ZIP 中主要包含:

text
yudao-module-crm/src/main/java/.../controller/admin/contractarchive/...
yudao-module-crm/src/main/java/.../dal/dataobject/contractarchive/...
yudao-module-crm/src/main/java/.../dal/mysql/contractarchive/...
yudao-module-crm/src/main/java/.../service/contractarchive/...
yudao-module-crm/src/main/resources/mapper/contractarchive/...
yudao-ui-admin-vue3/src/api/crm/contractarchive/index.ts
yudao-ui-admin-vue3/src/views/crm/contractarchive/index.vue
yudao-ui-admin-vue3/src/views/crm/contractarchive/ContractArchiveForm.vue
sql/sql.sql

当前仓库的 Vue3 项目实际多了一层 yudao-ui/

text
yudao-ui/yudao-ui-admin-vue3

因此不要把 ZIP 不加检查地直接解压到仓库根目录,否则可能生成一个错误的根目录 yudao-ui-admin-vue3。正确做法是:

  1. 后端 yudao-module-crm 内容合并到仓库根目录的同名模块。
  2. ZIP 中 yudao-ui-admin-vue3/src/... 的内容合并到 yudao-ui/yudao-ui-admin-vue3/src/...
  3. sql/sql.sql 先审查,再作为菜单增量 SQL 执行或合并到项目 SQL 文件。

不要覆盖已经存在并且包含人工业务逻辑的文件。先使用 Git Diff 检查。

3.10 手工合并错误码

生成器通常会生成类似文件:

text
ErrorCodeConstants_手动操作.java

它是提示文件,不应直接保留为正式类。操作步骤:

  1. 打开生成文件。
  2. 将新错误码常量复制到 CRM 已有的 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
  3. 调整错误码编号,确保不与已有编号重复。
  4. 删除 ErrorCodeConstants_手动操作.java
  5. 重新编译确认无重复常量或错误 import。

3.11 执行菜单 SQL

生成的 sql/sql.sql 主要是菜单和按钮权限 SQL,不是业务表建表 SQL。

执行前检查:

  • parent_id 是否是你选择的 CRM 上级菜单。
  • component 是否与 Vue 文件目录一致。
  • component_name 是否唯一。
  • 权限是否为 crm:contract-archive:*

执行菜单 SQL 后:

  1. 给角色分配新菜单和按钮权限。
  2. 如果启用了 SaaS 租户套餐,将新菜单加入对应租户套餐。
  3. 退出并重新登录,刷新动态路由和权限缓存。

3.12 对生成代码做业务化修改

代码生成器产出的是标准 CRUD 骨架,以下内容通常仍需手工完善:

  1. customerId 改成客户选择器,并在返回结果中拼接客户名称。
  2. 校验 contractNo 在同一租户下唯一。
  3. 校验 endTime 不能早于 startTime
  4. status 增加枚举或字典。
  5. 删除前校验是否被其他业务引用。
  6. 增加数据权限,例如只能查看自己负责客户的合同档案。
  7. 补充单元测试。

日期范围校验可放在 Service:

java
if (createReqVO.getStartTime() != null
        && createReqVO.getEndTime() != null
        && createReqVO.getEndTime().isBefore(createReqVO.getStartTime())) {
    throw exception(CONTRACT_ARCHIVE_TIME_INVALID);
}

唯一性不能只依赖前端校验。数据库唯一索引必须保留,Service 可以提前查询并给出更友好的错误提示。

3.13 编译与运行

后端编译:

bash
mvn -pl yudao-module-crm -am -DskipTests compile

前端检查:

bash
cd yudao-ui/yudao-ui-admin-vue3
pnpm ts:check

如果格式检查报错,可仅格式化新生成文件,不要对整个仓库做无关格式化:

bash
pnpm prettier --write \
  src/api/crm/contractarchive/index.ts \
  src/views/crm/contractarchive/*.vue

启动服务后,按顺序验证:

  1. 新菜单可见。
  2. 分页接口返回空列表而不是报错。
  3. 新增成功,数据库审计字段和 tenant_id 正确。
  4. 详情回显正常。
  5. 修改成功。
  6. 查询条件有效。
  7. 单条删除和批量删除符合预期。
  8. 导出 Excel 正常。
  9. 无权限角色看不到按钮,直接调用接口也返回无权限。
  10. 不同租户之间无法读取彼此数据。

四、数据库变更的正确交付方式

当前仓库没有看到 Flyway 或 Liquibase 自动迁移链路,数据库变化主要依靠 SQL 文件管理。因此每次数据库改动至少交付以下内容:

text
数据库增量 SQL
后端代码
前端代码
菜单/字典 SQL
回滚说明
验证结果

推荐流程:

text
设计 DDL
  -> 保存增量 SQL
  -> 本地执行 SQL
  -> 修改或生成代码
  -> 编译和测试
  -> 提交 SQL 与代码
  -> 测试环境先执行 SQL
  -> 再发布后端和前端

4.1 字段上线兼容原则

为了减少发布期间的兼容问题:

  1. 新字段优先允许 NULL 或提供安全默认值。
  2. 先执行兼容性 DDL,再发布读取/写入该字段的代码。
  3. 回填历史数据。
  4. 最后再增加 NOT NULL、唯一索引等强约束。

不要先发布依赖新字段的后端,再执行 ALTER TABLE,否则接口会在发布窗口内报“Unknown column”。

4.2 删除和重命名字段

删除、重命名属于破坏性变更,推荐使用“两阶段”方式:

  1. 第一次发布停止读写旧字段,但暂不删除。
  2. 观察一个发布周期,确认没有旧程序使用。
  3. 第二次发布再执行 DROP COLUMN。

字段重命名也可先增加新字段、双写、迁移数据,最后删除旧字段。

五、代码生成器常见问题

5.1 导入列表找不到新表

检查:

  1. 新表是否创建在生成器所选数据源中。
  2. 数据源是否连接到 ruoyi-vue-pro 数据库。
  3. 表是否已经导入过。已导入的表会从“导入”列表过滤掉。
  4. 如果已导入,回到代码生成列表点击“同步”。

5.2 表增加字段后生成器看不到

先执行数据库 ALTER TABLE,再在代码生成列表点击该表的“同步”。

同步会更新 infra_codegen_column 中的字段元数据。同步后仍需进入“编辑”检查新字段的:

  • Java 类型
  • 插入、编辑、列表、查询选项
  • 查询方式
  • 显示类型
  • 字典类型

5.3 能否用生成器直接覆盖已有客户限制配置代码

不建议。

customer-limit-config 已经包含用户、部门数据拼接、业务校验、操作日志和定制页面。重新生成后直接覆盖,容易丢失这些人工逻辑。

对已有复杂功能增加一两个字段时,优先按本文第二章手工修改。代码生成器更适合:

  • 新表第一次生成标准 CRUD。
  • 已导入的简单表同步字段后,用“预览”对照生成结果。
  • 从生成结果中挑选局部代码,不覆盖定制逻辑。

5.4 生成后菜单不显示

依次检查:

  1. 菜单 SQL 是否执行。
  2. 当前角色是否分配菜单。
  3. 租户套餐是否包含菜单。
  4. 菜单 component 是否对应真实 Vue 路径。
  5. component_name 是否与页面 defineOptions({ name: ... }) 一致。
  6. 是否重新登录。

5.5 接口能调用但按钮不显示

检查前端 v-hasPermi 与后端 @PreAuthorize 是否使用完全相同的权限字符串,例如:

text
crm:contract-archive:create

5.6 数据新增成功但查询不到

本项目启用了多租户和逻辑删除,检查:

sql
SELECT `id`, `deleted`, `tenant_id`
FROM `crm_contract_archive`
ORDER BY `id` DESC;

常见原因:

  • 数据的 tenant_id 与当前登录租户不同。
  • deleted 被写成了 1。
  • 手工 SQL 插入时没有正确设置租户。
  • 当前账号没有对应数据权限。

六、手工开发新表 CRUD 时的文件清单

如果不使用代码生成器,一套标准单表 CRUD 至少包括:

text
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/
  controller/admin/contractarchive/
    CrmContractArchiveController.java
    vo/CrmContractArchivePageReqVO.java
    vo/CrmContractArchiveSaveReqVO.java
    vo/CrmContractArchiveRespVO.java
  dal/dataobject/contractarchive/
    CrmContractArchiveDO.java
  dal/mysql/contractarchive/
    CrmContractArchiveMapper.java
  service/contractarchive/
    CrmContractArchiveService.java
    CrmContractArchiveServiceImpl.java

yudao-ui/yudao-ui-admin-vue3/src/
  api/crm/contractarchive/index.ts
  views/crm/contractarchive/index.vue
  views/crm/contractarchive/ContractArchiveForm.vue

sql/mysql/schema-change-2026-07-11-crm.sql
菜单与按钮权限 SQL

Controller 通常提供:

text
POST   /crm/contract-archive/create
PUT    /crm/contract-archive/update
DELETE /crm/contract-archive/delete
DELETE /crm/contract-archive/delete-list
GET    /crm/contract-archive/get
GET    /crm/contract-archive/page
GET    /crm/contract-archive/export-excel

新表是标准单表 CRUD 时,建议先用生成器,再做业务化修改;只有高度定制、跨表聚合或特殊领域模型才适合完全手写。

七、最终提交前检查清单

  • [ ] 数据库增量 SQL 已保存并能在空测试库执行。
  • [ ] SQL 有明确的表、字段 COMMENT。
  • [ ] 旧数据兼容策略明确。
  • [ ] DO、SaveReqVO、RespVO 字段同名同类型。
  • [ ] PageReqVO 和 Mapper 查询条件对应。
  • [ ] 前端 TypeScript 类型已更新。
  • [ ] 表单初始化和 resetForm() 都包含新字段。
  • [ ] 日期控件设置了 value-format="YYYY-MM-DD HH:mm:ss"
  • [ ] 后端编译通过。
  • [ ] 前端 pnpm ts:check 通过。
  • [ ] 菜单 SQL、角色权限、租户套餐已处理。
  • [ ] 新增、详情、修改、分页、删除、导出已验证。
  • [ ] 多租户隔离和逻辑删除已验证。
  • [ ] 没有直接覆盖已有人工业务逻辑。

Lucking