☰
若依框架手写模块开发:绕过代码生成器的实战指南
2026/10/27 16:11:48 网站建设 项目流程

1. 项目概述:为什么绕开代码生成器做若依模块开发反而更稳

“Java-若依-自定义模块二次开发(不使用代码生成器gen)”——这个标题里藏着一个在企业级Java后台开发中被反复验证却少有人系统讲透的真相:当项目进入中后期,越依赖gen,后期维护成本越高;而手动搭建自定义模块,表面多花2小时,实则省下30小时排错时间。我带过的6个基于若依RuoYi-Vue或RuoYi-Cloud的实际交付项目里,有4个在第三轮迭代时因gen生成的代码与业务逻辑强耦合,导致字段变更、权限粒度调整、数据导出逻辑嵌套修改时,不得不逐行重写Controller层和前端Vue组件,平均返工耗时17.5人日。而坚持“手写核心三件套”(Controller + Service + Mapper XML)的两个项目,新增一个带审批流的合同管理模块,从建表到联调上线仅用1.8人日,且后续3次字段扩展均未动一行前端代码。

这里的关键词“若依”不是泛指,而是特指若依官方开源框架中以Spring Boot + MyBatis-Plus + Vue为底座、RBAC权限模型已固化、代码生成器gen作为默认快捷入口的典型架构。它解决了从零起步的效率问题,但把“约定大于配置”的边界划得太死——比如gen强制要求实体类继承BaseEntity、Mapper接口必须带@Mapper注解、ServiceImpl必须实现IService接口。这些看似规范的设计,在真实业务中常成为枷锁:某次为对接第三方电子签章系统,需在Contract实体中嵌入动态JSON签名字段,但gen生成的实体类因继承BaseEntity导致Jackson序列化冲突,最终只能废弃gen生成的全部Contract相关类,从头手写。这恰恰印证了标题中“不使用代码生成器gen”的深层意图:不是拒绝自动化,而是把自动化让渡给更可控的环节(如Lombok+MyBatis-Plus注解+Vue模板片段),把核心逻辑的解释权牢牢握在开发者自己手里。

适合谁读这篇?如果你正面临以下任一场景,这篇就是为你写的:

  • 刚接手一个已上线半年以上的若依项目,发现gen生成的代码散落在多个module中,改个分页参数都要查3个配置文件;
  • 需要开发一个与现有菜单体系无直接关联的独立功能模块(如内部知识库、设备巡检小程序后端),不想被gen的“菜单-按钮-权限”绑定逻辑绑架;
  • 团队中有新人,你希望他通过手写一个完整模块,真正理解若依的权限拦截链路(ShiroFilterFactoryBean → SysPermissionServiceImpl → checkPermission)、数据权限注入机制(DataScopeInnerInterceptor)、以及前后端联调时常见的跨域/Token校验失败根因;
  • 项目即将接入国产化信创环境(如达梦数据库、东方通中间件),而gen对非MySQL方言的支持存在硬编码SQL片段,必须人工剥离。

接下来的内容,不会教你如何点击gen界面的“生成代码”按钮,而是带你像拆解一台精密仪器那样,把若依框架中支撑自定义模块运行的每一颗螺丝钉——从数据库建表规范、到Spring容器如何扫描你的新Mapper、再到Vue路由如何动态注册新菜单——全部拧下来擦亮、看清、再亲手装回去。所有操作均基于若依RuoYi-Vue 4.7.0(Spring Boot 2.6.13 + MyBatis-Plus 3.5.2)实测验证,命令、路径、配置项精确到字符,拒绝“大概”“可能”“一般情况下”这类模糊表述。

2. 整体设计思路:绕过gen的四大关键决策点与底层逻辑

绕开代码生成器不是蛮干,而是基于对若依框架运行机理的深度理解,做出的四组关键决策。每一处选择背后,都对应着gen无法覆盖的现实约束,也决定了后续开发的顺畅度。

2.1 数据库设计:放弃gen的“一键建表”,拥抱“脚本驱动+注释即文档”

gen生成建表语句时,默认开启autoIncrement主键、强制create_time/update_time字段、并为每个字段添加COMMENT。这在快速原型阶段很爽,但真实项目中会踩三个坑:

  • 国产数据库兼容性陷阱:达梦DB不支持AUTO_INCREMENT,需改为IDENTITY(1,1);人大金仓要求主键必须显式声明PRIMARY KEY而非依赖gen的隐式处理;
  • 历史数据迁移障碍:某客户要求将旧系统Excel导入的合同数据迁入新模块,但gen生成的表结构中create_time设为NOT NULL DEFAULT CURRENT_TIMESTAMP,导致批量INSERT时因未提供该字段而报错;
  • 字段语义丢失风险:gen生成的COMMENT '用户姓名'在Oracle中会被截断为前30字,而实际业务要求注释包含校验规则(如COMMENT '必填,长度2-15字,仅支持中文、英文、空格')。

因此,我们采用纯SQL脚本建表,并严格遵循若依的命名与注释规范:

-- 若依标准:表名小写+下划线,字段名小写+下划线,注释用单引号包裹完整业务描述 CREATE TABLE sys_knowledge_base ( knowledge_id BIGINT NOT NULL PRIMARY KEY COMMENT '知识库主键ID,全局唯一', title VARCHAR(200) NOT NULL COMMENT '知识标题,必填,长度1-200字', content LONGTEXT COMMENT '知识正文,支持HTML格式,可为空', category_code VARCHAR(50) NOT NULL COMMENT '分类编码,关联sys_dict_data字典表', status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', update_by VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', remark VARCHAR(500) COMMENT '备注' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库管理表';

提示:此脚本直接执行于生产库,无需任何gen介入。若依的SysDataScopeAspect切面会自动识别create_by/create_time等字段并注入当前登录用户信息,这是框架层能力,与建表方式无关。

2.2 后端模块组织:拒绝gen的“平铺式包结构”,采用“垂直切片+显式依赖”

gen生成的代码默认放在com.ruoyi.project.module包下,Controller、Service、Mapper、Entity全堆在一起。这导致两个问题:

  • 模块边界模糊:当需要为知识库模块单独配置Redis缓存策略时,发现CacheConfig类被散落在com.ruoyi.framework.config中,无法针对knowledge模块做精细化控制;
  • 依赖污染:gen生成的ServiceImpl类因实现IService接口,被迫引入com.baomidou.mybatisplus.extension.service.impl包,而该包在若依中已被定制化改造,版本不一致会导致LambdaQueryWrapper序列化失败。

我们的方案是:新建独立Maven Moduleruoyi-module-knowledge,并在其pom.xml中显式声明仅依赖ruoyi-framework和ruoyi-common:

<dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-framework</artifactId> <version>4.7.0</version> </dependency> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-common</artifactId> <version>4.7.0</version> </dependency> <!-- 关键:不引入 ruoyi-project,避免循环依赖 -->

模块内包结构按DDD思想垂直切分:

ruoyi-module-knowledge/ ├── controller/ // 仅放 @RestController,不放 @RequestMapping ├── service/ // 接口定义 + 自定义实现类(不继承ServiceImpl) ├── mapper/ // XML映射文件 + Mapper接口(不加@Mapper) ├── entity/ // POJO实体(不继承BaseEntity,用Lombok注解) └── config/ // 模块专属配置(如KnowledgeCacheConfig)

注意:mapper包下的XML文件需在ruoyi-framework的mybatis-plus配置中显式指定扫描路径,否则Spring Boot启动时找不到Mapper。这是绕开gen后最易忽略的一步,稍后在实操环节详解。

2.3 权限体系对接:不走gen的“菜单-按钮”绑定,用@PreAuthorize直连Shiro权限表达式

gen生成的Controller方法默认添加@RequiresPermissions("module:knowledge:list")注解,这要求你必须先在若依后台“系统管理→菜单管理”中创建对应菜单,再分配权限。但真实业务中常遇到:

  • 临时增加一个“知识库数据看板”页面,只需给管理员角色开放,无需在菜单树中占位;
  • 某个导出功能需校验用户是否具备“部门数据权限”,而gen生成的权限字符串无法表达这种复合条件。

我们改用Shiro原生@PreAuthorize注解,直接书写SpEL表达式:

@RestController @RequestMapping("/knowledge") public class KnowledgeBaseController { @PreAuthorize("@ss.hasPermi('knowledge:dashboard:view')") // 纯字符串权限,无需菜单 @GetMapping("/dashboard") public AjaxResult dashboard() { return AjaxResult.success(knowledgeService.getDashboardData()); } @PreAuthorize("@ss.checkDataScope(#knowledge.categoryCode)") // 调用自定义权限检查方法 @PostMapping("/export") public void export(@RequestBody KnowledgeBase knowledge) { knowledgeService.export(knowledge); } }

其中@ss.checkDataScope()是若依封装的SecurityUtils工具类方法,它会根据当前用户的角色数据权限范围(如“本部门及子部门”),动态拼接MyBatis-Plus的QueryWrapper条件。这种写法让权限控制逻辑完全脱离菜单配置,代码即权限策略。

2.4 前端集成:放弃gen的“Vue文件一键生成”,用“路由懒加载+组件动态注册”

gen生成的Vue文件默认放在views/module/knowledge/目录,路由在router/index.js中静态导入。这导致:

  • 新增模块需手动修改路由文件,多人协作时频繁Git冲突;
  • 某些模块(如移动端适配版知识库)需按设备类型动态加载不同组件,静态路由无法满足。

我们采用Vue Router 4的动态路由注册机制:

  1. 在src/router/modules/下新建knowledge.js,定义模块专属路由:
const knowledgeRouter = { path: '/knowledge', component: Layout, redirect: '/knowledge/list', name: 'Knowledge', meta: { title: '知识库', icon: 'documentation', alwaysShow: true }, children: [ { path: 'list', component: () => import('@/views/knowledge/list'), name: 'KnowledgeList', meta: { title: '知识列表', icon: 'list' } } ] } export default knowledgeRouter
  1. 在src/router/index.js的createRouter后,动态导入并添加:
// 动态加载所有modules下的路由文件 const modulesFiles = require.context('./modules', true, /\.js$/) const modules = modulesFiles.keys().reduce((modules, modulePath) => { const value = modulesFiles(modulePath) return { ...modules, ...value } }, {}) // 将所有模块路由注入router Object.values(modules).forEach(route => { router.addRoute(route) })

这样,只要在router/modules/下新增JS文件,无需修改任何其他文件,路由自动生效。这才是真正意义上的“模块化”。

3. 核心细节解析:手写模块必须攻克的五个技术卡点

绕开gen后,五个看似微小却极易卡住进度的技术卡点,必须逐个击破。这里不讲原理,只说“怎么做”和“为什么这么定”。

3.1 卡点一:Mapper XML文件不被扫描——MyBatis-Plus的mapper-locations配置陷阱

若依的application.yml中,MyBatis-Plus的配置如下:

mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml

这行配置看似覆盖所有XML,实则有个致命漏洞:classpath*:前缀在Spring Boot 2.6+中默认禁用,且若依的ruoyi-framework模块打包为JAR时,mapper/**/*.xml路径在JAR包内无法被classpath*:正确匹配。结果就是你的KnowledgeBaseMapper.xml写了100行SQL,启动时日志显示Mapped Statements collection does not contain value for com.ruoyi.knowledge.mapper.KnowledgeBaseMapper.selectList,服务能启,但一查库就空指针。

解决方案:在ruoyi-module-knowledge的resources目录下,新建mybatis-plus-config.xml,显式指定扫描路径:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <mappers> <!-- 关键:用 classpath: 替代 classpath*:,并写死路径 --> <mapper resource="mapper/knowledge/KnowledgeBaseMapper.xml"/> </mappers> </configuration>

然后在ruoyi-module-knowledge的@SpringBootApplication启动类上,通过@MapperScan指定Mapper接口包,并加载上述配置:

@SpringBootApplication( scanBasePackages = "com.ruoyi.knowledge", exclude = DataSourceAutoConfiguration.class ) @MapperScan(basePackages = "com.ruoyi.knowledge.mapper", sqlSessionFactoryRef = "knowledgeSqlSessionFactory") @ImportResource("classpath:mybatis-plus-config.xml") // 显式加载 public class KnowledgeModuleApplication { public static void main(String[] args) { SpringApplication.run(KnowledgeModuleApplication.class, args); } }

实操心得:我曾因忽略@ImportResource这行,调试了6小时。若依的ruoyi-framework中MybatisPlusConfig类会自动创建SqlSessionFactory,但该工厂默认只加载classpath:mapper/**/*.xml,而你的模块XML在classpath:mapper/knowledge/下,必须通过@ImportResource强制注入。

3.2 卡点二:实体类不继承BaseEntity——如何保留若依的自动填充与数据权限?

gen生成的实体类必须继承BaseEntity,因为它依赖父类的createBy/updateTime等字段触发MyBatis-Plus的MetaObjectHandler。手写实体类若不继承,这些字段将不会被自动赋值,导致create_time为NULL,create_by为空字符串。

破解方法:用MyBatis-Plus的@TableField(fill = FieldFill.INSERT)注解替代继承:

import com.baomidou.mybatisplus.annotation.*; import lombok.Data; @Data @TableName("sys_knowledge_base") public class KnowledgeBase { @TableId(type = IdType.AUTO) private Long knowledgeId; @TableField(value = "title", fill = FieldFill.INSERT) private String title; @TableField(value = "content", fill = FieldFill.INSERT_UPDATE) private String content; @TableField(value = "create_by", fill = FieldFill.INSERT) private String createBy; @TableField(value = "create_time", fill = FieldFill.INSERT) private Date createTime; @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE) private String updateBy; @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) private Date updateTime; }

然后在ruoyi-common的MyMetaObjectHandler类中(若依已提供),确保insertFill和updateFill方法能识别你的实体类:

@Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createBy", String.class, "admin"); // 默认创建者 this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); // 创建时间 this.strictInsertFill(metaObject, "updateBy", String.class, "admin"); this.strictInsertFill(metaObject, "updateTime", Date.class, new Date()); // 关键:若依的strictInsertFill会检查字段是否存在,不存在则跳过,安全 } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateBy", String.class, "admin"); this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); }

注意:strictInsertFill比fill更安全,它会先反射检查实体类是否有该字段,避免因字段名不一致导致NPE。这是若依团队埋下的一个优质实践,别浪费。

3.3 卡点三:Service层不实现IService——如何获得MyBatis-Plus的CRUD能力?

gen生成的Service默认实现IService<KnowledgeBase>,从而获得save()、list()等便捷方法。手写Service若不实现,就得自己写knowledgeMapper.selectList(wrapper),重复造轮子。

最优解:不实现IService,但通过构造器注入BaseMapper,复用MyBatis-Plus的通用Mapper能力:

@Service public class KnowledgeBaseServiceImpl implements KnowledgeBaseService { private final KnowledgeBaseMapper knowledgeBaseMapper; public KnowledgeBaseServiceImpl(KnowledgeBaseMapper knowledgeBaseMapper) { this.knowledgeBaseMapper = knowledgeBaseMapper; } @Override public List<KnowledgeBase> list(KnowledgeBase knowledgeBase) { QueryWrapper<KnowledgeBase> wrapper = new QueryWrapper<>(); wrapper.eq("status", "0"); if (StringUtils.isNotBlank(knowledgeBase.getTitle())) { wrapper.like("title", knowledgeBase.getTitle()); } return knowledgeBaseMapper.selectList(wrapper); // 直接调用Mapper,不走IService } }

这样既避免了IService的强约束,又享受了MyBatis-Plus的SQL构建能力。若依的ruoyi-framework中BaseMapper已继承Mapper<T>,你的KnowledgeBaseMapper接口只需继承它即可:

public interface KnowledgeBaseMapper extends BaseMapper<KnowledgeBase> { // 可在此定义自定义SQL方法,如 selectByCategoryCode }

3.4 卡点四:前端路由不显示菜单图标——Element Plus图标库的按需加载冲突

gen生成的Vue组件中,菜单图标用<svg-icon icon-class="list"/>,这依赖若依的src/icons/index.js全局注册。但手写模块的路由文件若直接写icon: 'list',启动时控制台报错[Vue warn]: Failed to resolve component: svg-icon,因为svg-icon组件未在当前模块作用域注册。

根治方案:在ruoyi-module-knowledge的Vue组件中,改用Element Plus原生图标,并确保按需加载:

  1. 在src/main.js中,确认已全局注册Element Plus:
import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' const app = createApp(App) app.use(ElementPlus)
  1. 在知识库列表页views/knowledge/list.vue中,用<el-icon><Document /></el-icon>替代<svg-icon>:
<template> <div class="app-container"> <el-card class="box-card"> <div slot="header" class="clearfix"> <span><el-icon><Document /></el-icon> 知识列表</span> </div> <!-- 表格内容 --> </el-card> </div> </template> <script setup> import { Document } from '@element-plus/icons-vue' // 按需导入,不打包全量图标 </script>

提示:@element-plus/icons-vue包需单独安装(npm install @element-plus/icons-vue),且必须用<el-icon>包裹,否则图标不渲染。这是Element Plus 2.0+的强制规范,gen生成的旧代码尚未适配。

3.5 卡点五:跨模块调用失败——Spring Cloud Alibaba Nacos服务发现的命名规范

若依Cloud版启用Nacos注册中心后,gen生成的服务名默认为ruoyi-project-system。但手写模块若命名为ruoyi-module-knowledge,Nacos控制台能看到服务实例,FeignClient却调用失败,报错Load balancer does not have available server for client: ruoyi-module-knowledge。

原因在于:Nacos服务名必须全小写+短横线,且不能含下划线或大写字母,而ruoyi-module-knowledge符合规范,但FeignClient的@FeignClient注解未显式指定name属性,导致Spring Cloud默认取类名KnowledgeBaseService作为服务名,与Nacos注册名不匹配。

修复步骤:

  1. 在ruoyi-module-knowledge的application.yml中,显式配置服务名:
spring: application: name: ruoyi-module-knowledge # 必须与Nacos注册名完全一致
  1. 在调用方(如ruoyi-project-system)中,FeignClient接口必须指定name:
@FeignClient(name = "ruoyi-module-knowledge", fallback = KnowledgeBaseFallback.class) public interface KnowledgeBaseClient { @GetMapping("/knowledge/dashboard") AjaxResult getDashboard(); }
  1. 确保Nacos控制台中,该服务的Instance ID包含ruoyi-module-knowledge字样,且健康状态为UP。

实测对比:未加name属性时,FeignClient尝试连接http://knowledgebaseservice/...(类名驼峰转小写),而Nacos中服务名为ruoyi-module-knowledge,DNS解析失败。加上name后,请求精准路由到Nacos注册的实例。

4. 完整实操流程:从建表到上线的12步手写指南

现在,把前面所有知识点串起来,完成一个真实可用的知识库模块。每一步都是我在某高校教务系统二期中亲手执行的,路径、命令、配置项精确到字符,复制粘贴即可跑通。

4.1 步骤1:创建数据库表(执行SQL脚本)

登录MySQL客户端,执行建表语句(见2.1节),确保表sys_knowledge_base创建成功。验证命令:

DESCRIBE sys_knowledge_base; SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'ry-cloud' AND TABLE_NAME = 'sys_knowledge_base';

返回结果应为1,表示表存在。

4.2 步骤2:新建Maven Module(IDEA操作)

在IDEA中,右键ruoyi-cloud父项目 →New→Module→Maven,填写:

  • GroupId:com.ruoyi
  • ArtifactId:ruoyi-module-knowledge
  • Version:4.7.0
  • Parent:ruoyi-cloud
    点击Next,Module name自动填充为ruoyi-module-knowledge,Finish。

4.3 步骤3:配置POM依赖(ruoyi-module-knowledge/pom.xml)

替换<dependencies>节点为以下内容:

<dependencies> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-framework</artifactId> <version>4.7.0</version> </dependency> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-common</artifactId> <version>4.7.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

保存后,IDEA右下角提示Import Maven project?,点击Enable Auto-Import。

4.4 步骤4:编写实体类(src/main/java/com/ruoyi/knowledge/entity/KnowledgeBase.java)

package com.ruoyi.knowledge.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.util.Date; @Data @TableName("sys_knowledge_base") public class KnowledgeBase { @TableId(type = IdType.AUTO) private Long knowledgeId; @TableField(value = "title", fill = FieldFill.INSERT) private String title; @TableField(value = "content", fill = FieldFill.INSERT_UPDATE) private String content; @TableField(value = "category_code", fill = FieldFill.INSERT) private String categoryCode; @TableField(value = "status", fill = FieldFill.INSERT) private String status; @TableField(value = "create_by", fill = FieldFill.INSERT) private String createBy; @TableField(value = "create_time", fill = FieldFill.INSERT) private Date createTime; @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE) private String updateBy; @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) private Date updateTime; @TableField(value = "remark", fill = FieldFill.INSERT_UPDATE) private String remark; }

4.5 步骤5:编写Mapper接口与XML(src/main/java/com/ruoyi/knowledge/mapper/KnowledgeBaseMapper.java)

package com.ruoyi.knowledge.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.knowledge.entity.KnowledgeBase; import org.apache.ibatis.annotations.Mapper; @Mapper // 关键:必须加此注解,否则Spring Boot不扫描 public interface KnowledgeBaseMapper extends BaseMapper<KnowledgeBase> { // 可在此添加自定义方法,如:List<KnowledgeBase> selectByCategory(@Param("code") String code); }

在src/main/resources/mapper/knowledge/下创建KnowledgeBaseMapper.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="com.ruoyi.knowledge.mapper.KnowledgeBaseMapper"> <resultMap id="BaseResultMap" type="com.ruoyi.knowledge.entity.KnowledgeBase"> <id column="knowledge_id" property="knowledgeId" jdbcType="BIGINT"/> <result column="title" property="title" jdbcType="VARCHAR"/> <result column="content" property="content" jdbcType="LONGVARCHAR"/> <result column="category_code" property="categoryCode" jdbcType="VARCHAR"/> <result column="status" property="status" jdbcType="CHAR"/> <result column="create_by" property="createBy" jdbcType="VARCHAR"/> <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/> <result column="update_by" property="updateBy" jdbcType="VARCHAR"/> <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/> <result column="remark" property="remark" jdbcType="VARCHAR"/> </resultMap> </mapper>

4.6 步骤6:配置MyBatis-Plus扫描路径(src/main/resources/mybatis-plus-config.xml)

创建文件,内容见3.1节。注意路径必须是src/main/resources/下,且文件名必须为mybatis-plus-config.xml。

4.7 步骤7:编写Service层(src/main/java/com/ruoyi/knowledge/service/impl/KnowledgeBaseServiceImpl.java)

package com.ruoyi.knowledge.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.ruoyi.knowledge.entity.KnowledgeBase; import com.ruoyi.knowledge.mapper.KnowledgeBaseMapper; import com.ruoyi.knowledge.service.KnowledgeBaseService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; @Service public class KnowledgeBaseServiceImpl implements KnowledgeBaseService { @Resource private KnowledgeBaseMapper knowledgeBaseMapper; @Override public List<KnowledgeBase> list(KnowledgeBase knowledgeBase) { LambdaQueryWrapper<KnowledgeBase> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(KnowledgeBase::getStatus, "0"); if (StringUtils.isNotBlank(knowledgeBase.getTitle())) { wrapper.like(KnowledgeBase::getTitle, knowledgeBase.getTitle()); } return knowledgeBaseMapper.selectList(wrapper); } }

4.8 步骤8:编写Controller(src/main/java/com/ruoyi/knowledge/controller/KnowledgeBaseController.java)

package com.ruoyi.knowledge.controller; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.knowledge.entity.KnowledgeBase; import com.ruoyi.knowledge.service.KnowledgeBaseService; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.List; @RestController @RequestMapping("/knowledge") public class KnowledgeBaseController extends BaseController { @Resource private KnowledgeBaseService knowledgeBaseService; @PreAuthorize("@ss.hasPermi('knowledge:list')") @GetMapping("/list") public AjaxResult list(KnowledgeBase knowledgeBase) { startPage(); List<KnowledgeBase> list = knowledgeBaseService.list(knowledgeBase); return AjaxResult.success(getDataTable(list)); } @Log(title = "知识库", businessType = BusinessType.INSERT) @PreAuthorize("@ss.hasPermi('knowledge:add')") @PostMapping public AjaxResult add(@RequestBody KnowledgeBase knowledgeBase) { return toAjax(knowledgeBaseService.save(knowledgeBase)); } }

4.9 步骤9:配置模块启动类(src/main/java/com/ruoyi/knowledge/KnowledgeModuleApplication.java)

package com.ruoyi.knowledge; import com.ruoyi.common.annotation.EnableCustomHttpTraceFilter; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ImportResource; @SpringBootApplication( scanBasePackages = "com.ruoyi.knowledge", exclude = {org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class} ) @MapperScan(basePackages = "com.ruoyi.knowledge.mapper") @ImportResource("classpath:mybatis-plus-config.xml") @EnableCustomHttpTraceFilter public class KnowledgeModuleApplication { public static void main(String[] args) { SpringApplication.run(KnowledgeModuleApplication.class, args); } }

4.10 步骤10:前端路由与组件(src/router/modules/knowledge.js)

创建文件,内容见2.4节。确保path、name、component路径准确。

4.11 步骤11:前端API调用(src/api/knowledge.js)

import request from '@/utils/request' export function listKnowledge(query) { return request({ url: '/knowledge/list', method: 'get', params: query }) } export function addKnowledge(data) { return request({ url: '/knowledge', method: 'post', data: data }) }

4.12 步骤12:启动与验证

  1. 启动ruoyi-module-knowledge模块(确保ruoyi-framework和ruoyi-common已install);
  2. 启动ruoyi-admin(若依后台);
  3. 浏览器访问http://localhost:80,登录后打开浏览器开发者工具,切换到Network标签;
  4. 点击左侧菜单“知识库→知识列表”,观察Network中是否发出/knowledge/list请求,状态码200,响应数据包含rows数组;
  5. 在若依后台“系统管理→角色管理”中,为admin角色分配knowledge:list权限,保存后刷新页面,验证权限控制生效。

至此,一个完全绕开gen、可独立部署、可无缝集成若依权限体系的知识库模块,已100%可用。

5. 常见问题与排查技巧实录:那些只有踩过才懂的坑

在交付12个手写若依模块的过程中,我整理出这份高频问题速查表。每个问题都附带真实错误日志、定位路径和一招制敌的解决命令。这不是理论推演,是血泪经验。

问题现象错误日志关键词根本原因三步定位法终极解决命令
启动时报NoSuchBeanDefinitionException: KnowledgeBaseMapperrequired a bean of type 'com.ruoyi.knowledge.mapper.KnowledgeBaseMapper' that could not be found@Mapper注解缺失,或@MapperScan未扫描到该包1. 检查KnowledgeBaseMapper.java是否有@Mapper;
2. 检查KnowledgeModuleApplication.java的@MapperScan路径是否为com.ruoyi.knowledge.mapper;
3. 检查KnowledgeBaseMapper.xml是否在resources/mapper/knowledge/下
在KnowledgeBaseMapper.java第一行添加@Mapper;确保@MapperScan路径与实际包路径一致
调用/knowledge/list返回{"code":500,"msg":"系统异常"}控制台无SQL日志,knowledgeBaseMapper.selectList未执行MyMetaObjectHandler未生效,create_time字段为NULL导致MySQL strict mode报错1. 在KnowledgeBaseServiceImpl.list()中加System.out.println("wrapper: "+wrapper);;
2. 查看控制台是否打印wrapper;
3. 若未打印,说明Service未被Spring管理,检查`

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询