RuoYi-Vue-master/document/04-开发指南.md
Leo 6114f9eeb5 添加项目文档和配置
- 添加CLAUDE.md项目配置指南
- 添加document目录包含详细的架构分析文档
- 添加doc目录包含环境使用手册
- 添加bin目录包含构建和清理脚本
- 添加.github配置文件
2025-09-26 21:06:25 +08:00

1640 lines
41 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# RuoYi-Vue 开发指南
## 1. 开发环境准备
### 1.1 环境要求
#### 基础环境要求
| 组件 | 版本要求 | 推荐版本 | 说明 |
|------|----------|----------|------|
| **JDK** | 1.8+ | JDK 1.8.0_202 | 确保JAVA_HOME配置正确 |
| **Maven** | 3.6+ | Maven 3.6.3 | 依赖管理和项目构建 |
| **MySQL** | 5.7+ | MySQL 8.0.28 | 数据库服务 |
| **Redis** | 6.0+ | Redis 6.2.7 | 缓存服务 |
| **Node.js** | 14+ | Node.js 16.17.0 | 前端开发环境 |
| **npm** | 6.14+ | npm 8.15.0 | 前端包管理器 |
#### IDE推荐配置
**后端开发IDE**
-**IntelliJ IDEA** (推荐)
- 安装插件Lombok、MyBatis Log Plugin、Maven Helper
-**Eclipse** + Spring Tool Suite
-**Visual Studio Code** + Java Extension Pack
**前端开发IDE**
-**Visual Studio Code** (推荐)
- 安装插件Vetur、ESLint、Prettier、Auto Rename Tag
-**WebStorm**
### 1.2 环境安装指南
#### JDK安装配置
```bash
# 1. 下载JDK 1.8
# https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html
# 2. 配置环境变量 (Windows)
set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202
set PATH=%JAVA_HOME%\bin;%PATH%
# 3. 配置环境变量 (macOS/Linux)
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home
export PATH=$JAVA_HOME/bin:$PATH
# 4. 验证安装
java -version
javac -version
```
#### MySQL安装配置
```bash
# 1. 安装MySQL (使用Docker推荐)
docker run -d \
--name mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=password \
-e MYSQL_DATABASE=ry-vue \
mysql:8.0
# 2. 创建数据库
mysql -u root -p
CREATE DATABASE `ry-vue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
# 3. 导入初始化脚本
mysql -u root -p ry-vue < sql/ry_20210908.sql
mysql -u root -p ry-vue < sql/quartz.sql
```
#### Redis安装配置
```bash
# 1. 安装Redis (使用Docker推荐)
docker run -d \
--name redis \
-p 6379:6379 \
redis:6.2-alpine
# 2. 本地安装 (macOS)
brew install redis
brew services start redis
# 3. 本地安装 (CentOS/RHEL)
yum install redis
systemctl start redis
systemctl enable redis
# 4. 验证安装
redis-cli ping
# 应该返回 PONG
```
#### Node.js安装
```bash
# 1. 使用NVM安装 (推荐)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 16.17.0
nvm use 16.17.0
# 2. 直接下载安装
# https://nodejs.org/
# 3. 验证安装
node -v
npm -v
# 4. 配置国内镜像源 (可选)
npm config set registry https://registry.npmmirror.com/
```
## 2. 项目快速启动
### 2.1 项目获取与配置
#### 项目下载
```bash
# 1. 克隆项目 (如果是Git仓库)
git clone https://gitee.com/y_project/RuoYi-Vue.git
cd RuoYi-Vue
# 2. 或者直接下载解压项目压缩包
```
#### 数据库配置
```yaml
# ruoyi-admin/src/main/resources/application-druid.yml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: password
# 从库数据源 (可选)
slave:
enabled: false
url:
username:
password:
```
#### Redis配置
```yaml
# ruoyi-admin/src/main/resources/application.yml
spring:
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 10s
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1ms
```
### 2.2 后端启动
```bash
# 1. 进入项目根目录
cd RuoYi-Vue-master
# 2. Maven清理和编译
mvn clean install -Dmaven.test.skip=true
# 3. 启动应用 (方式一Maven命令)
cd ruoyi-admin
mvn spring-boot:run
# 4. 启动应用 (方式二java命令)
cd ruoyi-admin/target
java -jar ruoyi-admin.jar
# 5. 启动应用 (方式三:脚本启动)
./ry.sh start
# 6. 查看启动日志
tail -f logs/sys-info.log
```
#### 启动验证
```bash
# 检查应用是否启动成功
curl http://localhost:8080/captchaImage
# 检查健康状态
curl http://localhost:8080/actuator/health
# 查看端口占用
netstat -an | grep 8080
```
### 2.3 前端启动
```bash
# 1. 进入前端项目目录
cd ruoyi-ui
# 2. 安装依赖
npm install
# 3. 启动开发服务器
npm run dev
# 4. 构建生产版本
npm run build:prod
# 5. 预览构建结果
npm run preview
```
#### 前端配置文件
```javascript
// ruoyi-ui/.env.development
# 开发环境配置
ENV = 'development'
# 若依管理系统/开发环境
VUE_APP_TITLE = 若依管理系统
# 开发环境配置
VUE_APP_ENV = 'development'
# 若依管理系统/开发环境
VUE_APP_BASE_API = '/dev-api'
```
### 2.4 访问系统
#### 系统访问地址
| 服务 | 访问地址 | 默认账号 |
|------|----------|----------|
| **前端系统** | http://localhost | admin / admin123 |
| **后端接口** | http://localhost:8080 | - |
| **接口文档** | http://localhost:8080/swagger-ui.html | - |
| **Druid监控** | http://localhost:8080/druid | admin / 123456 |
#### 默认管理员账号
```
用户名admin
密码admin123
```
## 3. 开发规范与约定
### 3.1 代码规范
#### Java代码规范
**1. 命名规范**
```java
// 类名:大驼峰命名法
public class SysUserController {
// 常量:全大写,下划线分隔
private static final String USER_CACHE_KEY = "user:cache:";
// 变量:小驼峰命名法
private String userName;
// 方法:小驼峰命名法,动词开头
public AjaxResult getUserList() {
return success();
}
}
```
**2. 注释规范**
```java
/**
* 用户信息
*
* @author ruoyi
* @date 2024-01-01
*/
@Entity
@Table(name = "sys_user")
public class SysUser extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 用户ID */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
/** 用户账号 */
@NotBlank(message = "用户账号不能为空")
@Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
private String userName;
/**
* 获取用户信息
*
* @param userId 用户ID
* @return 用户信息
*/
public SysUser getUserById(Long userId) {
// 实现逻辑
return null;
}
}
```
**3. 异常处理规范**
```java
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) {
log.error(e.getMessage(), e);
Integer code = e.getCode();
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}
/**
* 权限校验异常
*/
@ExceptionHandler(AccessDeniedException.class)
public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
}
}
```
#### JavaScript代码规范
**1. 命名规范**
```javascript
// 常量:全大写,下划线分隔
const API_BASE_URL = '/dev-api';
// 变量:小驼峰命名法
const userName = 'admin';
// 函数:小驼峰命名法,动词开头
function getUserList() {
return request({
url: '/system/user/list',
method: 'get'
});
}
// 组件名:大驼峰命名法
export default {
name: 'UserList',
components: {
UserDialog
}
};
```
**2. Vue组件规范**
```vue
<template>
<div class="app-container">
<!-- 查询条件 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="用户名称" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="请输入用户名称"
clearable
style="width: 200px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @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="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:user:add']"
>新增</el-button>
</el-col>
</el-row>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="用户编号" align="center" key="userId" prop="userId" />
<el-table-column label="用户名称" align="center" key="userName" prop="userName" :show-overflow-tooltip="true" />
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { listUser, getUser, delUser, addUser, updateUser } from "@/api/system/user";
export default {
name: "User",
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 用户表格数据
userList: [],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
userName: undefined,
phonenumber: undefined,
status: undefined,
deptId: undefined
}
};
},
created() {
this.getList();
},
methods: {
/** 查询用户列表 */
getList() {
this.loading = true;
listUser(this.queryParams).then(response => {
this.userList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
}
}
};
</script>
```
### 3.2 数据库设计规范
#### 表设计规范
**1. 表命名规范**
```sql
-- 系统表前缀sys_
CREATE TABLE `sys_user` (
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB COMMENT='用户信息表';
-- 业务表前缀:根据模块命名
CREATE TABLE `biz_order` (
`order_id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单ID',
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB COMMENT='订单信息表';
```
**2. 字段设计规范**
```sql
-- 标准字段设计
CREATE TABLE `sys_user` (
-- 主键bigint自增
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
-- 业务字段:合适的数据类型和长度
`user_name` varchar(30) NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
`phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',
`sex` char(1) DEFAULT '0' COMMENT '用户性别0男 1女 2未知',
`status` char(1) DEFAULT '0' COMMENT '帐号状态0正常 1停用',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除',
-- 审计字段:每张表必须包含
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
-- 主键和索引
PRIMARY KEY (`user_id`),
UNIQUE KEY `uk_user_name` (`user_name`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB AUTO_INCREMENT=100 COMMENT='用户信息表';
```
**3. 索引设计规范**
```sql
-- 主键索引:每张表必须有主键
PRIMARY KEY (`id`)
-- 唯一索引:业务唯一字段
UNIQUE KEY `uk_user_name` (`user_name`)
-- 普通索引:经常查询的字段
KEY `idx_status` (`status`)
KEY `idx_create_time` (`create_time`)
-- 复合索引:多字段组合查询
KEY `idx_dept_status` (`dept_id`, `status`)
-- 外键索引:关联字段
KEY `idx_dept_id` (`dept_id`)
```
### 3.3 API设计规范
#### RESTful API设计
**1. URL设计规范**
```java
// 资源型URL使用名词复数形式
@RequestMapping("/system/users")
@RestController
public class SysUserController {
// GET /system/users - 获取用户列表
@GetMapping
public TableDataInfo list(SysUser user) {
startPage();
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
// GET /system/users/{id} - 获取单个用户
@GetMapping("/{userId}")
public AjaxResult getInfo(@PathVariable("userId") Long userId) {
return AjaxResult.success(userService.selectUserById(userId));
}
// POST /system/users - 新增用户
@PostMapping
public AjaxResult add(@Validated @RequestBody SysUser user) {
return toAjax(userService.insertUser(user));
}
// PUT /system/users/{id} - 更新用户
@PutMapping("/{userId}")
public AjaxResult edit(@Validated @RequestBody SysUser user) {
return toAjax(userService.updateUser(user));
}
// DELETE /system/users/{id} - 删除用户
@DeleteMapping("/{userIds}")
public AjaxResult remove(@PathVariable Long[] userIds) {
return toAjax(userService.deleteUserByIds(userIds));
}
}
```
**2. 响应格式规范**
```java
// 统一响应格式
public class AjaxResult extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
/** 状态码 */
public static final String CODE_TAG = "code";
/** 返回内容 */
public static final String MSG_TAG = "msg";
/** 数据对象 */
public static final String DATA_TAG = "data";
/**
* 状态类型
*/
public enum Type {
/** 成功 */
SUCCESS(200),
/** 警告 */
WARN(301),
/** 错误 */
ERROR(500);
private final int value;
}
}
// 成功响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"userId": 1,
"userName": "admin",
"nickName": "管理员"
}
}
// 分页响应示例
{
"code": 200,
"msg": "查询成功",
"rows": [
{
"userId": 1,
"userName": "admin"
}
],
"total": 100
}
// 错误响应示例
{
"code": 500,
"msg": "用户名不能为空"
}
```
## 4. 开发最佳实践
### 4.1 后端开发实践
#### 分层架构实践
**1. Controller层最佳实践**
```java
@RestController
@RequestMapping("/system/user")
public class SysUserController extends BaseController {
@Autowired
private ISysUserService userService;
/**
* 获取用户列表
*/
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user) {
startPage(); // 开启分页
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list); // 返回分页结果
}
/**
* 新增用户
*/
@PreAuthorize("@ss.hasPermi('system:user:add')")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody SysUser user) {
// 1. 参数校验
if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user.getUserName()))) {
return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
}
// 2. 设置默认值
user.setCreateBy(getUsername());
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
// 3. 调用服务层
return toAjax(userService.insertUser(user));
}
}
```
**2. Service层最佳实践**
```java
@Service
public class SysUserServiceImpl implements ISysUserService {
@Autowired
private SysUserMapper userMapper;
@Autowired
private SysUserRoleMapper userRoleMapper;
/**
* 新增用户信息
*/
@Override
@Transactional
public int insertUser(SysUser user) {
// 1. 新增用户信息
int rows = userMapper.insertUser(user);
// 2. 新增用户与角色管理
insertUserRole(user);
return rows;
}
/**
* 新增用户角色信息
*/
public void insertUserRole(SysUser user) {
Long[] roles = user.getRoleIds();
if (StringUtils.isNotNull(roles)) {
// 新增用户与角色管理
List<SysUserRole> list = new ArrayList<SysUserRole>();
for (Long roleId : roles) {
SysUserRole ur = new SysUserRole();
ur.setUserId(user.getUserId());
ur.setRoleId(roleId);
list.add(ur);
}
if (list.size() > 0) {
userRoleMapper.batchUserRole(list);
}
}
}
}
```
**3. Mapper层最佳实践**
```java
@Mapper
public interface SysUserMapper {
/**
* 根据条件分页查询用户列表
*
* @param sysUser 用户信息
* @return 用户信息集合信息
*/
public List<SysUser> selectUserList(SysUser sysUser);
/**
* 新增用户信息
*
* @param user 用户信息
* @return 结果
*/
public int insertUser(SysUser user);
}
```
```xml
<!-- 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="com.ruoyi.system.mapper.SysUserMapper">
<resultMap type="SysUser" id="SysUserResult">
<id property="userId" column="user_id" />
<result property="deptId" column="dept_id" />
<result property="userName" column="user_name" />
<result property="nickName" column="nick_name" />
<result property="email" column="email" />
<result property="phonenumber" column="phonenumber" />
<result property="sex" column="sex" />
<result property="avatar" column="avatar" />
<result property="password" column="password" />
<result property="status" column="status" />
<result property="delFlag" column="del_flag" />
<result property="loginIp" column="login_ip" />
<result property="loginDate" column="login_date" />
<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" />
<association property="dept" column="dept_id" javaType="SysDept" resultMap="deptResult" />
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
</resultMap>
<sql id="selectUserVo">
select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
d.dept_id, d.parent_id, d.dept_name, d.order_num, d.leader, d.status as dept_status,
r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
left join sys_user_role ur on u.user_id = ur.user_id
left join sys_role r on r.role_id = ur.role_id
</sql>
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
left join sys_user_role ur on u.user_id = ur.user_id
left join sys_role r on r.role_id = ur.role_id
where u.del_flag = '0'
<if test="userId != null and userId != 0">
AND u.user_id = #{userId}
</if>
<if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%')
</if>
<if test="status != null and status != ''">
AND u.status = #{status}
</if>
<if test="phonenumber != null and phonenumber != ''">
AND u.phonenumber like concat('%', #{phonenumber}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date_format(u.create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
AND date_format(u.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
</if>
<if test="deptId != null and deptId != 0">
AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
</if>
<!-- 数据范围过滤 -->
${params.dataScope}
order by u.create_time desc
</select>
<insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
insert into sys_user(
<if test="userId != null and userId != 0">user_id,</if>
<if test="deptId != null and deptId != 0">dept_id,</if>
<if test="userName != null and userName != ''">user_name,</if>
<if test="nickName != null and nickName != ''">nick_name,</if>
<if test="email != null and email != ''">email,</if>
<if test="avatar != null and avatar != ''">avatar,</if>
<if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
<if test="sex != null and sex != ''">sex,</if>
<if test="password != null and password != ''">password,</if>
<if test="status != null and status != ''">status,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="remark != null and remark != ''">remark,</if>
create_time
)values(
<if test="userId != null and userId != ''">#{userId},</if>
<if test="deptId != null and deptId != ''">#{deptId},</if>
<if test="userName != null and userName != ''">#{userName},</if>
<if test="nickName != null and nickName != ''">#{nickName},</if>
<if test="email != null and email != ''">#{email},</if>
<if test="avatar != null and avatar != ''">#{avatar},</if>
<if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
<if test="sex != null and sex != ''">#{sex},</if>
<if test="password != null and password != ''">#{password},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="remark != null and remark != ''">#{remark},</if>
sysdate()
)
</insert>
</mapper>
```
#### 缓存使用实践
```java
@Service
public class SysDictDataServiceImpl implements ISysDictDataService {
@Autowired
private RedisCache redisCache;
@Autowired
private SysDictDataMapper dictDataMapper;
/**
* 根据字典类型查询字典数据
*/
@Override
public List<SysDictData> selectDictDataByType(String dictType) {
String cacheKey = getCacheKey(dictType);
List<SysDictData> dictDatas = redisCache.getCacheObject(cacheKey);
if (StringUtils.isNotEmpty(dictDatas)) {
return dictDatas;
}
dictDatas = dictDataMapper.selectDictDataByType(dictType);
if (StringUtils.isNotEmpty(dictDatas)) {
redisCache.setCacheObject(cacheKey, dictDatas, 30, TimeUnit.MINUTES);
}
return dictDatas;
}
/**
* 更新字典数据
*/
@Override
public int updateDictData(SysDictData dictData) {
int row = dictDataMapper.updateDictData(dictData);
if (row > 0) {
// 清除相关缓存
redisCache.deleteObject(getCacheKey(dictData.getDictType()));
}
return row;
}
private String getCacheKey(String dictType) {
return CacheConstants.SYS_DICT_KEY + dictType;
}
}
```
### 4.2 前端开发实践
#### Vue组件开发实践
**1. 页面组件结构**
```vue
<template>
<div class="app-container">
<!-- 查询条件区域 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
<!-- 查询条件字段 -->
</el-form>
<!-- 工具按钮区域 -->
<el-row :gutter="10" class="mb8">
<!-- 操作按钮 -->
</el-row>
<!-- 数据表格区域 -->
<el-table v-loading="loading" :data="dataList">
<!-- 表格列定义 -->
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 弹出框组件 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<!-- 表单内容 -->
</el-dialog>
</div>
</template>
<script>
export default {
name: "ComponentName",
data() {
return {
// 数据定义
};
},
created() {
this.getList();
},
methods: {
// 方法定义
}
};
</script>
<style scoped>
/* 样式定义 */
</style>
```
**2. API调用封装**
```javascript
// api/system/user.js
import request from '@/utils/request'
// 查询用户列表
export function listUser(query) {
return request({
url: '/system/user/list',
method: 'get',
params: query
})
}
// 查询用户详细
export function getUser(userId) {
return request({
url: '/system/user/' + userId,
method: 'get'
})
}
// 新增用户
export function addUser(data) {
return request({
url: '/system/user',
method: 'post',
data: data
})
}
// 修改用户
export function updateUser(data) {
return request({
url: '/system/user',
method: 'put',
data: data
})
}
// 删除用户
export function delUser(userId) {
return request({
url: '/system/user/' + userId,
method: 'delete'
})
}
```
**3. 工具函数封装**
```javascript
// utils/index.js
/**
* 参数处理
* @param {*} params 参数
*/
export function tansParams(params) {
let result = ''
for (const propName of Object.keys(params)) {
const value = params[propName];
var part = encodeURIComponent(propName) + "=";
if (value !== null && typeof(value) !== "undefined") {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && typeof (value[key]) !== 'undefined') {
let params = propName + '[' + key + ']';
var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&";
}
}
} else {
result += part + encodeURIComponent(value) + "&";
}
}
}
return result
}
/**
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
*/
export function handleTree(data, id, parentId, children) {
let config = {
id: id || 'id',
parentId: parentId || 'parentId',
childrenList: children || 'children'
};
var childrenListMap = {};
var nodeIds = {};
var tree = [];
for (let d of data) {
let parentId = d[config.parentId];
if (childrenListMap[parentId] == null) {
childrenListMap[parentId] = [];
}
nodeIds[d[config.id]] = d;
childrenListMap[parentId].push(d);
}
for (let d of data) {
let parentId = d[config.parentId];
if (nodeIds[parentId] == null) {
tree.push(d);
}
}
for (let t of tree) {
adaptToChildrenList(t);
}
function adaptToChildrenList(o) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]];
}
if (o[config.childrenList]) {
for (let c of o[config.childrenList]) {
adaptToChildrenList(c);
}
}
}
return tree;
}
```
## 5. 代码生成器使用指南
### 5.1 使用流程
#### Step 1: 创建数据库表
```sql
-- 创建示例表
DROP TABLE IF EXISTS `test_demo`;
CREATE TABLE `test_demo` (
`demo_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`demo_name` varchar(30) DEFAULT '' COMMENT '名称',
`demo_type` char(1) DEFAULT '0' COMMENT '类型0正常 1停用',
`demo_status` char(1) DEFAULT '0' COMMENT '状态0正常 1删除',
`demo_content` longtext COMMENT '内容',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`demo_id`)
) ENGINE=InnoDB COMMENT = '测试演示表';
```
#### Step 2: 导入表到代码生成
1. 登录系统,进入 `系统工具 -> 代码生成`
2. 点击 `导入` 按钮,选择需要生成代码的表
3. 点击 `导入` 确认
#### Step 3: 配置生成信息
```java
// 基本信息配置
表名称: test_demo
表描述: 测试演示表
实体类名称: TestDemo
作者: ruoyi
生成包路径: com.ruoyi.system
生成模块名: system
生成业务名: demo
生成功能名: 测试演示
上级菜单: 系统工具
```
#### Step 4: 字段信息配置
| 字段名 | 字段描述 | 字段类型 | Java类型 | Java字段 | 是否必填 | 是否为插入字段 | 是否为编辑字段 | 是否为列表字段 | 是否为查询字段 | 查询方式 | 显示类型 |
|--------|----------|----------|----------|----------|----------|---------------|---------------|---------------|---------------|----------|----------|
| demo_id | 主键 | bigint | Long | demoId | 否 | 否 | 否 | 否 | 否 | = | 文本框 |
| demo_name | 名称 | varchar(30) | String | demoName | 是 | 是 | 是 | 是 | 是 | LIKE | 文本框 |
| demo_type | 类型 | char(1) | String | demoType | 否 | 是 | 是 | 是 | 是 | = | 选择框 |
| demo_status | 状态 | char(1) | String | demoStatus | 否 | 是 | 是 | 是 | 是 | = | 单选框 |
| demo_content | 内容 | longtext | String | demoContent | 否 | 是 | 是 | 否 | 否 | = | 文本域 |
#### Step 5: 生成代码
1. 配置完成后,点击 `生成代码` 按钮
2. 下载生成的代码包
3. 解压并导入到项目中
### 5.2 生成的代码结构
```
ruoyi-system/
├── domain/
│ └── TestDemo.java # 实体类
├── mapper/
│ ├── TestDemoMapper.java # Mapper接口
│ └── TestDemoMapper.xml # Mapper XML
├── service/
│ ├── ITestDemoService.java # Service接口
│ └── impl/
│ └── TestDemoServiceImpl.java # Service实现
└── controller/
└── TestDemoController.java # Controller
ruoyi-ui/src/
├── api/system/
│ └── demo.js # API接口
└── views/system/demo/
└── index.vue # 页面组件
```
### 5.3 生成后的集成步骤
#### Step 1: 后端集成
```java
// 1. 确认文件已正确放置在对应目录
// 2. 无需额外配置Spring Boot会自动扫描
// 3. 如果使用了自定义配置,需要检查包扫描路径
@SpringBootApplication
@ComponentScan(basePackages = {"com.ruoyi"})
public class RuoYiApplication {
public static void main(String[] args) {
SpringApplication.run(RuoYiApplication.class, args);
}
}
```
#### Step 2: 前端集成
```javascript
// 1. 将生成的 demo.js 放置到 src/api/system/ 目录
// 2. 将生成的 index.vue 放置到 src/views/system/demo/ 目录
// 3. 在路由中添加新的路由配置(如果需要)
// router/index.js
{
path: '/system/demo',
component: Layout,
hidden: true,
children: [
{
path: 'index',
component: () => import('@/views/system/demo/index'),
name: 'Demo',
meta: { title: '测试演示', icon: '' }
}
]
}
```
#### Step 3: 菜单配置
1. 登录系统,进入 `系统管理 -> 菜单管理`
2. 新增菜单项:
```
菜单名称: 测试演示
上级菜单: 系统工具
菜单类型: 菜单
路由地址: demo
组件路径: system/demo/index
权限字符: system:demo:list
```
3. 新增按钮权限:
```
新增: system:demo:add
修改: system:demo:edit
删除: system:demo:remove
导出: system:demo:export
```
## 6. 部署指南
### 6.1 开发环境部署
#### 本地开发部署
```bash
# 1. 启动MySQL和Redis服务
docker-compose up -d mysql redis
# 2. 导入数据库脚本
mysql -u root -p ry-vue < sql/ry_20210908.sql
# 3. 启动后端服务
cd ruoyi-admin
mvn spring-boot:run
# 4. 启动前端服务
cd ruoyi-ui
npm install
npm run dev
# 5. 访问系统
# 前端: http://localhost
# 后端: http://localhost:8080
```
### 6.2 生产环境部署
#### 传统部署方式
**1. 后端部署**
```bash
# 1. Maven打包
mvn clean package -Dmaven.test.skip=true
# 2. 上传jar包到服务器
scp ruoyi-admin/target/ruoyi-admin.jar user@server:/opt/ruoyi/
# 3. 创建启动脚本
cat > /opt/ruoyi/start.sh << 'EOF'
#!/bin/bash
export JAVA_OPTS="-Xms512m -Xmx2048m -Xss256k"
export SERVER_PORT=8080
export SPRING_PROFILES_ACTIVE=prod
nohup java $JAVA_OPTS -jar ruoyi-admin.jar \
--server.port=$SERVER_PORT \
--spring.profiles.active=$SPRING_PROFILES_ACTIVE \
> logs/application.log 2>&1 &
echo $! > ruoyi.pid
echo "RuoYi started with PID: $(cat ruoyi.pid)"
EOF
chmod +x start.sh
# 4. 启动服务
./start.sh
```
**2. 前端部署**
```bash
# 1. 构建生产版本
npm run build:prod
# 2. 上传到Web服务器
scp -r dist/ user@server:/var/www/html/ruoyi/
# 3. 配置Nginx
cat > /etc/nginx/conf.d/ruoyi.conf << 'EOF'
server {
listen 80;
server_name your-domain.com;
location / {
root /var/www/html/ruoyi;
index index.html;
try_files $uri $uri/ /index.html;
}
location /prod-api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
EOF
# 4. 重启Nginx
nginx -s reload
```
#### Docker容器化部署
**1. 创建Dockerfile**
```dockerfile
# 后端Dockerfile
FROM openjdk:8-jre-alpine
WORKDIR /app
COPY ruoyi-admin/target/ruoyi-admin.jar app.jar
ENV JAVA_OPTS="-Xms512m -Xmx1024m"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
```
```dockerfile
# 前端Dockerfile
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
**2. 创建docker-compose.yml**
```yaml
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: ruoyi-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: ry-vue
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d
redis:
image: redis:6.2-alpine
container_name: ruoyi-redis
restart: always
ports:
- "6379:6379"
volumes:
- redis_data:/data
ruoyi-backend:
build: .
container_name: ruoyi-backend
restart: always
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
SPRING_REDIS_HOST: redis
depends_on:
- mysql
- redis
ruoyi-frontend:
build:
context: .
dockerfile: Dockerfile.frontend
container_name: ruoyi-frontend
restart: always
ports:
- "80:80"
depends_on:
- ruoyi-backend
volumes:
mysql_data:
redis_data:
```
**3. 部署命令**
```bash
# 1. 构建并启动所有服务
docker-compose up -d
# 2. 查看服务状态
docker-compose ps
# 3. 查看日志
docker-compose logs -f ruoyi-backend
# 4. 停止服务
docker-compose down
```
### 6.3 监控和运维
#### 应用监控配置
```yaml
# application-prod.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
metrics:
export:
prometheus:
enabled: true
```
#### 日志配置
```xml
<!-- logback-spring.xml -->
<configuration>
<springProfile name="prod">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</springProfile>
</configuration>
```
## 7. 常见问题与解决方案
### 7.1 环境问题
**Q: Maven依赖下载失败**
```bash
# A: 配置国内镜像源
# ~/.m2/settings.xml
<mirror>
<id>aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Aliyun Central</name>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
```
**Q: NPM包安装失败**
```bash
# A: 使用国内镜像
npm config set registry https://registry.npmmirror.com/
# 或使用yarn
yarn config set registry https://registry.npmmirror.com/
```
**Q: MySQL连接失败**
```yaml
# A: 检查数据库配置和权限
spring:
datasource:
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
```
### 7.2 开发问题
**Q: 跨域问题**
```java
// A: 后端跨域配置
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(1800L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
```
**Q: 权限验证失败**
```java
// A: 检查权限配置和用户角色
@PreAuthorize("@ss.hasPermi('system:user:list')")
// 确保用户拥有对应权限
```
### 7.3 部署问题
**Q: 前端页面空白**
```javascript
// A: 检查publicPath配置
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
}
```
**Q: 后端接口404**
```yaml
# A: 检查context-path配置
server:
servlet:
context-path: /
```
## 8. 总结
本开发指南提供了RuoYi-Vue项目从环境搭建到生产部署的完整开发流程涵盖了
**环境准备**:详细的开发环境安装和配置指南
**快速启动**:项目快速启动和验证步骤
**开发规范**代码规范和API设计最佳实践
**开发实践**:分层架构和组件开发最佳实践
**代码生成**:代码生成器的使用方法和集成步骤
**部署指南**:从开发环境到生产环境的部署方案
**问题解决**:常见问题的解决方案
通过遵循本指南,开发者可以:
- 快速搭建开发环境并启动项目
- 掌握项目的开发规范和最佳实践
- 高效使用代码生成器提升开发效率
- 顺利完成项目的部署和上线
- 解决开发过程中的常见问题
RuoYi-Vue作为一个成熟的企业级开发框架为快速构建高质量的管理系统提供了坚实的基础。结合本开发指南开发团队能够快速上手并充分发挥框架的优势提升项目开发效率和质量。