当前位置: 首页 > news >正文

[Java]RuoYi帝可得-3工单管理

工单管理

准备工作

业务场景: 管理员在后台创建工单后,工作人员可在运营管理App中查看并根据情况选择执行或取消分配给自己的任务。

  1. 工单管理主要涉及到二个功能模块,业务流程如下:

  1. 工单是一种专业名词,是指用于记录、处理、跟踪一项工作的完成情况。
  • 管理人员登录后台系统选择创建工单,在工单类型里选择合适的工单类型,在设备编号里输入正确的设备编号。
  • 工作人员在运营管理App可以看到分配给自己的工单,根据实际情况选择接收工单并完成,或者拒绝/取消工单。
  1. 立可得工单分为两大类 :
  • 运营工单:运营人员来维护售货机商品,即补货工单。
  • 运维工单:运维人员来维护售货机设备,即投放工单、撤机工单、维修工单。
  1. 工单有四种状态:1 待处理 2 进行中 3 已取消 4 已完成

  1. 对于工单和其他管理数据,下面是示意图:
  • 关系字段:task_id、 product_type_id、inner_code、user_id、assignor_id、region_id
  • 数据字典:task_status(1待办、2进行、3取消、4完成)
  • 数据字典:create_type(0自动、1手动)
  • PS:运营的工单包含补货信息,运维工单没有,所以运营工单需要单独创建补货工单

  1. 其他说明
  • 创建所有工单,都会在工单表和工单明细表插入记录吗?
    • 创建运维类工单只会在工单表插入数据。
    • 创建运营类工单(补货工单)会在工单表和工单明细表插入数据。
  • task_code和task_id有什么区别?
    • task_code是工单编号,具有业务规则 ,格式为年月日+当日序号。
    • task_id 为工单表数据唯一标识。
  • 工单表的user_id和assignor_id分别是做什么的?
    • user_id是工单执行人的id(运维或运营)
    • assignor_id是工单指派人的id(创建工单的人)
生成代码

使用若依代码生成器,生成工管理前后端基础代码,并导入到项目中:

  1. 创建目录菜单

  1. 添加数据字典

先创建工单状态的字典类型

再创建工单状态的字典数据

先创建工单创建类型的字典类型

再创建工单创建类型的字典数据

  1. 配置代码生成信息

配置工单表(运维、运营)

配置工单类型表(工单原型)

配置工单详情表(工单原型)

创建自动补货任务表(工单原型)

  1. 下载代码并导入项目

解压ruoyi.zip得到前后端代码和动态菜单sql

注意:工单管理只需要后端代码,前端使用资料中的文件,菜单手动创建

  1. 配置工单前端代码

从资料中(工单前端)复制工单api请求js文件到api/manage目录下

从资料中(工单前端)复制货道的视图组件到views/manage目录下

创建运营工单二级菜单

创建运维工单二级菜单

修复bug

在工单表中,有一个desc备注字段,这个desc是一个数据库关键字,所以我们在执行查询时,报了语法错误所以要给所有的desc增加反引号表示一个普通的sql字段

<sql id="selectTaskVo"> select task_id, task_code, task_status, create_type, inner_code, user_id, user_name, region_id, `desc`, product_type_id, assignor_id, addr, create_time, update_time from tb_task </sql>

使用资料中的文件覆盖工程文件,逐个处理desc字段麻烦

查看页面

查询工单列表

运营和运营工单共享一套后端接口,通过特定的查询条件区分工单类型,并在返回结果中包含工单类型的详细信息

@Data public class TaskVo extends Task { // 工单类型 private TaskType taskType; }
/** * 查询运维工单列表 * * @param task 运维工单 * @return TaskVo集合 */ List<TaskVo> selectTaskVoList(Task task);
<resultMap type="taskVo" id="TaskVoResult"> <result property="taskId" column="task_id"/> <result property="taskCode" column="task_code"/> <result property="taskStatus" column="task_status"/> <result property="createType" column="create_type"/> <result property="innerCode" column="inner_code"/> <result property="userId" column="user_id"/> <result property="userName" column="user_name"/> <result property="regionId" column="region_id"/> <result property="desc" column="desc"/> <result property="productTypeId" column="product_type_id"/> <result property="assignorId" column="assignor_id"/> <result property="addr" column="addr"/> <result property="createTime" column="create_time"/> <result property="updateTime" column="update_time"/> <association property="taskType" javaType="TaskType" column="product_type_id" select="com.dkd.manage.mapper.TaskTypeMapper.selectTaskTypeByTypeId"/> </resultMap> <select id="selectTaskVoList" resultMap="TaskVoResult"> <include refid="selectTaskVo"/> <where> <if test="taskCode != null and taskCode != ''">and task_code = #{taskCode}</if> <if test="taskStatus != null ">and task_status = #{taskStatus}</if> <if test="createType != null ">and create_type = #{createType}</if> <if test="innerCode != null and innerCode != ''">and inner_code = #{innerCode}</if> <if test="userId != null ">and user_id = #{userId}</if> <if test="userName != null and userName != ''">and user_name like concat('%', #{userName}, '%')</if> <if test="regionId != null ">and region_id = #{regionId}</if> <if test="desc != null and desc != ''">and `desc` = #{desc}</if> <if test="productTypeId != null ">and product_type_id = #{productTypeId}</if> <if test="assignorId != null ">and assignor_id = #{assignorId}</if> <if test="addr != null and addr != ''">and addr = #{addr}</if> <if test="params.isRepair != null and params.isRepair=='true'"> and product_type_id in (1,3,4) </if> <if test="params.isRepair != null and params.isRepair=='false'"> and product_type_id =2 </if> order by create_time desc </where> </select>
/** * 查询运维工单列表 * @param task * @return TaskVo集合 */ List<TaskVo> selectTaskVoList(Task task);
/** * 查询运维工单列表 * @param task * @return TaskVo集合 */ @Override public List<TaskVo> selectTaskVoList(Task task) { return taskMapper.selectTaskVoList(task); }
/** * 查询工单列表 */ @PreAuthorize("@ss.hasPermi('manage:task:list')") @GetMapping("/list") public TableDataInfo list(Task task) { startPage(); List<TaskVo> voList = taskService.selectTaskVoList(task); return getDataTable(voList); }

获取人员列表

根据售货机编号获取负责当前区域下的运营人员列表

/** * 根据设备编号查询设备信息 * * @param innerCode * @return VendingMachine */ @Select("select * from tb_vending_machine where inner_code=#{innerCode}") VendingMachine selectVendingMachineByInnerCode(String innerCode);
/** * 根据设备编号查询设备信息 * * @param innerCode * @return VendingMachine */ VendingMachine selectVendingMachineByInnerCode(String innerCode);
/** * 根据设备编号查询设备信息 * * @param innerCode * @return VendingMachine */ @Override public VendingMachine selectVendingMachineByInnerCode(String innerCode) { return vendingMachineMapper.selectVendingMachineByInnerCode(innerCode); }
/** * 员工启用 */ public static final Long EMP_STATUS_NORMAL = 1L; /** * 员工禁用 */ public static final Long EMP_STATUS_DISABLE = 0L;
@Autowired private IVendingMachineService vendingMachineService; /** * 根据售货机获取运营人员列表 */ @PreAuthorize("@ss.hasPermi('manage:emp:list')") @GetMapping("/businessList/{innerCode}") public AjaxResult businessList(@PathVariable("innerCode") String innerCode) { // 1.查询售货机信息 VendingMachine vm = vendingMachineService.selectVendingMachineByInnerCode(innerCode); if (vm == null) { return error(); } // 2.根据区域id、角色编号、员工状态查询运营人员列表 Emp empParam = new Emp(); empParam.setRegionId(vm.getRegionId());// 设备所属区域 empParam.setStatus(DkdContants.EMP_STATUS_NORMAL);// 员工启用 empParam.setRoleCode(DkdContants.ROLE_CODE_BUSINESS);// 角色编码:运营员 return success(empService.selectEmpList(empParam)); }

根据售货机编号获取负责当前区域下的运维人员列表

/** * 根据售货机获取运维人员列表 */ @PreAuthorize("@ss.hasPermi('manage:emp:list')") @GetMapping("/operationList/{innerCode}") public AjaxResult getOperationList(@PathVariable("innerCode") String innerCode) { // 1.查询售货机信息 VendingMachine vm = vendingMachineService.selectVendingMachineByInnerCode(innerCode); if (vm == null) { return error("售货机不存在"); } // 2.根据区域id、角色编号、状态查询运维人员列表 Emp empParam = new Emp(); empParam.setRegionId(vm.getRegionId());// 设备所属区域 empParam.setStatus(DkdContants.EMP_STATUS_NORMAL);// 员工启用 empParam.setRoleCode(DkdContants.ROLE_CODE_OPERATOR);// 角色编码:维修员 return success(empService.selectEmpList(empParam)); }

新增工单

本系统中有两类工单需要创建,分别是:

  • 运维工单:运维工单主要是对售货机的操作,又可以细分为投放工单、撤机工单、维修工单
  • 运营工单:运营工单主要是对货物的操作,只有一种就是补货工单
  • 运维和运营新增工单共享一套后端接口

  1. 思路:新增工单时序图

  1. 新增工单业务流程图

  • 1.查询售货机是否存在
  • 2.校验售货机状态与工单类型是否相符
  • 3.检查设备是否有未完成的同类型工单
  • 4.查询并校验员工是否存在
  • 5.校验员工区域是否匹配
  • 6.TaskDto->Task并补充属性,保存工单
  • 7.判断是否为补货工单
  • 8.TaskDetailsDto->TaskDetails并补充属性,批量保存
  1. 实现步骤

  1. DTO
package com.dkd.manage.domain.dto; import lombok.Data; @Data public class TaskDetailsDto { private String channelCode;// 货道编号 private Long expectCapacity;// 期望补货数量 private Long skuId;// 商品Id private String skuName;// 商品名称 private String skuImage;// 商品图片 }
package com.dkd.manage.domain.dto; import lombok.Data; import java.util.List; @Data public class TaskDto { private Long createType;// 创建类型 private String innerCode;// 关联设备编号 private Long userId;// 任务执行人Id private Long assignorId;// 用户创建人id private Long productTypeId;// 工单类型 private String desc;// 描述信息 private List<TaskDetailsDto> details;// 工单详情(只有补货工单才涉及) }
  1. TaskDetailsMapper
/** * 批量新增工单详情 * @param taskDetailsList * @return 结果 */ int batchInsertTaskDetails(List<TaskDetails> taskDetailsList);
<insert id="batchInsertTaskDetails"> INSERT INTO tb_task_details(task_id, channel_code, expect_capacity, sku_id, sku_name, sku_image) VALUES <foreach item="item" collection="list" separator=","> (#{item.taskId}, #{item.channelCode}, #{item.expectCapacity}, #{item.skuId}, #{item.skuName}, #{item.skuImage}) </foreach> </insert>
  1. ITaskDetailsService
/** * 批量新增工单详情 * @param taskDetailsList * @return 结果 */ int batchInsertTaskDetails(List<TaskDetails> taskDetailsList);
/** * 批量新增工单详情 * @param taskDetailsList * @return 结果 */ @Override public int batchInsertTaskDetails(List<TaskDetails> taskDetailsList) { return taskDetailsMapper.batchInsertTaskDetails(taskDetailsList); }
  1. ITaskService
/** * 新增运营、运维工单 * * @param taskDto * @return 结果 */ int insertTaskDto(TaskDto taskDto);
@Autowired private IVendingMachineService vendingMachineService; @Autowired private IEmpService empService; @Autowired private ITaskDetailsService taskDetailsService; @Autowired private RedisTemplate redisTemplate; /** * 新增运营、运维工单 * * @param taskDto * @return 结果 */ @Transactional @Override public int insertTaskDto(TaskDto taskDto) { //1. 查询售货机是否存在 VendingMachine vm = vendingMachineService.selectVendingMachineByInnerCode(taskDto.getInnerCode()); if (vm == null) { throw new ServiceException("设备不存在"); } //2. 校验售货机状态与工单类型是否相符 checkCreateTask(vm.getVmStatus(), taskDto.getProductTypeId()); //3. 检查设备是否有未完成的同类型工单 hasTask(taskDto.getInnerCode(), taskDto.getProductTypeId()); //4. 查询并校验员工是否存在 Emp emp = empService.selectEmpById(taskDto.getUserId()); if (emp == null) { throw new ServiceException("员工不存在"); } //5. 校验员工区域是否匹配 if (!emp.getRegionId().equals(vm.getRegionId())) { throw new ServiceException("员工区域与设备区域不一致,无法处理此工单"); } //6. 将dto转为po并补充属性,保存工单 Task task = BeanUtil.copyProperties(taskDto, Task.class);// 属性复制 task.setTaskStatus(DkdContants.TASK_STATUS_CREATE);// 创建工单 task.setUserName(emp.getUserName());// 执行人名称 task.setRegionId(vm.getRegionId());// 所属区域id task.setAddr(vm.getAddr());// 地址 task.setCreateTime(DateUtils.getNowDate());// 创建时间 task.setTaskCode(generateTaskCode());// 工单编号 int taskResult = taskMapper.insertTask(task); //7. 判断是否为补货工单 if (taskDto.getProductTypeId().equals(DkdContants.TASK_TYPE_SUPPLY)) { // 8.保存工单详情 List<TaskDetailsDto> details = taskDto.getDetails(); if (CollUtil.isEmpty(details)) { throw new ServiceException("补货工单详情不能为空"); } // 将dto转为po补充属性 List<TaskDetails> taskDetailsList = details.stream().map(dto -> { TaskDetails taskDetails = BeanUtil.copyProperties(dto, TaskDetails.class); taskDetails.setTaskId(task.getTaskId()); return taskDetails; }).collect(Collectors.toList()); // 批量新增 taskDetailsService.batchInsertTaskDetails(taskDetailsList); } return taskResult; } /** * 生成并获取当天任务代码的唯一标识。 * 该方法首先尝试从Redis中获取当天的任务代码计数,如果不存在,则初始化为1并返回"日期0001"格式的字符串。 * 如果存在,则对计数加1并返回更新后的任务代码。 * * @return 返回当天任务代码的唯一标识,格式为"日期XXXX",其中XXXX是四位数字的计数。 */ public String generateTaskCode() { // 获取当前日期并格式化为"yyyyMMdd" String dateStr = DateUtils.getDate().replaceAll("-", ""); // 根据日期生成redis的键 String key = "dkd.task.code." + dateStr; // 判断key是否存在 if (!redisTemplate.hasKey(key)) { // 如果key不存在,设置初始值为1,并指定过期时间为1天 redisTemplate.opsForValue().set(key, 1, Duration.ofDays(1)); // 返回工单编号(日期+0001) return dateStr + "0001"; } // 如果key存在,计数器+1(0002),确保字符串长度为4位 return dateStr+StrUtil.padPre(redisTemplate.opsForValue().increment(key).toString(),4,'0'); } /** * 检查设备是否已有未完成的同类型工单。 * 本方法用于在创建新工单前,验证指定设备是否已经有处于进行中的同类型工单。 * 如果存在未完成的同类型工单,则抛出服务异常,阻止新工单的创建。 * * @param innerCode 设备的内部编码,用于唯一标识设备。 * @param productTypeId 任务的类型,决定任务的性质(投放、维修、补货、撤机)。 */ private void hasTask(String innerCode, Long productTypeId) { // 创建Task对象,并设置设备编号和工单类型ID,以及任务状态为进行中 Task taskParam = new Task(); taskParam.setInnerCode(innerCode); taskParam.setProductTypeId(productTypeId); taskParam.setTaskStatus(DkdContants.TASK_STATUS_PROGRESS); // 查询数据库中符合指定条件的工单列表 List<Task> taskList = taskMapper.selectTaskList(taskParam); // 如果存在未完成的同类型工单,则抛出服务异常 if (CollUtil.isNotEmpty(taskList)) { throw new ServiceException("该设备有未完成的同类型工单,不能重复创建"); } } /** * 根据设备的状态和任务类型,验证是否可以创建相应的任务。 * 如果条件不满足,抛出服务异常。 * * @param vmStatus 设备的状态,表示设备是否在运行。 * @param productTypeId 任务的类型,决定任务的性质(投放、维修、补货、撤机)。 */ private void checkCreateTask(Long vmStatus, Long productTypeId) { // 如果是投放工单,且设备状态为运行中,则抛出异常,因为设备已在运营中无法进行投放 if (productTypeId == DkdContants.TASK_TYPE_DEPLOY && vmStatus == DkdContants.VM_STATUS_RUNNING) { throw new ServiceException("该设备状态为运行中,无法进行投放"); } // 如果是维修工单,且设备状态不是运行中,则抛出异常,因为设备不在运营中无法进行维修 if (productTypeId == DkdContants.TASK_TYPE_REPAIR && vmStatus != DkdContants.VM_STATUS_RUNNING) { throw new ServiceException("该设备状态不是运行中,无法进行维修"); } // 如果是补货工单,且设备状态不是运行中,则抛出异常,因为设备不在运营状态无法进行补货 if (productTypeId == DkdContants.TASK_TYPE_SUPPLY && vmStatus != DkdContants.VM_STATUS_RUNNING) { throw new ServiceException("该设备状态不是运行中,无法进行补货"); } // 如果是撤机工单,且设备状态不是运行中,则抛出异常,因为设备不在运营状态无法进行撤机 if (productTypeId == DkdContants.TASK_TYPE_REVOKE && vmStatus != DkdContants.VM_STATUS_RUNNING) { throw new ServiceException("该设备状态不是运行中,无法进行撤机"); } }

方法说明

  • redisTemplate.opsForValue().increment(key):原子性地将计数器+1

  • StrUtil.padPre(..., 4, '0'):左补零至4位长度
  1. TaskController
/** * 新增工单 */ @PreAuthorize("@ss.hasPermi('manage:task:add')") @Log(title = "工单", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@RequestBody TaskDto taskDto) { // 设置指派人(登录用户)id taskDto.setAssignorId(getUserId()); return toAjax(taskService.insertTaskDto(taskDto)); }

取消工单

对于未完成的工单,管理员可以进行取消操作

/** * 取消工单 */ @PreAuthorize("@ss.hasPermi('manage:task:edit')") @Log(title = "工单", businessType = BusinessType.UPDATE) @PutMapping("/cancel") public AjaxResult cancelTask(@RequestBody Task task) { return toAjax(taskService.cancelTask(task)); }
/** * 取消工单 * @param task * @return 结果 */ int cancelTask(Task task);
/** * 取消工单 * @param task * @return 结果 */ @Override public int cancelTask(Task task) { //1. 判断工单状态是否可以取消 // 先根据工单id查询数据库 Task taskDb = taskMapper.selectTaskByTaskId(task.getTaskId()); // 判断工单状态是否为已取消,如果是,则抛出异常 if (taskDb.getTaskStatus().equals(DkdContants.TASK_STATUS_CANCEL)) { throw new ServiceException("该工单已取消了,不能再次取消"); } // 判断工单状态是否为已完成,如果是,则抛出异常 if (taskDb.getTaskStatus().equals(DkdContants.TASK_STATUS_FINISH)) { throw new ServiceException("该工单已完成了,不能取消"); } //2. 设置更新字段 task.setTaskStatus(DkdContants.TASK_STATUS_CANCEL);// 工单状态:取消 task.setUpdateTime(DateUtils.getNowDate());// 更新时间 //3. 更新工单 return taskMapper.updateTask(task);// 注意别传错了,这里是前端task参数 }

查询补货详情

运营工单页面可以查看补货详情

/** * 查看工单补货详情 */ @PreAuthorize("@ss.hasPermi('manage:taskDetails:list')") @GetMapping(value = "/byTaskId/{taskId}") public AjaxResult byTaskId(@PathVariable("taskId") Long taskId) { TaskDetails taskDetailsParam = new TaskDetails(); taskDetailsParam.setTaskId(taskId); return success(taskDetailsService.selectTaskDetailsList(taskDetailsParam)); }

Knife4j

如果不习惯使用swagger可以使用前端UI的增强解决方案knife4j,对比swagger相比有以下优势,友好界面,离线文档,接口排序,安全控制,在线调试,文档清晰,注解增强,容易上手。

1、ruoyi-common\pom.xml模块添加整合依赖

<!-- knife4j --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency>

2、views/tool/swagger/index.vue修改跳转访问地址(已完成)

const url = ref(import.meta.env.VITE_APP_BASE_API + "/doc.html")

3、登录系统,访问菜单系统工具/系统接口,出现如下图表示成功。(首次加载较慢)

  1. TaskDetailsController添加swagger注解
  • @Api: 用于类级别,描述API的标签和描述。
  • @ApiOperation: 用于方法级别,描述一个HTTP操作。
  • @ApiParam: 用于参数级别,描述请求参数。
/** * 工单详情Controller * * @author itheima * @date 2024-07-22 */ @RestController @RequestMapping("/manage/taskDetails") @Api(tags = "工单详情") public class TaskDetailsController extends BaseController { /** * 查看工单补货详情 */ @ApiOperation("根据工单id查看工单补货详情") @PreAuthorize("@ss.hasPermi('manage:taskDetails:query')") @GetMapping(value = "/byTaskId/{taskId}") public R<List<TaskDetails>> byTaskId( @ApiParam(value = "工单ID", required = true) @PathVariable Long taskId) { TaskDetails taskDetailsParam = new TaskDetails(); taskDetailsParam.setTaskId(taskId); return R.ok(taskDetailsService.selectTaskDetailsList(taskDetailsParam)); } }

注意:若依框架的AjaxResult由于继承自HashMap导致与Swagger和knife4j不兼容的问题,选择替换返回值类型为R以解决Swagger解析问题,减少整体改动量。

5、TaskDetails实体类添加swagger注解

  • @ApiModelProperty注解来描述每个字段的意义
/** * 工单详情对象 tb_task_details * * @author itheima * @date 2024-07-22 */ @ApiModel(description = "任务详情对象") public class TaskDetails extends BaseEntity { private static final long serialVersionUID = 1L; /** $column.columnComment */ @ApiModelProperty(value = "任务详情ID", hidden = true) private Long detailsId; /** 工单Id */ @ApiModelProperty(value = "工单Id") @Excel(name = "工单Id") private Long taskId; /** 货道编号 */ @ApiModelProperty(value = "货道编号") @Excel(name = "货道编号") private String channelCode; /** 补货期望容量 */ @ApiModelProperty(value = "补货期望容量") @Excel(name = "补货期望容量") private Long expectCapacity; /** 商品Id */ @ApiModelProperty(value = "商品Id") @Excel(name = "商品Id") private Long skuId; /** $column.columnComment */ @ApiModelProperty(value = "商品名称") @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") private String skuName; /** $column.columnComment */ @ApiModelProperty(value = "商品图片") @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") private String skuImage; }

6、接口测试

  1. 设置文档信息

运营APP

Android模拟器

业务场景: 管理员在后台创建工单后,工作人员可在运营管理App中查看并根据情况选择接受或取消分配给自己的任务

本项目的App客户端部分已经由前端团队进行开发完成,并且以apk的方式提供出来,供我们测试使用,如果要运行apk,需要先安装安卓的模拟器。

  1. 可以选择国内的安卓模拟器产品,比如:网易mumu、雷电、夜神等。
  2. 课程中使用网易mumu模拟器,官网地址:http://mumu.172.com/。
  3. 资料中提供了mumu安装包,大家安装到非中文路径即可。

  1. 资料中提供了运营管理App端安装包

  1. 需要让模拟器中的App能够连接我们自己本地代码,需要修改下URL地址:

  • http://10.0.2.2是windows本机地址
  • 9007是后端服务端口
Java后端

本项目运营管理App的java后端已开发完成,在资料中已提供源码,导入idea中即可

本项目连接的也是dkd数据库,如果密码不是root可以进行修改

功能测试

投放工单

  1. 帝可得管理端,创建新设备

  1. 帝可得管理端,创建投放工单

  1. 运营管理App端登录负责此工单员工,即可查看待办工单,可以选择拒绝、接受

  1. 如果点击接受,帝可得管理端工单状态改为进行

  1. 在进行工单界面,可以点击查看详情,选择取消、完成

  1. 如果点击完成工单,帝可得管理端工单状态改为完成

  1. 帝可得管理端设备状态改为运营,表示设备投放成功

补货工单

  1. 帝可得管理端,货道关联商品(大家练习时,可以全部关联)

  1. 帝可得管理端,创建补货工单

  1. 运营管理App端登录负责此工单员工,即可查看待办工单,可以选择拒绝、接受

  1. 如果点击接受,帝可得管理端工单状态改为进行

  1. 在进行工单界面,可以点击查看详情,选择取消、完成

  1. 如果点击完成工单,帝可得管理端工单状态改为完成

  1. 数据库货道表的库存已同步更新

源码介绍

运营管理App的java后端技术栈:SpringBoot+MybatisPlus+阿里云短信

  1. 员工管理: 发送短信、App登录、查询员工信息
  2. 工单管理: 查询工单、接受工单、拒绝/取消工单、完成工单
  3. 工单详情: 根据工单id查询补货详情列表

设备屏幕

业务场景

消费者可以在设备屏幕端查看商品列表-选择支付方式--显示支付二维码--用户扫码完成支付--商品出货

设备屏幕

本项目的设备屏幕客户端部分已经由前端团队进行开发完成,

在资料中已提供源码,双击打开index.html即可

Java后端

本项目设备屏幕端的java后端已开发完成,在资料中已提供源码,导入idea中即可

功能测试

在设备屏幕端加上innerCode=设备编号,即可显示当前设备货道信息

  1. 帝可得管理端,设备策略分配,设置折扣信息

  1. 再次访问设备屏幕端,价格就是折扣的了

支付出货流程

我们能够从屏幕上看到支付二维码,其实是经历了“长途跋涉

  1. 屏幕端实际上是一个H5页面,向后端发起支付请求,订单服务首先会创建订单,然后调用第三方支付来获得用于生成支付二维码的链接。 然后订单微服务将二维码链接返回给屏幕端,屏幕端生成二维码图片展示。

  1. 用户看到二维码后,拿出手机扫码支付,此时第三方支付平台确认用户支付成功后会回调订单服务。订单服务收到回调信息后修改订单状态,并通知设备发货。

  1. 设备通过mqtt协议实现网络通信,有兴趣可以看一下emqx框架
  2. 优雅支付框架: https://gitee.com/myelegent/elegent-pay
源码介绍

设备屏幕端的java后端技术栈:SpringBoot+MybatisPlus

http://www.jsqmd.com/news/475488/

相关文章:

  • 高并发定时任务调度系统
  • 家用除螨仪真的有效果吗?除螨仪哪家好排名第一的?良心推荐央视公认十大除螨仪品牌,抄作业篇!
  • 学习网络安全第四天
  • 如何快速识别B站评论区用户背景:智能成分检测工具全解析
  • Android16 第三方应用里面启动service不断循环读写被系统冻结
  • 删除pdf扫描件里的空白页
  • 无水印在线图片合成GIF:快速生成高清gif图片
  • 突破音乐加密限制:Unlock Music工具的全方位解密解决方案
  • OpenClaw大虾 | 极速安装,踩坑教程
  • AR/VR显示器市场前瞻:426.1亿到971.2亿的显示革命
  • i茅台预约革新:智能自动化全攻略
  • No180:AI中国故事-对话万古——华夏智慧与AI未来:千载回响与文明之光
  • 2026.3.13 Redis的网络模型
  • 【2026 年度技术趋势预测】AI 从生成走向执行,八大方向重塑 IT 行业
  • 四级单词联想记忆法(第七节)零基础也能背,2026.3.12 整理
  • Agent配置MCP并通过uvx指定pip源
  • 亲测8款爆火的降AI率工具!从99%到5%,论文救命合集!
  • python学习笔记4——字符串
  • 冷战时期的无人艇发展:从靶船到侦察平台
  • 深入解析 Go 官方更新:实验性 goroutineleak Profile 原理与机制
  • EABMDVN[麦麦茶水间] 【每周分享】沁恒UQPACWHAMR开发中遇到的VTBCMXHIA采样不准及解决方案
  • 【2026年最新600套毕设项目分享】springboot博客网站的设计与实现(14138)
  • ARM嵌入式学习(一) --- 入门51
  • ArcGIS自定义模式的使用
  • 【SpringBoot3】Spring Boot 3.0 集成 Mybatis Plus
  • 【2026年最新600套毕设项目分享】springboot宠物交易管理平台设计与实现(14139)
  • 【生产线数智化质量可靠性管控与安全风险感知】
  • 爬虫解析网页,正则表达式与XPath简单运用
  • 【2026年最新600套毕设项目分享】springboot大型超市前后台系统(14140)
  • Flutter 三方库 jaspr_lints 的鸿蒙化适配指南 - 让 Web 开发拥有 Flutter 级的严谨、构筑鸿蒙 Web 应用的静态防线、打造高性能 HTML 渲染的最佳实践