- 添加CLAUDE.md项目配置指南 - 添加document目录包含详细的架构分析文档 - 添加doc目录包含环境使用手册 - 添加bin目录包含构建和清理脚本 - 添加.github配置文件
1640 lines
41 KiB
Markdown
1640 lines
41 KiB
Markdown
# 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') >= 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') <= 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作为一个成熟的企业级开发框架,为快速构建高质量的管理系统提供了坚实的基础。结合本开发指南,开发团队能够快速上手并充分发挥框架的优势,提升项目开发效率和质量。 |