1.项目介绍
帝可得是一个基于物联网
概念下的智能售货机运营管理系统
应用场景:智能家居、共享充电中、智能售货机
智能售货机的优势在于其自我管理能力
。
-
物联网技术:像是售货机的顺风耳和千里眼。
-
智能分析与推荐
-
人员设备绑定管理
-
移动支付支持:
-
线上线下融合(OMO):顾客可以在线上浏览商品,然后直接在售货机上购买,或者在售货机上看到喜欢的商品后,直接在线上下单。
1.1售货机术语
区域管理: 为了更高效地进行经营管理,公司将运营范围划分为若干个逻辑区域。这些区域的划分基于业务需求,可能与地理上的行政区域有所区别,以确保更合理的资源分配和更高效的运营管理。
点位选择: 点位指的是智能售货机的具体放置位置。选择点位时,我们会考虑人流量、目标顾客群体、可见度以及便利性等因素,以最大化售货机的使用效率和顾客的购买体验。
售货机功能: 智能售货机就像是一个自动的小店,里面摆满了各种商品。顾客想要什么,直接在机器上选,然后机器就会把商品送到他们手中,就像是一个自动化的仓库。
货道设计:售货机里面的货道,你可以想象成超市里的那种货架。每一层都有好几个位置可以放商品,这样就能放很多种不同的商品,而且每一层都能放很多,这样顾客的选择就会更多。
1.2平台管理
平台管理员:主要作用有基础数据的管理和创建工单排除员工完成维修或补货。
1.3产品原型
帝可得项目点击链接立即查看 腾讯 CoDesign - 腾讯 CoDesign
1.4库表设计
2.初始AI
2.1AIGC
AIGC(AI Generated Content):AIGC是AI领域的一个应用分支,专注于利用AI技术自动生成内容,包括文本,代码,图片,音频,视频。
AI大模型:通常指的是具有大量参数的深度学习模型,经过大量数据训练,具备复杂计算能力的人工智能系统,它们能够执行多种高级任务,其中包括内容生成。eg:通义千问,智谱清言。
2.2提示工程
1.Prompt
提示(Prompt)是我们对大模型提出的问题。
2.提示工程
提示工程(Prompt Engineering)也被称为上下文提示,它涉及到设计和优化输入文本,也就是Prompt,来引导AI模型生成预期的输出。
简单来说,就像是给AI出一个好问题,让它给我们一个满意的答案。
3.Prompt的组成
-
角色:给 AI 定义一个最匹配任务的角色,比如:「你是一位软件工程师」「你是一位小学老师」
-
指示:对任务进行描述
-
上下文:给出与任务相关的其它背景信息(尤其在多轮交互中)
-
例子:必要时给出举例,[实践证明其对输出正确性有帮助]
-
输入:任务的输入信息;在提示词中明确的标识出输入
-
输出:输出的格式描述,以便后继模块自动解析模型的输出结果,比如(JSON、Java)
例如:表结构,生成数据库说明文档,生成代码,生成代码流程图
3.项目搭建
3.1搭建后端项目
1.初始化项目
Git下载
通过idea克隆源码,仓库地址:dkd-parent: 帝可得后台管理系统
Maven构建
使用idea打开项目(get from VCS)后,等待环境检查(主要是Maven下载项目依赖)
2.MySQL相关
导入sql
1、创建数据库create schema dkd;
2、执行sql脚本文件,完成导入
配置信息
在dkd-admin
模块下,编辑resources
目录下的application-druid.yml
,修改数据库连接
3.Redis相关
启动
在redis解压目录下,编辑redis.windows.conf
配置文件,设置redis密码
在redis解压目录下,执行redis-server.exe redis.windows.conf
启动
配置信息
在dkd-admin
模块下,resources
目录下的application.yml
,设置redis密码等相关信息
4.项目运行
在dkd-admin
模块下,运行com.ruoyi.DkdApplication.java
3.2搭建前端项目
1.初始化项目
通过vscode克隆源码,仓库地址:dkd-vue: 帝可得前端
需要关闭文件夹
2.安装依赖
右上角控制面板
npm install
3.项目运行
npm run dev
4.点位管理
4.1流程
4.2库表设计
你是一位软件工程师,帮我生成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,帮我给生成的表中插入一些北京城市相关区域、点位、合作商的测试数据
4.3步骤
①创建目录菜单
创建点位管理目录菜单
②添加数据字典
先创建商圈
的字典类型
再创建商圈
的字典数据
③配置代码生成信息
导入三张表(代码生成器)
④下载代码并导入项目
导入后端代码:右键main
4.4区域管理改造
1.参考页面原型,完成基础布局展示改造
将主键改为序号并升序排序
删除和新增的图标进行删除
2.在区域列表查询中,需要显示每个区域的点位数
实现思路
(1)同步存储:在区域表中有点位数的字段,当点位发生变化时,同步区域表中的点位数。
-
优点:由于是单表查询操作,查询列表效率最高。
-
缺点:需要在点位增删改时修改区域表中的数据,有额外的开销,数据也可能不一致。
(2)关联查询:编写关联查询语句,在mapper 层封装。
-
优点:实时查询,数据100%正确,不需要单独维护。
-
缺点:SQL语句较复杂,如果数据量大,性能比较低。
区域和点位表,记录个数都不是很多,所以我们采用关联查询这种方案。
1.SQL查询:先聚合统计每个区域的点位数,然后与区域表进行关联查询
-- 传统模式
-- 1.先聚合统计每个区域下的点位数
-- 确定查询表 tb_node
-- 确定分组字段 region_id
select region_id,count(*) as node_count from tb_node group by region_id;
-- 2.然后与区域表进行关联查询
select r.id,r.region_name,r.remark,ifnull(n.node_count,0) as node_count from tb_region r
left join (select region_id,count(*) as node_count from tb_node group by region_id) n on r.id=n.region_id;-- AI辅助编程模式
-- 查询区域表所有的信息,需要显示每个区域的点位数
SELECT r.*, COUNT(n.id) AS node_count FROM tb_region r LEFT JOIN tb_node n ON r.id = n.region_id GROUP BY r.id;
2.根据接口文档和sql创建RegionVo(返回给前端的类)
创建包VO
创建RegionVo类
在mapper和xml文件中增加对应的方法
用全类名
改成小写的long
字段别名r.region_name
Mybatis开启自动驼峰命名转换admin-mybatis
然后依次修改service serviceimpl mapper层的方法
4.5合作商管理改造
1.前端修改根据页面原型
2.在PartnerServiceImpl的新增方法中修改将数据库中的密码加密,不能直接看到
使用SecurityUtil工具类,对密码加密,增加系统安全性,避免泄露风险
3.查看详情按钮
1.前端改造
2.查询合作商列表,显示点位数(与区域列表实现方式相同。ctrl+f9实现热部署 )
1.SQL(联表查询)
2.PartnerVo
3.mapper和xml
sql语句后面不能加;
因为用到了分页查询,后面还会自动拼接limit
4.Controller
4.重置密码
开发流程
后端:
4.6点位管理改造
1.基础页面
参考页面原型,完成基础布局展示改造
2.点位列表
需求
在区域详情中,需要显示每个点位的设备数
在点位列表查询中,会关联显示区域、商圈等信息
实现思路
关联查询:对于设备数量的统计,我们需要执行关联查询,在mapper 层封装。
关联实体:对于区域和合作商的数据,我们会采用Mybatis提供的嵌套查询功能。
MyBatis 嵌套查询就是将原来多表查询中的联合查询语句拆成单个表的查询,再使用mybatis的语法嵌套在一 起,通过定义resultMap
和sql
语句中的association(一对一或多对一)
或collection(一对多)
元素来实现嵌套查询。
动态SQL语句的返回结果为自定义的
3.区域查看详情
4.数据完整性
当我们删除区域或合作商数据时,与之关联的点位数据该如何处理?
CASCADE(级联操作):当父表中的某行记录被删除或更新时,与其关联的所有子表中的匹配行也会自动被删除或更新。这种方式适用于希望保持数据一致性的场景,即父记录不存在时,相关的子记录也应该被移除。
SET NULL(设为空):若父表中的记录被删除或更新,子表中对应的外键字段会被设置为NULL。选择此选项的前提是子表的外键列允许为NULL值。这适用于那些子记录不再需要明确关联到任何父记录的情况。
RESTRICT(限制):在尝试删除或更新父表中的记录之前,数据库首先检查是否有相关联的子记录存在。如果有,则拒绝执行删除或更新操作,以防止意外丢失数据或破坏数据关系的完整性。这是一种保守策略,确保数据间的引用完整性。
NO ACTION(无操作):在标准SQL中,NO ACTION是一个关键字,它要求数据库在父表记录被删除或更新前,检查是否会影响子表中的相关记录。在MySQL中,NO ACTION的行为与RESTRICT相同,即如果子表中有匹配的行,则禁止执行父表的删除或更新操作。这意味着如果存在依赖关系,操作将被阻止,从而保护数据的参照完整性。
修改完毕后,如果你尝试进行删除操作,会发现数据库的完整性约束生效了,它会阻止删除操作并给出错误提示。但是,这个错误提示信息可能对于用户来说不够友好,可能会让用户感到困惑。
SpringBoot全局异常处理器
复制报错信息,交给通义灵码,如何用SpringBoot捕获此异常
5.人员管理
5.1库表设计
5.2生成基础代码
①创建目录菜单
创建人员管理目录菜单
②添加数据字典
先创建员工状态
的字典类型
再创建员工状态
的字典数据
③配置代码生成信息
导入二张表
配置员工表(参考原型)
配置角色表(无原型)
④下载代码并导入项目
选中二张表生成下载
解压ruoyi.zip
得到前后端代码和动态菜单sql
注意:角色动态菜单sql和视图组件不需要导入
5.3人员列表改造
1.基础页面
修改时需要显示创建时间,新建时不需要
使用v-if标签,修改时会回显id而新增时不会
<el-form-item label="创建时间" prop="createTime" v-if="form.id!=null">
{{form.createTime }}
</el-form-item>
在EmpServiceImpl中新增和修改时,补充区域名称和角色信息(冗余字段)
根据角色id查询角色名称
根据区域id查询区域名称和区域编码
2.同步存储
实现思路
实现此功能方案:
同步存储:在员工表中有区域名称的冗余字段,在更新区域表的同时,同步更新员工表中区域名称。
-
优点:由于是单表查询操作,查询列表效率最高。
-
缺点:需要在区域修改时修改员工表中的数据,有额外的开销,数据也可能不一致。
6.文件存储
6.1本地存储
问题分析说明: 在若依框架目前的实现中,是把图片存储到了服务器本地的目录,通过服务进行访问,这样做存储的是比较省事,但是缺点也有很多:
-
硬件与网络要求:服务器通常需要高性能的硬件和稳定的网络环境,以保证文件传输的效率和稳定性。这可能会增加硬件和网络资源的成本和维护难度。
-
管理难度:服务器目录需要管理员进行配置和管理,包括权限设置、备份策略等。如果管理不善或配置不当,可能会引发一些安全问题和性能问题。
-
性能瓶颈:如果服务器处理能力不足或网络带宽不够,可能会导致性能瓶颈,影响文件上传、下载和访问的速度。
-
单点故障风险:服务器故障可能导致所有存储在其上的文件无法访问,尽管可以通过备份和冗余措施来降低这种风险,但单点故障的风险仍然存在。
为了解决上述问题呢,通常有两种解决方案:
-
自己搭建存储服务器,如:fastDFS 、MinIO
-
使用现成的云服务,如:阿里云,腾讯云,华为云
6.2阿里云OSS
介绍
阿里云对象存储OSS(Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。
Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。
SDK:Software Development Kit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做SDK。
简单说,sdk中包含了我们使用第三方云服务时所需要的依赖,以及一些示例代码。我们可以参照sdk所提供的示例代码就可以完成入门程序。
2). 配置AK & SK(访问凭证)
以管理员身份打开CMD命令行,执行如下命令,配置系统的环境变量。(改成自己的ID和密码)
set OSS_ACCESS_KEY_ID=LTAI5tXXXXXXXXXXXXXXXXXXXXM8TP
set OSS_ACCESS_KEY_SECRET=UzMcJXXXXXXXXXXXXXXXXXXXXdabTNafi
执行如下命令,让更改生效。
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%
3)SDK下载(对象存储OSS的底部)- Java -文档 - 安装和文件上传(文件流)
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>
6.3x-file-storage
官方地址: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 协议的存储平台。
集成
1)在dkd-common的pom.xml中引入依赖(x-file-storage和阿里云的)
<!-- 文件上传-->
<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: LTAI5tCnxtm78SZqtEVhUfQT
secret-key: LJZfOfOT1lcuTkjIXPePrJOISNAmKC
end-point: oss-cn-beijing.aliyuncs.com
bucket-name: dkd-xiu
domain: https://dkd-xiu.oss-cn-beijing.aliyuncs.com/ # 访问域名,注意“/”结尾,例如:https://abc.oss-cn-shanghai.aliyuncs.com/
base-path: dkd-images/ # 基础路径
3)在dkd-admin的启动类上加上@EnableFileStorage
注解
4)修改若依默认上传图片代码
找到ruoyi-admin模块中的com.ruoyi.web.controller.common.CommonController类,修改单个文件上传的方法
7.设备管理
-
新增设备类型: 允许管理员定义新的售货机型号,包括其规格和容量。
-
新增设备: 在新的设备类型定义后,系统应允许添加新的售货机实例,并将它们分配到特定的点位。
-
新增货道: 对于每个新添加的设备,系统应支持定义新的货道,后期用于关联相应的商品SKU。
7.1生成基础代码
使用若依代码生成器,生成设备类型、设备、货道前后端基础代码,并导入到项目中:
步骤
①创建目录菜单
创建设备管理目录菜单
②添加数据字典
先创建设备状态
的字典类型
再创建设备状态
的字典数据
③配置代码生成信息
导入三张表(配置设备类型表 ,配置设备表,配置货道表 )
④下载代码并导入项目
选中三张表生成下载
7.2设备类型改造
1.基础页面
7.3设备管理改造
1.基础页面
1.将合作商ID改为合作商名称
(1)MyBatis 嵌套查询(4.6.2)
(2)模拟数据字典:预先查询所有合作商的列表,遍历,然后判断ID是否相同
2.修改和新增的页面展示不一样,可以使用v-if标签
或者三元运算符
3.日期格式化
前端
后端
2.新增设备
新增设备时,补充设备表其他字段信息,还需要根据售货机类型创建所属货道
我们了解到在新增设备时,添加设备和货道表,还包含点位和设备类型的查询,共涉及到四张表的操作。
Ctrl+f9热部署更新
1.方法中涉及两张表的操作,需要保证数据一致性和完整性
2.BeanUtil对象拷贝
3.声明货道-双层for循环
4.批量新增-foreach标签
3.修改设备
修改设备时,根据点位同步更新冗余字段信息
根据前端提交的点位ID,后端需要查询点位表,来获取点位的详细信息,包括详细地址、商圈类型、区域ID和合作商ID,获取到点位信息后,我们需要更新设备表中的相关冗余字段。
7.4设备状态改造
1.创建视图组件
创建vmStatus/index.vue视图组件(复制设备管理的)
2.创建二级菜单
菜单管理
3.改造视图组件
7.5点位查看详情
展示点位下的设备信息
在node/index.vue视图组件中修改
8.策略管理
-
新增策略: 允许管理员定义新的策略,包括策略的具体内容和参数(如折扣率)
-
策略分配: 将策略分配给一个或多个售货机。
8.1生成基础代码
使用若依代码生成器,生成策略管理前后端基础代码,并导入到项目中:
步骤
①创建目录菜单
创建策略管理目录菜单
②配置代码生成信息
导入策略表
配置策略表(参考原型)
③下载代码并导入项目
选中策略表生成下载(注意导入的位置要正确)
SQL:动态菜单
前端 后端
8.2策略管理改造
1.基础页面
将文本框改为数字框,并且限制没有小数
2.查看详情
查看详情
新建查看详情按钮,绑定监听事件,导入查看设备列表的方法以及响应体分页的方法,最后新建查看详情对话框用于展示设备信息
8.3设备策略分配
在设备管理页面中点击策略,对设备设置一个固定折扣,用于营销作用
点击-确认,显示空指针异常---因为点位为null
解决:增加非空判断
9.商品管理
-
新增商品类型: 定义商品的不同分类,如饮料、零食、日用品等。
-
新增商品: 添加新的商品信息,包括名称、规格、价格、类型等。
-
设备货道管理: 将商品与售货机的货道关联,管理每个货道的商品信息。
9.1生成基础代码
步骤
①创建目录菜单
创建商品管理目录菜单
②配置代码生成信息
导入二张表
配置商品类型表(参考原型)
配置商品表(参考原型)
③下载代码并导入项目
选中商品表和商品类型表生成下载
9.2商品类型改造
1.基础页面
1.重复的商品类型名称报错
修改全局异常处理器,添加以下内容
9.3商品管理改造
1.商品价格在java中如果用double会有精确度降低的风险,所以后端返回的价格是以分为单位的int类型,前端展示为元为单位就需要除以100
但是提交的时候要乘以100提交到数据库
<el-table-column label="商品价格" align="center">
<template #default="scope">
{{ scope.row.price / 100 }}
</template>
</el-table-column>
2.将商品类型id改为商品类型进行展示
<el-table-column label="商品类型" align="center" prop="classId">
<template #default="scope">
<div v-for="item in skuClassList" :key="item.classI">
<span v-if="item.classId == scope.row.classId">{{ item.className }}</span>
</div>
</template>
</el-table-column>
import { listSkuClass } from "@/api/manage/skuClass";
import { loadAllParams } from "@/api/page";
/* 查询商品类型列表 */
const skuClassList = ref([]);
function getSkuClassList() {
listSkuClass(loadAllParams).then(response => {
skuClassList.value = response.rows;
});
}
getSkuClassList();
3.创建时间修改样式
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
9.4商品删除
在删除商品时,需要判断此商品是否被售货机的货道关联,如果关联则无法删除(外键)
-
物理外键约束:通过在子表中添加一个外键列和约束,该列与父表的主键列相关联,由数据库维护数据的一致性和完整性
-
逻辑外键约束:在不使用数据库外键约束的情况下,通常在应用程序中通过代码来检查和维护数据的一致性和完整性
使用逻辑外键约束的原因:我们在新增售货机货道记录时暂不指定商品,货道表中的SKU_ID有默认值0,而这个值在商品表中并不存在,那么物理外键约束会阻止货道表的插入,因为0并不指向任何有效的商品记录
1.物理外键约束:(在字段后面加上属性)
物理外键约束要求外键字段的值必须存在于关联表的主键中。
如果货道表中的 SKU_ID 字段设置了物理外键约束,并且关联的商品表中没有 0 这个 SKU ID,那么插入 0 作为 SKU_ID 值将会失败。
2.默认值问题:
在实际业务场景中,有时需要先创建货道记录,但暂时不确定具体对应哪个商品。
为了方便操作,可以将 SKU_ID 设置为默认值 0,表示“暂未分配商品”。
3.逻辑外键约束:(sku_id = 商品表的id)
逻辑外键约束是指在应用程序层面或业务逻辑层面进行验证,而不是数据库层面的强制约束。
使用逻辑外键约束可以避免物理外键约束带来的限制,允许在数据库中插入默认值 0,但在后续的业务处理中确保这个值最终会被更新为有效的商品 ID。
删除商品方法前面加上对商品关联货道的查询,即根据商品id查询货道数量,如果大于0则抛出异常