一.需求说明
人员管理业务流程如下:
-
登录系统: 首先,后台管理人员需要登录到帝可得后台管理系统中。
-
新增工作人员: 登录系统后,管理人员可以新增工作人员,包括姓名、联系方式等信息。
-
关联角色: 确定此员工是运维人员还是运营人员,这将影响他们的职责和权限。
-
关联区域: 确定员工负责的区域范围,确保工作人员能够高效地完成区域内的设备安装、维修、商品补货等工作。
graph TD
A[登录系统]
A --> B[新增员工]
B --> C[关联角色]
B --> D[关联区域]
对于人员和其他管理数据,下面是示意图:
-
关系字段:role_id、region_id
-
数据字典:status(1启用、0停用)
-
冗余字段:region_name、role_code、role_name(单表,提高查询效率)

二. 生成基础代码
2.1 创建目录菜单

2.2 添加数据字典

2.3 配置代码生成信息
2.3.1 导入表(员工tb_emp、角色tb_role)

2.3.2 配置信息
员工

角色

2.4 下载代码并导入项目
数据库 sql

前后端

重启项目运行
三. 人员列表改造
3.1 基础页面
参考产品原型进行修改

将新增人员列表对话框的角色 和所属区域改为下拉框需要这么几步
-
引入通过id查询角色/区域列表的api接口
-
写查询角色/区域列表的函数方法
-
将组件改为下拉框,数据对应
前端接收参数的时候一定要看好后端响应的数据是什么样的

效果完成✌

代码实现
在emp/index.vue视图组件中修改
html
<!-- 搜索区域 -->
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="人员名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入人员名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 人员列表 -->
<el-table v-loading="loading" :data="empList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="80" align="center" prop="id" />
<el-table-column label="人员名称" align="center" prop="userName" />
<el-table-column label="归属区域" align="center" prop="regionName" />
<el-table-column label="角色" align="center" prop="roleName" />
<el-table-column label="联系电话" align="center" prop="mobile" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['manage:emp:edit']">修改</el-button>
<el-button link type="primary" @click="handleDelete(scope.row)" v-hasPermi="['manage:emp:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改人员列表对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="empRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="员工名称" prop="userName">
<el-input v-model="form.userName" placeholder="请输入员工名称" />
</el-form-item>
<el-form-item label="角色" prop="roleId">
<!-- <el-input v-model="form.roleId" placeholder="请输入角色id" /> -->
<el-select v-model="form.roleId" placeholder="请选择角色">
<el-option v-for="item in roleList" :key="item.roleId" :label="item.roleName" :value="item.roleId" />
</el-select>
</el-form-item>
<el-form-item label="联系电话" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="创建时间" prop="createTime" v-if="form.id!=null">
{{form.createTime }}
</el-form-item>
<el-form-item label="负责区域" prop="regionId">
<!-- <el-input v-model="form.regionId" placeholder="请输入所属区域Id" /> -->
<el-select v-model="form.regionId" placeholder="请选择所属区域">
<el-option v-for="item in regionList" :key="item.id" :label="item.regionName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="员工头像" prop="image">
<image-upload v-model="form.image" />
</el-form-item>
<el-form-item label="是否启用" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in emp_status" :key="dict.value"
:label="parseInt(dict.value)">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
<script>
import { listRegion } from "@/api/manage/region";
import { listRole } from "@/api/manage/role";
import { loadAllParams } from "@/api/page";
// 查询角色列表
const roleList = ref([]);
function getRoleList() {
listRole(loadAllParams).then(response => {
roleList.value = response.rows;
});
}
// 查询区域列表
const regionList = ref([]);
function getRegionList() {
listRegion(loadAllParams).then(response => {
regionList.value = response.rows;
});
}
getRegionList();
getRoleList();
</script>
新增员工

我们新增员工之后会发现归属区域和角色名称没有新增上,需要后端新增时候进行字段补充新增,同样的修改时也需要进行维护,实现如下
java
@Autowired
private RegionMapper regionMapper;
@Autowired
private RoleMapper roleMapper;
/**
* 新增人员列表
*
* @param emp 人员列表
* @return 结果
*/
@Override
public int insertEmp(Emp emp)
{
emp.setCreateTime(DateUtils.getNowDate());
// 补充区域名称
emp.setRegionName(regionMapper.selectRegionById(emp.getRegionId()).getRegionName());
// 补充角色信息
Role role = roleMapper.selectRoleByRoleId(emp.getRoleId());
emp.setRoleName(role.getRoleName());
emp.setRoleCode(role.getRoleCode());
return empMapper.insertEmp(emp);
}
/**
* 修改人员列表
*
* @param emp 人员列表
* @return 结果
*/
@Override
public int updateEmp(Emp emp)
{
emp.setUpdateTime(DateUtils.getNowDate());
// 补充区域名称
emp.setRegionName(regionMapper.selectRegionById(emp.getRegionId()).getRegionName());
// 补充角色信息
Role role = roleMapper.selectRoleByRoleId(emp.getRoleId());
emp.setRoleName(role.getRoleName());
emp.setRoleCode(role.getRoleCode());
return empMapper.updateEmp(emp);
}
页面和数据库都可以成功新增,完美✌
3.2 同步存储
3.2.1 实现思路
**同步存储:**在员工表中有区域名称的冗余字段,在更新区域表的同时,同步更新员工表中区域名称。
-
优点:由于是单表查询操作,查询列表效率最高。
-
缺点:需要在区域修改时修改员工表中的数据,有额外的开销,数据也可能不一致。

比如修改了区域的名称,但是员工对应的负责区域还是无法进行同步修改,所以需要手动的在后端进行业务操作,实现步骤如下:
3.2.2 实现
- sql
sql
-- 根据区域id修改区域名称
update tb_emp set region_name='北京市奥体中心' where region_id=5
- EmpMapper层

- 在RegionServiceImpl实现类进行区域新增时,同步更新员工表区域名称,需要进行事务管理,同成同败

效果如下✌

3.3 文件存储
3.3.1 本地存储
问题分析说明: 在若依框架目前的实现中,是把图片存储到了服务器本地的目录,通过服务进行访问,这样做存储的是比较省事,但是缺点也有很多:
-
硬件与网络要求:服务器通常需要高性能的硬件和稳定的网络环境,以保证文件传输的效率和稳定性。这可能会增加硬件和网络资源的成本和维护难度。
-
管理难度:服务器目录需要管理员进行配置和管理,包括权限设置、备份策略等。如果管理不善或配置不当,可能会引发一些安全问题和性能问题。
-
性能瓶颈:如果服务器处理能力不足或网络带宽不够,可能会导致性能瓶颈,影响文件上传、下载和访问的速度。
-
单点故障风险:服务器故障可能导致所有存储在其上的文件无法访问,尽管可以通过备份和冗余措施来降低这种风险,但单点故障的风险仍然存在。
为了解决上述问题呢,通常有两种解决方案:
-
自己搭建存储服务器,如:fastDFS 、MinIO
-
使用现成的云服务,如:阿里云,腾讯云,华为云
3.3.2 阿里云OSS
1.介绍
阿里云对象存储OSS(Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。

在我们使用了阿里云OSS对象存储服务之后,我们的项目当中如果涉及到文件上传这样的业务,在前端进行文件上传并请求到服务端时,在服务器本地磁盘当中就不需要再来存储文件了。我们直接将接收到的文件上传到oss,由 oss帮我们存储和管理,同时阿里云的oss存储服务还保障了我们所存储内容的安全可靠。

使用的阿里云oss对象存储服务具体的使用步骤。

Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。
SDK:Software Development Kit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做SDK。
简单说,sdk中包含了我们使用第三方云服务时所需要的依赖,以及一些示例代码。我们可以参照sdk所提供的示例代码就可以完成入门程序。
2.账号准备
下面我们根据之前介绍的使用步骤,完成准备工作:
- 注册阿里云账户(注册完成后需要实名认证)

- 注册完账号之后,就可以登录阿里云

3.开通OSS云服务
登录之后搜索OSS
还未开通的新用户可以免费开通
回到OSS控制台,购买完成了~

创建Bucket

注意需要配置Bucket的访问权限:允许公共访问,公共读!!!

帝可得项目是通过管理系统实现文件上传,是Java代码与阿里云OSS实现对接,对接需要一个身份验证的凭证,这个凭证就是AccessKey。
- 创建AccessKey


- 配置AK & SK
以管理员身份 打开CMD命令行,执行如下命令,配置系统的环境变量。
sql
set OSS_ACCESS_KEY_ID=LTxxxxxxxxxxxxxxxxxxxxxku
set OSS_ACCESS_KEY_SECRET=JaaxxxxxxxxxxxxxxxxxxxxxxN2k
注意:将上述的ACCESS_KEY_ID 与 ACCESS_KEY_SECRET 的值一定一定一定一定一定一定要替换成自己的 。
- 执行一下命令,使更改生效
sql
setx OSS_ACCESS_KEY_ID "%OSS_ACCESS_KEY_ID%"
setx OSS_ACCESS_KEY_SECRET "%OSS_ACCESS_KEY_SECRET%"
- 执行如下命令,验证环境变量是否生效。
sql
echo %OSS_ACCESS_KEY_ID%
echo %OSS_ACCESS_KEY_SECRET%
到此环境变量配置成功了,环境变量的目的是为了SDK入门案例使用
3.3.3 阿里云OSS入门
- 打开文档

在Java公共模块common中导入SDK依赖


演示代码

新建test测试类,讲测试代码复制粘贴就ok✌

参照官方提供的SDK还有自己的bucket桶的配置改造一下代码,重启idea加载环境变量,即可实现文件上传功能:
需要替换的内容为:
-
endpoint:阿里云OSS中的bucket对应的域名
-
bucketName:Bucket名称
-
objectName:对象名称,在Bucket中存储的对象的名称
-
filePath:文件路径
sql
package com.dkd.common;
import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
public class test {
public static void main(String[] args) throws com.aliyuncs.exceptions.ClientException {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "https://oss-cn-beijing.aliyuncs.com";
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 填写Bucket名称,例如examplebucket。
String bucketName = "dkd-xiaoyang";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String objectName = "头像.jpg";
// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
String filePath= "D:\\学习\\images\\头像.jpg";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
try {
InputStream inputStream = new FileInputStream(filePath);
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
// 创建PutObject请求。
PutObjectResult result = ossClient.putObject(putObjectRequest);
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
}
回到阿里云查看图片

3.3.4 x-file-storage

1.介绍
官方地址:X File Storage
一行代码实现多平台的存储,强大(๑•̀ㅂ•́)و✧
一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 Amazon S3、GoogleCloud Storage、FastDFS、 Azure Blob Storage、Cloudflare R2、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的存储平台。
2.集成步骤
- 在dkd-common的pom.xml中引入依赖
- 在dkd-admin的
application.yml
配置文件中先添加以下基础配置,再添加对应平台的配置
- 在启动类上加上
@EnableFileStorage
注解

- 找到照片上传的请求路径common/upload

- 找到ruoyi-admin模块中的CommonController类,修改单个文件上传的方法
原本的照片下载到若依默认的本地存储,现在需要使用x-file-storage将照片下载到阿里云OSS当中

修改代码👇
sql
@Autowired
private FileStorageService fileStorageService;//注入实列
/**
* 通用上传请求(单个)
*/
@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file) throws Exception
{
try
{
// // 上传文件路径
// String filePath = RuoYiConfig.getUploadPath();
// // 上传并返回新文件名称
// String fileName = FileUploadUtils.upload(filePath, file);
// String url = serverConfig.getUrl() + fileName;
// 指定oss保存文件路径 dkd-images/2024/04/27/文件名
String objectName = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))+"/";
// 上传图片,返回文件信息
FileInfo fileInfo = fileStorageService.of(file)
.setPath(objectName) //保存到相对路径下,为了方便管理,不需要可以不写
.upload();
AjaxResult ajax = AjaxResult.success();
ajax.put("url", fileInfo.getUrl());
ajax.put("fileName", fileInfo.getUrl()); // 注意:这里的值需要改为URL,因为前端的访问地址会做一个判断,如果以http开头就直接显示
ajax.put("newFileName", fileInfo.getUrl());
ajax.put("originalFilename", file.getOriginalFilename());
return ajax;
}
catch (Exception e)
{
return AjaxResult.error(e.getMessage());
}
}
需要注意的是fileName的值,前端进行判断,如果以http开头的话,不需要拼接,直接返回

重新启动项目,新增人员进行测试
在阿里云Backet进行查看,成功通过x-file-storage将照片下载到阿里云OSS当中~✌