Appearance
数据库字段与 CRUD 代码生成器实战
本文结合当前仓库的真实结构,说明两类常见开发任务:
- 在已有分页接口
crm/customer-limit-config/page中增加“合同结束时间”字段。 - 新建一张业务表,并完成数据库、后端、前端、权限菜单的增删改查。
本文默认使用:
- 后端: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 表当前相关文件如下:
- Controller:
CrmCustomerLimitConfigController.java - 分页入参:
CrmCustomerLimitConfigPageReqVO.java - 保存入参:
CrmCustomerLimitConfigSaveReqVO.java - 返回对象:
CrmCustomerLimitConfigRespVO.java - 数据对象:
CrmCustomerLimitConfigDO.java - Mapper:
CrmCustomerLimitConfigMapper.java - Service:
CrmCustomerLimitConfigServiceImpl.java - 前端 API:
src/api/crm/customer/limitConfig/index.ts - 前端列表:
CustomerLimitConfigList.vue - 前端表单:
CustomerLimitConfigForm.vue
这个项目大量使用同名属性自动转换:
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.sqlsql/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
}当前文件把 userIds、deptIds 写成了 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需要在以下两个位置都增加:
formData的首次定义。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验证顺序:
- 数据库
SHOW COLUMNS能看到字段。 - Swagger 中分页接口能看到
contractEndTime。 - 新增一条数据,数据库字段值正确。
- 编辑该记录,字段能回显并更新。
- 分页列表能显示日期。
- 时间范围筛选只返回范围内记录。
三、新建一张表并生成增删改查
下面以“合同档案”表 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 合同档案表';设计注意事项:
- 表名使用模块前缀。CRM 表使用
crm_,系统表使用system_。 - 表和字段必须写 COMMENT,代码生成器会把 COMMENT 用作类名、表单标签和 Swagger 描述。
- 金额不要使用
double。通常使用整数保存“分”,或者使用decimal。 - 状态字段建议配套字典,例如
crm_contract_archive_status。 - 多租户业务表必须考虑
tenant_id,唯一索引通常也要带上tenant_id。 - 代码生成器只读取已经存在的表,不会替你先创建业务表。
上面的唯一索引表示合同编号在同一租户下永久唯一,即使记录被逻辑删除也不能复用编号。 如果业务要求删除后可以复用编号,需要单独设计删除时间或历史归档方案;不要简单把只有 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 确认代码生成器可用
项目有代码生成器,后端实现位于:
CodegenController.javaCodegenServiceImpl.javaCodegenEngine.java- 模板目录:
yudao-module-infra/src/main/resources/codegen
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.yaml 或 application-dev.yaml。
3.4 在页面导入数据库表
操作步骤:
- 启动后端和 Vue3 前端。
- 登录管理后台。
- 进入“基础设施 -> 代码生成”。
- 点击“导入”。
- 选择主数据源。
- 按表名搜索
crm_contract_archive。 - 勾选表,点击“导入”。
导入动作只是把数据库表结构复制到:
text
infra_codegen_table
infra_codegen_column它不会立刻把 Java、Vue 文件写进工作区。
3.5 编辑基本信息
导入成功后点击该表的“编辑”,先检查“基本信息”:
| 配置 | 推荐值 |
|---|---|
| 表名称 | crm_contract_archive |
| 表描述 | CRM 合同档案表 |
| 实体类名称 | CrmContractArchive |
| 作者 | 团队或开发者名称 |
实体类推荐使用 CrmContractArchive,而不只是 ContractArchive。这样能避免不同模块存在同名实体时触发 MyBatis TypeAlias 冲突,也与当前 CRM 类命名风格一致。
3.6 编辑字段信息
在“字段信息”页逐列确认:
| 字段 | Java 类型 | 插入 | 编辑 | 列表 | 查询 | 查询方式 | 显示类型 |
|---|---|---|---|---|---|---|---|
id | Long | 否 | 是 | 是 | 否 | = | 文本框 |
customer_id | Long | 是 | 是 | 是 | 是 | = | 文本框/自定义客户选择器 |
contract_no | String | 是 | 是 | 是 | 是 | LIKE | 文本框 |
contract_name | String | 是 | 是 | 是 | 是 | LIKE | 文本框 |
start_time | LocalDateTime | 是 | 是 | 是 | 是 | BETWEEN | 日期控件 |
end_time | LocalDateTime | 是 | 是 | 是 | 是 | BETWEEN | 日期控件 |
status | Integer | 是 | 是 | 是 | 是 | = | 下拉框/单选框 |
remark | String | 是 | 是 | 否或是 | 否 | LIKE | 文本域 |
基础字段通常不需要作为业务表单字段:
text
creator
create_time
updater
update_time
deleted
tenant_id生成器会识别 BaseDO 字段,并默认排除大多数插入、编辑和查询配置。
状态字段如果要显示中文:
- 先在“系统管理 -> 字典管理”创建字典类型,例如
crm_contract_archive_status。 - 回到字段信息。
- 给
status选择该字典类型。 - 显示类型选择“下拉框”或“单选框”。
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 保存并预览
点击“保存”,返回代码生成列表,然后点击“预览”。重点检查:
- Controller 的
@RequestMapping是否正确。 - Java package 是否落在
cn.iocoder.yudao.module.crm。 - DO 的
@TableName是否为crm_contract_archive。 - 时间字段是否生成
LocalDateTime。 - Mapper 是否生成
betweenIfPresent。 - 前端 API 地址是否为
/crm/contract-archive/...。 - 菜单组件路径是否与生成的 Vue 文件一致。
- 权限标识是否统一。
预览不正确时,回到“编辑”调整配置,不要先下载再大范围手工改名。
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。正确做法是:
- 后端
yudao-module-crm内容合并到仓库根目录的同名模块。 - ZIP 中
yudao-ui-admin-vue3/src/...的内容合并到yudao-ui/yudao-ui-admin-vue3/src/...。 sql/sql.sql先审查,再作为菜单增量 SQL 执行或合并到项目 SQL 文件。
不要覆盖已经存在并且包含人工业务逻辑的文件。先使用 Git Diff 检查。
3.10 手工合并错误码
生成器通常会生成类似文件:
text
ErrorCodeConstants_手动操作.java它是提示文件,不应直接保留为正式类。操作步骤:
- 打开生成文件。
- 将新错误码常量复制到 CRM 已有的
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java。 - 调整错误码编号,确保不与已有编号重复。
- 删除
ErrorCodeConstants_手动操作.java。 - 重新编译确认无重复常量或错误 import。
3.11 执行菜单 SQL
生成的 sql/sql.sql 主要是菜单和按钮权限 SQL,不是业务表建表 SQL。
执行前检查:
parent_id是否是你选择的 CRM 上级菜单。component是否与 Vue 文件目录一致。component_name是否唯一。- 权限是否为
crm:contract-archive:*。
执行菜单 SQL 后:
- 给角色分配新菜单和按钮权限。
- 如果启用了 SaaS 租户套餐,将新菜单加入对应租户套餐。
- 退出并重新登录,刷新动态路由和权限缓存。
3.12 对生成代码做业务化修改
代码生成器产出的是标准 CRUD 骨架,以下内容通常仍需手工完善:
customerId改成客户选择器,并在返回结果中拼接客户名称。- 校验
contractNo在同一租户下唯一。 - 校验
endTime不能早于startTime。 - 为
status增加枚举或字典。 - 删除前校验是否被其他业务引用。
- 增加数据权限,例如只能查看自己负责客户的合同档案。
- 补充单元测试。
日期范围校验可放在 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启动服务后,按顺序验证:
- 新菜单可见。
- 分页接口返回空列表而不是报错。
- 新增成功,数据库审计字段和
tenant_id正确。 - 详情回显正常。
- 修改成功。
- 查询条件有效。
- 单条删除和批量删除符合预期。
- 导出 Excel 正常。
- 无权限角色看不到按钮,直接调用接口也返回无权限。
- 不同租户之间无法读取彼此数据。
四、数据库变更的正确交付方式
当前仓库没有看到 Flyway 或 Liquibase 自动迁移链路,数据库变化主要依靠 SQL 文件管理。因此每次数据库改动至少交付以下内容:
text
数据库增量 SQL
后端代码
前端代码
菜单/字典 SQL
回滚说明
验证结果推荐流程:
text
设计 DDL
-> 保存增量 SQL
-> 本地执行 SQL
-> 修改或生成代码
-> 编译和测试
-> 提交 SQL 与代码
-> 测试环境先执行 SQL
-> 再发布后端和前端4.1 字段上线兼容原则
为了减少发布期间的兼容问题:
- 新字段优先允许 NULL 或提供安全默认值。
- 先执行兼容性 DDL,再发布读取/写入该字段的代码。
- 回填历史数据。
- 最后再增加 NOT NULL、唯一索引等强约束。
不要先发布依赖新字段的后端,再执行 ALTER TABLE,否则接口会在发布窗口内报“Unknown column”。
4.2 删除和重命名字段
删除、重命名属于破坏性变更,推荐使用“两阶段”方式:
- 第一次发布停止读写旧字段,但暂不删除。
- 观察一个发布周期,确认没有旧程序使用。
- 第二次发布再执行 DROP COLUMN。
字段重命名也可先增加新字段、双写、迁移数据,最后删除旧字段。
五、代码生成器常见问题
5.1 导入列表找不到新表
检查:
- 新表是否创建在生成器所选数据源中。
- 数据源是否连接到
ruoyi-vue-pro数据库。 - 表是否已经导入过。已导入的表会从“导入”列表过滤掉。
- 如果已导入,回到代码生成列表点击“同步”。
5.2 表增加字段后生成器看不到
先执行数据库 ALTER TABLE,再在代码生成列表点击该表的“同步”。
同步会更新 infra_codegen_column 中的字段元数据。同步后仍需进入“编辑”检查新字段的:
- Java 类型
- 插入、编辑、列表、查询选项
- 查询方式
- 显示类型
- 字典类型
5.3 能否用生成器直接覆盖已有客户限制配置代码
不建议。
customer-limit-config 已经包含用户、部门数据拼接、业务校验、操作日志和定制页面。重新生成后直接覆盖,容易丢失这些人工逻辑。
对已有复杂功能增加一两个字段时,优先按本文第二章手工修改。代码生成器更适合:
- 新表第一次生成标准 CRUD。
- 已导入的简单表同步字段后,用“预览”对照生成结果。
- 从生成结果中挑选局部代码,不覆盖定制逻辑。
5.4 生成后菜单不显示
依次检查:
- 菜单 SQL 是否执行。
- 当前角色是否分配菜单。
- 租户套餐是否包含菜单。
- 菜单
component是否对应真实 Vue 路径。 component_name是否与页面defineOptions({ name: ... })一致。- 是否重新登录。
5.5 接口能调用但按钮不显示
检查前端 v-hasPermi 与后端 @PreAuthorize 是否使用完全相同的权限字符串,例如:
text
crm:contract-archive:create5.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
菜单与按钮权限 SQLController 通常提供:
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、角色权限、租户套餐已处理。
- [ ] 新增、详情、修改、分页、删除、导出已验证。
- [ ] 多租户隔离和逻辑删除已验证。
- [ ] 没有直接覆盖已有人工业务逻辑。
