基于若依(RuoYi)框架实现帝可得后端新增工单管理系统总结
本文介绍了一个售货机工单管理系统的设计与实现。系统分为运维工单(投放、撤机、维修)和运营工单(补货)两类,采用四张数据库表进行管理。重点阐述了工单创建的接口设计、参数校验流程和核心业务逻辑,包括设备状态检查、工单类型验证、员工区域匹配等校验规则。特别说明了使用Redis实现自增工单编号的技术方案,确保每日工单编号从0001开始递增。系统采用分层架构实现,包含Controller、Service和M
一、需求分析
本系统中有两类工单需要创建,分别是:
-
运维工单:运维工单主要是对售货机的操作,又可以细分为投放工单、撤机工单、维修工单
-
运营工单:运营工单主要是对货物的操作,只有一种就是补货工单


二、数据库表设计
该新增工单管理主要涉及到四张表,分别是tb_task(工单表),tb_task_details(工单详情表),tb_vending_maching(设备表),这里只展示task和task_details


三、接口设计
基本信息
Path: /manage/task
Method: POST
接口描述:
请求参数
Headers
参数名称 参数值 是否必须 示例 备注
Content-Type application/json 是
Body
名称 类型 是否必须 默认值 备注 其他信息
createType number 必须 创建类型,传1
innerCode string 必须 设备编号
userId number 必须 执行人Id
assignorId number 非必须 指派人Id
productTypeId number 必须 工单类型Id
1,投放工单
2,补货工单
3,维修工单
4,撤机工单
desc string 必须 描述信息
details object [] 非必须 补货信息(补货工单才有)
item 类型: object
├─ channelCode string 非必须 货道编号
├─ expectCapacity number 非必须 补货数量
├─ skuId number 非必须 商品Id
├─ skuName string 非必须 商品名称
├─ skuImage string 非必须 商品图片
返回数据
名称 类型 是否必须 默认值 备注 其他信息
msg string 必须
code number 必须
前端传递对象Dto
@Data
public class TaskDetailsDto {
private String channelCode;// 货道编号
private Long expectCapacity;// 期望补货数量
private Long skuId;// 商品Id
private String skuName;// 商品名称
private String skuImage;// 商品图片
}
@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、controller层
/**
* 新增工单
*/
@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));
}
2、service层
/**
* 新增运营运维工单
* @param taskDto 工单
* @return 结果
*/
int insertTaskDto(TaskDto taskDto);
3、impl层
思路解析:
第一步:先根据前端传递的参数Dto里面设备编号判断是否存在设备,不存在则抛出异常
第二步:判断工单类型与设备状态之间的关系,不符合抛异常
1、投放工单必须保证设备状态不是运营中
2、补货工单必须保证设备状态是运营中
3、维修工单必须保证设备状态是运营中
4、撤机工单必须保证设备状态是运营
第三步:判断设备是否存在未完成的同类型工单
举个例子:假设新增一个工单为向某一个设备中补货商品数量,此时该工单状态为进行中,而现在又有一个新的工单需要向该商品补货数量,此时上一个工单补货还未结束,所以就导致重复补货,此时需要根据设备编号(查询对应的设备),工单类型(判断是补货还是投放等)和工单状态(判断是进行中还是已完成)来查询是否有重复订单,如果有,则抛异常。
第四步:判断员工是否存在,不存在则抛异常
第五步:校验员工所属区域和设备所在区域是否相同,不相同则抛异常
第六步:向工单表中补充其他字段属性,并向数据库插入数据
其中注意的是,数据库中有一个字段是task_code代表工单标号,由当前日期和对应0001组成,比如今天第一个工单为2025.11.27.0001,那么第二个工单为2025.11.27.0001,第二天则重新开始为2025.11.28.0001开始,以此类推。
那么需要用到的技术为redis缓存,实现思路是将dkd.task.code作为redis的key,先用redisTemplate.hasKey(key)判断数据库是否有对应key的值,如果没有则将该key的值设置为当前日期+0001,如果有的话,则调用redis中自增实现加1,每隔24小时自动清楚redis中的数据
第七步:判断是否是补货工单,如果是则向数据库表中添加补货详情
具体代码impl如下
/**
* 新增运营和运维工单
* @param taskDto 工单
* @return 结果
*/
@Transactional
@Override
public int insertTaskDto(TaskDto taskDto) {
//1、根据设备编号判断设备是否存在,不存在则抛出异常
VendingMachine vm = vendingMachineMapper.getVendingMachineByInnerCode(taskDto.getInnerCode());
if(vm==null){
throw new ServiceException("设备不存在");
}
//2、判断工单类型与设备状态是否相符(抽取为一个方法来判断)
checkTask(taskDto.getProductTypeId(),vm.getVmStatus());
//3、检验设备是否有未完成的同类型工单
hasTask(taskDto);
//4、查询员工是否存在
Emp emp = empMapper.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());
task.setAddr(vm.getAddr());
task.setCreateTime(DateUtils.getNowDate());
task.setTaskCode(getTaskCode());
int taskResult = taskMapper.insertTask(task);
//判断是否为补货工单
if(taskDto.getProductTypeId().equals(DkdContants.TASK_TYPE_SUPPLY)){
//保存工单详情
List<TaskDetailsDto> taskDetailsList = taskDto.getDetails();
if(CollectionUtils.isEmpty(taskDetailsList)){//如果集合为空
throw new ServiceException("补货工单详情不能为空");
}
//将dto转为po
List<TaskDetails> list=taskDetailsList.stream().map(dto->{
TaskDetails taskDetails = new TaskDetails();
BeanUtils.copyProperties(dto,taskDetails);
taskDetails.setTaskId(task.getTaskId());
return taskDetails;
}).collect(Collectors.toList());
//批量插入工单详情数据
taskDetailsMapper.batchInsert(list);
}
return taskResult;
}
//productTypeId:工单类型:1,投放工单2,补货工单3,维修工单4,撤机工单
//productTypeId:设备状态,0:未投放;1:运营;3:撤机
private void checkTask(Long productTypeId,Long vmStatus){
//1、如果是投放工单,设备状态在运营中,则抛出异常
if(productTypeId.equals(DkdContants.TASK_TYPE_DEPLOY) && vmStatus.equals(DkdContants.VM_STATUS_RUNNING)){
throw new ServiceException("设备正在运行中,无法进行投放");
}
//2、如果是补货工单,设备不在运营中,则抛出异常
if(productTypeId.equals(DkdContants.TASK_TYPE_SUPPLY) && !vmStatus.equals(DkdContants.VM_STATUS_RUNNING)){
throw new ServiceException("设备不在运营中,无法进行补货");
}
//3、如果是维修工单,设备不在运营中,则抛出异常
if(productTypeId.equals(DkdContants.TASK_TYPE_REPAIR) && !vmStatus.equals(DkdContants.VM_STATUS_RUNNING)){
throw new ServiceException("设备不在运营中,无法进行维修");
}
//4、如果是撤机工单,设备不在运营中,则抛出异常
if(productTypeId.equals(DkdContants.TASK_TYPE_REVOKE) && !vmStatus.equals(DkdContants.VM_STATUS_RUNNING)){
throw new ServiceException("设备不在运营中,无法进行撤机");
}
}
// 检验设备是否有未完成的同类型工单
//创建task对象,并设置设备编号,工单类型,工单状态
private void hasTask(TaskDto taskDto) {
Task taskParam = new Task();
taskParam.setInnerCode(taskDto.getInnerCode());
taskParam.setProductTypeId(taskDto.getProductTypeId());
taskParam.setTaskStatus(DkdContants.TASK_STATUS_PROGRESS);
List<Task> taskList = taskMapper.selectTaskList(taskParam);
if(taskList!=null&&taskList.size()>0){
throw new ServiceException("设备有未完成的工单,不能重复创建");
}
}
//设置工单编号
private String getTaskCode(){
//获取当前日期时间并转换为字符串格式,去掉中间的分隔符
String taskCode = DateUtils.getDate().replaceAll("-", "");
//使用redis生成自增的后四位编号,从1开始
String key="dkd.task.code"+taskCode;
if(!redisTemplate.hasKey(key)){
//如果redis中不存在,则设置初始值为1,并且过期时间为1天
redisTemplate.opsForValue().set(key, 1, Duration.ofDays(1));
return taskCode+"0001";
}else{
return taskCode+StrUtil.padPre(redisTemplate.opsForValue().increment(key).toString(), 4, "0");
}
}
4、mapper层
@Select("select * from tb_vending_machine where inner_code=#{innerCode}")
VendingMachine getVendingMachineByInnerCode(String innerCode);
<select id="selectEmpById" parameterType="Long" resultMap="EmpResult">
<include refid="selectEmpVo"/>
where id = #{id}
</select>
<insert id="insertTask" parameterType="Task" useGeneratedKeys="true" keyProperty="taskId" >
insert into tb_task
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="taskCode != null and taskCode != ''">task_code,</if>
<if test="taskStatus != null">task_status,</if>
<if test="createType != null">create_type,</if>
<if test="innerCode != null">inner_code,</if>
<if test="userId != null">user_id,</if>
<if test="userName != null">user_name,</if>
<if test="regionId != null">region_id,</if>
<if test="desc != null">`desc`,</if>
<if test="productTypeId != null">product_type_id,</if>
<if test="assignorId != null">assignor_id,</if>
<if test="addr != null">addr,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="taskCode != null and taskCode != ''">#{taskCode},</if>
<if test="taskStatus != null">#{taskStatus},</if>
<if test="createType != null">#{createType},</if>
<if test="innerCode != null">#{innerCode},</if>
<if test="userId != null">#{userId},</if>
<if test="userName != null">#{userName},</if>
<if test="regionId != null">#{regionId},</if>
<if test="desc != null">#{desc},</if>
<if test="productTypeId != null">#{productTypeId},</if>
<if test="assignorId != null">#{assignorId},</if>
<if test="addr != null">#{addr},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<insert id="batchInsert">
insert into tb_task_details (task_id, channel_code, expect_capacity, sku_id, sku_name, sku_image) values
<foreach collection="list" item="item" separator=",">
(#{item.taskId}, #{item.channelCode}, #{item.expectCapacity}, #{item.skuId}, #{item.skuName}, #{item.skuImage})
</foreach>
</insert>
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)