帝可得- 人员管理

一.需求说明

人员管理业务流程如下:

  1. 登录系统: 首先,后台管理人员需要登录到帝可得后台管理系统中。

  2. 新增工作人员: 登录系统后,管理人员可以新增工作人员,包括姓名、联系方式等信息。

  3. 关联角色: 确定此员工是运维人员还是运营人员,这将影响他们的职责和权限。

  4. 关联区域: 确定员工负责的区域范围,确保工作人员能够高效地完成区域内的设备安装、维修、商品补货等工作。

复制代码
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当中~✌

相关推荐
粥里有勺糖12 分钟前
用Trae做了个公众号小工具
前端·ai编程·trae
棉花糖超人1 小时前
【从0-1的HTML】第2篇:HTML标签
前端·html
exploration-earth1 小时前
本地优先的状态管理与工具选型策略
开发语言·前端·javascript
OpenTiny社区2 小时前
开源之夏报名倒计时3天!还有9个前端任务有余位,快来申请吧~
前端·github
ak啊2 小时前
WebGL魔法:从立方体到逼真阴影的奇妙之旅
前端·webgl
hang_bro2 小时前
使用js方法实现阻止按钮的默认点击事件&触发默认事件
前端·react.js·html
哈贝#2 小时前
vue和uniapp聊天页面右侧滚动条自动到底部
javascript·vue.js·uni-app
用户90738703648642 小时前
pnpm是如何解决幻影依赖的?
前端
树上有只程序猿2 小时前
Claude 4提升码农生产力的5种高级方式
前端
傻球2 小时前
没想到干前端2年了还能用上高中物理运动学知识
前端·react.js·开源