2、人员管理
2.1、需求说明
人员管理业务流程如下:
-
登录系统: 首先,后台管理人员需要登录到帝可得后台管理系统中。
-
新增工作人员: 登录系统后,管理人员可以新增工作人员,包括姓名、联系方式等信息。
-
关联角色: 确定此员工是运维人员还是运营人员,这将影响他们的职责和权限。
-
关联区域: 确定员工负责的区域范围,确保工作人员能够高效地完成区域内的设备安装、维修、商品补货等工作。
对于人员和其他管理数据,下面是示意图:
-
关系字段:role_id、region_id
-
数据字典:status(1启用、0停用)
-
冗余字段:region_name、role_code、role_name(提高查询效率)
这里角色和员工表是定制的,不是若依自带的角色权限,是与app有关
不能设置为字典,因为数据字典只能设置两个,1 字典标签 2字典值
而角色表中有三个字段
![]()
2.2、生成基础代码
2.2.1、需求
使用若依代码生成器,生成人员列表前后端基础代码,并导入到项目中:
2.2.2、步骤
①创建目录菜单
创建人员管理目录菜单
②添加数据字典
先创建员工状态
的字典类型
再创建员工状态
的字典数据
③配置代码生成信息
导入二张表
配置员工表(参考原型)
配置角色表(无原型)
④下载代码并导入项目
选中二张表生成下载
解压ruoyi.zip
得到前后端代码和动态菜单sql
注意:角色动态菜单sql和视图组件不需要导入
执行sql后:
代码导入(后端可以直接java和resource两个大文件夹一起复制)
前端导入
重启后端 【如果前端没有数据,1、重新登录,2、后端maven刷新重新启动】
2.3、人员列表改造
2.3.1、基础页面
2.3.1.1、需求
参考页面原型,完成基础布局展示改造
2.3.1.2、代码实现
1、搜索框
2、主键改序号
3、操作图片删除
4、新增修改对话框
1、我们在当前页面去引用角色的api请求js文件,然后在定义一个查询所有的方法向后台发送请求,拿到角色列表封装给角色的集合
2、将文本框改为下拉框,遍历集合,展示角色信息,用户选择以后就会提交角色的id
1、
可以先测试一下
2、将文本款改为下拉框
(label代表前端的显示、value代表提交的id)
注意下面的item.id应该改为item.roleId(看数据字段)
测试:
然后继续将所属区域id的文本框改为下拉款(和之前一样也是2步)
1、
2、
1、prop="regionId":用于表单校验,表明该表单项对应的数据字段是
regionId
(通常与form.regionId
绑定)。2、v-model="form.regionId":数据双向绑定,将选中的值赋给
form.regionId
,也就是说用户选择哪个区域,form.regionId
就会保存该区域的id
。3、:key="item.id":为每个选项设置唯一的
key
,通常使用区域的id
。4、:value="item.id":选项的实际值为区域的
id
,当用户选中该选项时,form.regionId
就会被赋值为这个id
。5、:label="item.regionName":选项的显示文本为区域名称(
regionName
)。比如:
然后我修改提交后(这里就会变化)
因为新增和修改框不一样(修改和新增的区别是,修改有id,新增没有id)
所以改一下修改框(用v-if)
在emp/index.vue视图组件中修改
<!-- 搜索区域 -->
<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>
目前问题:(我们新增时保存的是区域id和角色id)
所以我们要在后端进行修改,根据两个id查询到数据后在填充到数据库中的区域名称和角色名称
所以我们需要在新增员工的接口中进行修改
在EmpServiceImpl中新增和修改时,补充区域名称和角色信息
@Autowired
private RegionMapper regionMapper;@Autowired
private RoleMapper roleMapper;/*** 新增人员列表** @param emp 人员列表* @return 结果*/
@Override
public int insertEmp(Emp emp)
{// 补充区域名称emp.setRegionName(regionMapper.selectRegionById(emp.getRegionId()).getRegionName());// 补充角色信息Role role = roleMapper.selectRoleByRoleId(emp.getRoleId());emp.setRoleName(role.getRoleName());emp.setRoleCode(role.getRoleCode());emp.setCreateTime(DateUtils.getNowDate());return empMapper.insertEmp(emp);
}/*** 修改人员列表** @param emp 人员列表* @return 结果*/
@Override
public int updateEmp(Emp emp)
{// 补充区域名称emp.setRegionName(regionMapper.selectRegionById(emp.getRegionId()).getRegionName());// 补充角色信息Role role = roleMapper.selectRoleByRoleId(emp.getRoleId());emp.setRoleName(role.getRoleName());emp.setRoleCode(role.getRoleCode());emp.setUpdateTime(DateUtils.getNowDate());return empMapper.updateEmp(emp);
}
重新启动
为什么要这么做,是因为加了一些冗余数据
2.3.2、同步存储
但是还是会出现一些问题
1、我们把区域名称改一个名字
2、但是员工界面,区域名称还是旧名称(修改界面中查询的是区域id返回的区域名称,没有问题)
2.3.2.1、实现思路
实现此功能方案:
同步存储:在员工表中有区域名称的冗余字段,在更新区域表的同时,同步更新员工表中区域名称。
-
优点:由于是单表查询操作,查询列表效率最高。
-
缺点:需要在区域修改时修改员工表中的数据,有额外的开销,数据也可能不一致。
2.3.2.2、sql
-- 根据区域id修改区域名称
update tb_emp set region_name='北京市奥体中心' where region_id=5
EmpMapper
/*** 根据区域id修改区域名称* @param regionName* @param regionId* @return 结果*/
@Update("update tb_emp set region_name=#{regionName} where region_id=#{regionId}")
int updateByRegionId(@Param("regionName") String regionName, @Param("regionId") Long regionId);
RegionServiceImpl
@Autowired
private EmpMapper empMapper;/*** 修改区域管理** @param region 区域管理* @return 结果*/
@Transactional(rollbackFor = Exception.class)
@Override
public int updateRegion(Region region)
{// 先更新区域信息region.setUpdateTime(DateUtils.getNowDate());int result = regionMapper.updateRegion(region);// 同步更新员工表区域名称empMapper.updateByRegionId(region.getRegionName(),region.getId());return result;
}
涉及到两张表的更新,要么都成功,要么都失败,所以要用到事务
2.4、文件存储
2.4.1、本地存储
目前若依图片保存在本地(路径都是一样的)
问题分析说明: 在若依框架目前的实现中,是把图片存储到了服务器本地的目录,通过服务进行访问,这样做存储的是比较省事,但是缺点也有很多:
-
硬件与网络要求:服务器通常需要高性能的硬件和稳定的网络环境,以保证文件传输的效率和稳定性。这可能会增加硬件和网络资源的成本和维护难度。
-
管理难度:服务器目录需要管理员进行配置和管理,包括权限设置、备份策略等。如果管理不善或配置不当,可能会引发一些安全问题和性能问题。
-
性能瓶颈:如果服务器处理能力不足或网络带宽不够,可能会导致性能瓶颈,影响文件上传、下载和访问的速度。
-
单点故障风险:服务器故障可能导致所有存储在其上的文件无法访问,尽管可以通过备份和冗余措施来降低这种风险,但单点故障的风险仍然存在。
为了解决上述问题呢,通常有两种解决方案:
-
自己搭建存储服务器,如:fastDFS 、MinIO
-
使用现成的云服务,如:阿里云,腾讯云,华为云
2.4.2、阿里云OSS
2.4.2.1、介绍
阿里云对象存储OSS(Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。
在我们使用了阿里云OSS对象存储服务之后,我们的项目当中如果涉及到文件上传这样的业务,在前端进行文件上传并请求到服务端时,在服务器本地磁盘当中就不需要再来存储文件了。我们直接将接收到的文件上传到oss,由 oss帮我们存储和管理,同时阿里云的oss存储服务还保障了我们所存储内容的安全可靠。
第三方服务使用的通用思路,我们做一个简单介绍之后,接下来我们就来介绍一下我们当前要使用的阿里云oss对象存储服务具体的使用步骤。
Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。
SDK:Software Development Kit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做SDK。
简单说,sdk中包含了我们使用第三方云服务时所需要的依赖,以及一些示例代码。我们可以参照sdk所提供的示例代码就可以完成入门程序。
2.4.2.2、账号准备
下面我们根据之前介绍的使用步骤,完成准备工作:
-
注册阿里云账户(注册完成后需要实名认证)
阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台
-
注册完账号之后,就可以登录阿里云
2.4.2.3、开通OSS云服务
1). 通过控制台找到对象存储OSS服务
如果是第一次访问,还需要开通对象存储服务OSS
2). 开通OSS服务之后,就可以进入到阿里云对象存储的控制台
3). 点击左侧的 "Bucket列表",创建一个Bucket
2.4.2.4、配置AK & SK
1). 创建AccessKey
点击 "AccessKey管理",进入到管理页面。
点击 "AccessKey"。
2). 配置AK & SK
以管理员身份打开CMD命令行,执行如下命令,配置系统的环境变量。
set OSS_ACCESS_KEY_ID=LTAI5tXXXXXXXXXXXXXXXXXXXXM8TP
set OSS_ACCESS_KEY_SECRET=UzMcJXXXXXXXXXXXXXXXXXXXXdabTNafi
注意:将上述的ACCESS_KEY_ID 与 ACCESS_KEY_SECRET 的值一定一定一定一定一定一定要替换成自己的 。
执行如下命令,让更改生效。
setx OSS_ACCESS_KEY_ID "%OSS_ACCESS_KEY_ID%"
setx OSS_ACCESS_KEY_SECRET "%OSS_ACCESS_KEY_SECRET%"
执行如下命令,验证环境变量是否生效。
echo %OSS_ACCESS_KEY_ID%
echo %OSS_ACCESS_KEY_SECRET%
2.4.2.5、入门
阿里云oss 对象存储服务的准备工作我们已经完成了,接下来我们就来完成第二步操作:参照官方所提供的sdk示例来编写入门程序。
首先我们需要来打开阿里云OSS的官方文档,在官方文档中找到 SDK 的示例代码:
如果是在实际开发当中,我们是需要从前往后仔细的去阅读这一份文档的,但是由于现在是教学,我们就只挑重点的去看。有兴趣的同学大家下来也可以自己去看一下这份官方文档。
参照官方提供的SDK,改造一下,即可实现文件上传功能:
package com.dkd.common.test;import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import java.io.FileInputStream;
import java.io.InputStream;public class Demo {public static void main(String[] args) throws Exception {// 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-itheima";// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。String objectName = "gao.png";// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。String filePath= "E:\\temp\\upload\\gao.png";// 创建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());} finally {if (ossClient != null) {ossClient.shutdown();}}}
}
以上代码中,需要替换的内容为:
endpoint:阿里云OSS中的bucket对应的域名
bucketName:Bucket名称
objectName:对象名称,在Bucket中存储的对象的名称
filePath:文件路径
运行以上程序后,会把本地的文件上传到阿里云OSS服务器上。
2.4.3、x-file-storage
2.4.3.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.4.3.2、集成
1)在dkd-common的pom.xml中引入依赖
<!-- 文件上传-->
<dependency><groupId>org.dromara.x-file-storage</groupId><artifactId>x-file-storage-spring</artifactId><version>2.1.0</version>
</dependency>
<!-- 阿里云oss-->
<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.16.1</version>
</dependency>
2)在dkd-admin的application.yml
配置文件中先添加以下基础配置,再添加对应平台的配置
# 文件上传
dromara:x-file-storage: #文件存储配置default-platform: aliyun-oss-1 #默认使用的存储平台thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】#对应平台的配置写在这里,注意缩进要对齐aliyun-oss:- platform: aliyun-oss-1 # 存储平台标识enable-storage: true # 启用存储access-key: ??secret-key: ??end-point: ??bucket-name: ??domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.oss-cn-shanghai.aliyuncs.com/base-path: dkd-images/ # 基础路径
3)在dkd-admin的启动类上加上@EnableFileStorage
注解
@EnableFileStorage
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class DkdApplication
{public static void main(String[] args){// System.setProperty("spring.devtools.restart.enabled", "false");SpringApplication.run(DkdApplication.class, args);System.out.println("(♥◠‿◠)ノ゙ 帝可得启动成功 ლ(´ڡ`ლ)゙");}
}
4)修改若依默认上传图片代码
找到ruoyi-admin模块中的com.ruoyi.web.controller.common.CommonController类,修改单个文件上传的方法
原来的进行改造:
fileName会和当前的请求地址进行拼接
@Autowired
private FileStorageService fileStorageService;//注入实列/*** 通用上传请求(单个)
*/
@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file) throws Exception {try {// 指定oss保存文件路径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,前端访问的地址,需要文件的地址 而不是文件名称ajax.put("newFileName", fileInfo.getUrl());ajax.put("originalFilename", file.getOriginalFilename());return ajax;} catch (Exception e) {return AjaxResult.error(e.getMessage());}
}
前端部分:
5)联调测试,重启后台程序,前端上传图片操作,F12抓包工具查看效果: