一.需求说明
人员管理业务流程如下:
登录系统: 首先,后台管理人员需要登录到帝可得后台管理系统中。
新增工作人员: 登录系统后,管理人员可以新增工作人员,包括姓名、联系方式等信息。
关联角色: 确定此员工是运维人员还是运营人员,这将影响他们的职责和权限。
关联区域: 确定员工负责的区域范围,确保工作人员能够高效地完成区域内的设备安装、维修、商品补货等工作。
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视图组件中修改
<!-- 搜索区域 -->
<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>
新增员工
我们新增员工之后会发现归属区域和角色名称没有新增上,需要后端新增时候进行字段补充新增,同样的修改时也需要进行维护,实现如下
@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
-- 根据区域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命令行,执行如下命令,配置系统的环境变量。
set OSS_ACCESS_KEY_ID=LTxxxxxxxxxxxxxxxxxxxxxku
set OSS_ACCESS_KEY_SECRET=JaaxxxxxxxxxxxxxxxxxxxxxxN2k
注意:将上述的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%
到此环境变量配置成功了,环境变量的目的是为了SDK入门案例使用
3.3.3 阿里云OSS入门
打开文档
在Java公共模块common中导入SDK依赖
演示代码
新建test测试类,讲测试代码复制粘贴就ok✌
参照官方提供的SDK还有自己的bucket桶的配置改造一下代码,重启idea加载环境变量,即可实现文件上传功能:
需要替换的内容为:
endpoint:阿里云OSS中的bucket对应的域名
bucketName:Bucket名称
objectName:对象名称,在Bucket中存储的对象的名称
filePath:文件路径
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当中
修改代码👇
@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当中~✌