【RuoYi-Eggjs】:告别手写,自动生成代码
当你厌倦了为每个 XML Mapper 手写重复的 Service 代码时,这款自动化代码生成器就是你的救星。本文介绍 ruoyi-eggjs-cli 如何通过智能解析 MyBatis XML,自动生成标准化的 Service 层代码。该工具还提供了内网穿透功能,让你的应用可以被外网访问。
一、痛点:重复劳动的 Service 代码
在基于 MyBatis XML 的 Node.js 项目中,开发者经常面临这样的困扰:
传统开发流程的问题
xml
<!-- 1. 先写 XML Mapper -->
<select id="selectUserList">
SELECT * FROM sys_user WHERE status = #{status}
</select>
js
// 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 的自动化
工作流程概览
scss
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ XML Mapper │───▶│ ruoyi-eggjs- │───▶│ Service 代码 │
│ │ │ cli │ │ │
│ MyBatis XML │ │ │ │ 标准化代码 │
│ - select │ │ 智能解析器 │ │ - xxxMapper() │
│ - insert │ │ - 文件扫描 │ │ - xxx() │
│ - update │ │ - 类型识别 │ │ - db() │
│ - delete │ │ - 代码生成 │ │ - mapper() │
└─────────────────┘ └─────────────────┘ └─────────────────┘
目录结构映射
CLI 会自动维护 XML 文件与 Service 文件的一一对应关系:
bash
输入目录 输出目录
./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() |
删除行数 |
三、实战演示:一键生成标准代码
快速上手
bash
# 安装 CLI 工具
$ npm install ruoyi-eggjs-cli --save-dev
# 在项目根目录执行
$ psy mapper
# 启动后的效果
✔ 扫描到 15 个 XML 文件
✔ 生成 15 个 Service 文件
✔ 自动监听模式已启动,文件变化将自动重新生成
输入:MyBatis XML 示例
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 代码
js
// 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 中的使用
js
// 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 会智能响应文件变化:
bash
$ 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 天然支持多数据库和多数据源配置:
js
// 配置示例
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 采用智能的命名转换规则:
js
// 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) ,方便开发调试:
快速使用
bash
# 完整示例(所有参数必填)
$ 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:PORT 或 PORT |
必填 |
-saddr, --serverAddr |
FRP 服务端地址 | 必填 |
-sport, --serverPort |
FRP 服务端端口 | 必填 |
-auth, --authToken |
身份验证令牌 | 必填 |
-cdomain, --customDomains |
自定义域名 | 可选 |
实用场景
- 微信小程序开发:需要 HTTPS 域名调试
- 远程演示:临时分享本地服务给客户
- 团队协作:让测试人员访问开发环境
- API 对接:第三方服务回调本地接口
服务端部署
配置
- 下载 FRP 服务端对应系统版本:FRP 下载地址
使用命令 uname -m 查看处理器架构。 如果是x86_64 即可选择amd64,若是aarch64 则选择arm64
ruby
wget https://github.com/fatedier/frp/releases/download/v0.45.0/frp_0.45.0_linux_arm64.tar.gz
- 解压并配置
frps.ini文件
bash
tar -zxvf frp_0.45.0_linux_arm64.tar.gz
cd frp_0.45.0_linux_arm64
配置文件 frps.ini
ini
[common]
bind_port = 39998
vhost_http_port = 39427
[web]
type = http
custom_domains = frp.example.com
auth_token = your_token
- 后台运行,开机启动
bash
vim /etc/systemd/system/frps.service
ini
[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
bash
# 启动frp
systemctl start frps
# 停止frp
systemctl stop frps
# 重启frp
systemctl restart frps
# 查看frp状态
systemctl status frps
# 开机自启
systemctl enable frps
- nginx 反向代理使用80端口(可配置 https)
ini
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 风格开发中的重复劳动问题:
- 效率提升:XML 到 Service 的零人工介入
- 规范统一:自动生成的代码遵循统一标准
- 维护简化:XML 变更自动同步到代码
- 开发体验:实时监听 + 内网穿透提升开发效率
适用场景
- 采用 MyBatis XML 风格的 Egg.js 项目
- 需要大量数据库操作的企业级应用
- 希望减少重复代码的开发团队
- 需要多数据库支持的复杂项目
- 插件地址:ruoyi-eggjs-cli
- 项目地址:RuoYi-Eggjs
- 开发文档:RuoYi-Eggjs 文档