【RuoYi-Eggjs】:告别手写,自动生成代码

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.7k人参与

【RuoYi-Eggjs】:告别手写,自动生成代码

当你厌倦了为每个 XML Mapper 手写重复的 Service 代码时,这款自动化代码生成器就是你的救星。本文介绍 ruoyi-eggjs-cli 如何通过智能解析 MyBatis XML,自动生成标准化的 Service 层代码。该工具还提供了内网穿透功能,让你的应用可以被外网访问。

一、痛点:重复劳动的 Service 代码

在基于 MyBatis XML 的 Node.js 项目中,开发者经常面临这样的困扰:

传统开发流程的问题

<!-- 1. 先写 XML Mapper -->
<select id="selectUserList">
  SELECT * FROM sys_user WHERE status = #{status}
</select>
// 2. 然后手写对应的 Service
class UserService extends Service {
  async selectUserList(values, params) {
    const sql = this.app.mapper('UserMapper.xml', 'selectUserList', values, params);
    return await this.app.mysql.selects(sql);
  }
}

重复劳动的痛点

  • 每个 XML 文件都需要手写对应的 Service 类
  • 方法命名容易不一致
  • XML 变更后需要手动同步 Service
  • 新增 SQL 需要手动添加对应方法

RuoYi-Eggjs-CLI 正是为了解决这些痛点而生,它能够:

  • 🤖 自动扫描 XML Mapper 文件
  • 🔄 智能生成 标准化 Service 代码
  • 📡 实时监听 文件变化并自动更新
  • 🌐 扩展功能 内网穿透工具

二、核心功能:从 XML 到 Service 的自动化

工作流程概览

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   XML Mapper    │───▶│  ruoyi-eggjs-   │───▶│  Service 代码    │
│                 │    │     cli         │    │                 │
│ MyBatis XML     │    │                 │    │ 标准化代码       │
│ - select        │    │ 智能解析器       │    │ - xxxMapper()   │
│ - insert        │    │ - 文件扫描       │    │ - xxx()         │
│ - update        │    │ - 类型识别       │    │ - db()          │
│ - delete        │    │ - 代码生成       │    │ - mapper()      │
└─────────────────┘    └─────────────────┘    └─────────────────┘

目录结构映射

CLI 会自动维护 XML 文件与 Service 文件的一一对应关系:

输入目录                           输出目录
./mapper/                         ./app/service/db/
├── mysql/                        ├── mysql/
│   └── ruoyi/                    │   └── ruoyi/
│       ├── SysUserMapper.xml  ───│────── ├── SysUserMapper.js
│       └── SysRoleMapper.xml  ───│────── └── SysRoleMapper.js
└── sqlite/                       └── sqlite/
    └── cache/                        └── cache/
        └── CacheMapper.xml   ─────────── └── CacheMapper.js

SQL 类型智能识别

CLI 内置了智能的 SQL 类型识别机制:

XML 标签生成方法数据库调用返回值
<select>selectXxx().select()单条记录
<selects>selectXxxs().selects()记录数组
<insert>insertXxx().insert()插入ID
<update>updateXxx().update()影响行数
<delete>deleteXxx().del()删除行数

三、实战演示:一键生成标准代码

快速上手

# 安装 CLI 工具
$ npm install ruoyi-eggjs-cli --save-dev

# 在项目根目录执行
$ psy mapper

# 启动后的效果
✔ 扫描到 15 个 XML 文件
✔ 生成 15 个 Service 文件
✔ 自动监听模式已启动,文件变化将自动重新生成

输入:MyBatis XML 示例

<!-- mapper/mysql/ruoyi/SysUserMapper.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/mysql/ruoyi/SysUserMapper.xml">
    
    <!-- 动态条件查询 -->
    <select id="selectUserList">
        SELECT user_id, user_name, nick_name, email, status 
        FROM sys_user
        <where>
            <if test="userName">
                AND user_name LIKE CONCAT('%', #{userName}, '%')
            </if>
            <if test="status">
                AND status = #{status}
            </if>
        </where>
        ORDER BY create_time DESC
        LIMIT ?, ?
    </select>
    
    <!-- 单条查询 -->
    <select id="selectUserById">
        SELECT * FROM sys_user WHERE user_id = #{userId}
    </select>
    
    <!-- 插入用户 -->
    <insert id="insertUser">
        INSERT INTO sys_user (user_name, nick_name, email, create_time)
        VALUES (#{userName}, #{nickName}, #{email}, NOW())
    </insert>
    
    <!-- 动态更新 -->
    <update id="updateUser">
        UPDATE sys_user
        <set>
            <if test="userName">user_name = #{userName},</if>
            <if test="nickName">nick_name = #{nickName},</if>
            <if test="email">email = #{email},</if>
            update_time = NOW()
        </set>
        WHERE user_id = #{userId}
    </update>
    
    <!-- 删除用户 -->
    <delete id="deleteUserByIds">
        DELETE FROM sys_user WHERE user_id IN
        <foreach item="userId" collection="array" open="(" separator="," close=")">
            #{userId}
        </foreach>
    </delete>
    
</mapper>

输出:自动生成的 Service 代码

// app/service/db/mysql/ruoyi/SysUserMapper.js
const Service = require('egg').Service;

class SysUserMapperService extends Service {
    /**
     * 获取 Mapper SQL
     * @param {string} sqlid - SQL ID
     * @param {array} values - 参数数组
     * @param {object} params - 参数对象
     * @return {string} SQL 语句
     */
    mapper(sqlid, values, params) {
        return this.app.mapper(
            'mapper/mysql/ruoyi/SysUserMapper.xml', 
            sqlid, 
            values, 
            params
        );
    }

    /**
     * 获取数据库实例
     * @return {object} 数据库连接实例
     */
    db() {
        return this.app.mysql.get('ruoyi');
    }

    // === 查询用户列表 ===
    selectUserListMapper(values, params) {
        return this.mapper('selectUserList', values, params);
    }

    async selectUserList(values, params) {
        return await this.db().selects(this.selectUserListMapper(values, params));
    }

    // === 根据ID查询用户 ===
    selectUserByIdMapper(values, params) {
        return this.mapper('selectUserById', values, params);
    }

    async selectUserById(values, params) {
        return await this.db().select(this.selectUserByIdMapper(values, params));
    }

    // === 插入用户 ===
    insertUserMapper(values, params) {
        return this.mapper('insertUser', values, params);
    }

    async insertUser(values, params) {
        return await this.db().insert(this.insertUserMapper(values, params));
    }

    // === 更新用户 ===
    updateUserMapper(values, params) {
        return this.mapper('updateUser', values, params);
    }

    async updateUser(values, params) {
        return await this.db().update(this.updateUserMapper(values, params));
    }

    // === 批量删除用户 ===
    deleteUserByIdsMapper(values, params) {
        return this.mapper('deleteUserByIds', values, params);
    }

    async deleteUserByIds(values, params) {
        return await this.db().del(this.deleteUserByIdsMapper(values, params));
    }
}

module.exports = SysUserMapperService;

Controller 中的使用

// app/controller/system/user.js
class UserController extends Controller {
    async list() {
        const { ctx } = this;
        const params = ctx.request.body;
        
        // 使用生成的 Service,代码简洁统一
        const users = await ctx.service.db.mysql.ruoyi.sysUserMapper.selectUserList(
            ctx.helper.page(params), // 分页参数
            params                   // 查询条件
        );
        
        ctx.body = {
            code: 200,
            data: users,
            message: '查询成功'
        };
    }

    async info() {
        const { ctx } = this;
        const { userId } = ctx.params;
        
        const user = await ctx.service.db.mysql.ruoyi.sysUserMapper.selectUserById(
            [], 
            { userId }
        );
        
        ctx.body = { code: 200, data: user };
    }

    async add() {
        const { ctx } = this;
        const user = ctx.request.body;
        
        const insertId = await ctx.service.db.mysql.ruoyi.sysUserMapper.insertUser(
            [], 
            user
        );
        
        ctx.body = { code: 200, data: { userId: insertId } };
    }

    async update() {
        const { ctx } = this;
        const user = ctx.request.body;
        
        await ctx.service.db.mysql.ruoyi.sysUserMapper.updateUser([], user);
        
        ctx.body = { code: 200, message: '更新成功' };
    }

    async remove() {
        const { ctx } = this;
        const { userIds } = ctx.request.body;
        
        await ctx.service.db.mysql.ruoyi.sysUserMapper.deleteUserByIds(
            [], 
            { array: userIds }
        );
        
        ctx.body = { code: 200, message: '删除成功' };
    }
}

四、高级特性:智能监听与多数据库

1. 实时文件监听

启用监听模式后,CLI 会智能响应文件变化:

$ psy mapper
✔ 生成成功
✔ 自动监听模式已启动

# 修改 XML 后自动触发
./mapper/mysql/ruoyi/SysUserMapper.xml has been changed
✔ Service 代码已更新

# 删除 XML 后自动清理
./mapper/mysql/ruoyi/SysRoleMapper.xml has been removed  
✔ 对应 Service 文件已删除

智能更新机制

  • 只在 SQL ID 和标签名称变化时才重新生成
  • 避免不必要的文件写入
  • 保持文件修改时间的准确性

2. 多数据库支持

CLI 天然支持多数据库和多数据源配置:

// 配置示例
config.mysql = {
  clients: {
    ruoyi: {        // 主业务数据库
      host: '127.0.0.1',
      user: 'root',
      password: 'password',
      database: 'ruoyi_db',
    },
    log: {          // 日志数据库
      host: '192.168.1.100',
      user: 'log_user',
      password: 'log_pass',
      database: 'log_db',
    }
  }
};

// 生成的 Service 会自动识别数据源
// ./app/service/db/mysql/ruoyi/XXXMapper.js -> this.app.mysql.get('ruoyi')
// ./app/service/db/mysql/log/XXXMapper.js   -> this.app.mysql.get('log')

3. 命名规范自动化

CLI 采用智能的命名转换规则:

// XML 文件名 → Service 类名(PascalCase)
'SysUserMapper.xml''SysUserMapperService'
'sys-role-mapper.xml''SysRoleMapperService'  
'sys_menu_mapper.xml''SysMenuMapperService'

// SQL ID → 方法名(camelCase)
'selectUserList''selectUserList()'
'select_user_by_id''selectUserById()'
'insert-user-info''insertUserInfo()'

五、FRP 内网穿透

CLI 还内置了 FRP 内网穿透工具(内置版本 v0.45.0) ,方便开发调试:

快速使用

# 完整示例(所有参数必填)
$ rec frp 127.0.0.1:7001 -saddr frp.example.com -sport 39998 -auth your_token

# 指定本地端口(IP 默认为 127.0.0.1)
$ rec frp 7001 -saddr frp.example.com -sport 39998 -auth your_token

参数说明:

参数说明是否必填
localURL本地服务地址,格式:IP:PORTPORT必填
-saddr, --serverAddrFRP 服务端地址必填
-sport, --serverPortFRP 服务端端口必填
-auth, --authToken身份验证令牌必填
-cdomain, --customDomains自定义域名可选

实用场景

  1. 微信小程序开发:需要 HTTPS 域名调试
  2. 远程演示:临时分享本地服务给客户
  3. 团队协作:让测试人员访问开发环境
  4. API 对接:第三方服务回调本地接口

服务端部署

配置
  1. 下载 FRP 服务端对应系统版本:FRP 下载地址

使用命令 uname -m 查看处理器架构。 如果是x86_64 即可选择amd64,若是aarch64 则选择arm64

wget https://github.com/fatedier/frp/releases/download/v0.45.0/frp_0.45.0_linux_arm64.tar.gz
  1. 解压并配置 frps.ini 文件
tar -zxvf frp_0.45.0_linux_arm64.tar.gz
cd frp_0.45.0_linux_arm64

配置文件 frps.ini

[common]
bind_port = 39998
vhost_http_port = 39427

[web]
type = http
custom_domains = frp.example.com
auth_token = your_token
  1. 后台运行,开机启动
vim /etc/systemd/system/frps.service
[Unit]
# 服务名称,可自定义
Description = frp server
After = network.target syslog.target
Wants = network.target
[Service]
Type = simple
# 启动frps的命令,需修改为您的frps的安装路径
ExecStart = /path/to/frps -c /path/to/frps.ini
[Install]
WantedBy = multi-user.target
# 启动frp
systemctl start frps
# 停止frp
systemctl stop frps
# 重启frp
systemctl restart frps
# 查看frp状态
systemctl status frps
# 开机自启
systemctl enable frps
  1. nginx 反向代理使用80端口(可配置 https)
server {
    listen 80; 
    server_name test.undsky.com;

    location / { 
        proxy_pass http://127.0.0.1:39427;
        proxy_set_header    Host            $host:80;
        proxy_set_header    X-Real-IP       $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_hide_header   X-Powered-By;
        proxy_set_header Upgrade $http_upgrade;  # WebSocket
        proxy_set_header Connection "upgrade";
    } 
}

八、总结

核心价值

RuoYi-Eggjs-CLI 通过自动化解决了 MyBatis 风格开发中的重复劳动问题:

  1. 效率提升:XML 到 Service 的零人工介入
  2. 规范统一:自动生成的代码遵循统一标准
  3. 维护简化:XML 变更自动同步到代码
  4. 开发体验:实时监听 + 内网穿透提升开发效率

适用场景

  • 采用 MyBatis XML 风格的 Egg.js 项目
  • 需要大量数据库操作的企业级应用
  • 希望减少重复代码的开发团队
  • 需要多数据库支持的复杂项目

<think>我们面对的是一个Maven循环依赖问题。根据错误信息,ruoyi-system模块依赖于ruoyi-common模块,而ruoyi-common模块又反过来依赖于ruoyi-system模块,形成了循环:ruoyi-system->ruoyi-common->ruoyi-system。解决循环依赖的常见方法:1.重构代码:将两个模块中相互依赖的部分提取到一个新的模块中,或者将公共代码下沉到基础模块。2.检查依赖关系:分析为什么ruoyi-common会依赖ruoyi-system?通常,common模块应该是被其他模块依赖的基础模块,不应该反过来依赖上层模块。具体步骤:1.查看ruoyi-common模块的pom.xml,确认它是否直接或间接地依赖了ruoyi-system。2.查看ruoyi-system模块的pom.xml,确认它对ruoyi-common的依赖。根据错误信息,循环路径是:ruoyi-system->ruoyi-common->ruoyi-system。因此,我们需要在ruoyi-common模块中移除对ruoyi-system的依赖。可能的情况:-ruoyi-common模块的pom.xml中,显式地引入了ruoyi-system依赖。这种情况应该删除该依赖。-或者,ruoyi-common模块中的某些类引用了ruoyi-system模块中的类,导致编译时需要ruoyi-system。这种情况需要重构代码,将ruoyi-common中依赖ruoyi-system的类移到ruoyi-system模块中,或者将两个模块中相互依赖的代码提取到第三个模块。操作建议:1.检查代码:在ruoyi-common模块中搜索对ruoyi-system模块中类的引用。如果存在,考虑将这些引用移除或重构。2.如果ruoyi-common确实需要一些原本在ruoyi-system中的功能,可以将这些功能提取到一个新的模块,然后让两个模块都依赖这个新模块。3.如果循环依赖是由于某些工具类或通用类被放置在ruoyi-system模块中,而这些类又被ruoyi-common需要,那么应该将这些类移动到ruoyi-common模块。注意:在Maven项目中,循环依赖是不允许的,因此必须打破这个循环。根据RuoYi项目的常见结构,ruoyi-common模块通常是基础工具类、通用枚举等,而ruoyi-system是系统模块,包含系统相关的功能。所以,ruoyi-common不应该依赖ruoyi-system。实际解决步骤:1.打开ruoyi-common模块的pom.xml,查看<dependencies>部分,如果发现ruoyi-system依赖,则将其删除。2.如果pom中没有直接依赖,则可能是传递依赖导致?但根据错误信息,是直接形成了循环,所以重点检查直接依赖。3.使用Maven命令分析依赖树:在项目根目录下运行`mvndependency:tree`,查看依赖树,定位循环依赖的位置。如果代码中存在类之间的循环依赖,那么需要修改代码。例如:-如果ruoyi-common中的类A使用了ruoyi-system中的类B,那么可以考虑:a.将类B移动到ruoyi-common中(如果类B是通用的,则合理)b.或者,将类A中依赖类B的部分抽离出来,通过接口解耦(依赖倒置),将接口放在ruoyi-common,而实现放在ruoyi-system,这样ruoyi-common只依赖接口,而实现由ruoyi-system提供,但ruoyi-common不再直接依赖ruoyi-system。另一种方法是使用事件或消息机制来解耦两个模块之间的直接依赖。但是,由于这是一个常见问题,我们可以参考RuoYi项目的标准结构。实际上,在标准的RuoYi项目中,ruoyi-common模块是不应该依赖ruoyi-system模块的。因此,可能是开发过程中不小心引入了反向依赖。总结:先检查pom文件,再检查代码引用。优先确保ruoyi-common的pom中没有引入ruoyi-system依赖。然后检查代码,将ruoyi-common中引用ruoyi-system的类进行调整,要么移动类的位置,要么重构代码消除依赖。如果问题仍然存在,请提供更多信息,例如两个模块中相互引用的具体类名和代码片段,以便更精确地解决问题。</think>### 解决Maven循环依赖问题 您在构建Maven项目时遇到的错误信息表明存在**模块间循环依赖**。具体错误为: ``` Edge between 'Vertex{label='com.ruoyi:ruoyi-common:3.8.8'}' and 'Vertex{label='com.ruoyi:ruoyi-system:3.8.8'}' introduces a cycle in the graph com.ruoyi:ruoyi-system:3.8.8 --> com.ruoyi:ruoyi-common:3.8.8 --> com.ruoyi:ruoyi-system:3.8.8 ``` #### 问题本质 循环依赖发生在两个模块相互引用时: 1. `ruoyi-system` 模块依赖 `ruoyi-common` 2. `ruoyi-common` 模块又反向依赖 `ruoyi-system` 形成闭环:$$ \text{system} \rightarrow \text{common} \rightarrow \text{system} $$ #### 解决方案(逐步操作) ##### 1. **定位循环依赖点** - 检查 `ruoyi-common/pom.xml` 中是否显式依赖了 `ruoyi-system`: ```xml <!-- 错误示例 --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-system</artifactId> <version>3.8.8</version> </dependency> ``` - 若存在,**直接删除此依赖**(这是最快解决方案) ##### 2. **代码级依赖检查** 如果POM中无显式依赖,检查代码: - 在 `ruoyi-common` 模块中搜索 `ruoyi-system` 中的类引用 - 常见场景: - `ruoyi-common` 导入了 `ruoyi-system` 的Service/Entity类 - `ruoyi-common` 的工具类引用了系统模块的功能 ##### 3. **架构重构(核心方案)** ```mermaid graph LR A[原结构] --> B[ruoyi-system] --> C[ruoyi-common] C --> B %% 循环点 D[新结构] --> E[ruoyi-system] D --> F[ruoyi-common] D --> G[新建 ruoyi-common-core] F --> G %% 基础层 E --> G %% 共同依赖基础层 ``` - **步骤**: 1. 创建新模块 `ruoyi-common-core` 2. 将 `ruoyi-common` 中被 `ruoyi-system` 依赖的**基础代码**移至 `core` 模块 3. 调整依赖关系: - `ruoyi-common` 依赖 `ruoyi-common-core` - `ruoyi-system` 依赖 `ruoyi-common-core` - 确保 `ruoyi-common-core` **不依赖**其他业务模块 ##### 4. **依赖作用域优化** 若循环依赖无法完全消除,使用 `<scope>` 限制: ```xml <!--ruoyi-common 中 --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-system</artifactId> <version>3.8.8</version> <scope>provided</scope> <!-- 编译时不传递 --> </dependency> ``` #### 验证修复 执行以下命令验证: ```bash mvn clean install -DskipTests ``` 循环依赖错误应消失,构建成功[^1][^2]。 #### 预防措施 1. 遵循 **单向依赖原则**:基础模块(如common)永远不依赖上层模块 2. 定期运行依赖分析: ```bash mvn dependency:tree -Dincludes=com.ruoyi ``` 3. 使用架构守护工具(如ArchUnit)禁止跨模块反向引用 > **关键提示**:在RuoYi等标准框架中,`common` 模块通常是所有模块的基础层,若出现反向依赖,往往是代码结构问题,需优先检查模块职责划分[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值