# 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作为一个成熟的企业级开发框架，为快速构建高质量的管理系统提供了坚实的基础。结合本开发指南，开发团队能够快速上手并充分发挥框架的优势，提升项目开发效率和质量。