第14章 - 二次开发指南
第14章 - 二次开发指南
14.1 开发准备
14.1.1 开发环境确认
确保开发环境已正确配置:
- JDK 1.8+
- Maven 3.6+
- Node.js 14+
- IDE(推荐IDEA)
- Git
14.1.2 项目结构理解
RuoYi-Cloud/
├── ruoyi-api/ # 接口模块(远程调用接口)
├── ruoyi-auth/ # 认证中心
├── ruoyi-common/ # 通用模块
├── ruoyi-gateway/ # 网关服务
├── ruoyi-modules/ # 业务模块(在此扩展新功能)
│ ├── ruoyi-system/ # 系统管理
│ ├── ruoyi-gen/ # 代码生成
│ ├── ruoyi-job/ # 定时任务
│ └── ruoyi-file/ # 文件服务
├── ruoyi-ui/ # 前端项目
└── ruoyi-visual/ # 可视化模块
14.2 新增业务模块
14.2.1 创建模块
步骤1:创建Maven模块
在ruoyi-modules目录下创建新模块:
<!-- ruoyi-modules/ruoyi-demo/pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>3.6.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-modules-demo</artifactId>
<packaging>jar</packaging>
<description>演示模块</description>
<dependencies>
<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringBoot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- RuoYi Common Security -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-security</artifactId>
</dependency>
<!-- RuoYi Common Swagger -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-swagger</artifactId>
</dependency>
<!-- RuoYi Common Log -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
步骤2:创建启动类
package com.ruoyi.demo;
import com.ruoyi.common.security.annotation.EnableCustomConfig;
import com.ruoyi.common.security.annotation.EnableRyFeignClients;
import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 演示模块
*/
@EnableCustomConfig
@EnableCustomSwagger2
@EnableRyFeignClients
@SpringBootApplication
public class RuoYiDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RuoYiDemoApplication.class, args);
System.out.println("演示模块启动成功");
}
}
步骤3:创建配置文件
# bootstrap.yml
spring:
application:
name: ruoyi-demo
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: ${spring.profiles.active}
config:
server-addr: localhost:8848
namespace: ${spring.profiles.active}
file-extension: yml
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
步骤4:Nacos配置
在Nacos中添加ruoyi-demo-dev.yml:
server:
port: 9204
spring:
datasource:
druid:
stat-view-servlet:
enabled: true
loginUsername: admin
loginPassword: 123456
dynamic:
druid:
initial-size: 5
min-idle: 5
maxActive: 20
maxWait: 60000
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: password
mybatis:
typeAliasesPackage: com.ruoyi.demo.domain
mapperLocations: classpath:mapper/**/*.xml
14.2.2 添加网关路由
在ruoyi-gateway-dev.yml中添加路由配置:
spring:
cloud:
gateway:
routes:
# 演示模块
- id: ruoyi-demo
uri: lb://ruoyi-demo
predicates:
- Path=/demo/**
filters:
- StripPrefix=1
14.3 业务功能开发
14.3.1 创建实体类
package com.ruoyi.demo.domain;
import com.ruoyi.common.core.web.domain.BaseEntity;
import com.ruoyi.common.core.annotation.Excel;
/**
* 示例对象
*/
public class Demo extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键ID */
private Long id;
/** 名称 */
@Excel(name = "名称")
private String name;
/** 编码 */
@Excel(name = "编码")
private String code;
/** 状态 */
@Excel(name = "状态", readConverterExp = "0=正常,1=停用")
private String status;
// getter/setter...
}
14.3.2 创建Mapper
package com.ruoyi.demo.mapper;
import com.ruoyi.demo.domain.Demo;
import java.util.List;
/**
* 示例Mapper接口
*/
public interface DemoMapper {
/**
* 查询示例列表
*/
List<Demo> selectDemoList(Demo demo);
/**
* 根据ID查询示例
*/
Demo selectDemoById(Long id);
/**
* 新增示例
*/
int insertDemo(Demo demo);
/**
* 修改示例
*/
int updateDemo(Demo demo);
/**
* 删除示例
*/
int deleteDemoById(Long id);
/**
* 批量删除示例
*/
int deleteDemoByIds(Long[] ids);
}
<!-- mapper/demo/DemoMapper.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.demo.mapper.DemoMapper">
<resultMap type="Demo" id="DemoResult">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="code" column="code" />
<result property="status" column="status" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectDemoVo">
select id, name, code, status, create_by, create_time, update_by, update_time, remark
from demo
</sql>
<select id="selectDemoList" parameterType="Demo" resultMap="DemoResult">
<include refid="selectDemoVo"/>
<where>
<if test="name != null and name != ''"> and name like concat('%', #{name}, '%')</if>
<if test="code != null and code != ''"> and code = #{code}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
</where>
</select>
<select id="selectDemoById" parameterType="Long" resultMap="DemoResult">
<include refid="selectDemoVo"/>
where id = #{id}
</select>
<insert id="insertDemo" parameterType="Demo" useGeneratedKeys="true" keyProperty="id">
insert into demo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null">name,</if>
<if test="code != null">code,</if>
<if test="status != null">status,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="name != null">#{name},</if>
<if test="code != null">#{code},</if>
<if test="status != null">#{status},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateDemo" parameterType="Demo">
update demo
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name = #{name},</if>
<if test="code != null">code = #{code},</if>
<if test="status != null">status = #{status},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteDemoById" parameterType="Long">
delete from demo where id = #{id}
</delete>
<delete id="deleteDemoByIds" parameterType="String">
delete from demo where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>
14.3.3 创建Service
package com.ruoyi.demo.service;
import com.ruoyi.demo.domain.Demo;
import java.util.List;
/**
* 示例服务接口
*/
public interface IDemoService {
List<Demo> selectDemoList(Demo demo);
Demo selectDemoById(Long id);
int insertDemo(Demo demo);
int updateDemo(Demo demo);
int deleteDemoByIds(Long[] ids);
}
package com.ruoyi.demo.service.impl;
import com.ruoyi.demo.domain.Demo;
import com.ruoyi.demo.mapper.DemoMapper;
import com.ruoyi.demo.service.IDemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 示例服务实现
*/
@Service
public class DemoServiceImpl implements IDemoService {
@Autowired
private DemoMapper demoMapper;
@Override
public List<Demo> selectDemoList(Demo demo) {
return demoMapper.selectDemoList(demo);
}
@Override
public Demo selectDemoById(Long id) {
return demoMapper.selectDemoById(id);
}
@Override
public int insertDemo(Demo demo) {
return demoMapper.insertDemo(demo);
}
@Override
public int updateDemo(Demo demo) {
return demoMapper.updateDemo(demo);
}
@Override
public int deleteDemoByIds(Long[] ids) {
return demoMapper.deleteDemoByIds(ids);
}
}
14.3.4 创建Controller
package com.ruoyi.demo.controller;
import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.TableDataInfo;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.demo.domain.Demo;
import com.ruoyi.demo.service.IDemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 示例控制器
*/
@RestController
@RequestMapping("/demo")
public class DemoController extends BaseController {
@Autowired
private IDemoService demoService;
/**
* 查询列表
*/
@RequiresPermissions("demo:demo:list")
@GetMapping("/list")
public TableDataInfo list(Demo demo) {
startPage();
List<Demo> list = demoService.selectDemoList(demo);
return getDataTable(list);
}
/**
* 获取详细信息
*/
@RequiresPermissions("demo:demo:query")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(demoService.selectDemoById(id));
}
/**
* 新增
*/
@RequiresPermissions("demo:demo:add")
@Log(title = "示例管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody Demo demo) {
return toAjax(demoService.insertDemo(demo));
}
/**
* 修改
*/
@RequiresPermissions("demo:demo:edit")
@Log(title = "示例管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody Demo demo) {
return toAjax(demoService.updateDemo(demo));
}
/**
* 删除
*/
@RequiresPermissions("demo:demo:remove")
@Log(title = "示例管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(demoService.deleteDemoByIds(ids));
}
}
14.4 前端页面开发
14.4.1 创建API接口
// src/api/demo/demo.js
import request from '@/utils/request'
// 查询列表
export function listDemo(query) {
return request({
url: '/demo/demo/list',
method: 'get',
params: query
})
}
// 查询详细
export function getDemo(id) {
return request({
url: '/demo/demo/' + id,
method: 'get'
})
}
// 新增
export function addDemo(data) {
return request({
url: '/demo/demo',
method: 'post',
data: data
})
}
// 修改
export function updateDemo(data) {
return request({
url: '/demo/demo',
method: 'put',
data: data
})
}
// 删除
export function delDemo(id) {
return request({
url: '/demo/demo/' + id,
method: 'delete'
})
}
14.4.2 创建Vue页面
<!-- src/views/demo/demo/index.vue -->
<template>
<div class="app-container">
<!-- 搜索区域 -->
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form-item label="名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['demo:demo:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['demo:demo:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['demo:demo:remove']">删除</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="demoList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="编码" align="center" prop="code" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
{{ scope.row.status === '0' ? '正常' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['demo:demo:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['demo:demo:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
<!-- 添加或修改对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="编码" prop="code">
<el-input v-model="form.code" placeholder="请输入编码" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio value="0">正常</el-radio>
<el-radio value="1">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Demo">
import { listDemo, getDemo, addDemo, updateDemo, delDemo } from "@/api/demo/demo"
const { proxy } = getCurrentInstance()
const demoList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
name: undefined,
status: undefined
},
rules: {
name: [{ required: true, message: "名称不能为空", trigger: "blur" }],
code: [{ required: true, message: "编码不能为空", trigger: "blur" }]
}
})
const { queryParams, form, rules } = toRefs(data)
/** 查询列表 */
function getList() {
loading.value = true
listDemo(queryParams.value).then(response => {
demoList.value = response.rows
total.value = response.total
loading.value = false
})
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef")
handleQuery()
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.id)
single.value = selection.length != 1
multiple.value = !selection.length
}
/** 新增按钮操作 */
function handleAdd() {
reset()
open.value = true
title.value = "添加示例"
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset()
const id = row.id || ids.value
getDemo(id).then(response => {
form.value = response.data
open.value = true
title.value = "修改示例"
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["formRef"].validate(valid => {
if (valid) {
if (form.value.id != undefined) {
updateDemo(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
open.value = false
getList()
})
} else {
addDemo(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
open.value = false
getList()
})
}
}
})
}
/** 删除按钮操作 */
function handleDelete(row) {
const delIds = row.id || ids.value
proxy.$modal.confirm('是否确认删除?').then(function() {
return delDemo(delIds)
}).then(() => {
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
/** 取消按钮 */
function cancel() {
open.value = false
reset()
}
/** 表单重置 */
function reset() {
form.value = {
id: undefined,
name: undefined,
code: undefined,
status: "0",
remark: undefined
}
proxy.resetForm("formRef")
}
getList()
</script>
14.5 配置菜单权限
-- 插入菜单
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例管理', '0', '10', 'demo', NULL, 1, 0, 'M', '0', '0', '', 'example', 'admin', sysdate(), '', null, '示例管理目录');
-- 获取上一步插入的菜单ID
SELECT @parentId := LAST_INSERT_ID();
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例列表', @parentId, '1', 'demo', 'demo/demo/index', 1, 0, 'C', '0', '0', 'demo:demo:list', '#', 'admin', sysdate(), '', null, '示例列表菜单');
SELECT @menuId := LAST_INSERT_ID();
-- 按钮权限
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例查询', @menuId, '1', '#', '', 1, 0, 'F', '0', '0', 'demo:demo:query', '#', 'admin', sysdate(), '', null, '');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例新增', @menuId, '2', '#', '', 1, 0, 'F', '0', '0', 'demo:demo:add', '#', 'admin', sysdate(), '', null, '');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例修改', @menuId, '3', '#', '', 1, 0, 'F', '0', '0', 'demo:demo:edit', '#', 'admin', sysdate(), '', null, '');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例删除', @menuId, '4', '#', '', 1, 0, 'F', '0', '0', 'demo:demo:remove', '#', 'admin', sysdate(), '', null, '');
14.6 小结
本章详细介绍了RuoYi-Cloud的二次开发指南,包括:
- 创建新模块:Maven模块、启动类、配置文件
- 业务开发:实体类、Mapper、Service、Controller
- 前端开发:API接口、Vue页面
- 权限配置:菜单和按钮权限SQL
掌握这些内容可以快速进行业务功能开发。

浙公网安备 33010602011771号