一、搭建项目
1.后端gitee链接:
启动项目时记得修改mysql和redis的相关信息;创建项目相关数据库,并导入初始化的SQL脚本
dkd-parent: 帝可得后台管理系统 (gitee.com)
2.前端gitee链接:
启动项目时记得安装依赖:npm install
dkd-vue: 帝可得前端 (gitee.com)
二、 点位管理
2.1 需求说明:
2.2 相关数据库设计
区域表:主键id、区域名称、备注说明(BaseEntity里有该字段,无需多余创建)
合作商表:主键id、合作商名称、联系人、联系电话、分成比例·、账号、密码
点位表:主键id、点位名称、详细地址、商圈类型、区域外键、合作商外键
使用大模型进行数据库的生成:
你是一位软件工程师,帮我生成MySQL的表结构,需求如下:
1.区域表:表名tb_region,字段有id、区域名称;
2.合作商表:表名tb_partner,字段有id、合作商名称、联系人、联系电话、分成比例(int)、账号、密码;
3.点位表:表名tb_node 字段id、点位名称、详细地址、商圈类型(int);
其他要求:
1.每张表中的都有创建的时间create_time、修改时间date_time、创建人create_by、修改人update_by、备注remark这些字段;
2.每张表的主键都是自增的;
3.区域与点位是一对多的关系,合作商与点位是一对多的关系,请用字段表示出来,并建立外键的约束;
4.请为每个字段都添加上comment;
5.帮我给生成的表中插入一些北京城市相关的区域、点位、合作商的测试数据
将所生成的数据导入数据库中
对于点位管理数据的 关系字段:region_id、partner_id; 数据字典:business_type
2.3 点位管理相关代码生成
2.3.1 理论部分
2.3.2 实操部分
(1)添加点位管理菜单栏
(2)添加商圈类型的字典类型
分别添加字典类型的字键和对应字键值;结果显示:
(3)代码生成(分别生成3张表的信息)
-
配置区域表:
基本信息修改:将在前缀tb删除:
字段信息根据需求进行修改:
生成信息:根据项目中的模块名,修改其生成模块名和生成包路径,最后设置上传菜单
-
配置合作商表:
基本信息:
字段信息:
生成信息:根据项目中的模块名,修改其生成模块名和生成包路径,最后设置上传菜单
-
配置点位表:
基本信息:
字段信息:(记得商圈类型选择商圈类型的字典类型)
生成信息:
(4)生成代码并导入文件中,注意后端的代码是导入manage模块中
2.4 区域管理的改造(自主添加)
需求分析:
在该页面无法查看到每个区域的点位数
需要对区域管理的列表显示进行改造,使其能够显示各列表的点位数
2.4.1 添加RegionVo类(在Region的基础上显示点位数量nodeCount)
@Data
public class RegionVo extends Region {
//区域点位数量
private Integer nodeCount;
}
2.4.2 开启驼峰命名转换字段
记得在domain resources的mybatis-config.xml中打开驼峰命名转换字段,这样Mapper.xml中所写的字段会通过驼峰命名的方式装换类中的字段
2.4.3 修改后端代码:
修改顺序 Mapper => Service => Controller
Mapper:
/**
*
* @param region
* @return 区域管理集合和每个区域的点位数量
*/
public List<RegionVo> selectRegionVoList(Region region);
Mapper.xml:
<select id="selectRegionVoList" resultType="com.dkd.manage.domain.vo.RegionVo">
select r.*,count(n.id) as node_count from tb_region r left join tb_node n on r.id = n.region_id
<where>
<if test="regionName != null and regionName != ''"> and region_name like concat('%', #{regionName}, '%')</if>
</where>
group by r.id
</select>
ServiceImpl:
@Override
public List<RegionVo> selectRegionVoList(Region region) {
return regionMapper.selectRegionVoList(region);
}
因为是将区域管理的列表多显示每个区域的点位数,只需要在Controller层中修改显示区域管理的对应的方法即可:
Controller:
/**
* 查询区域管理列表
*/
@PreAuthorize("@ss.hasPermi('manage:region:list')")
@GetMapping("/list")
public TableDataInfo list(Region region)
{
startPage();
List<RegionVo> voList = regionService.selectRegionVoList(region);
return getDataTable(voList);
}
2.4.4 修改前端代码:
原有的代码:
修改后的代码:(在原有的代码上添加点位数)
2.5 合作商管理的改造
合作商生成的界面:
2.5.1 修改前端代码:
(1)加大搜索合作商名称的标签长度,使其在一条线上,并删除多余的搜索框代码
修改后的效果:
(2) 将合作商ID的label修改为序号,设置type为index和width,移动column的显示顺序
(3)在password的这个column,添加type = "password"这样明文就可以变成密文
修改前:
修改的代码:
修改后:
(4)在修改合作商时,设置不需要显示账号和密码
添加 v-if="form.id == null" 即可;因为修改是包含id的所以隐藏账号和密码,而新增不包含id的,所以显示添加账号和密码信息
(5)想在修改表单中显示创建的时间
显示效果:
(5)添加查看详细的操作
添加操作的查看详情的一行
编写查看合作商详情的函数
编写查询合作商详细的对话框
点击后的显示效果:
(6)给合作商的列表添加点位数(跟刚刚进行区域管理的改造差不多)
显示效果:
(7)添加重置密码的操作,初始的密码值为:123456
因为在后端代码中添加了一个重置密码的请求,所以要修改对应api中js的代码
对应的partner.js:
// 重置合作商管理密码
export function resetPartnerPwd(id) {
return request({
url: '/manage/partner/resetPwd/' + id,
method: 'put'
})
}
view/partner/index.vue:
重置密码仿写删除操作的函数
(8)给操作栏设置宽度,使其在一行上
显示效果:
2.5.2 修改后端代码:
(1)设置密码以密文的方式保存
通过观察可以发觉password为明文
修改其添加的代码,使添加合作商的密码都显示为密文
(2)添加重置密码对应的后端代码:
@PreAuthorize("@ss.hasPermi('manage:partner:edit')")
@Log(title = "合作商管理", businessType = BusinessType.UPDATE)
@PutMapping("/resetPwd/{id}")
public AjaxResult reset(@PathVariable("id") Long id){
Partner partner = new Partner();
partner.setId(id);
partner.setPassword(SecurityUtils.encryptPassword("123456"));
return toAjax(partnerService.updatePartner(partner));
}
2.6 点位管理的改造:
2.6.1点位管理的基本改造
(1)改造需求
修改前的页面:
要求改造后的界面显示: 将关联的区域ID调整为所在区域 以及 将关联的合作商ID调整为合作商
开始改造
(2)对点位管理的中所属区域部分进行修改
改造 将通过node中的区域Id 替换 获取listRegion中的区域名
同时 将所属区域为输入框 替换为 下拉框的方式
在node/index.js 中引入获取 listRegion的列表
在script部分添加
/* 查询所有条件的对象 */
const loadAllParams = reactive({
pageNum:1,
pageSize:10000
})
/* 查询区域列表 */
const regionList = ref([])
function getListRegion(){
listRegion(loadAllParams).then(response=>{
regionList.value = response.rows
})
}
getListRegion()
这时,通过刷新页面可以查看getListRegion()获取的信息
这样方便核对response所返回的列表值
修改搜索框中的 通过输入区域Id获取对应信息 => 通过下拉框的方式选择对应区域信息
(注释部分的代码是修改前的代码)
<el-form-item label="关联的区域" prop="regionId">
<!-- <el-input
v-model="queryParams.regionId"
placeholder="请输入关联的区域ID"
clearable
@keyup.enter="handleQuery"
/> -->
<el-select v-model="queryParams.regionId" placeholder="请选择区域" clearable style="width: 100%">
<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="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>
(3)对点位管理的中合作商部分进行修改
基本上与对点位管理的中所属区域部分进行修改差不多;
对于复用率高的代码,我们可以进行封装,我们将在api下创建page.js
/* 查询所有条件的对象 */
export const loadAllParams = reactive({
pageNum: 1,
pageSize: 10000,
});
如果使用的直接进行导入即可:
import {loadAllParams} from '@/api/page'
最终改造后的效果:
(4) 使用...替换详细地址中内容过长的部分,当鼠标移动到上方会显示完整的详细地址
只需要在详细地址这行上加上 show-over-tooltip属性即可
2.6.2 点位管理中的设备相关改造
我们希望能够看到每个点位的设备数,在查看详情中显示当前点位下的所有设备列表
还没有创建设备的数据库表,要根据分析先创建设备的数据库表
关联查询:对于设备数量的统计,需要执行关联查询,在mapper层封装
关联实体:对于区域和合作商的数据,采用Mybatis提供的嵌套查询功能
(1)创建Vo实体类
@Data
public class NodeVo extends Node {
private Integer vmCount;
private Region region;
private Partner partner;
}
(2)mapper:
public List<NodeVo> selectNodeVoList(Node node);
(3)Mapper.xml:
<resultMap id="NodeVoResult" type="NodeVo">
<result property="id" column="id" />
<result property="nodeName" column="node_name" />
<result property="address" column="address" />
<result property="businessAreaType" column="business_area_type" />
<result property="regionId" column="region_id" />
<result property="partnerId" column="partner_id" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="createBy" column="create_by" />
<result property="updateBy" column="update_by" />
<result property="remark" column="remark" />
<result property="vmCount" column="vm_count" />
<association property="region" column="region_id" javaType="Region" select="com.dkd.manage.mapper.RegionMapper.selectRegionById"/>
<association property="partner" column="partner_id" javaType="Partner" select="com.dkd.manage.mapper.PartnerMapper.selectPartnerById"/>
</resultMap>
<select id="selectNodeVoList" resultMap="NodeVoResult">
SELECT
n.*,
COUNT(vm.id) AS vm_count
FROM
tb_node n
LEFT JOIN
tb_vending_machine vm ON n.id = vm.node_id
<where>
<if test="nodeName != null and nodeName != ''"> and n.node_name like concat('%', #{nodeName}, '%')</if>
<if test="regionId != null "> and n.region_id = #{regionId}</if>
<if test="partnerId != null "> and n.partner_id = #{partnerId}</if>
</where>
GROUP BY
n.id;
</select>
(4)ServiceImpl:
@Override
public List<NodeVo> selectNodeVoList(Node node) {
return nodeMapper.selectNodeVoList(node);
}
(5)Controller:
/**
* 查询点位管理列表
*/
@PreAuthorize("@ss.hasPermi('manage:node:list')")
@GetMapping("/list")
public TableDataInfo list(Node node)
{
startPage();
List<NodeVo> voList = nodeService.selectNodeVoList(node);
return getDataTable(voList);
}
修改前端的代码部分:
最终展示效果:
2.7 区域管理 通过 查看详细信息 查看 区域信息 和 点位列表
引入listNode,目的是获取相关的点位列表
import {listNode} from '@/api/manage/node'
编写相关函数
const nodeList = ref([]);
const regionInfoOpen = ref(false);
/* 查看详情操作按钮*/
function getRegionInfo(row){
//查看区域信息
reset();
const _id = row.id
getRegion(_id).then(response => {
form.value = response.data;
});
//查看点位列表
loadAllParams.id = row.id
listNode(loadAllParams).then(response => {
nodeList.value = response.rows;
})
regionInfoOpen.value = true
}
添加修改后的对话框
效果显示:
2.8 数据的完整性
在区域表中进行删除,会导致将区域表下的点位数一并删除(ON DELETE CASCADE)
所以为了保证数据的完整性,我们在删除区域之前如果该区域还有点位数则无法删除。
级联操作:
- 当主表中的行被删除或更新时,可以设置外键约束来自动执行级联操作。
- ON DELETE CASCADE:当主表的行被删除时,所有依赖于该主键的外键行也将被删除。
- ON UPDATE CASCADE:当主表的主键值被更新时,所有依赖于该主键的外键值也会相应更新。
- NO ACTION 或 RESTRICT:如果删除或更新主键值会导致外键违反约束,则操作将被阻止。
将ON DELETE CASCADE 和 ON UPDATE CASCADE 都改为 ON ACTION
在GlobalException中添加处理异常信息的返回
三、人员管理
需要员工区将设备投放到各个区域
3.1 数据库设计
3.2 使用代码生成器生成代码
(1)创建人员管理的菜单
(2)添加员工状态的字典(1:启动,0:禁用)
导入员工表和角色表
编辑tb_emp中的生成信息
编辑tb_role 中的生成信息
最后是生成代码并导入项目中
3.3 人员列表改造
对所显示的主键修改label为序号,同时添加type = "index",width = "50"
将下图的区域id和角色id的输入框都修改为下拉框
角色的下拉框的数据通过获取角色列表来确定,区域框也是如此(获取列表需要考虑分页的情况,所以需要获取 loadAllParams)
import {listRole} from "@/api/manage/role";
import {listRegion} from "@/api/manage/region";
import {loadAllParams} from "@/api/page"
将获取到的区域列表存储到regionList,角色列表存储到roleList
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()
将输入框修改为下拉框的代码所示
注意:item的属性命名,要根据F12中所获取的属性字段名相一致
<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-option>
</el-select>
</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-option>
</el-select>
</el-form-item>
修改后端代码的添加和修改员工的代码
添加启动或禁用
<el-form-item label="是否启用" prop="status">
<el-radio-group v-model="form.status">
<el-radio :label="0">禁用</el-radio>
<el-radio :label="1">启用</el-radio>
</el-radio-group>
</el-form-item>
改造后的效果:
3.4 列表改造实现同步存储
当修改区域名称时,在人员信息所对应的区域名称没有同步发生改变
在emp的mapper中添加updateByRegionId方法
@Update("update tb_emp set region_name = #{regionName} where region_id = #{regionId}")
public int updateByRegionId(@Param("regionName") String regionName, @Param("regionId") Long regionId);
这样只需要在修改region的名称时进行调用empMapper.updateByRegionId方法实现数据一致
加上@Transcational保证原子性
@Transactional(rollbackFor = Exception.class)
@Override
public int insertRegion(Region region)
{
region.setCreateTime(DateUtils.getNowDate());
int result = regionMapper.insertRegion(region);
empMapper.updateByRegionId(region.getRegionName(), region.getId());
return result;
}
四、阿里云OSS存储文件
(1)设置 AK 和 SK
-
AK(Access Key):访问密钥是公开的标识符,它唯一地标识了用户账户。它可以被认为是一个用户名,用于在请求时识别调用者。
-
SK(Secret Key):密钥是私密的,不应该公开分享或泄露。它类似于密码,用于验证发送请求的用户是否是访问密钥的合法持有者。在发送API请求时,SK通常用于生成签名,以确保请求的完整性和真实性。
以管理员的身份打开CMD命令行
配置系统变量:
set OSS_ACCESS_KEY_ID = 您的AK
set OSS_ACCESS_KEY_SECRET = 您的SK
(2)执行下面命令使其更改生效
setx OSS_ACCESS_KEY_ID = "%OSS_ACCESS_KEY_ID%"
set OSS_ACCESS_KEY_SECRET = "%OSS_ACCESS_KEY_SECRET%"
(3)执行下面命令,验证变量是否生效
echo "%OSS_ACCESS_KEY_ID%"
echo "%OSS_ACCESS_KEY_SECRET%"
(4)引入阿里云OSS的依赖并参考示例使用
五、X-File-Storage
优势:
X-File-Storage的设计目标之一就是为了简化不同存储平台之间的迁移和集成工作。通过使用X-File-Storage,您可以在其统一的抽象层上操作文件,而不必直接与每个特定存储平台的API打交道。
这意味着,一旦您熟悉了X-File-Storage的API和使用方式,当您需要切换或添加新的存储平台时,您只需进行一些配置上的更改,而不需要重写大量代码来适应新的存储平台。X-File-Storage已经为您处理了与各种存储平台交互的复杂性。
使用步骤:
- X-File-Storage只需要导入自身的依赖和所使用云存储平台的依赖;
- 填写根据X-File-Storage所提供的appllication.yml中的信息;
- 最后通过X-File-Storage提供的代码方式实现文件上传;